diff --git a/.changeset/weak-months-turn.md b/.changeset/weak-months-turn.md new file mode 100644 index 0000000000..9d10b9d9c9 --- /dev/null +++ b/.changeset/weak-months-turn.md @@ -0,0 +1,5 @@ +--- +"ccip": minor +--- + +Filter out destination chain bridgeable tokens that are not configured on pricegetter diff --git a/core/scripts/go.mod b/core/scripts/go.mod index 19cb0d97f3..5174fb7f74 100644 --- a/core/scripts/go.mod +++ b/core/scripts/go.mod @@ -23,7 +23,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chain-selectors v1.0.13 github.com/smartcontractkit/chainlink-automation v1.0.2 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/v2 v2.0.0-00010101000000-000000000000 github.com/smartcontractkit/libocr v0.0.0-20240326191951-2bbe9382d052 diff --git a/core/scripts/go.sum b/core/scripts/go.sum index 1a61a021ab..139878c796 100644 --- a/core/scripts/go.sum +++ b/core/scripts/go.sum @@ -1182,8 +1182,8 @@ github.com/smartcontractkit/chain-selectors v1.0.13 h1:vHMbh7Wu+W+/DSD88feiwMMSX github.com/smartcontractkit/chain-selectors v1.0.13/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.2 h1:xsfyuswL15q2YBGQT3qn2SBz6fnSKiSW7XZ8IZQLpnI= github.com/smartcontractkit/chainlink-automation v1.0.2/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 h1:fY2wMtlr/VQxPyVVQdi1jFvQHi0VbDnGGVXzLKOZTOY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 h1:W8XC1b0GDM0OSzvHvUEFTaZUtognWVkEjCSW2nKQ1mc= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8 h1:I326nw5GwHQHsLKHwtu5Sb9EBLylC8CfUd7BFAS0jtg= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8/go.mod h1:a65NtrK4xZb01mf0dDNghPkN2wXgcqFQ55ADthVBgMc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2.go b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2.go index ed24469860..8c02667c81 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2.go @@ -184,7 +184,9 @@ func (r *CommitReportingPlugin) observePriceUpdates( return nil, nil, nil } - sortedChainTokens, err := ccipcommon.GetSortedChainTokens(ctx, r.offRampReaders, r.destPriceRegistryReader) + sortedChainTokens, filteredChainTokens, err := ccipcommon.GetFilteredSortedChainTokens(ctx, r.offRampReaders, r.destPriceRegistryReader, r.priceGetter) + lggr.Debugw("Filtered bridgeable tokens with no configured price getter", filteredChainTokens) + if err != nil { return nil, nil, fmt.Errorf("get destination tokens: %w", err) } @@ -324,7 +326,7 @@ func (r *CommitReportingPlugin) Report(ctx context.Context, epochAndRound types. parsableObservations := ccip.GetParsableObservations[ccip.CommitObservation](lggr, observations) - sortedChainTokens, err := ccipcommon.GetSortedChainTokens(ctx, r.offRampReaders, r.destPriceRegistryReader) + sortedChainTokens, _, err := ccipcommon.GetFilteredSortedChainTokens(ctx, r.offRampReaders, r.destPriceRegistryReader, r.priceGetter) if err != nil { return false, nil, fmt.Errorf("get destination tokens: %w", err) } diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go index 29041221b0..afeb821764 100644 --- a/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go +++ b/core/services/ocr2/plugins/ccip/ccipcommit/ocr2_test.go @@ -211,6 +211,10 @@ func TestCommitReportingPlugin_Observation(t *testing.T) { if !tc.priceReportingDisabled && len(tc.tokenPrices) > 0 { queryTokens := ccipcommon.FlattenUniqueSlice([]cciptypes.Address{sourceNativeTokenAddr}, destTokens) priceGet.On("TokenPricesUSD", mock.Anything, queryTokens).Return(tc.tokenPrices, nil) + priceGet.On("FilterConfiguredTokens", mock.Anything, destTokens).Return([]cciptypes.Address{ + bridgedTokens[0], + bridgedTokens[1], + }, []cciptypes.Address{}, nil) } gasPriceEstimator := prices.NewMockGasPriceEstimatorCommit(t) @@ -303,6 +307,9 @@ func TestCommitReportingPlugin_Report(t *testing.T) { chainHealthcheck := ccipcachemocks.NewChainHealthcheck(t) chainHealthcheck.On("IsHealthy", ctx).Return(true, nil).Maybe() p.chainHealthcheck = chainHealthcheck + pricegetter := pricegetter.NewMockPriceGetter(t) + pricegetter.On("FilterConfiguredTokens", mock.Anything, mock.Anything).Return([]cciptypes.Address{}, []cciptypes.Address{}, nil) + p.priceGetter = pricegetter o := ccip.CommitObservation{Interval: cciptypes.CommitStoreInterval{Min: 1, Max: 1}, SourceGasPriceUSD: big.NewInt(0)} obs, err := o.Marshal() @@ -510,7 +517,7 @@ func TestCommitReportingPlugin_Report(t *testing.T) { gasPriceEstimator.On("Deviates", mock.Anything, mock.Anything, mock.Anything).Return(false, nil) } - var destTokens []cciptypes.Address + destTokens := []cciptypes.Address{} for tk := range tc.tokenDecimals { destTokens = append(destTokens, tk) } @@ -559,6 +566,9 @@ func TestCommitReportingPlugin_Report(t *testing.T) { healthCheck := ccipcachemocks.NewChainHealthcheck(t) healthCheck.On("IsHealthy", ctx).Return(true, nil) + pricegetter := pricegetter.NewMockPriceGetter(t) + pricegetter.On("FilterConfiguredTokens", mock.Anything, destTokens).Return(destTokens, []cciptypes.Address{}, nil) + p := &CommitReportingPlugin{} p.lggr = logger.TestLogger(t) p.destPriceRegistryReader = destPriceRegistryReader @@ -569,6 +579,7 @@ func TestCommitReportingPlugin_Report(t *testing.T) { p.offchainConfig.GasPriceHeartBeat = gasPriceHeartBeat.Duration() p.commitStoreReader = commitStoreReader p.F = tc.f + p.priceGetter = pricegetter p.metricsCollector = ccip.NoopMetricsCollector p.offchainConfig.PriceReportingDisabled = tc.priceReportingDisabled p.chainHealthcheck = healthCheck diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go index d46c6750b6..f7de469de2 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go @@ -33,22 +33,56 @@ type BackfillArgs struct { SourceStartBlock, DestStartBlock uint64 } +// GetChainTokens returns union of all tokens supported on the destination chain, including fee tokens from the provided price registry +// and the bridgeable tokens from all the offRamps living on the chain. func GetSortedChainTokens(ctx context.Context, offRamps []ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader) (chainTokens []cciptypes.Address, err error) { - return getSortedChainTokensWithBatchLimit(ctx, offRamps, priceRegistry, offRampBatchSizeLimit) + destFeeTokens, destBridgeableTokens, err := getTokensWithBatchLimit(ctx, offRamps, priceRegistry, offRampBatchSizeLimit) + if err != nil { + return nil, fmt.Errorf("get tokens with batch limit: %w", err) + } + // fee token can overlap with bridgeable tokens + // we need to dedup them to arrive at chain token set + return flattenedAndSortedChainTokens(destFeeTokens, destBridgeableTokens), nil } -// GetChainTokens returns union of all tokens supported on the destination chain, including fee tokens from the provided price registry -// and the bridgeable tokens from all the offRamps living on the chain. -func getSortedChainTokensWithBatchLimit(ctx context.Context, offRamps []ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader, batchSize int) (chainTokens []cciptypes.Address, err error) { +// GetFilteredSortedChainTokens returns union of all tokens supported on the destination chain, including fee tokens from the provided price registry +// and the bridgeable tokens from all the offRamps living on the chain. Bridgeable tokens are only included if they are configured on the pricegetter +// Fee tokens are not filtered as they must always be priced +func GetFilteredSortedChainTokens(ctx context.Context, offRamps []ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader, priceGetter cciptypes.PriceGetter) (chainTokens []cciptypes.Address, excludedTokens []cciptypes.Address, err error) { + destFeeTokens, destBridgeableTokens, err := getTokensWithBatchLimit(ctx, offRamps, priceRegistry, offRampBatchSizeLimit) + if err != nil { + return nil, nil, fmt.Errorf("get tokens with batch limit: %w", err) + } + + destTokensWithPrice, destTokensWithoutPrice, err := priceGetter.FilterConfiguredTokens(ctx, destBridgeableTokens) + if err != nil { + return nil, nil, fmt.Errorf("filter for priced tokens: %w", err) + } + + return flattenedAndSortedChainTokens(destFeeTokens, destTokensWithPrice), destTokensWithoutPrice, nil +} + +func flattenedAndSortedChainTokens(slices ...[]cciptypes.Address) (chainTokens []cciptypes.Address) { + // same token can be returned by multiple offRamps, and fee token can overlap with bridgeable tokens, + // we need to dedup them to arrive at chain token set + chainTokens = FlattenUniqueSlice(slices...) + + // return the tokens in deterministic order to aid with testing and debugging + sort.Slice(chainTokens, func(i, j int) bool { + return chainTokens[i] < chainTokens[j] + }) + + return chainTokens +} + +func getTokensWithBatchLimit(ctx context.Context, offRamps []ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader, batchSize int) (destFeeTokens []cciptypes.Address, destBridgeableTokens []cciptypes.Address, err error) { if batchSize == 0 { - return nil, fmt.Errorf("batch size must be greater than 0") + return nil, nil, fmt.Errorf("batch size must be greater than 0") } eg := new(errgroup.Group) eg.SetLimit(batchSize) - var destFeeTokens []cciptypes.Address - var destBridgeableTokens []cciptypes.Address mu := &sync.RWMutex{} eg.Go(func() error { @@ -75,19 +109,11 @@ func getSortedChainTokensWithBatchLimit(ctx context.Context, offRamps []ccipdata } if err := eg.Wait(); err != nil { - return nil, err + return nil, nil, err } - // same token can be returned by multiple offRamps, and fee token can overlap with bridgeable tokens, - // we need to dedup them to arrive at chain token set - chainTokens = FlattenUniqueSlice(destFeeTokens, destBridgeableTokens) - - // return the tokens in deterministic order to aid with testing and debugging - sort.Slice(chainTokens, func(i, j int) bool { - return chainTokens[i] < chainTokens[j] - }) - - return chainTokens, nil + // same token can be returned by multiple offRamps + return destFeeTokens, flattenedAndSortedChainTokens(destBridgeableTokens), nil } // GetDestinationTokens returns the destination chain fee tokens from the provided price registry diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go index 529a49307f..bd774a20ce 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" @@ -17,6 +18,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata" ccipdatamocks "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata/mocks" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter" ) func TestGetMessageIDsAsHexString(t *testing.T) { @@ -141,23 +143,120 @@ func TestGetChainTokens(t *testing.T) { } } +func TestGetFilteredChainTokens(t *testing.T) { + const numTokens = 6 + var tokens []cciptypes.Address + for i := 0; i < numTokens; i++ { + tokens = append(tokens, ccipcalc.EvmAddrToGeneric(utils.RandomAddress())) + } + + testCases := []struct { + name string + feeTokens []cciptypes.Address + destTokens [][]cciptypes.Address + expectedChainTokens []cciptypes.Address + expectedFilteredTokens []cciptypes.Address + }{ + { + name: "empty", + feeTokens: []cciptypes.Address{}, + destTokens: [][]cciptypes.Address{{}}, + expectedChainTokens: []cciptypes.Address{}, + expectedFilteredTokens: []cciptypes.Address{}, + }, + { + name: "single offRamp", + feeTokens: []cciptypes.Address{tokens[0]}, + destTokens: [][]cciptypes.Address{ + {tokens[1], tokens[2], tokens[3]}, + }, + expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3]}, + expectedFilteredTokens: []cciptypes.Address{tokens[4], tokens[5]}, + }, + { + name: "multiple offRamps with distinct tokens", + feeTokens: []cciptypes.Address{tokens[0]}, + destTokens: [][]cciptypes.Address{ + {tokens[1], tokens[2]}, + {tokens[3], tokens[4]}, + {tokens[5]}, + }, + expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]}, + expectedFilteredTokens: []cciptypes.Address{}, + }, + { + name: "overlapping tokens", + feeTokens: []cciptypes.Address{tokens[0]}, + destTokens: [][]cciptypes.Address{ + {tokens[0], tokens[1], tokens[2], tokens[3]}, + {tokens[0], tokens[2], tokens[3], tokens[4], tokens[5]}, + {tokens[5]}, + }, + expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]}, + expectedFilteredTokens: []cciptypes.Address{}, + }, + { + name: "unconfigured tokens", + feeTokens: []cciptypes.Address{tokens[0]}, + destTokens: [][]cciptypes.Address{ + {tokens[0], tokens[1], tokens[2], tokens[3]}, + {tokens[0], tokens[2], tokens[3], tokens[4], tokens[5]}, + {tokens[5]}, + }, + expectedChainTokens: []cciptypes.Address{tokens[0], tokens[1], tokens[2], tokens[3], tokens[4]}, + expectedFilteredTokens: []cciptypes.Address{tokens[5]}, + }, + } + + ctx := testutils.Context(t) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + priceRegistry := ccipdatamocks.NewPriceRegistryReader(t) + priceRegistry.On("GetFeeTokens", ctx).Return(tc.feeTokens, nil).Once() + + priceGet := pricegetter.NewMockPriceGetter(t) + priceGet.On("FilterConfiguredTokens", mock.Anything, mock.Anything).Return(tc.expectedChainTokens, tc.expectedFilteredTokens, nil) + + var offRamps []ccipdata.OffRampReader + for _, destTokens := range tc.destTokens { + offRamp := ccipdatamocks.NewOffRampReader(t) + offRamp.On("GetTokens", ctx).Return(cciptypes.OffRampTokens{DestinationTokens: destTokens}, nil).Once() + offRamps = append(offRamps, offRamp) + } + + chainTokens, filteredTokens, err := GetFilteredSortedChainTokens(ctx, offRamps, priceRegistry, priceGet) + assert.NoError(t, err) + + sort.Slice(tc.expectedChainTokens, func(i, j int) bool { + return tc.expectedChainTokens[i] < tc.expectedChainTokens[j] + }) + assert.Equal(t, tc.expectedChainTokens, chainTokens) + assert.Equal(t, tc.expectedFilteredTokens, filteredTokens) + }) + } +} + func TestGetChainTokensWithBatchLimit(t *testing.T) { numTokens := 100 + numFeeTokens := 10 var tokens []cciptypes.Address for i := 0; i < numTokens; i++ { tokens = append(tokens, ccipcalc.EvmAddrToGeneric(utils.RandomAddress())) } - expectedTokens := make([]cciptypes.Address, numTokens) - copy(expectedTokens, tokens) - sort.Slice(expectedTokens, func(i, j int) bool { - return expectedTokens[i] < expectedTokens[j] + expectedFeeTokens := make([]cciptypes.Address, numFeeTokens) + copy(expectedFeeTokens, tokens[0:numFeeTokens]) + expectedBridgeableTokens := make([]cciptypes.Address, numTokens) + copy(expectedBridgeableTokens, tokens) + sort.Slice(expectedBridgeableTokens, func(i, j int) bool { + return expectedBridgeableTokens[i] < expectedBridgeableTokens[j] }) testCases := []struct { name string batchSize int - numOffRamps uint + numOffRamps int expectError bool }{ { @@ -203,23 +302,24 @@ func TestGetChainTokensWithBatchLimit(t *testing.T) { t.Run(tc.name, func(t *testing.T) { priceRegistry := ccipdatamocks.NewPriceRegistryReader(t) - priceRegistry.On("GetFeeTokens", ctx).Return(tokens[0:10], nil).Maybe() + priceRegistry.On("GetFeeTokens", ctx).Return(expectedFeeTokens, nil).Maybe() var offRamps []ccipdata.OffRampReader - for i := 0; i < int(tc.numOffRamps); i++ { + for i := 0; i < tc.numOffRamps; i++ { offRamp := ccipdatamocks.NewOffRampReader(t) offRamp.On("GetTokens", ctx).Return(cciptypes.OffRampTokens{DestinationTokens: tokens[i%numTokens:]}, nil).Maybe() offRamps = append(offRamps, offRamp) } - chainTokens, err := getSortedChainTokensWithBatchLimit(ctx, offRamps, priceRegistry, tc.batchSize) + destFeeTokens, destBridgeableTokens, err := getTokensWithBatchLimit(ctx, offRamps, priceRegistry, tc.batchSize) if tc.expectError { assert.Error(t, err) return } assert.NoError(t, err) - assert.Equal(t, expectedTokens, chainTokens) + assert.Equal(t, expectedFeeTokens, destFeeTokens) + assert.Equal(t, expectedBridgeableTokens, destBridgeableTokens) }) } } diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go index 54752ce03d..99808601d8 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go @@ -74,6 +74,28 @@ func NewDynamicPriceGetter(cfg config.DynamicPriceGetterConfig, evmClients map[u return &priceGetter, nil } +// FilterForConfiguredTokens implements the PriceGetter interface. +// It filters a list of token addresses for only those that have a price resolution rule configured on the PriceGetterConfig +func (d *DynamicPriceGetter) FilterConfiguredTokens(ctx context.Context, tokens []cciptypes.Address) (configured []cciptypes.Address, unconfigured []cciptypes.Address, err error) { + configured = []cciptypes.Address{} + unconfigured = []cciptypes.Address{} + for _, tk := range tokens { + evmAddr, err := ccipcalc.GenericAddrToEvm(tk) + if err != nil { + return nil, nil, err + } + + if _, isAgg := d.cfg.AggregatorPrices[evmAddr]; isAgg { + configured = append(configured, tk) + } else if _, isStatic := d.cfg.StaticPrices[evmAddr]; isStatic { + configured = append(configured, tk) + } else { + unconfigured = append(unconfigured, tk) + } + } + return configured, unconfigured, nil +} + // TokenPricesUSD implements the PriceGetter interface. // It returns static prices stored in the price getter, and batch calls to aggregators (on per chain) for aggregator-based prices. func (d *DynamicPriceGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) { diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go index dedbe25613..9f8efff494 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go @@ -62,10 +62,17 @@ func TestDynamicPriceGetter(t *testing.T) { } require.NoError(t, err) ctx := testutils.Context(t) + // Check configured token + unconfiguredTk := cciptypes.Address(utils.RandomAddress().String()) + cfgTokens, uncfgTokens, err := pg.FilterConfiguredTokens(ctx, []cciptypes.Address{unconfiguredTk}) + require.NoError(t, err) + assert.Equal(t, []cciptypes.Address{}, cfgTokens) + assert.Equal(t, []cciptypes.Address{unconfiguredTk}, uncfgTokens) // Build list of tokens to query. tokens := make([]cciptypes.Address, 0, len(test.param.expectedTokenPrices)) for tk := range test.param.expectedTokenPrices { - tokens = append(tokens, cciptypes.Address(tk.String())) + tokenAddr := cciptypes.Address(tk.String()) + tokens = append(tokens, tokenAddr) } prices, err := pg.TokenPricesUSD(ctx, tokens) if test.param.priceResolutionErrorExpected { diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/mock.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/mock.go index 69f30da3f8..ba93b1dc78 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/mock.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/mock.go @@ -34,6 +34,45 @@ func (_m *MockPriceGetter) Close() error { return r0 } +// FilterConfiguredTokens provides a mock function with given fields: ctx, tokens +func (_m *MockPriceGetter) FilterConfiguredTokens(ctx context.Context, tokens []ccip.Address) ([]ccip.Address, []ccip.Address, error) { + ret := _m.Called(ctx, tokens) + + if len(ret) == 0 { + panic("no return value specified for FilterConfiguredTokens") + } + + var r0 []ccip.Address + var r1 []ccip.Address + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) ([]ccip.Address, []ccip.Address, error)); ok { + return rf(ctx, tokens) + } + if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) []ccip.Address); ok { + r0 = rf(ctx, tokens) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]ccip.Address) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []ccip.Address) []ccip.Address); ok { + r1 = rf(ctx, tokens) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).([]ccip.Address) + } + } + + if rf, ok := ret.Get(2).(func(context.Context, []ccip.Address) error); ok { + r2 = rf(ctx, tokens) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // TokenPricesUSD provides a mock function with given fields: ctx, tokens func (_m *MockPriceGetter) TokenPricesUSD(ctx context.Context, tokens []ccip.Address) (map[ccip.Address]*big.Int, error) { ret := _m.Called(ctx, tokens) diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go index 28eeaaff37..f49e8a1f74 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go @@ -3,6 +3,7 @@ package pricegetter import ( "context" "math/big" + "strings" "time" mapset "github.com/deckarep/golang-set/v2" @@ -44,6 +45,21 @@ func NewPipelineGetter(source string, runner pipeline.Runner, jobID int32, exter }, nil } +// FilterForConfiguredTokens implements the PriceGetter interface. +// It filters a list of token addresses for only those that have a pipeline job configured on the TokenPricesUSDPipeline +func (d *PipelineGetter) FilterConfiguredTokens(ctx context.Context, tokens []cciptypes.Address) (configured []cciptypes.Address, unconfigured []cciptypes.Address, err error) { + lcSource := strings.ToLower(d.source) + for _, tk := range tokens { + lcToken := strings.ToLower(string(tk)) + if strings.Contains(lcSource, lcToken) { + configured = append(configured, tk) + } else { + unconfigured = append(unconfigured, tk) + } + } + return configured, unconfigured, nil +} + func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) { _, trrs, err := d.runner.ExecuteRun(ctx, pipeline.Spec{ ID: d.jobID, diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go index cfa2bf1c13..305d8e51bd 100644 --- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go +++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go @@ -56,6 +56,13 @@ func TestDataSource(t *testing.T) { `, linkEth.URL, usdcEth.URL, linkTokenAddress, usdcTokenAddress) priceGetter := newTestPipelineGetter(t, source) + + // USDC & LINK are configured + confTokens, _, err := priceGetter.FilterConfiguredTokens(context.Background(), []cciptypes.Address{linkTokenAddress, usdcTokenAddress}) + require.NoError(t, err) + assert.Equal(t, linkTokenAddress, confTokens[0]) + assert.Equal(t, usdcTokenAddress, confTokens[1]) + // Ask for all prices present in spec. prices, err := priceGetter.TokenPricesUSD(context.Background(), []cciptypes.Address{ linkTokenAddress, diff --git a/go.mod b/go.mod index 2fe0933dd3..f90de1c717 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/shopspring/decimal v1.3.1 github.com/smartcontractkit/chain-selectors v1.0.13 github.com/smartcontractkit/chainlink-automation v1.0.2 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8 github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 github.com/smartcontractkit/chainlink-feeds v0.0.0-20240119021347-3c541a78cdb8 diff --git a/go.sum b/go.sum index ddf342e43f..f69eb64d33 100644 --- a/go.sum +++ b/go.sum @@ -1176,8 +1176,8 @@ github.com/smartcontractkit/chain-selectors v1.0.13 h1:vHMbh7Wu+W+/DSD88feiwMMSX github.com/smartcontractkit/chain-selectors v1.0.13/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.2 h1:xsfyuswL15q2YBGQT3qn2SBz6fnSKiSW7XZ8IZQLpnI= github.com/smartcontractkit/chainlink-automation v1.0.2/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 h1:fY2wMtlr/VQxPyVVQdi1jFvQHi0VbDnGGVXzLKOZTOY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 h1:W8XC1b0GDM0OSzvHvUEFTaZUtognWVkEjCSW2nKQ1mc= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8 h1:I326nw5GwHQHsLKHwtu5Sb9EBLylC8CfUd7BFAS0jtg= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8/go.mod h1:a65NtrK4xZb01mf0dDNghPkN2wXgcqFQ55ADthVBgMc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 60a6729658..5d8978b4ce 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -28,7 +28,7 @@ require ( github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chain-selectors v1.0.13 github.com/smartcontractkit/chainlink-automation v1.0.2 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 github.com/smartcontractkit/chainlink-testing-framework v1.28.1 github.com/smartcontractkit/chainlink-vrf v0.0.0-20231120191722-fef03814f868 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-00010101000000-000000000000 diff --git a/integration-tests/go.sum b/integration-tests/go.sum index 44543c9a4d..cbd62ea66c 100644 --- a/integration-tests/go.sum +++ b/integration-tests/go.sum @@ -1521,8 +1521,8 @@ github.com/smartcontractkit/chain-selectors v1.0.13 h1:vHMbh7Wu+W+/DSD88feiwMMSX github.com/smartcontractkit/chain-selectors v1.0.13/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.2 h1:xsfyuswL15q2YBGQT3qn2SBz6fnSKiSW7XZ8IZQLpnI= github.com/smartcontractkit/chainlink-automation v1.0.2/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 h1:fY2wMtlr/VQxPyVVQdi1jFvQHi0VbDnGGVXzLKOZTOY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 h1:W8XC1b0GDM0OSzvHvUEFTaZUtognWVkEjCSW2nKQ1mc= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8 h1:I326nw5GwHQHsLKHwtu5Sb9EBLylC8CfUd7BFAS0jtg= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8/go.mod h1:a65NtrK4xZb01mf0dDNghPkN2wXgcqFQ55ADthVBgMc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo= diff --git a/integration-tests/load/go.mod b/integration-tests/load/go.mod index 6e810f0e53..a8af4de225 100644 --- a/integration-tests/load/go.mod +++ b/integration-tests/load/go.mod @@ -16,7 +16,7 @@ require ( github.com/rs/zerolog v1.30.0 github.com/slack-go/slack v0.12.2 github.com/smartcontractkit/chainlink-automation v1.0.2 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 + github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 github.com/smartcontractkit/chainlink-testing-framework v1.28.1 github.com/smartcontractkit/chainlink/integration-tests v0.0.0-20240214231432-4ad5eb95178c github.com/smartcontractkit/chainlink/v2 v2.9.0-beta0.0.20240216210048-da02459ddad8 diff --git a/integration-tests/load/go.sum b/integration-tests/load/go.sum index cdd9d8450c..834c686a59 100644 --- a/integration-tests/load/go.sum +++ b/integration-tests/load/go.sum @@ -1504,8 +1504,8 @@ github.com/smartcontractkit/chain-selectors v1.0.13 h1:vHMbh7Wu+W+/DSD88feiwMMSX github.com/smartcontractkit/chain-selectors v1.0.13/go.mod h1:d4Hi+E1zqjy9HqMkjBE5q1vcG9VGgxf5VxiRHfzi2kE= github.com/smartcontractkit/chainlink-automation v1.0.2 h1:xsfyuswL15q2YBGQT3qn2SBz6fnSKiSW7XZ8IZQLpnI= github.com/smartcontractkit/chainlink-automation v1.0.2/go.mod h1:RjboV0Qd7YP+To+OrzHGXaxUxoSONveCoAK2TQ1INLU= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25 h1:fY2wMtlr/VQxPyVVQdi1jFvQHi0VbDnGGVXzLKOZTOY= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240404141006-77085a02ce25/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3 h1:W8XC1b0GDM0OSzvHvUEFTaZUtognWVkEjCSW2nKQ1mc= +github.com/smartcontractkit/chainlink-common v0.1.7-0.20240410191726-b8a7349cd5d3/go.mod h1:kstYjAGqBswdZpl7YkSPeXBDVwaY1VaR6tUMPWl8ykA= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8 h1:I326nw5GwHQHsLKHwtu5Sb9EBLylC8CfUd7BFAS0jtg= github.com/smartcontractkit/chainlink-cosmos v0.4.1-0.20240213120401-01a23955f9f8/go.mod h1:a65NtrK4xZb01mf0dDNghPkN2wXgcqFQ55ADthVBgMc= github.com/smartcontractkit/chainlink-data-streams v0.0.0-20240220203239-09be0ea34540 h1:xFSv8561jsLtF6gYZr/zW2z5qUUAkcFkApin2mnbYTo=