From b563d77dd30ad96253ae6586c06fd34a66d61936 Mon Sep 17 00:00:00 2001
From: Mateusz Sekara <mateusz.sekara@smartcontract.com>
Date: Thu, 22 Aug 2024 08:59:21 +0200
Subject: [PATCH] Report all prices from Jobspec (#14185)

* Report all prices from Jobspec (#1275)

Fetch all the token prices in the jobspec for dest tokens

* Missing changeset

---------

Co-authored-by: nogo <110664798+0xnogo@users.noreply.github.com>
---
 .changeset/lucky-boats-run.md                 |   5 +
 .mockery.yaml                                 |   5 +
 .../plugins/ccip/ccipcommit/initializers.go   |   2 +-
 .../plugins/ccip/clo_ccip_integration_test.go |  26 +-
 .../plugins/ccip/integration_legacy_test.go   |  11 -
 .../ocr2/plugins/ccip/integration_test.go     |  11 -
 .../ccip/internal/ccipcommon/shortcuts.go     |  19 +-
 .../internal/ccipcommon/shortcuts_test.go     |  96 ++--
 .../ccip/internal/ccipdb/price_service.go     |  75 ++-
 .../internal/ccipdb/price_service_test.go     | 170 +++++--
 .../pricegetter/all_price_getter_mock.go      | 269 +++++++++++
 .../plugins/ccip/internal/pricegetter/evm.go  |  17 +
 .../ccip/internal/pricegetter/evm_test.go     | 440 +++++++++++++-----
 .../ccip/internal/pricegetter/pipeline.go     |  68 ++-
 .../internal/pricegetter/pipeline_test.go     |  18 +-
 .../ccip/internal/pricegetter/pricegetter.go  |  13 +-
 .../ccip/testhelpers/integration/chainlink.go |  43 ++
 17 files changed, 973 insertions(+), 315 deletions(-)
 create mode 100644 .changeset/lucky-boats-run.md
 create mode 100644 core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go

diff --git a/.changeset/lucky-boats-run.md b/.changeset/lucky-boats-run.md
new file mode 100644
index 00000000000..c5864a6e370
--- /dev/null
+++ b/.changeset/lucky-boats-run.md
@@ -0,0 +1,5 @@
+---
+"chainlink": patch
+---
+
+Reporting all the token prices from the job spec for CCIP #updated
diff --git a/.mockery.yaml b/.mockery.yaml
index 87c27cd46a9..5cba66f3dad 100644
--- a/.mockery.yaml
+++ b/.mockery.yaml
@@ -529,6 +529,11 @@ packages:
       PriceGetter:
         config:
           mockname: "Mock{{ .InterfaceName }}"
+          filename: mock.go
+      AllTokensPriceGetter:
+        config:
+          mockname: "Mock{{ .InterfaceName }}"
+          filename: all_price_getter_mock.go
   github.com/smartcontractkit/chainlink/v2/core/services/relay/evm/statuschecker:
     interfaces:
       CCIPTransactionStatusChecker:
diff --git a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go
index e964896ab93..5fb9733cc64 100644
--- a/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go
+++ b/core/services/ocr2/plugins/ccip/ccipcommit/initializers.go
@@ -69,7 +69,7 @@ func NewCommitServices(ctx context.Context, ds sqlutil.DataSource, srcProvider c
 	commitStoreReader = ccip.NewProviderProxyCommitStoreReader(srcCommitStore, dstCommitStore)
 	commitLggr := lggr.Named("CCIPCommit").With("sourceChain", sourceChainID, "destChain", destChainID)
 
-	var priceGetter pricegetter.PriceGetter
+	var priceGetter pricegetter.AllTokensPriceGetter
 	withPipeline := strings.Trim(pluginConfig.TokenPricesUSDPipeline, "\n\t ") != ""
 	if withPipeline {
 		priceGetter, err = pricegetter.NewPipelineGetter(pluginConfig.TokenPricesUSDPipeline, pr, jb.ID, jb.ExternalJobID, jb.Name.ValueOrZero(), lggr)
diff --git a/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go b/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go
index 142ba006be6..2fddd58ac8f 100644
--- a/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go
+++ b/core/services/ocr2/plugins/ccip/clo_ccip_integration_test.go
@@ -1,6 +1,7 @@
 package ccip_test
 
 import (
+	"context"
 	"encoding/json"
 	"math/big"
 	"testing"
@@ -32,7 +33,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) {
 	ccipTH := integrationtesthelpers.SetupCCIPIntegrationTH(t, testhelpers.SourceChainID, testhelpers.SourceChainSelector, testhelpers.DestChainID, testhelpers.DestChainSelector)
 
 	//Set up the aggregators here to avoid modifying ccipTH.
-	srcLinkAddr := ccipTH.Source.LinkToken.Address()
 	dstLinkAddr := ccipTH.Dest.LinkToken.Address()
 	srcNativeAddr, err := ccipTH.Source.Router.GetWrappedNative(nil)
 	require.NoError(t, err)
@@ -44,13 +44,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) {
 	require.NoError(t, err)
 	ccipTH.Source.Chain.Commit()
 
-	aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18))
-	require.NoError(t, err)
-	ccipTH.Dest.Chain.Commit()
-	_, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000))
-	require.NoError(t, err)
-	ccipTH.Source.Chain.Commit()
-
 	aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18))
 	require.NoError(t, err)
 	ccipTH.Dest.Chain.Commit()
@@ -74,10 +67,6 @@ func Test_CLOSpecApprovalFlow_dynamicPriceGetter(t *testing.T) {
 
 	priceGetterConfig := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			srcLinkAddr: {
-				ChainID:                   ccipTH.Source.ChainID,
-				AggregatorContractAddress: aggSrcLnkAddr,
-			},
 			srcNativeAddr: {
 				ChainID:                   ccipTH.Source.ChainID,
 				AggregatorContractAddress: aggSrcNatAddr,
@@ -125,11 +114,22 @@ func test_CLOSpecApprovalFlow(t *testing.T, ccipTH integrationtesthelpers.CCIPIn
 
 	_, err = ccipTH.Source.LinkToken.Approve(ccipTH.Source.User, ccipTH.Source.Router.Address(), new(big.Int).Set(fee))
 	require.NoError(t, err)
-	ccipTH.Source.Chain.Commit()
+	blockHash := ccipTH.Dest.Chain.Commit()
+	// get the block number
+	block, err := ccipTH.Dest.Chain.BlockByHash(context.Background(), blockHash)
+	require.NoError(t, err)
+	blockNumber := block.Number().Uint64() + 1 // +1 as a block will be mined for the request from EventuallyReportCommitted
 
 	ccipTH.SendRequest(t, msg)
 	ccipTH.AllNodesHaveReqSeqNum(t, currentSeqNum)
 	ccipTH.EventuallyReportCommitted(t, currentSeqNum)
+	ccipTH.EventuallyPriceRegistryUpdated(
+		t,
+		blockNumber,
+		ccipTH.Source.ChainSelector,
+		[]common.Address{ccipTH.Dest.LinkToken.Address(), ccipTH.Dest.WrappedNative.Address()},
+		ccipTH.Source.WrappedNative.Address(),
+	)
 
 	executionLogs := ccipTH.AllNodesHaveExecutedSeqNums(t, currentSeqNum, currentSeqNum)
 	assert.Len(t, executionLogs, 1)
diff --git a/core/services/ocr2/plugins/ccip/integration_legacy_test.go b/core/services/ocr2/plugins/ccip/integration_legacy_test.go
index 9bc94b5fe45..d89c50b4070 100644
--- a/core/services/ocr2/plugins/ccip/integration_legacy_test.go
+++ b/core/services/ocr2/plugins/ccip/integration_legacy_test.go
@@ -63,13 +63,6 @@ func TestIntegration_legacy_CCIP(t *testing.T) {
 				require.NoError(t, err)
 				ccipTH.Source.Chain.Commit()
 
-				aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18))
-				require.NoError(t, err)
-				ccipTH.Dest.Chain.Commit()
-				_, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000))
-				require.NoError(t, err)
-				ccipTH.Source.Chain.Commit()
-
 				aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18))
 				require.NoError(t, err)
 				ccipTH.Dest.Chain.Commit()
