diff --git a/deployment/ccip/changeset/cs_ccip_home.go b/deployment/ccip/changeset/cs_ccip_home.go index 84d70d6f6f0..bef7293e408 100644 --- a/deployment/ccip/changeset/cs_ccip_home.go +++ b/deployment/ccip/changeset/cs_ccip_home.go @@ -127,13 +127,13 @@ func validateUSDCConfig(usdcConfig *pluginconfig.USDCCCTPObserverConfig, state C if !ok { return fmt.Errorf("chain %d does not exist in state but provided in USDCCCTPObserverConfig", sel) } - if onchainState.USDCTokenPool == nil { - return fmt.Errorf("chain %d does not have USDC token pool deployed", sel) + if onchainState.USDCTokenPools == nil || onchainState.USDCTokenPools[deployment.Version1_5_1] == nil { + return fmt.Errorf("chain %d does not have USDC token pool deployed with version %s", sel, deployment.Version1_5_1) } - if common.HexToAddress(token.SourcePoolAddress) != onchainState.USDCTokenPool.Address() { + if common.HexToAddress(token.SourcePoolAddress) != onchainState.USDCTokenPools[deployment.Version1_5_1].Address() { return fmt.Errorf("chain %d has USDC token pool deployed at %s, "+ "but SourcePoolAddress %s is provided in USDCCCTPObserverConfig", - sel, onchainState.USDCTokenPool.Address().String(), token.SourcePoolAddress) + sel, onchainState.USDCTokenPools[deployment.Version1_5_1].Address().String(), token.SourcePoolAddress) } } return nil diff --git a/deployment/ccip/changeset/cs_deploy_usdc_token_pools.go b/deployment/ccip/changeset/cs_deploy_usdc_token_pools.go new file mode 100644 index 00000000000..36c41a819a9 --- /dev/null +++ b/deployment/ccip/changeset/cs_deploy_usdc_token_pools.go @@ -0,0 +1,155 @@ +package changeset + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink-integrations/evm/utils" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/latest/mock_usdc_token_messenger" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_5_1/usdc_token_pool" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" +) + +var _ deployment.ChangeSet[DeployUSDCTokenPoolContractsConfig] = DeployUSDCTokenPoolContractsChangeset + +// DeployUSDCTokenPoolInput defines all information required of the user to deploy a new USDC token pool contract. +type DeployUSDCTokenPoolInput struct { + // TokenMessenger is the address of the USDC token messenger contract. + TokenMessenger common.Address + // USDCTokenAddress is the address of the USDC token for which we are deploying a token pool. + TokenAddress common.Address + // AllowList is the optional list of addresses permitted to initiate a token transfer. + // If omitted, all addresses will be permitted to transfer the token. + AllowList []common.Address +} + +func (i DeployUSDCTokenPoolInput) Validate(ctx context.Context, chain deployment.Chain, state CCIPChainState) error { + // Ensure that required fields are populated + if i.TokenAddress == utils.ZeroAddress { + return errors.New("token address must be defined") + } + if i.TokenMessenger == utils.ZeroAddress { + return errors.New("token messenger must be defined") + } + + // Validate the token exists and matches the USDC symbol + token, err := erc20.NewERC20(i.TokenAddress, chain.Client) + if err != nil { + return fmt.Errorf("failed to connect address %s with erc20 bindings: %w", i.TokenAddress, err) + } + symbol, err := token.Symbol(&bind.CallOpts{Context: ctx}) + if err != nil { + return fmt.Errorf("failed to fetch symbol from token with address %s: %w", i.TokenAddress, err) + } + if symbol != string(USDCSymbol) { + return fmt.Errorf("symbol of token with address %s (%s) is not USDC", i.TokenAddress, symbol) + } + + // Check if a USDC token pool with the given version already exists + fmt.Println(state.USDCTokenPools[deployment.Version1_5_1]) + if _, ok := state.USDCTokenPools[deployment.Version1_5_1]; ok { + return fmt.Errorf("USDC token pool with version %s already exists on %s", deployment.Version1_5_1, chain) + } + + // Perform USDC checks (i.e. make sure we can call the required functions) + // LocalMessageTransmitter and MessageBodyVersion are called in the contract constructor: + // https://github.com/smartcontractkit/chainlink/blob/f52a57762643b9cdc8e9241737e13501a4278716/contracts/src/v0.8/ccip/pools/USDC/USDCTokenPool.sol#L83 + messenger, err := mock_usdc_token_messenger.NewMockE2EUSDCTokenMessenger(i.TokenMessenger, chain.Client) + if err != nil { + return fmt.Errorf("failed to connect address %s on %s with token messenger bindings: %w", i.TokenMessenger, chain, err) + } + _, err = messenger.LocalMessageTransmitter(&bind.CallOpts{Context: ctx}) + if err != nil { + return fmt.Errorf("failed to fetch local message transmitter from address %s on %s: %w", i.TokenMessenger, chain, err) + } + _, err = messenger.MessageBodyVersion(&bind.CallOpts{Context: ctx}) + if err != nil { + return fmt.Errorf("failed to fetch message body version from address %s on %s: %w", i.TokenMessenger, chain, err) + } + + return nil +} + +// DeployUSDCTokenPoolContractsConfig defines the USDC token pool contracts that need to be deployed on each chain. +type DeployUSDCTokenPoolContractsConfig struct { + // USDCPools defines the per-chain configuration of each new USDC pool. + USDCPools map[uint64]DeployUSDCTokenPoolInput +} + +func (c DeployUSDCTokenPoolContractsConfig) Validate(env deployment.Environment) error { + state, err := LoadOnchainState(env) + if err != nil { + return fmt.Errorf("failed to load onchain state: %w", err) + } + for chainSelector, poolConfig := range c.USDCPools { + err := deployment.IsValidChainSelector(chainSelector) + if err != nil { + return fmt.Errorf("failed to validate chain selector %d: %w", chainSelector, err) + } + chain, ok := env.Chains[chainSelector] + if !ok { + return fmt.Errorf("chain with selector %d does not exist in environment", chainSelector) + } + chainState, ok := state.Chains[chainSelector] + if !ok { + return fmt.Errorf("chain with selector %d does not exist in state", chainSelector) + } + if chainState.Router == nil { + return fmt.Errorf("missing router on %s", chain) + } + if chainState.RMNProxy == nil { + return fmt.Errorf("missing rmnProxy on %s", chain) + } + err = poolConfig.Validate(env.GetContext(), chain, chainState) + if err != nil { + return fmt.Errorf("failed to validate USDC token pool config for chain selector %d: %w", chainSelector, err) + } + } + return nil +} + +// DeployUSDCTokenPoolContractsChangeset deploys new USDC pools across multiple chains. +func DeployUSDCTokenPoolContractsChangeset(env deployment.Environment, c DeployUSDCTokenPoolContractsConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(env); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployUSDCTokenPoolContractsConfig: %w", err) + } + newAddresses := deployment.NewMemoryAddressBook() + + state, err := LoadOnchainState(env) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + + for chainSelector, poolConfig := range c.USDCPools { + chain := env.Chains[chainSelector] + chainState := state.Chains[chainSelector] + + _, err := deployment.DeployContract(env.Logger, chain, newAddresses, + func(chain deployment.Chain) deployment.ContractDeploy[*usdc_token_pool.USDCTokenPool] { + poolAddress, tx, usdcTokenPool, err := usdc_token_pool.DeployUSDCTokenPool( + chain.DeployerKey, chain.Client, poolConfig.TokenMessenger, poolConfig.TokenAddress, + poolConfig.AllowList, chainState.RMNProxy.Address(), chainState.Router.Address(), + ) + return deployment.ContractDeploy[*usdc_token_pool.USDCTokenPool]{ + Address: poolAddress, + Contract: usdcTokenPool, + Tv: deployment.NewTypeAndVersion(USDCTokenPool, deployment.Version1_5_1), + Tx: tx, + Err: err, + } + }, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to deploy USDC token pool on %s: %w", chain, err) + } + } + + return deployment.ChangesetOutput{ + AddressBook: newAddresses, + }, nil +} diff --git a/deployment/ccip/changeset/cs_deploy_usdc_token_pools_test.go b/deployment/ccip/changeset/cs_deploy_usdc_token_pools_test.go new file mode 100644 index 00000000000..5477fe76d09 --- /dev/null +++ b/deployment/ccip/changeset/cs_deploy_usdc_token_pools_test.go @@ -0,0 +1,299 @@ +package changeset_test + +import ( + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink-integrations/evm/utils" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/latest/mock_usdc_token_messenger" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/latest/mock_usdc_token_transmitter" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func deployUSDCPrerequisites( + t *testing.T, + logger logger.Logger, + chain deployment.Chain, + addressBook deployment.AddressBook, +) (*deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677], *deployment.ContractDeploy[*mock_usdc_token_messenger.MockE2EUSDCTokenMessenger]) { + usdcToken, err := deployment.DeployContract(logger, chain, addressBook, + func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677] { + tokenAddress, tx, token, err := burn_mint_erc677.DeployBurnMintERC677( + chain.DeployerKey, + chain.Client, + "USDC", + "USDC", + 6, + big.NewInt(0).Mul(big.NewInt(1e9), big.NewInt(1e18)), + ) + return deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677]{ + Address: tokenAddress, + Contract: token, + Tv: deployment.NewTypeAndVersion(changeset.USDCTokenPool, deployment.Version1_5_1), + Tx: tx, + Err: err, + } + }, + ) + require.NoError(t, err) + + transmitter, err := deployment.DeployContract(logger, chain, addressBook, + func(chain deployment.Chain) deployment.ContractDeploy[*mock_usdc_token_transmitter.MockE2EUSDCTransmitter] { + transmitterAddress, tx, transmitter, err := mock_usdc_token_transmitter.DeployMockE2EUSDCTransmitter(chain.DeployerKey, chain.Client, 0, 1, usdcToken.Address) + return deployment.ContractDeploy[*mock_usdc_token_transmitter.MockE2EUSDCTransmitter]{ + Address: transmitterAddress, + Contract: transmitter, + Tv: deployment.NewTypeAndVersion(changeset.USDCMockTransmitter, deployment.Version1_0_0), + Tx: tx, + Err: err, + } + }, + ) + require.NoError(t, err) + + messenger, err := deployment.DeployContract(logger, chain, addressBook, + func(chain deployment.Chain) deployment.ContractDeploy[*mock_usdc_token_messenger.MockE2EUSDCTokenMessenger] { + messengerAddress, tx, messenger, err := mock_usdc_token_messenger.DeployMockE2EUSDCTokenMessenger(chain.DeployerKey, chain.Client, 0, transmitter.Address) + return deployment.ContractDeploy[*mock_usdc_token_messenger.MockE2EUSDCTokenMessenger]{ + Address: messengerAddress, + Contract: messenger, + Tv: deployment.NewTypeAndVersion(changeset.USDCTokenMessenger, deployment.Version1_0_0), + Tx: tx, + Err: err, + } + }, + ) + require.NoError(t, err) + + return usdcToken, messenger +} + +func TestValidateDeployUSDCTokenPoolContractsConfig(t *testing.T) { + t.Parallel() + + lggr := logger.TestLogger(t) + e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 2, + }) + + tests := []struct { + Msg string + Input changeset.DeployUSDCTokenPoolContractsConfig + ErrStr string + }{ + { + Msg: "Chain selector is not valid", + Input: changeset.DeployUSDCTokenPoolContractsConfig{ + USDCPools: map[uint64]changeset.DeployUSDCTokenPoolInput{ + 0: changeset.DeployUSDCTokenPoolInput{}, + }, + }, + ErrStr: "failed to validate chain selector 0", + }, + { + Msg: "Chain selector doesn't exist in environment", + Input: changeset.DeployUSDCTokenPoolContractsConfig{ + USDCPools: map[uint64]changeset.DeployUSDCTokenPoolInput{ + 5009297550715157269: changeset.DeployUSDCTokenPoolInput{}, + }, + }, + ErrStr: "does not exist in environment", + }, + { + Msg: "Missing router", + Input: changeset.DeployUSDCTokenPoolContractsConfig{ + USDCPools: map[uint64]changeset.DeployUSDCTokenPoolInput{ + e.AllChainSelectors()[0]: changeset.DeployUSDCTokenPoolInput{}, + }, + }, + ErrStr: "missing router", + }, + } + + for _, test := range tests { + t.Run(test.Msg, func(t *testing.T) { + err := test.Input.Validate(e) + require.Contains(t, err.Error(), test.ErrStr) + }) + } +} + +func TestValidateDeployUSDCTokenPoolInput(t *testing.T) { + t.Parallel() + + lggr := logger.TestLogger(t) + e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 2, + }) + selector := e.AllChainSelectors()[0] + chain := e.Chains[selector] + addressBook := deployment.NewMemoryAddressBook() + + usdcToken, tokenMessenger := deployUSDCPrerequisites(t, lggr, chain, addressBook) + + nonUsdcToken, err := deployment.DeployContract(e.Logger, chain, addressBook, + func(chain deployment.Chain) deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677] { + tokenAddress, tx, token, err := burn_mint_erc677.DeployBurnMintERC677( + chain.DeployerKey, + chain.Client, + "NOTUSDC", + "NOTUSDC", + 6, + big.NewInt(0).Mul(big.NewInt(1e9), big.NewInt(1e18)), + ) + return deployment.ContractDeploy[*burn_mint_erc677.BurnMintERC677]{ + Address: tokenAddress, + Contract: token, + Tv: deployment.NewTypeAndVersion(changeset.USDCTokenPool, deployment.Version1_5_1), + Tx: tx, + Err: err, + } + }, + ) + require.NoError(t, err) + + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + tests := []struct { + Msg string + Input changeset.DeployUSDCTokenPoolInput + ErrStr string + }{ + { + Msg: "Missing token address", + Input: changeset.DeployUSDCTokenPoolInput{}, + ErrStr: "token address must be defined", + }, + { + Msg: "Missing token messenger", + Input: changeset.DeployUSDCTokenPoolInput{ + TokenAddress: utils.RandomAddress(), + }, + ErrStr: "token messenger must be defined", + }, + { + Msg: "Can't reach token", + Input: changeset.DeployUSDCTokenPoolInput{ + TokenAddress: utils.RandomAddress(), + TokenMessenger: utils.RandomAddress(), + }, + ErrStr: "failed to fetch symbol from token", + }, + { + Msg: "Symbol is wrong", + Input: changeset.DeployUSDCTokenPoolInput{ + TokenAddress: nonUsdcToken.Address, + TokenMessenger: utils.RandomAddress(), + }, + ErrStr: "is not USDC", + }, + { + Msg: "Can't reach token messenger", + Input: changeset.DeployUSDCTokenPoolInput{ + TokenAddress: usdcToken.Address, + TokenMessenger: utils.RandomAddress(), + }, + ErrStr: "failed to fetch local message transmitter from address", + }, + { + Msg: "No error", + Input: changeset.DeployUSDCTokenPoolInput{ + TokenAddress: usdcToken.Address, + TokenMessenger: tokenMessenger.Address, + }, + ErrStr: "", + }, + } + + for _, test := range tests { + t.Run(test.Msg, func(t *testing.T) { + err := test.Input.Validate(e.GetContext(), chain, state.Chains[selector]) + if test.ErrStr != "" { + require.Contains(t, err.Error(), test.ErrStr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestDeployUSDCTokenPoolContracts(t *testing.T) { + t.Parallel() + + for _, numRuns := range []int{1, 2} { + t.Run(fmt.Sprintf("Run deployment %d time(s)", numRuns), func(t *testing.T) { + lggr := logger.TestLogger(t) + e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ + Chains: 2, + }) + selectors := e.AllChainSelectors() + + addressBook := deployment.NewMemoryAddressBook() + prereqCfg := make([]changeset.DeployPrerequisiteConfigPerChain, len(selectors)) + for i, selector := range selectors { + prereqCfg[i] = changeset.DeployPrerequisiteConfigPerChain{ + ChainSelector: selector, + } + } + + e, err := commoncs.Apply(t, e, nil, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset.DeployPrerequisitesChangeset), + changeset.DeployPrerequisiteConfig{ + Configs: prereqCfg, + }, + ), + ) + require.NoError(t, err) + + newUSDCTokenPools := make(map[uint64]changeset.DeployUSDCTokenPoolInput, len(selectors)) + for _, selector := range selectors { + usdcToken, tokenMessenger := deployUSDCPrerequisites(t, lggr, e.Chains[selector], addressBook) + + newUSDCTokenPools[selector] = changeset.DeployUSDCTokenPoolInput{ + TokenAddress: usdcToken.Address, + TokenMessenger: tokenMessenger.Address, + } + } + + for i := range numRuns { + e, err = commoncs.Apply(t, e, nil, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset.DeployUSDCTokenPoolContractsChangeset), + changeset.DeployUSDCTokenPoolContractsConfig{ + USDCPools: newUSDCTokenPools, + }, + ), + ) + + if i > 0 { + require.ErrorContains(t, err, "already exists") + } else { + require.NoError(t, err) + + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + for _, selector := range selectors { + usdcTokenPools := state.Chains[selector].USDCTokenPools + require.Len(t, usdcTokenPools, 1) + owner, err := usdcTokenPools[deployment.Version1_5_1].Owner(nil) + require.NoError(t, err) + require.Equal(t, e.Chains[selector].DeployerKey.From, owner) + } + } + } + }) + } +} diff --git a/deployment/ccip/changeset/cs_prerequisites.go b/deployment/ccip/changeset/cs_prerequisites.go index 84b49862686..82b3e20c58d 100644 --- a/deployment/ccip/changeset/cs_prerequisites.go +++ b/deployment/ccip/changeset/cs_prerequisites.go @@ -548,7 +548,7 @@ func deployUSDC( Address: tokenPoolAddress, Contract: tokenPoolContract, Tx: tx, - Tv: deployment.NewTypeAndVersion(USDCTokenPool, deployment.Version1_0_0), + Tv: deployment.NewTypeAndVersion(USDCTokenPool, deployment.Version1_5_1), Err: err2, } }) diff --git a/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains.go b/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains.go new file mode 100644 index 00000000000..7203d6043bb --- /dev/null +++ b/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains.go @@ -0,0 +1,154 @@ +package changeset + +import ( + "errors" + "fmt" + + "github.com/Masterminds/semver/v3" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_5_1/usdc_token_pool" +) + +var _ deployment.ChangeSet[SyncUSDCDomainsWithChainsConfig] = SyncUSDCDomainsWithChainsChangeset + +// SyncUSDCDomainsWithChainsConfig defines the chain selector -> USDC domain mappings. +type SyncUSDCDomainsWithChainsConfig struct { + // USDCVersionByChain defines the USDC domain and pool version for each chain selector. + USDCVersionByChain map[uint64]semver.Version + // ChainSelectorToUSDCDomain maps chains selectors to their USDC domain identifiers. + ChainSelectorToUSDCDomain map[uint64]uint32 + // MCMS defines the delay to use for Timelock (if absent, the changeset will attempt to use the deployer key). + MCMS *MCMSConfig +} + +func (c SyncUSDCDomainsWithChainsConfig) Validate(env deployment.Environment, state CCIPOnChainState) error { + ctx := env.GetContext() + + if c.ChainSelectorToUSDCDomain == nil { + return errors.New("chain selector to usdc domain must be defined") + } + + // Validate that all USDC configs inputted are for valid chains that define USDC pools. + for chainSelector, version := range c.USDCVersionByChain { + err := deployment.IsValidChainSelector(chainSelector) + if err != nil { + return fmt.Errorf("failed to validate chain selector %d: %w", chainSelector, err) + } + chain, ok := env.Chains[chainSelector] + if !ok { + return fmt.Errorf("chain with selector %d does not exist in environment", chainSelector) + } + chainState, ok := state.Chains[chainSelector] + if !ok { + return fmt.Errorf("chain with selector %d does not exist in state", chainSelector) + } + if chainState.USDCTokenPools == nil { + return fmt.Errorf("%s does not define any USDC token pools, config should be removed", chain) + } + if chainState.Timelock == nil { + return fmt.Errorf("missing timelock on %s", chain.String()) + } + if chainState.ProposerMcm == nil { + return fmt.Errorf("missing proposerMcm on %s", chain.String()) + } + usdcTokenPool, ok := chainState.USDCTokenPools[version] + if !ok { + return fmt.Errorf("no USDC token pool found on %s with version %s", chain, version) + } + if len(usdcTokenPool.Address().Bytes()) > 32 { + // Will never be true for EVM + return fmt.Errorf("expected USDC token pool address on %s (%s) to be less than 32 bytes", chain, usdcTokenPool.Address()) + } + // Validate that the USDC token pool is owned by the address that will be actioning the transactions (i.e. Timelock or deployer key) + if err := commoncs.ValidateOwnership(ctx, c.MCMS != nil, chain.DeployerKey.From, chainState.Timelock.Address(), usdcTokenPool); err != nil { + return fmt.Errorf("token pool with address %s on %s failed ownership validation: %w", usdcTokenPool.Address(), chain, err) + } + // Validate that each supported chain has a domain ID defined + supportedChains, err := usdcTokenPool.GetSupportedChains(&bind.CallOpts{Context: ctx}) + if err != nil { + return fmt.Errorf("failed to get supported chains from USDC token pool on %s with address %s: %w", chain, usdcTokenPool.Address(), err) + } + for _, supportedChain := range supportedChains { + if _, ok := c.ChainSelectorToUSDCDomain[supportedChain]; !ok { + return fmt.Errorf("no USDC domain ID defined for chain with selector %d", supportedChain) + } + } + } + // Check that our input covers all chains that define USDC pools. + for chainSelector, chainState := range state.Chains { + if _, ok := c.USDCVersionByChain[chainSelector]; !ok && chainState.USDCTokenPools != nil { + return fmt.Errorf("no USDC chain config defined for %s, which does support USDC", env.Chains[chainSelector]) + } + } + return nil +} + +// SyncUSDCDomainsWithChainsChangeset syncs domain support on specified USDC token pools with its chain support. +// As such, it is expected that ConfigureTokenPoolContractsChangeset is executed before running this changeset. +func SyncUSDCDomainsWithChainsChangeset(env deployment.Environment, c SyncUSDCDomainsWithChainsConfig) (deployment.ChangesetOutput, error) { + state, err := LoadOnchainState(env) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err) + } + if err := c.Validate(env, state); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid SyncUSDCDomainsWithChainsConfig: %w", err) + } + readOpts := &bind.CallOpts{Context: env.GetContext()} + + deployerGroup := NewDeployerGroup(env, state, c.MCMS).WithDeploymentContext("sync domain support with chain support on USDC token pools") + + for chainSelector, version := range c.USDCVersionByChain { + chain := env.Chains[chainSelector] + chainState := state.Chains[chainSelector] + writeOpts, err := deployerGroup.GetDeployer(chainSelector) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get transaction opts for %s", chain) + } + + usdcTokenPool := chainState.USDCTokenPools[version] + supportedChains, err := usdcTokenPool.GetSupportedChains(readOpts) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch supported chains from USDC token pool with address %s on %s: %w", usdcTokenPool.Address(), chain, err) + } + + domainUpdates := make([]usdc_token_pool.USDCTokenPoolDomainUpdate, 0) + for _, remoteChainSelector := range supportedChains { + remoteChainState := state.Chains[remoteChainSelector] + remoteUSDCVersion := c.USDCVersionByChain[remoteChainSelector] + remoteUSDCTokenPool := remoteChainState.USDCTokenPools[remoteUSDCVersion] + + var desiredAllowedCaller [32]byte + copy(desiredAllowedCaller[:], common.LeftPadBytes(remoteUSDCTokenPool.Address().Bytes(), 32)) + + desiredDomainIdentifier := c.ChainSelectorToUSDCDomain[remoteChainSelector] + + currentDomain, err := usdcTokenPool.GetDomain(readOpts, remoteChainSelector) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to fetch domain for %d from USDC token pool with address %s on %s: %w", remoteChainSelector, usdcTokenPool.Address(), chain, err) + } + // If any parameters are different, we need to add a setDomains call + if currentDomain.AllowedCaller != desiredAllowedCaller || + currentDomain.DomainIdentifier != desiredDomainIdentifier { + domainUpdates = append(domainUpdates, usdc_token_pool.USDCTokenPoolDomainUpdate{ + AllowedCaller: desiredAllowedCaller, + Enabled: true, + DomainIdentifier: desiredDomainIdentifier, + DestChainSelector: remoteChainSelector, + }) + } + } + + if len(domainUpdates) > 0 { + _, err := usdcTokenPool.SetDomains(writeOpts, domainUpdates) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to create set domains operation on %s: %w", chain, err) + } + } + } + + return deployerGroup.Enact() +} diff --git a/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains_test.go b/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains_test.go new file mode 100644 index 00000000000..53c589ec99b --- /dev/null +++ b/deployment/ccip/changeset/cs_sync_usdc_domains_with_chains_test.go @@ -0,0 +1,303 @@ +package changeset_test + +import ( + "testing" + "time" + + "github.com/Masterminds/semver/v3" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" +) + +func TestValidateSyncUSDCDomainsWithChainsConfig(t *testing.T) { + t.Parallel() + + tests := []struct { + Msg string + Input func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig + ErrStr string + DeployUSDC bool + }{ + { + Msg: "Domain mapping not defined", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{} + }, + ErrStr: "chain selector to usdc domain must be defined", + }, + { + Msg: "Chain selector is not valid", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + 0: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + ErrStr: "failed to validate chain selector 0", + }, + { + Msg: "Chain selector doesn't exist in environment", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + 5009297550715157269: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + ErrStr: "does not exist in environment", + }, + { + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + selector: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + ErrStr: "does not define any USDC token pools, config should be removed", + }, + { + Msg: "No USDC token pool found with version", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + selector: deployment.Version1_0_0, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + DeployUSDC: true, + ErrStr: "no USDC token pool found", + }, + { + Msg: "Not owned by expected owner", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + selector: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + MCMS: &changeset.MCMSConfig{MinDelay: 0 * time.Second}, + } + }, + DeployUSDC: true, + ErrStr: "failed ownership validation", + }, + { + Msg: "No domain ID found for selector", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{ + selector: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + DeployUSDC: true, + ErrStr: "no USDC domain ID defined for chain with selector", + }, + { + Msg: "Missing USDC in input", + Input: func(selector uint64) changeset.SyncUSDCDomainsWithChainsConfig { + return changeset.SyncUSDCDomainsWithChainsConfig{ + USDCVersionByChain: map[uint64]semver.Version{}, + ChainSelectorToUSDCDomain: map[uint64]uint32{}, + } + }, + DeployUSDC: true, + ErrStr: "which does support USDC", + }, + } + + for _, test := range tests { + t.Run(test.Msg, func(t *testing.T) { + deployedEnvironment, _ := testhelpers.NewMemoryEnvironment(t, func(testCfg *testhelpers.TestConfigs) { + testCfg.Chains = 2 + testCfg.PrerequisiteDeploymentOnly = true + testCfg.IsUSDC = test.DeployUSDC + }) + e := deployedEnvironment.Env + selectors := deployedEnvironment.Env.AllChainSelectors() + + if test.DeployUSDC { + var err error + e, err = commoncs.Apply(t, e, nil, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset.ConfigureTokenPoolContractsChangeset), + changeset.ConfigureTokenPoolContractsConfig{ + PoolUpdates: map[uint64]changeset.TokenPoolConfig{ + selectors[0]: changeset.TokenPoolConfig{ + ChainUpdates: changeset.RateLimiterPerChain{ + selectors[1]: testhelpers.CreateSymmetricRateLimits(0, 0), + }, + Type: changeset.USDCTokenPool, + Version: deployment.Version1_5_1, + }, + selectors[1]: changeset.TokenPoolConfig{ + ChainUpdates: changeset.RateLimiterPerChain{ + selectors[0]: testhelpers.CreateSymmetricRateLimits(0, 0), + }, + Type: changeset.USDCTokenPool, + Version: deployment.Version1_5_1, + }, + }, + TokenSymbol: "USDC", + }, + ), + ) + require.NoError(t, err) + } + + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + err = test.Input(selectors[0]).Validate(e, state) + require.Contains(t, err.Error(), test.ErrStr) + }) + } +} + +func TestSyncUSDCDomainsWithChainsChangeset(t *testing.T) { + t.Parallel() + + for _, mcmsConfig := range []*changeset.MCMSConfig{nil, &changeset.MCMSConfig{MinDelay: 0 * time.Second}} { + msg := "Sync domains without MCMS" + if mcmsConfig != nil { + msg = "Sync domains with MCMS" + } + + t.Run(msg, func(t *testing.T) { + deployedEnvironment, _ := testhelpers.NewMemoryEnvironment(t, func(testCfg *testhelpers.TestConfigs) { + testCfg.Chains = 2 + testCfg.PrerequisiteDeploymentOnly = true + testCfg.IsUSDC = true + }) + e := deployedEnvironment.Env + selectors := e.AllChainSelectors() + + state, err := changeset.LoadOnchainState(e) + require.NoError(t, err) + + timelockContracts := make(map[uint64]*proposalutils.TimelockExecutionContracts, len(selectors)) + timelockOwnedContractsByChain := make(map[uint64][]common.Address, 1) + for _, selector := range selectors { + // Assemble map of addresses required for Timelock scheduling & execution + timelockContracts[selector] = &proposalutils.TimelockExecutionContracts{ + Timelock: state.Chains[selector].Timelock, + CallProxy: state.Chains[selector].CallProxy, + } + // We would only need the token pool owned by timelock in these tests (if mcms config is provided) + timelockOwnedContractsByChain[selector] = []common.Address{state.Chains[selector].USDCTokenPools[deployment.Version1_5_1].Address()} + } + + if mcmsConfig != nil { + // Transfer ownership of token pools to timelock + e, err = commoncs.Apply(t, e, timelockContracts, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(commoncs.TransferToMCMSWithTimelockV2), + commoncs.TransferToMCMSWithTimelockConfig{ + ContractsByChain: timelockOwnedContractsByChain, + MinDelay: 0, + }, + ), + ) + require.NoError(t, err) + } + + e, err = commoncs.Apply(t, e, timelockContracts, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset.ConfigureTokenPoolContractsChangeset), + changeset.ConfigureTokenPoolContractsConfig{ + MCMS: mcmsConfig, + PoolUpdates: map[uint64]changeset.TokenPoolConfig{ + selectors[0]: changeset.TokenPoolConfig{ + ChainUpdates: changeset.RateLimiterPerChain{ + selectors[1]: testhelpers.CreateSymmetricRateLimits(0, 0), + }, + Type: changeset.USDCTokenPool, + Version: deployment.Version1_5_1, + }, + selectors[1]: changeset.TokenPoolConfig{ + ChainUpdates: changeset.RateLimiterPerChain{ + selectors[0]: testhelpers.CreateSymmetricRateLimits(0, 0), + }, + Type: changeset.USDCTokenPool, + Version: deployment.Version1_5_1, + }, + }, + TokenSymbol: "USDC", + }, + ), + ) + require.NoError(t, err) + + e, err = commoncs.Apply(t, e, timelockContracts, + commonchangeset.Configure( + deployment.CreateLegacyChangeSet(changeset.SyncUSDCDomainsWithChainsChangeset), + changeset.SyncUSDCDomainsWithChainsConfig{ + MCMS: mcmsConfig, + USDCVersionByChain: map[uint64]semver.Version{ + selectors[0]: deployment.Version1_5_1, + selectors[1]: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{ + selectors[0]: 1, + selectors[1]: 2, + }, + }, + ), + ) + require.NoError(t, err) + + state, err = changeset.LoadOnchainState(e) + require.NoError(t, err) + + for i, selector := range selectors { + remoteSelector := selectors[0] + if i == 0 { + remoteSelector = selectors[1] + } + remoteDomain := uint32(1) + if i == 0 { + remoteDomain = 2 + } + usdcTokenPool := state.Chains[selector].USDCTokenPools[deployment.Version1_5_1] + remoteUsdcTokenPool := state.Chains[remoteSelector].USDCTokenPools[deployment.Version1_5_1] + domain, err := usdcTokenPool.GetDomain(nil, remoteSelector) + allowedCaller := make([]byte, 32) + bytesCopied := copy(allowedCaller, domain.AllowedCaller[:]) + require.Equal(t, 32, bytesCopied) + require.NoError(t, err) + require.True(t, domain.Enabled) + require.Equal(t, remoteDomain, domain.DomainIdentifier) + require.Equal(t, remoteUsdcTokenPool.Address(), common.BytesToAddress(allowedCaller)) + } + + // Idempotency check + output, err := changeset.SyncUSDCDomainsWithChainsChangeset(e, changeset.SyncUSDCDomainsWithChainsConfig{ + MCMS: mcmsConfig, + USDCVersionByChain: map[uint64]semver.Version{ + selectors[0]: deployment.Version1_5_1, + selectors[1]: deployment.Version1_5_1, + }, + ChainSelectorToUSDCDomain: map[uint64]uint32{ + selectors[0]: 1, + selectors[1]: 2, + }, + }) + require.NoError(t, err) + require.Empty(t, output.Proposals) //nolint:staticcheck //SA1019 ignoring deprecated field for compatibility; we don't have tools to generate the new field + }) + } +} diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index a11d068bc75..eb5654bc5a1 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -98,16 +98,17 @@ var ( USDCMockTransmitter deployment.ContractType = "USDCMockTransmitter" // Pools - BurnMintToken deployment.ContractType = "BurnMintToken" - ERC20Token deployment.ContractType = "ERC20Token" - ERC677Token deployment.ContractType = "ERC677Token" - BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" - BurnWithFromMintTokenPool deployment.ContractType = "BurnWithFromMintTokenPool" - BurnFromMintTokenPool deployment.ContractType = "BurnFromMintTokenPool" - LockReleaseTokenPool deployment.ContractType = "LockReleaseTokenPool" - USDCToken deployment.ContractType = "USDCToken" - USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" - USDCTokenPool deployment.ContractType = "USDCTokenPool" + BurnMintToken deployment.ContractType = "BurnMintToken" + ERC20Token deployment.ContractType = "ERC20Token" + ERC677Token deployment.ContractType = "ERC677Token" + BurnMintTokenPool deployment.ContractType = "BurnMintTokenPool" + BurnWithFromMintTokenPool deployment.ContractType = "BurnWithFromMintTokenPool" + BurnFromMintTokenPool deployment.ContractType = "BurnFromMintTokenPool" + LockReleaseTokenPool deployment.ContractType = "LockReleaseTokenPool" + USDCToken deployment.ContractType = "USDCToken" + USDCTokenMessenger deployment.ContractType = "USDCTokenMessenger" + USDCTokenPool deployment.ContractType = "USDCTokenPool" + HybridLockReleaseUSDCTokenPool deployment.ContractType = "HybridLockReleaseUSDCTokenPool" ) // CCIPChainState holds a Go binding for all the currently deployed CCIP contracts @@ -135,6 +136,7 @@ type CCIPChainState struct { BurnMintTokenPools map[TokenSymbol]map[semver.Version]*burn_mint_token_pool.BurnMintTokenPool BurnWithFromMintTokenPools map[TokenSymbol]map[semver.Version]*burn_with_from_mint_token_pool.BurnWithFromMintTokenPool BurnFromMintTokenPools map[TokenSymbol]map[semver.Version]*burn_from_mint_token_pool.BurnFromMintTokenPool + USDCTokenPools map[semver.Version]*usdc_token_pool.USDCTokenPool LockReleaseTokenPools map[TokenSymbol]map[semver.Version]*lock_release_token_pool.LockReleaseTokenPool // Map between token Symbol (e.g. LinkSymbol, WethSymbol) // and the respective aggregator USD feed contract @@ -149,7 +151,6 @@ type CCIPChainState struct { Receiver maybe_revert_message_receiver.MaybeRevertMessageReceiverInterface LogMessageDataReceiver *log_message_data_receiver.LogMessageDataReceiver TestRouter *router.Router - USDCTokenPool *usdc_token_pool.USDCTokenPool MockUSDCTransmitter *mock_usdc_token_transmitter.MockE2EUSDCTransmitter MockUSDCTokenMessenger *mock_usdc_token_messenger.MockE2EUSDCTokenMessenger Multicall3 *multicall3.Multicall3 @@ -628,12 +629,24 @@ func LoadChainState(ctx context.Context, chain deployment.Chain, addresses map[s state.BurnMintTokens677 = map[TokenSymbol]*burn_mint_erc677.BurnMintERC677{ USDCSymbol: ut, } - case deployment.NewTypeAndVersion(USDCTokenPool, deployment.Version1_0_0).String(): + case deployment.NewTypeAndVersion(USDCTokenPool, deployment.Version1_5_1).String(): utp, err := usdc_token_pool.NewUSDCTokenPool(common.HexToAddress(address), chain.Client) if err != nil { return state, err } - state.USDCTokenPool = utp + if state.USDCTokenPools == nil { + state.USDCTokenPools = make(map[semver.Version]*usdc_token_pool.USDCTokenPool) + } + state.USDCTokenPools[deployment.Version1_5_1] = utp + case deployment.NewTypeAndVersion(HybridLockReleaseUSDCTokenPool, deployment.Version1_5_1).String(): + utp, err := usdc_token_pool.NewUSDCTokenPool(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + if state.USDCTokenPools == nil { + state.USDCTokenPools = make(map[semver.Version]*usdc_token_pool.USDCTokenPool) + } + state.USDCTokenPools[deployment.Version1_5_1] = utp case deployment.NewTypeAndVersion(USDCMockTransmitter, deployment.Version1_0_0).String(): umt, err := mock_usdc_token_transmitter.NewMockE2EUSDCTransmitter(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/ccip/changeset/testhelpers/test_environment.go b/deployment/ccip/changeset/testhelpers/test_environment.go index ba02ab5be80..2bcc8d3ddb4 100644 --- a/deployment/ccip/changeset/testhelpers/test_environment.go +++ b/deployment/ccip/changeset/testhelpers/test_environment.go @@ -603,9 +603,9 @@ func AddCCIPContractsToEnvironment(t *testing.T, allChains []uint64, tEnv TestEn for _, usdcChain := range evmChains { require.NotNil(t, state.Chains[usdcChain].MockUSDCTokenMessenger) require.NotNil(t, state.Chains[usdcChain].MockUSDCTransmitter) - require.NotNil(t, state.Chains[usdcChain].USDCTokenPool) + require.NotNil(t, state.Chains[usdcChain].USDCTokenPools[deployment.Version1_5_1]) cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), + SourcePoolAddress: state.Chains[usdcChain].USDCTokenPools[deployment.Version1_5_1].Address().String(), SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), } } diff --git a/deployment/ccip/changeset/testhelpers/test_token_helpers.go b/deployment/ccip/changeset/testhelpers/test_token_helpers.go index a819ad09b18..8e56d040018 100644 --- a/deployment/ccip/changeset/testhelpers/test_token_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_token_helpers.go @@ -17,6 +17,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_5_1/token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" ) @@ -25,6 +26,22 @@ const ( TestTokenSymbol changeset.TokenSymbol = "TEST" ) +// CreateSymmetricRateLimits is a utility to quickly create a rate limiter config with equal inbound and outbound values. +func CreateSymmetricRateLimits(rate int64, capacity int64) changeset.RateLimiterConfig { + return changeset.RateLimiterConfig{ + Inbound: token_pool.RateLimiterConfig{ + IsEnabled: rate != 0 || capacity != 0, + Rate: big.NewInt(rate), + Capacity: big.NewInt(capacity), + }, + Outbound: token_pool.RateLimiterConfig{ + IsEnabled: rate != 0 || capacity != 0, + Rate: big.NewInt(rate), + Capacity: big.NewInt(capacity), + }, + } +} + // SetupTwoChainEnvironmentWithTokens preps the environment for token pool deployment testing. func SetupTwoChainEnvironmentWithTokens( t *testing.T, diff --git a/deployment/ccip/changeset/testhelpers/test_usdc_helpers.go b/deployment/ccip/changeset/testhelpers/test_usdc_helpers.go index 75c4c2993a0..4d0d4c4c8b8 100644 --- a/deployment/ccip/changeset/testhelpers/test_usdc_helpers.go +++ b/deployment/ccip/changeset/testhelpers/test_usdc_helpers.go @@ -22,8 +22,8 @@ func ConfigureUSDCTokenPools( ) (*burn_mint_erc677.BurnMintERC677, *burn_mint_erc677.BurnMintERC677, error) { srcToken := state.Chains[src].BurnMintTokens677[changeset.USDCSymbol] dstToken := state.Chains[dst].BurnMintTokens677[changeset.USDCSymbol] - srcPool := state.Chains[src].USDCTokenPool - dstPool := state.Chains[dst].USDCTokenPool + srcPool := state.Chains[src].USDCTokenPools[deployment.Version1_5_1] + dstPool := state.Chains[dst].USDCTokenPools[deployment.Version1_5_1] args := []struct { sourceChain deployment.Chain diff --git a/deployment/ccip/changeset/token_pools.go b/deployment/ccip/changeset/token_pools.go index e257ccca8c6..bff248d35eb 100644 --- a/deployment/ccip/changeset/token_pools.go +++ b/deployment/ccip/changeset/token_pools.go @@ -21,10 +21,12 @@ import ( var currentTokenPoolVersion semver.Version = deployment.Version1_5_1 var tokenPoolTypes map[deployment.ContractType]struct{} = map[deployment.ContractType]struct{}{ - BurnMintTokenPool: struct{}{}, - BurnWithFromMintTokenPool: struct{}{}, - BurnFromMintTokenPool: struct{}{}, - LockReleaseTokenPool: struct{}{}, + BurnMintTokenPool: struct{}{}, + BurnWithFromMintTokenPool: struct{}{}, + BurnFromMintTokenPool: struct{}{}, + LockReleaseTokenPool: struct{}{}, + USDCTokenPool: struct{}{}, + HybridLockReleaseUSDCTokenPool: struct{}{}, } var tokenPoolVersions map[semver.Version]struct{} = map[semver.Version]struct{}{ @@ -182,6 +184,14 @@ func getTokenPoolAddressFromSymbolTypeAndVersion( return tokenPool.Address(), true } } + case USDCTokenPool: + if tokenPool, ok := chainState.USDCTokenPools[version]; ok { + return tokenPool.Address(), true + } + case HybridLockReleaseUSDCTokenPool: + if tokenPool, ok := chainState.USDCTokenPools[version]; ok { + return tokenPool.Address(), true + } } return utils.ZeroAddress, false