From 3a73d2104680d6f2754358d1b132256de7645d5e Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 11:22:47 -0600 Subject: [PATCH 01/75] Feat: relconfig Bridge -> RFQAddress, add CCTPAddress --- services/rfq/e2e/setup_test.go | 4 +- services/rfq/relayer/inventory/manager.go | 12 +++++- services/rfq/relayer/quoter/quoter.go | 17 ++++----- services/rfq/relayer/relapi/server.go | 4 +- services/rfq/relayer/relapi/suite_test.go | 4 +- services/rfq/relayer/relconfig/config.go | 6 ++- services/rfq/relayer/relconfig/config_test.go | 37 ++++++++++++++----- services/rfq/relayer/relconfig/getters.go | 22 +++++++++-- services/rfq/relayer/service/relayer.go | 10 +++-- services/rfq/relayer/service/statushandler.go | 6 ++- services/rfq/relayer/service/suite_test.go | 4 +- 11 files changed, 86 insertions(+), 40 deletions(-) diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index 7b4207003b..c4a25ac4ca 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -197,7 +197,7 @@ func (i *IntegrationSuite) setupRelayer() { // generated ex-post facto Chains: map[int]relconfig.ChainConfig{ originBackendChainID: { - Bridge: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String(), + RFQAddress: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String(), Confirmations: 0, Tokens: map[string]relconfig.TokenConfig{ "ETH": { @@ -209,7 +209,7 @@ func (i *IntegrationSuite) setupRelayer() { NativeToken: "ETH", }, destBackendChainID: { - Bridge: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(), + RFQAddress: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(), Confirmations: 0, Tokens: map[string]relconfig.TokenConfig{ "ETH": { diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 7e4fc35f17..2618ab82ea 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -195,7 +195,11 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context, submitter s return nil, fmt.Errorf("could not get erc20: %w", err) } - approveAmount, err := erc20.Approve(transactor, common.HexToAddress(i.cfg.Chains[chainID].Bridge), abi.MaxInt256) + rfqAddr, err := i.cfg.GetRFQAddress(chainID) + if err != nil { + return nil, fmt.Errorf("could not get rfq address: %w", err) + } + approveAmount, err := erc20.Approve(transactor, common.HexToAddress(rfqAddr), abi.MaxInt256) if err != nil { return nil, fmt.Errorf("could not approve: %w", err) } @@ -294,11 +298,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), ) } diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index 0a7dce1f9b..efab9e2915 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -238,10 +238,9 @@ func (m *Manager) prepareAndSubmitQuotes(ctx context.Context, inv map[int]map[co // We can do this by looking at the quotableTokens map, and finding the key that matches the destination chain token. // Generates quotes for a given chain ID, address, and balance. func (m *Manager) generateQuotes(ctx context.Context, chainID int, address common.Address, balance *big.Int) ([]model.PutQuoteRequest, error) { - - destChainCfg, ok := m.config.Chains[chainID] - if !ok { - return nil, fmt.Errorf("error getting chain config for destination chain ID %d", chainID) + destRFQAddr, err := m.config.GetRFQAddress(chainID) + if err != nil { + return nil, fmt.Errorf("error getting destination RFQ address: %w", err) } destTokenID := fmt.Sprintf("%d-%s", chainID, address.Hex()) @@ -277,9 +276,9 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo if err != nil { return nil, fmt.Errorf("error getting total fee: %w", err) } - originChainCfg, ok := m.config.Chains[origin] - if !ok { - return nil, fmt.Errorf("error getting chain config for origin chain ID %d", origin) + originRFQAddr, err := m.config.GetRFQAddress(origin) + if err != nil { + return nil, fmt.Errorf("error getting RFQ address: %w", err) } // Build the quote @@ -295,8 +294,8 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo DestAmount: destAmount.String(), MaxOriginAmount: quoteAmount.String(), FixedFee: fee.String(), - OriginFastBridgeAddress: originChainCfg.Bridge, - DestFastBridgeAddress: destChainCfg.Bridge, + OriginFastBridgeAddress: originRFQAddr, + DestFastBridgeAddress: destRFQAddr, } quotes = append(quotes, quote) } diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go index 7be4fed499..aea0865112 100644 --- a/services/rfq/relayer/relapi/server.go +++ b/services/rfq/relayer/relapi/server.go @@ -59,11 +59,11 @@ func NewRelayerAPI( if err != nil { return nil, fmt.Errorf("could not create omnirpc client: %w", err) } - chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(chainCfg.Bridge), handler) + chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(chainCfg.RFQAddress), handler) if err != nil { return nil, fmt.Errorf("could not get chain listener: %w", err) } - chains[uint32(chainID)], err = chain.NewChain(ctx, chainClient, common.HexToAddress(chainCfg.Bridge), chainListener, submitter) + chains[uint32(chainID)], err = chain.NewChain(ctx, chainClient, common.HexToAddress(chainCfg.RFQAddress), chainListener, submitter) if err != nil { return nil, fmt.Errorf("could not create chain: %w", err) } diff --git a/services/rfq/relayer/relapi/suite_test.go b/services/rfq/relayer/relapi/suite_test.go index 3522f96596..879d682db4 100644 --- a/services/rfq/relayer/relapi/suite_test.go +++ b/services/rfq/relayer/relapi/suite_test.go @@ -79,10 +79,10 @@ func (c *RelayerServerSuite) SetupTest() { testConfig := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ int(c.originChainID): { - Bridge: ethFastBridgeAddress.Hex(), + RFQAddress: ethFastBridgeAddress.Hex(), }, int(c.destChainID): { - Bridge: arbFastBridgeAddress.Hex(), + RFQAddress: arbFastBridgeAddress.Hex(), }, }, RelayerAPIPort: strconv.Itoa(port), diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index c3cf3a6d49..ad868f2346 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -49,8 +49,10 @@ type Config struct { // ChainConfig represents the configuration for a chain. type ChainConfig struct { - // Bridge is the bridge confirmation count. - Bridge string `yaml:"address"` + // Bridge is the rfq bridge contract address. + RFQAddress string `yaml:"rfq_address"` + // CCTPAddress is the cctp contract address. + CCTPAddress string `yaml:"cctp_address"` // Confirmations is the number of required confirmations Confirmations uint64 `yaml:"confirmations"` // Tokens is a map of token ID -> token config. diff --git a/services/rfq/relayer/relconfig/config_test.go b/services/rfq/relayer/relconfig/config_test.go index 163413ddbb..e13d1592c8 100644 --- a/services/rfq/relayer/relconfig/config_test.go +++ b/services/rfq/relayer/relconfig/config_test.go @@ -14,7 +14,8 @@ func TestGetters(t *testing.T) { cfgWithBase := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { - Bridge: "0x123", + RFQAddress: "0x123", + CCTPAddress: "0x456", Confirmations: 1, NativeToken: "MATIC", DeadlineBufferSeconds: 10, @@ -30,7 +31,8 @@ func TestGetters(t *testing.T) { }, }, BaseChainConfig: relconfig.ChainConfig{ - Bridge: "0x1234", + RFQAddress: "0x1234", + CCTPAddress: "0x456", Confirmations: 2, NativeToken: "ARB", DeadlineBufferSeconds: 11, @@ -48,7 +50,8 @@ func TestGetters(t *testing.T) { cfg := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { - Bridge: "0x123", + RFQAddress: "0x123", + CCTPAddress: "0x456", Confirmations: 1, NativeToken: "MATIC", DeadlineBufferSeconds: 10, @@ -65,18 +68,32 @@ func TestGetters(t *testing.T) { }, } - t.Run("GetBridge", func(t *testing.T) { - defaultVal, err := cfg.GetBridge(badChainID) + t.Run("GetRFQAddress", func(t *testing.T) { + defaultVal, err := cfg.GetRFQAddress(badChainID) assert.NoError(t, err) - assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.Bridge) + assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.RFQAddress) - baseVal, err := cfgWithBase.GetBridge(badChainID) + baseVal, err := cfgWithBase.GetRFQAddress(badChainID) assert.NoError(t, err) - assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.Bridge) + assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.RFQAddress) - chainVal, err := cfgWithBase.GetBridge(chainID) + chainVal, err := cfgWithBase.GetRFQAddress(chainID) assert.NoError(t, err) - assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].Bridge) + assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].RFQAddress) + }) + + t.Run("GetCCTPAddress", func(t *testing.T) { + defaultVal, err := cfg.GetCCTPAddress(badChainID) + assert.NoError(t, err) + assert.Equal(t, defaultVal, relconfig.DefaultChainConfig.CCTPAddress) + + baseVal, err := cfgWithBase.GetCCTPAddress(badChainID) + assert.NoError(t, err) + assert.Equal(t, baseVal, cfgWithBase.BaseChainConfig.CCTPAddress) + + chainVal, err := cfgWithBase.GetCCTPAddress(chainID) + assert.NoError(t, err) + assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].CCTPAddress) }) t.Run("GetConfirmations", func(t *testing.T) { diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index 747a5483a0..1887ddad83 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -87,16 +87,30 @@ func isNonZero(value interface{}) bool { return reflect.ValueOf(value).Interface() != reflect.Zero(reflect.TypeOf(value)).Interface() } -// GetBridge returns the Bridge for the given chainID. -func (c Config) GetBridge(chainID int) (value string, err error) { - rawValue, err := c.getChainConfigValue(chainID, "Bridge") +// GetRFQAddress returns the RFQ address for the given chainID. +func (c Config) GetRFQAddress(chainID int) (value string, err error) { + rawValue, err := c.getChainConfigValue(chainID, "RFQAddress") if err != nil { return value, err } value, ok := rawValue.(string) if !ok { - return value, fmt.Errorf("failed to cast Bridge to string") + return value, fmt.Errorf("failed to cast RFQAddress to string") + } + return value, nil +} + +// GetCCTPAddress returns the RFQ address for the given chainID. +func (c Config) GetCCTPAddress(chainID int) (value string, err error) { + rawValue, err := c.getChainConfigValue(chainID, "CCTPAddress") + if err != nil { + return value, err + } + + value, ok := rawValue.(string) + if !ok { + return value, fmt.Errorf("failed to cast CCTPAddress to string") } return value, nil } diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index ba94ca7a86..74180f6591 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -62,15 +62,17 @@ func NewRelayer(ctx context.Context, metricHandler metrics.Handler, cfg relconfi chainListeners := make(map[int]listener.ContractListener) // setup chain listeners - for chainID, chainCFG := range cfg.GetChains() { - // TODO: consider getter for this convert step - bridge := common.HexToAddress(chainCFG.Bridge) + for chainID := range cfg.GetChains() { + rfqAddr, err := cfg.GetRFQAddress(chainID) + if err != nil { + return nil, fmt.Errorf("could not get rfq address: %w", err) + } chainClient, err := omniClient.GetChainClient(ctx, chainID) if err != nil { return nil, fmt.Errorf("could not get chain client: %w", err) } - chainListener, err := listener.NewChainListener(chainClient, store, bridge, metricHandler) + chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(rfqAddr), metricHandler) if err != nil { return nil, fmt.Errorf("could not get chain listener: %w", err) } diff --git a/services/rfq/relayer/service/statushandler.go b/services/rfq/relayer/service/statushandler.go index e9cb04a34c..9243e67003 100644 --- a/services/rfq/relayer/service/statushandler.go +++ b/services/rfq/relayer/service/statushandler.go @@ -130,7 +130,11 @@ func (r *Relayer) chainIDToChain(ctx context.Context, chainID uint32) (*chain.Ch } //nolint: wrapcheck - return chain.NewChain(ctx, chainClient, common.HexToAddress(r.cfg.GetChains()[id].Bridge), r.chainListeners[id], r.submitter) + rfqAddr, err := r.cfg.GetRFQAddress(id) + if err != nil { + return nil, fmt.Errorf("could not get rfq address: %w", err) + } + return chain.NewChain(ctx, chainClient, common.HexToAddress(rfqAddr), r.chainListeners[id], r.submitter) } // shouldCheckClaim checks if we should check the claim method. diff --git a/services/rfq/relayer/service/suite_test.go b/services/rfq/relayer/service/suite_test.go index 48d7514e82..6cdebefa06 100644 --- a/services/rfq/relayer/service/suite_test.go +++ b/services/rfq/relayer/service/suite_test.go @@ -69,10 +69,10 @@ func (r *RelayerTestSuite) SetupTest() { }, Chains: map[int]relconfig.ChainConfig{ int(r.originBackend.GetChainID()): { - Bridge: originContract.Address().String(), + RFQAddress: originContract.Address().String(), }, int(r.destBackend.GetChainID()): { - Bridge: destContract.Address().String(), + RFQAddress: destContract.Address().String(), }, }, OmniRPCURL: serverURL, From 6664fd9ce93c503b36a0dfeb8e3100b1dfca2ea1 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 11:36:06 -0600 Subject: [PATCH 02/75] Feat: add RebalanceMethod enum --- services/rfq/relayer/relconfig/config.go | 2 ++ services/rfq/relayer/relconfig/enum.go | 18 +++++++++++++ services/rfq/relayer/relconfig/getters.go | 20 +++++++++++++++ .../relconfig/rebalancemethod_string.go | 25 +++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 services/rfq/relayer/relconfig/enum.go create mode 100644 services/rfq/relayer/relconfig/rebalancemethod_string.go diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index ad868f2346..1176ea1053 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -91,6 +91,8 @@ type TokenConfig struct { PriceUSD float64 `yaml:"price_usd"` // MinQuoteAmount is the minimum amount to quote for this token in human-readable units. MinQuoteAmount string `yaml:"min_quote_amount"` + // RebalanceMethod is the method to use for rebalancing. + RebalanceMethod string `yaml:"rebalance_method"` } // DatabaseConfig represents the configuration for the database. diff --git a/services/rfq/relayer/relconfig/enum.go b/services/rfq/relayer/relconfig/enum.go new file mode 100644 index 0000000000..f2adcbb718 --- /dev/null +++ b/services/rfq/relayer/relconfig/enum.go @@ -0,0 +1,18 @@ +package relconfig + +// RebalanceMethod is the method to rebalance. +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=RebalanceMethod +type RebalanceMethod uint8 + +const ( + // CCTPRebalance is the rebalance method for CCTP. + CCTPRebalance RebalanceMethod = iota + 1 + // NativeBridgeRebalance is the rebalance method for native bridge. + NativeBridgeRebalance +) + +var stringToRebalanceMethod = map[string]RebalanceMethod{ + "cctp": CCTPRebalance, + "native": NativeBridgeRebalance, +} diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index 1887ddad83..a3c0f507c2 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -360,6 +360,26 @@ func (c Config) GetHTTPTimeout() time.Duration { return time.Duration(timeoutMs) * time.Millisecond } +// GetRebalanceMethod returns the rebalance method for the given chain and token. +func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceMethod, err error) { + chainConfig, ok := c.Chains[chainID] + if !ok { + return method, fmt.Errorf("no chain config for chain %d", chainID) + } + tokenConfig, ok := chainConfig.Tokens[token] + if !ok { + return method, fmt.Errorf("no token config for chain %d and token %s", chainID, token) + } + switch tokenConfig.RebalanceMethod { + case "cctp": + return CCTPRebalance, nil + case "native": + return NativeBridgeRebalance, nil + } + return method, fmt.Errorf("invalid rebalance method for chain %d and token %s: %s", chainID, token, tokenConfig.RebalanceMethod) + +} + // GetTokenID returns the tokenID for the given chain and address. func (c Config) GetTokenID(chain int, addr string) (string, error) { chainConfig, ok := c.Chains[chain] diff --git a/services/rfq/relayer/relconfig/rebalancemethod_string.go b/services/rfq/relayer/relconfig/rebalancemethod_string.go new file mode 100644 index 0000000000..bfe488a76f --- /dev/null +++ b/services/rfq/relayer/relconfig/rebalancemethod_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=RebalanceMethod"; DO NOT EDIT. + +package relconfig + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[CCTPRebalance-1] + _ = x[NativeBridgeRebalance-2] +} + +const _RebalanceMethod_name = "CCTPRebalanceNativeBridgeRebalance" + +var _RebalanceMethod_index = [...]uint8{0, 13, 34} + +func (i RebalanceMethod) String() string { + i -= 1 + if i >= RebalanceMethod(len(_RebalanceMethod_index)-1) { + return "RebalanceMethod(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _RebalanceMethod_name[_RebalanceMethod_index[i]:_RebalanceMethod_index[i+1]] +} From 91017b79b25a6c54815959de6f609720c38d06ec Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 11:40:53 -0600 Subject: [PATCH 03/75] Feat: add MaintenanceBalancePct and InitialBalancePct --- services/rfq/relayer/relconfig/config.go | 4 +++ services/rfq/relayer/relconfig/getters.go | 32 +++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 1176ea1053..3811c4c932 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -93,6 +93,10 @@ type TokenConfig struct { MinQuoteAmount string `yaml:"min_quote_amount"` // RebalanceMethod is the method to use for rebalancing. RebalanceMethod string `yaml:"rebalance_method"` + // MaintenanceBalancePct is the percentage of the total balance under which a rebalance will be triggered. + MaintenanceBalancePct float64 `yaml:"maintenance_balance_pct"` + // InitialBalancePct is the percentage of the total balance to retain when triggering a rebalance. + InitialBalancePct float64 `yaml:"initial_balance_pct"` } // DatabaseConfig represents the configuration for the database. diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index a3c0f507c2..eb401b1335 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -380,6 +380,38 @@ func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceM } +// GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token. +func (c Config) GetMaintenanceBalancePct(chainID int, token string) (float64, error) { + chainConfig, ok := c.Chains[chainID] + if !ok { + return 0, fmt.Errorf("no chain config for chain %d", chainID) + } + tokenConfig, ok := chainConfig.Tokens[token] + if !ok { + return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, token) + } + if tokenConfig.MaintenanceBalancePct <= 0 { + return 0, fmt.Errorf("maintenance balance pct must be positive: %f", tokenConfig.MaintenanceBalancePct) + } + return tokenConfig.MaintenanceBalancePct, nil +} + +// GetInitialBalancePct returns the initial balance percentage for the given chain and token. +func (c Config) GetInitialBalancePct(chainID int, token string) (float64, error) { + chainConfig, ok := c.Chains[chainID] + if !ok { + return 0, fmt.Errorf("no chain config for chain %d", chainID) + } + tokenConfig, ok := chainConfig.Tokens[token] + if !ok { + return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, token) + } + if tokenConfig.InitialBalancePct <= 0 { + return 0, fmt.Errorf("initial balance pct must be positive: %f", tokenConfig.InitialBalancePct) + } + return tokenConfig.InitialBalancePct, nil +} + // GetTokenID returns the tokenID for the given chain and address. func (c Config) GetTokenID(chain int, addr string) (string, error) { chainConfig, ok := c.Chains[chain] From f7d56f2801f194da4277c37a73ba9ecf7bf8cb0f Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 11:43:07 -0600 Subject: [PATCH 04/75] Feat: add RebalanceMethodNone --- services/rfq/relayer/relconfig/enum.go | 15 ++++++--------- services/rfq/relayer/relconfig/getters.go | 7 +++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/services/rfq/relayer/relconfig/enum.go b/services/rfq/relayer/relconfig/enum.go index f2adcbb718..2f2420a0fa 100644 --- a/services/rfq/relayer/relconfig/enum.go +++ b/services/rfq/relayer/relconfig/enum.go @@ -6,13 +6,10 @@ package relconfig type RebalanceMethod uint8 const ( - // CCTPRebalance is the rebalance method for CCTP. - CCTPRebalance RebalanceMethod = iota + 1 - // NativeBridgeRebalance is the rebalance method for native bridge. - NativeBridgeRebalance + // RebalanceMethodNone is the default rebalance method. + RebalanceMethodNone RebalanceMethod = iota + // RebalanceMethodCCTP is the rebalance method for CCTP. + RebalanceMethodCCTP + // RebalanceMethodNative is the rebalance method for native bridge. + RebalanceMethodNative ) - -var stringToRebalanceMethod = map[string]RebalanceMethod{ - "cctp": CCTPRebalance, - "native": NativeBridgeRebalance, -} diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index eb401b1335..c63ba42db3 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -372,12 +372,11 @@ func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceM } switch tokenConfig.RebalanceMethod { case "cctp": - return CCTPRebalance, nil + return RebalanceMethodCCTP, nil case "native": - return NativeBridgeRebalance, nil + return RebalanceMethodNative, nil } - return method, fmt.Errorf("invalid rebalance method for chain %d and token %s: %s", chainID, token, tokenConfig.RebalanceMethod) - + return RebalanceMethodNone, nil } // GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token. From cbf01e745a68d2bc3c62e707c92d1fa2f16dddda Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 12:39:40 -0600 Subject: [PATCH 05/75] Feat: add Rebalance() to inventory manager --- services/rfq/relayer/inventory/manager.go | 3 ++ services/rfq/relayer/inventory/rebalance.go | 29 +++++++++++++++++++ services/rfq/relayer/service/handlers.go | 20 ++++++++++++- services/rfq/relayer/service/statushandler.go | 2 +- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 services/rfq/relayer/inventory/rebalance.go diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 2618ab82ea..8f83c93fb8 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -42,6 +42,9 @@ type Manager interface { ApproveAllTokens(ctx context.Context, submitter submitter.TransactionSubmitter) 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, token common.Address) error } type inventoryManagerImpl struct { diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go new file mode 100644 index 0000000000..9394b04b2e --- /dev/null +++ b/services/rfq/relayer/inventory/rebalance.go @@ -0,0 +1,29 @@ +package inventory + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/submitter" + "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" +) + +// Rebalance is the interface for rebalancing inventory. +type Rebalance interface { + Execute(ctx context.Context, txSubmitter submitter.TransactionSubmitter, handler metrics.Handler) error +} + +type cctpRebalance struct { + origin int + dest int + token common.Address + amount *big.Int + contract *cctp.SynapseCCTP +} + +func (c *cctpRebalance) Execute(ctx context.Context, txSubmitter submitter.TransactionSubmitter, handler metrics.Handler) error { + // TODO: implement + return nil +} diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go index 2104a05bdf..168ec819a1 100644 --- a/services/rfq/relayer/service/handlers.go +++ b/services/rfq/relayer/service/handlers.go @@ -290,7 +290,7 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa return fmt.Errorf("could not check if can claim: %w", err) } - // can't cliam yet. we'll check again later + // can't claim yet. we'll check again later if !canClaim { return nil } @@ -313,6 +313,24 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa return nil } +// handleClaimCompleted handles the claim completed status and marks the claim as completed. +// Step 9: ClaimCompleted +// +// Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token +// is needed, and trigger it on the inventory manager if so. +func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) { + err = q.Inventory.Rebalance(ctx, request.Transaction.DestToken) + if err != nil { + return fmt.Errorf("could not rebalance: %w", err) + } + + err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimPending) + if err != nil { + return fmt.Errorf("could not update request status: %w", err) + } + return nil +} + // Error Handlers Only from this point belo // // handleNotEnoughInventory handles the not enough inventory status. diff --git a/services/rfq/relayer/service/statushandler.go b/services/rfq/relayer/service/statushandler.go index 9243e67003..44f2182fc3 100644 --- a/services/rfq/relayer/service/statushandler.go +++ b/services/rfq/relayer/service/statushandler.go @@ -76,7 +76,7 @@ func (r *Relayer) requestToHandler(ctx context.Context, req reldb.QuoteRequest) // no more need for deadline middleware now, we already relayed. qr.handlers[reldb.RelayCompleted] = r.gasMiddleware(qr.handleRelayCompleted) qr.handlers[reldb.ProvePosted] = qr.handleProofPosted - // TODO: we probably want a claim complete state once we've seen that event on chain + qr.handlers[reldb.ClaimCompleted] = qr.handleClaimCompleted // error handlers only qr.handlers[reldb.NotEnoughInventory] = r.deadlineMiddleware(qr.handleNotEnoughInventory) From 512fb304023688a3495b02972f232f2ce6b11f47 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 12:39:54 -0600 Subject: [PATCH 06/75] Cleanup: remove rebalance.go --- services/rfq/relayer/inventory/rebalance.go | 29 --------------------- 1 file changed, 29 deletions(-) delete mode 100644 services/rfq/relayer/inventory/rebalance.go diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go deleted file mode 100644 index 9394b04b2e..0000000000 --- a/services/rfq/relayer/inventory/rebalance.go +++ /dev/null @@ -1,29 +0,0 @@ -package inventory - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/synapsecns/sanguine/core/metrics" - "github.com/synapsecns/sanguine/ethergo/submitter" - "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" -) - -// Rebalance is the interface for rebalancing inventory. -type Rebalance interface { - Execute(ctx context.Context, txSubmitter submitter.TransactionSubmitter, handler metrics.Handler) error -} - -type cctpRebalance struct { - origin int - dest int - token common.Address - amount *big.Int - contract *cctp.SynapseCCTP -} - -func (c *cctpRebalance) Execute(ctx context.Context, txSubmitter submitter.TransactionSubmitter, handler metrics.Handler) error { - // TODO: implement - return nil -} From 536f227687809c592d6c04729049c3a41eaf3f55 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 12:44:58 -0600 Subject: [PATCH 07/75] Feat: add submitter to inventory manager --- services/rfq/relayer/inventory/manager.go | 21 ++++++++++++++++----- services/rfq/relayer/service/relayer.go | 8 ++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 8f83c93fb8..2c4c944192 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -39,7 +39,7 @@ 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 @@ -62,7 +62,10 @@ 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 + // db is the database + db reldb.Service } // GetCommittableBalance gets the committable balances. @@ -138,12 +141,14 @@ 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, db: db, } @@ -176,7 +181,7 @@ 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 { +func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { i.mux.RLock() defer i.mux.RUnlock() @@ -192,7 +197,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context, submitter s 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) { + _, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { erc20, err := ierc20.NewIERC20(address, backendClient) if err != nil { return nil, fmt.Errorf("could not get erc20: %w", err) @@ -237,6 +242,12 @@ 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. +func (i *inventoryManagerImpl) Rebalance(ctx context.Context, token common.Address) error { + // TODO: implement + return nil +} + // 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) { diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index 74180f6591..99bd3edca9 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -84,7 +84,9 @@ func NewRelayer(ctx context.Context, metricHandler metrics.Handler, cfg relconfi return nil, fmt.Errorf("could not get signer: %w", err) } - im, err := inventory.NewInventoryManager(ctx, omniClient, metricHandler, cfg, sg.Address(), store) + sm := submitter.NewTransactionSubmitter(metricHandler, sg, omniClient, store.SubmitterDB(), &cfg.SubmitterConfig) + + im, err := inventory.NewInventoryManager(ctx, omniClient, metricHandler, cfg, sg.Address(), sm, store) if err != nil { return nil, fmt.Errorf("could not add imanager: %w", err) } @@ -97,8 +99,6 @@ func NewRelayer(ctx context.Context, metricHandler metrics.Handler, cfg relconfi return nil, fmt.Errorf("could not get quoter") } - sm := submitter.NewTransactionSubmitter(metricHandler, sg, omniClient, store.SubmitterDB(), &cfg.SubmitterConfig) - apiServer, err := relapi.NewRelayerAPI(ctx, cfg, metricHandler, omniClient, store, sm) if err != nil { return nil, fmt.Errorf("could not get api server: %w", err) @@ -133,7 +133,7 @@ const defaultPostInterval = 1 // 4. Start the submitter: This will submit any transactions that need to be submitted. // nolint: cyclop func (r *Relayer) Start(ctx context.Context) error { - err := r.inventory.ApproveAllTokens(ctx, r.submitter) + err := r.inventory.ApproveAllTokens(ctx) if err != nil { return fmt.Errorf("could not approve all tokens: %w", err) } From 1e9e1d60f41f45393bdf0558ed83b077806bfce7 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 12:46:01 -0600 Subject: [PATCH 08/75] Cleanup: Adress -> Address --- services/rfq/relayer/service/handlers.go | 4 ++-- services/rfq/relayer/service/statushandler.go | 22 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go index 168ec819a1..4a67872c4e 100644 --- a/services/rfq/relayer/service/handlers.go +++ b/services/rfq/relayer/service/handlers.go @@ -285,7 +285,7 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa return nil } - canClaim, err := q.Origin.Bridge.CanClaim(&bind.CallOpts{Context: ctx}, request.TransactionID, q.RelayerAdress) + canClaim, err := q.Origin.Bridge.CanClaim(&bind.CallOpts{Context: ctx}, request.TransactionID, q.RelayerAddress) if err != nil { return fmt.Errorf("could not check if can claim: %w", err) } @@ -331,7 +331,7 @@ func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace. return nil } -// Error Handlers Only from this point belo +// Error Handlers Only from this point below. // // handleNotEnoughInventory handles the not enough inventory status. func (q *QuoteRequestHandler) handleNotEnoughInventory(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) { diff --git a/services/rfq/relayer/service/statushandler.go b/services/rfq/relayer/service/statushandler.go index 44f2182fc3..d4c61736c6 100644 --- a/services/rfq/relayer/service/statushandler.go +++ b/services/rfq/relayer/service/statushandler.go @@ -38,8 +38,8 @@ type QuoteRequestHandler struct { handlers map[reldb.QuoteRequestStatus]Handler // claimCache is the cache of claims used for figuring out when we should retry the claim method. claimCache *ttlcache.Cache[common.Hash, bool] - // RelayerAdress is the relayer RelayerAdress - RelayerAdress common.Address + // RelayerAddress is the relayer RelayerAddress + RelayerAddress common.Address // metrics is the metrics handler. metrics metrics.Handler } @@ -59,15 +59,15 @@ func (r *Relayer) requestToHandler(ctx context.Context, req reldb.QuoteRequest) } qr := &QuoteRequestHandler{ - Origin: *origin, - Dest: *dest, - db: r.db, - Inventory: r.inventory, - Quoter: r.quoter, - handlers: make(map[reldb.QuoteRequestStatus]Handler), - metrics: r.metrics, - RelayerAdress: r.signer.Address(), - claimCache: r.claimCache, + Origin: *origin, + Dest: *dest, + db: r.db, + Inventory: r.inventory, + Quoter: r.quoter, + handlers: make(map[reldb.QuoteRequestStatus]Handler), + metrics: r.metrics, + RelayerAddress: r.signer.Address(), + claimCache: r.claimCache, } qr.handlers[reldb.Seen] = r.deadlineMiddleware(qr.handleSeen) From 708eab05ffe893edb7aafef56bb7c03fa64f80e4 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 13:26:19 -0600 Subject: [PATCH 09/75] WIP: add Rebalance() impl to inventory manager --- services/rfq/relayer/inventory/manager.go | 114 +++++++++++++++++++++- services/rfq/relayer/service/handlers.go | 2 +- 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 2c4c944192..7b52957fc3 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -44,7 +44,7 @@ type Manager interface { 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, token common.Address) error + Rebalance(ctx context.Context, chainID int, token common.Address) error } type inventoryManagerImpl struct { @@ -243,8 +243,116 @@ func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, des } // Rebalance checks whether a given token should be rebalanced, and executes the rebalance if necessary. -func (i *inventoryManagerImpl) Rebalance(ctx context.Context, token common.Address) error { - // TODO: implement +// 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(ctx, chainID, token) + if err != nil { + return fmt.Errorf("could not get rebalance: %w", err) + } + if rebalance == nil { + return nil + } + + switch method { + case relconfig.RebalanceMethodCCTP: + return i.rebalanceCCTP(ctx, rebalance) + default: + return fmt.Errorf("unknown rebalance method: %s", method) + } +} + +// 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(ctx context.Context, 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 + var rebalanceOrigin, rebalanceDest int + var rebalanceOriginTokenAddr common.Address + for tokenChainID, tokenMap := range i.tokens { + for tokenAddr, tokenData := range tokenMap { + if tokenData.name == rebalanceTokenData.name { + if minTokenData == nil || minTokenData.balance.Cmp(minTokenData.balance) < 0 { + minTokenData = tokenData + rebalanceDest = tokenChainID + } + if maxTokenData == nil || maxTokenData.balance.Cmp(maxTokenData.balance) > 0 { + maxTokenData = tokenData + rebalanceOrigin = tokenChainID + rebalanceOriginTokenAddr = tokenAddr + } + } + } + } + + // get the initialPct for the origin chain + initialPct, err := i.cfg.GetInitialBalancePct(rebalanceDest, rebalanceOriginTokenAddr.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: rebalanceOrigin, + dest: rebalanceDest, + originMetadata: maxTokenData, + destMetadata: minTokenData, + amount: amount, + } + } + return rebalance, nil +} + +func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *rebalanceData) (err error) { + // TODO: impl return nil } diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go index 4a67872c4e..f00d6c4f39 100644 --- a/services/rfq/relayer/service/handlers.go +++ b/services/rfq/relayer/service/handlers.go @@ -319,7 +319,7 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa // Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token // is needed, and trigger it on the inventory manager if so. func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) { - err = q.Inventory.Rebalance(ctx, request.Transaction.DestToken) + err = q.Inventory.Rebalance(ctx, int(request.Transaction.DestChainId), request.Transaction.DestToken) if err != nil { return fmt.Errorf("could not rebalance: %w", err) } From eabdda071e61ac012f343d4f357424f3393470a9 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 13:32:39 -0600 Subject: [PATCH 10/75] Feat: add token address and chain id to tokenMetadata --- services/rfq/relayer/inventory/manager.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 7b52957fc3..00d0715ce5 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -128,6 +128,8 @@ type tokenMetadata struct { decimals uint8 startAllowance *big.Int isGasToken bool + chainID int + addr common.Address } var ( @@ -400,6 +402,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r } rtoken := &tokenMetadata{ isGasToken: tokenName == nativeToken, + chainID: chainID, } var token common.Address @@ -409,6 +412,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) From 1f7c3a32ea2b0b96d36fb147bf7692cc114a3425 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 13:34:13 -0600 Subject: [PATCH 11/75] Cleanup: utilize new tokenMetadata fields --- services/rfq/relayer/inventory/manager.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 00d0715ce5..008b96d9df 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -313,26 +313,21 @@ func (i *inventoryManagerImpl) getRebalance(ctx context.Context, chainID int, to // check if any balances are below maintenance threshold var minTokenData, maxTokenData *tokenMetadata - var rebalanceOrigin, rebalanceDest int - var rebalanceOriginTokenAddr common.Address - for tokenChainID, tokenMap := range i.tokens { - for tokenAddr, tokenData := range tokenMap { + for _, tokenMap := range i.tokens { + for _, tokenData := range tokenMap { if tokenData.name == rebalanceTokenData.name { if minTokenData == nil || minTokenData.balance.Cmp(minTokenData.balance) < 0 { minTokenData = tokenData - rebalanceDest = tokenChainID } if maxTokenData == nil || maxTokenData.balance.Cmp(maxTokenData.balance) > 0 { maxTokenData = tokenData - rebalanceOrigin = tokenChainID - rebalanceOriginTokenAddr = tokenAddr } } } } // get the initialPct for the origin chain - initialPct, err := i.cfg.GetInitialBalancePct(rebalanceDest, rebalanceOriginTokenAddr.Hex()) + initialPct, err := i.cfg.GetInitialBalancePct(maxTokenData.chainID, maxTokenData.addr.Hex()) if err != nil { return nil, fmt.Errorf("could not get initial pct: %w", err) } @@ -343,8 +338,8 @@ func (i *inventoryManagerImpl) getRebalance(ctx context.Context, chainID int, to 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: rebalanceOrigin, - dest: rebalanceDest, + origin: maxTokenData.chainID, + dest: minTokenData.chainID, originMetadata: maxTokenData, destMetadata: minTokenData, amount: amount, From 44be2b3faf75fb9289d24d3bafd25c1176561a57 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 13:41:58 -0600 Subject: [PATCH 12/75] Feat: add approval for CCTP bridge --- services/rfq/relayer/inventory/manager.go | 35 +++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 008b96d9df..229b9deb79 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -198,26 +198,43 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { 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 + + 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) { - erc20, err := ierc20.NewIERC20(address, backendClient) + rfqAddr, err := i.cfg.GetRFQAddress(chainID) + if err != nil { + 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 get erc20: %w", err) + 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) + } - rfqAddr, err := i.cfg.GetRFQAddress(chainID) + // 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 get rfq address: %w", err) + return nil, fmt.Errorf("could not get cctp address: %w", err) } - approveAmount, err := erc20.Approve(transactor, common.HexToAddress(rfqAddr), abi.MaxInt256) + tx, err = erc20.Approve(transactor, common.HexToAddress(cctpAddr), abi.MaxInt256) if err != nil { - return nil, fmt.Errorf("could not approve: %w", err) + 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) } } } From d2acc7987d6a9b0b99927d30f38806c3aa8d49df Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 13:48:01 -0600 Subject: [PATCH 13/75] Feat: impl rebalanceCCTP() --- services/rfq/relayer/inventory/manager.go | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 229b9deb79..59df65e698 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -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" @@ -64,6 +65,8 @@ type inventoryManagerImpl struct { chainClient submitter.ClientFetcher // 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 } @@ -151,6 +154,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc cfg: cfg, chainClient: clientFetcher, txSubmitter: txSubmitter, + cctpContracts: make(map[int]*cctp.SynapseCCTP), db: db, } @@ -366,7 +370,43 @@ func (i *inventoryManagerImpl) getRebalance(ctx context.Context, chainID int, to } func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *rebalanceData) (err error) { - // TODO: impl + // 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 } From 4ad03dcf97b0c7716cb676ec9ac6e00e2104064b Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 20 Feb 2024 21:12:20 -0600 Subject: [PATCH 14/75] Fix: regenerate --- .../rfq/relayer/relconfig/rebalancemethod_string.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/rfq/relayer/relconfig/rebalancemethod_string.go b/services/rfq/relayer/relconfig/rebalancemethod_string.go index bfe488a76f..377c060921 100644 --- a/services/rfq/relayer/relconfig/rebalancemethod_string.go +++ b/services/rfq/relayer/relconfig/rebalancemethod_string.go @@ -8,18 +8,18 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[CCTPRebalance-1] - _ = x[NativeBridgeRebalance-2] + _ = x[RebalanceMethodNone-0] + _ = x[RebalanceMethodCCTP-1] + _ = x[RebalanceMethodNative-2] } -const _RebalanceMethod_name = "CCTPRebalanceNativeBridgeRebalance" +const _RebalanceMethod_name = "RebalanceMethodNoneRebalanceMethodCCTPRebalanceMethodNative" -var _RebalanceMethod_index = [...]uint8{0, 13, 34} +var _RebalanceMethod_index = [...]uint8{0, 19, 38, 59} func (i RebalanceMethod) String() string { - i -= 1 if i >= RebalanceMethod(len(_RebalanceMethod_index)-1) { - return "RebalanceMethod(" + strconv.FormatInt(int64(i+1), 10) + ")" + return "RebalanceMethod(" + strconv.FormatInt(int64(i), 10) + ")" } return _RebalanceMethod_name[_RebalanceMethod_index[i]:_RebalanceMethod_index[i+1]] } From 7c0cf7ec0a6b820b6621437a692bdceb03742c61 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 10:14:35 -0600 Subject: [PATCH 15/75] Cleanup: lint --- services/rfq/relayer/inventory/manager.go | 14 +++++++++----- services/rfq/relayer/relconfig/config_test.go | 1 + services/rfq/relayer/service/statushandler.go | 6 +++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 59df65e698..2caf6a35f3 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -146,7 +146,7 @@ var ( const defaultPollPeriod = 5 // NewInventoryManager creates a list of tokens we should use. -// TODO: too many args here +// 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, @@ -187,6 +187,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc const maxBatchSize = 10 // ApproveAllTokens approves all checks if allowance is set and if not approves. +// nolint:gocognit,nestif func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { i.mux.RLock() defer i.mux.RUnlock() @@ -282,7 +283,7 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token return fmt.Errorf("could not refresh balances: %w", err) } - rebalance, err := i.getRebalance(ctx, chainID, token) + rebalance, err := i.getRebalance(chainID, token) if err != nil { return fmt.Errorf("could not get rebalance: %w", err) } @@ -290,9 +291,12 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token 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) } @@ -307,7 +311,7 @@ type rebalanceData struct { amount *big.Int } -func (i *inventoryManagerImpl) getRebalance(ctx context.Context, chainID int, token common.Address) (rebalance *rebalanceData, err error) { +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) @@ -337,10 +341,10 @@ func (i *inventoryManagerImpl) getRebalance(ctx context.Context, chainID int, to for _, tokenMap := range i.tokens { for _, tokenData := range tokenMap { if tokenData.name == rebalanceTokenData.name { - if minTokenData == nil || minTokenData.balance.Cmp(minTokenData.balance) < 0 { + if minTokenData == nil || tokenData.balance.Cmp(minTokenData.balance) < 0 { minTokenData = tokenData } - if maxTokenData == nil || maxTokenData.balance.Cmp(maxTokenData.balance) > 0 { + if maxTokenData == nil || tokenData.balance.Cmp(maxTokenData.balance) > 0 { maxTokenData = tokenData } } diff --git a/services/rfq/relayer/relconfig/config_test.go b/services/rfq/relayer/relconfig/config_test.go index e13d1592c8..b0b9e6e16d 100644 --- a/services/rfq/relayer/relconfig/config_test.go +++ b/services/rfq/relayer/relconfig/config_test.go @@ -8,6 +8,7 @@ import ( "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" ) +//nolint:maintidx func TestGetters(t *testing.T) { chainID := 1 badChainID := 2 diff --git a/services/rfq/relayer/service/statushandler.go b/services/rfq/relayer/service/statushandler.go index d4c61736c6..7902487283 100644 --- a/services/rfq/relayer/service/statushandler.go +++ b/services/rfq/relayer/service/statushandler.go @@ -134,7 +134,11 @@ func (r *Relayer) chainIDToChain(ctx context.Context, chainID uint32) (*chain.Ch if err != nil { return nil, fmt.Errorf("could not get rfq address: %w", err) } - return chain.NewChain(ctx, chainClient, common.HexToAddress(rfqAddr), r.chainListeners[id], r.submitter) + chain, err := chain.NewChain(ctx, chainClient, common.HexToAddress(rfqAddr), r.chainListeners[id], r.submitter) + if err != nil { + return nil, fmt.Errorf("could not create chain: %w", err) + } + return chain, nil } // shouldCheckClaim checks if we should check the claim method. From fda6f904840d62b1732fc9e775830d97a2724ff4 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 10:33:49 -0600 Subject: [PATCH 16/75] WIP: export inventory structs for use in tests --- services/rfq/relayer/inventory/manager.go | 121 +++++++++++----------- 1 file changed, 61 insertions(+), 60 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 2caf6a35f3..70cf599e54 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -49,8 +49,8 @@ type Manager interface { } type inventoryManagerImpl struct { - // map chainID->address->tokenMetadata - tokens map[int]map[common.Address]*tokenMetadata + // map chainID->address->TokenMetadata + tokens map[int]map[common.Address]*TokenMetadata // map chainID->balance gasBalances map[int]*big.Int // mux contains the mutex @@ -108,7 +108,7 @@ func (i *inventoryManagerImpl) GetCommittableBalances(ctx context.Context, optio for chainID, tokenMap := range i.tokens { res[chainID] = map[common.Address]*big.Int{} for address, tokenData := range tokenMap { - res[chainID][address] = core.CopyBigInt(tokenData.balance) + res[chainID][address] = core.CopyBigInt(tokenData.Balance) // now subtract by in flight quotes. // Yeah, this is an algorithmically atrocious for // TODO: fix, but we're really talking about 4 tokens @@ -125,14 +125,15 @@ func (i *inventoryManagerImpl) GetCommittableBalances(ctx context.Context, optio return res, nil } -type tokenMetadata struct { - name string - balance *big.Int - decimals uint8 - startAllowance *big.Int - isGasToken bool - chainID int - addr common.Address +// TokenMetadata contains metadata for a token. +type TokenMetadata struct { + Name string + Balance *big.Int + Decimals uint8 + StartAllowance *big.Int + IsGasToken bool + ChainID int + Addr common.Address } var ( @@ -200,7 +201,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { for address, token := range tokenMap { // if startAllowance is 0 - if address != chain.EthAddress && token.startAllowance.Cmp(big.NewInt(0)) == 0 { + if address != chain.EthAddress && token.StartAllowance.Cmp(big.NewInt(0)) == 0 { chainID := chainID // capture func literal address := address // capture func literal @@ -283,7 +284,7 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token return fmt.Errorf("could not refresh balances: %w", err) } - rebalance, err := i.getRebalance(chainID, token) + rebalance, err := getRebalance(i.cfg, i.tokens, chainID, token) if err != nil { return fmt.Errorf("could not get rebalance: %w", err) } @@ -302,24 +303,24 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token } } -// rebalanceData contains metadata for a rebalance action. -type rebalanceData struct { +// RebalanceData contains metadata for a rebalance action. +type RebalanceData struct { origin int dest int - originMetadata *tokenMetadata - destMetadata *tokenMetadata + 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()) +func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { + maintenancePct, err := 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] { + var rebalanceTokenData *TokenMetadata + for address, tokenData := range tokens[chainID] { if address == token { rebalanceTokenData = tokenData break @@ -328,23 +329,23 @@ func (i *inventoryManagerImpl) getRebalance(chainID int, token common.Address) ( // get total balance for given token across all chains totalBalance := big.NewInt(0) - for _, tokenMap := range i.tokens { + for _, tokenMap := range tokens { for _, tokenData := range tokenMap { - if tokenData.name == rebalanceTokenData.name { - totalBalance.Add(totalBalance, tokenData.balance) + 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 { + var minTokenData, maxTokenData *TokenMetadata + for _, tokenMap := range tokens { for _, tokenData := range tokenMap { - if tokenData.name == rebalanceTokenData.name { - if minTokenData == nil || tokenData.balance.Cmp(minTokenData.balance) < 0 { + 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 { + if maxTokenData == nil || tokenData.Balance.Cmp(maxTokenData.Balance) > 0 { maxTokenData = tokenData } } @@ -352,19 +353,19 @@ func (i *inventoryManagerImpl) getRebalance(chainID int, token common.Address) ( } // get the initialPct for the origin chain - initialPct, err := i.cfg.GetInitialBalancePct(maxTokenData.chainID, maxTokenData.addr.Hex()) + initialPct, err := 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 { + 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, + amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) + rebalance = &RebalanceData{ + origin: maxTokenData.ChainID, + dest: minTokenData.ChainID, originMetadata: maxTokenData, destMetadata: minTokenData, amount: amount, @@ -373,7 +374,7 @@ func (i *inventoryManagerImpl) getRebalance(chainID int, token common.Address) ( return rebalance, nil } -func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *rebalanceData) (err error) { +func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *RebalanceData) (err error) { // fetch the corresponding CCTP contract contract, ok := i.cctpContracts[rebalance.dest] if !ok { @@ -393,12 +394,12 @@ func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *reb } // 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) { + _, 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, + big.NewInt(int64(rebalance.destMetadata.ChainID)), + rebalance.originMetadata.Addr, rebalance.amount, 0, // TODO: inspect []byte{}, // TODO: inspect @@ -431,7 +432,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r meter := i.handler.Meter("github.com/synapsecns/sanguine/services/rfq/relayer/inventory") // TODO: this needs to be a struct bound variable otherwise will be stuck. - i.tokens = make(map[int]map[common.Address]*tokenMetadata) + i.tokens = make(map[int]map[common.Address]*TokenMetadata) i.gasBalances = make(map[int]*big.Int) type registerCall func() error @@ -442,7 +443,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r // iterate through all tokens to get the metadata for chainID, chainCfg := range cfg.GetChains() { - i.tokens[chainID] = map[common.Address]*tokenMetadata{} + i.tokens[chainID] = map[common.Address]*TokenMetadata{} // set up balance fetching for this chain's gas token i.gasBalances[chainID] = new(big.Int) @@ -456,28 +457,28 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r if err != nil { return fmt.Errorf("could not get native token: %w", err) } - rtoken := &tokenMetadata{ - isGasToken: tokenName == nativeToken, - chainID: chainID, + rtoken := &TokenMetadata{ + IsGasToken: tokenName == nativeToken, + ChainID: chainID, } var token common.Address - if rtoken.isGasToken { + if rtoken.IsGasToken { token = chain.EthAddress } else { token = common.HexToAddress(tokenCfg.Address) } i.tokens[chainID][token] = rtoken - rtoken.addr = token + rtoken.Addr = token // requires non-nil pointer - rtoken.balance = new(big.Int) - rtoken.startAllowance = new(big.Int) + rtoken.Balance = new(big.Int) + rtoken.StartAllowance = new(big.Int) - if rtoken.isGasToken { - rtoken.decimals = 18 - rtoken.name = tokenName - rtoken.balance = i.gasBalances[chainID] + if rtoken.IsGasToken { + rtoken.Decimals = 18 + rtoken.Name = tokenName + rtoken.Balance = i.gasBalances[chainID] // TODO: start allowance? } else { rfqAddr, err := cfg.GetRFQAddress(chainID) @@ -485,10 +486,10 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r 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(rfqAddr)).Returns(rtoken.startAllowance), + 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(rfqAddr)).Returns(rtoken.StartAllowance), ) } @@ -563,8 +564,8 @@ func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { // queue token balance fetches for tokenAddress, token := range tokenMap { // TODO: make sure Returns does nothing on error - if !token.isGasToken { - deferredCalls = append(deferredCalls, eth.CallFunc(funcBalanceOf, tokenAddress, i.relayerAddress).Returns(token.balance)) + if !token.IsGasToken { + deferredCalls = append(deferredCalls, eth.CallFunc(funcBalanceOf, tokenAddress, i.relayerAddress).Returns(token.Balance)) } } @@ -598,10 +599,10 @@ func (i *inventoryManagerImpl) registerMetric(meter metric.Meter, chainID int, t } attributes := attribute.NewSet(attribute.Int(metrics.ChainID, chainID), attribute.String("relayer_address", i.relayerAddress.String()), - attribute.String("token_name", tokenData.name), attribute.Int("decimals", int(tokenData.decimals)), + attribute.String("token_name", tokenData.Name), attribute.Int("decimals", int(tokenData.Decimals)), attribute.String("token_address", token.String())) - observer.ObserveFloat64(balanceGauge, core.BigToDecimals(tokenData.balance, tokenData.decimals), metric.WithAttributeSet(attributes)) + observer.ObserveFloat64(balanceGauge, core.BigToDecimals(tokenData.Balance, tokenData.Decimals), metric.WithAttributeSet(attributes)) return nil }, balanceGauge); err != nil { From 4db711f63f74f1191fb3529dd4d3f3c30c3b4bf5 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 11:26:57 -0600 Subject: [PATCH 17/75] Feat: add TestGetRebalance --- services/rfq/relayer/inventory/export_test.go | 11 ++++ services/rfq/relayer/inventory/manager.go | 4 +- .../rfq/relayer/inventory/manager_test.go | 56 ++++++++++++++++++- services/rfq/relayer/relconfig/getters.go | 35 +++++++----- 4 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 services/rfq/relayer/inventory/export_test.go diff --git a/services/rfq/relayer/inventory/export_test.go b/services/rfq/relayer/inventory/export_test.go new file mode 100644 index 0000000000..0bd65c18f7 --- /dev/null +++ b/services/rfq/relayer/inventory/export_test.go @@ -0,0 +1,11 @@ +package inventory + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" +) + +// GetRebalance is a wrapper around the internal getRebalance function. +func GetRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (*RebalanceData, error) { + return getRebalance(cfg, tokens, chainID, token) +} diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 70cf599e54..9ac05b2308 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -359,9 +359,9 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token } // 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) + maintenanceThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(maintenancePct/100)).Int(nil) if minTokenData.Balance.Cmp(maintenanceThresh) < 0 { - initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct)).Int(nil) + initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) rebalance = &RebalanceData{ origin: maxTokenData.ChainID, diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index 01909cd920..cc7ca3f5a5 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -52,8 +52,62 @@ func (i *InventoryTestSuite) TestInventoryBootAndRefresh() { } } - im, err := inventory.NewInventoryManager(i.GetTestContext(), omnirpcClient.NewOmnirpcClient(i.omnirpcURL, metrics.Get()), metrics.Get(), cfg, i.relayer.Address(), i.db) + im, err := inventory.NewInventoryManager(i.GetTestContext(), omnirpcClient.NewOmnirpcClient(i.omnirpcURL, metrics.Get()), metrics.Get(), cfg, i.relayer.Address(), nil, i.db) i.Require().NoError(err) _ = im } + +func (i *InventoryTestSuite) TestGetRebalance() { + origin := 1 + dest := 2 + usdcDataOrigin := inventory.TokenMetadata{ + Name: "USDC", + Decimals: 6, + ChainID: origin, + Addr: common.HexToAddress("0x0000000000000000000000000000000000000123"), + } + usdcDataDest := inventory.TokenMetadata{ + Name: "USDC", + Decimals: 6, + ChainID: dest, + Addr: common.HexToAddress("0x0000000000000000000000000000000000000456"), + } + tokens := map[int]map[common.Address]*inventory.TokenMetadata{ + origin: { + usdcDataOrigin.Addr: &usdcDataOrigin, + }, + dest: { + usdcDataDest.Addr: &usdcDataDest, + }, + } + cfg := relconfig.Config{ + Chains: map[int]relconfig.ChainConfig{ + origin: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataOrigin.Addr.Hex(), + MaintenanceBalancePct: 10, + InitialBalancePct: 30, + }, + }, + }, + dest: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataDest.Addr.Hex(), + MaintenanceBalancePct: 10, + InitialBalancePct: 30, + }, + }, + }, + }, + } + + // 10 USDC on both chains; no rebalance needed + usdcDataOrigin.Balance = big.NewInt(1e7) + usdcDataDest.Balance = big.NewInt(1e7) + rebalance, err := inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) + i.NoError(err) + i.Nil(rebalance) +} diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index c63ba42db3..0dd2d1c443 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -379,15 +379,24 @@ func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceM return RebalanceMethodNone, nil } -// GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token. -func (c Config) GetMaintenanceBalancePct(chainID int, token string) (float64, error) { +func (c Config) getTokenConfigByAddr(chainID int, tokenAddr string) (cfg TokenConfig, err error) { chainConfig, ok := c.Chains[chainID] if !ok { - return 0, fmt.Errorf("no chain config for chain %d", chainID) + return cfg, fmt.Errorf("no chain config for chain %d", chainID) } - tokenConfig, ok := chainConfig.Tokens[token] - if !ok { - return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, token) + for _, tokenConfig := range chainConfig.Tokens { + if common.HexToAddress(tokenConfig.Address).Hex() == common.HexToAddress(tokenAddr).Hex() { + return tokenConfig, nil + } + } + return cfg, fmt.Errorf("no token config for chain %d and address %s", chainID, tokenAddr) +} + +// GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token address. +func (c Config) GetMaintenanceBalancePct(chainID int, tokenAddr string) (float64, error) { + tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr) + if err != nil { + return 0, err } if tokenConfig.MaintenanceBalancePct <= 0 { return 0, fmt.Errorf("maintenance balance pct must be positive: %f", tokenConfig.MaintenanceBalancePct) @@ -395,15 +404,11 @@ func (c Config) GetMaintenanceBalancePct(chainID int, token string) (float64, er return tokenConfig.MaintenanceBalancePct, nil } -// GetInitialBalancePct returns the initial balance percentage for the given chain and token. -func (c Config) GetInitialBalancePct(chainID int, token string) (float64, error) { - chainConfig, ok := c.Chains[chainID] - if !ok { - return 0, fmt.Errorf("no chain config for chain %d", chainID) - } - tokenConfig, ok := chainConfig.Tokens[token] - if !ok { - return 0, fmt.Errorf("no token config for chain %d and token %s", chainID, token) +// GetInitialBalancePct returns the initial balance percentage for the given chain and token address. +func (c Config) GetInitialBalancePct(chainID int, tokenAddr string) (float64, error) { + tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr) + if err != nil { + return 0, err } if tokenConfig.InitialBalancePct <= 0 { return 0, fmt.Errorf("initial balance pct must be positive: %f", tokenConfig.InitialBalancePct) From 06e8e57a8720d8139100d792efa52f7b2dad2620 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 11:32:06 -0600 Subject: [PATCH 18/75] Feat: add rebalance case to TestGetRebalance --- services/rfq/relayer/inventory/manager.go | 36 +++++++++---------- .../rfq/relayer/inventory/manager_test.go | 22 +++++++++--- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 9ac05b2308..d89274e9d5 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -305,11 +305,11 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token // RebalanceData contains metadata for a rebalance action. type RebalanceData struct { - origin int - dest int - originMetadata *TokenMetadata - destMetadata *TokenMetadata - amount *big.Int + Origin int + Dest int + OriginMetadata *TokenMetadata + DestMetadata *TokenMetadata + Amount *big.Int } func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { @@ -364,11 +364,11 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) rebalance = &RebalanceData{ - origin: maxTokenData.ChainID, - dest: minTokenData.ChainID, - originMetadata: maxTokenData, - destMetadata: minTokenData, - amount: amount, + Origin: maxTokenData.ChainID, + Dest: minTokenData.ChainID, + OriginMetadata: maxTokenData, + DestMetadata: minTokenData, + Amount: amount, } } return rebalance, nil @@ -376,13 +376,13 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *RebalanceData) (err error) { // fetch the corresponding CCTP contract - contract, ok := i.cctpContracts[rebalance.dest] + contract, ok := i.cctpContracts[rebalance.Dest] if !ok { - contractAddr, err := i.cfg.GetCCTPAddress(rebalance.origin) + 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))) + chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.Origin))) if err != nil { return fmt.Errorf("could not get chain client: %w", err) } @@ -390,17 +390,17 @@ func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *Reb if err != nil { return fmt.Errorf("could not get cctp: %w", err) } - i.cctpContracts[rebalance.dest] = contract + 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) { + _, 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, + big.NewInt(int64(rebalance.DestMetadata.ChainID)), + rebalance.OriginMetadata.Addr, + rebalance.Amount, 0, // TODO: inspect []byte{}, // TODO: inspect ) diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index cc7ca3f5a5..8114518cdf 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -87,8 +87,8 @@ func (i *InventoryTestSuite) TestGetRebalance() { Tokens: map[string]relconfig.TokenConfig{ "USDC": { Address: usdcDataOrigin.Addr.Hex(), - MaintenanceBalancePct: 10, - InitialBalancePct: 30, + MaintenanceBalancePct: 20, + InitialBalancePct: 50, }, }, }, @@ -96,8 +96,8 @@ func (i *InventoryTestSuite) TestGetRebalance() { Tokens: map[string]relconfig.TokenConfig{ "USDC": { Address: usdcDataDest.Addr.Hex(), - MaintenanceBalancePct: 10, - InitialBalancePct: 30, + MaintenanceBalancePct: 20, + InitialBalancePct: 50, }, }, }, @@ -110,4 +110,18 @@ func (i *InventoryTestSuite) TestGetRebalance() { rebalance, err := inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) i.NoError(err) i.Nil(rebalance) + + // Set origin balance below maintenance threshold; need rebalance + usdcDataOrigin.Balance = big.NewInt(9e6) + usdcDataDest.Balance = big.NewInt(1e6) + rebalance, err = inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) + i.NoError(err) + expected := &inventory.RebalanceData{ + Origin: origin, + Dest: dest, + OriginMetadata: &usdcDataOrigin, + DestMetadata: &usdcDataDest, + Amount: big.NewInt(4e6), + } + i.Equal(expected, rebalance) } From 6b38ba5c134ff6507d355929b93cc609a43d0760 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 11:33:25 -0600 Subject: [PATCH 19/75] Cleanup: remove duplicate data from RebalanceData --- services/rfq/relayer/inventory/manager.go | 12 ++++-------- services/rfq/relayer/inventory/manager_test.go | 2 -- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index d89274e9d5..c315003f9c 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -305,8 +305,6 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token // RebalanceData contains metadata for a rebalance action. type RebalanceData struct { - Origin int - Dest int OriginMetadata *TokenMetadata DestMetadata *TokenMetadata Amount *big.Int @@ -364,8 +362,6 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) rebalance = &RebalanceData{ - Origin: maxTokenData.ChainID, - Dest: minTokenData.ChainID, OriginMetadata: maxTokenData, DestMetadata: minTokenData, Amount: amount, @@ -376,13 +372,13 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *RebalanceData) (err error) { // fetch the corresponding CCTP contract - contract, ok := i.cctpContracts[rebalance.Dest] + contract, ok := i.cctpContracts[rebalance.DestMetadata.ChainID] if !ok { - contractAddr, err := i.cfg.GetCCTPAddress(rebalance.Origin) + contractAddr, err := i.cfg.GetCCTPAddress(rebalance.OriginMetadata.ChainID) if err != nil { return fmt.Errorf("could not get cctp address: %w", err) } - chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.Origin))) + chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID))) if err != nil { return fmt.Errorf("could not get chain client: %w", err) } @@ -390,7 +386,7 @@ func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *Reb if err != nil { return fmt.Errorf("could not get cctp: %w", err) } - i.cctpContracts[rebalance.Dest] = contract + i.cctpContracts[rebalance.DestMetadata.ChainID] = contract } // perform rebalance by calling sendCircleToken() diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index 8114518cdf..d88dfb5bcd 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -117,8 +117,6 @@ func (i *InventoryTestSuite) TestGetRebalance() { rebalance, err = inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) i.NoError(err) expected := &inventory.RebalanceData{ - Origin: origin, - Dest: dest, OriginMetadata: &usdcDataOrigin, DestMetadata: &usdcDataDest, Amount: big.NewInt(4e6), From 90a63e620e5c23cae613edd8e31ea149459896a6 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 11:39:58 -0600 Subject: [PATCH 20/75] Feat: address rebalance case where origin chain cannot pass initial threshold --- services/rfq/relayer/inventory/manager.go | 4 ++++ .../rfq/relayer/inventory/manager_test.go | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index c315003f9c..01e2c0b0fd 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -361,6 +361,10 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token if minTokenData.Balance.Cmp(maintenanceThresh) < 0 { initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) + if amount.Cmp(big.NewInt(0)) < 0 { + // do not rebalance since it would take us below initial threshold + return nil, nil + } rebalance = &RebalanceData{ OriginMetadata: maxTokenData, DestMetadata: minTokenData, diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index d88dfb5bcd..c82eb6515b 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -61,6 +61,7 @@ func (i *InventoryTestSuite) TestInventoryBootAndRefresh() { func (i *InventoryTestSuite) TestGetRebalance() { origin := 1 dest := 2 + extra := 3 usdcDataOrigin := inventory.TokenMetadata{ Name: "USDC", Decimals: 6, @@ -73,6 +74,12 @@ func (i *InventoryTestSuite) TestGetRebalance() { ChainID: dest, Addr: common.HexToAddress("0x0000000000000000000000000000000000000456"), } + usdcDataExtra := inventory.TokenMetadata{ + Name: "USDC", + Decimals: 6, + ChainID: extra, + Addr: common.HexToAddress("0x0000000000000000000000000000000000000789"), + } tokens := map[int]map[common.Address]*inventory.TokenMetadata{ origin: { usdcDataOrigin.Addr: &usdcDataOrigin, @@ -101,6 +108,15 @@ func (i *InventoryTestSuite) TestGetRebalance() { }, }, }, + extra: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataExtra.Addr.Hex(), + MaintenanceBalancePct: 0, + InitialBalancePct: 0, + }, + }, + }, }, } @@ -122,4 +138,12 @@ func (i *InventoryTestSuite) TestGetRebalance() { Amount: big.NewInt(4e6), } i.Equal(expected, rebalance) + + // Increase initial threshold so that no rebalance can occur from origin + usdcDataOrigin.Balance = big.NewInt(2e6) + usdcDataDest.Balance = big.NewInt(1e6) + usdcDataExtra.Balance = big.NewInt(7e6) + rebalance, err = inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) + i.NoError(err) + i.Nil(rebalance) } From 6c70e96c8f0bcd65214ef453875a584f6d3bca0e Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 11:43:05 -0600 Subject: [PATCH 21/75] Cleanup: regenerate inventory --- .../rfq/relayer/inventory/mocks/manager.go | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/services/rfq/relayer/inventory/mocks/manager.go b/services/rfq/relayer/inventory/mocks/manager.go index 4c097661b8..e60623a778 100644 --- a/services/rfq/relayer/inventory/mocks/manager.go +++ b/services/rfq/relayer/inventory/mocks/manager.go @@ -11,8 +11,6 @@ import ( inventory "github.com/synapsecns/sanguine/services/rfq/relayer/inventory" mock "github.com/stretchr/testify/mock" - - submitter "github.com/synapsecns/sanguine/ethergo/submitter" ) // Manager is an autogenerated mock type for the Manager type @@ -20,13 +18,13 @@ type Manager struct { mock.Mock } -// ApproveAllTokens provides a mock function with given fields: ctx, _a1 -func (_m *Manager) ApproveAllTokens(ctx context.Context, _a1 submitter.TransactionSubmitter) error { - ret := _m.Called(ctx, _a1) +// ApproveAllTokens provides a mock function with given fields: ctx +func (_m *Manager) ApproveAllTokens(ctx context.Context) error { + ret := _m.Called(ctx) var r0 error - if rf, ok := ret.Get(0).(func(context.Context, submitter.TransactionSubmitter) error); ok { - r0 = rf(ctx, _a1) + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) } else { r0 = ret.Error(0) } @@ -115,6 +113,20 @@ func (_m *Manager) HasSufficientGas(ctx context.Context, origin int, dest int) ( return r0, r1 } +// Rebalance provides a mock function with given fields: ctx, chainID, token +func (_m *Manager) Rebalance(ctx context.Context, chainID int, token common.Address) error { + ret := _m.Called(ctx, chainID, token) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int, common.Address) error); ok { + r0 = rf(ctx, chainID, token) + } else { + r0 = ret.Error(0) + } + + return r0 +} + type mockConstructorTestingTNewManager interface { mock.TestingT Cleanup(func()) From 3d561b00dc495b2d5ea2c1e574d816acce72cc05 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 12:38:42 -0600 Subject: [PATCH 22/75] Feat: remove handleClaimCompleted and move Rebalance() call to chainindexer --- services/rfq/relayer/service/chainindexer.go | 10 +++++++--- services/rfq/relayer/service/handlers.go | 19 ------------------- services/rfq/relayer/service/statushandler.go | 1 - 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/services/rfq/relayer/service/chainindexer.go b/services/rfq/relayer/service/chainindexer.go index 7a7c07a94b..41121c28f0 100644 --- a/services/rfq/relayer/service/chainindexer.go +++ b/services/rfq/relayer/service/chainindexer.go @@ -101,7 +101,7 @@ func (r *Relayer) runChainIndexer(ctx context.Context, chainID int) (err error) return nil } - err = r.handleDepositClaimed(ctx, event) + err = r.handleDepositClaimed(ctx, event, chainID) if err != nil { return fmt.Errorf("could not handle deposit claimed: %w", err) } @@ -199,8 +199,12 @@ type decimalsRes struct { originDecimals, destDecimals uint8 } -func (r *Relayer) handleDepositClaimed(ctx context.Context, event *fastbridge.FastBridgeBridgeDepositClaimed) error { - err := r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) +func (r *Relayer) handleDepositClaimed(ctx context.Context, event *fastbridge.FastBridgeBridgeDepositClaimed, chainID int) error { + err := r.inventory.Rebalance(ctx, chainID, event.Token) + if err != nil { + return fmt.Errorf("could not rebalance: %w", err) + } + err = r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) if err != nil { return fmt.Errorf("could not update request status: %w", err) } diff --git a/services/rfq/relayer/service/handlers.go b/services/rfq/relayer/service/handlers.go index f00d6c4f39..28e234b355 100644 --- a/services/rfq/relayer/service/handlers.go +++ b/services/rfq/relayer/service/handlers.go @@ -299,7 +299,6 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa if err != nil { return nil, fmt.Errorf("could not relay: %w", err) } - return tx, nil }) if err != nil { @@ -313,24 +312,6 @@ func (q *QuoteRequestHandler) handleProofPosted(ctx context.Context, _ trace.Spa return nil } -// handleClaimCompleted handles the claim completed status and marks the claim as completed. -// Step 9: ClaimCompleted -// -// Since this marks the completion of a RFQ bridge sequence, we check if a rebalance for the given token -// is needed, and trigger it on the inventory manager if so. -func (q *QuoteRequestHandler) handleClaimCompleted(ctx context.Context, _ trace.Span, request reldb.QuoteRequest) (err error) { - err = q.Inventory.Rebalance(ctx, int(request.Transaction.DestChainId), request.Transaction.DestToken) - if err != nil { - return fmt.Errorf("could not rebalance: %w", err) - } - - err = q.db.UpdateQuoteRequestStatus(ctx, request.TransactionID, reldb.ClaimPending) - if err != nil { - return fmt.Errorf("could not update request status: %w", err) - } - return nil -} - // Error Handlers Only from this point below. // // handleNotEnoughInventory handles the not enough inventory status. diff --git a/services/rfq/relayer/service/statushandler.go b/services/rfq/relayer/service/statushandler.go index 7902487283..945944f2de 100644 --- a/services/rfq/relayer/service/statushandler.go +++ b/services/rfq/relayer/service/statushandler.go @@ -76,7 +76,6 @@ func (r *Relayer) requestToHandler(ctx context.Context, req reldb.QuoteRequest) // no more need for deadline middleware now, we already relayed. qr.handlers[reldb.RelayCompleted] = r.gasMiddleware(qr.handleRelayCompleted) qr.handlers[reldb.ProvePosted] = qr.handleProofPosted - qr.handlers[reldb.ClaimCompleted] = qr.handleClaimCompleted // error handlers only qr.handlers[reldb.NotEnoughInventory] = r.deadlineMiddleware(qr.handleNotEnoughInventory) From e5868deb6dcbc196551df98d882530532213e95f Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 12:39:01 -0600 Subject: [PATCH 23/75] Feat: enforce rebalance methods to match from config --- services/rfq/relayer/relconfig/getters.go | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index 0dd2d1c443..bb5d5e1c01 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -360,15 +360,32 @@ func (c Config) GetHTTPTimeout() time.Duration { return time.Duration(timeoutMs) * time.Millisecond } -// GetRebalanceMethod returns the rebalance method for the given chain and token. -func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceMethod, err error) { +func (c Config) getTokenConfigByAddr(chainID int, tokenAddr string) (cfg TokenConfig, name string, err error) { chainConfig, ok := c.Chains[chainID] if !ok { - return method, fmt.Errorf("no chain config for chain %d", chainID) + return cfg, name, fmt.Errorf("no chain config for chain %d", chainID) } - tokenConfig, ok := chainConfig.Tokens[token] - if !ok { - return method, fmt.Errorf("no token config for chain %d and token %s", chainID, token) + for tokenName, tokenConfig := range chainConfig.Tokens { + if common.HexToAddress(tokenConfig.Address).Hex() == common.HexToAddress(tokenAddr).Hex() { + return tokenConfig, tokenName, nil + } + } + return cfg, name, fmt.Errorf("no token config for chain %d and address %s", chainID, tokenAddr) +} + +// GetRebalanceMethod returns the rebalance method for the given chain and token address. +func (c Config) GetRebalanceMethod(chainID int, tokenAddr string) (method RebalanceMethod, err error) { + tokenConfig, tokenName, err := c.getTokenConfigByAddr(chainID, tokenAddr) + if err != nil { + return 0, err + } + for cid, chainCfg := range c.Chains { + tokenCfg, ok := chainCfg.Tokens[tokenName] + if ok { + if tokenConfig.RebalanceMethod != tokenCfg.RebalanceMethod { + return RebalanceMethodNone, fmt.Errorf("rebalance method mismatch for token %s on chains %d and %d", tokenName, chainID, cid) + } + } } switch tokenConfig.RebalanceMethod { case "cctp": @@ -379,22 +396,9 @@ func (c Config) GetRebalanceMethod(chainID int, token string) (method RebalanceM return RebalanceMethodNone, nil } -func (c Config) getTokenConfigByAddr(chainID int, tokenAddr string) (cfg TokenConfig, err error) { - chainConfig, ok := c.Chains[chainID] - if !ok { - return cfg, fmt.Errorf("no chain config for chain %d", chainID) - } - for _, tokenConfig := range chainConfig.Tokens { - if common.HexToAddress(tokenConfig.Address).Hex() == common.HexToAddress(tokenAddr).Hex() { - return tokenConfig, nil - } - } - return cfg, fmt.Errorf("no token config for chain %d and address %s", chainID, tokenAddr) -} - // GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token address. func (c Config) GetMaintenanceBalancePct(chainID int, tokenAddr string) (float64, error) { - tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr) + tokenConfig, _, err := c.getTokenConfigByAddr(chainID, tokenAddr) if err != nil { return 0, err } @@ -406,7 +410,7 @@ func (c Config) GetMaintenanceBalancePct(chainID int, tokenAddr string) (float64 // GetInitialBalancePct returns the initial balance percentage for the given chain and token address. func (c Config) GetInitialBalancePct(chainID int, tokenAddr string) (float64, error) { - tokenConfig, err := c.getTokenConfigByAddr(chainID, tokenAddr) + tokenConfig, _, err := c.getTokenConfigByAddr(chainID, tokenAddr) if err != nil { return 0, err } From eca3b63bfbdd703bb599ae51dfe008a87c620de4 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 12:44:40 -0600 Subject: [PATCH 24/75] WIP: set rebalance params in e2e test --- services/rfq/e2e/setup_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index c4a25ac4ca..9e71118f0b 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -262,9 +262,12 @@ func (i *IntegrationSuite) setupRelayer() { // first the simple part, add the token to the token map cfg.Chains[int(backend.GetChainID())].Tokens[tokenType.Name()] = relconfig.TokenConfig{ - Address: tokenAddress, - Decimals: decimals, - PriceUSD: 1, // TODO: this will break on non-stables + Address: tokenAddress, + Decimals: decimals, + PriceUSD: 1, // TODO: this will break on non-stables + RebalanceMethod: "cctp", + MaintenanceBalancePct: 20, + InitialBalancePct: 50, } compatibleTokens := []contracts.ContractType{tokenType} From 383ece0059e5e1f936b13f27b09973aa8a97b946 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 16:18:53 -0600 Subject: [PATCH 25/75] WIP: add Rebalance interface and rebalanceManagerCCTP --- services/rfq/relayer/inventory/manager.go | 83 ++++---------- services/rfq/relayer/inventory/rebalance.go | 115 ++++++++++++++++++++ services/rfq/relayer/relconfig/getters.go | 17 +++ 3 files changed, 156 insertions(+), 59 deletions(-) create mode 100644 services/rfq/relayer/inventory/rebalance.go diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 01e2c0b0fd..a894f2f70b 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -18,7 +18,6 @@ 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" @@ -65,8 +64,8 @@ type inventoryManagerImpl struct { chainClient submitter.ClientFetcher // txSubmitter is the transaction submitter txSubmitter submitter.TransactionSubmitter - // cctpContracts is the map of cctp contracts (used for rebalancing) - cctpContracts map[int]*cctp.SynapseCCTP + // rebalanceManagers is the map of rebalance managers + rebalanceManagers map[relconfig.RebalanceMethod]RebalanceManager // db is the database db reldb.Service } @@ -149,17 +148,31 @@ const defaultPollPeriod = 5 // NewInventoryManager creates a list of tokens we should use. // 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) { + rebalanceMethods, err := cfg.GetRebalanceMethods() + if err != nil { + return nil, fmt.Errorf("could not get rebalance methods: %w", err) + } + rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager) + for method := range rebalanceMethods { + switch method { + case relconfig.RebalanceMethodCCTP: + rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db) + default: + return nil, fmt.Errorf("unsupported rebalance method: %s", method) + } + } + i := inventoryManagerImpl{ - relayerAddress: relayer, - handler: handler, - cfg: cfg, - chainClient: clientFetcher, - txSubmitter: txSubmitter, - cctpContracts: make(map[int]*cctp.SynapseCCTP), - db: db, + relayerAddress: relayer, + handler: handler, + cfg: cfg, + chainClient: clientFetcher, + txSubmitter: txSubmitter, + rebalanceManagers: rebalanceManagers, + db: db, } - err := i.initializeTokens(ctx, cfg) + err = i.initializeTokens(ctx, cfg) if err != nil { return nil, fmt.Errorf("could not initialize tokens: %w", err) } @@ -303,13 +316,6 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token } } -// RebalanceData contains metadata for a rebalance action. -type RebalanceData struct { - OriginMetadata *TokenMetadata - DestMetadata *TokenMetadata - Amount *big.Int -} - func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { maintenancePct, err := cfg.GetMaintenanceBalancePct(chainID, token.Hex()) if err != nil { @@ -374,47 +380,6 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token return rebalance, nil } -func (i *inventoryManagerImpl) rebalanceCCTP(ctx context.Context, rebalance *RebalanceData) (err error) { - // fetch the corresponding CCTP contract - contract, ok := i.cctpContracts[rebalance.DestMetadata.ChainID] - if !ok { - contractAddr, err := i.cfg.GetCCTPAddress(rebalance.OriginMetadata.ChainID) - if err != nil { - return fmt.Errorf("could not get cctp address: %w", err) - } - chainClient, err := i.chainClient.GetClient(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID))) - 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.DestMetadata.ChainID] = 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 -} - // 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) { diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go new file mode 100644 index 0000000000..8ae69ac87a --- /dev/null +++ b/services/rfq/relayer/inventory/rebalance.go @@ -0,0 +1,115 @@ +package inventory + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "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/relayer/relconfig" + "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" +) + +// RebalanceData contains metadata for a rebalance action. +type RebalanceData struct { + OriginMetadata *TokenMetadata + DestMetadata *TokenMetadata + Amount *big.Int +} + +// RebalanceManager is the interface for the rebalance manager. +type RebalanceManager interface { + // Start starts the rebalance manager. + Start(ctx context.Context) (err error) + // Execute executes a rebalance action. + Execute(ctx context.Context, rebalance *RebalanceData) error +} + +type rebalanceManagerCCTP struct { + // cfg is the config + cfg relconfig.Config + // handler is the metrics handler + handler metrics.Handler + // chainClient is an omnirpc client + chainClient submitter.ClientFetcher + // txSubmitter is the transaction submitter + txSubmitter submitter.TransactionSubmitter + // cctpContracts is the map of cctp contracts (used for rebalancing) + cctpContracts map[int]*cctp.SynapseCCTP + // relayerAddress contains the relayer address + relayerAddress common.Address + // db is the database + db reldb.Service +} + +func newRebalanceManagerCCTP(cfg relconfig.Config, handler metrics.Handler, chainClient submitter.ClientFetcher, txSubmitter submitter.TransactionSubmitter, relayerAddress common.Address, db reldb.Service) *rebalanceManagerCCTP { + return &rebalanceManagerCCTP{ + cfg: cfg, + handler: handler, + chainClient: chainClient, + txSubmitter: txSubmitter, + cctpContracts: make(map[int]*cctp.SynapseCCTP), + relayerAddress: relayerAddress, + db: db, + } +} + +func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { + for chainID := range c.cfg.Chains { + contractAddr, err := c.cfg.GetCCTPAddress(chainID) + if err != nil { + return fmt.Errorf("could not get cctp address: %w", err) + } + chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID))) + 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) + } + c.cctpContracts[chainID] = contract + } + + go func() { + for { + select { + case <-ctx.Done(): + return + } + } + }() + return nil +} + +func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *RebalanceData) (err error) { + contract, ok := c.cctpContracts[rebalance.DestMetadata.ChainID] + if !ok { + return fmt.Errorf("could not find cctp contract for chain %d", rebalance.DestMetadata.ChainID) + } + // perform rebalance by calling sendCircleToken() + _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { + tx, err = contract.SendCircleToken( + transactor, + c.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 +} diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index bb5d5e1c01..d88d3c86a8 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -396,6 +396,23 @@ func (c Config) GetRebalanceMethod(chainID int, tokenAddr string) (method Rebala return RebalanceMethodNone, nil } +// GetRebalanceMethods returns all rebalance methods present in the config. +func (c Config) GetRebalanceMethods() (methods map[RebalanceMethod]bool, err error) { + methods = make(map[RebalanceMethod]bool) + for chainID, chainCfg := range c.Chains { + for _, tokenCfg := range chainCfg.Tokens { + method, err := c.GetRebalanceMethod(chainID, tokenCfg.Address) + if err != nil { + return nil, err + } + if method != RebalanceMethodNone { + methods[method] = true + } + } + } + return methods, nil +} + // GetMaintenanceBalancePct returns the maintenance balance percentage for the given chain and token address. func (c Config) GetMaintenanceBalancePct(chainID int, tokenAddr string) (float64, error) { tokenConfig, _, err := c.getTokenConfigByAddr(chainID, tokenAddr) From 0095c7c8b620a89a28369551db8ff1ac39779ad3 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 16:23:43 -0600 Subject: [PATCH 26/75] Feat: add Start() to inventory manager --- services/rfq/relayer/inventory/manager.go | 41 ++++++++++++++--------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index a894f2f70b..ff1c16fed5 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -32,6 +32,8 @@ import ( // //go:generate go run github.com/vektra/mockery/v2 --name Manager --output ./mocks --case=underscore type Manager interface { + // Start starts the inventory manager. + Start(ctx context.Context) (err error) // GetCommittableBalance gets the total balance available for quotes // this does not include on-chain balances committed in previous quotes that may be // refunded in the event of a revert. @@ -145,7 +147,7 @@ var ( // TODO: replace w/ config. const defaultPollPeriod = 5 -// NewInventoryManager creates a list of tokens we should use. +// NewInventoryManager creates a new inventory manager. // 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) { rebalanceMethods, err := cfg.GetRebalanceMethods() @@ -177,25 +179,36 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc return nil, fmt.Errorf("could not initialize tokens: %w", err) } - // TODO: move - go func() { + return &i, nil +} + +func (i *inventoryManagerImpl) Start(ctx context.Context) error { + var g errgroup.Group + for _, rebalanceManager := range i.rebalanceManagers { + rebalanceManager := rebalanceManager + g.Go(func() error { + return rebalanceManager.Start(ctx) + }) + } + + g.Go(func() error { for { select { case <-ctx.Done(): - return + return fmt.Errorf("context canceled: %w", ctx.Err()) case <-time.After(defaultPollPeriod * time.Second): // this returning an error isn't really possible unless a config error happens // TODO: need better error handling. - err = i.refreshBalances(ctx) + err := i.refreshBalances(ctx) if err != nil { logger.Errorf("could not refresh balances") - return + return nil } } } - }() + }) - return &i, nil + return g.Wait() } const maxBatchSize = 10 @@ -305,15 +318,11 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token 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) + manager, ok := i.rebalanceManagers[method] + if !ok { + return fmt.Errorf("no rebalance manager for method: %s", method) } + return manager.Execute(ctx, rebalance) } func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { From 58c8a7f1236890466e82e4e4f49c4378116915c9 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 21 Feb 2024 17:14:44 -0600 Subject: [PATCH 27/75] Feat: add rebalance db models and functions --- .../rfq/relayer/inventory/mocks/manager.go | 14 ++++ services/rfq/relayer/listener/listener.go | 6 +- services/rfq/relayer/reldb/base/model.go | 33 +++++++++ services/rfq/relayer/reldb/base/rebalance.go | 70 +++++++++++++++++++ services/rfq/relayer/reldb/base/store.go | 2 +- services/rfq/relayer/reldb/db.go | 64 ++++++++++++++++- .../relayer/reldb/rebalancestatus_string.go | 25 +++++++ 7 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 services/rfq/relayer/reldb/base/rebalance.go create mode 100644 services/rfq/relayer/reldb/rebalancestatus_string.go diff --git a/services/rfq/relayer/inventory/mocks/manager.go b/services/rfq/relayer/inventory/mocks/manager.go index e60623a778..1e9cc66c85 100644 --- a/services/rfq/relayer/inventory/mocks/manager.go +++ b/services/rfq/relayer/inventory/mocks/manager.go @@ -127,6 +127,20 @@ func (_m *Manager) Rebalance(ctx context.Context, chainID int, token common.Addr return r0 } +// Start provides a mock function with given fields: ctx +func (_m *Manager) Start(ctx context.Context) error { + ret := _m.Called(ctx) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(ctx) + } else { + r0 = ret.Error(0) + } + + return r0 +} + type mockConstructorTestingTNewManager interface { mock.TestingT Cleanup(func()) diff --git a/services/rfq/relayer/listener/listener.go b/services/rfq/relayer/listener/listener.go index a876edf31a..719f9c9f9e 100644 --- a/services/rfq/relayer/listener/listener.go +++ b/services/rfq/relayer/listener/listener.go @@ -4,6 +4,9 @@ import ( "context" "errors" "fmt" + "math/big" + "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -17,8 +20,6 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" - "math/big" - "time" ) // ContractListener listens for chain events and calls HandleLog. @@ -91,7 +92,6 @@ func (c *chainListener) Listen(ctx context.Context, handler HandleLog) (err erro if err != nil { logger.Warn(err) } - } } } diff --git a/services/rfq/relayer/reldb/base/model.go b/services/rfq/relayer/reldb/base/model.go index c8f36bbfe9..368587f43e 100644 --- a/services/rfq/relayer/reldb/base/model.go +++ b/services/rfq/relayer/reldb/base/model.go @@ -25,6 +25,7 @@ func init() { transactionIDFieldName = namer.GetConsistentName("TransactionID") originTxHashFieldName = namer.GetConsistentName("OriginTxHash") destTxHashFieldName = namer.GetConsistentName("DestTxHash") + rebalanceIDFieldName = namer.GetConsistentName("RebalanceID") } var ( @@ -40,6 +41,8 @@ var ( originTxHashFieldName string // destTxHashFieldName is the dest tx hash field name. destTxHashFieldName string + // rebalanceIDFieldName is the rebalances id field name. + rebalanceIDFieldName string ) // LastIndexed is used to make sure we haven't missed any events while offline. @@ -112,6 +115,17 @@ type RequestForQuote struct { SendChainGas bool } +// Rebalance is the event model for a rebalance action. +type Rebalance struct { + RebalanceID sql.NullString + Origin uint64 + Destination uint64 + OriginAmount string + Status reldb.RebalanceStatus + OriginTxHash sql.NullString + DestTxHash sql.NullString +} + // FromQuoteRequest converts a quote request to an object that can be stored in the db. // TODO: add validation for deadline > uint64 // TODO: roundtripper test. @@ -141,6 +155,25 @@ func FromQuoteRequest(request reldb.QuoteRequest) RequestForQuote { } } +// FromRebalance converts a rebalance to a db object. +func FromRebalance(rebalance reldb.Rebalance) Rebalance { + var id sql.NullString + if rebalance.RebalanceID == nil { + id = sql.NullString{Valid: false} + } else { + id = sql.NullString{String: hexutil.Encode(rebalance.RebalanceID[:]), Valid: true} + } + return Rebalance{ + RebalanceID: id, + Origin: rebalance.Origin, + Destination: rebalance.Destination, + OriginAmount: rebalance.OriginAmount.String(), + Status: rebalance.Status, + OriginTxHash: stringToNullString(rebalance.OriginTxHash.String()), + DestTxHash: stringToNullString(rebalance.DestTxHash.String()), + } +} + func stringToNullString(s string) sql.NullString { if s == "" { return sql.NullString{Valid: false} diff --git a/services/rfq/relayer/reldb/base/rebalance.go b/services/rfq/relayer/reldb/base/rebalance.go new file mode 100644 index 0000000000..9d36547563 --- /dev/null +++ b/services/rfq/relayer/reldb/base/rebalance.go @@ -0,0 +1,70 @@ +package base + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" + "gorm.io/gorm" +) + +// StoreRebalance stores a rebalance action. +func (s Store) StoreRebalance(ctx context.Context, rebalance reldb.Rebalance) error { + reb := FromRebalance(rebalance) + dbTx := s.DB().WithContext(ctx).Create(&reb) + if dbTx.Error != nil { + return fmt.Errorf("could not store rebalance: %w", dbTx.Error) + } + return nil +} + +// UpdateRebalanceStatus updates the rebalance status. +func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, originTxHash *common.Hash, status reldb.RebalanceStatus) error { + var tx *gorm.DB + if originTxHash != nil { + tx = s.DB().WithContext(ctx).Model(&Rebalance{}). + Where(fmt.Sprintf("%s = ?", originTxHashFieldName), originTxHash.String()). + Updates(map[string]interface{}{ + rebalanceIDFieldName: hexutil.Encode(id[:]), + statusFieldName: status, + }) + } else { + tx = s.DB().WithContext(ctx).Model(&Rebalance{}). + Where(fmt.Sprintf("%s = ?", rebalanceIDFieldName), hexutil.Encode(id[:])). + Update(statusFieldName, status) + } + + if tx.Error != nil { + return fmt.Errorf("could not update rebalance status: %w", tx.Error) + } + return nil +} + +// HasPendingRebalance checks if there is a pending rebalance for the given chain ids +func (s Store) HasPendingRebalance(ctx context.Context, chainIDs ...uint64) (bool, error) { + var rebalances []Rebalance + + matchStatuses := []reldb.RebalanceStatus{reldb.RebalanceInitiated, reldb.RebalancePending} + inArgs := make([]int, len(matchStatuses)) + for i := range matchStatuses { + inArgs[i] = int(matchStatuses[i].Int()) + } + + // TODO: can be made more efficient by doing below check inside sql query + tx := s.DB().WithContext(ctx).Model(&Rebalance{}).Where(fmt.Sprintf("%s IN ?", statusFieldName), inArgs).Find(&rebalances) + if tx.Error != nil { + return false, fmt.Errorf("could not get db results: %w", tx.Error) + } + + // Check if any pending rebalances involve the given chain ids + for _, result := range rebalances { + for _, chainID := range chainIDs { + if result.Origin == chainID || result.Destination == chainID { + return true, nil + } + } + } + return false, nil +} diff --git a/services/rfq/relayer/reldb/base/store.go b/services/rfq/relayer/reldb/base/store.go index e153da1dee..0d1a2bf30a 100644 --- a/services/rfq/relayer/reldb/base/store.go +++ b/services/rfq/relayer/reldb/base/store.go @@ -33,7 +33,7 @@ func (s Store) SubmitterDB() submitterDB.Service { // GetAllModels gets all models to migrate // see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time func GetAllModels() (allModels []interface{}) { - allModels = append(txdb.GetAllModels(), &LastIndexed{}, &RequestForQuote{}) + allModels = append(txdb.GetAllModels(), &LastIndexed{}, &RequestForQuote{}, &Rebalance{}) return allModels } diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index c3901f190e..4dc200b6d5 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -5,6 +5,7 @@ import ( "database/sql/driver" "errors" "fmt" + "math/big" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/dbcommon" @@ -16,11 +17,16 @@ import ( type Writer interface { // PutLatestBlock upsers the latest block on a given chain id to be new height. PutLatestBlock(ctx context.Context, chainID, height uint64) error - // StoreQuoteRequest stores a quote reuquest. If one already exists, only the status will be updated + // StoreQuoteRequest stores a quote request. If one already exists, only the status will be updated // TODO: find a better way to describe this in the name StoreQuoteRequest(ctx context.Context, request QuoteRequest) error + // StoreRebalance stores a rebalance. + StoreRebalance(ctx context.Context, rebalance Rebalance) error // UpdateQuoteRequestStatus updates the status of a quote request UpdateQuoteRequestStatus(ctx context.Context, id [32]byte, status QuoteRequestStatus) error + // UpdateRebalanceStatus updates the status of a rebalance action. + // If the originTxHash is supplied, it will be used to update the ID for the corresponding rebalance model. + UpdateRebalanceStatus(ctx context.Context, id [32]byte, originTxHash *common.Hash, status RebalanceStatus) error // UpdateDestTxHash updates the dest tx hash of a quote request UpdateDestTxHash(ctx context.Context, id [32]byte, destTxHash common.Hash) error } @@ -35,6 +41,8 @@ type Reader interface { GetQuoteRequestByOriginTxHash(ctx context.Context, txHash common.Hash) (*QuoteRequest, error) // GetQuoteResultsByStatus gets quote results by status GetQuoteResultsByStatus(ctx context.Context, matchStatuses ...QuoteRequestStatus) (res []QuoteRequest, _ error) + // HasPendingRebalance checks if there is a pending rebalance for the given chain ids + HasPendingRebalance(ctx context.Context, chainIDs ...uint64) (bool, error) } // Service is the interface for the database service. @@ -149,3 +157,57 @@ func (q QuoteRequestStatus) Value() (driver.Value, error) { } var _ dbcommon.Enum = (*QuoteRequestStatus)(nil) + +// Rebalance represents a rebalance action. +type Rebalance struct { + RebalanceID *[32]byte + Origin uint64 + Destination uint64 + OriginAmount *big.Int + Status RebalanceStatus + OriginTxHash common.Hash + DestTxHash common.Hash +} + +// RebalanceStatus is the status of a rebalance action in the db. +// +//go:generate go run golang.org/x/tools/cmd/stringer -type=RebalanceStatus +type RebalanceStatus uint8 + +const ( + // RebalanceInitiated means the rebalance transaction has been initiated. + RebalanceInitiated RebalanceStatus = iota + // RebalancePending means the rebalance transaction has been confirmed on the origin. + RebalancePending + // RebalanceCompleted means the rebalance transaction has been confirmed on the destination. + RebalanceCompleted +) + +// Int returns the int value of the quote request status. +func (r RebalanceStatus) Int() uint8 { + return uint8(r) +} + +// GormDataType implements the gorm common interface for enums. +func (r RebalanceStatus) GormDataType() string { + return dbcommon.EnumDataType +} + +// Scan implements the gorm common interface for enums. +func (r *RebalanceStatus) Scan(src any) error { + res, err := dbcommon.EnumScan(src) + if err != nil { + return fmt.Errorf("could not scan %w", err) + } + newStatus := RebalanceStatus(res) + *r = newStatus + return nil +} + +// Value implements the gorm common interface for enums. +func (r RebalanceStatus) Value() (driver.Value, error) { + // nolint: wrapcheck + return dbcommon.EnumValue(r) +} + +var _ dbcommon.Enum = (*RebalanceStatus)(nil) diff --git a/services/rfq/relayer/reldb/rebalancestatus_string.go b/services/rfq/relayer/reldb/rebalancestatus_string.go new file mode 100644 index 0000000000..49d7c7df9f --- /dev/null +++ b/services/rfq/relayer/reldb/rebalancestatus_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=RebalanceStatus"; DO NOT EDIT. + +package reldb + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[RebalanceInitiated-0] + _ = x[RebalancePending-1] + _ = x[RebalanceCompleted-2] +} + +const _RebalanceStatus_name = "RebalanceInitiatedRebalancePendingRebalanceCompleted" + +var _RebalanceStatus_index = [...]uint8{0, 18, 34, 52} + +func (i RebalanceStatus) String() string { + if i >= RebalanceStatus(len(_RebalanceStatus_index)-1) { + return "RebalanceStatus(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _RebalanceStatus_name[_RebalanceStatus_index[i]:_RebalanceStatus_index[i+1]] +} From 5aeb2c52e27d595f25d9eec12eaa1789423d31a7 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 10:28:59 -0600 Subject: [PATCH 28/75] Feat: rebalance manager listens for cctp events and updates db --- services/rfq/relayer/inventory/manager.go | 2 +- services/rfq/relayer/inventory/rebalance.go | 101 ++++++++++++++++++-- 2 files changed, 94 insertions(+), 9 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index ff1c16fed5..f72325100b 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -183,7 +183,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc } func (i *inventoryManagerImpl) Start(ctx context.Context) error { - var g errgroup.Group + g, _ := errgroup.WithContext(ctx) for _, rebalanceManager := range i.rebalanceManagers { rebalanceManager := rebalanceManager g.Go(func() error { diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 8ae69ac87a..dc475eb0d9 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -11,8 +11,10 @@ import ( "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/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" + "golang.org/x/sync/errgroup" ) // RebalanceData contains metadata for a rebalance action. @@ -43,6 +45,8 @@ type rebalanceManagerCCTP struct { cctpContracts map[int]*cctp.SynapseCCTP // relayerAddress contains the relayer address relayerAddress common.Address + // chainListeners is the map of chain listeners for CCTP events + chainListeners map[int]listener.ContractListener // db is the database db reldb.Service } @@ -55,11 +59,13 @@ func newRebalanceManagerCCTP(cfg relconfig.Config, handler metrics.Handler, chai txSubmitter: txSubmitter, cctpContracts: make(map[int]*cctp.SynapseCCTP), relayerAddress: relayerAddress, + chainListeners: make(map[int]listener.ContractListener), db: db, } } func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { + // initialize contracts for chainID := range c.cfg.Chains { contractAddr, err := c.cfg.GetCCTPAddress(chainID) if err != nil { @@ -76,15 +82,34 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { c.cctpContracts[chainID] = contract } - go func() { - for { - select { - case <-ctx.Done(): - return - } + // initialize chain listeners + for chainID := range c.cfg.GetChains() { + cctpAddr, err := c.cfg.GetCCTPAddress(chainID) + if err != nil { + return fmt.Errorf("could not get cctp address: %w", err) } - }() - return nil + chainClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID))) + if err != nil { + return fmt.Errorf("could not get chain client: %w", err) + } + + chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), c.handler) + if err != nil { + return fmt.Errorf("could not get chain listener: %w", err) + } + c.chainListeners[chainID] = chainListener + } + + g, _ := errgroup.WithContext(ctx) + for cid := range c.cfg.Chains { + // capture func literal + chainID := cid + g.Go(func() error { + return c.listen(ctx, chainID) + }) + } + + return g.Wait() } func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *RebalanceData) (err error) { @@ -92,7 +117,9 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance if !ok { return fmt.Errorf("could not find cctp contract for chain %d", rebalance.DestMetadata.ChainID) } + // perform rebalance by calling sendCircleToken() + var tx *types.Transaction _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { tx, err = contract.SendCircleToken( transactor, @@ -111,5 +138,63 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance if err != nil { return fmt.Errorf("could not submit CCTP rebalance: %w", err) } + if tx == nil { + return fmt.Errorf("could not submit CCTP rebalance: tx is nil") + } + + // store the rebalance in the db + model := reldb.Rebalance{ + Origin: uint64(rebalance.OriginMetadata.ChainID), + Destination: uint64(rebalance.DestMetadata.ChainID), + OriginAmount: rebalance.Amount, + Status: reldb.RebalanceInitiated, + OriginTxHash: tx.Hash(), + } + err = c.db.StoreRebalance(ctx, model) + if err != nil { + return fmt.Errorf("could not store rebalance: %w", err) + } return nil } + +func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { + listener, ok := c.chainListeners[chainID] + if !ok { + return fmt.Errorf("could not find listener for chain %d", chainID) + } + ethClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID))) + if err != nil { + return fmt.Errorf("could not get chain client: %w", err) + } + cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress) + parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient) + if err != nil { + return fmt.Errorf("could not get cctp events: %w", err) + } + + return listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { + switch log.Topics[0] { + case cctp.CircleRequestSentTopic: + parsedEvent, err := parser.ParseCircleRequestSent(log) + if err != nil { + return fmt.Errorf("could not parse circle request sent: %w", err) + } + err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &log.TxHash, reldb.RebalancePending) + if err != nil { + return fmt.Errorf("could not update rebalance status: %w", err) + } + case cctp.CircleRequestFulfilledTopic: + parsedEvent, err := parser.ParseCircleRequestFulfilled(log) + if err != nil { + return fmt.Errorf("could not parse circle request fulfilled: %w", err) + } + err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted) + if err != nil { + return fmt.Errorf("could not update rebalance status: %w", err) + } + default: + logger.Warnf("unknown event %s", log.Topics[0]) + } + return nil + }) +} From baab0665215a5354fda6370a8cb49c91b02b19b7 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 10:51:09 -0600 Subject: [PATCH 29/75] Feat: rebalance on interval instead of on DepositClaimed --- services/rfq/relayer/inventory/manager.go | 23 ++++++++++++++++++++ services/rfq/relayer/relconfig/config.go | 5 ++++- services/rfq/relayer/relconfig/getters.go | 11 ++++++++++ services/rfq/relayer/service/chainindexer.go | 6 +---- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index f72325100b..a14817c1ff 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -191,6 +191,7 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { }) } + // continuously refresh balances g.Go(func() error { for { select { @@ -208,6 +209,28 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { } }) + // continuously check for rebalances + rebalanceInterval := i.cfg.GetRebalanceInterval() + if rebalanceInterval > 0 { + g.Go(func() error { + for { + select { + case <-ctx.Done(): + return fmt.Errorf("context canceled: %w", ctx.Err()) + case <-time.After(rebalanceInterval): + for chainID, chainConfig := range i.cfg.Chains { + for tokenName, tokenConfig := range chainConfig.Tokens { + err := i.Rebalance(ctx, chainID, common.HexToAddress(tokenConfig.Address)) + if err != nil { + logger.Errorf("could not rebalance %s on chain %d: %v", tokenName, chainID, err) + } + } + } + } + } + }) + } + return g.Wait() } diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 3811c4c932..23192caab3 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -6,6 +6,7 @@ import ( "os" "strconv" "strings" + "time" "github.com/ethereum/go-ethereum/common" "github.com/jftuga/ellipsis" @@ -44,7 +45,9 @@ type Config struct { // ScreenerAPIUrl is the TRM API key. ScreenerAPIUrl string `yaml:"screener_api_url"` // DBSelectorIntervalSeconds is the interval for the db selector. - DBSelectorIntervalSeconds int `yaml:"db_selector_interval_seconds"` + DBSelectorIntervalSeconds int `yaml:"db_selector_interval_seconds"` + // RebalanceInterval is the interval for rebalancing. + RebalanceInterval time.Duration `yaml:"rebalance_interval"` } // ChainConfig represents the configuration for a chain. diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index d88d3c86a8..a8e82f2d81 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -542,3 +542,14 @@ func (c Config) GetDBSelectorInterval() time.Duration { } return time.Duration(interval) * time.Second } + +const defaultRebalanceIntervalSeconds = 30 + +// GetRebalanceInterval returns the interval for rebalancing. +func (c Config) GetRebalanceInterval() time.Duration { + interval := c.RebalanceInterval + if interval == 0 { + interval = defaultRebalanceIntervalSeconds + } + return interval +} diff --git a/services/rfq/relayer/service/chainindexer.go b/services/rfq/relayer/service/chainindexer.go index 41121c28f0..a9aab87278 100644 --- a/services/rfq/relayer/service/chainindexer.go +++ b/services/rfq/relayer/service/chainindexer.go @@ -200,11 +200,7 @@ type decimalsRes struct { } func (r *Relayer) handleDepositClaimed(ctx context.Context, event *fastbridge.FastBridgeBridgeDepositClaimed, chainID int) error { - err := r.inventory.Rebalance(ctx, chainID, event.Token) - if err != nil { - return fmt.Errorf("could not rebalance: %w", err) - } - err = r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) + err := r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) if err != nil { return fmt.Errorf("could not update request status: %w", err) } From 752763b37012fa3761e91227329315345f701d9b Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 10:53:20 -0600 Subject: [PATCH 30/75] Cleanup: DBSelectorIntervalSeconds -> DBSelectorInterval --- services/rfq/relayer/relconfig/config.go | 6 +++--- services/rfq/relayer/relconfig/getters.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 23192caab3..9c29a00546 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -44,10 +44,10 @@ type Config struct { FeePricer FeePricerConfig `yaml:"fee_pricer"` // ScreenerAPIUrl is the TRM API key. ScreenerAPIUrl string `yaml:"screener_api_url"` - // DBSelectorIntervalSeconds is the interval for the db selector. - DBSelectorIntervalSeconds int `yaml:"db_selector_interval_seconds"` + // DBSelectorInterval is the interval for the db selector. + DBSelectorInterval time.Duration `yaml:"db_selector_interval"` // RebalanceInterval is the interval for rebalancing. - RebalanceInterval time.Duration `yaml:"rebalance_interval"` + RebalanceInterval time.Duration `yaml:"rebalance_interval"` } // ChainConfig represents the configuration for a chain. diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index a8e82f2d81..602be81f65 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -536,11 +536,11 @@ const defaultDBSelectorIntervalSeconds = 1 // GetDBSelectorInterval returns the interval for the DB selector. func (c Config) GetDBSelectorInterval() time.Duration { - interval := c.DBSelectorIntervalSeconds + interval := c.DBSelectorInterval if interval <= 0 { - return defaultDBSelectorIntervalSeconds + interval = time.Duration(defaultDBSelectorIntervalSeconds) * time.Second } - return time.Duration(interval) * time.Second + return interval } const defaultRebalanceIntervalSeconds = 30 @@ -549,7 +549,7 @@ const defaultRebalanceIntervalSeconds = 30 func (c Config) GetRebalanceInterval() time.Duration { interval := c.RebalanceInterval if interval == 0 { - interval = defaultRebalanceIntervalSeconds + interval = time.Duration(defaultRebalanceIntervalSeconds) * time.Second } return interval } From ab90a63b1dc311f2ba07ab4a775eb9e06f025f19 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 10:57:50 -0600 Subject: [PATCH 31/75] Feat: check for pending rebalances --- services/rfq/relayer/inventory/manager.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index a14817c1ff..b23ded05fb 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -218,9 +218,13 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { case <-ctx.Done(): return fmt.Errorf("context canceled: %w", ctx.Err()) case <-time.After(rebalanceInterval): + err := i.refreshBalances(ctx) + if err != nil { + return fmt.Errorf("could not refresh balances: %w", err) + } for chainID, chainConfig := range i.cfg.Chains { for tokenName, tokenConfig := range chainConfig.Tokens { - err := i.Rebalance(ctx, chainID, common.HexToAddress(tokenConfig.Address)) + err = i.Rebalance(ctx, chainID, common.HexToAddress(tokenConfig.Address)) if err != nil { logger.Errorf("could not rebalance %s on chain %d: %v", tokenName, chainID, err) } @@ -320,6 +324,7 @@ func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, des // 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 { + // evaluate the rebalance method method, err := i.cfg.GetRebalanceMethod(chainID, token.Hex()) if err != nil { return fmt.Errorf("could not get rebalance method: %w", err) @@ -328,11 +333,7 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token return nil } - err = i.refreshBalances(ctx) - if err != nil { - return fmt.Errorf("could not refresh balances: %w", err) - } - + // build the rebalance action rebalance, err := getRebalance(i.cfg, i.tokens, chainID, token) if err != nil { return fmt.Errorf("could not get rebalance: %w", err) @@ -341,6 +342,16 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token return nil } + // make sure there are no pending rebalances that touch the given path + pending, err := i.db.HasPendingRebalance(ctx, uint64(rebalance.OriginMetadata.ChainID), uint64(rebalance.DestMetadata.ChainID)) + if err != nil { + return fmt.Errorf("could not check pending rebalance: %w", err) + } + if pending { + return nil + } + + // execute the rebalance manager, ok := i.rebalanceManagers[method] if !ok { return fmt.Errorf("no rebalance manager for method: %s", method) From 989c2d5ce5e7c9b121f9883d58bdc1c953afafce Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 11:00:17 -0600 Subject: [PATCH 32/75] Feat: attempt rebalance on DepositClaimed --- services/rfq/relayer/service/chainindexer.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/rfq/relayer/service/chainindexer.go b/services/rfq/relayer/service/chainindexer.go index a9aab87278..41121c28f0 100644 --- a/services/rfq/relayer/service/chainindexer.go +++ b/services/rfq/relayer/service/chainindexer.go @@ -200,7 +200,11 @@ type decimalsRes struct { } func (r *Relayer) handleDepositClaimed(ctx context.Context, event *fastbridge.FastBridgeBridgeDepositClaimed, chainID int) error { - err := r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) + err := r.inventory.Rebalance(ctx, chainID, event.Token) + if err != nil { + return fmt.Errorf("could not rebalance: %w", err) + } + err = r.db.UpdateQuoteRequestStatus(ctx, event.TransactionId, reldb.ClaimCompleted) if err != nil { return fmt.Errorf("could not update request status: %w", err) } From 73e027d1c5d3bd5b1c7565d9f7e00b2c5b4ec8ce Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 22 Feb 2024 12:32:35 -0600 Subject: [PATCH 33/75] Cleanup: lint --- services/rfq/relayer/inventory/manager.go | 22 +++++++++++++++++---- services/rfq/relayer/inventory/rebalance.go | 5 +++-- services/rfq/relayer/reldb/db.go | 2 +- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index b23ded05fb..fa29886833 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -149,6 +149,8 @@ const defaultPollPeriod = 5 // NewInventoryManager creates a new inventory manager. // TODO: too many args here. +// +//nolint:gocognit 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) { rebalanceMethods, err := cfg.GetRebalanceMethods() if err != nil { @@ -156,6 +158,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc } rebalanceManagers := make(map[relconfig.RebalanceMethod]RebalanceManager) for method := range rebalanceMethods { + //nolint:exhaustive switch method { case relconfig.RebalanceMethodCCTP: rebalanceManagers[method] = newRebalanceManagerCCTP(cfg, handler, clientFetcher, txSubmitter, relayer, db) @@ -187,7 +190,11 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { for _, rebalanceManager := range i.rebalanceManagers { rebalanceManager := rebalanceManager g.Go(func() error { - return rebalanceManager.Start(ctx) + err := rebalanceManager.Start(ctx) + if err != nil { + return fmt.Errorf("could not start rebalance manager: %w", err) + } + return nil }) } @@ -203,6 +210,7 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { err := i.refreshBalances(ctx) if err != nil { logger.Errorf("could not refresh balances") + //nolint:nilerr return nil } } @@ -235,13 +243,17 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { }) } - return g.Wait() + err := g.Wait() + if err != nil { + return fmt.Errorf("error starting inventory manager: %w", err) + } + return nil } const maxBatchSize = 10 // ApproveAllTokens approves all checks if allowance is set and if not approves. -// nolint:gocognit,nestif +// nolint:gocognit,nestif,cyclop func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { i.mux.RLock() defer i.mux.RUnlock() @@ -359,6 +371,7 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token return manager.Execute(ctx, rebalance) } +//nolint:cyclop func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { maintenancePct, err := cfg.GetMaintenanceBalancePct(chainID, token.Hex()) if err != nil { @@ -412,6 +425,7 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) if amount.Cmp(big.NewInt(0)) < 0 { // do not rebalance since it would take us below initial threshold + //nolint:nilnil return nil, nil } rebalance = &RebalanceData{ @@ -503,7 +517,7 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r chainID := chainID // capture func literal deferredRegisters = append(deferredRegisters, func() error { - //nolint: wrapcheck + //nolint:wrapcheck return i.registerMetric(meter, chainID, token) }) } diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index dc475eb0d9..9de20bc3a9 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -157,6 +157,7 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance return nil } +// nolint:cyclop func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { listener, ok := c.chainListeners[chainID] if !ok { @@ -179,7 +180,7 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err if err != nil { return fmt.Errorf("could not parse circle request sent: %w", err) } - err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &log.TxHash, reldb.RebalancePending) + err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, &log.TxHash, reldb.RebalancePending) if err != nil { return fmt.Errorf("could not update rebalance status: %w", err) } @@ -188,7 +189,7 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err if err != nil { return fmt.Errorf("could not parse circle request fulfilled: %w", err) } - err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted) + err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted) if err != nil { return fmt.Errorf("could not update rebalance status: %w", err) } diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index 4dc200b6d5..63b26fbd14 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -41,7 +41,7 @@ type Reader interface { GetQuoteRequestByOriginTxHash(ctx context.Context, txHash common.Hash) (*QuoteRequest, error) // GetQuoteResultsByStatus gets quote results by status GetQuoteResultsByStatus(ctx context.Context, matchStatuses ...QuoteRequestStatus) (res []QuoteRequest, _ error) - // HasPendingRebalance checks if there is a pending rebalance for the given chain ids + // HasPendingRebalance checks if there is a pending rebalance for the given chain ids. HasPendingRebalance(ctx context.Context, chainIDs ...uint64) (bool, error) } From a4e308ca3e3fc7146f768fe79939be129535ca19 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 23 Feb 2024 12:13:30 -0600 Subject: [PATCH 34/75] Fix: start inventory manager --- services/rfq/relayer/service/relayer.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index 99bd3edca9..24adbce7c2 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -197,6 +197,14 @@ func (r *Relayer) Start(ctx context.Context) error { return nil }) + g.Go(func() error { + err := r.inventory.Start(ctx) + if err != nil { + return fmt.Errorf("could not start inventory manager: %w", err) + } + return nil + }) + err = g.Wait() if err != nil { return fmt.Errorf("could not start: %w", err) From 03891fb35b86d17459e270e768eaf8a82e789f6a Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 23 Feb 2024 12:38:00 -0600 Subject: [PATCH 35/75] WIP: add cctp contract setup for integration test --- services/rfq/e2e/rfq_test.go | 9 ++++-- services/rfq/e2e/setup_test.go | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index c462c0ee9e..9830910d57 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -14,6 +14,7 @@ import ( "github.com/synapsecns/sanguine/ethergo/backends/anvil" "github.com/synapsecns/sanguine/ethergo/signer/signer/localsigner" "github.com/synapsecns/sanguine/ethergo/signer/wallet" + cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil" omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" "github.com/synapsecns/sanguine/services/rfq/api/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" @@ -24,9 +25,10 @@ import ( type IntegrationSuite struct { *testsuite.TestSuite - manager *testutil.DeployManager - originBackend backends.SimulatedTestBackend - destBackend backends.SimulatedTestBackend + manager *testutil.DeployManager + cctpDeployManager *cctpTest.DeployManager + originBackend backends.SimulatedTestBackend + destBackend backends.SimulatedTestBackend //omniserver is the omnirpc server address omniServer string omniClient omnirpcClient.RPCClient @@ -68,6 +70,7 @@ func (i *IntegrationSuite) SetupTest() { } i.manager = testutil.NewDeployManager(i.T()) + i.cctpDeployManager = cctpTest.NewDeployManager(i.T()) // TODO: consider jaeger i.metrics = metrics.NewNullHandler() // setup backends for ethereum & omnirpc diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index 9e71118f0b..cdeda624a6 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" + cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil" "github.com/Flaque/filet" "github.com/ethereum/go-ethereum/accounts/abi" @@ -113,6 +114,8 @@ func (i *IntegrationSuite) setupBackends() { i.omniServer = testhelper.NewOmnirpcServer(i.GetTestContext(), i.T(), i.originBackend, i.destBackend) i.omniClient = omnirpcClient.NewOmnirpcClient(i.omniServer, i.metrics, omnirpcClient.WithCaptureReqRes()) + + i.setupCCTP() } // setupBe sets up one backend @@ -150,6 +153,45 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) { } +func (i *IntegrationSuite) setupCCTP() { + for _, backend := range core.ToSlice(i.originBackend, i.destBackend) { + cctpContract, cctpHandle := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), backend) + _, tokenMessengeHandle := i.cctpDeployManager.GetMockTokenMessengerType(i.GetTestContext(), backend) + + // on the above contract, set the remote for each backend + for _, backendToSetFrom := range core.ToSlice(i.originBackend, i.destBackend) { + // we don't need to set the backends own remote! + if backendToSetFrom.GetChainID() == backend.GetChainID() { + continue + } + + remoteCCTP, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), backendToSetFrom) + remoteMessenger, _ := i.cctpDeployManager.GetMockTokenMessengerType(i.GetTestContext(), backendToSetFrom) + + txOpts := backend.GetTxContext(i.GetTestContext(), cctpContract.OwnerPtr()) + // set the remote cctp contract on this cctp contract + // TODO: verify chainID / domain are correct + remoteDomain := cctpTest.ChainIDDomainMap[uint32(remoteCCTP.ChainID().Int64())] + + tx, err := cctpHandle.SetRemoteDomainConfig(txOpts.TransactOpts, + big.NewInt(remoteCCTP.ChainID().Int64()), remoteDomain, remoteCCTP.Address()) + i.Require().NoError(err) + backend.WaitForConfirmation(i.GetTestContext(), tx) + + // register the remote token messenger on the tokenMessenger contract + _, err = tokenMessengeHandle.SetRemoteTokenMessenger(txOpts.TransactOpts, uint32(backendToSetFrom.GetChainID()), addressToBytes32(remoteMessenger.Address())) + i.Nil(err) + } + } +} + +// addressToBytes32 converts an address to a bytes32. +func addressToBytes32(addr common.Address) [32]byte { + var buf [32]byte + copy(buf[:], addr[:]) + return buf +} + // Approve checks if the token is approved and approves it if not. func (i *IntegrationSuite) Approve(backend backends.SimulatedTestBackend, token contracts.DeployedContract, user wallet.Wallet) { erc20, err := ierc20.NewIERC20(token.Address(), backend) @@ -193,11 +235,14 @@ func (i *IntegrationSuite) setupRelayer() { relayerAPIPort, err := freeport.GetFreePort() i.NoError(err) dsn := filet.TmpDir(i.T(), "") + cctpContractOrigin, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.originBackend) + cctpContractDest, _ := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), i.destBackend) cfg := relconfig.Config{ // generated ex-post facto Chains: map[int]relconfig.ChainConfig{ originBackendChainID: { RFQAddress: i.manager.Get(i.GetTestContext(), i.originBackend, testutil.FastBridgeType).Address().String(), + CCTPAddress: cctpContractOrigin.Address().Hex(), Confirmations: 0, Tokens: map[string]relconfig.TokenConfig{ "ETH": { @@ -210,6 +255,7 @@ func (i *IntegrationSuite) setupRelayer() { }, destBackendChainID: { RFQAddress: i.manager.Get(i.GetTestContext(), i.destBackend, testutil.FastBridgeType).Address().String(), + CCTPAddress: cctpContractDest.Address().Hex(), Confirmations: 0, Tokens: map[string]relconfig.TokenConfig{ "ETH": { @@ -283,6 +329,13 @@ func (i *IntegrationSuite) setupRelayer() { cfg.QuotableTokens[quotableTokenID] = append(cfg.QuotableTokens[quotableTokenID], fmt.Sprintf("%d-%s", otherBackend.GetChainID(), otherToken)) } + + // register the token with cctp contract + cctpContract, cctpHandle := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), backend) + txOpts := backend.GetTxContext(i.GetTestContext(), cctpContract.OwnerPtr()) + tx, err := cctpHandle.AddToken(txOpts.TransactOpts, "CCTP.USDC", tokenCaller.Address(), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) + i.Require().NoError(err) + backend.WaitForConfirmation(i.GetTestContext(), tx) } } From c9ca663f391bd21dbe1952a83bc8d57e20d16395 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Mon, 26 Feb 2024 11:48:32 -0600 Subject: [PATCH 36/75] WIP: use origin instead of origin tx hash for rebalance updating --- services/rfq/relayer/inventory/rebalance.go | 3 ++- services/rfq/relayer/reldb/base/model.go | 2 ++ services/rfq/relayer/reldb/base/rebalance.go | 8 ++++---- services/rfq/relayer/reldb/db.go | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 9de20bc3a9..253956b5b2 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -180,7 +180,8 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err if err != nil { return fmt.Errorf("could not parse circle request sent: %w", err) } - err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, &log.TxHash, reldb.RebalancePending) + origin := uint64(parsedEvent.ChainId.Int64()) + err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, &origin, reldb.RebalancePending) if err != nil { return fmt.Errorf("could not update rebalance status: %w", err) } diff --git a/services/rfq/relayer/reldb/base/model.go b/services/rfq/relayer/reldb/base/model.go index 368587f43e..1321a1d833 100644 --- a/services/rfq/relayer/reldb/base/model.go +++ b/services/rfq/relayer/reldb/base/model.go @@ -117,6 +117,8 @@ type RequestForQuote struct { // Rebalance is the event model for a rebalance action. type Rebalance struct { + CreatedAt time.Time + UpdatedAt time.Time RebalanceID sql.NullString Origin uint64 Destination uint64 diff --git a/services/rfq/relayer/reldb/base/rebalance.go b/services/rfq/relayer/reldb/base/rebalance.go index 9d36547563..ce40008307 100644 --- a/services/rfq/relayer/reldb/base/rebalance.go +++ b/services/rfq/relayer/reldb/base/rebalance.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "gorm.io/gorm" @@ -21,11 +20,12 @@ func (s Store) StoreRebalance(ctx context.Context, rebalance reldb.Rebalance) er } // UpdateRebalanceStatus updates the rebalance status. -func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, originTxHash *common.Hash, status reldb.RebalanceStatus) error { +func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *uint64, status reldb.RebalanceStatus) error { var tx *gorm.DB - if originTxHash != nil { + if origin != nil { tx = s.DB().WithContext(ctx).Model(&Rebalance{}). - Where(fmt.Sprintf("%s = ?", originTxHashFieldName), originTxHash.String()). + Where(fmt.Sprintf("%s = ?", "origin"), *origin). + Where(fmt.Sprintf("%s = ?", statusFieldName), reldb.RebalanceInitiated.Int()). Updates(map[string]interface{}{ rebalanceIDFieldName: hexutil.Encode(id[:]), statusFieldName: status, diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index 63b26fbd14..3e21d50527 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -25,8 +25,8 @@ type Writer interface { // UpdateQuoteRequestStatus updates the status of a quote request UpdateQuoteRequestStatus(ctx context.Context, id [32]byte, status QuoteRequestStatus) error // UpdateRebalanceStatus updates the status of a rebalance action. - // If the originTxHash is supplied, it will be used to update the ID for the corresponding rebalance model. - UpdateRebalanceStatus(ctx context.Context, id [32]byte, originTxHash *common.Hash, status RebalanceStatus) error + // If the origin is supplied, it will be used to update the ID for the corresponding rebalance model. + UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *uint64, status RebalanceStatus) error // UpdateDestTxHash updates the dest tx hash of a quote request UpdateDestTxHash(ctx context.Context, id [32]byte, destTxHash common.Hash) error } From 81cfc797fbd355c39c053f125ddd16be7ca2d041 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Mon, 26 Feb 2024 11:52:41 -0600 Subject: [PATCH 37/75] WIP: enforce only one row affected in rebalance update --- services/rfq/relayer/reldb/base/rebalance.go | 25 ++++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/services/rfq/relayer/reldb/base/rebalance.go b/services/rfq/relayer/reldb/base/rebalance.go index ce40008307..8891c038cc 100644 --- a/services/rfq/relayer/reldb/base/rebalance.go +++ b/services/rfq/relayer/reldb/base/rebalance.go @@ -21,9 +21,15 @@ func (s Store) StoreRebalance(ctx context.Context, rebalance reldb.Rebalance) er // UpdateRebalanceStatus updates the rebalance status. func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *uint64, status reldb.RebalanceStatus) error { - var tx *gorm.DB + tx := s.DB().WithContext(ctx).Begin() + if tx.Error != nil { + return fmt.Errorf("could not start transaction: %w", tx.Error) + } + + // prepare the update transaction + var result *gorm.DB if origin != nil { - tx = s.DB().WithContext(ctx).Model(&Rebalance{}). + result = tx.Model(&Rebalance{}). Where(fmt.Sprintf("%s = ?", "origin"), *origin). Where(fmt.Sprintf("%s = ?", statusFieldName), reldb.RebalanceInitiated.Int()). Updates(map[string]interface{}{ @@ -31,13 +37,22 @@ func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *u statusFieldName: status, }) } else { - tx = s.DB().WithContext(ctx).Model(&Rebalance{}). + result = tx.Model(&Rebalance{}). Where(fmt.Sprintf("%s = ?", rebalanceIDFieldName), hexutil.Encode(id[:])). Update(statusFieldName, status) } - if tx.Error != nil { - return fmt.Errorf("could not update rebalance status: %w", tx.Error) + // commit the transaction if only one row is affected + if result.Error != nil { + tx.Rollback() + return fmt.Errorf("could not update rebalance status: %w", result.Error) + } + if result.RowsAffected != 1 { + tx.Rollback() + } + err := tx.Commit().Error + if err != nil { + return fmt.Errorf("could not commit transaction: %w", err) } return nil } From 3a6ea21a1a14bd5b8b1ea51306a884a0b18b3315 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Mon, 26 Feb 2024 11:53:45 -0600 Subject: [PATCH 38/75] WIP: don't track origin tx hash for now --- services/rfq/relayer/inventory/rebalance.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 253956b5b2..d525ae91e8 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -119,7 +119,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance } // perform rebalance by calling sendCircleToken() - var tx *types.Transaction _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { tx, err = contract.SendCircleToken( transactor, @@ -138,9 +137,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance if err != nil { return fmt.Errorf("could not submit CCTP rebalance: %w", err) } - if tx == nil { - return fmt.Errorf("could not submit CCTP rebalance: tx is nil") - } // store the rebalance in the db model := reldb.Rebalance{ @@ -148,7 +144,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance Destination: uint64(rebalance.DestMetadata.ChainID), OriginAmount: rebalance.Amount, Status: reldb.RebalanceInitiated, - OriginTxHash: tx.Hash(), } err = c.db.StoreRebalance(ctx, model) if err != nil { From 42f24ce1124346df53866566b2e3d36d27012053 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Mon, 26 Feb 2024 15:35:01 -0600 Subject: [PATCH 39/75] WIP: logs --- services/rfq/e2e/rfq_test.go | 42 ++++++++++++--------- services/rfq/e2e/setup_test.go | 6 ++- services/rfq/relayer/inventory/manager.go | 1 + services/rfq/relayer/inventory/rebalance.go | 9 ++++- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 9830910d57..03ae9b5bc4 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -1,6 +1,7 @@ package e2e_test import ( + "fmt" "math/big" "testing" "time" @@ -96,6 +97,8 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { if core.GetEnvBool("CI", false) { i.T().Skip("skipping until anvil issues are fixed in CI") } + + fmt.Printf("[cctp] omni: %v\n", i.omniClient.GetDefaultEndpoint(int(i.originBackend.GetChainID()))) // Before we do anything, we're going to mint ourselves some USDC on the destination chain. // 100k should do. i.manager.MintToAddress(i.GetTestContext(), i.destBackend, testutil.USDCType, i.relayerWallet.Address(), big.NewInt(100000)) @@ -200,6 +203,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { } return false }) + time.Sleep(10 * time.Minute) } // nolint: cyclop @@ -260,26 +264,28 @@ func (i *IntegrationSuite) TestETHtoETH() { i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) // TODO: this, but cleaner - anvilClient, err := anvil.Dial(i.GetTestContext(), i.originBackend.RPCAddress()) - i.NoError(err) - - go func() { - for { - select { - case <-i.GetTestContext().Done(): - return - case <-time.After(time.Second * 4): - // increase time by 30 mintutes every second, should be enough to get us a fastish e2e test - // we don't need to worry about deadline since we're only doing this on origin - err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30) - i.NoError(err) + for _, rpcAddr := range []string{i.originBackend.RPCAddress(), i.destBackend.RPCAddress()} { + anvilClient, err := anvil.Dial(i.GetTestContext(), rpcAddr) + i.NoError(err) - // because can claim works on last block timestamp, we need to do something - err = anvilClient.Mine(i.GetTestContext(), 1) - i.NoError(err) + go func() { + for { + select { + case <-i.GetTestContext().Done(): + return + case <-time.After(time.Second * 4): + // increase time by 30 mintutes every second, should be enough to get us a fastish e2e test + // we don't need to worry about deadline since we're only doing this on origin + err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30) + i.NoError(err) + + // because can claim works on last block timestamp, we need to do something + err = anvilClient.Mine(i.GetTestContext(), 1) + i.NoError(err) + } } - } - }() + }() + } // since relayer started w/ 0 ETH, once they're offering the inventory up on origin chain we know the workflow completed i.Eventually(func() bool { diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index cdeda624a6..6143918377 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -290,6 +290,8 @@ func (i *IntegrationSuite) setupRelayer() { TokenPriceCacheTTLSeconds: 60, }, } + fmt.Printf("config cctp origin addr: %v\n", cfg.Chains[originBackendChainID].CCTPAddress) + fmt.Printf("config cctp dest addr: %v\n", cfg.Chains[destBackendChainID].CCTPAddress) // in the first backend, we want to deploy a bunch of different tokens // TODO: functionalize me. @@ -333,9 +335,11 @@ func (i *IntegrationSuite) setupRelayer() { // register the token with cctp contract cctpContract, cctpHandle := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), backend) txOpts := backend.GetTxContext(i.GetTestContext(), cctpContract.OwnerPtr()) - tx, err := cctpHandle.AddToken(txOpts.TransactOpts, "CCTP.USDC", tokenCaller.Address(), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) + tokenName := fmt.Sprintf("CCTP.%s", tokenType.Name()) + tx, err := cctpHandle.AddToken(txOpts.TransactOpts, tokenName, tokenCaller.Address(), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) i.Require().NoError(err) backend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] added token %s on chain %d: %v, hash %v\n", tokenName, backend.GetChainID(), tokenCaller.Address().String(), tx.Hash()) } } diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index fa29886833..0747d88a93 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -301,6 +301,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { if err != nil { return nil, fmt.Errorf("could not approve cctp: %w", err) } + fmt.Printf("[cctp] approve cctp tx on chain %d, token %v: %s\n", chainID, address, tx.Hash().Hex()) return tx, nil }) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index d525ae91e8..10ae0c9f99 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -75,6 +75,7 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("could not get chain client: %w", err) } + fmt.Printf("CCTP contract addr for chain %d: %v\n", chainID, contractAddr) contract, err := cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient) if err != nil { return fmt.Errorf("could not get cctp: %w", err) @@ -113,12 +114,13 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { } func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *RebalanceData) (err error) { - contract, ok := c.cctpContracts[rebalance.DestMetadata.ChainID] + contract, ok := c.cctpContracts[rebalance.OriginMetadata.ChainID] if !ok { - return fmt.Errorf("could not find cctp contract for chain %d", rebalance.DestMetadata.ChainID) + return fmt.Errorf("could not find cctp contract for chain %d", rebalance.OriginMetadata.ChainID) } // perform rebalance by calling sendCircleToken() + fmt.Printf("[cctp] execute rebalance on chain %d and token %v\n", rebalance.OriginMetadata.ChainID, rebalance.OriginMetadata.Addr) _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { tx, err = contract.SendCircleToken( transactor, @@ -132,6 +134,7 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance if err != nil { return nil, fmt.Errorf("could not send circle token: %w", err) } + fmt.Printf("[cctp] rebalance tx: %s\n", tx.Hash().Hex()) return tx, nil }) if err != nil { @@ -154,6 +157,7 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance // nolint:cyclop func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { + fmt.Printf("listen on chain %d\n", chainID) listener, ok := c.chainListeners[chainID] if !ok { return fmt.Errorf("could not find listener for chain %d", chainID) @@ -163,6 +167,7 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err return fmt.Errorf("could not get chain client: %w", err) } cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress) + fmt.Printf("CCTP listen on chain %d: %s\n", chainID, cctpAddr.String()) parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient) if err != nil { return fmt.Errorf("could not get cctp events: %w", err) From ed841c8eb321f6432f4e2486b1066c61b1496f6a Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 12:12:32 -0600 Subject: [PATCH 40/75] WIP: working test with MockMintBurnToken --- services/rfq/e2e/rfq_test.go | 54 +++++++++++++++++++---- services/rfq/e2e/setup_test.go | 44 +++++++++++++----- services/rfq/relayer/inventory/manager.go | 53 +++++++++++++++++----- services/rfq/relayer/quoter/quoter.go | 4 ++ services/rfq/testutil/deployers.go | 5 ++- services/rfq/testutil/typecast.go | 1 + 6 files changed, 128 insertions(+), 33 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 03ae9b5bc4..50f6139a9d 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/suite" "github.com/synapsecns/sanguine/core" @@ -53,7 +55,7 @@ func TestIntegrationSuite(t *testing.T) { const ( originBackendChainID = 1 - destBackendChainID = 2 + destBackendChainID = 43114 ) // SetupTest sets up each test in the integration suite. We need to do a few things here: @@ -101,15 +103,42 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { fmt.Printf("[cctp] omni: %v\n", i.omniClient.GetDefaultEndpoint(int(i.originBackend.GetChainID()))) // Before we do anything, we're going to mint ourselves some USDC on the destination chain. // 100k should do. - i.manager.MintToAddress(i.GetTestContext(), i.destBackend, testutil.USDCType, i.relayerWallet.Address(), big.NewInt(100000)) - destUSDC := i.manager.Get(i.GetTestContext(), i.destBackend, testutil.USDCType) - i.Approve(i.destBackend, destUSDC, i.relayerWallet) + // i.manager.MintToAddress(i.GetTestContext(), i.destBackend, cctpTest.MockMintBurnTokenType, i.relayerWallet.Address(), big.NewInt(100000)) + // destUSDC := i.manager.Get(i.GetTestContext(), i.destBackend, cctpTest.MockMintBurnTokenType) + // i.Approve(i.destBackend, destUSDC, i.relayerWallet) + + opts := i.destBackend.GetTxContext(i.GetTestContext(), nil) + amount := big.NewInt(1e12) + destUSDC, destUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.destBackend) + fmt.Printf("[cctp] destUSDC addr: %v, relayer addr: %v\n", destUSDC.Address(), i.relayerWallet.Address()) + tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), amount) + i.Nil(err) + i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] mint dest tx: %v\n", tx.Hash()) + + // approve token + tx, err = destUSDCHandle.Approve(opts.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + i.Nil(err) + i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) // let's give the user some money as well, $500 should do. const userWantAmount = 500 - i.manager.MintToAddress(i.GetTestContext(), i.originBackend, testutil.USDCType, i.userWallet.Address(), big.NewInt(userWantAmount)) - originUSDC := i.manager.Get(i.GetTestContext(), i.originBackend, testutil.USDCType) - i.Approve(i.originBackend, originUSDC, i.userWallet) + // i.manager.MintToAddress(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType, i.userWallet.Address(), big.NewInt(userWantAmount)) + // originUSDC := i.manager.Get(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType) + // i.Approve(i.originBackend, originUSDC, i.userWallet) + + optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil) + originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend) + fmt.Printf("[cctp] originUSDC addr: %v, relayer addr: %v\n", originUSDC.Address(), i.relayerWallet.Address()) + tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), amount) + i.Nil(err) + i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) + + // approve token + tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + i.Nil(err) + i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) // non decimal adjusted user want amount realWantAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(userWantAmount), destUSDC.ContractHandle()) @@ -121,6 +150,13 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { userAPIClient, err := client.NewAuthenticatedClient(metrics.Get(), i.apiServer, localsigner.NewSigner(i.userWallet.PrivateKey())) i.NoError(err) + balanceDest, err := destUSDCHandle.BalanceOf(&bind.CallOpts{}, i.relayerWallet.Address()) + i.Nil(err) + fmt.Printf("[cctp] dest balance: %v\n", balanceDest) + balanceOrigin, err := originUSDCHandle.BalanceOf(&bind.CallOpts{}, i.relayerWallet.Address()) + i.Nil(err) + fmt.Printf("[cctp] origin balance: %v\n", balanceOrigin) + allQuotes, err := userAPIClient.GetAllQuotes() i.NoError(err) @@ -142,7 +178,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { _, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend) auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr()) // we want 499 usdc for 500 requested within a day - tx, err := originFastBridge.Bridge(auth.TransactOpts, fastbridge.IFastBridgeBridgeParams{ + tx, err = originFastBridge.Bridge(auth.TransactOpts, fastbridge.IFastBridgeBridgeParams{ DstChainId: uint32(i.destBackend.GetChainID()), To: i.userWallet.Address(), OriginToken: originUSDC.Address(), @@ -203,7 +239,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { } return false }) - time.Sleep(10 * time.Minute) + // time.Sleep(10 * time.Minute) } // nolint: cyclop diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index 6143918377..343841968a 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -9,7 +9,6 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" - cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil" "github.com/Flaque/filet" "github.com/ethereum/go-ethereum/accounts/abi" @@ -26,6 +25,7 @@ import ( "github.com/synapsecns/sanguine/ethergo/contracts" signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" "github.com/synapsecns/sanguine/ethergo/signer/wallet" + cctpTest "github.com/synapsecns/sanguine/services/cctp-relayer/testutil" omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" "github.com/synapsecns/sanguine/services/omnirpc/testhelper" apiConfig "github.com/synapsecns/sanguine/services/rfq/api/config" @@ -123,7 +123,7 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) { // prdeploys are contracts we want to deploy before running the test to speed it up. Obviously, these can be deployed when we need them as well, // but this way we can do something while we're waiting for the other backend to startup. // no need to wait for these to deploy since they can happen in background as soon as the backend is up. - predeployTokens := []contracts.ContractType{testutil.DAIType, testutil.USDTType, testutil.USDCType, testutil.WETH9Type} + predeployTokens := []contracts.ContractType{testutil.DAIType, testutil.USDTType, testutil.WETH9Type} predeploys := append(predeployTokens, testutil.FastBridgeType) slices.Reverse(predeploys) // return fast bridge first @@ -154,7 +154,12 @@ func (i *IntegrationSuite) setupBE(backend backends.SimulatedTestBackend) { } func (i *IntegrationSuite) setupCCTP() { - for _, backend := range core.ToSlice(i.originBackend, i.destBackend) { + // deploy the contract to all backends + testBackends := core.ToSlice(i.originBackend, i.destBackend) + i.cctpDeployManager.BulkDeploy(i.GetTestContext(), testBackends, cctpTest.SynapseCCTPType, cctpTest.MockMintBurnTokenType) + + // register remote deployments and tokens + for _, backend := range testBackends { cctpContract, cctpHandle := i.cctpDeployManager.GetSynapseCCTP(i.GetTestContext(), backend) _, tokenMessengeHandle := i.cctpDeployManager.GetMockTokenMessengerType(i.GetTestContext(), backend) @@ -177,10 +182,13 @@ func (i *IntegrationSuite) setupCCTP() { big.NewInt(remoteCCTP.ChainID().Int64()), remoteDomain, remoteCCTP.Address()) i.Require().NoError(err) backend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] backend: %d, backend to set from: %d, remote domain: %d\n", backend.GetChainID(), backendToSetFrom.GetChainID(), remoteDomain) + fmt.Printf("[cctp] remote domain config setup tx on chain %d: %v\n", backendToSetFrom.GetChainID(), tx.Hash()) // register the remote token messenger on the tokenMessenger contract _, err = tokenMessengeHandle.SetRemoteTokenMessenger(txOpts.TransactOpts, uint32(backendToSetFrom.GetChainID()), addressToBytes32(remoteMessenger.Address())) i.Nil(err) + fmt.Printf("[cctp] remote token messenger on chain %d: %v\n", backendToSetFrom.GetChainID(), tx.Hash()) } } } @@ -296,10 +304,16 @@ func (i *IntegrationSuite) setupRelayer() { // in the first backend, we want to deploy a bunch of different tokens // TODO: functionalize me. for _, backend := range core.ToSlice(i.originBackend, i.destBackend) { - tokenTypes := []contracts.ContractType{testutil.DAIType, testutil.USDTType, testutil.USDCType, testutil.WETH9Type} + tokenTypes := []contracts.ContractType{testutil.DAIType, testutil.USDTType, testutil.WETH9Type, cctpTest.MockMintBurnTokenType} for _, tokenType := range tokenTypes { - tokenAddress := i.manager.Get(i.GetTestContext(), backend, tokenType).Address().String() + useCCTP := tokenType == cctpTest.MockMintBurnTokenType + var tokenAddress string + if useCCTP { + tokenAddress = i.cctpDeployManager.Get(i.GetTestContext(), backend, cctpTest.MockMintBurnTokenType).Address().String() + } else { + tokenAddress = i.manager.Get(i.GetTestContext(), backend, tokenType).Address().String() + } quotableTokenID := fmt.Sprintf("%d-%s", backend.GetChainID(), tokenAddress) tokenCaller, err := ierc20.NewIerc20Ref(common.HexToAddress(tokenAddress), backend) @@ -308,26 +322,36 @@ func (i *IntegrationSuite) setupRelayer() { decimals, err := tokenCaller.Decimals(&bind.CallOpts{Context: i.GetTestContext()}) i.NoError(err) + rebalanceMethod := "" + if useCCTP { + rebalanceMethod = "cctp" + } + // first the simple part, add the token to the token map cfg.Chains[int(backend.GetChainID())].Tokens[tokenType.Name()] = relconfig.TokenConfig{ Address: tokenAddress, Decimals: decimals, PriceUSD: 1, // TODO: this will break on non-stables - RebalanceMethod: "cctp", + RebalanceMethod: rebalanceMethod, MaintenanceBalancePct: 20, InitialBalancePct: 50, } compatibleTokens := []contracts.ContractType{tokenType} - // DAI/USDT are fungible - if tokenType == testutil.DAIType || tokenType == testutil.USDCType { - compatibleTokens = []contracts.ContractType{testutil.DAIType, testutil.USDCType} + // DAI/USDC are fungible + if tokenType == testutil.DAIType || tokenType == cctpTest.MockMintBurnTokenType { + compatibleTokens = []contracts.ContractType{testutil.DAIType, cctpTest.MockMintBurnTokenType} } // now we need to add the token to the quotable tokens map for _, token := range compatibleTokens { otherBackend := i.getOtherBackend(backend) - otherToken := i.manager.Get(i.GetTestContext(), otherBackend, token).Address().String() + var otherToken string + if token == cctpTest.MockMintBurnTokenType { + otherToken = i.cctpDeployManager.Get(i.GetTestContext(), otherBackend, cctpTest.MockMintBurnTokenType).Address().String() + } else { + otherToken = i.manager.Get(i.GetTestContext(), otherBackend, token).Address().String() + } cfg.QuotableTokens[quotableTokenID] = append(cfg.QuotableTokens[quotableTokenID], fmt.Sprintf("%d-%s", otherBackend.GetChainID(), otherToken)) } diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 0747d88a93..5dee1048f6 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -128,13 +128,14 @@ func (i *inventoryManagerImpl) GetCommittableBalances(ctx context.Context, optio // TokenMetadata contains metadata for a token. type TokenMetadata struct { - Name string - Balance *big.Int - Decimals uint8 - StartAllowance *big.Int - IsGasToken bool - ChainID int - Addr common.Address + Name string + Balance *big.Int + Decimals uint8 + StartAllowanceRFQ *big.Int + StartAllowanceCCTP *big.Int + IsGasToken bool + ChainID int + Addr common.Address } var ( @@ -145,7 +146,7 @@ var ( ) // TODO: replace w/ config. -const defaultPollPeriod = 5 +const defaultPollPeriod = 0 // NewInventoryManager creates a new inventory manager. // TODO: too many args here. @@ -204,7 +205,7 @@ func (i *inventoryManagerImpl) Start(ctx context.Context) error { select { case <-ctx.Done(): return fmt.Errorf("context canceled: %w", ctx.Err()) - case <-time.After(defaultPollPeriod * time.Second): + case <-time.After(250 * time.Millisecond): // this returning an error isn't really possible unless a config error happens // TODO: need better error handling. err := i.refreshBalances(ctx) @@ -266,7 +267,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { for address, token := range tokenMap { // if startAllowance is 0 - if address != chain.EthAddress && token.StartAllowance.Cmp(big.NewInt(0)) == 0 { + if address != chain.EthAddress && token.StartAllowanceRFQ.Cmp(big.NewInt(0)) == 0 { chainID := chainID // capture func literal address := address // capture func literal @@ -290,6 +291,16 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { if err != nil { return fmt.Errorf("could not submit RFQ approval: %w", err) } + } + + if address != chain.EthAddress && token.StartAllowanceCCTP.Cmp(big.NewInt(0)) == 0 { + chainID := chainID // capture func literal + address := address // capture func literal + + erc20, err := ierc20.NewIERC20(address, backendClient) + if err != nil { + return fmt.Errorf("could not get erc20: %w", err) + } // approve CCTP bridge, if configured _, err = i.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(chainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { @@ -496,7 +507,8 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r // requires non-nil pointer rtoken.Balance = new(big.Int) - rtoken.StartAllowance = new(big.Int) + rtoken.StartAllowanceRFQ = new(big.Int) + rtoken.StartAllowanceCCTP = new(big.Int) if rtoken.IsGasToken { rtoken.Decimals = 18 @@ -508,11 +520,16 @@ func (i *inventoryManagerImpl) initializeTokens(parentCtx context.Context, cfg r if err != nil { return fmt.Errorf("could not get rfq address: %w", err) } + cctpAddr, err := cfg.GetCCTPAddress(chainID) + if err != nil { + return fmt.Errorf("could not get cctp 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(rfqAddr)).Returns(rtoken.StartAllowance), + eth.CallFunc(funcAllowance, token, i.relayerAddress, common.HexToAddress(rfqAddr)).Returns(rtoken.StartAllowanceRFQ), + eth.CallFunc(funcAllowance, token, i.relayerAddress, common.HexToAddress(cctpAddr)).Returns(rtoken.StartAllowanceCCTP), ) } @@ -568,6 +585,7 @@ var logger = log.Logger("inventory") // refreshBalances refreshes all the token balances. func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { + fmt.Printf("[cctp] refreshBalances on addr %v\n", i.relayerAddress.String()) i.mux.Lock() defer i.mux.Unlock() var wg sync.WaitGroup @@ -588,6 +606,17 @@ func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { for tokenAddress, token := range tokenMap { // TODO: make sure Returns does nothing on error if !token.IsGasToken { + fmt.Printf("[cctp] creating deferred call for token %v on chain %d, addr %v\n", tokenAddress.String(), chainID, i.relayerAddress.String()) + erc20, err := ierc20.NewIERC20(tokenAddress, chainClient) + if err != nil { + fmt.Printf("[cctp] could not get erc20: %v\n", err) + } else { + balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, i.relayerAddress) + if err != nil { + fmt.Printf("[cctp] error getting balance: %v\n", err) + } + fmt.Printf("[cctp] got balance for token %v on chain %v: %v\n", tokenAddress.String(), chainID, balance) + } deferredCalls = append(deferredCalls, eth.CallFunc(funcBalanceOf, tokenAddress, i.relayerAddress).Returns(token.Balance)) } } diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index efab9e2915..eb3a32b042 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -215,6 +215,7 @@ func (m *Manager) prepareAndSubmitQuotes(ctx context.Context, inv map[int]map[co // First, generate all quotes for chainID, balances := range inv { for address, balance := range balances { + fmt.Printf("[cctp] generating quotes for token %v on chain %v: %v\n", address.Hex(), chainID, balance.String()) quotes, err := m.generateQuotes(ctx, chainID, address, balance) if err != nil { return err @@ -244,12 +245,14 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo } destTokenID := fmt.Sprintf("%d-%s", chainID, address.Hex()) + fmt.Printf("[cctp] destTokenID: %v\n", destTokenID) var quotes []model.PutQuoteRequest for keyTokenID, itemTokenIDs := range m.quotableTokens { for _, tokenID := range itemTokenIDs { //nolint:nestif if tokenID == destTokenID { + fmt.Printf("[cctp] destTokenID found: %v\n", destTokenID) // Parse token info originStr := strings.Split(keyTokenID, "-")[0] origin, err := strconv.Atoi(originStr) @@ -298,6 +301,7 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo DestFastBridgeAddress: destRFQAddr, } quotes = append(quotes, quote) + fmt.Printf("[cctp], sumbitting quote with amount %v, balance %v from chain %d to chain %d, tokens %v, %v\n", quote.DestAmount, balance.String(), quote.OriginChainID, quote.DestChainID, quote.OriginTokenAddr, quote.DestTokenAddr) } } } diff --git a/services/rfq/testutil/deployers.go b/services/rfq/testutil/deployers.go index 92aeb37484..4d0522d0e1 100644 --- a/services/rfq/testutil/deployers.go +++ b/services/rfq/testutil/deployers.go @@ -3,6 +3,9 @@ package testutil import ( "context" "fmt" + "math/big" + "testing" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -13,8 +16,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/manager" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/fastbridgemock" - "math/big" - "testing" ) // DeployManager wraps DeployManager and allows typed contract handles to be returned. diff --git a/services/rfq/testutil/typecast.go b/services/rfq/testutil/typecast.go index a18a6563e9..fd096d497b 100644 --- a/services/rfq/testutil/typecast.go +++ b/services/rfq/testutil/typecast.go @@ -2,6 +2,7 @@ package testutil import ( "context" + "github.com/synapsecns/sanguine/ethergo/backends" "github.com/synapsecns/sanguine/ethergo/contracts" "github.com/synapsecns/sanguine/ethergo/manager" From 628733dfef8a26d87633e70428117f50ecbbbc60 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 12:17:05 -0600 Subject: [PATCH 41/75] WIP: adjust amounts --- services/rfq/e2e/rfq_test.go | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 50f6139a9d..f9d43349e2 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -107,11 +107,17 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { // destUSDC := i.manager.Get(i.GetTestContext(), i.destBackend, cctpTest.MockMintBurnTokenType) // i.Approve(i.destBackend, destUSDC, i.relayerWallet) + const startAmount = 1000 + const rfqAmount = 900 + opts := i.destBackend.GetTxContext(i.GetTestContext(), nil) - amount := big.NewInt(1e12) destUSDC, destUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.destBackend) + realStartAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(startAmount), destUSDC.ContractHandle()) + i.NoError(err) + realRFQAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(rfqAmount), destUSDC.ContractHandle()) + i.NoError(err) fmt.Printf("[cctp] destUSDC addr: %v, relayer addr: %v\n", destUSDC.Address(), i.relayerWallet.Address()) - tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), amount) + tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), realStartAmount) i.Nil(err) i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) fmt.Printf("[cctp] mint dest tx: %v\n", tx.Hash()) @@ -122,7 +128,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) // let's give the user some money as well, $500 should do. - const userWantAmount = 500 // i.manager.MintToAddress(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType, i.userWallet.Address(), big.NewInt(userWantAmount)) // originUSDC := i.manager.Get(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType) // i.Approve(i.originBackend, originUSDC, i.userWallet) @@ -130,7 +135,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil) originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend) fmt.Printf("[cctp] originUSDC addr: %v, relayer addr: %v\n", originUSDC.Address(), i.relayerWallet.Address()) - tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), amount) + tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), realStartAmount) i.Nil(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) @@ -141,9 +146,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) // non decimal adjusted user want amount - realWantAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(userWantAmount), destUSDC.ContractHandle()) - i.NoError(err) - // now our friendly user is going to check the quote and send us some USDC on the origin chain. i.Eventually(func() bool { // first he's gonna check the quotes. @@ -164,7 +166,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { for _, quote := range allQuotes { if common.HexToAddress(quote.DestTokenAddr) == destUSDC.Address() { destAmountBigInt, _ := new(big.Int).SetString(quote.DestAmount, 10) - if destAmountBigInt.Cmp(realWantAmount) > 0 { + if destAmountBigInt.Cmp(realRFQAmount) > 0 { // we found our quote! // now we can move on return true @@ -184,8 +186,8 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { OriginToken: originUSDC.Address(), SendChainGas: true, DestToken: destUSDC.Address(), - OriginAmount: realWantAmount, - DestAmount: new(big.Int).Sub(realWantAmount, big.NewInt(10_000_000)), + OriginAmount: realRFQAmount, + DestAmount: new(big.Int).Sub(realRFQAmount, big.NewInt(10_000_000)), Deadline: new(big.Int).SetInt64(time.Now().Add(time.Hour * 24).Unix()), }) i.NoError(err) From efaf3bda3d0b43472484270901941584776acde7 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 13:06:48 -0600 Subject: [PATCH 42/75] WIP: fix approvals --- services/rfq/e2e/rfq_test.go | 30 ++++++++++++++++----- services/rfq/e2e/setup_test.go | 1 + services/rfq/relayer/inventory/manager.go | 26 +++++++++--------- services/rfq/relayer/inventory/rebalance.go | 5 ++-- services/rfq/relayer/quoter/quoter.go | 8 +++--- 5 files changed, 44 insertions(+), 26 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index f9d43349e2..4c4d63aea4 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/suite" @@ -123,15 +122,17 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { fmt.Printf("[cctp] mint dest tx: %v\n", tx.Hash()) // approve token - tx, err = destUSDCHandle.Approve(opts.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) - i.Nil(err) - i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) + i.Approve(i.destBackend, destUSDC, i.relayerWallet) + // tx, err = destUSDCHandle.Approve(opts.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + // i.Nil(err) + // i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) // let's give the user some money as well, $500 should do. // i.manager.MintToAddress(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType, i.userWallet.Address(), big.NewInt(userWantAmount)) // originUSDC := i.manager.Get(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType) // i.Approve(i.originBackend, originUSDC, i.userWallet) + // add initial USDC to relayer on origin optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil) originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend) fmt.Printf("[cctp] originUSDC addr: %v, relayer addr: %v\n", originUSDC.Address(), i.relayerWallet.Address()) @@ -141,9 +142,23 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) // approve token - tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + i.Approve(i.originBackend, originUSDC, i.relayerWallet) + // tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + // i.Nil(err) + // i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) + + // add initial USDC to user on origin + tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.userWallet.Address(), realRFQAmount) i.Nil(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) + + // approve token + i.Approve(i.originBackend, originUSDC, i.userWallet) + // tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.userWallet.Address(), core.CopyBigInt(abi.MaxUint256)) + // i.Nil(err) + // i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) + // fmt.Printf("[cctp] user approve tx: %v for address %v\n", tx.Hash(), i.userWallet.Address()) // non decimal adjusted user want amount // now our friendly user is going to check the quote and send us some USDC on the origin chain. @@ -192,6 +207,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { }) i.NoError(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) + fmt.Printf("[cctp] fastBridge tx: %v\n", tx.Hash()) // TODO: this, but cleaner anvilClient, err := anvil.Dial(i.GetTestContext(), i.originBackend.RPCAddress()) @@ -232,7 +248,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { // we should now have some usdc on the origin chain since we claimed // this should be offered up as inventory destAmountBigInt, _ := new(big.Int).SetString(quote.DestAmount, 10) - if destAmountBigInt.Cmp(big.NewInt(0)) > 0 { + if destAmountBigInt.Cmp(realStartAmount) > 0 { // we found our quote! // now we can move on return true @@ -241,7 +257,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { } return false }) - // time.Sleep(10 * time.Minute) + time.Sleep(10 * time.Minute) } // nolint: cyclop diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index 343841968a..4417bf351d 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -297,6 +297,7 @@ func (i *IntegrationSuite) setupRelayer() { GasPriceCacheTTLSeconds: 60, TokenPriceCacheTTLSeconds: 60, }, + RebalanceInterval: 0, } fmt.Printf("config cctp origin addr: %v\n", cfg.Chains[originBackendChainID].CCTPAddress) fmt.Printf("config cctp dest addr: %v\n", cfg.Chains[destBackendChainID].CCTPAddress) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 5dee1048f6..2da2ef542d 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -312,7 +312,7 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { if err != nil { return nil, fmt.Errorf("could not approve cctp: %w", err) } - fmt.Printf("[cctp] approve cctp tx on chain %d, token %v: %s\n", chainID, address, tx.Hash().Hex()) + // fmt.Printf("[cctp] approve cctp tx on chain %d, token %v: %s\n", chainID, address, tx.Hash().Hex()) return tx, nil }) @@ -585,7 +585,7 @@ var logger = log.Logger("inventory") // refreshBalances refreshes all the token balances. func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { - fmt.Printf("[cctp] refreshBalances on addr %v\n", i.relayerAddress.String()) + // fmt.Printf("[cctp] refreshBalances on addr %v\n", i.relayerAddress.String()) i.mux.Lock() defer i.mux.Unlock() var wg sync.WaitGroup @@ -606,17 +606,17 @@ func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { for tokenAddress, token := range tokenMap { // TODO: make sure Returns does nothing on error if !token.IsGasToken { - fmt.Printf("[cctp] creating deferred call for token %v on chain %d, addr %v\n", tokenAddress.String(), chainID, i.relayerAddress.String()) - erc20, err := ierc20.NewIERC20(tokenAddress, chainClient) - if err != nil { - fmt.Printf("[cctp] could not get erc20: %v\n", err) - } else { - balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, i.relayerAddress) - if err != nil { - fmt.Printf("[cctp] error getting balance: %v\n", err) - } - fmt.Printf("[cctp] got balance for token %v on chain %v: %v\n", tokenAddress.String(), chainID, balance) - } + // fmt.Printf("[cctp] creating deferred call for token %v on chain %d, addr %v\n", tokenAddress.String(), chainID, i.relayerAddress.String()) + // erc20, err := ierc20.NewIERC20(tokenAddress, chainClient) + // if err != nil { + // // fmt.Printf("[cctp] could not get erc20: %v\n", err) + // } else { + // // balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, i.relayerAddress) + // // if err != nil { + // // // fmt.Printf("[cctp] error getting balance: %v\n", err) + // // } + // // fmt.Printf("[cctp] got balance for token %v on chain %v: %v\n", tokenAddress.String(), chainID, balance) + // } deferredCalls = append(deferredCalls, eth.CallFunc(funcBalanceOf, tokenAddress, i.relayerAddress).Returns(token.Balance)) } } diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 10ae0c9f99..a79d084c4f 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -157,7 +157,7 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance // nolint:cyclop func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { - fmt.Printf("listen on chain %d\n", chainID) + fmt.Printf("[cctp] listen on chain %d\n", chainID) listener, ok := c.chainListeners[chainID] if !ok { return fmt.Errorf("could not find listener for chain %d", chainID) @@ -167,13 +167,14 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err return fmt.Errorf("could not get chain client: %w", err) } cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress) - fmt.Printf("CCTP listen on chain %d: %s\n", chainID, cctpAddr.String()) + fmt.Printf("[cctp] CCTP listen on chain %d: %s\n", chainID, cctpAddr.String()) parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient) if err != nil { return fmt.Errorf("could not get cctp events: %w", err) } return listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { + fmt.Printf("[cctp] got log: %v\n", log.Topics[0]) switch log.Topics[0] { case cctp.CircleRequestSentTopic: parsedEvent, err := parser.ParseCircleRequestSent(log) diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index eb3a32b042..c09e8522f1 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -215,7 +215,7 @@ func (m *Manager) prepareAndSubmitQuotes(ctx context.Context, inv map[int]map[co // First, generate all quotes for chainID, balances := range inv { for address, balance := range balances { - fmt.Printf("[cctp] generating quotes for token %v on chain %v: %v\n", address.Hex(), chainID, balance.String()) + // fmt.Printf("[cctp] generating quotes for token %v on chain %v: %v\n", address.Hex(), chainID, balance.String()) quotes, err := m.generateQuotes(ctx, chainID, address, balance) if err != nil { return err @@ -245,14 +245,14 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo } destTokenID := fmt.Sprintf("%d-%s", chainID, address.Hex()) - fmt.Printf("[cctp] destTokenID: %v\n", destTokenID) + // fmt.Printf("[cctp] destTokenID: %v\n", destTokenID) var quotes []model.PutQuoteRequest for keyTokenID, itemTokenIDs := range m.quotableTokens { for _, tokenID := range itemTokenIDs { //nolint:nestif if tokenID == destTokenID { - fmt.Printf("[cctp] destTokenID found: %v\n", destTokenID) + // fmt.Printf("[cctp] destTokenID found: %v\n", destTokenID) // Parse token info originStr := strings.Split(keyTokenID, "-")[0] origin, err := strconv.Atoi(originStr) @@ -301,7 +301,7 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo DestFastBridgeAddress: destRFQAddr, } quotes = append(quotes, quote) - fmt.Printf("[cctp], sumbitting quote with amount %v, balance %v from chain %d to chain %d, tokens %v, %v\n", quote.DestAmount, balance.String(), quote.OriginChainID, quote.DestChainID, quote.OriginTokenAddr, quote.DestTokenAddr) + // fmt.Printf("[cctp], sumbitting quote with amount %v, balance %v from chain %d to chain %d, tokens %v, %v\n", quote.DestAmount, balance.String(), quote.OriginChainID, quote.DestChainID, quote.OriginTokenAddr, quote.DestTokenAddr) } } } From 1e7175f52a0468ad252e0ee59d404bdcbb85eace Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 13:46:07 -0600 Subject: [PATCH 43/75] Feat: remove fastbridge ref from chain listener --- services/rfq/relayer/inventory/rebalance.go | 3 +- services/rfq/relayer/listener/export_test.go | 21 ++++---- services/rfq/relayer/listener/listener.go | 51 +++++++------------ .../rfq/relayer/listener/listener_test.go | 6 ++- services/rfq/relayer/listener/suite_test.go | 31 ++++++----- services/rfq/relayer/relapi/server.go | 16 +++++- services/rfq/relayer/service/relayer.go | 12 ++++- 7 files changed, 78 insertions(+), 62 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index a79d084c4f..c5c38210af 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -94,7 +94,8 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { return fmt.Errorf("could not get chain client: %w", err) } - chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), c.handler) + //TODO: configure start block? + chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), 0, c.handler) if err != nil { return fmt.Errorf("could not get chain listener: %w", err) } diff --git a/services/rfq/relayer/listener/export_test.go b/services/rfq/relayer/listener/export_test.go index 060e7611c1..469ae09670 100644 --- a/services/rfq/relayer/listener/export_test.go +++ b/services/rfq/relayer/listener/export_test.go @@ -2,10 +2,10 @@ package listener import ( "context" + "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/ethergo/client" - "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) @@ -21,18 +21,19 @@ func (c chainListener) GetMetadata(ctx context.Context) (startBlock, chainID uin } type TestChainListenerArgs struct { - Address common.Address - Client client.EVM - Contract *fastbridge.FastBridgeRef - Store reldb.Service - Handler metrics.Handler + Address common.Address + InitialBlock uint64 + Client client.EVM + Store reldb.Service + Handler metrics.Handler } func NewTestChainListener(args TestChainListenerArgs) TestChainListener { return &chainListener{ - client: args.Client, - contract: args.Contract, - store: args.Store, - handler: args.Handler, + client: args.Client, + address: args.Address, + initialBlock: args.InitialBlock, + store: args.Store, + handler: args.Handler, } } diff --git a/services/rfq/relayer/listener/listener.go b/services/rfq/relayer/listener/listener.go index 719f9c9f9e..0872f543a8 100644 --- a/services/rfq/relayer/listener/listener.go +++ b/services/rfq/relayer/listener/listener.go @@ -8,14 +8,12 @@ import ( "time" "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ipfs/go-log" "github.com/jpillora/backoff" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/ethergo/client" - "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" @@ -38,11 +36,12 @@ type ContractListener interface { type HandleLog func(ctx context.Context, log types.Log) error type chainListener struct { - client client.EVM - contract *fastbridge.FastBridgeRef - store reldb.Service - handler metrics.Handler - backoff *backoff.Backoff + client client.EVM + address common.Address + initialBlock uint64 + store reldb.Service + handler metrics.Handler + backoff *backoff.Backoff // IMPORTANT! These fields cannot be used until they has been set. They are NOT // set in the constructor startBlock, chainID, latestBlock uint64 @@ -53,18 +52,14 @@ type chainListener struct { var logger = log.Logger("chainlistener-logger") // NewChainListener creates a new chain listener. -func NewChainListener(omnirpcClient client.EVM, store reldb.Service, address common.Address, handler metrics.Handler) (ContractListener, error) { - fastBridge, err := fastbridge.NewFastBridgeRef(address, omnirpcClient) - if err != nil { - return nil, fmt.Errorf("could not create fast bridge contract: %w", err) - } - +func NewChainListener(omnirpcClient client.EVM, store reldb.Service, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) { return &chainListener{ - handler: handler, - store: store, - client: omnirpcClient, - contract: fastBridge, - backoff: newBackoffConfig(), + handler: handler, + address: address, + initialBlock: initialBlock, + store: store, + client: omnirpcClient, + backoff: newBackoffConfig(), }, nil } @@ -97,7 +92,7 @@ func (c *chainListener) Listen(ctx context.Context, handler HandleLog) (err erro } func (c *chainListener) Address() common.Address { - return c.contract.Address() + return c.address } func (c *chainListener) LatestBlock() uint64 { @@ -164,7 +159,7 @@ func (c *chainListener) doPoll(parentCtx context.Context, handler HandleLog) (er } func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chainID uint64, err error) { - var deployBlock, lastIndexed uint64 + var lastIndexed uint64 ctx, span := c.handler.Tracer().Start(parentCtx, "getMetadata") defer func() { @@ -174,16 +169,6 @@ func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chain // TODO: consider some kind of backoff here in case rpcs are down at boot. // this becomes more of an issue as we add more chains g, ctx := errgroup.WithContext(ctx) - g.Go(func() error { - deployBlock, err := c.contract.DeployBlock(&bind.CallOpts{Context: ctx}) - if err != nil { - return fmt.Errorf("could not get deploy block: %w", err) - } - - startBlock = deployBlock.Uint64() - return nil - }) - g.Go(func() error { // TODO: one thing I've been going back and forth on is whether or not this method should be chain aware // passing in the chain ID would allow us to pull everything directly from the config, but be less testable @@ -213,8 +198,10 @@ func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chain return 0, 0, fmt.Errorf("could not get metadata: %w", err) } - if lastIndexed > deployBlock { + if lastIndexed > c.startBlock { startBlock = lastIndexed + } else { + startBlock = c.initialBlock } return startBlock, chainID, nil @@ -233,6 +220,6 @@ func (c chainListener) buildFilterQuery(fromBlock, toBlock uint64) ethereum.Filt return ethereum.FilterQuery{ FromBlock: new(big.Int).SetUint64(fromBlock), ToBlock: new(big.Int).SetUint64(toBlock), - Addresses: []common.Address{c.contract.Address()}, + Addresses: []common.Address{c.address}, } } diff --git a/services/rfq/relayer/listener/listener_test.go b/services/rfq/relayer/listener/listener_test.go index 0797bc9833..a7268efa4e 100644 --- a/services/rfq/relayer/listener/listener_test.go +++ b/services/rfq/relayer/listener/listener_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/brianvoe/gofakeit/v6" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -72,7 +73,10 @@ func (l *ListenerTestSuite) TestListenForEvents() { wg.Wait() - cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), l.metrics) + startBlock, err := handle.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) + l.NoError(err) + + cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), uint64(startBlock.Int64()), l.metrics) l.NoError(err) eventCount := 0 diff --git a/services/rfq/relayer/listener/suite_test.go b/services/rfq/relayer/listener/suite_test.go index 91cb814351..32248de399 100644 --- a/services/rfq/relayer/listener/suite_test.go +++ b/services/rfq/relayer/listener/suite_test.go @@ -1,9 +1,11 @@ package listener_test import ( + "math/big" + "testing" + "github.com/Flaque/filet" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/suite" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/core/testsuite" @@ -15,8 +17,6 @@ import ( "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/sqlite" "github.com/synapsecns/sanguine/services/rfq/testutil" - "math/big" - "testing" ) const chainID = 10 @@ -55,31 +55,30 @@ func (l *ListenerTestSuite) SetupTest() { } func (l *ListenerTestSuite) TestGetMetadataNoStore() { + deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) + l.NoError(err) + // nothing stored, should use start block cl := listener.NewTestChainListener(listener.TestChainListenerArgs{ - Address: common.Address{}, - Client: l.backend, - Contract: l.fastBridge, - Store: l.store, - Handler: l.metrics, + Address: l.fastBridge.Address(), + InitialBlock: deployBlock.Uint64(), + Client: l.backend, + Store: l.store, + Handler: l.metrics, }) startBlock, myChainID, err := cl.GetMetadata(l.GetTestContext()) l.NoError(err) l.Equal(myChainID, uint64(chainID)) - - deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) - l.NoError(err) l.Equal(startBlock, deployBlock.Uint64()) } func (l *ListenerTestSuite) TestStartBlock() { cl := listener.NewTestChainListener(listener.TestChainListenerArgs{ - Address: common.Address{}, - Client: l.backend, - Contract: l.fastBridge, - Store: l.store, - Handler: l.metrics, + Address: l.fastBridge.Address(), + Client: l.backend, + Store: l.store, + Handler: l.metrics, }) deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go index aea0865112..b520805fd1 100644 --- a/services/rfq/relayer/relapi/server.go +++ b/services/rfq/relayer/relapi/server.go @@ -9,11 +9,13 @@ import ( "github.com/synapsecns/sanguine/core/ginhelper" "github.com/synapsecns/sanguine/ethergo/submitter" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" "github.com/synapsecns/sanguine/core/metrics" baseServer "github.com/synapsecns/sanguine/core/server" omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" @@ -59,7 +61,19 @@ func NewRelayerAPI( if err != nil { return nil, fmt.Errorf("could not create omnirpc client: %w", err) } - chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(chainCfg.RFQAddress), handler) + rfqAddr, err := cfg.GetRFQAddress(chainID) + if err != nil { + return nil, fmt.Errorf("could not get rfq address: %w", err) + } + contract, err := fastbridge.NewFastBridgeRef(common.HexToAddress(rfqAddr), chainClient) + if err != nil { + return nil, fmt.Errorf("could not create fast bridge contract: %w", err) + } + startBlock, err := contract.DeployBlock(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, fmt.Errorf("could not get deploy block: %w", err) + } + chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(rfqAddr), uint64(startBlock.Int64()), handler) if err != nil { return nil, fmt.Errorf("could not get chain listener: %w", err) } diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index 24adbce7c2..c97142d190 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -6,6 +6,7 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ipfs/go-log" "github.com/jellydator/ttlcache/v3" @@ -15,6 +16,7 @@ import ( "github.com/synapsecns/sanguine/ethergo/signer/signer" "github.com/synapsecns/sanguine/ethergo/submitter" omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" + "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/inventory" "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/pricer" @@ -72,7 +74,15 @@ func NewRelayer(ctx context.Context, metricHandler metrics.Handler, cfg relconfi return nil, fmt.Errorf("could not get chain client: %w", err) } - chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(rfqAddr), metricHandler) + contract, err := fastbridge.NewFastBridgeRef(common.HexToAddress(rfqAddr), chainClient) + if err != nil { + return nil, fmt.Errorf("could not create fast bridge contract: %w", err) + } + startBlock, err := contract.DeployBlock(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, fmt.Errorf("could not get deploy block: %w", err) + } + chainListener, err := listener.NewChainListener(chainClient, store, common.HexToAddress(rfqAddr), uint64(startBlock.Int64()), metricHandler) if err != nil { return nil, fmt.Errorf("could not get chain listener: %w", err) } From 5c99c21c53a695d6b6f9088e195d4b3124b03337 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 14:25:03 -0600 Subject: [PATCH 44/75] Feat: use iota+1 for rebalance status, regen model --- services/rfq/relayer/reldb/base/rebalance.go | 1 + services/rfq/relayer/reldb/db.go | 2 +- services/rfq/relayer/reldb/rebalancestatus_string.go | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/services/rfq/relayer/reldb/base/rebalance.go b/services/rfq/relayer/reldb/base/rebalance.go index 8891c038cc..3658370ce8 100644 --- a/services/rfq/relayer/reldb/base/rebalance.go +++ b/services/rfq/relayer/reldb/base/rebalance.go @@ -49,6 +49,7 @@ func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *u } if result.RowsAffected != 1 { tx.Rollback() + return fmt.Errorf("expected 1 row to be affected, got %d", result.RowsAffected) } err := tx.Commit().Error if err != nil { diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index 3e21d50527..9dbb1b4328 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -176,7 +176,7 @@ type RebalanceStatus uint8 const ( // RebalanceInitiated means the rebalance transaction has been initiated. - RebalanceInitiated RebalanceStatus = iota + RebalanceInitiated RebalanceStatus = iota + 1 // RebalancePending means the rebalance transaction has been confirmed on the origin. RebalancePending // RebalanceCompleted means the rebalance transaction has been confirmed on the destination. diff --git a/services/rfq/relayer/reldb/rebalancestatus_string.go b/services/rfq/relayer/reldb/rebalancestatus_string.go index 49d7c7df9f..7808a7cf49 100644 --- a/services/rfq/relayer/reldb/rebalancestatus_string.go +++ b/services/rfq/relayer/reldb/rebalancestatus_string.go @@ -8,9 +8,9 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[RebalanceInitiated-0] - _ = x[RebalancePending-1] - _ = x[RebalanceCompleted-2] + _ = x[RebalanceInitiated-1] + _ = x[RebalancePending-2] + _ = x[RebalanceCompleted-3] } const _RebalanceStatus_name = "RebalanceInitiatedRebalancePendingRebalanceCompleted" @@ -18,8 +18,9 @@ const _RebalanceStatus_name = "RebalanceInitiatedRebalancePendingRebalanceComple var _RebalanceStatus_index = [...]uint8{0, 18, 34, 52} func (i RebalanceStatus) String() string { + i -= 1 if i >= RebalanceStatus(len(_RebalanceStatus_index)-1) { - return "RebalanceStatus(" + strconv.FormatInt(int64(i), 10) + ")" + return "RebalanceStatus(" + strconv.FormatInt(int64(i+1), 10) + ")" } return _RebalanceStatus_name[_RebalanceStatus_index[i]:_RebalanceStatus_index[i+1]] } From 8fcfbcb3e44deca61ddc2f044fbf3f7aae0a6bfe Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 14:25:17 -0600 Subject: [PATCH 45/75] WIP: don't return error in listener handler --- services/rfq/relayer/inventory/rebalance.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index c5c38210af..76b275578a 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -175,26 +175,30 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err } return listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { - fmt.Printf("[cctp] got log: %v\n", log.Topics[0]) + fmt.Printf("[cctp] got log: %v with hash %v, blocknumber %v\n", log.Topics[0], log.TxHash, log.BlockNumber) switch log.Topics[0] { case cctp.CircleRequestSentTopic: parsedEvent, err := parser.ParseCircleRequestSent(log) if err != nil { - return fmt.Errorf("could not parse circle request sent: %w", err) + logger.Warnf("could not parse circle request sent: %w", err) + return nil } - origin := uint64(parsedEvent.ChainId.Int64()) + origin := uint64(chainID) err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, &origin, reldb.RebalancePending) if err != nil { - return fmt.Errorf("could not update rebalance status: %w", err) + logger.Warnf("could not update rebalance status: %w", err) + return nil } case cctp.CircleRequestFulfilledTopic: parsedEvent, err := parser.ParseCircleRequestFulfilled(log) if err != nil { - return fmt.Errorf("could not parse circle request fulfilled: %w", err) + logger.Warnf("could not parse circle request fulfilled: %w", err) + return nil } err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted) if err != nil { - return fmt.Errorf("could not update rebalance status: %w", err) + logger.Warnf("could not update rebalance status: %w", err) + return nil } default: logger.Warnf("unknown event %s", log.Topics[0]) From 88de62adbea0c3eaf4c01c60f979338556fd8449 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:06:26 -0600 Subject: [PATCH 46/75] Feat: check for pending rebalances in integration test --- services/rfq/e2e/rfq_test.go | 37 +++++++++++++++++++++++++++++++++- services/rfq/e2e/setup_test.go | 6 ++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 4c4d63aea4..2fe427d197 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -21,6 +21,7 @@ import ( "github.com/synapsecns/sanguine/services/rfq/api/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" + "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "github.com/synapsecns/sanguine/services/rfq/relayer/service" "github.com/synapsecns/sanguine/services/rfq/testutil" ) @@ -35,6 +36,7 @@ type IntegrationSuite struct { omniServer string omniClient omnirpcClient.RPCClient metrics metrics.Handler + store reldb.Service apiServer string relayer *service.Relayer relayerWallet wallet.Wallet @@ -191,6 +193,15 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { return false }) + go func() { + for { + balance, err := originUSDCHandle.BalanceOf(&bind.CallOpts{Context: i.GetTestContext()}, i.relayerWallet.Address()) + i.NoError(err) + fmt.Printf("origin usdc balance: %v\n", balance.String()) + time.Sleep(1 * time.Second) + } + }() + // now we can send the money _, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend) auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr()) @@ -257,7 +268,31 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { } return false }) - time.Sleep(10 * time.Minute) + + i.Eventually(func() bool { + // check to see if the USDC balance has decreased on destination due to rebalance + balance, err := originUSDCHandle.BalanceOf(&bind.CallOpts{Context: i.GetTestContext()}, i.relayerWallet.Address()) + i.NoError(err) + fmt.Printf("got relayer origin balance: %v\n", balance) + balanceThresh, _ := new(big.Float).Mul(big.NewFloat(1.5), new(big.Float).SetInt(realStartAmount)).Int(nil) + if balance.Cmp(balanceThresh) > 0 { + return false + } + + // check to see if there is a pending rebalance from the destination back to origin + // TODO: validate more of the rebalance- expose in db interface just for testing? + destPending, err := i.store.HasPendingRebalance(i.GetTestContext(), uint64(i.destBackend.GetChainID())) + i.NoError(err) + if !destPending { + return false + } + originPending, err := i.store.HasPendingRebalance(i.GetTestContext(), uint64(i.originBackend.GetChainID())) + i.NoError(err) + if !originPending { + return false + } + return true + }) } // nolint: cyclop diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index 4417bf351d..a9b595e957 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -34,6 +34,7 @@ import ( "github.com/synapsecns/sanguine/services/rfq/contracts/ierc20" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" + "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/connect" "github.com/synapsecns/sanguine/services/rfq/relayer/service" "github.com/synapsecns/sanguine/services/rfq/testutil" ) @@ -382,4 +383,9 @@ func (i *IntegrationSuite) setupRelayer() { go func() { err = i.relayer.Start(i.GetTestContext()) }() + + dbType, err := dbcommon.DBTypeFromString(cfg.Database.Type) + i.NoError(err) + i.store, err = connect.Connect(i.GetTestContext(), dbType, cfg.Database.DSN, i.metrics) + i.NoError(err) } From ce32aa9abc7a6d12d993a00732fdb54173b41e41 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:06:54 -0600 Subject: [PATCH 47/75] Cleanup: remove balance logs --- services/rfq/e2e/rfq_test.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 2fe427d197..2abb70f7fa 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -193,15 +193,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { return false }) - go func() { - for { - balance, err := originUSDCHandle.BalanceOf(&bind.CallOpts{Context: i.GetTestContext()}, i.relayerWallet.Address()) - i.NoError(err) - fmt.Printf("origin usdc balance: %v\n", balance.String()) - time.Sleep(1 * time.Second) - } - }() - // now we can send the money _, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend) auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr()) From 819dd14a3fae72bd8633d96ad7a54a63b4bcb79a Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:11:53 -0600 Subject: [PATCH 48/75] Cleanup: logs / comments --- services/rfq/e2e/rfq_test.go | 47 ++------------------- services/rfq/e2e/setup_test.go | 6 --- services/rfq/relayer/inventory/manager.go | 13 ------ services/rfq/relayer/inventory/rebalance.go | 6 --- services/rfq/relayer/quoter/quoter.go | 4 -- 5 files changed, 3 insertions(+), 73 deletions(-) diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 2abb70f7fa..0a8e376271 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -1,7 +1,6 @@ package e2e_test import ( - "fmt" "math/big" "testing" "time" @@ -101,66 +100,35 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { i.T().Skip("skipping until anvil issues are fixed in CI") } - fmt.Printf("[cctp] omni: %v\n", i.omniClient.GetDefaultEndpoint(int(i.originBackend.GetChainID()))) - // Before we do anything, we're going to mint ourselves some USDC on the destination chain. - // 100k should do. - // i.manager.MintToAddress(i.GetTestContext(), i.destBackend, cctpTest.MockMintBurnTokenType, i.relayerWallet.Address(), big.NewInt(100000)) - // destUSDC := i.manager.Get(i.GetTestContext(), i.destBackend, cctpTest.MockMintBurnTokenType) - // i.Approve(i.destBackend, destUSDC, i.relayerWallet) - + // load token contracts const startAmount = 1000 const rfqAmount = 900 - opts := i.destBackend.GetTxContext(i.GetTestContext(), nil) destUSDC, destUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.destBackend) realStartAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(startAmount), destUSDC.ContractHandle()) i.NoError(err) realRFQAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(rfqAmount), destUSDC.ContractHandle()) i.NoError(err) - fmt.Printf("[cctp] destUSDC addr: %v, relayer addr: %v\n", destUSDC.Address(), i.relayerWallet.Address()) + + // add initial usdc to relayer on destination tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), realStartAmount) i.Nil(err) i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] mint dest tx: %v\n", tx.Hash()) - - // approve token i.Approve(i.destBackend, destUSDC, i.relayerWallet) - // tx, err = destUSDCHandle.Approve(opts.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) - // i.Nil(err) - // i.destBackend.WaitForConfirmation(i.GetTestContext(), tx) - - // let's give the user some money as well, $500 should do. - // i.manager.MintToAddress(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType, i.userWallet.Address(), big.NewInt(userWantAmount)) - // originUSDC := i.manager.Get(i.GetTestContext(), i.originBackend, cctpTest.MockMintBurnTokenType) - // i.Approve(i.originBackend, originUSDC, i.userWallet) // add initial USDC to relayer on origin optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil) originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend) - fmt.Printf("[cctp] originUSDC addr: %v, relayer addr: %v\n", originUSDC.Address(), i.relayerWallet.Address()) tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), realStartAmount) i.Nil(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) - - // approve token i.Approve(i.originBackend, originUSDC, i.relayerWallet) - // tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.relayerWallet.Address(), core.CopyBigInt(abi.MaxUint256)) - // i.Nil(err) - // i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) // add initial USDC to user on origin tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.userWallet.Address(), realRFQAmount) i.Nil(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] mint origin tx: %v\n", tx.Hash()) - - // approve token i.Approve(i.originBackend, originUSDC, i.userWallet) - // tx, err = originUSDCHandle.Approve(optsOrigin.TransactOpts, i.userWallet.Address(), core.CopyBigInt(abi.MaxUint256)) - // i.Nil(err) - // i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) - // fmt.Printf("[cctp] user approve tx: %v for address %v\n", tx.Hash(), i.userWallet.Address()) // non decimal adjusted user want amount // now our friendly user is going to check the quote and send us some USDC on the origin chain. @@ -169,13 +137,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { userAPIClient, err := client.NewAuthenticatedClient(metrics.Get(), i.apiServer, localsigner.NewSigner(i.userWallet.PrivateKey())) i.NoError(err) - balanceDest, err := destUSDCHandle.BalanceOf(&bind.CallOpts{}, i.relayerWallet.Address()) - i.Nil(err) - fmt.Printf("[cctp] dest balance: %v\n", balanceDest) - balanceOrigin, err := originUSDCHandle.BalanceOf(&bind.CallOpts{}, i.relayerWallet.Address()) - i.Nil(err) - fmt.Printf("[cctp] origin balance: %v\n", balanceOrigin) - allQuotes, err := userAPIClient.GetAllQuotes() i.NoError(err) @@ -209,7 +170,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { }) i.NoError(err) i.originBackend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] fastBridge tx: %v\n", tx.Hash()) // TODO: this, but cleaner anvilClient, err := anvil.Dial(i.GetTestContext(), i.originBackend.RPCAddress()) @@ -264,7 +224,6 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { // check to see if the USDC balance has decreased on destination due to rebalance balance, err := originUSDCHandle.BalanceOf(&bind.CallOpts{Context: i.GetTestContext()}, i.relayerWallet.Address()) i.NoError(err) - fmt.Printf("got relayer origin balance: %v\n", balance) balanceThresh, _ := new(big.Float).Mul(big.NewFloat(1.5), new(big.Float).SetInt(realStartAmount)).Int(nil) if balance.Cmp(balanceThresh) > 0 { return false diff --git a/services/rfq/e2e/setup_test.go b/services/rfq/e2e/setup_test.go index a9b595e957..11cc2aa62c 100644 --- a/services/rfq/e2e/setup_test.go +++ b/services/rfq/e2e/setup_test.go @@ -183,13 +183,10 @@ func (i *IntegrationSuite) setupCCTP() { big.NewInt(remoteCCTP.ChainID().Int64()), remoteDomain, remoteCCTP.Address()) i.Require().NoError(err) backend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] backend: %d, backend to set from: %d, remote domain: %d\n", backend.GetChainID(), backendToSetFrom.GetChainID(), remoteDomain) - fmt.Printf("[cctp] remote domain config setup tx on chain %d: %v\n", backendToSetFrom.GetChainID(), tx.Hash()) // register the remote token messenger on the tokenMessenger contract _, err = tokenMessengeHandle.SetRemoteTokenMessenger(txOpts.TransactOpts, uint32(backendToSetFrom.GetChainID()), addressToBytes32(remoteMessenger.Address())) i.Nil(err) - fmt.Printf("[cctp] remote token messenger on chain %d: %v\n", backendToSetFrom.GetChainID(), tx.Hash()) } } } @@ -300,8 +297,6 @@ func (i *IntegrationSuite) setupRelayer() { }, RebalanceInterval: 0, } - fmt.Printf("config cctp origin addr: %v\n", cfg.Chains[originBackendChainID].CCTPAddress) - fmt.Printf("config cctp dest addr: %v\n", cfg.Chains[destBackendChainID].CCTPAddress) // in the first backend, we want to deploy a bunch of different tokens // TODO: functionalize me. @@ -365,7 +360,6 @@ func (i *IntegrationSuite) setupRelayer() { tx, err := cctpHandle.AddToken(txOpts.TransactOpts, tokenName, tokenCaller.Address(), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0)) i.Require().NoError(err) backend.WaitForConfirmation(i.GetTestContext(), tx) - fmt.Printf("[cctp] added token %s on chain %d: %v, hash %v\n", tokenName, backend.GetChainID(), tokenCaller.Address().String(), tx.Hash()) } } diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 2da2ef542d..a1959e37f1 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -312,7 +312,6 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { if err != nil { return nil, fmt.Errorf("could not approve cctp: %w", err) } - // fmt.Printf("[cctp] approve cctp tx on chain %d, token %v: %s\n", chainID, address, tx.Hash().Hex()) return tx, nil }) @@ -585,7 +584,6 @@ var logger = log.Logger("inventory") // refreshBalances refreshes all the token balances. func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { - // fmt.Printf("[cctp] refreshBalances on addr %v\n", i.relayerAddress.String()) i.mux.Lock() defer i.mux.Unlock() var wg sync.WaitGroup @@ -606,17 +604,6 @@ func (i *inventoryManagerImpl) refreshBalances(ctx context.Context) error { for tokenAddress, token := range tokenMap { // TODO: make sure Returns does nothing on error if !token.IsGasToken { - // fmt.Printf("[cctp] creating deferred call for token %v on chain %d, addr %v\n", tokenAddress.String(), chainID, i.relayerAddress.String()) - // erc20, err := ierc20.NewIERC20(tokenAddress, chainClient) - // if err != nil { - // // fmt.Printf("[cctp] could not get erc20: %v\n", err) - // } else { - // // balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, i.relayerAddress) - // // if err != nil { - // // // fmt.Printf("[cctp] error getting balance: %v\n", err) - // // } - // // fmt.Printf("[cctp] got balance for token %v on chain %v: %v\n", tokenAddress.String(), chainID, balance) - // } deferredCalls = append(deferredCalls, eth.CallFunc(funcBalanceOf, tokenAddress, i.relayerAddress).Returns(token.Balance)) } } diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 76b275578a..47ba01b84d 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -75,7 +75,6 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("could not get chain client: %w", err) } - fmt.Printf("CCTP contract addr for chain %d: %v\n", chainID, contractAddr) contract, err := cctp.NewSynapseCCTP(common.HexToAddress(contractAddr), chainClient) if err != nil { return fmt.Errorf("could not get cctp: %w", err) @@ -121,7 +120,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance } // perform rebalance by calling sendCircleToken() - fmt.Printf("[cctp] execute rebalance on chain %d and token %v\n", rebalance.OriginMetadata.ChainID, rebalance.OriginMetadata.Addr) _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { tx, err = contract.SendCircleToken( transactor, @@ -135,7 +133,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance if err != nil { return nil, fmt.Errorf("could not send circle token: %w", err) } - fmt.Printf("[cctp] rebalance tx: %s\n", tx.Hash().Hex()) return tx, nil }) if err != nil { @@ -158,7 +155,6 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance // nolint:cyclop func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { - fmt.Printf("[cctp] listen on chain %d\n", chainID) listener, ok := c.chainListeners[chainID] if !ok { return fmt.Errorf("could not find listener for chain %d", chainID) @@ -168,14 +164,12 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err return fmt.Errorf("could not get chain client: %w", err) } cctpAddr := common.HexToAddress(c.cfg.Chains[chainID].CCTPAddress) - fmt.Printf("[cctp] CCTP listen on chain %d: %s\n", chainID, cctpAddr.String()) parser, err := cctp.NewSynapseCCTPEvents(cctpAddr, ethClient) if err != nil { return fmt.Errorf("could not get cctp events: %w", err) } return listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { - fmt.Printf("[cctp] got log: %v with hash %v, blocknumber %v\n", log.Topics[0], log.TxHash, log.BlockNumber) switch log.Topics[0] { case cctp.CircleRequestSentTopic: parsedEvent, err := parser.ParseCircleRequestSent(log) diff --git a/services/rfq/relayer/quoter/quoter.go b/services/rfq/relayer/quoter/quoter.go index c09e8522f1..efab9e2915 100644 --- a/services/rfq/relayer/quoter/quoter.go +++ b/services/rfq/relayer/quoter/quoter.go @@ -215,7 +215,6 @@ func (m *Manager) prepareAndSubmitQuotes(ctx context.Context, inv map[int]map[co // First, generate all quotes for chainID, balances := range inv { for address, balance := range balances { - // fmt.Printf("[cctp] generating quotes for token %v on chain %v: %v\n", address.Hex(), chainID, balance.String()) quotes, err := m.generateQuotes(ctx, chainID, address, balance) if err != nil { return err @@ -245,14 +244,12 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo } destTokenID := fmt.Sprintf("%d-%s", chainID, address.Hex()) - // fmt.Printf("[cctp] destTokenID: %v\n", destTokenID) var quotes []model.PutQuoteRequest for keyTokenID, itemTokenIDs := range m.quotableTokens { for _, tokenID := range itemTokenIDs { //nolint:nestif if tokenID == destTokenID { - // fmt.Printf("[cctp] destTokenID found: %v\n", destTokenID) // Parse token info originStr := strings.Split(keyTokenID, "-")[0] origin, err := strconv.Atoi(originStr) @@ -301,7 +298,6 @@ func (m *Manager) generateQuotes(ctx context.Context, chainID int, address commo DestFastBridgeAddress: destRFQAddr, } quotes = append(quotes, quote) - // fmt.Printf("[cctp], sumbitting quote with amount %v, balance %v from chain %d to chain %d, tokens %v, %v\n", quote.DestAmount, balance.String(), quote.OriginChainID, quote.DestChainID, quote.OriginTokenAddr, quote.DestTokenAddr) } } } From 5f4486bd35f90b3320db132094d8318457bcb026 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:46:54 -0600 Subject: [PATCH 49/75] Cleanup: dedup code in inv manager --- services/rfq/relayer/inventory/manager.go | 82 +++++++++++------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index a1959e37f1..1836caba0a 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -17,6 +17,7 @@ import ( "github.com/lmittmann/w3/w3types" "github.com/synapsecns/sanguine/core" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/client" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/services/rfq/contracts/ierc20" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" @@ -146,7 +147,7 @@ var ( ) // TODO: replace w/ config. -const defaultPollPeriod = 0 +const defaultPollPeriod = 5 // NewInventoryManager creates a new inventory manager. // TODO: too many args here. @@ -186,6 +187,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc return &i, nil } +//nolint:gocognit func (i *inventoryManagerImpl) Start(ctx context.Context) error { g, _ := errgroup.WithContext(ctx) for _, rebalanceManager := range i.rebalanceManagers { @@ -266,57 +268,31 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { } for address, token := range tokenMap { - // if startAllowance is 0 + // approve RFQ contract. + // Note: in the case where submitter hasn't finished from last boot, + // this will double submit approvals unfortunately. if address != chain.EthAddress && token.StartAllowanceRFQ.Cmp(big.NewInt(0)) == 0 { - chainID := chainID // capture func literal - address := address // capture func literal - - erc20, err := ierc20.NewIERC20(address, backendClient) + tokenAddr := address // capture func literal + contractAddr, err := i.cfg.GetRFQAddress(chainID) if err != nil { - return fmt.Errorf("could not get erc20: %w", err) + return fmt.Errorf("could not get RFQ address: %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 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 - }) + err = i.approve(ctx, tokenAddr, common.HexToAddress(contractAddr), backendClient) if err != nil { - return fmt.Errorf("could not submit RFQ approval: %w", err) + return fmt.Errorf("could not approve RFQ contract: %w", err) } } + // approve CCTP contract if address != chain.EthAddress && token.StartAllowanceCCTP.Cmp(big.NewInt(0)) == 0 { - chainID := chainID // capture func literal - address := address // capture func literal - - erc20, err := ierc20.NewIERC20(address, backendClient) + tokenAddr := address // capture func literal + contractAddr, err := i.cfg.GetCCTPAddress(chainID) if err != nil { - return fmt.Errorf("could not get erc20: %w", err) + return fmt.Errorf("could not get CCTP address: %w", err) } - - // 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 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 tx, nil - }) + err = i.approve(ctx, tokenAddr, common.HexToAddress(contractAddr), backendClient) if err != nil { - return fmt.Errorf("could not submit CCTP approval: %w", err) + return fmt.Errorf("could not approve CCTP contract: %w", err) } } } @@ -324,6 +300,30 @@ func (i *inventoryManagerImpl) ApproveAllTokens(ctx context.Context) error { return nil } +// approve submits an ERC20 approval for a given token and contract address. +func (i *inventoryManagerImpl) approve(ctx context.Context, tokenAddr, contractAddr common.Address, backendClient client.EVM) (err error) { + erc20, err := ierc20.NewIERC20(tokenAddr, backendClient) + if err != nil { + return fmt.Errorf("could not get erc20: %w", err) + } + chainID, err := backendClient.ChainID(ctx) + if err != nil { + return fmt.Errorf("could not get chain id: %w", err) + } + + _, err = i.txSubmitter.SubmitTransaction(ctx, chainID, func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { + tx, err = erc20.Approve(transactor, contractAddr, abi.MaxInt256) + if err != nil { + return nil, fmt.Errorf("could not approve: %w", err) + } + return tx, nil + }) + if err != nil { + return fmt.Errorf("could not submit approval: %w", err) + } + return nil +} + // HasSufficientGas checks if there is sufficient gas for a given route. func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, dest int) (sufficient bool, err error) { gasThresh, err := i.cfg.GetMinGasToken(dest) From c8f2c6700442a1fc592b8d4b27a93bbd82cf35ff Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:50:50 -0600 Subject: [PATCH 50/75] Cleanup: lints --- services/rfq/relayer/inventory/manager.go | 6 +++++- services/rfq/relayer/inventory/rebalance.go | 12 ++++++++++-- services/rfq/relayer/relapi/server.go | 2 ++ services/rfq/relayer/reldb/base/rebalance.go | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 1836caba0a..01fdcaf8fd 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -379,7 +379,11 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token if !ok { return fmt.Errorf("no rebalance manager for method: %s", method) } - return manager.Execute(ctx, rebalance) + err = manager.Execute(ctx, rebalance) + if err != nil { + return fmt.Errorf("could not execute rebalance: %w", err) + } + return nil } //nolint:cyclop diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 47ba01b84d..073a7ce18f 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -110,7 +110,11 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { }) } - return g.Wait() + err := g.Wait() + if err != nil { + return fmt.Errorf("error listening to contract: %w", err) + } + return nil } func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *RebalanceData) (err error) { @@ -169,7 +173,7 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err return fmt.Errorf("could not get cctp events: %w", err) } - return listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { + err = listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { switch log.Topics[0] { case cctp.CircleRequestSentTopic: parsedEvent, err := parser.ParseCircleRequestSent(log) @@ -199,4 +203,8 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err } return nil }) + if err != nil { + return fmt.Errorf("could not listen to contract: %w", err) + } + return nil } diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go index b520805fd1..e3cb376ebe 100644 --- a/services/rfq/relayer/relapi/server.go +++ b/services/rfq/relayer/relapi/server.go @@ -34,6 +34,8 @@ type RelayerAPIServer struct { // NewRelayerAPI holds the configuration, database connection, gin engine, RPC client, metrics handler, and fast bridge contracts. // It is used to initialize and run the API server. +// +//nolint:cyclop func NewRelayerAPI( ctx context.Context, cfg relconfig.Config, diff --git a/services/rfq/relayer/reldb/base/rebalance.go b/services/rfq/relayer/reldb/base/rebalance.go index 3658370ce8..e899b37679 100644 --- a/services/rfq/relayer/reldb/base/rebalance.go +++ b/services/rfq/relayer/reldb/base/rebalance.go @@ -58,7 +58,7 @@ func (s Store) UpdateRebalanceStatus(ctx context.Context, id [32]byte, origin *u return nil } -// HasPendingRebalance checks if there is a pending rebalance for the given chain ids +// HasPendingRebalance checks if there is a pending rebalance for the given chain ids. func (s Store) HasPendingRebalance(ctx context.Context, chainIDs ...uint64) (bool, error) { var rebalances []Rebalance From b7dd419663ac71814277ebaf8b3223c0ef033869 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:58:24 -0600 Subject: [PATCH 51/75] Feat: add CCTPStartBlock to relconfig --- services/rfq/relayer/inventory/rebalance.go | 8 +++++--- services/rfq/relayer/relconfig/config.go | 2 ++ services/rfq/relayer/relconfig/getters.go | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 073a7ce18f..a1e0c63a44 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -92,9 +92,11 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { if err != nil { return fmt.Errorf("could not get chain client: %w", err) } - - //TODO: configure start block? - chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), 0, c.handler) + initialBlock, err := c.cfg.GetCCTPStartBlock(chainID) + if err != nil { + return fmt.Errorf("could not get cctp start block: %w", err) + } + chainListener, err := listener.NewChainListener(chainClient, c.db, common.HexToAddress(cctpAddr), initialBlock, c.handler) if err != nil { return fmt.Errorf("could not get chain listener: %w", err) } diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 9c29a00546..86347d8ab4 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -82,6 +82,8 @@ type ChainConfig struct { QuoteOffsetBps float64 `yaml:"quote_offset_bps"` // FixedFeeMultiplier is the multiplier for the fixed fee. FixedFeeMultiplier float64 `yaml:"fixed_fee_multiplier"` + // CCTP start block is the block at which the chain listener will listen for CCTP events. + CCTPStartBlock uint64 `yaml:"cctp_start_block"` } // TokenConfig represents the configuration for a token. diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index 602be81f65..d0d805a20f 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -295,6 +295,23 @@ func (c Config) GetFixedFeeMultiplier(chainID int) (value float64, err error) { return value, nil } +// GetCCTPStartBlock returns the CCTPStartBlock for the given chainID. +func (c Config) GetCCTPStartBlock(chainID int) (value uint64, err error) { + rawValue, err := c.getChainConfigValue(chainID, "CCTPStartBlock") + if err != nil { + return value, err + } + + value, ok := rawValue.(uint64) + if !ok { + return value, fmt.Errorf("failed to cast CCTPStartBlock to int") + } + if value < 0 { + return 0, fmt.Errorf("invalid CCTPStartBlock: %d", value) + } + return value, nil +} + // GetL1FeeParams returns the L1 fee params for the given chain. func (c Config) GetL1FeeParams(chainID uint32, origin bool) (uint32, int, bool) { var gasEstimate int From 53e8129a8bdb2771964ca00c3b3b4aaaba8ab5a3 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 15:58:32 -0600 Subject: [PATCH 52/75] [goreleaser] From 2e33da368947abbafebdef3fffad778a929aef40 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 16:08:18 -0600 Subject: [PATCH 53/75] Cleanup: lints --- services/rfq/relayer/inventory/rebalance.go | 48 +++++++++++++-------- services/rfq/relayer/relconfig/getters.go | 3 -- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index a1e0c63a44..442dde112e 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -64,8 +64,34 @@ func newRebalanceManagerCCTP(cfg relconfig.Config, handler metrics.Handler, chai } } -func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { - // initialize contracts +func (c *rebalanceManagerCCTP) Start(ctx context.Context) (err error) { + err = c.initContracts(ctx) + if err != nil { + return fmt.Errorf("could not initialize contracts: %w", err) + } + + err = c.initListeners(ctx) + if err != nil { + return fmt.Errorf("could not initialize listeners: %w", err) + } + + g, _ := errgroup.WithContext(ctx) + for cid := range c.cfg.Chains { + // capture func literal + chainID := cid + g.Go(func() error { + return c.listen(ctx, chainID) + }) + } + + err = g.Wait() + if err != nil { + return fmt.Errorf("error listening to contract: %w", err) + } + return nil +} + +func (c *rebalanceManagerCCTP) initContracts(ctx context.Context) (err error) { for chainID := range c.cfg.Chains { contractAddr, err := c.cfg.GetCCTPAddress(chainID) if err != nil { @@ -81,8 +107,10 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { } c.cctpContracts[chainID] = contract } + return nil +} - // initialize chain listeners +func (c *rebalanceManagerCCTP) initListeners(ctx context.Context) (err error) { for chainID := range c.cfg.GetChains() { cctpAddr, err := c.cfg.GetCCTPAddress(chainID) if err != nil { @@ -102,20 +130,6 @@ func (c *rebalanceManagerCCTP) Start(ctx context.Context) error { } c.chainListeners[chainID] = chainListener } - - g, _ := errgroup.WithContext(ctx) - for cid := range c.cfg.Chains { - // capture func literal - chainID := cid - g.Go(func() error { - return c.listen(ctx, chainID) - }) - } - - err := g.Wait() - if err != nil { - return fmt.Errorf("error listening to contract: %w", err) - } return nil } diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index d0d805a20f..69133b6e53 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -306,9 +306,6 @@ func (c Config) GetCCTPStartBlock(chainID int) (value uint64, err error) { if !ok { return value, fmt.Errorf("failed to cast CCTPStartBlock to int") } - if value < 0 { - return 0, fmt.Errorf("invalid CCTPStartBlock: %d", value) - } return value, nil } From 45c2ad9b4f79781546953736f4e3704fb5287fcc Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Tue, 27 Feb 2024 16:11:52 -0600 Subject: [PATCH 54/75] Cleanup: lints --- services/rfq/relayer/inventory/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 01fdcaf8fd..921a73be0c 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -187,7 +187,7 @@ func NewInventoryManager(ctx context.Context, clientFetcher submitter.ClientFetc return &i, nil } -//nolint:gocognit +//nolint:gocognit,cyclop func (i *inventoryManagerImpl) Start(ctx context.Context) error { g, _ := errgroup.WithContext(ctx) for _, rebalanceManager := range i.rebalanceManagers { From ba5887636d2823c01e125647e3bfbc5d4e4aa4d7 Mon Sep 17 00:00:00 2001 From: trajan0x <83933037+trajan0x@users.noreply.github.com> Date: Wed, 28 Feb 2024 05:33:53 +0100 Subject: [PATCH 55/75] add directive (#2122) Co-authored-by: Trajan0x --- services/rfq/go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/services/rfq/go.mod b/services/rfq/go.mod index cfb7ecd394..aec72db020 100644 --- a/services/rfq/go.mod +++ b/services/rfq/go.mod @@ -292,4 +292,5 @@ replace ( github.com/synapsecns/sanguine/services/omnirpc => ../omnirpc github.com/synapsecns/sanguine/services/scribe => ../scribe github.com/synapsecns/sanguine/tools => ../../tools + github.com/synapsecns/sanguine/services/cctp-relayer => ../cctp-relayer ) From 4febe40f4c713fc237529a16904e282c685764c4 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 28 Feb 2024 11:33:13 -0600 Subject: [PATCH 56/75] Feat: move listener to ethergo/chain/listener package --- {services/rfq/relayer => ethergo/chain}/listener/doc.go | 0 {services/rfq/relayer => ethergo/chain}/listener/export_test.go | 0 {services/rfq/relayer => ethergo/chain}/listener/listener.go | 0 .../rfq/relayer => ethergo/chain}/listener/listener_test.go | 2 +- {services/rfq/relayer => ethergo/chain}/listener/suite_test.go | 2 +- services/rfq/relayer/chain/chain.go | 2 +- services/rfq/relayer/inventory/rebalance.go | 2 +- services/rfq/relayer/relapi/server.go | 2 +- services/rfq/relayer/service/relayer.go | 2 +- 9 files changed, 6 insertions(+), 6 deletions(-) rename {services/rfq/relayer => ethergo/chain}/listener/doc.go (100%) rename {services/rfq/relayer => ethergo/chain}/listener/export_test.go (100%) rename {services/rfq/relayer => ethergo/chain}/listener/listener.go (100%) rename {services/rfq/relayer => ethergo/chain}/listener/listener_test.go (97%) rename {services/rfq/relayer => ethergo/chain}/listener/suite_test.go (97%) diff --git a/services/rfq/relayer/listener/doc.go b/ethergo/chain/listener/doc.go similarity index 100% rename from services/rfq/relayer/listener/doc.go rename to ethergo/chain/listener/doc.go diff --git a/services/rfq/relayer/listener/export_test.go b/ethergo/chain/listener/export_test.go similarity index 100% rename from services/rfq/relayer/listener/export_test.go rename to ethergo/chain/listener/export_test.go diff --git a/services/rfq/relayer/listener/listener.go b/ethergo/chain/listener/listener.go similarity index 100% rename from services/rfq/relayer/listener/listener.go rename to ethergo/chain/listener/listener.go diff --git a/services/rfq/relayer/listener/listener_test.go b/ethergo/chain/listener/listener_test.go similarity index 97% rename from services/rfq/relayer/listener/listener_test.go rename to ethergo/chain/listener/listener_test.go index a7268efa4e..d8788161cd 100644 --- a/services/rfq/relayer/listener/listener_test.go +++ b/ethergo/chain/listener/listener_test.go @@ -11,8 +11,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/fastbridgemock" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" ) func (l *ListenerTestSuite) TestListenForEvents() { diff --git a/services/rfq/relayer/listener/suite_test.go b/ethergo/chain/listener/suite_test.go similarity index 97% rename from services/rfq/relayer/listener/suite_test.go rename to ethergo/chain/listener/suite_test.go index 32248de399..d81ea6fe32 100644 --- a/services/rfq/relayer/listener/suite_test.go +++ b/ethergo/chain/listener/suite_test.go @@ -11,9 +11,9 @@ import ( "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends" "github.com/synapsecns/sanguine/ethergo/backends/geth" + "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/ethergo/contracts" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/sqlite" "github.com/synapsecns/sanguine/services/rfq/testutil" diff --git a/services/rfq/relayer/chain/chain.go b/services/rfq/relayer/chain/chain.go index 36da42a345..32cffe9287 100644 --- a/services/rfq/relayer/chain/chain.go +++ b/services/rfq/relayer/chain/chain.go @@ -10,10 +10,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core" + "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/ethergo/client" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 442dde112e..9cf37b82ac 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -9,9 +9,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "golang.org/x/sync/errgroup" diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go index e3cb376ebe..73d215de31 100644 --- a/services/rfq/relayer/relapi/server.go +++ b/services/rfq/relayer/relapi/server.go @@ -14,10 +14,10 @@ import ( "github.com/gin-gonic/gin" "github.com/synapsecns/sanguine/core/metrics" baseServer "github.com/synapsecns/sanguine/core/server" + "github.com/synapsecns/sanguine/ethergo/chain/listener" omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index c97142d190..d4cb2fb40d 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -12,13 +12,13 @@ import ( "github.com/jellydator/ttlcache/v3" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/chain/listener" signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" "github.com/synapsecns/sanguine/ethergo/signer/signer" "github.com/synapsecns/sanguine/ethergo/submitter" omnirpcClient "github.com/synapsecns/sanguine/services/omnirpc/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/inventory" - "github.com/synapsecns/sanguine/services/rfq/relayer/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/pricer" "github.com/synapsecns/sanguine/services/rfq/relayer/quoter" "github.com/synapsecns/sanguine/services/rfq/relayer/relapi" From ceb84792d5f01d779380c28be141d03779ade208 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 28 Feb 2024 11:33:32 -0600 Subject: [PATCH 57/75] [goreleaser] From 19dc08083d15b74856d76794f134b1363cf6665a Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 28 Feb 2024 11:55:25 -0600 Subject: [PATCH 58/75] Cleanup: lints --- ethergo/chain/listener/listener.go | 3 +-- ethergo/chain/listener/listener_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ethergo/chain/listener/listener.go b/ethergo/chain/listener/listener.go index 0872f543a8..2c62396666 100644 --- a/ethergo/chain/listener/listener.go +++ b/ethergo/chain/listener/listener.go @@ -120,9 +120,8 @@ func (c *chainListener) doPoll(parentCtx context.Context, handler HandleLog) (er } // Check if latest block is the same as start block (for chains with slow block times) - if c.latestBlock == c.startBlock { - return + return nil } // Handle if the listener is more than one get logs range behind the head diff --git a/ethergo/chain/listener/listener_test.go b/ethergo/chain/listener/listener_test.go index d8788161cd..9634a07bfe 100644 --- a/ethergo/chain/listener/listener_test.go +++ b/ethergo/chain/listener/listener_test.go @@ -28,7 +28,7 @@ func (l *ListenerTestSuite) TestListenForEvents() { testAddress := common.BigToAddress(big.NewInt(int64(i))) auth := l.backend.GetTxContext(l.GetTestContext(), nil) - //nolint: typecheck + //nolint:typecheck txID := [32]byte(crypto.Keccak256(testAddress.Bytes())) bridgeRequestTX, err := handle.MockBridgeRequest(auth.TransactOpts, txID, testAddress, fastbridgemock.IFastBridgeBridgeParams{ DstChainId: gofakeit.Uint32(), From 085ac5e185395682bdff4a3b5d465fad31f91075 Mon Sep 17 00:00:00 2001 From: Trajan0x Date: Wed, 28 Feb 2024 19:05:29 +0000 Subject: [PATCH 59/75] update db --- ethergo/chain/listener/db/service.go | 41 ++++++++++++++ ethergo/chain/listener/db/store.go | 71 ++++++++++++++++++++++++ ethergo/chain/listener/export_test.go | 5 +- ethergo/chain/listener/listener.go | 14 +++-- ethergo/chain/listener/suite_test.go | 61 ++++++++++++++++++-- services/rfq/relayer/reldb/base/block.go | 40 ------------- services/rfq/relayer/reldb/base/model.go | 26 --------- services/rfq/relayer/reldb/base/store.go | 8 ++- services/rfq/relayer/reldb/db.go | 8 +-- services/rfq/relayer/reldb/db_test.go | 3 +- 10 files changed, 190 insertions(+), 87 deletions(-) create mode 100644 ethergo/chain/listener/db/service.go create mode 100644 ethergo/chain/listener/db/store.go delete mode 100644 services/rfq/relayer/reldb/base/block.go diff --git a/ethergo/chain/listener/db/service.go b/ethergo/chain/listener/db/service.go new file mode 100644 index 0000000000..fb2e7d922c --- /dev/null +++ b/ethergo/chain/listener/db/service.go @@ -0,0 +1,41 @@ +package db + +import ( + "context" + "gorm.io/gorm" + "time" +) + +// ChainListenerDB is the interface for the chain listener database. +type ChainListenerDB interface { + // PutLatestBlock upsers the latest block on a given chain id to be new height. + PutLatestBlock(ctx context.Context, chainID, height uint64) error + // LatestBlockForChain gets the latest block for a given chain id. + // will return ErrNoLatestBlockForChainID if no block exists for the chain. + LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) +} + +// LastIndexed is used to make sure we haven't missed any events while offline. +// since we event source - rather than use a state machine this is needed to make sure we haven't missed any events +// by allowing us to go back and source any events we may have missed. +// +// this does not inherit from gorm.model to allow us to use ChainID as a primary key. +type LastIndexed struct { + // CreatedAt is the creation time + CreatedAt time.Time + // UpdatedAt is the update time + UpdatedAt time.Time + // DeletedAt time + DeletedAt gorm.DeletedAt `gorm:"index"` + // ChainID is the chain id of the chain we're watching blocks on. This is our primary index. + ChainID uint64 `gorm:"column:chain_id;primaryKey;autoIncrement:false"` + // BlockHeight is the highest height we've seen on the chain + BlockNumber int `gorm:"block_number"` +} + +// GetAllModels gets all models to migrate +// see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time +func GetAllModels() (allModels []interface{}) { + allModels = []interface{}{&LastIndexed{}} + return allModels +} diff --git a/ethergo/chain/listener/db/store.go b/ethergo/chain/listener/db/store.go new file mode 100644 index 0000000000..396d2177e7 --- /dev/null +++ b/ethergo/chain/listener/db/store.go @@ -0,0 +1,71 @@ +package db + +import ( + "context" + "errors" + "fmt" + "github.com/synapsecns/sanguine/core/dbcommon" + "github.com/synapsecns/sanguine/core/metrics" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +// NewChainListenerStore creates a new transaction store. +func NewChainListenerStore(db *gorm.DB, metrics metrics.Handler) *Store { + return &Store{ + db: db, + metrics: metrics, + } +} + +// Store is the sqlite store. It extends the base store for sqlite specific queries. +type Store struct { + db *gorm.DB + metrics metrics.Handler +} + +// PutLatestBlock upserts the latest block into the database. +func (s Store) PutLatestBlock(ctx context.Context, chainID, height uint64) error { + tx := s.db.WithContext(ctx).Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: chainIDFieldName}}, + DoUpdates: clause.AssignmentColumns([]string{chainIDFieldName, blockNumberFieldName}), + }).Create(&LastIndexed{ + ChainID: chainID, + BlockNumber: int(height), + }) + + if tx.Error != nil { + return fmt.Errorf("could not block updated: %w", tx.Error) + } + return nil +} + +// LatestBlockForChain gets the latest block for a chain. +func (s Store) LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) { + blockWatchModel := LastIndexed{ChainID: chainID} + err := s.db.WithContext(ctx).First(&blockWatchModel).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, ErrNoLatestBlockForChainID + } + return 0, fmt.Errorf("could not fetch latest block: %w", err) + } + + return uint64(blockWatchModel.BlockNumber), nil +} + +func init() { + namer := dbcommon.NewNamer(GetAllModels()) + chainIDFieldName = namer.GetConsistentName("ChainID") + blockNumberFieldName = namer.GetConsistentName("BlockNumber") +} + +var ( + // chainIDFieldName gets the chain id field name. + chainIDFieldName string + // blockNumberFieldName is the name of the block number field. + blockNumberFieldName string +) + +// ErrNoLatestBlockForChainID is returned when no block exists for the chain. +var ErrNoLatestBlockForChainID = errors.New("no latest block for chainId") diff --git a/ethergo/chain/listener/export_test.go b/ethergo/chain/listener/export_test.go index 469ae09670..d2d17df3fc 100644 --- a/ethergo/chain/listener/export_test.go +++ b/ethergo/chain/listener/export_test.go @@ -2,11 +2,10 @@ package listener import ( "context" - "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/metrics" + "github.com/synapsecns/sanguine/ethergo/chain/listener/db" "github.com/synapsecns/sanguine/ethergo/client" - "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) // TestChainListener wraps chain listener for testing. @@ -24,7 +23,7 @@ type TestChainListenerArgs struct { Address common.Address InitialBlock uint64 Client client.EVM - Store reldb.Service + Store db.ChainListenerDB Handler metrics.Handler } diff --git a/ethergo/chain/listener/listener.go b/ethergo/chain/listener/listener.go index 2c62396666..280abe5dd7 100644 --- a/ethergo/chain/listener/listener.go +++ b/ethergo/chain/listener/listener.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/synapsecns/sanguine/ethergo/chain/listener/db" "math/big" "time" @@ -14,7 +15,6 @@ import ( "github.com/jpillora/backoff" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/ethergo/client" - "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" @@ -39,7 +39,7 @@ type chainListener struct { client client.EVM address common.Address initialBlock uint64 - store reldb.Service + store db.ChainListenerDB handler metrics.Handler backoff *backoff.Backoff // IMPORTANT! These fields cannot be used until they has been set. They are NOT @@ -49,10 +49,14 @@ type chainListener struct { // latestBlock uint64 } -var logger = log.Logger("chainlistener-logger") +var ( + logger = log.Logger("chainlistener-logger") + // ErrNoLatestBlockForChainID is returned when no block exists for the chain. + ErrNoLatestBlockForChainID = db.ErrNoLatestBlockForChainID +) // NewChainListener creates a new chain listener. -func NewChainListener(omnirpcClient client.EVM, store reldb.Service, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) { +func NewChainListener(omnirpcClient client.EVM, store db.ChainListenerDB, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) { return &chainListener{ handler: handler, address: address, @@ -181,7 +185,7 @@ func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chain chainID = rpcChainID.Uint64() lastIndexed, err = c.store.LatestBlockForChain(ctx, chainID) - if errors.Is(err, reldb.ErrNoLatestBlockForChainID) { + if errors.Is(err, ErrNoLatestBlockForChainID) { // TODO: consider making this negative 1, requires type change lastIndexed = 0 return nil diff --git a/ethergo/chain/listener/suite_test.go b/ethergo/chain/listener/suite_test.go index d81ea6fe32..825d1267a0 100644 --- a/ethergo/chain/listener/suite_test.go +++ b/ethergo/chain/listener/suite_test.go @@ -1,8 +1,19 @@ package listener_test import ( + "context" + "fmt" + "github.com/brianvoe/gofakeit/v6" + "github.com/ipfs/go-log" + common_base "github.com/synapsecns/sanguine/core/dbcommon" + "github.com/synapsecns/sanguine/ethergo/chain/listener/db" + "github.com/synapsecns/sanguine/ethergo/submitter/db/txdb" + "gorm.io/gorm" + "gorm.io/gorm/schema" "math/big" + "os" "testing" + "time" "github.com/Flaque/filet" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -14,9 +25,8 @@ import ( "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/ethergo/contracts" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" - "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" - "github.com/synapsecns/sanguine/services/rfq/relayer/reldb/sqlite" "github.com/synapsecns/sanguine/services/rfq/testutil" + "gorm.io/driver/sqlite" ) const chainID = 10 @@ -25,7 +35,7 @@ type ListenerTestSuite struct { *testsuite.TestSuite manager *testutil.DeployManager backend backends.SimulatedTestBackend - store reldb.Service + store db.ChainListenerDB metrics metrics.Handler fastBridge *fastbridge.FastBridgeRef fastBridgeMetadata contracts.DeployedContract @@ -48,7 +58,7 @@ func (l *ListenerTestSuite) SetupTest() { l.backend = geth.NewEmbeddedBackendForChainID(l.GetTestContext(), l.T(), big.NewInt(chainID)) var err error l.metrics = metrics.NewNullHandler() - l.store, err = sqlite.NewSqliteStore(l.GetTestContext(), filet.TmpDir(l.T(), ""), l.metrics) + l.store, err = NewSqliteStore(l.GetTestContext(), filet.TmpDir(l.T(), ""), l.metrics) l.Require().NoError(err) l.fastBridgeMetadata, l.fastBridge = l.manager.GetFastBridge(l.GetTestContext(), l.backend) @@ -96,3 +106,46 @@ func (l *ListenerTestSuite) TestStartBlock() { func (l *ListenerTestSuite) TestListen() { } + +// NewSqliteStore creates a new sqlite data store. +func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Handler) (_ *db.Store, err error) { + logger := log.Logger("sqlite-store") + + logger.Debugf("creating sqlite store at %s", dbPath) + + ctx, span := handler.Tracer().Start(parentCtx, "start-sqlite") + defer func() { + metrics.EndSpanWithErr(span, err) + }() + + // create the directory to the store if it doesn't exist + err = os.MkdirAll(dbPath, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("could not create sqlite store") + } + + logger.Warnf("submitter database is at %s/synapse.db", dbPath) + + namingStrategy := schema.NamingStrategy{ + TablePrefix: fmt.Sprintf("test%d_%d_", gofakeit.Int64(), time.Now().Unix()), + } + + gdb, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s/%s", dbPath, "synapse.db")), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: common_base.GetGormLogger(logger), + FullSaveAssociations: true, + SkipDefaultTransaction: true, + NamingStrategy: namingStrategy, + }) + if err != nil { + return nil, fmt.Errorf("could not connect to db %s: %w", dbPath, err) + } + + handler.AddGormCallbacks(gdb) + + err = gdb.WithContext(ctx).AutoMigrate(txdb.GetAllModels()...) + if err != nil { + return nil, fmt.Errorf("could not migrate models: %w", err) + } + return db.NewChainListenerStore(gdb, handler), nil +} diff --git a/services/rfq/relayer/reldb/base/block.go b/services/rfq/relayer/reldb/base/block.go deleted file mode 100644 index 75322aa224..0000000000 --- a/services/rfq/relayer/reldb/base/block.go +++ /dev/null @@ -1,40 +0,0 @@ -package base - -import ( - "context" - "errors" - "fmt" - "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -// PutLatestBlock upserts the latest block into the database. -func (s Store) PutLatestBlock(ctx context.Context, chainID, height uint64) error { - tx := s.DB().WithContext(ctx).Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: chainIDFieldName}}, - DoUpdates: clause.AssignmentColumns([]string{chainIDFieldName, blockNumberFieldName}), - }).Create(&LastIndexed{ - ChainID: chainID, - BlockNumber: int(height), - }) - - if tx.Error != nil { - return fmt.Errorf("could not block updated: %w", tx.Error) - } - return nil -} - -// LatestBlockForChain gets the latest block for a chain. -func (s Store) LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) { - blockWatchModel := LastIndexed{ChainID: chainID} - err := s.db.WithContext(ctx).First(&blockWatchModel).Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return 0, reldb.ErrNoLatestBlockForChainID - } - return 0, fmt.Errorf("could not fetch latest block: %w", err) - } - - return uint64(blockWatchModel.BlockNumber), nil -} diff --git a/services/rfq/relayer/reldb/base/model.go b/services/rfq/relayer/reldb/base/model.go index 1321a1d833..f4226369f9 100644 --- a/services/rfq/relayer/reldb/base/model.go +++ b/services/rfq/relayer/reldb/base/model.go @@ -14,13 +14,10 @@ import ( "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" - "gorm.io/gorm" ) func init() { namer := dbcommon.NewNamer(GetAllModels()) - chainIDFieldName = namer.GetConsistentName("ChainID") - blockNumberFieldName = namer.GetConsistentName("BlockNumber") statusFieldName = namer.GetConsistentName("Status") transactionIDFieldName = namer.GetConsistentName("TransactionID") originTxHashFieldName = namer.GetConsistentName("OriginTxHash") @@ -29,11 +26,6 @@ func init() { } var ( - // chainIDFieldName gets the chain id field name. - chainIDFieldName string - // blockNumberFieldName is the name of the block number field. - blockNumberFieldName string - statusFieldName string // transactionIDFieldName is the transactions id field name. transactionIDFieldName string @@ -45,24 +37,6 @@ var ( rebalanceIDFieldName string ) -// LastIndexed is used to make sure we haven't missed any events while offline. -// since we event source - rather than use a state machine this is needed to make sure we haven't missed any events -// by allowing us to go back and source any events we may have missed. -// -// this does not inherit from gorm.model to allow us to use ChainID as a primary key. -type LastIndexed struct { - // CreatedAt is the creation time - CreatedAt time.Time - // UpdatedAt is the update time - UpdatedAt time.Time - // DeletedAt time - DeletedAt gorm.DeletedAt `gorm:"index"` - // ChainID is the chain id of the chain we're watching blocks on. This is our primary index. - ChainID uint64 `gorm:"column:chain_id;primaryKey;autoIncrement:false"` - // BlockHeight is the highest height we've seen on the chain - BlockNumber int `gorm:"block_number"` -} - // RequestForQuote is the primary event model. type RequestForQuote struct { // CreatedAt is the creation time diff --git a/services/rfq/relayer/reldb/base/store.go b/services/rfq/relayer/reldb/base/store.go index 0d1a2bf30a..ce9bb59de6 100644 --- a/services/rfq/relayer/reldb/base/store.go +++ b/services/rfq/relayer/reldb/base/store.go @@ -2,6 +2,7 @@ package base import ( "github.com/synapsecns/sanguine/core/metrics" + listenerDB "github.com/synapsecns/sanguine/ethergo/chain/listener/db" submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db" "github.com/synapsecns/sanguine/ethergo/submitter/db/txdb" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" @@ -10,6 +11,7 @@ import ( // Store implements the service. type Store struct { + listenerDB.ChainListenerDB db *gorm.DB submitterStore submitterDB.Service } @@ -17,7 +19,8 @@ type Store struct { // NewStore creates a new store. func NewStore(db *gorm.DB, metrics metrics.Handler) *Store { txDB := txdb.NewTXStore(db, metrics) - return &Store{db: db, submitterStore: txDB} + + return &Store{ChainListenerDB: listenerDB.NewChainListenerStore(db, metrics), db: db, submitterStore: txDB} } // DB gets the database object for mutation outside of the lib. @@ -33,7 +36,8 @@ func (s Store) SubmitterDB() submitterDB.Service { // GetAllModels gets all models to migrate // see: https://medium.com/@SaifAbid/slice-interfaces-8c78f8b6345d for an explanation of why we can't do this at initialization time func GetAllModels() (allModels []interface{}) { - allModels = append(txdb.GetAllModels(), &LastIndexed{}, &RequestForQuote{}, &Rebalance{}) + allModels = append(txdb.GetAllModels(), &RequestForQuote{}, &Rebalance{}) + allModels = append(allModels, listenerDB.GetAllModels()...) return allModels } diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index 9dbb1b4328..894358dd30 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -5,6 +5,7 @@ import ( "database/sql/driver" "errors" "fmt" + "github.com/synapsecns/sanguine/ethergo/chain/listener/db" "math/big" "github.com/ethereum/go-ethereum/common" @@ -15,8 +16,6 @@ import ( // Writer is the interface for writing to the database. type Writer interface { - // PutLatestBlock upsers the latest block on a given chain id to be new height. - PutLatestBlock(ctx context.Context, chainID, height uint64) error // StoreQuoteRequest stores a quote request. If one already exists, only the status will be updated // TODO: find a better way to describe this in the name StoreQuoteRequest(ctx context.Context, request QuoteRequest) error @@ -33,8 +32,6 @@ type Writer interface { // Reader is the interface for reading from the database. type Reader interface { - // LatestBlockForChain gets the latest block for a given chain id. - LatestBlockForChain(ctx context.Context, chainID uint64) (uint64, error) // GetQuoteRequestByID gets a quote request by id. Should return ErrNoQuoteForID if not found GetQuoteRequestByID(ctx context.Context, id [32]byte) (*QuoteRequest, error) // GetQuoteRequestByOriginTxHash gets a quote request by origin tx hash. Should return ErrNoQuoteForTxHash if not found @@ -51,11 +48,10 @@ type Service interface { // SubmitterDB returns the submitter database service. SubmitterDB() submitterDB.Service Writer + db.ChainListenerDB } var ( - // ErrNoLatestBlockForChainID is returned when no block exists for the chain. - ErrNoLatestBlockForChainID = errors.New("no latest block for chainId") // ErrNoQuoteForID means the quote was not found. ErrNoQuoteForID = errors.New("no quote found for tx id") // ErrNoQuoteForTxHash means the quote was not found. diff --git a/services/rfq/relayer/reldb/db_test.go b/services/rfq/relayer/reldb/db_test.go index ea6ce9a1ad..3756641dd9 100644 --- a/services/rfq/relayer/reldb/db_test.go +++ b/services/rfq/relayer/reldb/db_test.go @@ -2,6 +2,7 @@ package reldb_test import ( "errors" + "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) @@ -9,7 +10,7 @@ func (d *DBSuite) TestBlock() { d.RunOnAllDBs(func(testDB reldb.Service) { const testChainID = 5 _, err := testDB.LatestBlockForChain(d.GetTestContext(), testChainID) - d.True(errors.Is(err, reldb.ErrNoLatestBlockForChainID)) + d.True(errors.Is(err, listener.ErrNoLatestBlockForChainID)) testHeight := 10 From 782b8694d778b7f3a4c9eb7e2b768e5056872ac2 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 28 Feb 2024 15:47:50 -0600 Subject: [PATCH 60/75] Cleanup: remove unnecessary errgroup --- ethergo/chain/listener/listener.go | 44 +++++++++++------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/ethergo/chain/listener/listener.go b/ethergo/chain/listener/listener.go index 2c62396666..793c109e76 100644 --- a/ethergo/chain/listener/listener.go +++ b/ethergo/chain/listener/listener.go @@ -17,7 +17,6 @@ import ( "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "golang.org/x/sync/errgroup" ) // ContractListener listens for chain events and calls HandleLog. @@ -167,34 +166,23 @@ func (c chainListener) getMetadata(parentCtx context.Context) (startBlock, chain // TODO: consider some kind of backoff here in case rpcs are down at boot. // this becomes more of an issue as we add more chains - g, ctx := errgroup.WithContext(ctx) - g.Go(func() error { - // TODO: one thing I've been going back and forth on is whether or not this method should be chain aware - // passing in the chain ID would allow us to pull everything directly from the config, but be less testable - // for now, this is probably the best solution for testability, but it's certainly a bit annoying we need to do - // an rpc call in order to get the chain id - // - rpcChainID, err := c.client.ChainID(ctx) - if err != nil { - return fmt.Errorf("could not get chain ID: %w", err) - } - chainID = rpcChainID.Uint64() - - lastIndexed, err = c.store.LatestBlockForChain(ctx, chainID) - if errors.Is(err, reldb.ErrNoLatestBlockForChainID) { - // TODO: consider making this negative 1, requires type change - lastIndexed = 0 - return nil - } - if err != nil { - return fmt.Errorf("could not get the latest block for chainID: %w", err) - } - return nil - }) - - err = g.Wait() + // TODO: one thing I've been going back and forth on is whether or not this method should be chain aware + // passing in the chain ID would allow us to pull everything directly from the config, but be less testable + // for now, this is probably the best solution for testability, but it's certainly a bit annoying we need to do + // an rpc call in order to get the chain id + // + rpcChainID, err := c.client.ChainID(ctx) if err != nil { - return 0, 0, fmt.Errorf("could not get metadata: %w", err) + return 0, 0, fmt.Errorf("could not get chain ID: %w", err) + } + chainID = rpcChainID.Uint64() + + lastIndexed, err = c.store.LatestBlockForChain(ctx, chainID) + if errors.Is(err, reldb.ErrNoLatestBlockForChainID) { + // TODO: consider making this negative 1, requires type change + lastIndexed = 0 + } else if err != nil { + return 0, 0, fmt.Errorf("could not get the latest block for chainID: %w", err) } if lastIndexed > c.startBlock { From 07dbaeb36e6d97ce576c2584742e5104e1bd9952 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Wed, 28 Feb 2024 16:24:09 -0600 Subject: [PATCH 61/75] [goreleaser] From f2d7ab8c72e3693a3f9c45699b4ad398c6f2ebd9 Mon Sep 17 00:00:00 2001 From: Trajan0x Date: Thu, 29 Feb 2024 18:29:07 +0000 Subject: [PATCH 62/75] cleanup --- ethergo/README.md | 5 +- ethergo/chain/listener/listener_test.go | 95 ---- ethergo/example/README.md | 72 +++ ethergo/example/counter/counter.abigen.go | 439 +++++++++++++++++- .../example/counter/counter.contractinfo.json | 2 +- ethergo/example/counter/counter.sol | 15 + ethergo/example/deploymanager.go | 30 ++ ethergo/listener/db/doc.go | 2 + ethergo/{chain => }/listener/db/service.go | 0 ethergo/{chain => }/listener/db/store.go | 0 ethergo/{chain => }/listener/doc.go | 0 ethergo/{chain => }/listener/export_test.go | 2 +- ethergo/{chain => }/listener/listener.go | 8 +- ethergo/listener/listener_test.go | 59 +++ ethergo/{chain => }/listener/suite_test.go | 41 +- services/rfq/e2e/rfq_test.go | 5 +- services/rfq/go.mod | 8 +- services/rfq/relayer/chain/chain.go | 2 +- services/rfq/relayer/inventory/rebalance.go | 2 +- services/rfq/relayer/relapi/server.go | 2 +- services/rfq/relayer/reldb/base/store.go | 2 +- services/rfq/relayer/reldb/db.go | 2 +- services/rfq/relayer/reldb/db_test.go | 2 +- services/rfq/relayer/service/relayer.go | 2 +- services/rfq/testutil/deployers.go | 1 - 25 files changed, 657 insertions(+), 141 deletions(-) delete mode 100644 ethergo/chain/listener/listener_test.go create mode 100644 ethergo/example/deploymanager.go create mode 100644 ethergo/listener/db/doc.go rename ethergo/{chain => }/listener/db/service.go (100%) rename ethergo/{chain => }/listener/db/store.go (100%) rename ethergo/{chain => }/listener/doc.go (100%) rename ethergo/{chain => }/listener/export_test.go (94%) rename ethergo/{chain => }/listener/listener.go (95%) create mode 100644 ethergo/listener/listener_test.go rename ethergo/{chain => }/listener/suite_test.go (77%) diff --git a/ethergo/README.md b/ethergo/README.md index 4a474621a3..02cfa8a70b 100644 --- a/ethergo/README.md +++ b/ethergo/README.md @@ -29,15 +29,16 @@ root │ ├── geth: Contains an embedded geth backend. This is useful for testing against a local geth instance without forking capabilities. This does not require docker and runs fully embedded in the go application, as such it is faster than the docker-based backends, but less versatile. Used when an rpc address is needed for a localnet. │ ├── preset: Contains a number of preset backends for testing. │ ├── simulated: The fastest backend, this does not expose an rpc endpoint and uses geth's [simulated backend](https://goethereumbook.org/en/client-simulated/) -├── chain: Contains a client for interacting with the chain. This will be removed in a future version. Please use [client](./client) going forward. +├── chain: Contains a client for interacting with the chain. This will be removed in a future version. Please use [client](./client) going forward. │ ├── chainwatcher: Watches the chain for events, blocks and logs │ ├── client: Contains eth clients w/ rate limiting, workarounds for bugs in some chains, etc. │ ├── gas: Contains a deterministic gas estimator -│ ├── watcher: Client interface for chain watcher. +│ ├── watcher: Client interface for chain watcher. ├── contracts: Contains interfaces for using contracts with the deployer + manager ├── client: Contains an open tracing compatible ethclient with batching. ├── example: Contains a full featured example of how to use deployer + manager ├── forker: Allows the use of fork tests in live chains without docker using an anvil binary. +├── listener: Drop-in contract listener ├── manager: Manages contract deployments. ├── mocks: Contains mocks for testing various data types (transactions, addresses, logs, etc) ├── parser: Parse hardhat deployments diff --git a/ethergo/chain/listener/listener_test.go b/ethergo/chain/listener/listener_test.go deleted file mode 100644 index 9634a07bfe..0000000000 --- a/ethergo/chain/listener/listener_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package listener_test - -import ( - "context" - "math/big" - "sync" - "time" - - "github.com/brianvoe/gofakeit/v6" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/synapsecns/sanguine/ethergo/chain/listener" - "github.com/synapsecns/sanguine/services/rfq/contracts/testcontracts/fastbridgemock" -) - -func (l *ListenerTestSuite) TestListenForEvents() { - _, handle := l.manager.GetMockFastBridge(l.GetTestContext(), l.backend) - var wg sync.WaitGroup - const iterations = 50 - for i := 0; i < iterations; i++ { - i := i - go func(num int) { - wg.Add(1) - defer wg.Done() - - testAddress := common.BigToAddress(big.NewInt(int64(i))) - auth := l.backend.GetTxContext(l.GetTestContext(), nil) - - //nolint:typecheck - txID := [32]byte(crypto.Keccak256(testAddress.Bytes())) - bridgeRequestTX, err := handle.MockBridgeRequest(auth.TransactOpts, txID, testAddress, fastbridgemock.IFastBridgeBridgeParams{ - DstChainId: gofakeit.Uint32(), - Sender: testAddress, - To: testAddress, - OriginToken: testAddress, - DestToken: testAddress, - OriginAmount: new(big.Int).SetUint64(gofakeit.Uint64()), - DestAmount: new(big.Int).SetUint64(gofakeit.Uint64()), - SendChainGas: false, - Deadline: new(big.Int).SetUint64(uint64(time.Now().Add(-1 * time.Second * time.Duration(gofakeit.Uint16())).Unix())), - }) - l.NoError(err) - l.NotNil(bridgeRequestTX) - - l.backend.WaitForConfirmation(l.GetTestContext(), bridgeRequestTX) - - bridgeResponseTX, err := handle.MockBridgeRelayer(auth.TransactOpts, - // transactionID - txID, - // relayer - testAddress, - // to - testAddress, - // originChainID - uint32(gofakeit.Uint16()), - // originToken - testAddress, - // destToken - testAddress, - // originAmount - new(big.Int).SetUint64(gofakeit.Uint64()), - // destAmount - new(big.Int).SetUint64(gofakeit.Uint64()), - // gasAmount - new(big.Int).SetUint64(gofakeit.Uint64())) - l.NoError(err) - l.NotNil(bridgeResponseTX) - l.backend.WaitForConfirmation(l.GetTestContext(), bridgeResponseTX) - }(i) - } - - wg.Wait() - - startBlock, err := handle.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) - l.NoError(err) - - cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), uint64(startBlock.Int64()), l.metrics) - l.NoError(err) - - eventCount := 0 - - // TODO: check for timeout,but it will be extremely obvious if it gets hit. - listenCtx, cancel := context.WithCancel(l.GetTestContext()) - err = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error { - eventCount++ - - if eventCount == iterations*2 { - cancel() - } - - return nil - }) -} diff --git a/ethergo/example/README.md b/ethergo/example/README.md index 824cdaa050..6ad31612ee 100644 --- a/ethergo/example/README.md +++ b/ethergo/example/README.md @@ -195,5 +195,77 @@ } ``` + 4. (Optional): Create a typecast getter: + To avoid naked casts of contract handle, we can potionally create a typecast getter. + To do this, we're going to create a thin wrapper around deploymanager. + + ```go + package example + import ( + "context" + "github.com/synapsecns/sanguine/ethergo/backends" + "github.com/synapsecns/sanguine/ethergo/contracts" + "github.com/synapsecns/sanguine/ethergo/manager" + "testing" + ) + + // DeployManager wraps DeployManager and allows typed contract handles to be returned. + type DeployManager struct { + *manager.DeployerManager + } + + // NewDeployManager creates a new DeployManager. + func NewDeployManager(t *testing.T) *DeployManager { + t.Helper() + + parentManager := manager.NewDeployerManager(t, NewCounterDeployer) + return &DeployManager{parentManager} + } + ``` + + Now we can create a handle to get the contract for us; + + ```go + package example + // see above for imports + + import ( + "context" + "github.com/synapsecns/sanguine/ethergo/backends" + "github.com/synapsecns/sanguine/ethergo/contracts" + "github.com/synapsecns/sanguine/ethergo/example/counter" + "github.com/synapsecns/sanguine/ethergo/manager" + "testing" + ) + + // GetCounter gets the pre-created counter. + func (d *DeployManager) GetCounter(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *counter.CounterRef) { + d.T().Helper() + + return manager.GetContract[*counter.CounterRef](ctx, d.T(), d, backend, CounterType) + } + ``` + + 5. (Optional) Make sure are dependencies are correct: We can also create a test to assert our dependencides are correctly listed in each deployer. That looks like this: + + ```go + package example_test + + import ( + "context" + "github.com/synapsecns/sanguine/ethergo/backends" + "github.com/synapsecns/sanguine/ethergo/contracts" + "github.com/synapsecns/sanguine/ethergo/example" + "github.com/synapsecns/sanguine/ethergo/manager" + "testing" + ) + + + func TestDependenciesCorrect(t *testing.T) { + manager.AssertDependenciesCorrect(context.Background(), t, func() manager.IDeployManager { + return example.NewDeployerManager(t) + }) + } + ``` That's it! You should be done. As you can see, there's a lot more that can be done here. Passing in a list of all your deployers every time doesn't make sense. You'll want to create a standard testutil and extend it. We also haven't covered that any backend here is interchangable: you can use simulated, ganache, or embedded geth. This tutorial should've covered the basics though diff --git a/ethergo/example/counter/counter.abigen.go b/ethergo/example/counter/counter.abigen.go index 8dfa0b19ec..9b4eac4e66 100644 --- a/ethergo/example/counter/counter.abigen.go +++ b/ethergo/example/counter/counter.abigen.go @@ -31,15 +31,16 @@ var ( // CounterMetaData contains all meta data concerning the Counter contract. var CounterMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Decremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Incremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"IncrementedByUser\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deployBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", Sigs: map[string]string{ "f5c5ad83": "decrementCounter()", + "a3ec191a": "deployBlock()", "a87d942c": "getCount()", "9f6f1ec1": "getVitalikCount()", "5b34b966": "incrementCounter()", "6c573535": "vitalikIncrement()", }, - Bin: "0x608060405260008055600060015534801561001957600080fd5b506102b0806100296000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033", + Bin: "0x60a060405260008055600060015534801561001957600080fd5b5043608052608051610390610038600039600060a401526103906000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033", } // CounterABI is the input ABI used to generate the binding from. @@ -213,6 +214,37 @@ func (_Counter *CounterTransactorRaw) Transact(opts *bind.TransactOpts, method s return _Counter.Contract.contract.Transact(opts, method, params...) } +// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a. +// +// Solidity: function deployBlock() view returns(uint256) +func (_Counter *CounterCaller) DeployBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _Counter.contract.Call(opts, &out, "deployBlock") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a. +// +// Solidity: function deployBlock() view returns(uint256) +func (_Counter *CounterSession) DeployBlock() (*big.Int, error) { + return _Counter.Contract.DeployBlock(&_Counter.CallOpts) +} + +// DeployBlock is a free data retrieval call binding the contract method 0xa3ec191a. +// +// Solidity: function deployBlock() view returns(uint256) +func (_Counter *CounterCallerSession) DeployBlock() (*big.Int, error) { + return _Counter.Contract.DeployBlock(&_Counter.CallOpts) +} + // GetCount is a free data retrieval call binding the contract method 0xa87d942c. // // Solidity: function getCount() view returns(int256) @@ -337,3 +369,406 @@ func (_Counter *CounterSession) VitalikIncrement() (*types.Transaction, error) { func (_Counter *CounterTransactorSession) VitalikIncrement() (*types.Transaction, error) { return _Counter.Contract.VitalikIncrement(&_Counter.TransactOpts) } + +// CounterDecrementedIterator is returned from FilterDecremented and is used to iterate over the raw logs and unpacked data for Decremented events raised by the Counter contract. +type CounterDecrementedIterator struct { + Event *CounterDecremented // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CounterDecrementedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CounterDecremented) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CounterDecremented) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CounterDecrementedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CounterDecrementedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CounterDecremented represents a Decremented event raised by the Counter contract. +type CounterDecremented struct { + Count *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDecremented is a free log retrieval operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de. +// +// Solidity: event Decremented(int256 count) +func (_Counter *CounterFilterer) FilterDecremented(opts *bind.FilterOpts) (*CounterDecrementedIterator, error) { + + logs, sub, err := _Counter.contract.FilterLogs(opts, "Decremented") + if err != nil { + return nil, err + } + return &CounterDecrementedIterator{contract: _Counter.contract, event: "Decremented", logs: logs, sub: sub}, nil +} + +// WatchDecremented is a free log subscription operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de. +// +// Solidity: event Decremented(int256 count) +func (_Counter *CounterFilterer) WatchDecremented(opts *bind.WatchOpts, sink chan<- *CounterDecremented) (event.Subscription, error) { + + logs, sub, err := _Counter.contract.WatchLogs(opts, "Decremented") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CounterDecremented) + if err := _Counter.contract.UnpackLog(event, "Decremented", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseDecremented is a log parse operation binding the contract event 0x22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de. +// +// Solidity: event Decremented(int256 count) +func (_Counter *CounterFilterer) ParseDecremented(log types.Log) (*CounterDecremented, error) { + event := new(CounterDecremented) + if err := _Counter.contract.UnpackLog(event, "Decremented", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CounterIncrementedIterator is returned from FilterIncremented and is used to iterate over the raw logs and unpacked data for Incremented events raised by the Counter contract. +type CounterIncrementedIterator struct { + Event *CounterIncremented // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CounterIncrementedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CounterIncremented) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CounterIncremented) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CounterIncrementedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CounterIncrementedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CounterIncremented represents a Incremented event raised by the Counter contract. +type CounterIncremented struct { + Count *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterIncremented is a free log retrieval operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be. +// +// Solidity: event Incremented(int256 count) +func (_Counter *CounterFilterer) FilterIncremented(opts *bind.FilterOpts) (*CounterIncrementedIterator, error) { + + logs, sub, err := _Counter.contract.FilterLogs(opts, "Incremented") + if err != nil { + return nil, err + } + return &CounterIncrementedIterator{contract: _Counter.contract, event: "Incremented", logs: logs, sub: sub}, nil +} + +// WatchIncremented is a free log subscription operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be. +// +// Solidity: event Incremented(int256 count) +func (_Counter *CounterFilterer) WatchIncremented(opts *bind.WatchOpts, sink chan<- *CounterIncremented) (event.Subscription, error) { + + logs, sub, err := _Counter.contract.WatchLogs(opts, "Incremented") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CounterIncremented) + if err := _Counter.contract.UnpackLog(event, "Incremented", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseIncremented is a log parse operation binding the contract event 0xda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be. +// +// Solidity: event Incremented(int256 count) +func (_Counter *CounterFilterer) ParseIncremented(log types.Log) (*CounterIncremented, error) { + event := new(CounterIncremented) + if err := _Counter.contract.UnpackLog(event, "Incremented", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// CounterIncrementedByUserIterator is returned from FilterIncrementedByUser and is used to iterate over the raw logs and unpacked data for IncrementedByUser events raised by the Counter contract. +type CounterIncrementedByUserIterator struct { + Event *CounterIncrementedByUser // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *CounterIncrementedByUserIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(CounterIncrementedByUser) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(CounterIncrementedByUser) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *CounterIncrementedByUserIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *CounterIncrementedByUserIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// CounterIncrementedByUser represents a IncrementedByUser event raised by the Counter contract. +type CounterIncrementedByUser struct { + User common.Address + Count *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterIncrementedByUser is a free log retrieval operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557. +// +// Solidity: event IncrementedByUser(address user, int256 count) +func (_Counter *CounterFilterer) FilterIncrementedByUser(opts *bind.FilterOpts) (*CounterIncrementedByUserIterator, error) { + + logs, sub, err := _Counter.contract.FilterLogs(opts, "IncrementedByUser") + if err != nil { + return nil, err + } + return &CounterIncrementedByUserIterator{contract: _Counter.contract, event: "IncrementedByUser", logs: logs, sub: sub}, nil +} + +// WatchIncrementedByUser is a free log subscription operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557. +// +// Solidity: event IncrementedByUser(address user, int256 count) +func (_Counter *CounterFilterer) WatchIncrementedByUser(opts *bind.WatchOpts, sink chan<- *CounterIncrementedByUser) (event.Subscription, error) { + + logs, sub, err := _Counter.contract.WatchLogs(opts, "IncrementedByUser") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(CounterIncrementedByUser) + if err := _Counter.contract.UnpackLog(event, "IncrementedByUser", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseIncrementedByUser is a log parse operation binding the contract event 0x5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557. +// +// Solidity: event IncrementedByUser(address user, int256 count) +func (_Counter *CounterFilterer) ParseIncrementedByUser(log types.Log) (*CounterIncrementedByUser, error) { + event := new(CounterIncrementedByUser) + if err := _Counter.contract.UnpackLog(event, "IncrementedByUser", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/ethergo/example/counter/counter.contractinfo.json b/ethergo/example/counter/counter.contractinfo.json index 0766d19bc4..3117834c3b 100644 --- a/ethergo/example/counter/counter.contractinfo.json +++ b/ethergo/example/counter/counter.contractinfo.json @@ -1 +1 @@ -{"/solidity/counter.sol:Counter":{"code":"0x608060405260008055600060015534801561001957600080fd5b506102b0806100296000396000f3fe608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033","runtime-code":"0x608060405234801561001057600080fd5b50600436106100675760003560e01c80639f6f1ec1116100505780639f6f1ec11461007e578063a87d942c14610094578063f5c5ad831461009c57600080fd5b80635b34b9661461006c5780636c57353514610076575b600080fd5b6100746100a4565b005b6100746100bd565b6001545b60405190815260200160405180910390f35b600054610082565b610074610151565b60016000808282546100b69190610163565b9091555050565b3373d8da6bf26964af9d7eed9e03e53415d37aa960451461013e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546100b69190610163565b60016000808282546100b691906101d7565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561019d5761019d61024b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156101d1576101d161024b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102115761021161024b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156102455761024561024b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea26469706673582212201e03c8b68dcbcef6344fae810afa5d485b33bc238c0e8cb1508114b9c0ca702964736f6c63430008040033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ncontract Counter {\n // this is used for testing account impersonation\n address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);\n\n int private count = 0;\n int private vitaikCount = 0;\n\n function incrementCounter() public {\n count += 1;\n }\n function decrementCounter() public {\n count -= 1;\n }\n\n function vitalikIncrement() public {\n require(msg.sender == VITALIK, \"Only Vitalik can count by 10\");\n vitaikCount += 10;\n }\n\n function getCount() public view returns (int) {\n return count;\n }\n\n function getVitalikCount() public view returns (int) {\n return vitaikCount;\n }\n}\n","language":"Solidity","languageVersion":"0.8.4","compilerVersion":"0.8.4","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"57:676:0:-:0;;;239:1;219:21;;272:1;246:27;;57:676;;;;;;;;;;;;;;;;","srcMapRuntime":"57:676:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;280:62;;;:::i;:::-;;415:141;;;:::i;643:88::-;713:11;;643:88;;;158:25:1;;;146:2;131:18;643:88:0;;;;;;;562:75;603:3;625:5;562:75;;347:62;;;:::i;280:::-;334:1;325:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;280:62:0:o;415:141::-;468:10;169:42;468:21;460:62;;;;;;;396:2:1;460:62:0;;;378:21:1;435:2;415:18;;;408:30;474;454:18;;;447:58;522:18;;460:62:0;;;;;;;;547:2;532:11;;:17;;;;;;;:::i;347:62::-;401:1;392:5;;:10;;;;;;;:::i;551:369:1:-;590:3;625;622:1;618:11;736:1;668:66;664:74;661:1;657:82;652:2;645:10;641:99;638:2;;;743:18;;:::i;:::-;862:1;794:66;790:74;787:1;783:82;779:2;775:91;772:2;;;869:18;;:::i;:::-;-1:-1:-1;;905:9:1;;598:322::o;925:372::-;964:4;1000;997:1;993:12;1112:1;1044:66;1040:74;1037:1;1033:82;1028:2;1021:10;1017:99;1014:2;;;1119:18;;:::i;:::-;1238:1;1170:66;1166:74;1163:1;1159:82;1155:2;1151:91;1148:2;;;1245:18;;:::i;:::-;-1:-1:-1;;1282:9:1;;973:324::o;1302:184::-;1354:77;1351:1;1344:88;1451:4;1448:1;1441:15;1475:4;1472:1;1465:15","abiDefinition":[{"inputs":[],"name":"decrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVitalikCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vitalikIncrement","outputs":[],"stateMutability":"nonpayable","type":"function"}],"userDoc":{"kind":"user","methods":{},"version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/solidity/counter.sol\":\"Counter\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"/solidity/counter.sol\":{\"keccak256\":\"0x42676ddc10b9e27a3896bfe453fc4e32f321449b6f1ad9bd61dc69248df0eea1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://237ef43b2c1b1136dd04f57595b5e54f952a37bf02b0f56fc62407eea868171d\",\"dweb:/ipfs/QmZnBMnf1xZ2yfwqVjBZRR4W4CtLcLEn1FyHvRgAEShu7k\"]}},\"version\":1}"},"hashes":{"decrementCounter()":"f5c5ad83","getCount()":"a87d942c","getVitalikCount()":"9f6f1ec1","incrementCounter()":"5b34b966","vitalikIncrement()":"6c573535"}}} \ No newline at end of file +{"/solidity/counter.sol:Counter":{"code":"0x60a060405260008055600060015534801561001957600080fd5b5043608052608051610390610038600039600060a401526103906000f3fe608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033","runtime-code":"0x608060405234801561001057600080fd5b50600436106100725760003560e01c8063a3ec191a11610050578063a3ec191a1461009f578063a87d942c146100c6578063f5c5ad83146100ce57600080fd5b80635b34b966146100775780636c573535146100815780639f6f1ec114610089575b600080fd5b61007f6100d6565b005b61007f610126565b6001545b60405190815260200160405180910390f35b61008d7f000000000000000000000000000000000000000000000000000000000000000081565b60005461008d565b61007f6101f9565b60016000808282546100e89190610243565b90915550506000546040519081527fda0bc8b9b52da793a50e130494716550dab510a10a485be3f1b23d4da60ff4be906020015b60405180910390a1565b3373d8da6bf26964af9d7eed9e03e53415d37aa96045146101a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4f6e6c7920566974616c696b2063616e20636f756e7420627920313000000000604482015260640160405180910390fd5b600a600160008282546101ba9190610243565b90915550506001546040805133815260208101929092527f5832be325e40e91e7a991db4415bdfa9c689e8007072fdb8de3be47757a14557910161011c565b600160008082825461020b91906102b7565b90915550506000546040519081527f22ccb5ba3d32a9221c3efe39ffab06d1ddc4bd6684975ea75fa60f95ccff53de9060200161011c565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561027d5761027d61032b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156102b1576102b161032b565b50500190565b6000808312837f8000000000000000000000000000000000000000000000000000000000000000018312811516156102f1576102f161032b565b837f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0183138116156103255761032561032b565b50500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fdfea2646970667358221220dd6810ba2d049a0f7354bf12f6a017744c806f0470f592679a80ef552f69b85164736f6c63430008040033","info":{"source":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ncontract Counter {\n // this is used for testing account impersonation\n address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045);\n\n event Incremented(int count);\n event Decremented(int count);\n event IncrementedByUser(address user, int count);\n\n int private count = 0;\n int private vitaikCount = 0;\n\n // @dev the block the contract was deployed at\n uint256 public immutable deployBlock;\n\n constructor() {\n deployBlock = block.number;\n }\n\n\n function incrementCounter() public {\n count += 1;\n emit Incremented(count);\n }\n function decrementCounter() public {\n count -= 1;\n emit Decremented(count);\n }\n\n function vitalikIncrement() public {\n require(msg.sender == VITALIK, \"Only Vitalik can count by 10\");\n vitaikCount += 10;\n emit IncrementedByUser(msg.sender, vitaikCount);\n }\n\n function getCount() public view returns (int) {\n return count;\n }\n\n function getVitalikCount() public view returns (int) {\n return vitaikCount;\n }\n}\n","language":"Solidity","languageVersion":"0.8.4","compilerVersion":"0.8.4","compilerOptions":"--combined-json bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc,metadata,hashes --optimize --optimize-runs 10000 --allow-paths ., ./, ../","srcMap":"57:1081:0:-:0;;;362:1;342:21;;395:1;369:27;;497:58;;;;;;;;;-1:-1:-1;536:12:0;522:26;;57:1081;;;;;;;;;;","srcMapRuntime":"57:1081:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;562:95;;;:::i;:::-;;763:198;;;:::i;1048:88::-;1118:11;;1048:88;;;458:25:1;;;446:2;431:18;1048:88:0;;;;;;;454:36;;;;;967:75;1008:3;1030:5;967:75;;662:95;;;:::i;562:::-;616:1;607:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;644:5:0;;632:18;;458:25:1;;;632:18:0;;446:2:1;431:18;632::0;;;;;;;;562:95::o;763:198::-;816:10;169:42;816:21;808:62;;;;;;;696:2:1;808:62:0;;;678:21:1;735:2;715:18;;;708:30;774;754:18;;;747:58;822:18;;808:62:0;;;;;;;;895:2;880:11;;:17;;;;;;;:::i;:::-;;;;-1:-1:-1;;942:11:0;;912:42;;;930:10;186:74:1;;291:2;276:18;;269:34;;;;912:42:0;;159:18:1;912:42:0;141:168:1;662:95:0;716:1;707:5;;:10;;;;;;;:::i;:::-;;;;-1:-1:-1;;744:5:0;;732:18;;458:25:1;;;732:18:0;;446:2:1;431:18;732::0;413:76:1;1033:369;1072:3;1107;1104:1;1100:11;1218:1;1150:66;1146:74;1143:1;1139:82;1134:2;1127:10;1123:99;1120:2;;;1225:18;;:::i;:::-;1344:1;1276:66;1272:74;1269:1;1265:82;1261:2;1257:91;1254:2;;;1351:18;;:::i;:::-;-1:-1:-1;;1387:9:1;;1080:322::o;1407:372::-;1446:4;1482;1479:1;1475:12;1594:1;1526:66;1522:74;1519:1;1515:82;1510:2;1503:10;1499:99;1496:2;;;1601:18;;:::i;:::-;1720:1;1652:66;1648:74;1645:1;1641:82;1637:2;1633:91;1630:2;;;1727:18;;:::i;:::-;-1:-1:-1;;1764:9:1;;1455:324::o;1784:184::-;1836:77;1833:1;1826:88;1933:4;1930:1;1923:15;1957:4;1954:1;1947:15","abiDefinition":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"Decremented","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"Incremented","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"int256","name":"count","type":"int256"}],"name":"IncrementedByUser","type":"event"},{"inputs":[],"name":"decrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deployBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getVitalikCount","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incrementCounter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vitalikIncrement","outputs":[],"stateMutability":"nonpayable","type":"function"}],"userDoc":{"kind":"user","methods":{},"version":1},"developerDoc":{"kind":"dev","methods":{},"version":1},"metadata":"{\"compiler\":{\"version\":\"0.8.4+commit.c7e474f2\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Decremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"Incremented\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"count\",\"type\":\"int256\"}],\"name\":\"IncrementedByUser\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"decrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deployBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVitalikCount\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"\",\"type\":\"int256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"incrementCounter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"vitalikIncrement\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"/solidity/counter.sol\":\"Counter\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000},\"remappings\":[]},\"sources\":{\"/solidity/counter.sol\":{\"keccak256\":\"0x390b53ff5f95e07097f3bde2e637f0987013723a5694b49068f9f3cf9c6638a9\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2343068f1a3e9757b578ce9c0e3fa8c6a447f9ae2986b381c25960ab07c4fec8\",\"dweb:/ipfs/QmXLh2vW9mvc8Zfe56eoJzpYkx9fUByeTVwNMDApCKcraH\"]}},\"version\":1}"},"hashes":{"decrementCounter()":"f5c5ad83","deployBlock()":"a3ec191a","getCount()":"a87d942c","getVitalikCount()":"9f6f1ec1","incrementCounter()":"5b34b966","vitalikIncrement()":"6c573535"}}} \ No newline at end of file diff --git a/ethergo/example/counter/counter.sol b/ethergo/example/counter/counter.sol index 581c3ceca8..49da60c0e9 100644 --- a/ethergo/example/counter/counter.sol +++ b/ethergo/example/counter/counter.sol @@ -5,19 +5,34 @@ contract Counter { // this is used for testing account impersonation address constant VITALIK = address(0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); + event Incremented(int count); + event Decremented(int count); + event IncrementedByUser(address user, int count); + int private count = 0; int private vitaikCount = 0; + // @dev the block the contract was deployed at + uint256 public immutable deployBlock; + + constructor() { + deployBlock = block.number; + } + + function incrementCounter() public { count += 1; + emit Incremented(count); } function decrementCounter() public { count -= 1; + emit Decremented(count); } function vitalikIncrement() public { require(msg.sender == VITALIK, "Only Vitalik can count by 10"); vitaikCount += 10; + emit IncrementedByUser(msg.sender, vitaikCount); } function getCount() public view returns (int) { diff --git a/ethergo/example/deploymanager.go b/ethergo/example/deploymanager.go new file mode 100644 index 0000000000..8f86031b7d --- /dev/null +++ b/ethergo/example/deploymanager.go @@ -0,0 +1,30 @@ +package example + +import ( + "context" + "github.com/synapsecns/sanguine/ethergo/backends" + "github.com/synapsecns/sanguine/ethergo/contracts" + "github.com/synapsecns/sanguine/ethergo/example/counter" + "github.com/synapsecns/sanguine/ethergo/manager" + "testing" +) + +// DeployManager wraps DeployManager and allows typed contract handles to be returned. +type DeployManager struct { + *manager.DeployerManager +} + +// NewDeployManager creates a new DeployManager. +func NewDeployManager(t *testing.T) *DeployManager { + t.Helper() + + parentManager := manager.NewDeployerManager(t, NewCounterDeployer) + return &DeployManager{parentManager} +} + +// GetCounter gets the pre-created counter. +func (d *DeployManager) GetCounter(ctx context.Context, backend backends.SimulatedTestBackend) (contract contracts.DeployedContract, handle *counter.CounterRef) { + d.T().Helper() + + return manager.GetContract[*counter.CounterRef](ctx, d.T(), d, backend, CounterType) +} diff --git a/ethergo/listener/db/doc.go b/ethergo/listener/db/doc.go new file mode 100644 index 0000000000..cf130b4543 --- /dev/null +++ b/ethergo/listener/db/doc.go @@ -0,0 +1,2 @@ +// Package db provides the database layer for the chain listener. +package db diff --git a/ethergo/chain/listener/db/service.go b/ethergo/listener/db/service.go similarity index 100% rename from ethergo/chain/listener/db/service.go rename to ethergo/listener/db/service.go diff --git a/ethergo/chain/listener/db/store.go b/ethergo/listener/db/store.go similarity index 100% rename from ethergo/chain/listener/db/store.go rename to ethergo/listener/db/store.go diff --git a/ethergo/chain/listener/doc.go b/ethergo/listener/doc.go similarity index 100% rename from ethergo/chain/listener/doc.go rename to ethergo/listener/doc.go diff --git a/ethergo/chain/listener/export_test.go b/ethergo/listener/export_test.go similarity index 94% rename from ethergo/chain/listener/export_test.go rename to ethergo/listener/export_test.go index d2d17df3fc..16c1a3faf1 100644 --- a/ethergo/chain/listener/export_test.go +++ b/ethergo/listener/export_test.go @@ -4,8 +4,8 @@ import ( "context" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/core/metrics" - "github.com/synapsecns/sanguine/ethergo/chain/listener/db" "github.com/synapsecns/sanguine/ethergo/client" + "github.com/synapsecns/sanguine/ethergo/listener/db" ) // TestChainListener wraps chain listener for testing. diff --git a/ethergo/chain/listener/listener.go b/ethergo/listener/listener.go similarity index 95% rename from ethergo/chain/listener/listener.go rename to ethergo/listener/listener.go index 280abe5dd7..3a9fa00749 100644 --- a/ethergo/chain/listener/listener.go +++ b/ethergo/listener/listener.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "github.com/synapsecns/sanguine/ethergo/chain/listener/db" + db2 "github.com/synapsecns/sanguine/ethergo/listener/db" "math/big" "time" @@ -39,7 +39,7 @@ type chainListener struct { client client.EVM address common.Address initialBlock uint64 - store db.ChainListenerDB + store db2.ChainListenerDB handler metrics.Handler backoff *backoff.Backoff // IMPORTANT! These fields cannot be used until they has been set. They are NOT @@ -52,11 +52,11 @@ type chainListener struct { var ( logger = log.Logger("chainlistener-logger") // ErrNoLatestBlockForChainID is returned when no block exists for the chain. - ErrNoLatestBlockForChainID = db.ErrNoLatestBlockForChainID + ErrNoLatestBlockForChainID = db2.ErrNoLatestBlockForChainID ) // NewChainListener creates a new chain listener. -func NewChainListener(omnirpcClient client.EVM, store db.ChainListenerDB, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) { +func NewChainListener(omnirpcClient client.EVM, store db2.ChainListenerDB, address common.Address, initialBlock uint64, handler metrics.Handler) (ContractListener, error) { return &chainListener{ handler: handler, address: address, diff --git a/ethergo/listener/listener_test.go b/ethergo/listener/listener_test.go new file mode 100644 index 0000000000..53476a5f1c --- /dev/null +++ b/ethergo/listener/listener_test.go @@ -0,0 +1,59 @@ +package listener_test + +import ( + "context" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/synapsecns/sanguine/ethergo/listener" + "sync" +) + +func (l *ListenerTestSuite) TestListenForEvents() { + _, handle := l.manager.GetCounter(l.GetTestContext(), l.backend) + var wg sync.WaitGroup + const iterations = 50 + for i := 0; i < iterations; i++ { + i := i + wg.Add(1) + go func(_ int) { + defer wg.Done() + + auth := l.backend.GetTxContext(l.GetTestContext(), nil) + + //nolint:typecheck + bridgeRequestTX, err := handle.IncrementCounter(auth.TransactOpts) + l.NoError(err) + l.NotNil(bridgeRequestTX) + + l.backend.WaitForConfirmation(l.GetTestContext(), bridgeRequestTX) + + bridgeResponseTX, err := handle.DecrementCounter(auth.TransactOpts) + l.NoError(err) + l.NotNil(bridgeResponseTX) + l.backend.WaitForConfirmation(l.GetTestContext(), bridgeResponseTX) + }(i) + } + + wg.Wait() + + startBlock, err := handle.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) + l.NoError(err) + + cl, err := listener.NewChainListener(l.backend, l.store, handle.Address(), uint64(startBlock.Int64()), l.metrics) + l.NoError(err) + + eventCount := 0 + + // TODO: check for timeout,but it will be extremely obvious if it gets hit. + listenCtx, cancel := context.WithCancel(l.GetTestContext()) + err = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error { + eventCount++ + + if eventCount == iterations*2 { + cancel() + } + + return nil + }) + l.NoError(err) +} diff --git a/ethergo/chain/listener/suite_test.go b/ethergo/listener/suite_test.go similarity index 77% rename from ethergo/chain/listener/suite_test.go rename to ethergo/listener/suite_test.go index 0f68eabdc5..bea962c705 100644 --- a/ethergo/chain/listener/suite_test.go +++ b/ethergo/listener/suite_test.go @@ -6,7 +6,10 @@ import ( "github.com/brianvoe/gofakeit/v6" "github.com/ipfs/go-log" common_base "github.com/synapsecns/sanguine/core/dbcommon" - "github.com/synapsecns/sanguine/ethergo/chain/listener/db" + "github.com/synapsecns/sanguine/ethergo/example" + "github.com/synapsecns/sanguine/ethergo/example/counter" + "github.com/synapsecns/sanguine/ethergo/listener" + db2 "github.com/synapsecns/sanguine/ethergo/listener/db" "github.com/synapsecns/sanguine/ethergo/submitter/db/txdb" "gorm.io/gorm" "gorm.io/gorm/schema" @@ -22,10 +25,6 @@ import ( "github.com/synapsecns/sanguine/core/testsuite" "github.com/synapsecns/sanguine/ethergo/backends" "github.com/synapsecns/sanguine/ethergo/backends/geth" - "github.com/synapsecns/sanguine/ethergo/chain/listener" - "github.com/synapsecns/sanguine/ethergo/contracts" - "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" - "github.com/synapsecns/sanguine/services/rfq/testutil" "gorm.io/driver/sqlite" ) @@ -33,15 +32,16 @@ const chainID = 10 type ListenerTestSuite struct { *testsuite.TestSuite - manager *testutil.DeployManager - backend backends.SimulatedTestBackend - store db.ChainListenerDB - metrics metrics.Handler - fastBridge *fastbridge.FastBridgeRef - fastBridgeMetadata contracts.DeployedContract + manager *example.DeployManager + backend backends.SimulatedTestBackend + store db2.ChainListenerDB + metrics metrics.Handler + counter *counter.CounterRef } func NewListenerSuite(tb testing.TB) *ListenerTestSuite { + tb.Helper() + return &ListenerTestSuite{ TestSuite: testsuite.NewTestSuite(tb), } @@ -54,23 +54,23 @@ func TestListenerSuite(t *testing.T) { func (l *ListenerTestSuite) SetupTest() { l.TestSuite.SetupTest() - l.manager = testutil.NewDeployManager(l.T()) + l.manager = example.NewDeployManager(l.T()) l.backend = geth.NewEmbeddedBackendForChainID(l.GetTestContext(), l.T(), big.NewInt(chainID)) var err error l.metrics = metrics.NewNullHandler() l.store, err = NewSqliteStore(l.GetTestContext(), filet.TmpDir(l.T(), ""), l.metrics) l.Require().NoError(err) - l.fastBridgeMetadata, l.fastBridge = l.manager.GetFastBridge(l.GetTestContext(), l.backend) + _, l.counter = l.manager.GetCounter(l.GetTestContext(), l.backend) } func (l *ListenerTestSuite) TestGetMetadataNoStore() { - deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) + deployBlock, err := l.counter.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) l.NoError(err) // nothing stored, should use start block cl := listener.NewTestChainListener(listener.TestChainListenerArgs{ - Address: l.fastBridge.Address(), + Address: l.counter.Address(), InitialBlock: deployBlock.Uint64(), Client: l.backend, Store: l.store, @@ -85,13 +85,13 @@ func (l *ListenerTestSuite) TestGetMetadataNoStore() { func (l *ListenerTestSuite) TestStartBlock() { cl := listener.NewTestChainListener(listener.TestChainListenerArgs{ - Address: l.fastBridge.Address(), + Address: l.counter.Address(), Client: l.backend, Store: l.store, Handler: l.metrics, }) - deployBlock, err := l.fastBridge.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) + deployBlock, err := l.counter.DeployBlock(&bind.CallOpts{Context: l.GetTestContext()}) l.NoError(err) expectedLastIndexed := deployBlock.Uint64() + 10 @@ -99,6 +99,7 @@ func (l *ListenerTestSuite) TestStartBlock() { l.NoError(err) startBlock, cid, err := cl.GetMetadata(l.GetTestContext()) + l.NoError(err) l.Equal(cid, uint64(chainID)) l.Equal(startBlock, expectedLastIndexed) } @@ -108,7 +109,7 @@ func (l *ListenerTestSuite) TestListen() { } // NewSqliteStore creates a new sqlite data store. -func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Handler) (_ *db.Store, err error) { +func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Handler) (_ *db2.Store, err error) { logger := log.Logger("sqlite-store") logger.Debugf("creating sqlite store at %s", dbPath) @@ -141,7 +142,7 @@ func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Ha return nil, fmt.Errorf("could not connect to db %s: %w", dbPath, err) } - err = gdb.AutoMigrate(&db.LastIndexed{}) + err = gdb.AutoMigrate(&db2.LastIndexed{}) if err != nil { return nil, fmt.Errorf("could not migrate models: %w", err) } @@ -152,5 +153,5 @@ func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Ha if err != nil { return nil, fmt.Errorf("could not migrate models: %w", err) } - return db.NewChainListenerStore(gdb, handler), nil + return db2.NewChainListenerStore(gdb, handler), nil } diff --git a/services/rfq/e2e/rfq_test.go b/services/rfq/e2e/rfq_test.go index 0a8e376271..e87cef2739 100644 --- a/services/rfq/e2e/rfq_test.go +++ b/services/rfq/e2e/rfq_test.go @@ -238,10 +238,7 @@ func (i *IntegrationSuite) TestUSDCtoUSDC() { } originPending, err := i.store.HasPendingRebalance(i.GetTestContext(), uint64(i.originBackend.GetChainID())) i.NoError(err) - if !originPending { - return false - } - return true + return originPending }) } diff --git a/services/rfq/go.mod b/services/rfq/go.mod index e10412187b..f6d0289d79 100644 --- a/services/rfq/go.mod +++ b/services/rfq/go.mod @@ -14,7 +14,6 @@ require ( github.com/ipfs/go-log v1.0.5 github.com/jellydator/ttlcache/v3 v3.1.1 github.com/jftuga/ellipsis v1.0.0 - github.com/jpillora/backoff v1.0.0 github.com/lmittmann/w3 v0.10.0 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/puzpuzpuz/xsync/v2 v2.5.1 @@ -22,7 +21,8 @@ require ( github.com/stretchr/testify v1.8.4 github.com/synapsecns/sanguine/contrib/screener-api v0.0.0-00010101000000-000000000000 github.com/synapsecns/sanguine/core v0.0.0-00010101000000-000000000000 - github.com/synapsecns/sanguine/ethergo v0.0.2 + github.com/synapsecns/sanguine/ethergo v0.1.0 + github.com/synapsecns/sanguine/services/cctp-relayer v0.0.0-00010101000000-000000000000 github.com/synapsecns/sanguine/services/omnirpc v0.0.0-00010101000000-000000000000 github.com/urfave/cli/v2 v2.25.7 go.opentelemetry.io/otel v1.22.0 @@ -128,7 +128,6 @@ require ( github.com/go-stack/stack v1.8.1 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.3 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -167,6 +166,7 @@ require ( github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/klauspost/compress v1.17.3 // indirect @@ -288,8 +288,8 @@ replace ( github.com/synapsecns/sanguine/contrib/screener-api => ../../contrib/screener-api github.com/synapsecns/sanguine/core => ../../core github.com/synapsecns/sanguine/ethergo => ../../ethergo + github.com/synapsecns/sanguine/services/cctp-relayer => ../cctp-relayer github.com/synapsecns/sanguine/services/omnirpc => ../omnirpc github.com/synapsecns/sanguine/services/scribe => ../scribe github.com/synapsecns/sanguine/tools => ../../tools - github.com/synapsecns/sanguine/services/cctp-relayer => ../cctp-relayer ) diff --git a/services/rfq/relayer/chain/chain.go b/services/rfq/relayer/chain/chain.go index 32cffe9287..1655c36cfa 100644 --- a/services/rfq/relayer/chain/chain.go +++ b/services/rfq/relayer/chain/chain.go @@ -10,8 +10,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core" - "github.com/synapsecns/sanguine/ethergo/chain/listener" "github.com/synapsecns/sanguine/ethergo/client" + "github.com/synapsecns/sanguine/ethergo/listener" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 9cf37b82ac..d464c7947f 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -9,7 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/metrics" - "github.com/synapsecns/sanguine/ethergo/chain/listener" + "github.com/synapsecns/sanguine/ethergo/listener" "github.com/synapsecns/sanguine/ethergo/submitter" "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" diff --git a/services/rfq/relayer/relapi/server.go b/services/rfq/relayer/relapi/server.go index 73d215de31..faad66141d 100644 --- a/services/rfq/relayer/relapi/server.go +++ b/services/rfq/relayer/relapi/server.go @@ -14,7 +14,7 @@ import ( "github.com/gin-gonic/gin" "github.com/synapsecns/sanguine/core/metrics" baseServer "github.com/synapsecns/sanguine/core/server" - "github.com/synapsecns/sanguine/ethergo/chain/listener" + "github.com/synapsecns/sanguine/ethergo/listener" omniClient "github.com/synapsecns/sanguine/services/omnirpc/client" "github.com/synapsecns/sanguine/services/rfq/contracts/fastbridge" "github.com/synapsecns/sanguine/services/rfq/relayer/chain" diff --git a/services/rfq/relayer/reldb/base/store.go b/services/rfq/relayer/reldb/base/store.go index ce9bb59de6..1a026933f6 100644 --- a/services/rfq/relayer/reldb/base/store.go +++ b/services/rfq/relayer/reldb/base/store.go @@ -2,7 +2,7 @@ package base import ( "github.com/synapsecns/sanguine/core/metrics" - listenerDB "github.com/synapsecns/sanguine/ethergo/chain/listener/db" + listenerDB "github.com/synapsecns/sanguine/ethergo/listener/db" submitterDB "github.com/synapsecns/sanguine/ethergo/submitter/db" "github.com/synapsecns/sanguine/ethergo/submitter/db/txdb" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" diff --git a/services/rfq/relayer/reldb/db.go b/services/rfq/relayer/reldb/db.go index 894358dd30..f3af3ed0b7 100644 --- a/services/rfq/relayer/reldb/db.go +++ b/services/rfq/relayer/reldb/db.go @@ -5,7 +5,7 @@ import ( "database/sql/driver" "errors" "fmt" - "github.com/synapsecns/sanguine/ethergo/chain/listener/db" + "github.com/synapsecns/sanguine/ethergo/listener/db" "math/big" "github.com/ethereum/go-ethereum/common" diff --git a/services/rfq/relayer/reldb/db_test.go b/services/rfq/relayer/reldb/db_test.go index 3756641dd9..d36fc760c1 100644 --- a/services/rfq/relayer/reldb/db_test.go +++ b/services/rfq/relayer/reldb/db_test.go @@ -2,7 +2,7 @@ package reldb_test import ( "errors" - "github.com/synapsecns/sanguine/ethergo/chain/listener" + "github.com/synapsecns/sanguine/ethergo/listener" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" ) diff --git a/services/rfq/relayer/service/relayer.go b/services/rfq/relayer/service/relayer.go index d4cb2fb40d..d1af5aa87a 100644 --- a/services/rfq/relayer/service/relayer.go +++ b/services/rfq/relayer/service/relayer.go @@ -12,7 +12,7 @@ import ( "github.com/jellydator/ttlcache/v3" "github.com/synapsecns/sanguine/core/dbcommon" "github.com/synapsecns/sanguine/core/metrics" - "github.com/synapsecns/sanguine/ethergo/chain/listener" + "github.com/synapsecns/sanguine/ethergo/listener" signerConfig "github.com/synapsecns/sanguine/ethergo/signer/config" "github.com/synapsecns/sanguine/ethergo/signer/signer" "github.com/synapsecns/sanguine/ethergo/submitter" diff --git a/services/rfq/testutil/deployers.go b/services/rfq/testutil/deployers.go index 4d0522d0e1..79ae1538e8 100644 --- a/services/rfq/testutil/deployers.go +++ b/services/rfq/testutil/deployers.go @@ -27,7 +27,6 @@ type DeployManager struct { func NewDeployManager(t *testing.T) *DeployManager { t.Helper() - // TODO: add contracts here parentManager := manager.NewDeployerManager(t, NewFastBridgeDeployer, NewMockERC20Deployer, NewMockFastBridgeDeployer, NewWETH9Deployer, NewUSDTDeployer, NewUSDCDeployer, NewDAIDeployer) return &DeployManager{parentManager} } From cd760c251b0150921ba23fa211268b1790b2bdbc Mon Sep 17 00:00:00 2001 From: Trajan0x Date: Thu, 29 Feb 2024 18:58:06 +0000 Subject: [PATCH 63/75] more cleanup --- ethergo/listener/listener_test.go | 5 ++--- ethergo/listener/suite_test.go | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ethergo/listener/listener_test.go b/ethergo/listener/listener_test.go index 53476a5f1c..24b314afa9 100644 --- a/ethergo/listener/listener_test.go +++ b/ethergo/listener/listener_test.go @@ -11,7 +11,7 @@ import ( func (l *ListenerTestSuite) TestListenForEvents() { _, handle := l.manager.GetCounter(l.GetTestContext(), l.backend) var wg sync.WaitGroup - const iterations = 50 + const iterations = 10 for i := 0; i < iterations; i++ { i := i wg.Add(1) @@ -46,7 +46,7 @@ func (l *ListenerTestSuite) TestListenForEvents() { // TODO: check for timeout,but it will be extremely obvious if it gets hit. listenCtx, cancel := context.WithCancel(l.GetTestContext()) - err = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error { + _ = cl.Listen(listenCtx, func(ctx context.Context, log types.Log) error { eventCount++ if eventCount == iterations*2 { @@ -55,5 +55,4 @@ func (l *ListenerTestSuite) TestListenForEvents() { return nil }) - l.NoError(err) } diff --git a/ethergo/listener/suite_test.go b/ethergo/listener/suite_test.go index bea962c705..de38f6594e 100644 --- a/ethergo/listener/suite_test.go +++ b/ethergo/listener/suite_test.go @@ -10,7 +10,6 @@ import ( "github.com/synapsecns/sanguine/ethergo/example/counter" "github.com/synapsecns/sanguine/ethergo/listener" db2 "github.com/synapsecns/sanguine/ethergo/listener/db" - "github.com/synapsecns/sanguine/ethergo/submitter/db/txdb" "gorm.io/gorm" "gorm.io/gorm/schema" "math/big" @@ -149,7 +148,8 @@ func NewSqliteStore(parentCtx context.Context, dbPath string, handler metrics.Ha handler.AddGormCallbacks(gdb) - err = gdb.WithContext(ctx).AutoMigrate(txdb.GetAllModels()...) + err = gdb.WithContext(ctx).AutoMigrate(db2.GetAllModels()...) + if err != nil { return nil, fmt.Errorf("could not migrate models: %w", err) } From 7ff3b2a0b4f2378683301c3c668e1084e6abdc84 Mon Sep 17 00:00:00 2001 From: Trajan0x Date: Thu, 29 Feb 2024 19:16:05 +0000 Subject: [PATCH 64/75] [goreleaser] From d91525a6cc93f7fb84b1e0b5123f5dc36e7fb8ad Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 29 Feb 2024 13:31:41 -0600 Subject: [PATCH 65/75] Feat: add tracing --- services/rfq/relayer/inventory/export_test.go | 2 +- services/rfq/relayer/inventory/manager.go | 27 ++++++++++++-- services/rfq/relayer/inventory/rebalance.go | 36 ++++++++++++++++--- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/services/rfq/relayer/inventory/export_test.go b/services/rfq/relayer/inventory/export_test.go index 0bd65c18f7..1a7c2dd7ad 100644 --- a/services/rfq/relayer/inventory/export_test.go +++ b/services/rfq/relayer/inventory/export_test.go @@ -7,5 +7,5 @@ import ( // GetRebalance is a wrapper around the internal getRebalance function. func GetRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (*RebalanceData, error) { - return getRebalance(cfg, tokens, chainID, token) + return getRebalance(nil, cfg, tokens, chainID, token) } diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 921a73be0c..e882511e16 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/big" + "strconv" "sync" "time" @@ -346,7 +347,7 @@ func (i *inventoryManagerImpl) HasSufficientGas(ctx context.Context, origin, des // 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 { +func (i *inventoryManagerImpl) Rebalance(parentCtx context.Context, chainID int, token common.Address) error { // evaluate the rebalance method method, err := i.cfg.GetRebalanceMethod(chainID, token.Hex()) if err != nil { @@ -355,21 +356,35 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token if method == relconfig.RebalanceMethodNone { return nil } + ctx, span := i.handler.Tracer().Start(parentCtx, "Rebalance", trace.WithAttributes( + attribute.Int(metrics.ChainID, chainID), + attribute.String("token", token.Hex()), + attribute.String("rebalance_method", method.String()), + )) + defer func(err error) { + metrics.EndSpanWithErr(span, err) + }(err) // build the rebalance action - rebalance, err := getRebalance(i.cfg, i.tokens, chainID, token) + rebalance, err := getRebalance(span, i.cfg, i.tokens, chainID, token) if err != nil { return fmt.Errorf("could not get rebalance: %w", err) } if rebalance == nil { return nil } + span.SetAttributes( + attribute.String("rebalance_origin", strconv.Itoa(rebalance.OriginMetadata.ChainID)), + attribute.String("rebalance_dest", strconv.Itoa(rebalance.DestMetadata.ChainID)), + attribute.String("rebalance_amount", rebalance.Amount.String()), + ) // make sure there are no pending rebalances that touch the given path pending, err := i.db.HasPendingRebalance(ctx, uint64(rebalance.OriginMetadata.ChainID), uint64(rebalance.DestMetadata.ChainID)) if err != nil { return fmt.Errorf("could not check pending rebalance: %w", err) } + span.SetAttributes(attribute.Bool("rebalance_pending", pending)) if pending { return nil } @@ -387,7 +402,7 @@ func (i *inventoryManagerImpl) Rebalance(ctx context.Context, chainID int, token } //nolint:cyclop -func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { +func getRebalance(span trace.Span, cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { maintenancePct, err := cfg.GetMaintenanceBalancePct(chainID, token.Hex()) if err != nil { return nil, fmt.Errorf("could not get maintenance pct: %w", err) @@ -432,6 +447,12 @@ func getRebalance(cfg relconfig.Config, tokens map[int]map[common.Address]*Token if err != nil { return nil, fmt.Errorf("could not get initial pct: %w", err) } + if span != nil { + span.SetAttributes(attribute.Float64("maintenance_pct", maintenancePct)) + span.SetAttributes(attribute.Float64("initial_pct", initialPct)) + span.SetAttributes(attribute.String("max_token_balance", maxTokenData.Balance.String())) + span.SetAttributes(attribute.String("min_token_balance", minTokenData.Balance.String())) + } // 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/100)).Int(nil) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 9cf37b82ac..3ab77fe934 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/synapsecns/sanguine/core/metrics" "github.com/synapsecns/sanguine/ethergo/chain/listener" @@ -14,6 +15,8 @@ import ( "github.com/synapsecns/sanguine/services/cctp-relayer/contracts/cctp" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" "github.com/synapsecns/sanguine/services/rfq/relayer/reldb" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" ) @@ -133,11 +136,19 @@ func (c *rebalanceManagerCCTP) initListeners(ctx context.Context) (err error) { return nil } -func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *RebalanceData) (err error) { +func (c *rebalanceManagerCCTP) Execute(parentCtx context.Context, rebalance *RebalanceData) (err error) { contract, ok := c.cctpContracts[rebalance.OriginMetadata.ChainID] if !ok { return fmt.Errorf("could not find cctp contract for chain %d", rebalance.OriginMetadata.ChainID) } + ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Execute", trace.WithAttributes( + attribute.Int("rebalance_origin", int(rebalance.OriginMetadata.ChainID)), + attribute.Int("rebalance_dest", int(rebalance.DestMetadata.ChainID)), + attribute.String("rebalance_amount", rebalance.Amount.String()), + )) + defer func(err error) { + metrics.EndSpanWithErr(span, err) + }(err) // perform rebalance by calling sendCircleToken() _, err = c.txSubmitter.SubmitTransaction(ctx, big.NewInt(int64(rebalance.OriginMetadata.ChainID)), func(transactor *bind.TransactOpts) (tx *types.Transaction, err error) { @@ -174,12 +185,12 @@ func (c *rebalanceManagerCCTP) Execute(ctx context.Context, rebalance *Rebalance } // nolint:cyclop -func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err error) { +func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (err error) { listener, ok := c.chainListeners[chainID] if !ok { return fmt.Errorf("could not find listener for chain %d", chainID) } - ethClient, err := c.chainClient.GetClient(ctx, big.NewInt(int64(chainID))) + ethClient, err := c.chainClient.GetClient(parentCtx, big.NewInt(int64(chainID))) if err != nil { return fmt.Errorf("could not get chain client: %w", err) } @@ -189,7 +200,14 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err return fmt.Errorf("could not get cctp events: %w", err) } - err = listener.Listen(ctx, func(parentCtx context.Context, log types.Log) (err error) { + err = listener.Listen(parentCtx, func(parentCtx context.Context, log types.Log) (err error) { + ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Listen", trace.WithAttributes( + attribute.Int(metrics.ChainID, chainID), + )) + defer func(err error) { + metrics.EndSpanWithErr(span, err) + }(err) + switch log.Topics[0] { case cctp.CircleRequestSentTopic: parsedEvent, err := parser.ParseCircleRequestSent(log) @@ -197,8 +215,12 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err logger.Warnf("could not parse circle request sent: %w", err) return nil } + span.SetAttributes( + attribute.String("log_type", "CircleRequestSent"), + attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])), + ) origin := uint64(chainID) - err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, &origin, reldb.RebalancePending) + err = c.db.UpdateRebalanceStatus(ctx, parsedEvent.RequestID, &origin, reldb.RebalancePending) if err != nil { logger.Warnf("could not update rebalance status: %w", err) return nil @@ -209,6 +231,10 @@ func (c *rebalanceManagerCCTP) listen(ctx context.Context, chainID int) (err err logger.Warnf("could not parse circle request fulfilled: %w", err) return nil } + span.SetAttributes( + attribute.String("log_type", "CircleRequestFulfilled"), + attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])), + ) err = c.db.UpdateRebalanceStatus(parentCtx, parsedEvent.RequestID, nil, reldb.RebalanceCompleted) if err != nil { logger.Warnf("could not update rebalance status: %w", err) From edeeb0eb07cb1634d8dd37ce9fa3d94b51510073 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Thu, 29 Feb 2024 13:31:53 -0600 Subject: [PATCH 66/75] [goreleaser] From 5743f5801823657ce2035534ec6bd4bea0bb09d9 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 11:12:01 -0600 Subject: [PATCH 67/75] Feat: add more tracing around rebalance trigger --- services/rfq/relayer/inventory/manager.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index e882511e16..e9bbd98b1c 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -447,18 +447,24 @@ func getRebalance(span trace.Span, cfg relconfig.Config, tokens map[int]map[comm if err != nil { return nil, fmt.Errorf("could not get initial pct: %w", err) } + maintenanceThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(maintenancePct/100)).Int(nil) if span != nil { span.SetAttributes(attribute.Float64("maintenance_pct", maintenancePct)) span.SetAttributes(attribute.Float64("initial_pct", initialPct)) span.SetAttributes(attribute.String("max_token_balance", maxTokenData.Balance.String())) span.SetAttributes(attribute.String("min_token_balance", minTokenData.Balance.String())) + span.SetAttributes(attribute.String("total_balance", totalBalance.String())) + span.SetAttributes(attribute.String("maintenance_thresh", maintenanceThresh.String())) } // 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/100)).Int(nil) if minTokenData.Balance.Cmp(maintenanceThresh) < 0 { initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) + span.SetAttributes( + attribute.String("initial_thresh", initialThresh.String()), + attribute.String("rebalance_amount", amount.String()), + ) if amount.Cmp(big.NewInt(0)) < 0 { // do not rebalance since it would take us below initial threshold //nolint:nilnil @@ -473,7 +479,7 @@ func getRebalance(span trace.Span, cfg relconfig.Config, tokens map[int]map[comm return rebalance, nil } -// initializes tokens converts the configuration into a data structure we can use to determine inventory +// initializeTokens 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) { i.mux.Lock() From d8deade691a193d5635b4f3f67691b8d82d694eb Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 11:12:04 -0600 Subject: [PATCH 68/75] [goreleaser] From e1296a404a697aecd21b4329928e62a421973cc1 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:09:14 -0600 Subject: [PATCH 69/75] Feat: add MaxRebalanceAmount and getter test --- services/rfq/relayer/relconfig/config.go | 2 ++ services/rfq/relayer/relconfig/config_test.go | 18 ++++++++++ services/rfq/relayer/relconfig/getters.go | 33 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/services/rfq/relayer/relconfig/config.go b/services/rfq/relayer/relconfig/config.go index 3439df4889..12d3cb7ef6 100644 --- a/services/rfq/relayer/relconfig/config.go +++ b/services/rfq/relayer/relconfig/config.go @@ -102,6 +102,8 @@ type TokenConfig struct { MaintenanceBalancePct float64 `yaml:"maintenance_balance_pct"` // InitialBalancePct is the percentage of the total balance to retain when triggering a rebalance. InitialBalancePct float64 `yaml:"initial_balance_pct"` + // MaxRebalanceAmount is the maximum amount to rebalance in human-readable units. + MaxRebalanceAmount string `yaml:"max_rebalance_amount"` } // DatabaseConfig represents the configuration for the database. diff --git a/services/rfq/relayer/relconfig/config_test.go b/services/rfq/relayer/relconfig/config_test.go index b0b9e6e16d..b364ca2a7e 100644 --- a/services/rfq/relayer/relconfig/config_test.go +++ b/services/rfq/relayer/relconfig/config_test.go @@ -5,6 +5,8 @@ import ( "time" "github.com/alecthomas/assert" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/services/rfq/relayer/relconfig" ) @@ -12,6 +14,7 @@ import ( func TestGetters(t *testing.T) { chainID := 1 badChainID := 2 + usdcAddr := "0x123" cfgWithBase := relconfig.Config{ Chains: map[int]relconfig.ChainConfig{ chainID: { @@ -65,6 +68,13 @@ func TestGetters(t *testing.T) { QuotePct: 50, QuoteOffsetBps: 10, FixedFeeMultiplier: 1.1, + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcAddr, + Decimals: 6, + MaxRebalanceAmount: "1000", + }, + }, }, }, } @@ -264,4 +274,12 @@ func TestGetters(t *testing.T) { assert.NoError(t, err) assert.Equal(t, chainVal, cfgWithBase.Chains[chainID].FixedFeeMultiplier) }) + + t.Run("GetMaxRebalanceAmount", func(t *testing.T) { + defaultVal := cfg.GetMaxRebalanceAmount(badChainID, common.HexToAddress(usdcAddr)) + assert.Equal(t, defaultVal.String(), abi.MaxInt256.String()) + + chainVal := cfg.GetMaxRebalanceAmount(chainID, common.HexToAddress(usdcAddr)) + assert.Equal(t, chainVal.String(), "1000000000") + }) } diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index 69133b6e53..a1a9014618 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -6,6 +6,7 @@ import ( "reflect" "time" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/synapsecns/sanguine/ethergo/signer/config" ) @@ -546,6 +547,38 @@ func (c Config) GetMinQuoteAmount(chainID int, addr common.Address) *big.Int { return quoteAmountScaled } +var defaultMaxRebalanceAmount = abi.MaxInt256 + +// GetMaxRebalanceAmount returns the max rebalance amount for the given chain and address. +// Note that this getter returns the value in native token decimals. +func (c Config) GetMaxRebalanceAmount(chainID int, addr common.Address) *big.Int { + chainCfg, ok := c.Chains[chainID] + if !ok { + return defaultMaxRebalanceAmount + } + + var tokenCfg *TokenConfig + for _, cfg := range chainCfg.Tokens { + if common.HexToAddress(cfg.Address).Hex() == addr.Hex() { + cfgCopy := cfg + tokenCfg = &cfgCopy + break + } + } + if tokenCfg == nil { + return defaultMaxRebalanceAmount + } + maxRebalanceAmount, _ := new(big.Int).SetString(tokenCfg.MaxRebalanceAmount, 10) + if maxRebalanceAmount == nil { + return defaultMaxRebalanceAmount + } + + // Scale by the token decimals. + denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil) + maxRebalanceAmountScaled := new(big.Int).Mul(maxRebalanceAmount, denomDecimalsFactor) + return maxRebalanceAmountScaled +} + const defaultDBSelectorIntervalSeconds = 1 // GetDBSelectorInterval returns the interval for the DB selector. From edae4523890925c2b4238f9e1be4713d664cfc12 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:27:02 -0600 Subject: [PATCH 70/75] Feat: incorporate MaxRebalanceAmount and test --- services/rfq/relayer/inventory/manager.go | 40 +++++++---- .../rfq/relayer/inventory/manager_test.go | 66 ++++++++++++------- 2 files changed, 70 insertions(+), 36 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index e9bbd98b1c..2ce5d8b6b4 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -458,23 +458,37 @@ func getRebalance(span trace.Span, cfg relconfig.Config, tokens map[int]map[comm } // check if the minimum balance is below the threshold and trigger rebalance - if minTokenData.Balance.Cmp(maintenanceThresh) < 0 { - initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) - amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) + if minTokenData.Balance.Cmp(maintenanceThresh) > 0 { + return rebalance, nil + } + + // calculate the amount to rebalance vs the initial threshold on origin + initialThresh, _ := new(big.Float).Mul(new(big.Float).SetInt(totalBalance), big.NewFloat(initialPct/100)).Int(nil) + amount := new(big.Int).Sub(maxTokenData.Balance, initialThresh) + + // no need to rebalance since amount would be negative + if amount.Cmp(big.NewInt(0)) < 0 { + //nolint:nilnil + return nil, nil + } + + // clip the rebalance amount by the configured max + maxAmount := cfg.GetMaxRebalanceAmount(maxTokenData.ChainID, maxTokenData.Addr) + if amount.Cmp(maxAmount) > 0 { + amount = maxAmount + } + if span != nil { span.SetAttributes( attribute.String("initial_thresh", initialThresh.String()), attribute.String("rebalance_amount", amount.String()), + attribute.String("max_rebalance_amount", maxAmount.String()), ) - if amount.Cmp(big.NewInt(0)) < 0 { - // do not rebalance since it would take us below initial threshold - //nolint:nilnil - return nil, nil - } - rebalance = &RebalanceData{ - OriginMetadata: maxTokenData, - DestMetadata: minTokenData, - Amount: amount, - } + } + + rebalance = &RebalanceData{ + OriginMetadata: maxTokenData, + DestMetadata: minTokenData, + Amount: amount, } return rebalance, nil } diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index c82eb6515b..7a9d44b707 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -88,39 +88,48 @@ func (i *InventoryTestSuite) TestGetRebalance() { usdcDataDest.Addr: &usdcDataDest, }, } - cfg := relconfig.Config{ - Chains: map[int]relconfig.ChainConfig{ - origin: { - Tokens: map[string]relconfig.TokenConfig{ - "USDC": { - Address: usdcDataOrigin.Addr.Hex(), - MaintenanceBalancePct: 20, - InitialBalancePct: 50, + getConfig := func(maxRebalanceAmount string) relconfig.Config { + return relconfig.Config{ + Chains: map[int]relconfig.ChainConfig{ + origin: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataOrigin.Addr.Hex(), + Decimals: 6, + MaintenanceBalancePct: 20, + InitialBalancePct: 50, + MaxRebalanceAmount: maxRebalanceAmount, + }, }, }, - }, - dest: { - Tokens: map[string]relconfig.TokenConfig{ - "USDC": { - Address: usdcDataDest.Addr.Hex(), - MaintenanceBalancePct: 20, - InitialBalancePct: 50, + dest: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataDest.Addr.Hex(), + Decimals: 6, + MaintenanceBalancePct: 20, + InitialBalancePct: 50, + MaxRebalanceAmount: maxRebalanceAmount, + }, }, }, - }, - extra: { - Tokens: map[string]relconfig.TokenConfig{ - "USDC": { - Address: usdcDataExtra.Addr.Hex(), - MaintenanceBalancePct: 0, - InitialBalancePct: 0, + extra: { + Tokens: map[string]relconfig.TokenConfig{ + "USDC": { + Address: usdcDataExtra.Addr.Hex(), + Decimals: 6, + MaintenanceBalancePct: 0, + InitialBalancePct: 0, + MaxRebalanceAmount: maxRebalanceAmount, + }, }, }, }, - }, + } } // 10 USDC on both chains; no rebalance needed + cfg := getConfig("") usdcDataOrigin.Balance = big.NewInt(1e7) usdcDataDest.Balance = big.NewInt(1e7) rebalance, err := inventory.GetRebalance(cfg, tokens, origin, usdcDataOrigin.Addr) @@ -139,6 +148,17 @@ func (i *InventoryTestSuite) TestGetRebalance() { } i.Equal(expected, rebalance) + // Set max rebalance amount + cfgWithMax := getConfig("1") + rebalance, err = inventory.GetRebalance(cfgWithMax, tokens, origin, usdcDataOrigin.Addr) + i.NoError(err) + expected = &inventory.RebalanceData{ + OriginMetadata: &usdcDataOrigin, + DestMetadata: &usdcDataDest, + Amount: big.NewInt(1e6), + } + i.Equal(expected, rebalance) + // Increase initial threshold so that no rebalance can occur from origin usdcDataOrigin.Balance = big.NewInt(2e6) usdcDataDest.Balance = big.NewInt(1e6) From ab2a9ab624f1770fbe40e6e52fdca8e5c74add58 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:30:23 -0600 Subject: [PATCH 71/75] Feat: use big.Float for MaxRebalanceAmount parsing --- services/rfq/relayer/inventory/manager_test.go | 4 ++-- services/rfq/relayer/relconfig/getters.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/rfq/relayer/inventory/manager_test.go b/services/rfq/relayer/inventory/manager_test.go index 7a9d44b707..11d33709f6 100644 --- a/services/rfq/relayer/inventory/manager_test.go +++ b/services/rfq/relayer/inventory/manager_test.go @@ -149,13 +149,13 @@ func (i *InventoryTestSuite) TestGetRebalance() { i.Equal(expected, rebalance) // Set max rebalance amount - cfgWithMax := getConfig("1") + cfgWithMax := getConfig("1.1") rebalance, err = inventory.GetRebalance(cfgWithMax, tokens, origin, usdcDataOrigin.Addr) i.NoError(err) expected = &inventory.RebalanceData{ OriginMetadata: &usdcDataOrigin, DestMetadata: &usdcDataDest, - Amount: big.NewInt(1e6), + Amount: big.NewInt(1.1e6), } i.Equal(expected, rebalance) diff --git a/services/rfq/relayer/relconfig/getters.go b/services/rfq/relayer/relconfig/getters.go index a1a9014618..2e0b5362de 100644 --- a/services/rfq/relayer/relconfig/getters.go +++ b/services/rfq/relayer/relconfig/getters.go @@ -568,14 +568,14 @@ func (c Config) GetMaxRebalanceAmount(chainID int, addr common.Address) *big.Int if tokenCfg == nil { return defaultMaxRebalanceAmount } - maxRebalanceAmount, _ := new(big.Int).SetString(tokenCfg.MaxRebalanceAmount, 10) - if maxRebalanceAmount == nil { + rebalanceAmountFlt, ok := new(big.Float).SetString(tokenCfg.MaxRebalanceAmount) + if !ok || rebalanceAmountFlt == nil { return defaultMaxRebalanceAmount } // Scale by the token decimals. denomDecimalsFactor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenCfg.Decimals)), nil) - maxRebalanceAmountScaled := new(big.Int).Mul(maxRebalanceAmount, denomDecimalsFactor) + maxRebalanceAmountScaled, _ := new(big.Float).Mul(rebalanceAmountFlt, new(big.Float).SetInt(denomDecimalsFactor)).Int(nil) return maxRebalanceAmountScaled } From 996a82b0ba7ce5dcb71e5fbf8b2a2c8e32a531f9 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:30:25 -0600 Subject: [PATCH 72/75] [goreleaser] From 93a1e046dce078a5557ce708335f394e55e56126 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:47:11 -0600 Subject: [PATCH 73/75] Feat: add check for address match in cctp event --- services/rfq/relayer/inventory/rebalance.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index 602e68f196..d7e4670712 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -215,6 +215,9 @@ func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (e logger.Warnf("could not parse circle request sent: %w", err) return nil } + if parsedEvent.Sender != c.relayerAddress { + return nil + } span.SetAttributes( attribute.String("log_type", "CircleRequestSent"), attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])), @@ -231,6 +234,9 @@ func (c *rebalanceManagerCCTP) listen(parentCtx context.Context, chainID int) (e logger.Warnf("could not parse circle request fulfilled: %w", err) return nil } + if parsedEvent.Recipient != c.relayerAddress { + return nil + } span.SetAttributes( attribute.String("log_type", "CircleRequestFulfilled"), attribute.String("request_id", hexutil.Encode(parsedEvent.RequestID[:])), From b0f9d22c0613553627b93fcb5a9bfbbf900161b5 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Fri, 1 Mar 2024 12:47:13 -0600 Subject: [PATCH 74/75] [goreleaser] From 3285fe63a8555464908f5429084491f5de25acd6 Mon Sep 17 00:00:00 2001 From: Daniel Wasserman Date: Mon, 4 Mar 2024 12:24:05 -0600 Subject: [PATCH 75/75] Cleanup: lint --- services/rfq/relayer/inventory/manager.go | 2 +- services/rfq/relayer/inventory/rebalance.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/rfq/relayer/inventory/manager.go b/services/rfq/relayer/inventory/manager.go index 2ce5d8b6b4..658d1a7220 100644 --- a/services/rfq/relayer/inventory/manager.go +++ b/services/rfq/relayer/inventory/manager.go @@ -401,7 +401,7 @@ func (i *inventoryManagerImpl) Rebalance(parentCtx context.Context, chainID int, return nil } -//nolint:cyclop +//nolint:cyclop,gocognit func getRebalance(span trace.Span, cfg relconfig.Config, tokens map[int]map[common.Address]*TokenMetadata, chainID int, token common.Address) (rebalance *RebalanceData, err error) { maintenancePct, err := cfg.GetMaintenanceBalancePct(chainID, token.Hex()) if err != nil { diff --git a/services/rfq/relayer/inventory/rebalance.go b/services/rfq/relayer/inventory/rebalance.go index d7e4670712..55535ecbdf 100644 --- a/services/rfq/relayer/inventory/rebalance.go +++ b/services/rfq/relayer/inventory/rebalance.go @@ -142,8 +142,8 @@ func (c *rebalanceManagerCCTP) Execute(parentCtx context.Context, rebalance *Reb return fmt.Errorf("could not find cctp contract for chain %d", rebalance.OriginMetadata.ChainID) } ctx, span := c.handler.Tracer().Start(parentCtx, "rebalance.Execute", trace.WithAttributes( - attribute.Int("rebalance_origin", int(rebalance.OriginMetadata.ChainID)), - attribute.Int("rebalance_dest", int(rebalance.DestMetadata.ChainID)), + attribute.Int("rebalance_origin", rebalance.OriginMetadata.ChainID), + attribute.Int("rebalance_dest", rebalance.DestMetadata.ChainID), attribute.String("rebalance_amount", rebalance.Amount.String()), )) defer func(err error) {