diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index f96328953ec..566d7dd05a2 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -111,5 +111,13 @@ runner-test-matrix: - PR Integration CCIP Tests test_cmd: cd integration-tests/ && go test smoke/ccip/ccip_cs_rmn_curse_uncurse_test.go -timeout 10m -test.parallel=1 -count=1 -json + - id: smoke/ccip/ccip_disable_lane_test.go:* + path: integration-tests/smoke/ccip/ccip_disable_lane_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_disable_lane_test.go -timeout 10m -test.parallel=1 -count=1 -json + # END: CCIP tests diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go index a3a0505b950..2d882ddddda 100644 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -76,7 +76,7 @@ func Test_ActiveCandidate(t *testing.T) { Config: changeset.UpdateFeeQuoterDestsConfig{ UpdatesByChain: map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig{ source: { - dest: changeset.DefaultFeeQuoterDestChainConfig(), + dest: changeset.DefaultFeeQuoterDestChainConfig(true), }, }, }, diff --git a/deployment/ccip/changeset/cs_chain_contracts.go b/deployment/ccip/changeset/cs_chain_contracts.go index 201f724eea5..57d3b980254 100644 --- a/deployment/ccip/changeset/cs_chain_contracts.go +++ b/deployment/ccip/changeset/cs_chain_contracts.go @@ -706,12 +706,10 @@ func UpdateOffRampSourcesChangeset(e deployment.Environment, cfg UpdateOffRampSo var args []offramp.OffRampSourceChainConfigArgs for source, update := range updates { router := common.HexToAddress("0x0") - if update.IsEnabled { - if update.TestRouter { - router = s.Chains[chainSel].TestRouter.Address() - } else { - router = s.Chains[chainSel].Router.Address() - } + if update.TestRouter { + router = s.Chains[chainSel].TestRouter.Address() + } else { + router = s.Chains[chainSel].Router.Address() } onRamp := s.Chains[source].OnRamp args = append(args, offramp.OffRampSourceChainConfigArgs{ @@ -1115,7 +1113,9 @@ func isOCR3ConfigSetOnOffRamp( return true, nil } -func DefaultFeeQuoterDestChainConfig() fee_quoter.FeeQuoterDestChainConfig { +// DefaultFeeQuoterDestChainConfig returns the default FeeQuoterDestChainConfig +// with the config enabled/disabled based on the configEnabled flag. +func DefaultFeeQuoterDestChainConfig(configEnabled bool) fee_quoter.FeeQuoterDestChainConfig { // https://github.com/smartcontractkit/ccip/blob/c4856b64bd766f1ddbaf5d13b42d3c4b12efde3a/contracts/src/v0.8/ccip/libraries/Internal.sol#L337-L337 /* ```Solidity @@ -1125,7 +1125,7 @@ func DefaultFeeQuoterDestChainConfig() fee_quoter.FeeQuoterDestChainConfig { */ evmFamilySelector, _ := hex.DecodeString("2812d52c") return fee_quoter.FeeQuoterDestChainConfig{ - IsEnabled: true, + IsEnabled: configEnabled, MaxNumberOfTokensPerMsg: 10, MaxDataBytes: 256, MaxPerMsgGasLimit: 3_000_000, diff --git a/deployment/ccip/changeset/cs_chain_contracts_test.go b/deployment/ccip/changeset/cs_chain_contracts_test.go index 7b7f420e531..c50e4d5ee53 100644 --- a/deployment/ccip/changeset/cs_chain_contracts_test.go +++ b/deployment/ccip/changeset/cs_chain_contracts_test.go @@ -198,8 +198,8 @@ func TestUpdateFQDests(t *testing.T) { } } - fqCfg1 := changeset.DefaultFeeQuoterDestChainConfig() - fqCfg2 := changeset.DefaultFeeQuoterDestChainConfig() + fqCfg1 := changeset.DefaultFeeQuoterDestChainConfig(true) + fqCfg2 := changeset.DefaultFeeQuoterDestChainConfig(true) fqCfg2.DestGasOverhead = 1000 _, err = commonchangeset.ApplyChangesets(t, tenv.Env, tenv.TimelockContracts(t), []commonchangeset.ChangesetApplication{ { diff --git a/deployment/ccip/changeset/testhelpers/test_helpers.go b/deployment/ccip/changeset/testhelpers/test_helpers.go index 4edfc5ce4c3..7a85a23c11e 100644 --- a/deployment/ccip/changeset/testhelpers/test_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_helpers.go @@ -482,6 +482,52 @@ func AddLane( require.NoError(t, err) } +// RemoveLane removes a lane between the source and destination chains in the deployed environment. +func RemoveLane(t *testing.T, e *DeployedEnv, src, dest uint64, isTestRouter bool) { + var err error + apps := []commoncs.ChangesetApplication{ + { + Changeset: commoncs.WrapChangeSet(changeset.UpdateRouterRampsChangeset), + Config: changeset.UpdateRouterRampsConfig{ + UpdatesByChain: map[uint64]changeset.RouterUpdates{ + // onRamp update on source chain + src: { + OnRampUpdates: map[uint64]bool{ + dest: false, + }, + }, + }, + }, + }, + { + Changeset: commoncs.WrapChangeSet(changeset.UpdateFeeQuoterDestsChangeset), + Config: changeset.UpdateFeeQuoterDestsConfig{ + UpdatesByChain: map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig{ + src: { + dest: changeset.DefaultFeeQuoterDestChainConfig(false), + }, + }, + }, + }, + { + Changeset: commoncs.WrapChangeSet(changeset.UpdateOnRampsDestsChangeset), + Config: changeset.UpdateOnRampDestsConfig{ + UpdatesByChain: map[uint64]map[uint64]changeset.OnRampDestinationUpdate{ + src: { + dest: { + IsEnabled: false, + TestRouter: isTestRouter, + AllowListEnabled: false, + }, + }, + }, + }, + }, + } + e.Env, err = commoncs.ApplyChangesets(t, e.Env, e.TimelockContracts(t), apps) + require.NoError(t, err) +} + func AddLaneWithDefaultPricesAndFeeQuoterConfig(t *testing.T, e *DeployedEnv, state changeset.CCIPOnChainState, from, to uint64, isTestRouter bool) { stateChainFrom := state.Chains[from] AddLane( @@ -494,7 +540,7 @@ func AddLaneWithDefaultPricesAndFeeQuoterConfig(t *testing.T, e *DeployedEnv, st }, map[common.Address]*big.Int{ stateChainFrom.LinkToken.Address(): DefaultLinkPrice, stateChainFrom.Weth9.Address(): DefaultWethPrice, - }, changeset.DefaultFeeQuoterDestChainConfig()) + }, changeset.DefaultFeeQuoterDestChainConfig(true)) } // AddLanesForAll adds densely connected lanes for all chains in the environment so that each chain diff --git a/deployment/environment/crib/ccip_deployer.go b/deployment/environment/crib/ccip_deployer.go index 96ff9071890..5c4913b799e 100644 --- a/deployment/environment/crib/ccip_deployer.go +++ b/deployment/environment/crib/ccip_deployer.go @@ -278,7 +278,7 @@ func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig de Config: changeset.UpdateFeeQuoterDestsConfig{ UpdatesByChain: map[uint64]map[uint64]fee_quoter.FeeQuoterDestChainConfig{ src: { - dst: changeset.DefaultFeeQuoterDestChainConfig(), + dst: changeset.DefaultFeeQuoterDestChainConfig(true), }, }, }, diff --git a/integration-tests/smoke/ccip/ccip_add_chain_test.go b/integration-tests/smoke/ccip/ccip_add_chain_test.go index 0d729b34371..4b634543462 100644 --- a/integration-tests/smoke/ccip/ccip_add_chain_test.go +++ b/integration-tests/smoke/ccip/ccip_add_chain_test.go @@ -667,7 +667,7 @@ func feeQuoterDestUpdates(t *testing.T, dests []uint64, sources []uint64) (updat if _, ok := updates[source]; !ok { updates[source] = make(map[uint64]fee_quoter.FeeQuoterDestChainConfig) } - updates[source][dest] = ccipcs.DefaultFeeQuoterDestChainConfig() + updates[source][dest] = ccipcs.DefaultFeeQuoterDestChainConfig(true) } } return diff --git a/integration-tests/smoke/ccip/ccip_disable_lane_test.go b/integration-tests/smoke/ccip/ccip_disable_lane_test.go new file mode 100644 index 00000000000..6622a6061e9 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_disable_lane_test.go @@ -0,0 +1,142 @@ +package ccip + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" +) + +// Intention of this test is to ensure that the lane can be disabled and enabled correctly +// without disrupting the other lanes and in-flight requests are delivered. +func TestDisableLane(t *testing.T) { + tenv, _, _ := testsetups.NewIntegrationEnvironment(t, + testhelpers.WithNumOfChains(3), + testhelpers.WithNumOfUsersPerChain(2), + ) + + e := tenv.Env + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + // add all lanes + testhelpers.AddLanesForAll(t, &tenv, state) + + var ( + chains = e.AllChainSelectors() + chainA, chainB, chainC = chains[0], chains[1], chains[2] + expectedSeqNumExec = make(map[testhelpers.SourceDestPair][]uint64) + startBlocks = make(map[uint64]*uint64) + pairs []testhelpers.SourceDestPair + linkPrice = deployment.E18Mult(100) + wethPrice = deployment.E18Mult(4000) + noOfRequests = 3 + sendmessage = func(src, dest uint64, deployer *bind.TransactOpts) (*onramp.OnRampCCIPMessageSent, error) { + return testhelpers.DoSendRequest( + t, + e, + state, + testhelpers.WithSender(deployer), + testhelpers.WithSourceChain(src), + testhelpers.WithDestChain(dest), + testhelpers.WithTestRouter(false), + testhelpers.WithEvm2AnyMessage(router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[chainB].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + })) + } + + assertSendRequestReverted = func(src, dest uint64, deployer *bind.TransactOpts) { + _, err = sendmessage(src, dest, deployer) + require.Error(t, err) + require.Contains(t, err.Error(), "execution reverted") + } + + assertRequestSent = func(src, dest uint64, deployer *bind.TransactOpts) { + latestHeader, err := e.Chains[dest].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latestHeader.Number.Uint64() + messageSentEvent, err := sendmessage(src, dest, e.Chains[src].DeployerKey) + require.NoError(t, err) + expectedSeqNumExec[testhelpers.SourceDestPair{ + SourceChainSelector: src, + DestChainSelector: dest, + }] = []uint64{messageSentEvent.SequenceNumber} + startBlocks[dest] = &block + } + ) + + // disable lane A -> B + pairs = append(pairs, testhelpers.SourceDestPair{ + SourceChainSelector: chainA, + DestChainSelector: chainB, + }) + testhelpers.RemoveLane(t, &tenv, chainA, chainB, false) + // send a message to confirm it is reverted between A -> B + assertSendRequestReverted(chainA, chainB, e.Chains[chainA].Users[0]) + + // send a message in other direction B -> A to confirm it is delivered + assertRequestSent(chainB, chainA, e.Chains[chainB].Users[0]) + testhelpers.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) + + // send a multiple message between A -> C and disable the lane while the requests are in-flight + expectedSeqNumExec = make(map[testhelpers.SourceDestPair][]uint64) + for range noOfRequests { + assertRequestSent(chainA, chainC, e.Chains[chainA].Users[1]) + } + // disable lane A -> C while requests are getting sent in that lane + pairs = append(pairs, testhelpers.SourceDestPair{ + SourceChainSelector: chainA, + DestChainSelector: chainC, + }) + testhelpers.RemoveLane(t, &tenv, chainA, chainC, false) + + // confirm all in-flight messages are delivered in A -> C lane + testhelpers.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) + + // now, as the lane is disabled, confirm that message sent in A -> C is reverted + assertSendRequestReverted(chainA, chainC, e.Chains[chainA].Users[0]) + + // check getting token and gas price form fee quoter returns error when A -> C lane is disabled + gp, err := state.Chains[chainA].FeeQuoter.GetTokenAndGasPrices(&bind.CallOpts{ + Context: tests.Context(t), + }, state.Chains[chainC].Weth9.Address(), chainC) + require.Error(t, err) + require.Contains(t, err.Error(), "execution reverted") + require.Nil(t, gp.GasPriceValue) + require.Nil(t, gp.TokenPrice) + + // re-enable all the disabled lanes + for _, pair := range pairs { + testhelpers.AddLane(t, &tenv, pair.SourceChainSelector, pair.DestChainSelector, false, + map[uint64]*big.Int{ + pair.DestChainSelector: testhelpers.DefaultGasPrice, + }, + map[common.Address]*big.Int{ + state.Chains[pair.SourceChainSelector].LinkToken.Address(): linkPrice, + state.Chains[pair.SourceChainSelector].Weth9.Address(): wethPrice, + }, + changeset.DefaultFeeQuoterDestChainConfig(true)) + } + // send a message in all the lane including re-enabled lanes + for _, pair := range pairs { + assertRequestSent(pair.SourceChainSelector, pair.DestChainSelector, e.Chains[pair.SourceChainSelector].Users[0]) + } + // confirm all messages are delivered + testhelpers.ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) +} diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go index dbe929fe322..6f4deea8b23 100644 --- a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -80,7 +80,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { ) t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) - feeQuoterCfg := changeset.DefaultFeeQuoterDestChainConfig() + feeQuoterCfg := changeset.DefaultFeeQuoterDestChainConfig(true) // the default adds 10% to the gas price, we want to increase it // to make sure the fee boosting will be finished in proper time for testing feeQuoterCfg.GasMultiplierWeiPerEth = 120e16 diff --git a/integration-tests/smoke/ccip/ccip_reader_test.go b/integration-tests/smoke/ccip/ccip_reader_test.go index 99ea0e10569..2e7cfc75d83 100644 --- a/integration-tests/smoke/ccip/ccip_reader_test.go +++ b/integration-tests/smoke/ccip/ccip_reader_test.go @@ -802,7 +802,7 @@ func Test_GetMedianDataAvailabilityGasConfig(t *testing.T) { boundContracts := map[cciptypes.ChainSelector][]types.BoundContract{} for i, selector := range env.Env.AllChainSelectorsExcluding([]uint64{destChain}) { feeQuoter := state.Chains[selector].FeeQuoter - destChainCfg := changeset.DefaultFeeQuoterDestChainConfig() + destChainCfg := changeset.DefaultFeeQuoterDestChainConfig(true) //nolint:gosec // disable G115 destChainCfg.DestDataAvailabilityOverheadGas = uint32(100 + i) //nolint:gosec // disable G115 diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 9d4a46c90f9..a16440cb166 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -721,3 +721,18 @@ func SetNodeConfig(nets []blockchain.EVMNetwork, nodeConfig, commonChain string, tomlStr, err := tomlCfg.TOMLString() return tomlCfg, tomlStr, err } + +func GetSourceDestPairs(chains []uint64) []testhelpers.SourceDestPair { + var pairs []testhelpers.SourceDestPair + for i, src := range chains { + for j, dest := range chains { + if i != j { + pairs = append(pairs, testhelpers.SourceDestPair{ + SourceChainSelector: src, + DestChainSelector: dest, + }) + } + } + } + return pairs +}