-
Notifications
You must be signed in to change notification settings - Fork 38
Add CCTP rebalancing to RFQ relayer #2073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
3a73d21
6664fd9
91017b7
f7d56f2
cbf01e7
512fb30
536f227
1e9e1d6
708eab0
eabdda0
1f7c3a3
44be2b3
d2acc79
4ad03dc
7c0cf7e
fda6f90
4db711f
06e8e57
6b38ba5
90a63e6
6c70e96
3d561b0
e5868de
eca3b63
383ece0
0095c7c
58c8a7f
5aeb2c5
baab066
752763b
ab90a63
989c2d5
73e027d
a4e308c
03891fb
c9ca663
81cfc79
3a6ea21
42f24ce
ed841c8
628733d
efaf3bd
1e7175f
5c99c21
8fcfbcb
88de62a
ce32aa9
819dd14
5f4486b
c8f2c67
b7dd419
53e8129
2e33da3
45c2ad9
ba58876
4febe40
000549f
ceb8479
19dc080
085ac5e
782b869
6195502
07dbaeb
7a9a30b
f2d7ab8
cd760c2
4055168
7ff3b2a
d91525a
ecbdc0e
edeeb0e
5743f58
d8deade
e1296a4
edae452
ab2a9ab
996a82b
93a1e04
b0f9d22
3285fe6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import ( | |
| "github.com/synapsecns/sanguine/core" | ||
| "github.com/synapsecns/sanguine/core/metrics" | ||
| "github.com/synapsecns/sanguine/ethergo/submitter" | ||
| "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" | ||
| "github.com/synapsecns/sanguine/services/rfq/contracts/ierc20" | ||
| "github.com/synapsecns/sanguine/services/rfq/relayer/chain" | ||
| "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" | ||
|
|
@@ -39,9 +40,12 @@ type Manager interface { | |
| // GetCommittableBalances gets the total balances committable for all tracked tokens. | ||
| GetCommittableBalances(ctx context.Context, options ...BalanceFetchArgOption) (map[int]map[common.Address]*big.Int, error) | ||
| // ApproveAllTokens approves all tokens for the relayer address. | ||
| ApproveAllTokens(ctx context.Context, submitter submitter.TransactionSubmitter) error | ||
| ApproveAllTokens(ctx context.Context) error | ||
| // HasSufficientGas checks if there is sufficient gas for a given route. | ||
| HasSufficientGas(ctx context.Context, origin, dest int) (bool, error) | ||
| // Rebalance checks whether a given token should be rebalanced, and | ||
| // executes the rebalance if necessary. | ||
| Rebalance(ctx context.Context, chainID int, token common.Address) error | ||
|
Comment on lines
+49
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure
Comment on lines
+49
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Would you like assistance in creating unit tests for the
Comment on lines
+37
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The addition of the Would you like assistance in creating unit tests for these methods? |
||
| } | ||
|
|
||
| type inventoryManagerImpl struct { | ||
|
|
@@ -59,7 +63,12 @@ type inventoryManagerImpl struct { | |
| relayerAddress common.Address | ||
| // chainClient is an omnirpc client | ||
| chainClient submitter.ClientFetcher | ||
| db reldb.Service | ||
| // txSubmitter is the transaction submitter | ||
| txSubmitter submitter.TransactionSubmitter | ||
| // cctpContracts is the map of cctp contracts (used for rebalancing) | ||
| cctpContracts map[int]*cctp.SynapseCCTP | ||
| // db is the database | ||
| db reldb.Service | ||
| } | ||
|
|
||
| // GetCommittableBalance gets the committable balances. | ||
|
|
@@ -122,6 +131,8 @@ type tokenMetadata struct { | |
| decimals uint8 | ||
| startAllowance *big.Int | ||
| isGasToken bool | ||
| chainID int | ||
| addr common.Address | ||
| } | ||
|
|
||
| var ( | ||
|
|
@@ -135,12 +146,15 @@ var ( | |
| const defaultPollPeriod = 5 | ||
|
|
||
| // NewInventoryManager creates a list of tokens we should use. | ||
| func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, db reldb.Service) (Manager, error) { | ||
| // TODO: too many args here. | ||
| func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetcher, handler metrics.Handler, cfg relconfig.Config, relayer common.Address, txSubmitter submitter.TransactionSubmitter, db reldb.Service) (Manager, error) { | ||
| i := inventoryManagerImpl{ | ||
| relayerAddress: relayer, | ||
| handler: handler, | ||
| cfg: cfg, | ||
| chainClient: clientFetcher, | ||
| txSubmitter: txSubmitter, | ||
| cctpContracts: make(map[int]*cctp.SynapseCCTP), | ||
| db: db, | ||
| } | ||
|
|
||
|
|
@@ -173,7 +187,8 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc | |
| const maxBatchSize = 10 | ||
|
|
||
| // ApproveAllTokens approves all checks if allowance is set and if not approves. | ||
| func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context, submitter submitter.TransactionSubmitter) error { | ||
| // nolint:gocognit,nestif | ||
| func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { | ||
| i.mux.RLock() | ||
| defer i.mux.RUnlock() | ||
|
|
||
|
|
@@ -188,22 +203,43 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context, submitter s | |
| if address != chain.EthAddress && token.startAllowance.Cmp(big.NewInt(0)) == 0 { | ||
| chainID := chainID // capture func literal | ||
| address := address // capture func literal | ||
| // init an approval in submitter. Note: in the case where submitter hasn't finished from last boot, this will double submit approvals unfortanutely | ||
| _, err = submitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { | ||
| erc20, err := ierc20.NewIERC20(address, backendClient) | ||
|
|
||
| erc20, err := ierc20.NewIERC20(address, backendClient) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get erc20: %w", err) | ||
| } | ||
|
|
||
| // init an approval for RFQ bridge in submitter. Note: in the case where submitter hasn't finished from last boot, this will double submit approvals unfortanutely | ||
| _, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { | ||
| rfqAddr, err := i.cfg.GetRFQAddress(chainID) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not get erc20: %w", err) | ||
| return nil, fmt.Errorf("could not get rfq address: %w", err) | ||
| } | ||
| tx, err = erc20.Approve(transactor, common.HexToAddress(rfqAddr), abi.MaxInt256) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not approve rfq: %w", err) | ||
| } | ||
| return tx, nil | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("could not submit RFQ approval: %w", err) | ||
| } | ||
|
|
||
| approveAmount, err := erc20.Approve(transactor, common.HexToAddress(i.cfg.Chains[chainID].Bridge), abi.MaxInt256) | ||
| // approve CCTP bridge, if configured | ||
| _, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { | ||
| cctpAddr, err := i.cfg.GetCCTPAddress(chainID) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not approve: %w", err) | ||
| return nil, fmt.Errorf("could not get cctp address: %w", err) | ||
| } | ||
| tx, err = erc20.Approve(transactor, common.HexToAddress(cctpAddr), abi.MaxInt256) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not approve cctp: %w", err) | ||
| } | ||
|
|
||
| return approveAmount, nil | ||
| return tx, nil | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("could not submit approval: %w", err) | ||
| return fmt.Errorf("could not submit CCTP approval: %w", err) | ||
| } | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The updated logic in Consider enhancing error logging with more context, such as the chain ID and token address, to improve traceability in case of failures.
Comment on lines
227
to
299
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The enhanced Consider refactoring the
Comment on lines
266
to
299
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The updated Consider refactoring the
Comment on lines
267
to
299
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The - // nolint:gocognit,nestif,cyclop
+ // Consider refactoring to improve readability and maintainability. |
||
|
|
@@ -230,6 +266,154 @@ func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, des | |
| return sufficient, nil | ||
| } | ||
|
|
||
| // Rebalance checks whether a given token should be rebalanced, and executes the rebalance if necessary. | ||
| // Note that if there are multiple tokens whose balance is below the maintenance balance, only the lowest balance | ||
| // will be rebalanced. | ||
| func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token common.Address) error { | ||
| method, err := i.cfg.GetRebalanceMethod(chainID, token.Hex()) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get rebalance method: %w", err) | ||
| } | ||
| if method == relconfig.RebalanceMethodNone { | ||
| return nil | ||
| } | ||
|
|
||
| err = i.refreshBalances(ctx) | ||
| if err != nil { | ||
| return fmt.Errorf("could not refresh balances: %w", err) | ||
| } | ||
|
|
||
| rebalance, err := i.getRebalance(chainID, token) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get rebalance: %w", err) | ||
| } | ||
| if rebalance == nil { | ||
| return nil | ||
| } | ||
|
|
||
| //nolint:exhaustive | ||
| switch method { | ||
| case relconfig.RebalanceMethodCCTP: | ||
| return i.rebalanceCCTP(ctx, rebalance) | ||
| case relconfig.RebalanceMethodNative: | ||
| return fmt.Errorf("native rebalance method not implemented") | ||
| default: | ||
| return fmt.Errorf("unknown rebalance method: %s", method) | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Consider replacing the placeholders in the
Comment on lines
+296
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Add more comprehensive tests for the
Comment on lines
+336
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure
Comment on lines
+346
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Would you like assistance in creating unit tests for the
Comment on lines
+346
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Would you like assistance in creating additional unit tests for the
Comment on lines
+347
to
+402
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Would you like assistance in creating unit tests for the |
||
|
|
||
| // rebalanceData contains metadata for a rebalance action. | ||
| type rebalanceData struct { | ||
| origin int | ||
| dest int | ||
| originMetadata *tokenMetadata | ||
| destMetadata *tokenMetadata | ||
| amount *big.Int | ||
| } | ||
|
|
||
| func (i *inventoryManagerImpl) getRebalance(chainID int, token common.Address) (rebalance *rebalanceData, err error) { | ||
| maintenancePct, err := i.cfg.GetMaintenanceBalancePct(chainID, token.Hex()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not get maintenance pct: %w", err) | ||
| } | ||
|
|
||
| // get token metadata | ||
| var rebalanceTokenData *tokenMetadata | ||
| for address, tokenData := range i.tokens[chainID] { | ||
| if address == token { | ||
| rebalanceTokenData = tokenData | ||
| break | ||
| } | ||
| } | ||
|
|
||
| // get total balance for given token across all chains | ||
| totalBalance := big.NewInt(0) | ||
| for _, tokenMap := range i.tokens { | ||
| for _, tokenData := range tokenMap { | ||
| if tokenData.name == rebalanceTokenData.name { | ||
| totalBalance.Add(totalBalance, tokenData.balance) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // check if any balances are below maintenance threshold | ||
| var minTokenData, maxTokenData *tokenMetadata | ||
| for _, tokenMap := range i.tokens { | ||
| for _, tokenData := range tokenMap { | ||
| if tokenData.name == rebalanceTokenData.name { | ||
| if minTokenData == nil || tokenData.balance.Cmp(minTokenData.balance) < 0 { | ||
| minTokenData = tokenData | ||
| } | ||
| if maxTokenData == nil || tokenData.balance.Cmp(maxTokenData.balance) > 0 { | ||
| maxTokenData = tokenData | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // get the initialPct for the origin chain | ||
| initialPct, err := i.cfg.GetInitialBalancePct(maxTokenData.chainID, maxTokenData.addr.Hex()) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not get initial pct: %w", err) | ||
| } | ||
|
|
||
| // check if the minimum balance is below the threshold and trigger rebalance | ||
| maintenanceThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(maintenancePct)).Int(nil) | ||
| if minTokenData.balance.Cmp(maintenanceThresh) < 0 { | ||
| initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct)).Int(nil) | ||
| amount := new(big.Int).Sub(maxTokenData.balance, initialThresh) | ||
| rebalance = &rebalanceData{ | ||
| origin: maxTokenData.chainID, | ||
| dest: minTokenData.chainID, | ||
| originMetadata: maxTokenData, | ||
| destMetadata: minTokenData, | ||
| amount: amount, | ||
| } | ||
| } | ||
| return rebalance, nil | ||
| } | ||
|
|
||
| func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *rebalanceData) (err error) { | ||
| // fetch the corresponding CCTP contract | ||
| contract, ok := i.cctpContracts[rebalance.dest] | ||
| if !ok { | ||
| contractAddr, err := i.cfg.GetCCTPAddress(rebalance.origin) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get cctp address: %w", err) | ||
| } | ||
| chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.origin))) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get chain client: %w", err) | ||
| } | ||
| contract, err = cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get cctp: %w", err) | ||
| } | ||
| i.cctpContracts[rebalance.dest] = contract | ||
| } | ||
|
|
||
| // perform rebalance by calling sendCircleToken() | ||
| _, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.originMetadata.chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { | ||
| tx, err = contract.SendCircleToken( | ||
| transactor, | ||
| i.relayerAddress, | ||
| big.NewInt(int64(rebalance.destMetadata.chainID)), | ||
| rebalance.originMetadata.addr, | ||
| rebalance.amount, | ||
| 0, // TODO: inspect | ||
| []byte{}, // TODO: inspect | ||
| ) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("could not send circle token: %w", err) | ||
| } | ||
| return tx, nil | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("could not submit CCTP rebalance: %w", err) | ||
| } | ||
| return nil | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Consider replacing the placeholders in the |
||
| } | ||
|
Comment on lines
+270
to
+494
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation of the Would you like assistance in creating unit tests for the |
||
|
|
||
| // initializes tokens converts the configuration into a data structure we can use to determine inventory | ||
| // it gets metadata like name, decimals, etc once and exports these to prometheus for ease of debugging. | ||
| func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg relconfig.Config) (err error) { | ||
|
|
@@ -274,6 +458,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r | |
| } | ||
| rtoken := &tokenMetadata{ | ||
| isGasToken: tokenName == nativeToken, | ||
| chainID: chainID, | ||
| } | ||
|
|
||
| var token common.Address | ||
|
|
@@ -283,6 +468,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r | |
| token = common.HexToAddress(tokenCfg.Address) | ||
| } | ||
| i.tokens[chainID][token] = rtoken | ||
| rtoken.addr = token | ||
|
|
||
| // requires non-nil pointer | ||
| rtoken.balance = new(big.Int) | ||
|
|
@@ -294,11 +480,15 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r | |
| rtoken.balance = i.gasBalances[chainID] | ||
| // TODO: start allowance? | ||
| } else { | ||
| rfqAddr, err := cfg.GetRFQAddress(chainID) | ||
| if err != nil { | ||
| return fmt.Errorf("could not get rfq address: %w", err) | ||
| } | ||
| deferredCalls[chainID] = append(deferredCalls[chainID], | ||
| eth.CallFunc(funcBalanceOf, token, i.relayerAddress).Returns(rtoken.balance), | ||
| eth.CallFunc(funcDecimals, token).Returns(&rtoken.decimals), | ||
| eth.CallFunc(funcName, token).Returns(&rtoken.name), | ||
| eth.CallFunc(funcAllowance, token, i.relayerAddress, common.HexToAddress(i.cfg.Chains[chainID].Bridge)).Returns(rtoken.startAllowance), | ||
| eth.CallFunc(funcAllowance, token, i.relayerAddress, common.HexToAddress(rfqAddr)).Returns(rtoken.startAllowance), | ||
| ) | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.