From 70026d5316ec2bd9fb28c1a4bf1b49ad50d851de Mon Sep 17 00:00:00 2001 From: chris-de-leon-cll <147140544+chris-de-leon-cll@users.noreply.github.com> Date: Thu, 28 May 2026 11:29:33 -0700 Subject: [PATCH 1/3] feat: allow finality config to be set via DeployTokenPoolInput --- .../sequences/tokens/deploy_token_pool.go | 56 ++++++++++++++----- ...allowed_finality_config_for_token_pools.go | 41 +++++++++----- .../tokens/configure_tokens_for_transfers.go | 6 +- deployment/tokens/token_expansion.go | 5 ++ .../deployment/token_pool_rate_limits_test.go | 35 ++++++++++++ 5 files changed, 112 insertions(+), 31 deletions(-) diff --git a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go index 20f816aa20..d151fb6215 100644 --- a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go +++ b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go @@ -13,6 +13,7 @@ import ( adaptersV1_0_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/adapters" "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/operations/token_pool" + "github.com/smartcontractkit/chainlink-ccip/deployment/finality" "github.com/smartcontractkit/chainlink-ccip/deployment/tokens" "github.com/smartcontractkit/chainlink-ccip/deployment/utils" datastore_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils/datastore" @@ -134,14 +135,29 @@ var DeployTokenPool = cldf_ops.NewSequence( // ConfigureTokenPool reads current values and only emits a write when they // differ so reruns with the same inputs are no-ops. Fields that the caller // leaves unset (zero/empty) retain their current on-chain values. + output := sequences.OnChainOutput{Addresses: matches} if report, err := cldf_ops.ExecuteSequence(b, ConfigureTokenPool, chain, configureInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to reconcile dynamic config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) } else { - return sequences.OnChainOutput{ - Addresses: append(matches, report.Output.Addresses...), - BatchOps: report.Output.BatchOps, - }, nil + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = report.Output.BatchOps } + + // Set the allowed finality config (if applicable) - this does not produce a + // batch if the current on-chain config already matches the requested config + if !input.AllowedFinalityConfig.IsZero() { + if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ + Settings: map[string]finality.Config{tokenPoolAddress.Hex(): input.AllowedFinalityConfig}, + Selector: chain.Selector, + }); err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) + } else { + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) + } + } + + return output, nil } // Infer pool deployment inputs @@ -210,29 +226,41 @@ var DeployTokenPool = cldf_ops.NewSequence( } // Deploy the desired pool contract + output := sequences.OnChainOutput{Addresses: matches} switch { case poolutil.IsLockReleasePoolType(tokenPoolType.String()): if report, err := cldf_ops.ExecuteSequence(b, DeployLockReleaseTokenPool, chain, internalInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to deploy lock release token pool on chain %d: %w", chain.Selector, err) } else { - return sequences.OnChainOutput{ - Addresses: append(matches, report.Output.Addresses...), - BatchOps: report.Output.BatchOps, - }, nil + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) } - case poolutil.IsBurnMintPoolType(tokenPoolType.String()): if report, err := cldf_ops.ExecuteSequence(b, DeployBurnMintTokenPool, chain, internalInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to deploy burn mint token pool on chain %d: %w", chain.Selector, err) } else { - return sequences.OnChainOutput{ - Addresses: append(matches, report.Output.Addresses...), - BatchOps: report.Output.BatchOps, - }, nil + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) } - default: return sequences.OnChainOutput{}, fmt.Errorf("unsupported token pool type '%s' for chain with selector %d", input.PoolType, chain.Selector) } + + // Configure the finality config (if applicable) + if !input.AllowedFinalityConfig.IsZero() && len(output.Addresses) > 0 { + poolRef := output.Addresses[0] + if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ + Settings: map[string]finality.Config{poolRef.Address: input.AllowedFinalityConfig}, + Selector: chain.Selector, + }); err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for token pool (%s) on chain %d: %w", datastore_utils.SprintRef(poolRef), chain.Selector, err) + } else { + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) + } + } + + // Return the deployment output + return output, nil }, ) diff --git a/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go b/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go index 244a2e89de..c6163a437c 100644 --- a/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go +++ b/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go @@ -1,6 +1,7 @@ package tokens import ( + "bytes" "fmt" "github.com/ethereum/go-ethereum/common" @@ -28,32 +29,44 @@ var SetAllowedFinalityConfigForTokenPools = operations.NewSequence( writes := make([]contract.WriteOutput, 0, len(input.Settings)) for pool, finalityConfig := range input.Settings { if err := finalityConfig.Validate(); err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("invalid finality config for pool %s on src %d: %w", pool, chain.Selector, err) + return sequences.OnChainOutput{}, fmt.Errorf("invalid finality config for pool %s: %w", pool, err) } - src := chain.Selector + selector := chain.Selector if !common.IsHexAddress(pool) { - return sequences.OnChainOutput{}, fmt.Errorf("invalid pool address for src %d: %s", src, pool) + return sequences.OnChainOutput{}, fmt.Errorf("invalid pool address for src %d: %s", selector, pool) } addr := common.HexToAddress(pool) if addr == (common.Address{}) { - return sequences.OnChainOutput{}, fmt.Errorf("pool address cannot be the zero address for src %d", src) + return sequences.OnChainOutput{}, fmt.Errorf("pool address cannot be the zero address for src %d", selector) } - report, err := operations.ExecuteOperation( - b, token_pool.SetAllowedFinalityConfig, chain, - contract.FunctionInput[[4]byte]{ - ChainSelector: src, - Address: addr, - Args: finalityConfig.Raw(), - }, - ) + currentFinalityConfigReport, err := operations.ExecuteOperation(b, token_pool.GetAllowedFinalityConfig, chain, contract.FunctionInput[struct{}]{ + ChainSelector: selector, + Address: addr, + Args: struct{}{}, + }) if err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("failed to execute token_pool.SetAllowedFinalityConfig for pool %s on src %d: %w", pool, src, err) + return sequences.OnChainOutput{}, fmt.Errorf("failed to get current finality config for token pool at address %s on chain %d: %w", addr.Hex(), selector, err) } - writes = append(writes, report.Output) + requestedFinalityConfig := finalityConfig.Raw() + if !bytes.Equal(currentFinalityConfigReport.Output[:], requestedFinalityConfig[:]) { + write, err := operations.ExecuteOperation(b, token_pool.SetAllowedFinalityConfig, chain, contract.FunctionInput[[4]byte]{ + ChainSelector: selector, + Address: addr, + Args: requestedFinalityConfig, + }) + if err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("failed to set finality config for token pool at address %s on chain %d: %w", addr.Hex(), selector, err) + } + writes = append(writes, write.Output) + } + } + + if len(writes) == 0 { + return sequences.OnChainOutput{}, nil } batch, err := contract.NewBatchOperationFromWrites(writes) diff --git a/deployment/tokens/configure_tokens_for_transfers.go b/deployment/tokens/configure_tokens_for_transfers.go index 656978aee8..81a4678f50 100644 --- a/deployment/tokens/configure_tokens_for_transfers.go +++ b/deployment/tokens/configure_tokens_for_transfers.go @@ -39,9 +39,9 @@ type TokenTransferConfig struct { RegistryRef datastore.AddressRef `yaml:"registryRef" json:"registryRef"` // RemoteChains specifies the remote chains to configure on the token pool. RemoteChains map[uint64]RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef] `yaml:"remoteChains" json:"remoteChains"` - // AllowedFinalityConfig, if set, specifies the allowed finality configurations to set on the token pool. If this is unspecified, then one of - // two things will happen. If this is a new pool, then the onchain code will use a sensible default (e.g. WAIT_FOR_FINALITY). Otherwise, this - // config will be left as-is, meaning that the existing allowed finality config on the pool remains in place. + // AllowedFinalityConfig specifies the finality config to set on the token pool. If this is + // the zero value, then the finality config will remain unchanged on-chain. Pre-v2 pools will + // ignore this parameter as it is not supported on those versions. AllowedFinalityConfig finality.Config `yaml:"allowedFinalityConfig" json:"allowedFinalityConfig"` // LiquidityMigrationAmount, if set, specifies an exact token amount to migrate from the old pool (read from the // TokenAdminRegistry) to the new pool's lockbox. Mutually exclusive with LiquidityMigrationBasisPoints. diff --git a/deployment/tokens/token_expansion.go b/deployment/tokens/token_expansion.go index 447d9636bc..a1900640e2 100644 --- a/deployment/tokens/token_expansion.go +++ b/deployment/tokens/token_expansion.go @@ -10,6 +10,7 @@ import ( mcms_types "github.com/smartcontractkit/mcms/types" ccipdeploy "github.com/smartcontractkit/chainlink-ccip/deployment/deploy" + "github.com/smartcontractkit/chainlink-ccip/deployment/finality" "github.com/smartcontractkit/chainlink-ccip/deployment/utils/changesets" datastore_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils/datastore" "github.com/smartcontractkit/chainlink-ccip/deployment/utils/mcms" @@ -97,6 +98,10 @@ type DeployTokenPoolInput struct { PoolType string `yaml:"poolType" json:"poolType"` TokenPoolVersion *semver.Version `yaml:"tokenPoolVersion" json:"tokenPoolVersion"` Allowlist []string `yaml:"allowlist" json:"allowlist"` + // AllowedFinalityConfig specifies the finality config to set on the token pool. If this is + // the zero value, then the finality config will remain unchanged on-chain. Pre-v2 pools will + // ignore this parameter as it is not supported on those versions. + AllowedFinalityConfig finality.Config `yaml:"allowedFinalityConfig" json:"allowedFinalityConfig"` // RateLimitAdmin specifies the rate limit admin for the token pool. This field is optional. // For Solana: If empty, DeployTokenPoolForToken sets the timelock signer PDA from the datastore; // if non-empty, sets this base58 pubkey. On EVM, empty leaves the pool default (unchanged from contract deploy). diff --git a/integration-tests/deployment/token_pool_rate_limits_test.go b/integration-tests/deployment/token_pool_rate_limits_test.go index 30520f414d..83f381419b 100644 --- a/integration-tests/deployment/token_pool_rate_limits_test.go +++ b/integration-tests/deployment/token_pool_rate_limits_test.go @@ -16,6 +16,7 @@ import ( bnmOpsV2_0_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v2_0_0/operations/burn_mint_token_pool" tokenpoolV1_6_1 "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_1/token_pool" tokenpoolV2_0_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v2_0_0/token_pool" + "github.com/smartcontractkit/chainlink-ccip/deployment/finality" tokensapi "github.com/smartcontractkit/chainlink-ccip/deployment/tokens" cciputils "github.com/smartcontractkit/chainlink-ccip/deployment/utils" datastore_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils/datastore" @@ -221,6 +222,10 @@ func TestTPRL_BasicSetTokenPoolRateLimitsV2(t *testing.T) { DeployTokenPoolInput: &tokensapi.DeployTokenPoolInput{ PoolType: string(bnmOpsV2_0_0.ContractType), TokenPoolQualifier: "", + AllowedFinalityConfig: finality.Config{ + WaitForFinality: true, + BlockDepth: 12, + }, }, TokenTransferConfig: &tokensapi.TokenTransferConfig{ RemoteChains: map[uint64]tokensapi.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{ @@ -242,6 +247,9 @@ func TestTPRL_BasicSetTokenPoolRateLimitsV2(t *testing.T) { DeployTokenPoolInput: &tokensapi.DeployTokenPoolInput{ PoolType: string(bnmOpsV2_0_0.ContractType), TokenPoolQualifier: "", + AllowedFinalityConfig: finality.Config{ + BlockDepth: 20, + }, }, TokenTransferConfig: &tokensapi.TokenTransferConfig{ RemoteChains: map[uint64]tokensapi.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{ @@ -263,6 +271,10 @@ func TestTPRL_BasicSetTokenPoolRateLimitsV2(t *testing.T) { require.NoError(t, err) require.NotEqual(t, poolA, poolB) + // Validate that the finality configs were correctly set for both pools + validateFinalityConfigV2_0_0(t, poolA, clientA, finality.Config{WaitForFinality: true, BlockDepth: 12}) + validateFinalityConfigV2_0_0(t, poolB, clientB, finality.Config{BlockDepth: 20}) + // Apply the rate limits _, err = tokensapi.SetTokenPoolRateLimits().Apply(*e, tokensapi.TPRLInput{ MCMS: mcms.Input{}, @@ -351,6 +363,9 @@ func TestTPRL_NoAccidentalOverwritesV2(t *testing.T) { DeployTokenPoolInput: &tokensapi.DeployTokenPoolInput{ PoolType: string(bnmOpsV2_0_0.ContractType), TokenPoolQualifier: "", + AllowedFinalityConfig: finality.Config{ + WaitForFinality: true, + }, }, TokenTransferConfig: &tokensapi.TokenTransferConfig{ RemoteChains: map[uint64]tokensapi.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{ @@ -372,6 +387,9 @@ func TestTPRL_NoAccidentalOverwritesV2(t *testing.T) { DeployTokenPoolInput: &tokensapi.DeployTokenPoolInput{ PoolType: string(bnmOpsV2_0_0.ContractType), TokenPoolQualifier: "", + AllowedFinalityConfig: finality.Config{ + WaitForSafe: true, + }, }, TokenTransferConfig: &tokensapi.TokenTransferConfig{ RemoteChains: map[uint64]tokensapi.RemoteChainConfig[*datastore.AddressRef, datastore.AddressRef]{ @@ -392,6 +410,10 @@ func TestTPRL_NoAccidentalOverwritesV2(t *testing.T) { poolB, err := datastore_utils.FindAndFormatRef(e.DataStore, fltrB, selB, evm_datastore_utils.ToEVMAddress) require.NoError(t, err) + // Validate that the finality configs were correctly set for both pools + validateFinalityConfigV2_0_0(t, poolA, clientA, finality.Config{WaitForFinality: true}) + validateFinalityConfigV2_0_0(t, poolB, clientB, finality.Config{WaitForSafe: true}) + // Set initial default rate limits initRateLimitAB := tokensapi.RateLimitConfig{RateLimit: tokensapi.RateLimiterConfigFloatInput{IsEnabled: true, Capacity: 1000, Rate: 100}, FastFinality: false} initRateLimitBA := tokensapi.RateLimitConfig{RateLimit: tokensapi.RateLimiterConfigFloatInput{IsEnabled: true, Capacity: 2000, Rate: 200}, FastFinality: false} @@ -566,6 +588,9 @@ func TestTPRL_AsymmetricPoolVersions(t *testing.T) { poolV2, err := datastore_utils.FindAndFormatRef(ev.DataStore, fltrV2, selV2, evm_datastore_utils.ToEVMAddress) require.NoError(t, err) + // If no AllowedFinalityConfig was provided, then the v2 token pool contract defaults to WaitForFinality=true + validateFinalityConfigV2_0_0(t, poolV2, clientV2, finality.Config{WaitForFinality: true}) + // Setting a FF and default rate limit on a pre-V2 token pool should only change the pool's // default rate limits. For the V2 pool, the FF and default rate limits should be updated. newRateLimitsTowardV2 := tokensapi.RemoteOutbounds{ @@ -799,6 +824,16 @@ func validateScaledTPRLBucket(t *testing.T, label string, localDecimals uint8, b RequireBigIntsEqual(t, inRate, bucket.InboundRateLimiterConfig.Rate, label+" inbound rate") } +func validateFinalityConfigV2_0_0(t *testing.T, address common.Address, backend bind.ContractBackend, expectedFinalityConfig finality.Config) { + t.Helper() + + tokenPool, err := tokenpoolV2_0_0.NewTokenPool(address, backend) + require.NoError(t, err) + actualFinalityConfigRaw, err := tokenPool.GetAllowedFinalityConfig(&bind.CallOpts{Context: t.Context()}) + require.NoError(t, err) + require.Equal(t, expectedFinalityConfig.Raw(), actualFinalityConfigRaw, "finality config mismatch") +} + func getRateLimits(t *testing.T, version *semver.Version, address common.Address, backend bind.ContractBackend, destSel uint64) (*tokensapi.TPRLRateLimitBucket, *tokensapi.TPRLRateLimitBucket) { t.Helper() From 5aeeb112927b315f5cb41e93c787fc1a2df531ab Mon Sep 17 00:00:00 2001 From: chris-de-leon-cll <147140544+chris-de-leon-cll@users.noreply.github.com> Date: Thu, 28 May 2026 13:15:57 -0700 Subject: [PATCH 2/3] refactor: treat finality config of zero as a no-op --- .../sequences/tokens/deploy_token_pool.go | 33 +++++++++---------- ...allowed_finality_config_for_token_pools.go | 6 +++- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go index d151fb6215..dc0b69044f 100644 --- a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go +++ b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go @@ -145,16 +145,14 @@ var DeployTokenPool = cldf_ops.NewSequence( // Set the allowed finality config (if applicable) - this does not produce a // batch if the current on-chain config already matches the requested config - if !input.AllowedFinalityConfig.IsZero() { - if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ - Settings: map[string]finality.Config{tokenPoolAddress.Hex(): input.AllowedFinalityConfig}, - Selector: chain.Selector, - }); err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) - } else { - output.Addresses = append(output.Addresses, report.Output.Addresses...) - output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) - } + if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ + Settings: map[string]finality.Config{tokenPoolAddress.Hex(): input.AllowedFinalityConfig}, + Selector: chain.Selector, + }); err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) + } else { + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) } return output, nil @@ -226,28 +224,26 @@ var DeployTokenPool = cldf_ops.NewSequence( } // Deploy the desired pool contract - output := sequences.OnChainOutput{Addresses: matches} + output := sequences.OnChainOutput{} switch { case poolutil.IsLockReleasePoolType(tokenPoolType.String()): if report, err := cldf_ops.ExecuteSequence(b, DeployLockReleaseTokenPool, chain, internalInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to deploy lock release token pool on chain %d: %w", chain.Selector, err) } else { - output.Addresses = append(output.Addresses, report.Output.Addresses...) - output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) + output = report.Output } case poolutil.IsBurnMintPoolType(tokenPoolType.String()): if report, err := cldf_ops.ExecuteSequence(b, DeployBurnMintTokenPool, chain, internalInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to deploy burn mint token pool on chain %d: %w", chain.Selector, err) } else { - output.Addresses = append(output.Addresses, report.Output.Addresses...) - output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) + output = report.Output } default: return sequences.OnChainOutput{}, fmt.Errorf("unsupported token pool type '%s' for chain with selector %d", input.PoolType, chain.Selector) } // Configure the finality config (if applicable) - if !input.AllowedFinalityConfig.IsZero() && len(output.Addresses) > 0 { + if len(output.Addresses) > 0 { poolRef := output.Addresses[0] if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ Settings: map[string]finality.Config{poolRef.Address: input.AllowedFinalityConfig}, @@ -261,6 +257,9 @@ var DeployTokenPool = cldf_ops.NewSequence( } // Return the deployment output - return output, nil + return sequences.OnChainOutput{ + Addresses: append(matches, output.Addresses...), + BatchOps: output.BatchOps, + }, nil }, ) diff --git a/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go b/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go index c6163a437c..89d6c3b691 100644 --- a/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go +++ b/chains/evm/deployment/v2_0_0/sequences/tokens/set_allowed_finality_config_for_token_pools.go @@ -28,8 +28,12 @@ var SetAllowedFinalityConfigForTokenPools = operations.NewSequence( writes := make([]contract.WriteOutput, 0, len(input.Settings)) for pool, finalityConfig := range input.Settings { + if finalityConfig.IsZero() { + b.Logger.Warnf("skipping finality configuration for pool %s for src %d since finality config is zero", pool, input.Selector) + continue + } if err := finalityConfig.Validate(); err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("invalid finality config for pool %s: %w", pool, err) + return sequences.OnChainOutput{}, fmt.Errorf("invalid finality config for pool %s for src %d: %w", pool, input.Selector, err) } selector := chain.Selector From 484031a2ead4a8d628cc4851b0e5c8b5a8f10f49 Mon Sep 17 00:00:00 2001 From: chris-de-leon-cll <147140544+chris-de-leon-cll@users.noreply.github.com> Date: Thu, 28 May 2026 13:31:09 -0700 Subject: [PATCH 3/3] fix: early return bug prevents finality config from being set --- .../sequences/tokens/deploy_token_pool.go | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go index dc0b69044f..5448019ac9 100644 --- a/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go +++ b/chains/evm/deployment/v2_0_0/sequences/tokens/deploy_token_pool.go @@ -123,10 +123,23 @@ var DeployTokenPool = cldf_ops.NewSequence( } } + // Set the allowed finality config (if applicable) - this does not produce a + // batch if the current on-chain config already matches the requested config + output := sequences.OnChainOutput{Addresses: matches} + if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ + Settings: map[string]finality.Config{tokenPoolAddress.Hex(): input.AllowedFinalityConfig}, + Selector: chain.Selector, + }); err != nil { + return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) + } else { + output.Addresses = append(output.Addresses, report.Output.Addresses...) + output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) + } + // If the caller did not provide any dynamic config fields to update, then // skip the configure step and return early. if configureInput == (ConfigureTokenPoolInput{}) { - return sequences.OnChainOutput{Addresses: matches}, nil + return output, nil } else { configureInput.TokenPoolAddress = tokenPoolAddress configureInput.ChainSelector = chain.Selector @@ -135,21 +148,8 @@ var DeployTokenPool = cldf_ops.NewSequence( // ConfigureTokenPool reads current values and only emits a write when they // differ so reruns with the same inputs are no-ops. Fields that the caller // leaves unset (zero/empty) retain their current on-chain values. - output := sequences.OnChainOutput{Addresses: matches} if report, err := cldf_ops.ExecuteSequence(b, ConfigureTokenPool, chain, configureInput); err != nil { return sequences.OnChainOutput{}, fmt.Errorf("failed to reconcile dynamic config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) - } else { - output.Addresses = append(output.Addresses, report.Output.Addresses...) - output.BatchOps = report.Output.BatchOps - } - - // Set the allowed finality config (if applicable) - this does not produce a - // batch if the current on-chain config already matches the requested config - if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ - Settings: map[string]finality.Config{tokenPoolAddress.Hex(): input.AllowedFinalityConfig}, - Selector: chain.Selector, - }); err != nil { - return sequences.OnChainOutput{}, fmt.Errorf("failed to set allowed finality config for existing token pool %s on chain %d: %w", tokenPoolAddress, chain.Selector, err) } else { output.Addresses = append(output.Addresses, report.Output.Addresses...) output.BatchOps = append(output.BatchOps, report.Output.BatchOps...) @@ -244,6 +244,8 @@ var DeployTokenPool = cldf_ops.NewSequence( // Configure the finality config (if applicable) if len(output.Addresses) > 0 { + // NOTE: Addresses[0] is the deployed pool AddressRef by convention + // for both DeployLockReleaseTokenPool and DeployBurnMintTokenPool. poolRef := output.Addresses[0] if report, err := cldf_ops.ExecuteSequence(b, SetAllowedFinalityConfigForTokenPools, chains, tokens.SetAllowedFinalityConfigSequenceInput{ Settings: map[string]finality.Config{poolRef.Address: input.AllowedFinalityConfig},