Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -122,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
Expand All @@ -137,11 +151,11 @@ var DeployTokenPool = cldf_ops.NewSequence(
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 = append(output.BatchOps, report.Output.BatchOps...)
}

return output, nil
}

// Infer pool deployment inputs
Expand Down Expand Up @@ -210,29 +224,44 @@ var DeployTokenPool = cldf_ops.NewSequence(
}

// Deploy the desired pool contract
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 {
return sequences.OnChainOutput{
Addresses: append(matches, report.Output.Addresses...),
BatchOps: report.Output.BatchOps,
}, nil
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 {
return sequences.OnChainOutput{
Addresses: append(matches, report.Output.Addresses...),
BatchOps: report.Output.BatchOps,
}, nil
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 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},
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 sequences.OnChainOutput{
Addresses: append(matches, output.Addresses...),
BatchOps: output.BatchOps,
}, nil
},
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tokens

import (
"bytes"
"fmt"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -27,33 +28,49 @@ 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 on src %d: %w", pool, chain.Selector, err)
return sequences.OnChainOutput{}, fmt.Errorf("invalid finality config for pool %s for src %d: %w", pool, input.Selector, 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)
}

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)
}
}

writes = append(writes, report.Output)
if len(writes) == 0 {
return sequences.OnChainOutput{}, nil
}

batch, err := contract.NewBatchOperationFromWrites(writes)
Expand Down
6 changes: 3 additions & 3 deletions deployment/tokens/configure_tokens_for_transfers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Comment thread
chris-de-leon-cll marked this conversation as resolved.
// 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.
Expand Down
5 changes: 5 additions & 0 deletions deployment/tokens/token_expansion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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).
Expand Down
35 changes: 35 additions & 0 deletions integration-tests/deployment/token_pool_rate_limits_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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]{
Expand All @@ -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]{
Expand All @@ -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{},
Expand Down Expand Up @@ -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]{
Expand All @@ -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]{
Expand All @@ -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}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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()

Expand Down
Loading