diff --git a/core/services/ocr2/plugins/ccip/customtokens/usdc.go b/core/services/ocr2/plugins/ccip/customtokens/usdc.go index d2b1b8657f..f8c0a13f17 100644 --- a/core/services/ocr2/plugins/ccip/customtokens/usdc.go +++ b/core/services/ocr2/plugins/ccip/customtokens/usdc.go @@ -5,10 +5,16 @@ import ( "fmt" "io" "net/http" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" ) type USDCService struct { - attestationApi string + attestationApi string + sourceChainId uint64 + SourceUSDCToken common.Address } type USDCAttestationResponse struct { @@ -16,9 +22,21 @@ type USDCAttestationResponse struct { Attestation string `json:"attestation"` } +// Hard coded mapping of chain id to USDC token addresses +// Will be removed in favour of more flexible solution. +var USDCTokenMapping = map[uint64]common.Address{ + 420: common.HexToAddress("0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6"), + 43113: common.HexToAddress("0x5425890298aed601595a70ab815c96711a31bc65"), + 80001: common.HexToAddress("0x9999f7fea5938fd3b1e26a12c3f2fb024e194f97"), + 84531: common.HexToAddress("0xf175520c52418dfe19c8098071a252da48cd1c19"), + 421613: common.HexToAddress("0xfd064A18f3BF249cf1f87FC203E90D8f650f2d63"), +} + const ( - version = "v1" - attestationPath = "attestations" + version = "v1" + attestationPath = "attestations" + eventSignature = "8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036" + USDC_MESSAGE_SENT = "USDC message sent" ) type USDCAttestationStatus string @@ -28,8 +46,8 @@ const ( USDCAttestationStatusPending USDCAttestationStatus = "pending_confirmations" ) -func NewUSDCService(usdcAttestationApi string) *USDCService { - return &USDCService{attestationApi: usdcAttestationApi} +func NewUSDCService(usdcAttestationApi string, sourceChainId uint64) *USDCService { + return &USDCService{attestationApi: usdcAttestationApi, sourceChainId: sourceChainId, SourceUSDCToken: USDCTokenMapping[sourceChainId]} } func (usdc *USDCService) TryGetAttestation(messageHash string) (USDCAttestationResponse, error) { @@ -68,3 +86,13 @@ func (usdc *USDCService) IsAttestationComplete(messageHash string) (bool, string } return false, "", nil } + +func GetUSDCServiceSourceLPFilters(usdcTokenAddress common.Address) []logpoller.Filter { + return []logpoller.Filter{ + { + Name: logpoller.FilterName(USDC_MESSAGE_SENT, usdcTokenAddress.Hex()), + EventSigs: []common.Hash{common.HexToHash(eventSignature)}, + Addresses: []common.Address{usdcTokenAddress}, + }, + } +} diff --git a/core/services/ocr2/plugins/ccip/customtokens/usdc_test.go b/core/services/ocr2/plugins/ccip/customtokens/usdc_test.go index 3b9c0c5e41..e2678d506e 100644 --- a/core/services/ocr2/plugins/ccip/customtokens/usdc_test.go +++ b/core/services/ocr2/plugins/ccip/customtokens/usdc_test.go @@ -6,13 +6,17 @@ import ( "net/http/httptest" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/utils" ) func TestUSDCService_TryGetAttestation(t *testing.T) { t.Skipf("Skipping test because it uses the real USDC attestation API") usdcMessageHash := "0x912f22a13e9ccb979b621500f6952b2afd6e75be7eadaed93fc2625fe11c52a2" - usdcService := NewUSDCService("https://iris-api-sandbox.circle.com") + usdcService := NewUSDCService("https://iris-api-sandbox.circle.com", 420) attestation, err := usdcService.TryGetAttestation(usdcMessageHash) require.NoError(t, err) @@ -30,7 +34,7 @@ func TestUSDCService_TryGetAttestationMock(t *testing.T) { ts := getMockUSDCEndpoint(t, response) defer ts.Close() - usdcService := NewUSDCService(ts.URL) + usdcService := NewUSDCService(ts.URL, 420) attestation, err := usdcService.TryGetAttestation("0x912f22a13e9ccb979b621500f6952b2afd6e75be7eadaed93fc2625fe11c52a2") require.NoError(t, err) @@ -44,7 +48,7 @@ func TestUSDCService_TryGetAttestationMockError(t *testing.T) { })) defer ts.Close() - usdcService := NewUSDCService(ts.URL) + usdcService := NewUSDCService(ts.URL, 420) _, err := usdcService.TryGetAttestation("0x912f22a13e9ccb979b621500f6952b2afd6e75be7eadaed93fc2625fe11c52a2") require.Error(t, err) } @@ -58,7 +62,7 @@ func TestUSDCService_IsAttestationComplete(t *testing.T) { ts := getMockUSDCEndpoint(t, response) defer ts.Close() - usdcService := NewUSDCService(ts.URL) + usdcService := NewUSDCService(ts.URL, 420) isReady, attestation, err := usdcService.IsAttestationComplete("0x912f22a13e9ccb979b621500f6952b2afd6e75be7eadaed93fc2625fe11c52a2") require.NoError(t, err) @@ -75,3 +79,17 @@ func getMockUSDCEndpoint(t *testing.T, response USDCAttestationResponse) *httpte require.NoError(t, err) })) } + +// Asserts the hard coded event signature matches Keccak256("MessageSent(bytes)") +func TestGetUSDCServiceSourceLPFilters(t *testing.T) { + usdcTokenAddress := common.HexToAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48") + filters := GetUSDCServiceSourceLPFilters(usdcTokenAddress) + + require.Equal(t, 1, len(filters)) + filter := filters[0] + require.Equal(t, logpoller.FilterName(USDC_MESSAGE_SENT, usdcTokenAddress.Hex()), filter.Name) + hash, err := utils.Keccak256([]byte("MessageSent(bytes)")) + require.NoError(t, err) + require.Equal(t, hash, filter.EventSigs[0].Bytes()) + require.Equal(t, usdcTokenAddress, filter.Addresses[0]) +} diff --git a/core/services/ocr2/plugins/ccip/execution_plugin.go b/core/services/ocr2/plugins/ccip/execution_plugin.go index 6a4506b45c..6ed8171625 100644 --- a/core/services/ocr2/plugins/ccip/execution_plugin.go +++ b/core/services/ocr2/plugins/ccip/execution_plugin.go @@ -123,7 +123,7 @@ func NewExecutionServices(lggr logger.Logger, jb job.Job, chainSet evm.LegacyCha sourceClient: sourceChain.Client(), destGasEstimator: destChain.GasEstimator(), leafHasher: hasher.NewLeafHasher(offRampConfig.SourceChainSelector, offRampConfig.ChainSelector, onRamp.Address(), hasher.NewKeccakCtx()), - usdcService: customtokens.NewUSDCService(pluginConfig.USDCAttestationApi), + usdcService: customtokens.NewUSDCService(pluginConfig.USDCAttestationApi, chainId), }) err = wrappedPluginFactory.UpdateLogPollerFilters(zeroAddress) @@ -159,8 +159,13 @@ func NewExecutionServices(lggr logger.Logger, jb job.Job, chainSet evm.LegacyCha return []job.ServiceCtx{job.NewServiceAdapter(oracle)}, nil } -func getExecutionPluginSourceLpChainFilters(onRamp, priceRegistry common.Address) []logpoller.Filter { - return []logpoller.Filter{ +func getExecutionPluginSourceLpChainFilters(onRamp, priceRegistry, usdcToken common.Address) []logpoller.Filter { + var filters []logpoller.Filter + if usdcToken != common.HexToAddress("") { + filters = customtokens.GetUSDCServiceSourceLPFilters(usdcToken) + } + + return append(filters, []logpoller.Filter{ { Name: logpoller.FilterName(EXEC_CCIP_SENDS, onRamp.String()), EventSigs: []common.Hash{abihelpers.EventSignatures.SendRequested}, @@ -176,7 +181,7 @@ func getExecutionPluginSourceLpChainFilters(onRamp, priceRegistry common.Address EventSigs: []common.Hash{abihelpers.EventSignatures.FeeTokenRemoved}, Addresses: []common.Address{priceRegistry}, }, - } + }...) } func getExecutionPluginDestLpChainFilters(commitStore, offRamp, priceRegistry common.Address) []logpoller.Filter { @@ -284,10 +289,15 @@ func unregisterExecutionPluginLpFilters( return err } + chainId, err := chainselectors.ChainIdFromSelector(destOffRampConfig.SourceChainSelector) + if err != nil { + return err + } + if err := unregisterLpFilters( q, sourceLP, - getExecutionPluginSourceLpChainFilters(destOffRampConfig.OnRamp, onRampDynCfg.PriceRegistry), + getExecutionPluginSourceLpChainFilters(destOffRampConfig.OnRamp, onRampDynCfg.PriceRegistry, customtokens.USDCTokenMapping[chainId]), ); err != nil { return err } diff --git a/core/services/ocr2/plugins/ccip/execution_plugin_test.go b/core/services/ocr2/plugins/ccip/execution_plugin_test.go index 85728682b6..9e1f49c640 100644 --- a/core/services/ocr2/plugins/ccip/execution_plugin_test.go +++ b/core/services/ocr2/plugins/ccip/execution_plugin_test.go @@ -116,8 +116,10 @@ func TestGetExecutionPluginFilterNames(t *testing.T) { dstLP, mockOffRamp, evm_2_evm_offramp.EVM2EVMOffRampStaticConfig{ - CommitStore: commitStoreAddr, - OnRamp: onRampAddr, + CommitStore: commitStoreAddr, + OnRamp: onRampAddr, + ChainSelector: 5790810961207155433, + SourceChainSelector: 16015286601757825753, }, mockOnRamp, nil, diff --git a/core/services/ocr2/plugins/ccip/execution_reporting_plugin.go b/core/services/ocr2/plugins/ccip/execution_reporting_plugin.go index fbcd518351..b1be55f36d 100644 --- a/core/services/ocr2/plugins/ccip/execution_reporting_plugin.go +++ b/core/services/ocr2/plugins/ccip/execution_reporting_plugin.go @@ -202,7 +202,11 @@ func (rf *ExecutionReportingPluginFactory) UpdateLogPollerFilters(destPriceRegis defer rf.filtersMu.Unlock() // source chain filters - sourceFiltersBefore, sourceFiltersNow := rf.sourceChainFilters, getExecutionPluginSourceLpChainFilters(rf.config.onRamp.Address(), rf.config.sourcePriceRegistry.Address()) + sourceFiltersBefore, sourceFiltersNow := rf.sourceChainFilters, getExecutionPluginSourceLpChainFilters( + rf.config.onRamp.Address(), + rf.config.sourcePriceRegistry.Address(), + rf.config.usdcService.SourceUSDCToken, + ) created, deleted := filtersDiff(sourceFiltersBefore, sourceFiltersNow) if err := unregisterLpFilters(nilQueryer, rf.config.sourceLP, deleted); err != nil { return err diff --git a/core/services/ocr2/plugins/ccip/execution_reporting_plugin_test.go b/core/services/ocr2/plugins/ccip/execution_reporting_plugin_test.go index 2c2d8c7a3d..ab8a6f2aaf 100644 --- a/core/services/ocr2/plugins/ccip/execution_reporting_plugin_test.go +++ b/core/services/ocr2/plugins/ccip/execution_reporting_plugin_test.go @@ -30,6 +30,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/cache" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/ccipevents" ccipconfig "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/customtokens" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers" plugintesthelpers "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers/plugins" "github.com/smartcontractkit/chainlink/v2/core/utils" @@ -1142,6 +1143,9 @@ func TestExecutionReportingPluginFactory_UpdateLogPollerFilters(t *testing.T) { offRamp := mock_contracts.NewEVM2EVMOffRampInterface(t) offRamp.On("Address").Return(utils.RandomAddress(), nil) + sourceChainId := uint64(420) + sourceUSDCTokenAddress := customtokens.USDCTokenMapping[sourceChainId] + destPriceRegistryAddr := utils.RandomAddress() rf := &ExecutionReportingPluginFactory{ @@ -1155,10 +1159,11 @@ func TestExecutionReportingPluginFactory_UpdateLogPollerFilters(t *testing.T) { commitStore: commitStore, offRamp: offRamp, sourcePriceRegistry: sourcePriceRegistry, + usdcService: customtokens.NewUSDCService("", sourceChainId), }, } - for _, f := range getExecutionPluginSourceLpChainFilters(onRamp.Address(), sourcePriceRegistry.Address()) { + for _, f := range getExecutionPluginSourceLpChainFilters(onRamp.Address(), sourcePriceRegistry.Address(), sourceUSDCTokenAddress) { sourceLP.On("RegisterFilter", f).Return(nil) } for _, f := range getExecutionPluginDestLpChainFilters(commitStore.Address(), offRamp.Address(), destPriceRegistryAddr) {