@@ -79,10 +72,6 @@ func TestIntegration_legacy_CCIP(t *testing.T) {
 
 				priceGetterConfig := config.DynamicPriceGetterConfig{
 					AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-						ccipTH.Source.LinkToken.Address(): {
-							ChainID:                   ccipTH.Source.ChainID,
-							AggregatorContractAddress: aggSrcLnkAddr,
-						},
 						ccipTH.Source.WrappedNative.Address(): {
 							ChainID:                   ccipTH.Source.ChainID,
 							AggregatorContractAddress: aggSrcNatAddr,
diff --git a/core/services/ocr2/plugins/ccip/integration_test.go b/core/services/ocr2/plugins/ccip/integration_test.go
index 202d2ef2304..bbf785efa8e 100644
--- a/core/services/ocr2/plugins/ccip/integration_test.go
+++ b/core/services/ocr2/plugins/ccip/integration_test.go
@@ -66,13 +66,6 @@ func TestIntegration_CCIP(t *testing.T) {
 				require.NoError(t, err)
 				ccipTH.Source.Chain.Commit()
 
-				aggSrcLnkAddr, _, aggSrcLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Source.User, ccipTH.Source.Chain, 18, big.NewInt(3e18))
-				require.NoError(t, err)
-				ccipTH.Dest.Chain.Commit()
-				_, err = aggSrcLnk.UpdateRoundData(ccipTH.Source.User, big.NewInt(50), big.NewInt(8000000), big.NewInt(1000), big.NewInt(1000))
-				require.NoError(t, err)
-				ccipTH.Source.Chain.Commit()
-
 				aggDstLnkAddr, _, aggDstLnk, err := mock_v3_aggregator_contract.DeployMockV3AggregatorContract(ccipTH.Dest.User, ccipTH.Dest.Chain, 18, big.NewInt(3e18))
 				require.NoError(t, err)
 				ccipTH.Dest.Chain.Commit()
@@ -82,10 +75,6 @@ func TestIntegration_CCIP(t *testing.T) {
 
 				priceGetterConfig := config.DynamicPriceGetterConfig{
 					AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-						ccipTH.Source.LinkToken.Address(): {
-							ChainID:                   ccipTH.Source.ChainID,
-							AggregatorContractAddress: aggSrcLnkAddr,
-						},
 						ccipTH.Source.WrappedNative.Address(): {
 							ChainID:                   ccipTH.Source.ChainID,
 							AggregatorContractAddress: aggSrcNatAddr,
diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go
index 4f5ba6cfaea..8372ae47486 100644
--- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go
+++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts.go
@@ -32,24 +32,7 @@ type BackfillArgs struct {
 	SourceStartBlock, DestStartBlock uint64
 }
 
-// GetFilteredSortedLaneTokens returns union of tokens supported on this lane, including fee tokens from the provided price registry
-// and the bridgeable tokens from offRamp. Bridgeable tokens are only included if they are configured on the pricegetter
-// Fee tokens are not filtered as they must always be priced
-func GetFilteredSortedLaneTokens(ctx context.Context, offRamp ccipdata.OffRampReader, priceRegistry cciptypes.PriceRegistryReader, priceGetter cciptypes.PriceGetter) (laneTokens []cciptypes.Address, excludedTokens []cciptypes.Address, err error) {
-	destFeeTokens, destBridgeableTokens, err := GetDestinationTokens(ctx, offRamp, priceRegistry)
-	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 flattenedAndSortedTokens(destFeeTokens, destTokensWithPrice), destTokensWithoutPrice, nil
-}
-
-func flattenedAndSortedTokens(slices ...[]cciptypes.Address) (tokens []cciptypes.Address) {
+func FlattenedAndSortedTokens(slices ...[]cciptypes.Address) (tokens []cciptypes.Address) {
 	// fee token can overlap with bridgeable tokens, we need to dedup them to arrive at lane token set
 	tokens = FlattenUniqueSlice(slices...)
 
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 73a3b834956..6f1cdb4a6af 100644
--- a/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go
+++ b/core/services/ocr2/plugins/ccip/internal/ccipcommon/shortcuts_test.go
@@ -3,22 +3,14 @@ package ccipcommon
 import (
 	"fmt"
 	"math/rand"
-	"sort"
 	"strconv"
 	"testing"
 	"time"
 
 	"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"
-
-	"github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils"
-	"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
-	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc"
-	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) {
@@ -70,77 +62,47 @@ func TestFlattenUniqueSlice(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()))
-	}
-
+func TestFlattenedAndSortedTokens(t *testing.T) {
 	testCases := []struct {
-		name                   string
-		feeTokens              []cciptypes.Address
-		destTokens             []cciptypes.Address
-		expectedChainTokens    []cciptypes.Address
-		expectedFilteredTokens []cciptypes.Address
+		name           string
+		inputSlices    [][]cciptypes.Address
+		expectedOutput []cciptypes.Address
 	}{
+		{name: "empty", inputSlices: nil, expectedOutput: []cciptypes.Address{}},
+		{name: "empty 2", inputSlices: [][]cciptypes.Address{}, expectedOutput: []cciptypes.Address{}},
 		{
-			name:                   "empty",
-			feeTokens:              []cciptypes.Address{},
-			destTokens:             []cciptypes.Address{},
-			expectedChainTokens:    []cciptypes.Address{},
-			expectedFilteredTokens: []cciptypes.Address{},
+			name:           "single",
+			inputSlices:    [][]cciptypes.Address{{"0x1", "0x2", "0x3"}},
+			expectedOutput: []cciptypes.Address{"0x1", "0x2", "0x3"},
 		},
 		{
-			name:                   "unique tokens",
-			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:           "simple",
+			inputSlices:    [][]cciptypes.Address{{"0x1", "0x2", "0x3"}, {"0x2", "0x3", "0x4"}},
+			expectedOutput: []cciptypes.Address{"0x1", "0x2", "0x3", "0x4"},
 		},
 		{
-			name:                   "all 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[1], tokens[2], tokens[5], 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]},
+			name: "more complex case",
+			inputSlices: [][]cciptypes.Address{
+				{"0x1", "0x3"},
+				{"0x2", "0x4", "0x3"},
+				{"0x5", "0x2", "0x7", "0xa"},
+			},
+			expectedOutput: []cciptypes.Address{
+				"0x1",
+				"0x2",
+				"0x3",
+				"0x4",
+				"0x5",
+				"0x7",
+				"0xa",
+			},
 		},
 	}
 
-	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)
-
-			offRamp := ccipdatamocks.NewOffRampReader(t)
-			offRamp.On("GetTokens", ctx).Return(cciptypes.OffRampTokens{DestinationTokens: tc.destTokens}, nil).Once()
-
-			chainTokens, filteredTokens, err := GetFilteredSortedLaneTokens(ctx, offRamp, 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)
+			res := FlattenedAndSortedTokens(tc.inputSlices...)
+			assert.Equal(t, tc.expectedOutput, res)
 		})
 	}
 }
diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go
index 2118d5832da..ad44555477f 100644
--- a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go
+++ b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"math/big"
+	"slices"
 	"sort"
 	"sync"
 	"time"
@@ -18,6 +19,7 @@ import (
 	"github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets"
 	cciporm "github.com/smartcontractkit/chainlink/v2/core/services/ccip"
 	"github.com/smartcontractkit/chainlink/v2/core/services/job"
+	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcommon"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipdata"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/pricegetter"
@@ -72,7 +74,7 @@ type priceService struct {
 
 	sourceChainSelector     uint64
 	sourceNative            cciptypes.Address
-	priceGetter             pricegetter.PriceGetter
+	priceGetter             pricegetter.AllTokensPriceGetter
 	offRampReader           ccipdata.OffRampReader
 	gasPriceEstimator       prices.GasPriceEstimatorCommit
 	destPriceRegistryReader ccipdata.PriceRegistryReader
@@ -92,7 +94,7 @@ func NewPriceService(
 	sourceChainSelector uint64,
 
 	sourceNative cciptypes.Address,
-	priceGetter pricegetter.PriceGetter,
+	priceGetter pricegetter.AllTokensPriceGetter,
 	offRampReader ccipdata.OffRampReader,
 ) PriceService {
 	ctx, cancel := context.WithCancel(context.Background())
@@ -322,6 +324,7 @@ func (p *priceService) observeGasPriceUpdates(
 
 	// Include wrapped native to identify the source native USD price, notice USD is in 1e18 scale, i.e. $1 = 1e18
 	rawTokenPricesUSD, err := p.priceGetter.TokenPricesUSD(ctx, []cciptypes.Address{p.sourceNative})
+
 	if err != nil {
 		return nil, fmt.Errorf("failed to fetch source native price (%s): %w", p.sourceNative, err)
 	}
@@ -355,6 +358,9 @@ func (p *priceService) observeGasPriceUpdates(
 }
 
 // All prices are USD ($1=1e18) denominated. All prices must be not nil.
+// Jobspec should have the destination tokens (Aggregate Rate Limit, Bps) and 1 source token (source native).
+// Not respecting this will error out as we need to fetch the token decimals for all tokens expect sourceNative.
+// destTokens is only used to check if sourceNative has the same address as one of the dest tokens.
 // Return token prices should contain the exact same tokens as in tokenDecimals.
 func (p *priceService) observeTokenPriceUpdates(
 	ctx context.Context,
@@ -363,35 +369,72 @@ func (p *priceService) observeTokenPriceUpdates(
 	if p.destPriceRegistryReader == nil {
 		return nil, fmt.Errorf("destPriceRegistry is not set yet")
 	}
-
-	sortedLaneTokens, filteredLaneTokens, err := ccipcommon.GetFilteredSortedLaneTokens(ctx, p.offRampReader, p.destPriceRegistryReader, p.priceGetter)
+	rawTokenPricesUSD, err := p.priceGetter.GetJobSpecTokenPricesUSD(ctx)
 	if err != nil {
-		return nil, fmt.Errorf("get destination tokens: %w", err)
+		return nil, fmt.Errorf("failed to fetch token prices: %w", err)
+	}
+
+	// Verify no price returned by price getter is nil
+	for token, price := range rawTokenPricesUSD {
+		if price == nil {
+			return nil, fmt.Errorf("Token price is nil for token %s", token)
+		}
 	}
 
-	lggr.Debugw("Filtered bridgeable tokens with no configured price getter", "filteredLaneTokens", filteredLaneTokens)
+	lggr.Infow("Raw token prices", "rawTokenPrices", rawTokenPricesUSD)
 
-	queryTokens := ccipcommon.FlattenUniqueSlice(sortedLaneTokens)
-	rawTokenPricesUSD, err := p.priceGetter.TokenPricesUSD(ctx, queryTokens)
+	sourceNativeEvmAddr, err := ccipcalc.GenericAddrToEvm(p.sourceNative)
 	if err != nil {
-		return nil, fmt.Errorf("failed to fetch token prices (%v): %w", queryTokens, err)
+		return nil, fmt.Errorf("failed to convert source native to EVM address: %w", err)
 	}
-	lggr.Infow("Raw token prices", "rawTokenPrices", rawTokenPricesUSD)
 
-	// make sure that we got prices for all the tokens of our query
-	for _, token := range queryTokens {
-		if rawTokenPricesUSD[token] == nil {
-			return nil, fmt.Errorf("missing token price: %+v", token)
+	// Filter out source native token only if source native not in dest tokens
+	var finalDestTokens []cciptypes.Address
+	for token := range rawTokenPricesUSD {
+		tokenEvmAddr, err2 := ccipcalc.GenericAddrToEvm(token)
+		if err2 != nil {
+			return nil, fmt.Errorf("failed to convert token to EVM address: %w", err)
+		}
+
+		if tokenEvmAddr != sourceNativeEvmAddr {
+			finalDestTokens = append(finalDestTokens, token)
 		}
 	}
 
-	destTokensDecimals, err := p.destPriceRegistryReader.GetTokensDecimals(ctx, sortedLaneTokens)
+	fee, bridged, err := ccipcommon.GetDestinationTokens(ctx, p.offRampReader, p.destPriceRegistryReader)
+	if err != nil {
+		return nil, fmt.Errorf("get destination tokens: %w", err)
+	}
+	onchainDestTokens := ccipcommon.FlattenedAndSortedTokens(fee, bridged)
+	lggr.Debugw("Destination tokens", "destTokens", onchainDestTokens)
+
+	onchainTokensEvmAddr, err := ccipcalc.GenericAddrsToEvm(onchainDestTokens...)
+	if err != nil {
+		return nil, fmt.Errorf("failed to convert sorted lane tokens to EVM addresses: %w", err)
+	}
+	// Check for case where sourceNative has same address as one of the dest tokens (example: WETH in Base and Optimism)
+	hasSameDestAddress := slices.Contains(onchainTokensEvmAddr, sourceNativeEvmAddr)
+
+	if hasSameDestAddress {
+		finalDestTokens = append(finalDestTokens, p.sourceNative)
+	}
+
+	// Sort tokens to make the order deterministic, easier for testing and debugging
+	sort.Slice(finalDestTokens, func(i, j int) bool {
+		return finalDestTokens[i] < finalDestTokens[j]
+	})
+
+	destTokensDecimals, err := p.destPriceRegistryReader.GetTokensDecimals(ctx, finalDestTokens)
 	if err != nil {
 		return nil, fmt.Errorf("get tokens decimals: %w", err)
 	}
 
+	if len(destTokensDecimals) != len(finalDestTokens) {
+		return nil, fmt.Errorf("mismatched token decimals and tokens")
+	}
+
 	tokenPricesUSD = make(map[cciptypes.Address]*big.Int, len(rawTokenPricesUSD))
-	for i, token := range sortedLaneTokens {
+	for i, token := range finalDestTokens {
 		tokenPricesUSD[token] = calculateUsdPer1e18TokenAmount(rawTokenPricesUSD[token], destTokensDecimals[i])
 	}
 
diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go
index 26721bdf8e4..0468c3addb8 100644
--- a/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go
+++ b/core/services/ocr2/plugins/ccip/internal/ccipdb/price_service_test.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"math/big"
 	"reflect"
+	"slices"
 	"sort"
 	"testing"
 	"time"
@@ -24,7 +25,6 @@ import (
 	cciporm "github.com/smartcontractkit/chainlink/v2/core/services/ccip"
 	ccipmocks "github.com/smartcontractkit/chainlink/v2/core/services/ccip/mocks"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc"
-	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcommon"
 	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"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/prices"
@@ -314,7 +314,7 @@ func TestPriceService_observeGasPriceUpdates(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			priceGetter := pricegetter.NewMockPriceGetter(t)
+			priceGetter := pricegetter.NewMockAllTokensPriceGetter(t)
 			defer priceGetter.AssertExpectations(t)
 
 			gasPriceEstimator := prices.NewMockGasPriceEstimatorCommit(t)
@@ -358,6 +358,7 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 	jobId := int32(1)
 	destChainSelector := uint64(12345)
 	sourceChainSelector := uint64(67890)
+	sourceNativeToken := cciptypes.Address(utils.RandomAddress().String())
 
 	const nTokens = 10
 	tokens := make([]cciptypes.Address, nTokens)
@@ -368,29 +369,50 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 
 	testCases := []struct {
 		name                string
+		destTokens          []cciptypes.Address
 		tokenDecimals       map[cciptypes.Address]uint8
+		sourceNativeToken   cciptypes.Address
 		filterOutTokens     []cciptypes.Address
 		priceGetterRespData map[cciptypes.Address]*big.Int
 		priceGetterRespErr  error
 		expTokenPricesUSD   map[cciptypes.Address]*big.Int
 		expErr              bool
+		expDecimalErr       bool
 	}{
 		{
-			name: "base",
+			name: "base case with src native token not equals to dest token",
+			tokenDecimals: map[cciptypes.Address]uint8{ // only destination tokens
+				tokens[1]: 18,
+				tokens[2]: 12,
+			},
+			sourceNativeToken: sourceNativeToken,
+			priceGetterRespData: map[cciptypes.Address]*big.Int{ // should return all tokens (including source native token)
+				sourceNativeToken: val1e18(100),
+				tokens[1]:         val1e18(200),
+				tokens[2]:         val1e18(300),
+			},
+			priceGetterRespErr: nil,
+			expTokenPricesUSD: map[cciptypes.Address]*big.Int{ // should only return the tokens in destination chain
+				tokens[1]: val1e18(200),
+				tokens[2]: val1e18(300 * 1e6),
+			},
+			expErr: false,
+		},
+		{
+			name: "base case with src native token equals to dest token",
 			tokenDecimals: map[cciptypes.Address]uint8{
-				tokens[0]: 18,
-				tokens[1]: 12,
+				sourceNativeToken: 18,
+				tokens[1]:         12,
 			},
-			filterOutTokens: []cciptypes.Address{tokens[2]},
+			sourceNativeToken: sourceNativeToken,
 			priceGetterRespData: map[cciptypes.Address]*big.Int{
-				tokens[0]: val1e18(100),
-				tokens[1]: val1e18(200),
-				tokens[2]: val1e18(300), // price getter returned a price for this token even though we didn't request it (should be skipped)
+				sourceNativeToken: val1e18(100),
+				tokens[1]:         val1e18(200),
 			},
 			priceGetterRespErr: nil,
 			expTokenPricesUSD: map[cciptypes.Address]*big.Int{
-				tokens[0]: val1e18(100),
-				tokens[1]: val1e18(200 * 1e6),
+				sourceNativeToken: val1e18(100),
+				tokens[1]:         val1e18(200 * 1e6),
 			},
 			expErr: false,
 		},
@@ -400,29 +422,73 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 				tokens[0]: 18,
 				tokens[1]: 18,
 			},
+			sourceNativeToken:   tokens[0],
 			priceGetterRespData: nil,
 			priceGetterRespErr:  fmt.Errorf("some random network error"),
 			expErr:              true,
 		},
+		{
+			name:       "price getter returns more prices than destTokens",
+			destTokens: []cciptypes.Address{tokens[1]},
+			tokenDecimals: map[cciptypes.Address]uint8{
+				tokens[1]: 18,
+				tokens[2]: 12,
+				tokens[3]: 18,
+			},
+			sourceNativeToken: sourceNativeToken,
+			priceGetterRespData: map[cciptypes.Address]*big.Int{
+				sourceNativeToken: val1e18(100),
+				tokens[1]:         val1e18(200),
+				tokens[2]:         val1e18(300),
+				tokens[3]:         val1e18(400),
+			},
+			expTokenPricesUSD: map[cciptypes.Address]*big.Int{
+				tokens[1]: val1e18(200),
+				tokens[2]: val1e18(300 * 1e6),
+				tokens[3]: val1e18(400),
+			},
+		},
+		{
+			name: "price getter returns more prices with missing decimals",
+			tokenDecimals: map[cciptypes.Address]uint8{
+				tokens[1]: 18,
+				tokens[2]: 12,
+			},
+			sourceNativeToken: sourceNativeToken,
+			priceGetterRespData: map[cciptypes.Address]*big.Int{
+				sourceNativeToken: val1e18(100),
+				tokens[1]:         val1e18(200),
+				tokens[2]:         val1e18(300),
+				tokens[3]:         val1e18(400),
+			},
+			priceGetterRespErr: nil,
+			expErr:             true,
+			expDecimalErr:      true,
+		},
 		{
 			name: "price getter skipped a requested price",
 			tokenDecimals: map[cciptypes.Address]uint8{
 				tokens[0]: 18,
-				tokens[1]: 18,
 			},
+			sourceNativeToken: tokens[0],
 			priceGetterRespData: map[cciptypes.Address]*big.Int{
 				tokens[0]: val1e18(100),
 			},
 			priceGetterRespErr: nil,
-			expErr:             true,
+			expTokenPricesUSD: map[cciptypes.Address]*big.Int{
+				tokens[0]: val1e18(100),
+			},
+			expErr: false,
 		},
 		{
 			name: "nil token price",
 			tokenDecimals: map[cciptypes.Address]uint8{
 				tokens[0]: 18,
 				tokens[1]: 18,
+				tokens[2]: 18,
 			},
-			filterOutTokens: []cciptypes.Address{tokens[2]},
+			sourceNativeToken: tokens[0],
+			filterOutTokens:   []cciptypes.Address{tokens[2]},
 			priceGetterRespData: map[cciptypes.Address]*big.Int{
 				tokens[0]: nil,
 				tokens[1]: val1e18(200),
@@ -434,27 +500,34 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			priceGetter := pricegetter.NewMockPriceGetter(t)
+			priceGetter := pricegetter.NewMockAllTokensPriceGetter(t)
 			defer priceGetter.AssertExpectations(t)
 
 			var destTokens []cciptypes.Address
-			for tk := range tc.tokenDecimals {
-				destTokens = append(destTokens, tk)
+			if len(tc.destTokens) == 0 {
+				for tk := range tc.tokenDecimals {
+					destTokens = append(destTokens, tk)
+				}
+			} else {
+				destTokens = tc.destTokens
+			}
+
+			finalDestTokens := make([]cciptypes.Address, 0, len(destTokens))
+			for addr := range tc.priceGetterRespData {
+				if (tc.sourceNativeToken != addr) || (slices.Contains(destTokens, addr)) {
+					finalDestTokens = append(finalDestTokens, addr)
+				}
 			}
-			sort.Slice(destTokens, func(i, j int) bool {
-				return destTokens[i] < destTokens[j]
+			sort.Slice(finalDestTokens, func(i, j int) bool {
+				return finalDestTokens[i] < finalDestTokens[j]
 			})
+
 			var destDecimals []uint8
-			for _, token := range destTokens {
+			for _, token := range finalDestTokens {
 				destDecimals = append(destDecimals, tc.tokenDecimals[token])
 			}
 
-			queryTokens := ccipcommon.FlattenUniqueSlice(destTokens)
-
-			if len(queryTokens) > 0 {
-				priceGetter.On("TokenPricesUSD", mock.Anything, queryTokens).Return(tc.priceGetterRespData, tc.priceGetterRespErr)
-				priceGetter.On("FilterConfiguredTokens", mock.Anything, mock.Anything).Return(destTokens, tc.filterOutTokens, nil)
-			}
+			priceGetter.On("GetJobSpecTokenPricesUSD", mock.Anything).Return(tc.priceGetterRespData, tc.priceGetterRespErr)
 
 			offRampReader := ccipdatamocks.NewOffRampReader(t)
 			offRampReader.On("GetTokens", mock.Anything).Return(cciptypes.OffRampTokens{
@@ -462,7 +535,11 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 			}, nil).Maybe()
 
 			destPriceReg := ccipdatamocks.NewPriceRegistryReader(t)
-			destPriceReg.On("GetTokensDecimals", mock.Anything, destTokens).Return(destDecimals, nil).Maybe()
+			if tc.expDecimalErr {
+				destPriceReg.On("GetTokensDecimals", mock.Anything, finalDestTokens).Return([]uint8{}, fmt.Errorf("Token not found")).Maybe()
+			} else {
+				destPriceReg.On("GetTokensDecimals", mock.Anything, finalDestTokens).Return(destDecimals, nil).Maybe()
+			}
 			destPriceReg.On("GetFeeTokens", mock.Anything).Return([]cciptypes.Address{destTokens[0]}, nil).Maybe()
 
 			priceService := NewPriceService(
@@ -471,7 +548,7 @@ func TestPriceService_observeTokenPriceUpdates(t *testing.T) {
 				jobId,
 				destChainSelector,
 				sourceChainSelector,
-				"0x123",
+				tc.sourceNativeToken,
 				priceGetter,
 				offRampReader,
 			).(*priceService)
@@ -754,39 +831,48 @@ func TestPriceService_priceWriteAndCleanupInBackground(t *testing.T) {
 	sourceChainSelector := uint64(67890)
 	ctx := tests.Context(t)
 
-	sourceNative := cciptypes.Address("0x123")
-	feeTokens := []cciptypes.Address{"0x234"}
-	rampTokens := []cciptypes.Address{"0x345", "0x456"}
-	rampFilteredTokens := []cciptypes.Address{"0x345"}
-	rampFilterOutTokens := []cciptypes.Address{"0x456"}
+	sourceNative := cciptypes.Address(utils.RandomAddress().String())
+	feeToken := cciptypes.Address(utils.RandomAddress().String())
+	destToken1 := cciptypes.Address(utils.RandomAddress().String())
+	destToken2 := cciptypes.Address(utils.RandomAddress().String())
 
-	laneTokens := []cciptypes.Address{"0x234", "0x345"}
-	laneTokenDecimals := []uint8{18, 18}
+	feeTokens := []cciptypes.Address{feeToken}
+	rampTokens := []cciptypes.Address{destToken1, destToken2}
 
-	tokens := []cciptypes.Address{sourceNative, "0x234", "0x345"}
-	tokenPrices := []int64{2, 3, 4}
+	laneTokens := []cciptypes.Address{sourceNative, feeToken, destToken1, destToken2}
+	// sort laneTokens
+	sort.Slice(laneTokens, func(i, j int) bool {
+		return laneTokens[i] < laneTokens[j]
+	})
+	laneTokenDecimals := []uint8{18, 18, 18, 18}
+
+	tokens := []cciptypes.Address{sourceNative, feeToken, destToken1, destToken2}
+	tokenPrices := []int64{2, 3, 4, 5}
 	gasPrice := big.NewInt(10)
 
 	orm := setupORM(t)
 
-	priceGetter := pricegetter.NewMockPriceGetter(t)
+	priceGetter := pricegetter.NewMockAllTokensPriceGetter(t)
 	defer priceGetter.AssertExpectations(t)
 
 	gasPriceEstimator := prices.NewMockGasPriceEstimatorCommit(t)
 	defer gasPriceEstimator.AssertExpectations(t)
 
-	priceGetter.On("TokenPricesUSD", mock.Anything, tokens[:1]).Return(map[cciptypes.Address]*big.Int{
+	priceGetter.On("TokenPricesUSD", mock.Anything, []cciptypes.Address{sourceNative}).Return(map[cciptypes.Address]*big.Int{
 		tokens[0]: val1e18(tokenPrices[0]),
 	}, nil)
-	priceGetter.On("TokenPricesUSD", mock.Anything, tokens[1:]).Return(map[cciptypes.Address]*big.Int{
+
+	priceGetter.On("GetJobSpecTokenPricesUSD", mock.Anything).Return(map[cciptypes.Address]*big.Int{
+		tokens[0]: val1e18(tokenPrices[0]),
 		tokens[1]: val1e18(tokenPrices[1]),
 		tokens[2]: val1e18(tokenPrices[2]),
+		tokens[3]: val1e18(tokenPrices[3]),
 	}, nil)
-	priceGetter.On("FilterConfiguredTokens", mock.Anything, rampTokens).Return(rampFilteredTokens, rampFilterOutTokens, nil)
 
+	destTokens := append(rampTokens, sourceNative)
 	offRampReader := ccipdatamocks.NewOffRampReader(t)
 	offRampReader.On("GetTokens", mock.Anything).Return(cciptypes.OffRampTokens{
-		DestinationTokens: rampTokens,
+		DestinationTokens: destTokens,
 	}, nil).Maybe()
 
 	gasPriceEstimator.On("GetGasPrice", mock.Anything).Return(gasPrice, nil)
diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go
new file mode 100644
index 00000000000..010c955c766
--- /dev/null
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/all_price_getter_mock.go
@@ -0,0 +1,269 @@
+// Code generated by mockery v2.43.2. DO NOT EDIT.
+
+package pricegetter
+
+import (
+	context "context"
+	big "math/big"
+
+	ccip "github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// MockAllTokensPriceGetter is an autogenerated mock type for the AllTokensPriceGetter type
+type MockAllTokensPriceGetter struct {
+	mock.Mock
+}
+
+type MockAllTokensPriceGetter_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *MockAllTokensPriceGetter) EXPECT() *MockAllTokensPriceGetter_Expecter {
+	return &MockAllTokensPriceGetter_Expecter{mock: &_m.Mock}
+}
+
+// Close provides a mock function with given fields:
+func (_m *MockAllTokensPriceGetter) Close() error {
+	ret := _m.Called()
+
+	if len(ret) == 0 {
+		panic("no return value specified for Close")
+	}
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func() error); ok {
+		r0 = rf()
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockAllTokensPriceGetter_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
+type MockAllTokensPriceGetter_Close_Call struct {
+	*mock.Call
+}
+
+// Close is a helper method to define mock.On call
+func (_e *MockAllTokensPriceGetter_Expecter) Close() *MockAllTokensPriceGetter_Close_Call {
+	return &MockAllTokensPriceGetter_Close_Call{Call: _e.mock.On("Close")}
+}
+
+func (_c *MockAllTokensPriceGetter_Close_Call) Run(run func()) *MockAllTokensPriceGetter_Close_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run()
+	})
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_Close_Call) Return(_a0 error) *MockAllTokensPriceGetter_Close_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_Close_Call) RunAndReturn(run func() error) *MockAllTokensPriceGetter_Close_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// FilterConfiguredTokens provides a mock function with given fields: ctx, tokens
+func (_m *MockAllTokensPriceGetter) 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
+}
+
+// MockAllTokensPriceGetter_FilterConfiguredTokens_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FilterConfiguredTokens'
+type MockAllTokensPriceGetter_FilterConfiguredTokens_Call struct {
+	*mock.Call
+}
+
+// FilterConfiguredTokens is a helper method to define mock.On call
+//   - ctx context.Context
+//   - tokens []ccip.Address
+func (_e *MockAllTokensPriceGetter_Expecter) FilterConfiguredTokens(ctx interface{}, tokens interface{}) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call {
+	return &MockAllTokensPriceGetter_FilterConfiguredTokens_Call{Call: _e.mock.On("FilterConfiguredTokens", ctx, tokens)}
+}
+
+func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) Run(run func(ctx context.Context, tokens []ccip.Address)) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].([]ccip.Address))
+	})
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) Return(configured []ccip.Address, unconfigured []ccip.Address, err error) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call {
+	_c.Call.Return(configured, unconfigured, err)
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_FilterConfiguredTokens_Call) RunAndReturn(run func(context.Context, []ccip.Address) ([]ccip.Address, []ccip.Address, error)) *MockAllTokensPriceGetter_FilterConfiguredTokens_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// GetJobSpecTokenPricesUSD provides a mock function with given fields: ctx
+func (_m *MockAllTokensPriceGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[ccip.Address]*big.Int, error) {
+	ret := _m.Called(ctx)
+
+	if len(ret) == 0 {
+		panic("no return value specified for GetJobSpecTokenPricesUSD")
+	}
+
+	var r0 map[ccip.Address]*big.Int
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context) (map[ccip.Address]*big.Int, error)); ok {
+		return rf(ctx)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context) map[ccip.Address]*big.Int); ok {
+		r0 = rf(ctx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(map[ccip.Address]*big.Int)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+		r1 = rf(ctx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJobSpecTokenPricesUSD'
+type MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call struct {
+	*mock.Call
+}
+
+// GetJobSpecTokenPricesUSD is a helper method to define mock.On call
+//   - ctx context.Context
+func (_e *MockAllTokensPriceGetter_Expecter) GetJobSpecTokenPricesUSD(ctx interface{}) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call {
+	return &MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call{Call: _e.mock.On("GetJobSpecTokenPricesUSD", ctx)}
+}
+
+func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) Run(run func(ctx context.Context)) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context))
+	})
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) Return(_a0 map[ccip.Address]*big.Int, _a1 error) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call) RunAndReturn(run func(context.Context) (map[ccip.Address]*big.Int, error)) *MockAllTokensPriceGetter_GetJobSpecTokenPricesUSD_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// TokenPricesUSD provides a mock function with given fields: ctx, tokens
+func (_m *MockAllTokensPriceGetter) TokenPricesUSD(ctx context.Context, tokens []ccip.Address) (map[ccip.Address]*big.Int, error) {
+	ret := _m.Called(ctx, tokens)
+
+	if len(ret) == 0 {
+		panic("no return value specified for TokenPricesUSD")
+	}
+
+	var r0 map[ccip.Address]*big.Int
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) (map[ccip.Address]*big.Int, error)); ok {
+		return rf(ctx, tokens)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, []ccip.Address) map[ccip.Address]*big.Int); ok {
+		r0 = rf(ctx, tokens)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(map[ccip.Address]*big.Int)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, []ccip.Address) error); ok {
+		r1 = rf(ctx, tokens)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockAllTokensPriceGetter_TokenPricesUSD_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TokenPricesUSD'
+type MockAllTokensPriceGetter_TokenPricesUSD_Call struct {
+	*mock.Call
+}
+
+// TokenPricesUSD is a helper method to define mock.On call
+//   - ctx context.Context
+//   - tokens []ccip.Address
+func (_e *MockAllTokensPriceGetter_Expecter) TokenPricesUSD(ctx interface{}, tokens interface{}) *MockAllTokensPriceGetter_TokenPricesUSD_Call {
+	return &MockAllTokensPriceGetter_TokenPricesUSD_Call{Call: _e.mock.On("TokenPricesUSD", ctx, tokens)}
+}
+
+func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) Run(run func(ctx context.Context, tokens []ccip.Address)) *MockAllTokensPriceGetter_TokenPricesUSD_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].([]ccip.Address))
+	})
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) Return(_a0 map[ccip.Address]*big.Int, _a1 error) *MockAllTokensPriceGetter_TokenPricesUSD_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockAllTokensPriceGetter_TokenPricesUSD_Call) RunAndReturn(run func(context.Context, []ccip.Address) (map[ccip.Address]*big.Int, error)) *MockAllTokensPriceGetter_TokenPricesUSD_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewMockAllTokensPriceGetter creates a new instance of MockAllTokensPriceGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+// The first argument is typically a *testing.T value.
+func NewMockAllTokensPriceGetter(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *MockAllTokensPriceGetter {
+	mock := &MockAllTokensPriceGetter{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go
index ed54428bd9b..ac4002f53fb 100644
--- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm.go
@@ -103,6 +103,11 @@ func (d *DynamicPriceGetter) FilterConfiguredTokens(ctx context.Context, tokens
 	return configured, unconfigured, nil
 }
 
+// It returns the prices of all tokens defined in the price getter.
+func (d *DynamicPriceGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error) {
+	return d.TokenPricesUSD(ctx, d.getAllTokensDefined())
+}
+
 // TokenPricesUSD implements the PriceGetter interface.
 // It returns static prices stored in the price getter, and batch calls aggregators (one per chain) to retrieve aggregator-based prices.
 func (d *DynamicPriceGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) {
@@ -116,6 +121,18 @@ func (d *DynamicPriceGetter) TokenPricesUSD(ctx context.Context, tokens []ccipty
 	return prices, nil
 }
 
+func (d *DynamicPriceGetter) getAllTokensDefined() []cciptypes.Address {
+	tokens := make([]cciptypes.Address, 0)
+
+	for addr := range d.cfg.AggregatorPrices {
+		tokens = append(tokens, ccipcalc.EvmAddrToGeneric(addr))
+	}
+	for addr := range d.cfg.StaticPrices {
+		tokens = append(tokens, ccipcalc.EvmAddrToGeneric(addr))
+	}
+	return tokens
+}
+
 // performBatchCalls performs batch calls on all chains to retrieve token prices.
 func (d *DynamicPriceGetter) performBatchCalls(ctx context.Context, batchCallsPerChain map[uint64]*batchCallsForChain, prices map[cciptypes.Address]*big.Int) error {
 	for chainID, batchCalls := range batchCallsPerChain {
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 673b9776c79..78de2699688 100644
--- a/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/evm_test.go
@@ -25,12 +25,27 @@ type testParameters struct {
 	evmClients                   map[uint64]DynamicPriceGetterClient
 	tokens                       []common.Address
 	expectedTokenPrices          map[common.Address]big.Int
+	expectedTokenPricesForAll    map[common.Address]big.Int
 	evmCallErr                   bool
 	invalidConfigErrorExpected   bool
 	priceResolutionErrorExpected bool
 }
 
-func TestDynamicPriceGetter(t *testing.T) {
+var (
+	TK1 common.Address
+	TK2 common.Address
+	TK3 common.Address
+	TK4 common.Address
+)
+
+func init() {
+	TK1 = utils.RandomAddress()
+	TK2 = utils.RandomAddress()
+	TK3 = utils.RandomAddress()
+	TK4 = utils.RandomAddress()
+}
+
+func TestDynamicPriceGetterWithEmptyInput(t *testing.T) {
 	tests := []struct {
 		name  string
 		param testParameters
@@ -63,6 +78,22 @@ func TestDynamicPriceGetter(t *testing.T) {
 			name:  "batchCall_returns_err",
 			param: testParamBatchCallReturnsErr(t),
 		},
+		{
+			name:  "less_inputs_than_defined_prices",
+			param: testLessInputsThanDefinedPrices(t),
+		},
+		{
+			name:  "get_all_tokens_aggregator_and_static",
+			param: testGetAllTokensAggregatorAndStatic(t),
+		},
+		{
+			name:  "get_all_tokens_aggregator_only",
+			param: testGetAllTokensAggregatorOnly(t),
+		},
+		{
+			name:  "get_all_tokens_static_only",
+			param: testGetAllTokensStaticOnly(),
+		},
 	}
 
 	for _, test := range tests {
@@ -74,34 +105,31 @@ 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.tokens))
-			for _, tk := range test.param.tokens {
-				tokenAddr := ccipcalc.EvmAddrToGeneric(tk)
-				tokens = append(tokens, tokenAddr)
-			}
-			prices, err := pg.TokenPricesUSD(ctx, tokens)
 
-			if test.param.evmCallErr {
-				require.Error(t, err)
-				return
-			}
+			var prices map[cciptypes.Address]*big.Int
+			var expectedTokens map[common.Address]big.Int
+			if len(test.param.expectedTokenPricesForAll) == 0 {
+				prices, err = pg.TokenPricesUSD(ctx, ccipcalc.EvmAddrsToGeneric(test.param.tokens...))
+				if test.param.evmCallErr {
+					require.Error(t, err)
+					return
+				}
 
-			if test.param.priceResolutionErrorExpected {
-				require.Error(t, err)
-				return
+				if test.param.priceResolutionErrorExpected {
+					require.Error(t, err)
+					return
+				}
+				expectedTokens = test.param.expectedTokenPrices
+			} else {
+				prices, err = pg.GetJobSpecTokenPricesUSD(ctx)
+				expectedTokens = test.param.expectedTokenPricesForAll
 			}
+
 			require.NoError(t, err)
-			// we expect prices for at least all queried tokens (it is possible that additional tokens are returned).
-			assert.True(t, len(prices) >= len(test.param.expectedTokenPrices))
+			// Ensure all expected prices are present.
+			assert.True(t, len(prices) == len(expectedTokens))
 			// Check prices are matching expected result.
-			for tk, expectedPrice := range test.param.expectedTokenPrices {
+			for tk, expectedPrice := range expectedTokens {
 				if prices[cciptypes.Address(tk.String())] == nil {
 					assert.Fail(t, "Token price not found")
 				}
@@ -113,25 +141,21 @@ func TestDynamicPriceGetter(t *testing.T) {
 }
 
 func testParamAggregatorOnly(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
-	tk4 := utils.RandomAddress()
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk3: {
+			TK3: {
 				ChainID:                   103,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk4: {
+			TK4: {
 				ChainID:                   104,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
@@ -177,15 +201,15 @@ func testParamAggregatorOnly(t *testing.T) testParameters {
 		uint64(104): mockClient(t, []uint8{20}, []aggregator_v3_interface.LatestRoundData{round4}),
 	}
 	expectedTokenPrices := map[common.Address]big.Int{
-		tk1: *multExp(round1.Answer, 10),         // expected in 1e18 format.
-		tk2: *multExp(round2.Answer, 10),         // expected in 1e18 format.
-		tk3: *round3.Answer,                      // already in 1e18 format (contract decimals==18).
-		tk4: *multExp(big.NewInt(1234567890), 8), // expected in 1e18 format.
+		TK1: *multExp(round1.Answer, 10),         // expected in 1e18 format.
+		TK2: *multExp(round2.Answer, 10),         // expected in 1e18 format.
+		TK3: *round3.Answer,                      // already in 1e18 format (contract decimals==18).
+		TK4: *multExp(big.NewInt(1234567890), 8), // expected in 1e18 format.
 	}
 	return testParameters{
 		cfg:                        cfg,
 		evmClients:                 evmClients,
-		tokens:                     []common.Address{tk1, tk2, tk3, tk4},
+		tokens:                     []common.Address{TK1, TK2, TK3, TK4},
 		expectedTokenPrices:        expectedTokenPrices,
 		invalidConfigErrorExpected: false,
 	}
@@ -193,20 +217,17 @@ func testParamAggregatorOnly(t *testing.T) testParameters {
 
 // testParamAggregatorOnlyMulti test with several tokens on chain 102.
 func testParamAggregatorOnlyMulti(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk3: {
+			TK3: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
@@ -241,35 +262,32 @@ func testParamAggregatorOnlyMulti(t *testing.T) testParameters {
 		uint64(102): mockClient(t, []uint8{8, 8}, []aggregator_v3_interface.LatestRoundData{round2, round3}),
 	}
 	expectedTokenPrices := map[common.Address]big.Int{
-		tk1: *multExp(round1.Answer, 10),
-		tk2: *multExp(round2.Answer, 10),
-		tk3: *multExp(round3.Answer, 10),
+		TK1: *multExp(round1.Answer, 10),
+		TK2: *multExp(round2.Answer, 10),
+		TK3: *multExp(round3.Answer, 10),
 	}
 	return testParameters{
 		cfg:                        cfg,
 		evmClients:                 evmClients,
 		invalidConfigErrorExpected: false,
-		tokens:                     []common.Address{tk1, tk2, tk3},
+		tokens:                     []common.Address{TK1, TK2, TK3},
 		expectedTokenPrices:        expectedTokenPrices,
 	}
 }
 
 func testParamStaticOnly() testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{},
 		StaticPrices: map[common.Address]config.StaticPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID: 101,
 				Price:   big.NewInt(1_234_000),
 			},
-			tk2: {
+			TK2: {
 				ChainID: 102,
 				Price:   big.NewInt(2_234_000),
 			},
-			tk3: {
+			TK3: {
 				ChainID: 103,
 				Price:   big.NewInt(3_234_000),
 			},
@@ -278,35 +296,86 @@ func testParamStaticOnly() testParameters {
 	// Real LINK/USD example from OP.
 	evmClients := map[uint64]DynamicPriceGetterClient{}
 	expectedTokenPrices := map[common.Address]big.Int{
-		tk1: *cfg.StaticPrices[tk1].Price,
-		tk2: *cfg.StaticPrices[tk2].Price,
-		tk3: *cfg.StaticPrices[tk3].Price,
+		TK1: *cfg.StaticPrices[TK1].Price,
+		TK2: *cfg.StaticPrices[TK2].Price,
+		TK3: *cfg.StaticPrices[TK3].Price,
 	}
 	return testParameters{
 		cfg:                 cfg,
 		evmClients:          evmClients,
-		tokens:              []common.Address{tk1, tk2, tk3},
+		tokens:              []common.Address{TK1, TK2, TK3},
 		expectedTokenPrices: expectedTokenPrices,
 	}
 }
 
+func testParamNoAggregatorForToken(t *testing.T) testParameters {
+	cfg := config.DynamicPriceGetterConfig{
+		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
+			TK1: {
+				ChainID:                   101,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+			TK2: {
+				ChainID:                   102,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+		},
+		StaticPrices: map[common.Address]config.StaticPriceConfig{
+			TK3: {
+				ChainID: 103,
+				Price:   big.NewInt(1_234_000),
+			},
+		},
+	}
+	// Real LINK/USD example from OP.
+	round1 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(1000),
+		Answer:          big.NewInt(1396818990),
+		StartedAt:       big.NewInt(1704896575),
+		UpdatedAt:       big.NewInt(1704896575),
+		AnsweredInRound: big.NewInt(1000),
+	}
+	// Real ETH/USD example from OP.
+	round2 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(2000),
+		Answer:          big.NewInt(238879815123),
+		StartedAt:       big.NewInt(1704897197),
+		UpdatedAt:       big.NewInt(1704897197),
+		AnsweredInRound: big.NewInt(2000),
+	}
+	evmClients := map[uint64]DynamicPriceGetterClient{
+		uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}),
+		uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}),
+	}
+	expectedTokenPrices := map[common.Address]big.Int{
+		TK1: *round1.Answer,
+		TK2: *round2.Answer,
+		TK3: *cfg.StaticPrices[TK3].Price,
+		TK4: *big.NewInt(0),
+	}
+	return testParameters{
+		cfg:                          cfg,
+		evmClients:                   evmClients,
+		tokens:                       []common.Address{TK1, TK2, TK3, TK4},
+		expectedTokenPrices:          expectedTokenPrices,
+		priceResolutionErrorExpected: true,
+	}
+}
+
 func testParamAggregatorAndStaticValid(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
 		},
 		StaticPrices: map[common.Address]config.StaticPriceConfig{
-			tk3: {
+			TK3: {
 				ChainID: 103,
 				Price:   big.NewInt(1_234_000),
 			},
@@ -333,39 +402,36 @@ func testParamAggregatorAndStaticValid(t *testing.T) testParameters {
 		uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}),
 	}
 	expectedTokenPrices := map[common.Address]big.Int{
-		tk1: *multExp(round1.Answer, 10),
-		tk2: *multExp(round2.Answer, 10),
-		tk3: *cfg.StaticPrices[tk3].Price,
+		TK1: *multExp(round1.Answer, 10),
+		TK2: *multExp(round2.Answer, 10),
+		TK3: *cfg.StaticPrices[TK3].Price,
 	}
 	return testParameters{
 		cfg:                 cfg,
 		evmClients:          evmClients,
-		tokens:              []common.Address{tk1, tk2, tk3},
+		tokens:              []common.Address{TK1, TK2, TK3},
 		expectedTokenPrices: expectedTokenPrices,
 	}
 }
 
 func testParamAggregatorAndStaticTokenCollision(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk3: {
+			TK3: {
 				ChainID:                   103,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
 		},
 		StaticPrices: map[common.Address]config.StaticPriceConfig{
-			tk3: {
+			TK3: {
 				ChainID: 103,
 				Price:   big.NewInt(1_234_000),
 			},
@@ -402,29 +468,25 @@ func testParamAggregatorAndStaticTokenCollision(t *testing.T) testParameters {
 	return testParameters{
 		cfg:                        cfg,
 		evmClients:                 evmClients,
-		tokens:                     []common.Address{tk1, tk2, tk3},
+		tokens:                     []common.Address{TK1, TK2, TK3},
 		invalidConfigErrorExpected: true,
 	}
 }
 
-func testParamNoAggregatorForToken(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
-	tk4 := utils.RandomAddress()
+func testParamBatchCallReturnsErr(t *testing.T) testParameters {
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
 		},
 		StaticPrices: map[common.Address]config.StaticPriceConfig{
-			tk3: {
+			TK3: {
 				ChainID: 103,
 				Price:   big.NewInt(1_234_000),
 			},
@@ -438,6 +500,51 @@ func testParamNoAggregatorForToken(t *testing.T) testParameters {
 		UpdatedAt:       big.NewInt(1704896575),
 		AnsweredInRound: big.NewInt(1000),
 	}
+	evmClients := map[uint64]DynamicPriceGetterClient{
+		uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}),
+		uint64(102): {
+			BatchCaller: mockErrCaller(t),
+		},
+	}
+	return testParameters{
+		cfg:        cfg,
+		evmClients: evmClients,
+		tokens:     []common.Address{TK1, TK2, TK3},
+		evmCallErr: true,
+	}
+}
+
+func testLessInputsThanDefinedPrices(t *testing.T) testParameters {
+	cfg := config.DynamicPriceGetterConfig{
+		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
+			TK1: {
+				ChainID:                   101,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+			TK2: {
+				ChainID:                   102,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+			TK3: {
+				ChainID:                   103,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+		},
+		StaticPrices: map[common.Address]config.StaticPriceConfig{
+			TK4: {
+				ChainID: 104,
+				Price:   big.NewInt(1_234_000),
+			},
+		},
+	}
+	// Real LINK/USD example from OP.
+	round1 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(1000),
+		Answer:          big.NewInt(3749350456),
+		StartedAt:       big.NewInt(1704896575),
+		UpdatedAt:       big.NewInt(1704896575),
+		AnsweredInRound: big.NewInt(1000),
+	}
 	// Real ETH/USD example from OP.
 	round2 := aggregator_v3_interface.LatestRoundData{
 		RoundId:         big.NewInt(2000),
@@ -446,43 +553,51 @@ func testParamNoAggregatorForToken(t *testing.T) testParameters {
 		UpdatedAt:       big.NewInt(1704897197),
 		AnsweredInRound: big.NewInt(2000),
 	}
+	// Real LINK/ETH example from OP.
+	round3 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(3000),
+		Answer:          big.NewInt(4468862777874802),
+		StartedAt:       big.NewInt(1715743907),
+		UpdatedAt:       big.NewInt(1715743907),
+		AnsweredInRound: big.NewInt(3000),
+	}
 	evmClients := map[uint64]DynamicPriceGetterClient{
 		uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}),
 		uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}),
+		uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}),
 	}
 	expectedTokenPrices := map[common.Address]big.Int{
-		tk1: *round1.Answer,
-		tk2: *round2.Answer,
-		tk3: *cfg.StaticPrices[tk3].Price,
-		tk4: *big.NewInt(0),
+		TK1: *multExp(round1.Answer, 10),
+		TK2: *multExp(round2.Answer, 10),
+		TK3: *multExp(round3.Answer, 10),
 	}
 	return testParameters{
-		cfg:                          cfg,
-		evmClients:                   evmClients,
-		tokens:                       []common.Address{tk1, tk2, tk3, tk4},
-		expectedTokenPrices:          expectedTokenPrices,
-		priceResolutionErrorExpected: true,
+		cfg:                 cfg,
+		evmClients:          evmClients,
+		tokens:              []common.Address{TK1, TK2, TK3},
+		expectedTokenPrices: expectedTokenPrices,
 	}
 }
 
-func testParamBatchCallReturnsErr(t *testing.T) testParameters {
-	tk1 := utils.RandomAddress()
-	tk2 := utils.RandomAddress()
-	tk3 := utils.RandomAddress()
+func testGetAllTokensAggregatorAndStatic(t *testing.T) testParameters {
 	cfg := config.DynamicPriceGetterConfig{
 		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
-			tk1: {
+			TK1: {
 				ChainID:                   101,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
-			tk2: {
+			TK2: {
 				ChainID:                   102,
 				AggregatorContractAddress: utils.RandomAddress(),
 			},
+			TK3: {
+				ChainID:                   103,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
 		},
 		StaticPrices: map[common.Address]config.StaticPriceConfig{
-			tk3: {
-				ChainID: 103,
+			TK4: {
+				ChainID: 104,
 				Price:   big.NewInt(1_234_000),
 			},
 		},
@@ -490,22 +605,133 @@ func testParamBatchCallReturnsErr(t *testing.T) testParameters {
 	// Real LINK/USD example from OP.
 	round1 := aggregator_v3_interface.LatestRoundData{
 		RoundId:         big.NewInt(1000),
-		Answer:          big.NewInt(1396818990),
+		Answer:          big.NewInt(3749350456),
 		StartedAt:       big.NewInt(1704896575),
 		UpdatedAt:       big.NewInt(1704896575),
 		AnsweredInRound: big.NewInt(1000),
 	}
+	// Real ETH/USD example from OP.
+	round2 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(2000),
+		Answer:          big.NewInt(238879815123),
+		StartedAt:       big.NewInt(1704897197),
+		UpdatedAt:       big.NewInt(1704897197),
+		AnsweredInRound: big.NewInt(2000),
+	}
+	// Real LINK/ETH example from OP.
+	round3 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(3000),
+		Answer:          big.NewInt(4468862777874802),
+		StartedAt:       big.NewInt(1715743907),
+		UpdatedAt:       big.NewInt(1715743907),
+		AnsweredInRound: big.NewInt(3000),
+	}
 	evmClients := map[uint64]DynamicPriceGetterClient{
 		uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}),
-		uint64(102): {
-			BatchCaller: mockErrCaller(t),
+		uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}),
+		uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}),
+	}
+	expectedTokenPricesForAll := map[common.Address]big.Int{
+		TK1: *multExp(round1.Answer, 10),
+		TK2: *multExp(round2.Answer, 10),
+		TK3: *multExp(round3.Answer, 10),
+		TK4: *cfg.StaticPrices[TK4].Price,
+	}
+	return testParameters{
+		cfg:                       cfg,
+		evmClients:                evmClients,
+		expectedTokenPricesForAll: expectedTokenPricesForAll,
+	}
+}
+
+func testGetAllTokensAggregatorOnly(t *testing.T) testParameters {
+	cfg := config.DynamicPriceGetterConfig{
+		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{
+			TK1: {
+				ChainID:                   101,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+			TK2: {
+				ChainID:                   102,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
+			TK3: {
+				ChainID:                   103,
+				AggregatorContractAddress: utils.RandomAddress(),
+			},
 		},
+		StaticPrices: map[common.Address]config.StaticPriceConfig{},
+	}
+	// Real LINK/USD example from OP.
+	round1 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(1000),
+		Answer:          big.NewInt(3749350456),
+		StartedAt:       big.NewInt(1704896575),
+		UpdatedAt:       big.NewInt(1704896575),
+		AnsweredInRound: big.NewInt(1000),
+	}
+	// Real ETH/USD example from OP.
+	round2 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(2000),
+		Answer:          big.NewInt(238879815123),
+		StartedAt:       big.NewInt(1704897197),
+		UpdatedAt:       big.NewInt(1704897197),
+		AnsweredInRound: big.NewInt(2000),
+	}
+	// Real LINK/ETH example from OP.
+	round3 := aggregator_v3_interface.LatestRoundData{
+		RoundId:         big.NewInt(3000),
+		Answer:          big.NewInt(4468862777874802),
+		StartedAt:       big.NewInt(1715743907),
+		UpdatedAt:       big.NewInt(1715743907),
+		AnsweredInRound: big.NewInt(3000),
+	}
+	evmClients := map[uint64]DynamicPriceGetterClient{
+		uint64(101): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round1}),
+		uint64(102): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round2}),
+		uint64(103): mockClient(t, []uint8{8}, []aggregator_v3_interface.LatestRoundData{round3}),
+	}
+	expectedTokenPricesForAll := map[common.Address]big.Int{
+		TK1: *multExp(round1.Answer, 10),
+		TK2: *multExp(round2.Answer, 10),
+		TK3: *multExp(round3.Answer, 10),
 	}
 	return testParameters{
-		cfg:        cfg,
-		evmClients: evmClients,
-		tokens:     []common.Address{tk1, tk2, tk3},
-		evmCallErr: true,
+		cfg:                       cfg,
+		evmClients:                evmClients,
+		expectedTokenPricesForAll: expectedTokenPricesForAll,
+	}
+}
+
+func testGetAllTokensStaticOnly() testParameters {
+	cfg := config.DynamicPriceGetterConfig{
+		AggregatorPrices: map[common.Address]config.AggregatorPriceConfig{},
+		StaticPrices: map[common.Address]config.StaticPriceConfig{
+			TK1: {
+				ChainID: 101,
+				Price:   big.NewInt(1_234_000),
+			},
+			TK2: {
+				ChainID: 102,
+				Price:   big.NewInt(2_234_000),
+			},
+			TK3: {
+				ChainID: 103,
+				Price:   big.NewInt(3_234_000),
+			},
+		},
+	}
+
+	evmClients := map[uint64]DynamicPriceGetterClient{}
+	expectedTokenPricesForAll := map[common.Address]big.Int{
+		TK1: *cfg.StaticPrices[TK1].Price,
+		TK2: *cfg.StaticPrices[TK2].Price,
+		TK3: *cfg.StaticPrices[TK3].Price,
+	}
+	return testParameters{
+		cfg:                       cfg,
+		evmClients:                evmClients,
+		expectedTokenPricesForAll: expectedTokenPricesForAll,
 	}
 }
 
diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go
index ae9a10deb65..34977eda9f1 100644
--- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline.go
@@ -11,7 +11,6 @@ import (
 	"github.com/pkg/errors"
 
 	cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
-
 	"github.com/smartcontractkit/chainlink/v2/core/logger"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc"
 	"github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/parseutil"
@@ -60,28 +59,30 @@ func (d *PipelineGetter) FilterConfiguredTokens(ctx context.Context, tokens []cc
 	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,
-		DotDagSource: d.source,
-		CreatedAt:    time.Now(),
-		JobID:        d.jobID,
-		JobName:      d.name,
-		JobType:      "",
-	}, pipeline.NewVarsFrom(map[string]interface{}{}))
+func (d *PipelineGetter) GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error) {
+	prices, err := d.getPricesFromRunner(ctx)
 	if err != nil {
 		return nil, err
 	}
-	finalResult := trrs.FinalResult()
-	if finalResult.HasErrors() {
-		return nil, errors.Errorf("error getting prices %v", finalResult.AllErrors)
-	}
-	if len(finalResult.Values) != 1 {
-		return nil, errors.Errorf("invalid number of price results, expected 1 got %v", len(finalResult.Values))
+
+	tokenPrices := make(map[cciptypes.Address]*big.Int)
+	for tokenAddressStr, rawPrice := range prices {
+		tokenAddressStr := ccipcalc.HexToAddress(tokenAddressStr)
+		castedPrice, err := parseutil.ParseBigIntFromAny(rawPrice)
+		if err != nil {
+			return nil, err
+		}
+
+		tokenPrices[tokenAddressStr] = castedPrice
 	}
-	prices, ok := finalResult.Values[0].(map[string]interface{})
-	if !ok {
-		return nil, errors.Errorf("expected map output of price pipeline, got %T", finalResult.Values[0])
+
+	return tokenPrices, nil
+}
+
+func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.Address) (map[cciptypes.Address]*big.Int, error) {
+	prices, err := d.getPricesFromRunner(ctx)
+	if err != nil {
+		return nil, err
 	}
 
 	providedTokensSet := mapset.NewSet(tokens...)
@@ -101,7 +102,7 @@ func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.
 	// The mapping of token address to source of token price has to live offchain.
 	// Best we can do is sanity check that the token price spec covers all our desired execution token prices.
 	for _, token := range tokens {
-		if _, ok = tokenPrices[token]; !ok {
+		if _, ok := tokenPrices[token]; !ok {
 			return nil, errors.Errorf("missing token %s from tokensForFeeCoin spec, got %v", token, prices)
 		}
 	}
@@ -109,6 +110,33 @@ func (d *PipelineGetter) TokenPricesUSD(ctx context.Context, tokens []cciptypes.
 	return tokenPrices, nil
 }
 
+func (d *PipelineGetter) getPricesFromRunner(ctx context.Context) (map[string]interface{}, error) {
+	_, trrs, err := d.runner.ExecuteRun(ctx, pipeline.Spec{
+		ID:           d.jobID,
+		DotDagSource: d.source,
+		CreatedAt:    time.Now(),
+		JobID:        d.jobID,
+		JobName:      d.name,
+		JobType:      "",
+	}, pipeline.NewVarsFrom(map[string]interface{}{}))
+	if err != nil {
+		return nil, err
+	}
+	finalResult := trrs.FinalResult()
+	if finalResult.HasErrors() {
+		return nil, errors.Errorf("error getting prices %v", finalResult.AllErrors)
+	}
+	if len(finalResult.Values) != 1 {
+		return nil, errors.Errorf("invalid number of price results, expected 1 got %v", len(finalResult.Values))
+	}
+	prices, ok := finalResult.Values[0].(map[string]interface{})
+	if !ok {
+		return nil, errors.Errorf("expected map output of price pipeline, got %T", finalResult.Values[0])
+	}
+
+	return prices, nil
+}
+
 func (d *PipelineGetter) Close() error {
 	return d.runner.Close()
 }
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 37970750732..8aeeff96b57 100644
--- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pipeline_test.go
@@ -57,19 +57,21 @@ func TestDataSource(t *testing.T) {
 
 	priceGetter := newTestPipelineGetter(t, source)
 
-	// USDC & LINK are configured
-	confTokens, _, err := priceGetter.FilterConfiguredTokens(context.Background(), []cciptypes.Address{linkTokenAddress, usdcTokenAddress})
+	// Ask for all prices present in spec.
+	prices, err := priceGetter.GetJobSpecTokenPricesUSD(context.Background())
 	require.NoError(t, err)
-	assert.Equal(t, linkTokenAddress, confTokens[0])
-	assert.Equal(t, usdcTokenAddress, confTokens[1])
+	assert.Equal(t, prices, map[cciptypes.Address]*big.Int{
+		linkTokenAddress: big.NewInt(0).Mul(big.NewInt(200), big.NewInt(1000000000000000000)),
+		usdcTokenAddress: big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(1000000000000000000)),
+	})
 
-	// Ask for all prices present in spec.
-	prices, err := priceGetter.TokenPricesUSD(context.Background(), []cciptypes.Address{
+	// Specifically ask for all prices
+	pricesWithInput, errWithInput := priceGetter.TokenPricesUSD(context.Background(), []cciptypes.Address{
 		linkTokenAddress,
 		usdcTokenAddress,
 	})
-	require.NoError(t, err)
-	assert.Equal(t, prices, map[cciptypes.Address]*big.Int{
+	require.NoError(t, errWithInput)
+	assert.Equal(t, pricesWithInput, map[cciptypes.Address]*big.Int{
 		linkTokenAddress: big.NewInt(0).Mul(big.NewInt(200), big.NewInt(1000000000000000000)),
 		usdcTokenAddress: big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(1000000000000000000)),
 	})
diff --git a/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go b/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go
index 9ee0e8f3d0a..d4bcfc57b6e 100644
--- a/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go
+++ b/core/services/ocr2/plugins/ccip/internal/pricegetter/pricegetter.go
@@ -1,7 +1,18 @@
 package pricegetter
 
-import cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
+import (
+	"context"
+	"math/big"
+
+	cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip"
+)
 
 type PriceGetter interface {
 	cciptypes.PriceGetter
 }
+
+type AllTokensPriceGetter interface {
+	PriceGetter
+	// GetJobSpecTokenPricesUSD returns all token prices defined in the jobspec.
+	GetJobSpecTokenPricesUSD(ctx context.Context) (map[cciptypes.Address]*big.Int, error)
+}
diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go
index 177ccf323b7..fe9021e4c14 100644
--- a/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go
+++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/chainlink.go
@@ -7,6 +7,7 @@ import (
 	"math/big"
 	"net/http"
 	"net/http/httptest"
+	"slices"
 	"strconv"
 	"strings"
 	"testing"
@@ -49,6 +50,7 @@ import (
 	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp"
 	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp"
 	"github.com/smartcontractkit/chainlink/v2/core/internal/cltest/heavyweight"
+	"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0"
 	"github.com/smartcontractkit/chainlink/v2/core/internal/testutils"
 	"github.com/smartcontractkit/chainlink/v2/core/logger"
 	"github.com/smartcontractkit/chainlink/v2/core/logger/audit"
@@ -762,6 +764,47 @@ func (c *CCIPIntegrationTestHarness) NoNodesHaveExecutedSeqNum(t *testing.T, seq
 	return log
 }
 
+func (c *CCIPIntegrationTestHarness) EventuallyPriceRegistryUpdated(t *testing.T, block uint64, srcSelector uint64, tokens []common.Address, sourceNative common.Address, priceRegistryOpts ...common.Address) {
+	var priceRegistry *price_registry_1_2_0.PriceRegistry
+	var err error
+	if len(priceRegistryOpts) > 0 {
+		priceRegistry, err = price_registry_1_2_0.NewPriceRegistry(priceRegistryOpts[0], c.Dest.Chain)
+		require.NoError(t, err)
+	} else {
+		require.NotNil(t, c.Dest.PriceRegistry, "no priceRegistry configured")
+		priceRegistry = c.Dest.PriceRegistry
+	}
+
+	g := gomega.NewGomegaWithT(t)
+	g.Eventually(func() bool {
+		it, err := priceRegistry.FilterUsdPerTokenUpdated(&bind.FilterOpts{Start: block}, tokens)
+		g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerTokenUpdated event")
+
+		tokensFetched := make([]common.Address, 0, len(tokens))
+		for it.Next() {
+			tokenFetched := it.Event.Token
+			tokensFetched = append(tokensFetched, tokenFetched)
+			t.Log("Token price updated", tokenFetched.String(), it.Event.Value.String(), it.Event.Timestamp.String())
+		}
+
+		for _, token := range tokens {
+			if !slices.Contains(tokensFetched, token) {
+				return false
+			}
+		}
+
+		return true
+	}, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "Tokens prices has not been updated")
+
+	g.Eventually(func() bool {
+		it, err := priceRegistry.FilterUsdPerUnitGasUpdated(&bind.FilterOpts{Start: block}, []uint64{srcSelector})
+		g.Expect(err).NotTo(gomega.HaveOccurred(), "Error filtering UsdPerUnitGasUpdated event")
+		g.Expect(it.Next()).To(gomega.BeTrue(), "No UsdPerUnitGasUpdated event found")
+
+		return true
+	}, testutils.WaitTimeout(t), 10*time.Second).Should(gomega.BeTrue(), "source gas price has not been updated")
+}
+
 func (c *CCIPIntegrationTestHarness) EventuallyCommitReportAccepted(t *testing.T, currentBlock uint64, commitStoreOpts ...common.Address) commit_store.CommitStoreCommitReport {
 	var commitStore *commit_store.CommitStore
 	var err error