diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index 7e0b90fecbe..be3cc9ac121 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" @@ -25,7 +26,7 @@ func TestActiveCandidate(t *testing.T) { t.Skipf("to be enabled after latest cl-ccip is compatible") lggr := logger.TestLogger(t) - tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 5) + tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 5, nil) e := tenv.Env state, err := LoadOnchainState(tenv.Env) require.NoError(t, err) diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index aa702a002cd..d369a481c7d 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -39,7 +39,7 @@ func TestAddChainInbound(t *testing.T) { // We deploy to the rest. initialDeploy := e.Env.AllChainSelectorsExcluding([]uint64{newChain}) newAddresses := deployment.NewMemoryAddressBook() - err = DeployPrerequisiteChainContracts(e.Env, newAddresses, initialDeploy) + err = deployPrerequisiteChainContracts(e.Env, newAddresses, initialDeploy, nil) require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) @@ -59,7 +59,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) newAddresses = deployment.NewMemoryAddressBook() tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - err = DeployCCIPContracts(e.Env, newAddresses, DeployCCIPContractConfig{ + err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ HomeChainSel: e.HomeChainSel, FeedChainSel: e.FeedChainSel, ChainsToDeploy: initialDeploy, @@ -67,7 +67,7 @@ func TestAddChainInbound(t *testing.T) { OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + state, err = LoadOnchainState(e.Env) require.NoError(t, err) @@ -94,7 +94,8 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) newAddresses = deployment.NewMemoryAddressBook() - err = DeployPrerequisiteChainContracts(e.Env, newAddresses, []uint64{newChain}) + + err = deployPrerequisiteChainContracts(e.Env, newAddresses, []uint64{newChain}, nil) require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) newAddresses = deployment.NewMemoryAddressBook() diff --git a/deployment/ccip/changeset/add_lane_test.go b/deployment/ccip/changeset/add_lane_test.go index 4ad6f992bbd..367fd68ba75 100644 --- a/deployment/ccip/changeset/add_lane_test.go +++ b/deployment/ccip/changeset/add_lane_test.go @@ -9,6 +9,7 @@ import ( commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" @@ -20,7 +21,7 @@ import ( func TestAddLane(t *testing.T) { t.Parallel() // We add more chains to the chainlink nodes than the number of chains where CCIP is deployed. - e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4) + e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, nil) // Here we have CR + nodes set up, but no CCIP contracts deployed. state, err := LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/deploy.go index 33459c17678..8d2b6a63e12 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/deploy.go @@ -7,11 +7,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/pkg/errors" + "golang.org/x/sync/errgroup" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment" @@ -33,29 +32,24 @@ import ( ) var ( - MockRMN deployment.ContractType = "MockRMN" - RMNRemote deployment.ContractType = "RMNRemote" - LinkToken deployment.ContractType = "LinkToken" - ARMProxy deployment.ContractType = "ARMProxy" - WETH9 deployment.ContractType = "WETH9" - Router deployment.ContractType = "Router" - CommitStore deployment.ContractType = "CommitStore" - TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" - RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" - NonceManager deployment.ContractType = "NonceManager" - FeeQuoter deployment.ContractType = "FeeQuoter" - AdminManyChainMultisig deployment.ContractType = "AdminManyChainMultiSig" - BypasserManyChainMultisig deployment.ContractType = "BypasserManyChainMultiSig" - CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" - ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" - CCIPHome deployment.ContractType = "CCIPHome" - CCIPConfig deployment.ContractType = "CCIPConfig" - RMNHome deployment.ContractType = "RMNHome" - RBACTimelock deployment.ContractType = "RBACTimelock" - OnRamp deployment.ContractType = "OnRamp" - OffRamp deployment.ContractType = "OffRamp" - CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" - PriceFeed deployment.ContractType = "PriceFeed" + MockRMN deployment.ContractType = "MockRMN" + RMNRemote deployment.ContractType = "RMNRemote" + LinkToken deployment.ContractType = "LinkToken" + ARMProxy deployment.ContractType = "ARMProxy" + WETH9 deployment.ContractType = "WETH9" + Router deployment.ContractType = "Router" + CommitStore deployment.ContractType = "CommitStore" + TokenAdminRegistry deployment.ContractType = "TokenAdminRegistry" + RegistryModule deployment.ContractType = "RegistryModuleOwnerCustom" + NonceManager deployment.ContractType = "NonceManager" + FeeQuoter deployment.ContractType = "FeeQuoter" + CCIPHome deployment.ContractType = "CCIPHome" + CCIPConfig deployment.ContractType = "CCIPConfig" + RMNHome deployment.ContractType = "RMNHome" + OnRamp deployment.ContractType = "OnRamp" + OffRamp deployment.ContractType = "OffRamp" + CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" + PriceFeed deployment.ContractType = "PriceFeed" // Note test router maps to a regular router contract. TestRouter deployment.ContractType = "TestRouter" CCIPReceiver deployment.ContractType = "CCIPReceiver" @@ -67,25 +61,62 @@ var ( USDCTokenPool deployment.ContractType = "USDCTokenPool" ) -func DeployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, selectors []uint64) error { +type DeployPrerequisiteContractsOpts struct { + USDCEnabledChains []uint64 + Multicall3Enabled bool +} + +type PrerequisiteOpt func(o *DeployPrerequisiteContractsOpts) + +func WithUSDCChains(chains []uint64) PrerequisiteOpt { + return func(o *DeployPrerequisiteContractsOpts) { + o.USDCEnabledChains = chains + } +} + +func WithMulticall3(enabled bool) PrerequisiteOpt { + return func(o *DeployPrerequisiteContractsOpts) { + o.Multicall3Enabled = enabled + } +} + +func deployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, selectors []uint64, opts ...PrerequisiteOpt) error { state, err := LoadOnchainState(e) if err != nil { e.Logger.Errorw("Failed to load existing onchain state", "err") return err } + deployGrp := errgroup.Group{} for _, sel := range selectors { chain := e.Chains[sel] - err = DeployPrerequisiteContracts(e, ab, state, chain) - if err != nil { - return errors.Wrapf(err, "failed to deploy prerequisite contracts for chain %d", sel) - } + deployGrp.Go(func() error { + err := deployPrerequisiteContracts(e, ab, state, chain, opts...) + if err != nil { + e.Logger.Errorw("Failed to deploy prerequisite contracts", "chain", sel, "err", err) + return err + } + return nil + }) } - return nil + return deployGrp.Wait() } -// DeployPrerequisiteContracts deploys the contracts that can be ported from previous CCIP version to the new one. +// deployPrerequisiteContracts deploys the contracts that can be ported from previous CCIP version to the new one. // This is only required for staging and test environments where the contracts are not already deployed. -func DeployPrerequisiteContracts(e deployment.Environment, ab deployment.AddressBook, state CCIPOnChainState, chain deployment.Chain) error { +func deployPrerequisiteContracts(e deployment.Environment, ab deployment.AddressBook, state CCIPOnChainState, chain deployment.Chain, opts ...PrerequisiteOpt) error { + deployOpts := &DeployPrerequisiteContractsOpts{} + for _, opt := range opts { + if opt != nil { + opt(deployOpts) + } + } + var isUSDC bool + for _, sel := range deployOpts.USDCEnabledChains { + if sel == chain.Selector { + isUSDC = true + break + } + } lggr := e.Logger chainState, chainExists := state.Chains[chain.Selector] var weth9Contract *weth9.WETH9 @@ -265,43 +296,31 @@ func DeployPrerequisiteContracts(e deployment.Environment, ab deployment.Address } else { e.Logger.Infow("router already deployed", "addr", chainState.Router.Address) } + if isUSDC { + token, pool, messenger, transmitter, err1 := DeployUSDC(e.Logger, chain, ab, rmnProxy.Address(), r.Address()) + if err1 != nil { + return err1 + } + e.Logger.Infow("Deployed USDC contracts", + "chainSelector", chain.Selector, + "token", token.Address(), + "pool", pool.Address(), + "transmitter", transmitter.Address(), + "messenger", messenger.Address(), + ) + } return nil } -type USDCConfig struct { - Enabled bool - USDCAttestationConfig -} - -type USDCAttestationConfig struct { - API string - APITimeout *commonconfig.Duration - APIInterval *commonconfig.Duration -} - -type DeployCCIPContractConfig struct { - HomeChainSel uint64 - FeedChainSel uint64 - ChainsToDeploy []uint64 - TokenConfig TokenConfig - USDCConfig USDCConfig - // For setting OCR configuration - OCRSecrets deployment.OCRSecrets -} - -// DeployCCIPContracts assumes the following contracts are deployed: -// - Capability registry -// - CCIP home -// - RMN home -// - Fee tokens on all chains. -// and present in ExistingAddressBook. -// It then deploys the rest of the CCIP chain contracts to the selected chains -// registers the nodes with the capability registry and creates a DON for -// each new chain. TODO: Might be better to break this down a bit? -func DeployCCIPContracts( +// configureChain assumes the all the Home chain contracts and CCIP contracts are deployed +// It does - +// 1. AddChainConfig for each chain in CCIPHome +// 2. Registers the nodes with the capability registry +// 3. SetOCR3Config on the remote chain +func configureChain( e deployment.Environment, - ab deployment.AddressBook, - c DeployCCIPContractConfig) error { + c NewChainsConfig, +) error { if c.OCRSecrets.IsEmpty() { return fmt.Errorf("OCR secrets are empty") } @@ -331,52 +350,16 @@ func DeployCCIPContracts( return fmt.Errorf("rmn home not found") } - usdcConfiguration := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - for _, chainSel := range c.ChainsToDeploy { - chain, exists := e.Chains[chainSel] - if !exists { - return fmt.Errorf("chain %d not found", chainSel) - } - if c.USDCConfig.Enabled { - token, pool, messenger, transmitter, err1 := DeployUSDC(e.Logger, chain, ab, existingState.Chains[chainSel]) - if err1 != nil { - return err1 - } - e.Logger.Infow("Deployed USDC contracts", - "chainSelector", chainSel, - "token", token.Address(), - "pool", pool.Address(), - "transmitter", transmitter.Address(), - "messenger", messenger.Address(), - ) - - usdcConfiguration[cciptypes.ChainSelector(chainSel)] = pluginconfig.USDCCCTPTokenConfig{ - SourcePoolAddress: pool.Address().Hex(), - SourceMessageTransmitterAddr: transmitter.Address().Hex(), - } - } - } - err = DeployChainContractsForChains(e, ab, c.HomeChainSel, c.ChainsToDeploy) - if err != nil { - e.Logger.Errorw("Failed to deploy chain contracts", "err", err) - return err - } for _, chainSel := range c.ChainsToDeploy { chain, _ := e.Chains[chainSel] - chainAddresses, err := ab.AddressesForChain(chain.Selector) - if err != nil { - e.Logger.Errorw("Failed to get chain addresses", "err", err) - return err + chainState, ok := existingState.Chains[chain.Selector] + if !ok { + return fmt.Errorf("chain state not found for chain %d", chain.Selector) } - chainState, err := LoadChainState(chain, chainAddresses) - if err != nil { - e.Logger.Errorw("Failed to load chain state", "err", err) - return err + if chainState.OffRamp == nil { + return fmt.Errorf("off ramp not found for chain %d", chain.Selector) } - tokenInfo := c.TokenConfig.GetTokenInfo(e.Logger, existingState.Chains[chainSel].LinkToken, existingState.Chains[chainSel].Weth9) - // TODO: Do we want to extract this? - // Add chain config for each chain. _, err = AddChainConfig( e.Logger, e.Chains[c.HomeChainSel], @@ -387,12 +370,12 @@ func DeployCCIPContracts( return err } var tokenDataObserversConf []pluginconfig.TokenDataObserverConfig - if c.USDCConfig.Enabled { + if enabled, ok := c.USDCConfig.EnabledChainMap()[chainSel]; ok && enabled { tokenDataObserversConf = []pluginconfig.TokenDataObserverConfig{{ Type: pluginconfig.USDCCCTPHandlerType, Version: "1.0", USDCCCTPObserverConfig: &pluginconfig.USDCCCTPObserverConfig{ - Tokens: usdcConfiguration, + Tokens: c.USDCConfig.CCTPTokenConfig, AttestationAPI: c.USDCConfig.API, AttestationAPITimeout: c.USDCConfig.APITimeout, AttestationAPIInterval: c.USDCConfig.APIInterval, @@ -422,7 +405,39 @@ func DeployCCIPContracts( return nil } -func DeployChainContractsForChains( +// deployCCIPContracts assumes the following contracts are deployed: +// - Capability registry +// - CCIP home +// - RMN home +// - Fee tokens on all chains. +// and present in ExistingAddressBook. +// It then deploys the rest of the CCIP chain contracts to the selected chains +// registers the nodes with the capability registry and creates a DON for +// each new chain. +func deployCCIPContracts( + e deployment.Environment, + ab deployment.AddressBook, + c NewChainsConfig) error { + err := deployChainContractsForChains(e, ab, c.HomeChainSel, c.ChainsToDeploy) + if err != nil { + e.Logger.Errorw("Failed to deploy chain contracts", "err", err) + return err + } + err = e.ExistingAddresses.Merge(ab) + if err != nil { + e.Logger.Errorw("Failed to merge address book", "err", err) + return err + } + err = configureChain(e, c) + if err != nil { + e.Logger.Errorw("Failed to add chain", "err", err) + return err + } + + return nil +} + +func deployChainContractsForChains( e deployment.Environment, ab deployment.AddressBook, homeChainSel uint64, @@ -445,7 +460,9 @@ func DeployChainContractsForChains( return err } if cr != internal.CCIPCapabilityID { - return fmt.Errorf("capability registry does not support CCIP %s %s", hexutil.Encode(cr[:]), hexutil.Encode(internal.CCIPCapabilityID[:])) + return fmt.Errorf("unexpected mismatch between calculated ccip capability id (%s) and expected ccip capability id constant (%s)", + hexutil.Encode(cr[:]), + hexutil.Encode(internal.CCIPCapabilityID[:])) } capability, err := capReg.GetCapability(nil, internal.CCIPCapabilityID) if err != nil { @@ -465,6 +482,7 @@ func DeployChainContractsForChains( e.Logger.Errorw("Failed to get rmn home", "err", err) return fmt.Errorf("rmn home not found") } + deployGrp := errgroup.Group{} for _, chainSel := range chainsToDeploy { chain, ok := e.Chains[chainSel] if !ok { @@ -473,11 +491,19 @@ func DeployChainContractsForChains( if existingState.Chains[chainSel].LinkToken == nil || existingState.Chains[chainSel].Weth9 == nil { return fmt.Errorf("fee tokens not found for chain %d", chainSel) } - err := deployChainContracts(e, chain, ab, rmnHome) - if err != nil { - e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err) - return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err) - } + deployGrp.Go( + func() error { + err := deployChainContracts(e, chain, ab, rmnHome) + if err != nil { + e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err) + return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err) + } + return nil + }) + } + if err := deployGrp.Wait(); err != nil { + e.Logger.Errorw("Failed to deploy chain contracts", "err", err) + return err } return nil } diff --git a/deployment/ccip/changeset/deploy_chain.go b/deployment/ccip/changeset/deploy_chain.go index cb60f1ddabd..4c37603c64d 100644 --- a/deployment/ccip/changeset/deploy_chain.go +++ b/deployment/ccip/changeset/deploy_chain.go @@ -10,9 +10,14 @@ import ( var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts +// DeployChainContracts deploys all new CCIP v1.6 or later contracts for the given chains. +// It returns the new addresses for the contracts. +// DeployChainContracts is idempotent. If there is an error, it will return the successfully deployed addresses and the error so that the caller can call the +// changeset again with the same input to retry the failed deployment. +// Caller should update the environment's address book with the returned addresses. func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { newAddresses := deployment.NewMemoryAddressBook() - err := DeployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) + err := deployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) if err != nil { env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) diff --git a/deployment/ccip/changeset/deploy_test.go b/deployment/ccip/changeset/deploy_test.go index 5054ac2dba5..fb5729c5b77 100644 --- a/deployment/ccip/changeset/deploy_test.go +++ b/deployment/ccip/changeset/deploy_test.go @@ -12,10 +12,7 @@ import ( func TestDeployCCIPContracts(t *testing.T) { lggr := logger.TestLogger(t) - e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, - 2, - 4, - ) + e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 2, 4, nil) // Deploy all the CCIP contracts. state, err := LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/deployment/ccip/changeset/initial_add_chain.go b/deployment/ccip/changeset/initial_add_chain.go new file mode 100644 index 00000000000..0e8da566626 --- /dev/null +++ b/deployment/ccip/changeset/initial_add_chain.go @@ -0,0 +1,110 @@ +package changeset + +import ( + "fmt" + + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/smartcontractkit/chainlink/deployment" +) + +var _ deployment.ChangeSet[NewChainsConfig] = ConfigureNewChains + +// ConfigureNewChains enables new chains as destination for CCIP +// It performs the following steps: +// - AddChainConfig + AddDON (candidate->primary promotion i.e. init) on the home chain +// - SetOCR3Config on the remote chain +// ConfigureNewChains assumes that the home chain is already enabled and all CCIP contracts are already deployed. +func ConfigureNewChains(env deployment.Environment, c NewChainsConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid NewChainsConfig: %w", err) + } + err := configureChain(env, c) + if err != nil { + env.Logger.Errorw("Failed to configure chain", "err", err) + return deployment.ChangesetOutput{}, deployment.MaybeDataErr(err) + } + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{}, + AddressBook: nil, + JobSpecs: nil, + }, nil +} + +type USDCConfig struct { + EnabledChains []uint64 + USDCAttestationConfig + CCTPTokenConfig map[ccipocr3.ChainSelector]pluginconfig.USDCCCTPTokenConfig +} + +func (cfg USDCConfig) EnabledChainMap() map[uint64]bool { + m := make(map[uint64]bool) + for _, chain := range cfg.EnabledChains { + m[chain] = true + } + return m +} + +type USDCAttestationConfig struct { + API string + APITimeout *config.Duration + APIInterval *config.Duration +} + +type NewChainsConfig struct { + HomeChainSel uint64 + FeedChainSel uint64 + ChainsToDeploy []uint64 + TokenConfig TokenConfig + USDCConfig USDCConfig + // For setting OCR configuration + OCRSecrets deployment.OCRSecrets +} + +func (c NewChainsConfig) Validate() error { + if err := deployment.IsValidChainSelector(c.HomeChainSel); err != nil { + return fmt.Errorf("invalid home chain selector: %d - %w", c.HomeChainSel, err) + } + if err := deployment.IsValidChainSelector(c.FeedChainSel); err != nil { + return fmt.Errorf("invalid feed chain selector: %d - %w", c.FeedChainSel, err) + } + mapChainsToDeploy := make(map[uint64]bool) + for _, cs := range c.ChainsToDeploy { + mapChainsToDeploy[cs] = true + if err := deployment.IsValidChainSelector(cs); err != nil { + return fmt.Errorf("invalid chain selector: %d - %w", cs, err) + } + } + for token := range c.TokenConfig.TokenSymbolToInfo { + if err := c.TokenConfig.TokenSymbolToInfo[token].Validate(); err != nil { + return fmt.Errorf("invalid token config for token %s: %w", token, err) + } + } + if c.OCRSecrets.IsEmpty() { + return fmt.Errorf("no OCR secrets provided") + } + usdcEnabledChainMap := c.USDCConfig.EnabledChainMap() + for chain := range usdcEnabledChainMap { + if _, exists := mapChainsToDeploy[chain]; !exists { + return fmt.Errorf("chain %d is not in chains to deploy", chain) + } + if err := deployment.IsValidChainSelector(chain); err != nil { + return fmt.Errorf("invalid chain selector: %d - %w", chain, err) + } + } + for chain := range c.USDCConfig.CCTPTokenConfig { + if _, exists := mapChainsToDeploy[uint64(chain)]; !exists { + return fmt.Errorf("chain %d is not in chains to deploy", chain) + } + if _, exists := usdcEnabledChainMap[uint64(chain)]; !exists { + return fmt.Errorf("chain %d is not enabled in USDC config", chain) + } + if err := deployment.IsValidChainSelector(uint64(chain)); err != nil { + return fmt.Errorf("invalid chain selector: %d - %w", chain, err) + } + } + return nil +} diff --git a/deployment/ccip/changeset/initial_deploy.go b/deployment/ccip/changeset/initial_deploy.go deleted file mode 100644 index 29cfde18ec2..00000000000 --- a/deployment/ccip/changeset/initial_deploy.go +++ /dev/null @@ -1,28 +0,0 @@ -package changeset - -import ( - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - - "github.com/smartcontractkit/chainlink/deployment" -) - -var _ deployment.ChangeSet[DeployCCIPContractConfig] = InitialDeploy - -func InitialDeploy(env deployment.Environment, c DeployCCIPContractConfig) (deployment.ChangesetOutput, error) { - newAddresses := deployment.NewMemoryAddressBook() - err := DeployCCIPContracts(env, newAddresses, c) - if err != nil { - env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) - return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) - } - js, err := NewCCIPJobSpecs(env.NodeIDs, env.Offchain) - if err != nil { - return deployment.ChangesetOutput{AddressBook: newAddresses}, err - } - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: newAddresses, - // Mapping of which nodes get which jobs. - JobSpecs: js, - }, nil -} diff --git a/deployment/ccip/changeset/initial_deploy_test.go b/deployment/ccip/changeset/initial_deploy_test.go index 14ce267d646..ff68659102f 100644 --- a/deployment/ccip/changeset/initial_deploy_test.go +++ b/deployment/ccip/changeset/initial_deploy_test.go @@ -1,17 +1,11 @@ package changeset import ( - "math/big" "testing" "github.com/ethereum/go-ethereum/common" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" - commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" - commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -21,61 +15,11 @@ import ( func TestInitialDeploy(t *testing.T) { lggr := logger.TestLogger(t) - ctx := Context(t) - tenv := NewMemoryEnvironment(t, lggr, 3, 4, MockLinkPrice, MockWethPrice) + tenv := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 4, nil) e := tenv.Env - state, err := LoadOnchainState(tenv.Env) - require.NoError(t, err) - output, err := DeployPrerequisites(e, DeployPrerequisiteConfig{ - ChainSelectors: tenv.Env.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - - cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) - for _, chain := range e.AllChainSelectors() { - cfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } - } - output, err = commonchangeset.DeployMCMSWithTimelock(e, cfg) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - - output, err = InitialDeploy(tenv.Env, DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - // Get new state after migration. - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - state, err = LoadOnchainState(e) + state, err := LoadOnchainState(e) require.NoError(t, err) - require.NotNil(t, state.Chains[tenv.HomeChainSel].LinkToken) - // Ensure capreg logs are up to date. - ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Add all lanes require.NoError(t, AddLanesForAll(e, state)) // Need to keep track of the block number for each chain so that event subscription can be done from that block. diff --git a/deployment/ccip/changeset/jobspec.go b/deployment/ccip/changeset/jobspec.go index bd968c97e1e..04b658202ea 100644 --- a/deployment/ccip/changeset/jobspec.go +++ b/deployment/ccip/changeset/jobspec.go @@ -7,14 +7,18 @@ import ( "github.com/smartcontractkit/chainlink/deployment" ) -func Jobspec(env deployment.Environment, _ any) (deployment.ChangesetOutput, error) { +var _ deployment.ChangeSet[any] = CCIPCapabilityJobspec + +// CCIPCapabilityJobspec returns the job specs for the CCIP capability. +// The caller needs to propose these job specs to the offchain system. +func CCIPCapabilityJobspec(env deployment.Environment, _ any) (deployment.ChangesetOutput, error) { js, err := NewCCIPJobSpecs(env.NodeIDs, env.Offchain) if err != nil { return deployment.ChangesetOutput{}, errors.Wrapf(err, "failed to create job specs") } return deployment.ChangesetOutput{ Proposals: []timelock.MCMSWithTimelockProposal{}, - AddressBook: deployment.NewMemoryAddressBook(), + AddressBook: nil, JobSpecs: js, }, nil } diff --git a/deployment/ccip/changeset/jobspec_test.go b/deployment/ccip/changeset/jobspec_test.go index 4a10bdc2436..21e80e85aa2 100644 --- a/deployment/ccip/changeset/jobspec_test.go +++ b/deployment/ccip/changeset/jobspec_test.go @@ -18,7 +18,7 @@ func TestJobSpecChangeset(t *testing.T) { Chains: 1, Nodes: 4, }) - output, err := Jobspec(e, nil) + output, err := CCIPCapabilityJobspec(e, nil) require.NoError(t, err) require.NotNil(t, output.JobSpecs) nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) diff --git a/deployment/ccip/changeset/prerequisites.go b/deployment/ccip/changeset/prerequisites.go index 3136c5cc35e..34780809827 100644 --- a/deployment/ccip/changeset/prerequisites.go +++ b/deployment/ccip/changeset/prerequisites.go @@ -16,13 +16,15 @@ var ( // DeployPrerequisites deploys the pre-requisite contracts for CCIP // pre-requisite contracts are the contracts which can be reused from previous versions of CCIP +// Or the contracts which are already deployed on the chain ( for example, tokens, feeds, etc) +// Caller should update the environment's address book with the returned addresses. func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfig) (deployment.ChangesetOutput, error) { err := cfg.Validate() if err != nil { return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) } ab := deployment.NewMemoryAddressBook() - err = DeployPrerequisiteChainContracts(env, ab, cfg.ChainSelectors) + err = deployPrerequisiteChainContracts(env, ab, cfg.ChainSelectors, cfg.Opts...) if err != nil { env.Logger.Errorw("Failed to deploy prerequisite contracts", "err", err, "addressBook", ab) return deployment.ChangesetOutput{ @@ -38,13 +40,16 @@ func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfi type DeployPrerequisiteConfig struct { ChainSelectors []uint64 + Opts []PrerequisiteOpt // TODO handle tokens and feeds in prerequisite config Tokens map[TokenSymbol]common.Address Feeds map[TokenSymbol]common.Address } func (c DeployPrerequisiteConfig) Validate() error { + mapAllChainSelectors := make(map[uint64]struct{}) for _, cs := range c.ChainSelectors { + mapAllChainSelectors[cs] = struct{}{} if err := deployment.IsValidChainSelector(cs); err != nil { return fmt.Errorf("invalid chain selector: %d - %w", cs, err) } diff --git a/deployment/ccip/changeset/save_existing_test.go b/deployment/ccip/changeset/save_existing_test.go index c3f25870d2e..93f3d7e067d 100644 --- a/deployment/ccip/changeset/save_existing_test.go +++ b/deployment/ccip/changeset/save_existing_test.go @@ -9,11 +9,12 @@ import ( "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink/deployment" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" ) -func TestSaveExisting(t *testing.T) { +func TestSaveExistingCCIP(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ @@ -24,8 +25,8 @@ func TestSaveExisting(t *testing.T) { chains := e.AllChainSelectors() chain1 := chains[0] chain2 := chains[1] - cfg := ExistingContractsConfig{ - ExistingContracts: []Contract{ + cfg := commonchangeset.ExistingContractsConfig{ + ExistingContracts: []commonchangeset.Contract{ { Address: common.BigToAddress(big.NewInt(1)), TypeAndVersion: deployment.NewTypeAndVersion(LinkToken, deployment.Version1_0_0), @@ -54,7 +55,7 @@ func TestSaveExisting(t *testing.T) { }, } - output, err := SaveExistingContracts(e, cfg) + output, err := commonchangeset.SaveExistingContracts(e, cfg) require.NoError(t, err) err = e.ExistingAddresses.Merge(output.AddressBook) require.NoError(t, err) diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index a8b3fb04c96..0e1c76221fc 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -48,10 +48,17 @@ import ( // on a chain. If a binding is nil, it means here is no such contract on the chain. type CCIPChainState struct { commoncs.MCMSWithTimelockState - OnRamp *onramp.OnRamp - OffRamp *offramp.OffRamp - FeeQuoter *fee_quoter.FeeQuoter - RMNProxyNew *rmn_proxy_contract.RMNProxyContract + OnRamp *onramp.OnRamp + OffRamp *offramp.OffRamp + FeeQuoter *fee_quoter.FeeQuoter + // We need 2 RMNProxy contracts because we are in the process of migrating to a new version. + // We will switch to the existing one once the migration is complete. + // This is the new RMNProxy contract that will be used for testing RMNRemote before migration. + // Initially RMNProxyNew will point to RMNRemote + RMNProxyNew *rmn_proxy_contract.RMNProxyContract + // Existing RMNProxy contract that is used in production, This already has existing 1.5 RMN set. + // once RMNRemote is tested with RMNProxyNew, as part of migration + // RMNProxyExisting will point to RMNRemote. This will switch over CCIP 1.5 to 1.6 RMNProxyExisting *rmn_proxy_contract.RMNProxyContract NonceManager *nonce_manager.NonceManager TokenAdminRegistry *token_admin_registry.TokenAdminRegistry diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index ffc7e9a1a1f..921d0a222eb 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -15,8 +15,10 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" - + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -242,15 +244,15 @@ func mockAttestationResponse() *httptest.Server { return server } -func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int) DeployedEnv { - e := NewMemoryEnvironment(t, lggr, numChains, numNodes, MockLinkPrice, MockWethPrice) - e.SetupJobs(t) - // Take first non-home chain as the new chain. - newAddresses := deployment.NewMemoryAddressBook() - err := DeployPrerequisiteChainContracts(e.Env, newAddresses, e.Env.AllChainSelectors()) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) +type TestConfigs struct { + IsUSDC bool + IsMultiCall3 bool +} +func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int, tCfg *TestConfigs) DeployedEnv { + var err error + e := NewMemoryEnvironment(t, lggr, numChains, numNodes, MockLinkPrice, MockWethPrice) + allChains := e.Env.AllChainSelectors() cfg := commontypes.MCMSWithTimelockConfig{ Canceller: commonchangeset.SingleGroupMCMS(t), Bypasser: commonchangeset.SingleGroupMCMS(t), @@ -262,37 +264,104 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = cfg } - out, err := commonchangeset.DeployMCMSWithTimelock(e.Env, mcmsCfg) + var usdcChains []uint64 + if tCfg != nil && tCfg.IsUSDC { + usdcChains = allChains + } + // Need to deploy prerequisites first so that we can form the USDC config + // no proposals to be made, timelock can be passed as nil here + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), + Config: DeployPrerequisiteConfig{ + ChainSelectors: allChains, + Opts: []PrerequisiteOpt{ + WithUSDCChains(usdcChains), + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: mcmsCfg, + }, + }) require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) + state, err := LoadOnchainState(e.Env) require.NoError(t, err) - - newAddresses = deployment.NewMemoryAddressBook() tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - server := mockAttestationResponse() - defer server.Close() - endpoint := server.URL - err = DeployCCIPContracts(e.Env, newAddresses, DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: e.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - USDCConfig: USDCConfig{ - Enabled: true, - USDCAttestationConfig: USDCAttestationConfig{ - API: endpoint, - APITimeout: commonconfig.MustNewDuration(time.Second), - APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) + timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + for _, chain := range usdcChains { + require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) + require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) + require.NotNil(t, state.Chains[chain].USDCTokenPool) + usdcCCTPConfig[cciptypes.ChainSelector(chain)] = pluginconfig.USDCCCTPTokenConfig{ + SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), + SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), + } + timelocksPerChain[chain] = state.Chains[chain].Timelock + } + var usdcCfg USDCAttestationConfig + if len(usdcChains) > 0 { + server := mockAttestationResponse() + defer server.Close() + endpoint := server.URL + usdcCfg = USDCAttestationConfig{ + API: endpoint, + APITimeout: commonconfig.MustNewDuration(time.Second), + APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + } + } + + // Deploy second set of changesets to deploy and configure the CCIP contracts. + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(DeployChainContracts), + Config: DeployChainContractsConfig{ + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, }, }, + { + Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), + Config: NewChainsConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: allChains, + TokenConfig: tokenConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + USDCConfig: USDCConfig{ + EnabledChains: usdcChains, + USDCAttestationConfig: usdcCfg, + CCTPTokenConfig: usdcCCTPConfig, + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(CCIPCapabilityJobspec), + }, }) require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + state, err = LoadOnchainState(e.Env) require.NoError(t, err) - + require.NotNil(t, state.Chains[e.HomeChainSel].CapabilityRegistry) + require.NotNil(t, state.Chains[e.HomeChainSel].CCIPHome) + require.NotNil(t, state.Chains[e.HomeChainSel].RMNHome) + for _, chain := range allChains { + require.NotNil(t, state.Chains[chain].LinkToken) + require.NotNil(t, state.Chains[chain].Weth9) + require.NotNil(t, state.Chains[chain].TokenAdminRegistry) + require.NotNil(t, state.Chains[chain].RegistryModule) + require.NotNil(t, state.Chains[chain].Router) + require.NotNil(t, state.Chains[chain].RMNRemote) + require.NotNil(t, state.Chains[chain].TestRouter) + require.NotNil(t, state.Chains[chain].NonceManager) + require.NotNil(t, state.Chains[chain].FeeQuoter) + require.NotNil(t, state.Chains[chain].OffRamp) + require.NotNil(t, state.Chains[chain].OnRamp) + } return e } @@ -571,6 +640,7 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta return nil } +// TODO: Remove this to replace with ApplyChangeset func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.ChangesetOutput) { // TODO: Add support for jobspecs as well diff --git a/deployment/ccip/changeset/test_usdc_helpers.go b/deployment/ccip/changeset/test_usdc_helpers.go index 88e9c07f06a..4a39f4e7ba1 100644 --- a/deployment/ccip/changeset/test_usdc_helpers.go +++ b/deployment/ccip/changeset/test_usdc_helpers.go @@ -7,6 +7,7 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/reader" "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_messenger" @@ -119,7 +120,8 @@ func DeployUSDC( lggr logger.Logger, chain deployment.Chain, addresses deployment.AddressBook, - state CCIPChainState, + rmnProxy common.Address, + router common.Address, ) ( *burn_mint_erc677.BurnMintERC677, *usdc_token_pool.USDCTokenPool, @@ -214,8 +216,8 @@ func DeployUSDC( messenger.Address, token.Address, []common.Address{}, - state.RMNProxyExisting.Address(), - state.Router.Address(), + rmnProxy, + router, ) return deployment.ContractDeploy[*usdc_token_pool.USDCTokenPool]{ Address: tokenPoolAddress, diff --git a/deployment/ccip/changeset/save_existing.go b/deployment/common/changeset/save_existing.go similarity index 92% rename from deployment/ccip/changeset/save_existing.go rename to deployment/common/changeset/save_existing.go index 76330a3a20a..a5177c8e49b 100644 --- a/deployment/ccip/changeset/save_existing.go +++ b/deployment/common/changeset/save_existing.go @@ -42,6 +42,8 @@ func (cfg ExistingContractsConfig) Validate() error { return nil } +// SaveExistingContracts saves the existing contracts to the address book. +// Caller should update the environment's address book with the returned addresses. func SaveExistingContracts(env deployment.Environment, cfg ExistingContractsConfig) (deployment.ChangesetOutput, error) { err := cfg.Validate() if err != nil { diff --git a/deployment/common/changeset/save_existing_test.go b/deployment/common/changeset/save_existing_test.go new file mode 100644 index 00000000000..2a2618c8f54 --- /dev/null +++ b/deployment/common/changeset/save_existing_test.go @@ -0,0 +1,54 @@ +package changeset + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestSaveExisting(t *testing.T) { + dummyEnv := deployment.Environment{ + Name: "dummy", + Logger: logger.TestLogger(t), + ExistingAddresses: deployment.NewMemoryAddressBook(), + Chains: map[uint64]deployment.Chain{ + chainsel.TEST_90000001.Selector: {}, + chainsel.TEST_90000002.Selector: {}, + }, + } + ExistingContracts := ExistingContractsConfig{ + ExistingContracts: []Contract{ + { + Address: common.BigToAddress(big.NewInt(1)), + TypeAndVersion: deployment.TypeAndVersion{ + Type: "dummy1", + Version: deployment.Version1_5_0, + }, + ChainSelector: chainsel.TEST_90000001.Selector, + }, + { + Address: common.BigToAddress(big.NewInt(2)), + TypeAndVersion: deployment.TypeAndVersion{ + Type: "dummy2", + Version: deployment.Version1_1_0, + }, + ChainSelector: chainsel.TEST_90000002.Selector, + }, + }, + } + + output, err := SaveExistingContracts(dummyEnv, ExistingContracts) + require.NoError(t, err) + require.NoError(t, dummyEnv.ExistingAddresses.Merge(output.AddressBook)) + addresses, err := dummyEnv.ExistingAddresses.Addresses() + require.Len(t, addresses, 2) + addressForChain1, exists := addresses[chainsel.TEST_90000001.Selector] + require.True(t, exists) + require.Len(t, addressForChain1, 1) +} diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go new file mode 100644 index 00000000000..913b4544f30 --- /dev/null +++ b/deployment/common/changeset/test_helpers.go @@ -0,0 +1,96 @@ +package changeset + +import ( + "fmt" + "testing" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + + "github.com/smartcontractkit/chainlink/deployment" +) + +type ChangesetApplication struct { + Changeset deployment.ChangeSet[any] + Config any +} + +func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environment, config any) (deployment.ChangesetOutput, error) { + return func(e deployment.Environment, config any) (deployment.ChangesetOutput, error) { + var zeroC C + if config != nil { + c, ok := config.(C) + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid config type, expected %T", c) + } + return fn(e, config.(C)) + } + + return fn(e, zeroC) + } +} + +// ApplyChangesets applies the changeset applications to the environment and returns the updated environment. +func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { + currentEnv := e + for i, csa := range changesetApplications { + out, err := csa.Changeset(currentEnv, csa.Config) + if err != nil { + return e, fmt.Errorf("failed to apply changeset at index %d: %w", i, err) + } + var addresses deployment.AddressBook + if out.AddressBook != nil { + addresses = out.AddressBook + err := addresses.Merge(currentEnv.ExistingAddresses) + if err != nil { + return e, fmt.Errorf("failed to merge address book: %w", err) + } + } else { + addresses = currentEnv.ExistingAddresses + } + if out.JobSpecs != nil { + ctx := testcontext.Get(t) + for nodeID, jobs := range out.JobSpecs { + for _, job := range jobs { + // Note these auto-accept + _, err := currentEnv.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + if err != nil { + return e, fmt.Errorf("failed to propose job: %w", err) + } + } + } + } + if out.Proposals != nil { + for _, prop := range out.Proposals { + chains := mapset.NewSet[uint64]() + for _, op := range prop.Transactions { + chains.Add(uint64(op.ChainIdentifier)) + } + + signed := SignProposal(t, e, &prop) + for _, sel := range chains.ToSlice() { + timelock, ok := timelocksPerChain[sel] + if !ok || timelock == nil { + return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) + } + ExecuteProposal(t, e, signed, timelock, sel) + } + } + } + currentEnv = deployment.Environment{ + Name: e.Name, + Logger: e.Logger, + ExistingAddresses: addresses, + Chains: e.Chains, + NodeIDs: e.NodeIDs, + Offchain: e.Offchain, + } + } + return currentEnv, nil +} diff --git a/deployment/environment/devenv/environment.go b/deployment/environment/devenv/environment.go index af39e59e3bf..94319d2247e 100644 --- a/deployment/environment/devenv/environment.go +++ b/deployment/environment/devenv/environment.go @@ -34,15 +34,17 @@ func NewEnvironment(ctx context.Context, lggr logger.Logger, config EnvironmentC if !ok { return nil, nil, fmt.Errorf("offchain client does not implement JobDistributor") } - if jd == nil || jd.don == nil { - return nil, nil, fmt.Errorf("offchain client does not have a DON") + if jd == nil { + return nil, nil, fmt.Errorf("offchain client is not set up") } - - err = jd.don.CreateSupportedChains(ctx, config.Chains, *jd) - if err != nil { - return nil, nil, err + var nodeIDs []string + if jd.don != nil { + err = jd.don.CreateSupportedChains(ctx, config.Chains, *jd) + if err != nil { + return nil, nil, err + } + nodeIDs = jd.don.NodeIds() } - nodeIDs := jd.don.NodeIds() return deployment.NewEnvironment( DevEnv, diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 652bdcdf88b..2446eb69ecf 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -34,10 +34,11 @@ require ( github.com/segmentio/ksuid v1.0.4 github.com/shopspring/decimal v1.4.0 github.com/slack-go/slack v0.15.0 + github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 github.com/smartcontractkit/chain-selectors v1.0.30 github.com/smartcontractkit/chainlink-automation v0.8.1 + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241118091009-43c2b4804cec github.com/smartcontractkit/chainlink-common v0.3.1-0.20241120111740-a6a70ec7692b - github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 github.com/smartcontractkit/chainlink-testing-framework/lib v1.50.16 github.com/smartcontractkit/chainlink-testing-framework/lib/grafana v1.50.0 @@ -412,11 +413,10 @@ require ( github.com/shirou/gopsutil/v3 v3.24.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241118091009-43c2b4804cec // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect + github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 // indirect github.com/smartcontractkit/chainlink-protos/orchestrator v0.3.0 // indirect github.com/smartcontractkit/chainlink-solana v1.1.1-0.20241118190857-e2db20a6a969 // indirect github.com/smartcontractkit/chainlink-starknet/relayer v0.1.1-0.20241017135645-176a23722fd8 // indirect diff --git a/integration-tests/smoke/ccip/ccip_messaging_test.go b/integration-tests/smoke/ccip/ccip_messaging_test.go index cd9a1f0c6d8..696655d960c 100644 --- a/integration-tests/smoke/ccip/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip/ccip_messaging_test.go @@ -15,6 +15,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/hashutil" "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" @@ -48,7 +49,7 @@ func Test_CCIPMessaging(t *testing.T) { // Setup 2 chains and a single lane. lggr := logger.TestLogger(t) ctx := changeset.Context(t) - e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + e, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/integration-tests/smoke/ccip/ccip_test.go b/integration-tests/smoke/ccip/ccip_test.go index 84f705b77e1..5a89f9a5f2d 100644 --- a/integration-tests/smoke/ccip/ccip_test.go +++ b/integration-tests/smoke/ccip/ccip_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" @@ -17,7 +18,7 @@ import ( func TestInitialDeployOnLocal(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) e := tenv.Env state, err := changeset.LoadOnchainState(e) require.NoError(t, err) @@ -72,7 +73,7 @@ func TestInitialDeployOnLocal(t *testing.T) { func TestTokenTransfer(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) e := tenv.Env state, err := changeset.LoadOnchainState(e) require.NoError(t, err) diff --git a/integration-tests/smoke/ccip/ccip_usdc_test.go b/integration-tests/smoke/ccip/ccip_usdc_test.go index 675ced9bc84..9e8a0dd57a5 100644 --- a/integration-tests/smoke/ccip/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip/ccip_usdc_test.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" @@ -25,7 +26,9 @@ import ( func TestUSDCTokenTransfer(t *testing.T) { lggr := logger.TestLogger(t) - tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr, &changeset.TestConfigs{ + IsUSDC: true, + }) e := tenv.Env state, err := changeset.LoadOnchainState(e) diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 8c9f3339267..37d6d8523ce 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -34,10 +34,7 @@ type priceFeedPrices struct { // TODO: find a way to reuse the same test setup for all tests func Test_CCIPFeeBoosting(t *testing.T) { setupTestEnv := func(t *testing.T, numChains int) (changeset.DeployedEnv, changeset.CCIPOnChainState, []uint64) { - e, _, _ := testsetups.NewLocalDevEnvironment( - t, logger.TestLogger(t), - deployment.E18Mult(5), - big.NewInt(9e8)) + e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), big.NewInt(9e8), nil) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) diff --git a/integration-tests/testsetups/test_helpers.go b/integration-tests/testsetups/test_helpers.go index 8ff7ea79a63..ed3e6aafea5 100644 --- a/integration-tests/testsetups/test_helpers.go +++ b/integration-tests/testsetups/test_helpers.go @@ -9,10 +9,13 @@ import ( "testing" "time" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" chainsel "github.com/smartcontractkit/chain-selectors" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" @@ -22,6 +25,7 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-testing-framework/seth" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" @@ -36,7 +40,6 @@ import ( ccipactions "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" - "github.com/smartcontractkit/chainlink/integration-tests/testconfig" tc "github.com/smartcontractkit/chainlink/integration-tests/testconfig" "github.com/smartcontractkit/chainlink/integration-tests/utils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -79,16 +82,16 @@ func (d DeployedLocalDevEnvironment) RestartChainlinkNodes(t *testing.T) error { return errGrp.Wait() } -func NewLocalDevEnvironmentWithDefaultPrice( - t *testing.T, - lggr logger.Logger) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, testconfig.TestConfig) { - return NewLocalDevEnvironment(t, lggr, changeset.MockLinkPrice, changeset.MockWethPrice) +func NewLocalDevEnvironmentWithDefaultPrice(t *testing.T, lggr logger.Logger, tCfg *changeset.TestConfigs) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, tc.TestConfig) { + return NewLocalDevEnvironment(t, lggr, changeset.MockLinkPrice, changeset.MockWethPrice, tCfg) } func NewLocalDevEnvironment( t *testing.T, lggr logger.Logger, - linkPrice, wethPrice *big.Int) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, testconfig.TestConfig) { + linkPrice, wethPrice *big.Int, + tCfg *changeset.TestConfigs, +) (changeset.DeployedEnv, *test_env.CLClusterTestEnv, tc.TestConfig) { ctx := testcontext.Get(t) // create a local docker environment with simulated chains and job-distributor // we cannot create the chainlink nodes yet as we need to deploy the capability registry first @@ -114,95 +117,129 @@ func NewLocalDevEnvironment( crConfig, testEnv, cfg) require.NoError(t, err) + e, don, err := devenv.NewEnvironment(ctx, lggr, *envConfig) require.NoError(t, err) require.NotNil(t, e) e.ExistingAddresses = ab - envNodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - require.NoError(t, err) - out, err := changeset.DeployHomeChain(*e, - changeset.DeployHomeChainConfig{ - HomeChainSel: homeChainSel, - RMNStaticConfig: changeset.NewTestRMNStaticConfig(), - RMNDynamicConfig: changeset.NewTestRMNDynamicConfig(), - NodeOperators: changeset.NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), - NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{ - "NodeOperator": envNodes.NonBootstraps().PeerIDs(), - }, - }, - ) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(out.AddressBook)) - zeroLogLggr := logging.GetTestLogger(t) // fund the nodes + zeroLogLggr := logging.GetTestLogger(t) FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) - output, err := changeset.DeployPrerequisites(*e, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.AllChainSelectors(), - }) + env := *e + envNodes, err := deployment.NodeInfo(env.NodeIDs, env.Offchain) require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + allChains := env.AllChainSelectors() + var usdcChains []uint64 + if tCfg != nil && tCfg.IsUSDC { + usdcChains = allChains + } + mcmsCfgPerChain := commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) - for _, chain := range e.AllChainSelectors() { - mcmsCfg[chain] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), - } + for _, c := range env.AllChainSelectors() { + mcmsCfg[c] = mcmsCfgPerChain } - output, err = commonchangeset.DeployMCMSWithTimelock(*e, mcmsCfg) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - - state, err := changeset.LoadOnchainState(*e) + // Need to deploy prerequisites first so that we can form the USDC config + // no proposals to be made, timelock can be passed as nil here + env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployHomeChain), + Config: changeset.DeployHomeChainConfig{ + HomeChainSel: homeChainSel, + RMNStaticConfig: changeset.NewTestRMNStaticConfig(), + RMNDynamicConfig: changeset.NewTestRMNDynamicConfig(), + NodeOperators: changeset.NewTestNodeOperator(chains[homeChainSel].DeployerKey.From), + NodeP2PIDsPerNodeOpAdmin: map[string][][32]byte{ + "NodeOperator": envNodes.NonBootstraps().PeerIDs(), + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployPrerequisites), + Config: changeset.DeployPrerequisiteConfig{ + ChainSelectors: allChains, + Opts: []changeset.PrerequisiteOpt{ + changeset.WithUSDCChains(usdcChains), + }, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: mcmsCfg, + }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContracts), + Config: changeset.DeployChainContractsConfig{ + ChainSelectors: allChains, + HomeChainSelector: homeChainSel, + }, + }, + }) require.NoError(t, err) - var endpoint string - err = ccipactions.SetMockServerWithUSDCAttestation(testEnv.MockAdapter, nil) + state, err := changeset.LoadOnchainState(env) require.NoError(t, err) - endpoint = testEnv.MockAdapter.InternalEndpoint - tokenConfig := changeset.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) - // Apply migration - output, err = changeset.InitialDeploy(*e, changeset.DeployCCIPContractConfig{ - HomeChainSel: homeChainSel, - FeedChainSel: feedSel, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: tokenConfig, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - USDCConfig: changeset.USDCConfig{ - Enabled: true, - USDCAttestationConfig: changeset.USDCAttestationConfig{ - API: endpoint, - APITimeout: commonconfig.MustNewDuration(time.Second), - APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + usdcCCTPConfig := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) + timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + for _, chain := range usdcChains { + require.NotNil(t, state.Chains[chain].MockUSDCTokenMessenger) + require.NotNil(t, state.Chains[chain].MockUSDCTransmitter) + require.NotNil(t, state.Chains[chain].USDCTokenPool) + usdcCCTPConfig[cciptypes.ChainSelector(chain)] = pluginconfig.USDCCCTPTokenConfig{ + SourcePoolAddress: state.Chains[chain].USDCTokenPool.Address().String(), + SourceMessageTransmitterAddr: state.Chains[chain].MockUSDCTransmitter.Address().String(), + } + timelocksPerChain[chain] = state.Chains[chain].Timelock + } + var usdcAttestationCfg changeset.USDCAttestationConfig + if len(usdcChains) > 0 { + var endpoint string + err = ccipactions.SetMockServerWithUSDCAttestation(testEnv.MockAdapter, nil) + require.NoError(t, err) + endpoint = testEnv.MockAdapter.InternalEndpoint + usdcAttestationCfg = changeset.USDCAttestationConfig{ + API: endpoint, + APITimeout: commonconfig.MustNewDuration(time.Second), + APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + } + } + + // Deploy second set of changesets to deploy and configure the CCIP contracts. + env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureNewChains), + Config: changeset.NewChainsConfig{ + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ChainsToDeploy: allChains, + TokenConfig: tokenConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + USDCConfig: changeset.USDCConfig{ + EnabledChains: usdcChains, + USDCAttestationConfig: usdcAttestationCfg, + CCTPTokenConfig: usdcCCTPConfig, + }, }, }, + { + Changeset: commonchangeset.WrapChangeSet(changeset.CCIPCapabilityJobspec), + }, }) require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) // Ensure capreg logs are up to date. changeset.ReplayLogs(t, e.Offchain, replayBlocks) - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - return changeset.DeployedEnv{ - Env: *e, + Env: env, HomeChainSel: homeChainSel, FeedChainSel: feedSel, ReplayBlocks: replayBlocks, @@ -214,7 +251,7 @@ func NewLocalDevEnvironmentWithRMN( lggr logger.Logger, numRmnNodes int, ) (changeset.DeployedEnv, devenv.RMNCluster) { - tenv, dockerenv, testCfg := NewLocalDevEnvironmentWithDefaultPrice(t, lggr) + tenv, dockerenv, testCfg := NewLocalDevEnvironmentWithDefaultPrice(t, lggr, nil) l := logging.GetTestLogger(t) config := GenerateTestRMNConfig(t, numRmnNodes, tenv, MustNetworksToRPCMap(dockerenv.EVMNetworks)) require.NotNil(t, testCfg.CCIP) @@ -545,41 +582,58 @@ func FundNodes(t *testing.T, lggr zerolog.Logger, env *test_env.CLClusterTestEnv } } }) + fundGrp := errgroup.Group{} for i := range evmNetworks { - evmNetwork := evmNetworks[i] - sethClient, err := utils.TestAwareSethClient(t, cfg, &evmNetwork) - require.NoError(t, err, "Error getting seth client for network %s", evmNetwork.Name) - require.Greater(t, len(sethClient.PrivateKeys), 0, seth.ErrNoKeyLoaded) - privateKey := sethClient.PrivateKeys[0] - if evmNetwork.ChainID < 0 { - t.Fatalf("negative chain ID: %d", evmNetwork.ChainID) - } - for _, node := range nodes { - nodeAddr, ok := node.AccountAddr[uint64(evmNetwork.ChainID)] - require.True(t, ok, "Account address not found for chain %d", evmNetwork.ChainID) - fromAddress, err := actions.PrivateKeyToAddress(privateKey) - require.NoError(t, err, "Error getting address from private key") - amount := big.NewFloat(pointer.GetFloat64(cfg.Common.ChainlinkNodeFunding)) - toAddr := common.HexToAddress(nodeAddr) - receipt, err := actions.SendFunds(lggr, sethClient, actions.FundsToSendPayload{ - ToAddress: toAddr, - Amount: conversions.EtherToWei(amount), - PrivateKey: privateKey, - }) - require.NoError(t, err, "Error sending funds to node %s", node.Name) - require.NotNil(t, receipt, "Receipt is nil") - txHash := "(none)" - if receipt != nil { - txHash = receipt.TxHash.String() + fundGrp.Go(func() error { + evmNetwork := evmNetworks[i] + sethClient, err := utils.TestAwareSethClient(t, cfg, &evmNetwork) + if err != nil { + return fmt.Errorf("error getting seth client for network %s: %w", evmNetwork.Name, err) } - lggr.Info(). - Str("From", fromAddress.Hex()). - Str("To", toAddr.String()). - Str("TxHash", txHash). - Str("Amount", amount.String()). - Msg("Funded Chainlink node") - } + if len(sethClient.PrivateKeys) == 0 { + return fmt.Errorf(seth.ErrNoKeyLoaded) + } + privateKey := sethClient.PrivateKeys[0] + if evmNetwork.ChainID < 0 { + return fmt.Errorf("negative chain ID: %d", evmNetwork.ChainID) + } + for _, node := range nodes { + nodeAddr, ok := node.AccountAddr[uint64(evmNetwork.ChainID)] + if !ok { + return fmt.Errorf("account address not found for chain %d", evmNetwork.ChainID) + } + fromAddress, err := actions.PrivateKeyToAddress(privateKey) + if err != nil { + return fmt.Errorf("error getting address from private key: %w", err) + } + amount := big.NewFloat(pointer.GetFloat64(cfg.Common.ChainlinkNodeFunding)) + toAddr := common.HexToAddress(nodeAddr) + receipt, err := actions.SendFunds(lggr, sethClient, actions.FundsToSendPayload{ + ToAddress: toAddr, + Amount: conversions.EtherToWei(amount), + PrivateKey: privateKey, + }) + if err != nil { + return fmt.Errorf("error sending funds to node %s: %w", node.Name, err) + } + if receipt == nil { + return fmt.Errorf("receipt is nil") + } + txHash := "(none)" + if receipt != nil { + txHash = receipt.TxHash.String() + } + lggr.Info(). + Str("From", fromAddress.Hex()). + Str("To", toAddr.String()). + Str("TxHash", txHash). + Str("Amount", amount.String()). + Msg("Funded Chainlink node") + } + return nil + }) } + require.NoError(t, fundGrp.Wait(), "Error funding chainlink nodes") } // CreateChainConfigFromNetworks creates a list of ChainConfig from the network config provided in test config.