From 1ed47a60db3d43624a0604ebf7e968451543a6c0 Mon Sep 17 00:00:00 2001 From: nogo <110664798+0xnogo@users.noreply.github.com> Date: Fri, 29 Nov 2024 19:44:00 +0400 Subject: [PATCH] Fee boosting improvements (#15418) * v2 test * use last testsetups * new logic * modtidy * lint * cleaning * more clean ups * comments * addressing comments 2 * mv test file --- .github/e2e-tests.yml | 13 - .github/integration-in-memory-tests.yml | 8 + deployment/ccip/changeset/test_helpers.go | 5 +- .../smoke/ccip/ccip_fee_boosting_test.go | 317 ++++++++++++++++++ .../smoke/ccip/fee_boosting_test.go | 121 ------- .../testsetups/ccip/test_helpers.go | 9 +- 6 files changed, 335 insertions(+), 138 deletions(-) create mode 100644 integration-tests/smoke/ccip/ccip_fee_boosting_test.go delete mode 100644 integration-tests/smoke/ccip/fee_boosting_test.go diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 3f412854e1c..12cfcfb9256 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -987,19 +987,6 @@ runner-test-matrix: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/fee_boosting_test.go:* - path: integration-tests/smoke/ccip/fee_boosting_test.go - test_env_type: docker - runs_on: ubuntu-latest - triggers: - - PR E2E Core Tests - - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test fee_boosting_test.go -timeout 15m -test.parallel=1 -count=1 -json - pyroscope_env: ci-smoke-ccipv1_6-evm-simulated - test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 - E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOnTwoLanesIncludingBatching$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index 4865676d727..de1909f2060 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -23,4 +23,12 @@ runner-test-matrix: triggers: - PR Integration CCIP Tests test_cmd: cd integration-tests/smoke/ccip && go test ccip_messaging_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: smoke/ccip/ccip_fee_boosting_test.go:* + path: integration-tests/smoke/ccip/ccip_fee_boosting_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/smoke/ccip && go test ccip_fee_boosting_test.go -timeout 12m -test.parallel=2 -count=1 -json # END: CCIP tests diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 7889d491c86..58e63da7d66 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -259,8 +259,9 @@ func mockAttestationResponse() *httptest.Server { } type TestConfigs struct { - IsUSDC bool - IsMultiCall3 bool + IsUSDC bool + IsMultiCall3 bool + OCRConfigOverride func(CCIPOCRParams) CCIPOCRParams } func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int, tCfg *TestConfigs) DeployedEnv { diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go new file mode 100644 index 00000000000..2b14c883238 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -0,0 +1,317 @@ +package smoke + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/test-go/testify/require" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + linkPrice = deployment.E18Mult(100) + wethPrice = deployment.E18Mult(4000) +) + +func Test_CCIPFeeBoosting(t *testing.T) { + e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + // Only 1 boost (=OCR round) is enough to cover the fee + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + // Disable token price updates + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable gas price updates + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable token price updates + params.CommitOffChainConfig.TokenInfo = nil + return params + }, + }) + + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + require.Len(t, allChainSelectors, 2) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + fetchedGasPriceDest, err := e.Env.Chains[destChain].Client.SuggestGasPrice(tests.Context(t)) + require.NoError(t, err) + originalGasPriceDestUSD := new(big.Int).Div( + new(big.Int).Mul(fetchedGasPriceDest, wethPrice), + big.NewInt(1e18), + ) + t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) + + // Adjust destination gas price on source fee quoter to 95% of the current value + adjustedGasPriceDest := + new(big.Int).Div( + new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), + big.NewInt(100), + ) + t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) + + initialPrices := changeset.InitialPrices{ + LinkPrice: linkPrice, + WethPrice: wethPrice, + GasPrice: changeset.ToPackedFee(adjustedGasPriceDest, big.NewInt(0)), + } + + laneCfg := changeset.LaneConfig{ + SourceSelector: sourceChain, + DestSelector: destChain, + InitialPricesBySource: initialPrices, + FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), + } + require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) + + // Update token prices in destination chain FeeQuoter + err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ + state.Chains[destChain].LinkToken.Address(): linkPrice, + state.Chains[destChain].Weth9.Address(): wethPrice, + }) + require.NoError(t, err) + + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[changeset.SourceDestPair]uint64) + expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) + + latesthdr, err := e.Env.Chains[sourceChain].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + msgSentEvent := changeset.TestSendRequest(t, e.Env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + Data: []byte("message that needs fee boosting"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + startBlocks[sourceChain] = &block + expectedSeqNum[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = []uint64{msgSentEvent.SequenceNumber} + + err = updateGasPrice(e, state, sourceChain, destChain, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm gas prices are updated + srcFeeQuoter := state.Chains[sourceChain].FeeQuoter + err = changeset.ConfirmGasPriceUpdated(t, e.Env.Chains[destChain], srcFeeQuoter, 0, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm that fee boosting will be triggered + require.True(t, willTriggerFeeBoosting(t, msgSentEvent, state, sourceChain, destChain)) + + // hack + time.Sleep(30 * time.Second) + replayBlocks := make(map[uint64]uint64) + replayBlocks[sourceChain] = 1 + replayBlocks[destChain] = 1 + changeset.ReplayLogs(t, e.Env.Offchain, replayBlocks) + + // Confirm that the message is committed and executed + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) +} + +// TODO: Find a more accurate way to determine if fee boosting will be triggered +func willTriggerFeeBoosting( + t *testing.T, + msgSentEvent *onramp.OnRampCCIPMessageSent, + state changeset.CCIPOnChainState, + srcChain, destChain uint64) bool { + msg := convertToMessage(msgSentEvent.Message) + t.Log("\n=== Fee Boosting Analysis ===") + t.Logf("Src Chain: %d", msg.Header.SourceChainSelector) + t.Logf("Dest Chain: %d", msg.Header.DestChainSelector) + + ep := ccipevm.NewGasEstimateProvider() + chainState, exists := state.Chains[srcChain] + require.True(t, exists) + feeQuoter := chainState.FeeQuoter + + premium, err := feeQuoter.GetPremiumMultiplierWeiPerEth(&bind.CallOpts{Context: context.Background()}, chainState.Weth9.Address()) + require.NoError(t, err) + t.Logf("Premium: %d", premium) + + // Get LINK price + linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) + require.NoError(t, err) + t.Logf("LINK Price: %s", linkPrice.Value.String()) + t.Logf("Juels in message: %s", msg.FeeValueJuels.String()) + + // Calculate fee in USD token + fee := new(big.Int).Div( + new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), + new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), + ) + t.Logf("Fee paid (in USD token): %s", fee.String()) + + // Calculate message gas + messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) + t.Logf("Estimated message gas: %s", messageGas.String()) + + // Get token and gas prices + nativeTokenAddress := chainState.Weth9.Address() + tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, destChain) + require.NoError(t, err) + t.Logf("Raw gas price (uint224): %s for chain: %d", tokenAndGasPrice.GasPriceValue.String(), destChain) + + // Extract uint112 gas price + gasPrice, err := convertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) + require.NoError(t, err) + t.Logf("Extracted gas price (uint112): %s", gasPrice.String()) + t.Logf("Native token price: %s", tokenAndGasPrice.TokenPrice.String()) + + // Calculate total execution cost + execCost := new(big.Int).Mul(messageGas, gasPrice) + t.Logf("Total execution cost: %s", execCost.String()) + + // Check if fee boosting will trigger + willBoost := execCost.Cmp(fee) > 0 + t.Logf("\nWill fee boosting trigger? %v", willBoost) + t.Logf("Execution cost / Fee ratio: %.2f", + new(big.Float).Quo( + new(big.Float).SetInt(execCost), + new(big.Float).SetInt(fee), + ), + ) + + return execCost.Cmp(fee) > 0 +} + +func convertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { + // Create a mask for uint112 (112 bits of 1s) + mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) + + // Extract the lower 112 bits using AND operation + result := new(big.Int).And(gasPrice, mask) + + return result, nil +} + +func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { + // Convert header + header := cciptypes.RampMessageHeader{ + MessageID: cciptypes.Bytes32(msg.Header.MessageId), + SourceChainSelector: cciptypes.ChainSelector(msg.Header.SourceChainSelector), + DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), + SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), + Nonce: msg.Header.Nonce, + } + + // Convert token amounts + tokenAmounts := make([]cciptypes.RampTokenAmount, len(msg.TokenAmounts)) + for i, ta := range msg.TokenAmounts { + tokenAmounts[i] = cciptypes.RampTokenAmount{ + SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), + DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), + ExtraData: cciptypes.Bytes(ta.ExtraData), + Amount: cciptypes.BigInt{Int: ta.Amount}, + DestExecData: cciptypes.Bytes(ta.DestExecData), + } + } + + return cciptypes.Message{ + Header: header, + Sender: cciptypes.UnknownAddress(msg.Sender.Bytes()), + Data: cciptypes.Bytes(msg.Data), + Receiver: cciptypes.UnknownAddress(msg.Receiver), + ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), + FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), + FeeTokenAmount: cciptypes.BigInt{Int: msg.FeeTokenAmount}, + FeeValueJuels: cciptypes.BigInt{Int: msg.FeeValueJuels}, + TokenAmounts: tokenAmounts, + } +} + +func updateGasPrice(env changeset.DeployedEnv, state changeset.CCIPOnChainState, srcChain, destChain uint64, gasPrice *big.Int) error { + chainState, exists := state.Chains[srcChain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", srcChain) + } + + feeQuoter := chainState.FeeQuoter + // Update gas price + auth := env.Env.Chains[srcChain].DeployerKey + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: nil, + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + { + DestChainSelector: destChain, + UsdPerUnitGas: gasPrice, + }, + }, + }) + if err != nil { + return errors.Wrapf(err, "updating gas price on chain %d", srcChain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[srcChain], tx, err); err != nil { + return err + } + + return nil +} + +func updateTokensPrices(env changeset.DeployedEnv, state changeset.CCIPOnChainState, chain uint64, tokenPrices map[common.Address]*big.Int) error { + chainState, exists := state.Chains[chain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", chain) + } + + feeQuoter := chainState.FeeQuoter + // Update token prices + auth := env.Env.Chains[chain].DeployerKey + tokenPricesUpdates := make([]fee_quoter.InternalTokenPriceUpdate, 0, len(tokenPrices)) + for token, price := range tokenPrices { + tokenPricesUpdates = append(tokenPricesUpdates, fee_quoter.InternalTokenPriceUpdate{ + SourceToken: token, + UsdPerToken: price, + }) + } + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: tokenPricesUpdates, + GasPriceUpdates: nil, + }) + if err != nil { + return errors.Wrapf(err, "updating token prices on chain %d", chain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[chain], tx, err); err != nil { + return err + } + + return nil +} diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go deleted file mode 100644 index 918ac243ab8..00000000000 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package smoke - -import ( - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/test-go/testify/require" - "golang.org/x/exp/maps" - - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -type feeboostTestCase struct { - t *testing.T - sender []byte - deployedEnv changeset.DeployedEnv - onchainState changeset.CCIPOnChainState - initialPrices changeset.InitialPrices - priceFeedPrices priceFeedPrices - sourceChain, destChain uint64 -} - -type priceFeedPrices struct { - linkPrice *big.Int - wethPrice *big.Int -} - -// TODO: find a way to reuse the same test setup for all tests -func Test_CCIPFeeBoosting(t *testing.T) { - setupTestEnv := func(t *testing.T, numChains int) (changeset.DeployedEnv, changeset.CCIPOnChainState, []uint64) { - e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), big.NewInt(9e8), nil) - - state, err := changeset.LoadOnchainState(e.Env) - require.NoError(t, err) - - allChainSelectors := maps.Keys(e.Env.Chains) - require.Len(t, allChainSelectors, numChains) - return e, state, allChainSelectors - } - - t.Run("boost needed due to WETH price increase (also covering gas price inscrease)", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, - priceFeedPrices: priceFeedPrices{ - linkPrice: deployment.E18Mult(5), - wethPrice: big.NewInt(9.9e8), // increase from 9e8 to 9.9e8 - }, - sourceChain: chains[0], - destChain: chains[1], - }) - }) - - t.Run("boost needed due to LINK price decrease", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, - priceFeedPrices: priceFeedPrices{ - linkPrice: big.NewInt(4.5e18), // decrease from 5e18 to 4.5e18 - wethPrice: big.NewInt(9e8), - }, - sourceChain: chains[0], - destChain: chains[1], - }) - }) -} - -func runFeeboostTestCase(tc feeboostTestCase) { - require.NoError(tc.t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false)) - - startBlocks := make(map[uint64]*uint64) - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - msgSentEvent := changeset.TestSendRequest(tc.t, tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(tc.onchainState.Chains[tc.destChain].Receiver.Address().Bytes(), 32), - Data: []byte("message that needs fee boosting"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, - }] = []uint64{msgSentEvent.SequenceNumber} - - // hack - time.Sleep(30 * time.Second) - replayBlocks := make(map[uint64]uint64) - replayBlocks[tc.sourceChain] = 1 - replayBlocks[tc.destChain] = 1 - changeset.ReplayLogs(tc.t, tc.deployedEnv.Env.Offchain, replayBlocks) - - changeset.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) - changeset.ConfirmExecWithSeqNrsForAll(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNumExec, startBlocks) -} diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 41650f33050..a2c680ee814 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -15,7 +15,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" @@ -224,7 +223,13 @@ func NewLocalDevEnvironment( for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock tokenInfo := tokenConfig.GetTokenInfo(env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams[chain] = changeset.DefaultOCRParams(feedSel, tokenInfo) + + params := changeset.DefaultOCRParams(feedSel, tokenInfo) + if tCfg.OCRConfigOverride != nil { + params = tCfg.OCRConfigOverride(params) + } + + ocrParams[chain] = params } // Deploy second set of changesets to deploy and configure the CCIP contracts. env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{