Skip to content

Commit 134adea

Browse files
committed
Connect EVM with EVM: Add support for skipping FQ v2 config if already configured during migration
1 parent 9100268 commit 134adea

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

deployment/ccip/changeset/v1_6/cs_update_bidirectional_lanes.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/Masterminds/semver/v3"
99

10+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
1011
"github.com/ethereum/go-ethereum/common"
1112

1213
fqv2ops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/operations/fee_quoter"
@@ -308,6 +309,10 @@ func UpdateLanesLogic(e cldf.Environment, mcmsConfig *proposalutils.TimelockConf
308309
if err != nil {
309310
return cldf.ChangesetOutput{}, fmt.Errorf("failed to convert v1.6 fee quoter destination updates for chain %d: %w", chainSel, err)
310311
}
312+
destCfgs, err = FilterOutExistingDestChainConfigs(e, dests.Address, chainSel, destCfgs)
313+
if err != nil {
314+
return cldf.ChangesetOutput{}, err
315+
}
311316
fqUpdate.DestChainConfigs = destCfgs
312317
}
313318
if prices, ok := feeQuoterPricesInput.UpdatesByChain[chainSel]; ok {
@@ -349,6 +354,38 @@ func UpdateLanesLogic(e cldf.Environment, mcmsConfig *proposalutils.TimelockConf
349354
return output, nil
350355
}
351356

357+
// FilterOutExistingDestChainConfigs queries the on-chain v2 FeeQuoter and removes
358+
// destination chain configs that are already enabled. This prevents overwriting existing
359+
// configurations during lane updates when a destination was previously configured.
360+
func FilterOutExistingDestChainConfigs(
361+
e cldf.Environment,
362+
fqAddr common.Address,
363+
chainSel uint64,
364+
destCfgs []fqv2ops.DestChainConfigArgs,
365+
) ([]fqv2ops.DestChainConfigArgs, error) {
366+
fqContract, err := fqv2ops.NewFeeQuoterContract(fqAddr, e.BlockChains.EVMChains()[chainSel].Client)
367+
if err != nil {
368+
return nil, fmt.Errorf("failed to bind v2 FeeQuoter on chain %d: %w", chainSel, err)
369+
}
370+
filtered := make([]fqv2ops.DestChainConfigArgs, 0, len(destCfgs))
371+
for _, destCfg := range destCfgs {
372+
existing, err := fqContract.GetDestChainConfig(&bind.CallOpts{Context: e.GetContext()}, destCfg.DestChainSelector)
373+
if err != nil {
374+
return nil, fmt.Errorf("failed to query existing dest chain config on chain %d for dest %d: %w",
375+
chainSel, destCfg.DestChainSelector, err)
376+
}
377+
if existing.IsEnabled {
378+
e.Logger.Infow("skipping dest chain config already present on v2 FeeQuoter",
379+
"sourceChain", chainSel,
380+
"destChain", destCfg.DestChainSelector,
381+
)
382+
continue
383+
}
384+
filtered = append(filtered, destCfg)
385+
}
386+
return filtered, nil
387+
}
388+
352389
func ConvertV16FeeQuoterDestUpdatesToV2(in []fee_quoter.FeeQuoterDestChainConfigArgs) ([]fqv2ops.DestChainConfigArgs, error) {
353390
out := make([]fqv2ops.DestChainConfigArgs, 0, len(in))
354391
for _, cfg := range in {

deployment/ccip/changeset/v1_6/cs_update_bidirectional_lanes_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,89 @@ func TestUpdateBidirectionalLanesChangesetWithV2FeeQuoter(t *testing.T) {
543543
}
544544
}
545545

546+
func TestFilterOutExistingDestChainConfigs(t *testing.T) {
547+
t.Parallel()
548+
549+
deployedEnvironment, _ := testhelpers.NewMemoryEnvironment(t, func(testCfg *testhelpers.TestConfigs) {
550+
testCfg.Chains = 2
551+
})
552+
e := deployedEnvironment.Env
553+
554+
state, err := stateview.LoadOnchainState(e)
555+
require.NoError(t, err, "must load onchain state")
556+
557+
selectors := e.BlockChains.ListChainSelectors(cldf_chain.WithFamily(chain_selectors.FamilyEVM))
558+
require.Len(t, selectors, 2, "must have 2 chains")
559+
560+
chainSel := selectors[0]
561+
otherChainSel := selectors[1]
562+
evmChain := e.BlockChains.EVMChains()[chainSel]
563+
564+
parsedABI, err := abi.JSON(strings.NewReader(fqv2ops.FeeQuoterABI))
565+
require.NoError(t, err, "must parse v2 FeeQuoter ABI")
566+
567+
// Deploy a v2 FeeQuoter, then configure one destination (otherChainSel) after deployment.
568+
// Constructor-time dest config with sparse fields reverts, so we apply it post-deploy.
569+
fqV2Addr, tx, _, err := bind.DeployContract(
570+
evmChain.DeployerKey,
571+
parsedABI,
572+
common.FromHex(fqv2ops.FeeQuoterBin),
573+
evmChain.Client,
574+
fqv2ops.StaticConfig{
575+
MaxFeeJuelsPerMsg: big.NewInt(1e18),
576+
LinkToken: state.Chains[chainSel].LinkToken.Address(),
577+
},
578+
[]common.Address{evmChain.DeployerKey.From},
579+
[]fqv2ops.TokenTransferFeeConfigArgs{},
580+
[]fqv2ops.DestChainConfigArgs{},
581+
)
582+
require.NoError(t, err, "must deploy v2 FeeQuoter")
583+
584+
_, err = evmChain.Confirm(tx)
585+
require.NoError(t, err, "must confirm v2 FeeQuoter deployment")
586+
587+
fqV2, err := fqv2ops.NewFeeQuoterContract(fqV2Addr, evmChain.Client)
588+
require.NoError(t, err, "must bind v2 FeeQuoter")
589+
590+
// Convert a default v1.6 config to v2 format and apply it so otherChainSel is already enabled
591+
v2Cfgs, err := v1_6.ConvertV16FeeQuoterDestUpdatesToV2([]fee_quoter.FeeQuoterDestChainConfigArgs{
592+
{
593+
DestChainSelector: otherChainSel,
594+
DestChainConfig: v1_6.DefaultFeeQuoterDestChainConfig(true),
595+
},
596+
})
597+
require.NoError(t, err, "must convert v1.6 dest config to v2")
598+
599+
applyTx, err := fqV2.ApplyDestChainConfigUpdates(evmChain.DeployerKey, v2Cfgs)
600+
require.NoError(t, err, "must apply dest chain config")
601+
_, err = evmChain.Confirm(applyTx)
602+
require.NoError(t, err, "must confirm dest chain config tx")
603+
604+
unconfiguredChainSel := uint64(999)
605+
606+
// Call FilterOutExistingDestChainConfigs with both destinations
607+
input := []fqv2ops.DestChainConfigArgs{
608+
{
609+
DestChainSelector: otherChainSel,
610+
DestChainConfig: fqv2ops.DestChainConfig{IsEnabled: true, MaxDataBytes: 50_000},
611+
},
612+
{
613+
DestChainSelector: unconfiguredChainSel,
614+
DestChainConfig: fqv2ops.DestChainConfig{IsEnabled: true, MaxDataBytes: 60_000},
615+
},
616+
}
617+
618+
filtered, err := v1_6.FilterOutExistingDestChainConfigs(e, fqV2Addr, chainSel, input)
619+
require.NoError(t, err, "FilterOutExistingDestChainConfigs must not error")
620+
621+
// Only the unconfigured chain should remain
622+
require.Len(t, filtered, 1, "must filter out the already-enabled destination")
623+
assert.Equal(t, unconfiguredChainSel, filtered[0].DestChainSelector,
624+
"remaining entry must be the unconfigured chain")
625+
assert.Equal(t, uint32(60_000), filtered[0].DestChainConfig.MaxDataBytes,
626+
"remaining entry must preserve original config")
627+
}
628+
546629
func TestUpdateBidirectionalLanesChangesetWithV2FeeQuoterWithMCMS(t *testing.T) {
547630
t.Parallel()
548631

0 commit comments

Comments
 (0)