From cc49b25b2df6736ce095d09fa2e9e8280865bbc4 Mon Sep 17 00:00:00 2001 From: Anindita Ghosh <88458927+AnieeG@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:37:34 -0800 Subject: [PATCH] CCIP-4573 Changesets ccip 1.5 (#15588) * just start * placeholders * state * change * updates * updates * all updates * change * comments * progress so far * add test * more updates * updates * changes * updates * formatting * fix crib * fix more tests * lint fix * comments * fix format * multichain * fix lint * fix lint * change func name * review comments * lint fix * removed legacyjobclient * review comments --- .../ccip/testhelpers/integration/jobspec.go | 25 ++ deployment/ccip/changeset/cs_deploy_chain.go | 26 +- .../ccip/changeset/cs_deploy_chain_test.go | 8 +- deployment/ccip/changeset/cs_prerequisites.go | 221 +++++++--- .../ccip/changeset/cs_prerequisites_test.go | 6 +- .../changeset/cs_update_rmn_config_test.go | 6 +- deployment/ccip/changeset/state.go | 156 +++++-- deployment/ccip/changeset/test_environment.go | 149 +++++-- deployment/ccip/changeset/v1_5/cs_jobspec.go | 149 +++++++ .../ccip/changeset/v1_5/cs_lane_contracts.go | 288 +++++++++++++ .../ccip/changeset/v1_5/cs_ocr2_config.go | 329 +++++++++++++++ deployment/ccip/changeset/v1_5/e2e_test.go | 56 +++ .../ccip/changeset/v1_5/test_helpers.go | 384 ++++++++++++++++++ deployment/ccip/view/view.go | 15 +- .../common/changeset/internal/mcms_test.go | 8 +- deployment/environment.go | 3 + deployment/environment/crib/ccip_deployer.go | 16 +- deployment/environment/memory/chain.go | 5 +- deployment/environment/memory/environment.go | 22 +- deployment/environment/memory/job_client.go | 24 +- deployment/go.mod | 1 + deployment/go.sum | 4 +- deployment/multiclient.go | 7 +- .../smoke/ccip/ccip_legacy_test.go | 51 +++ .../testsetups/ccip/test_helpers.go | 14 +- 25 files changed, 1814 insertions(+), 159 deletions(-) create mode 100644 deployment/ccip/changeset/v1_5/cs_jobspec.go create mode 100644 deployment/ccip/changeset/v1_5/cs_lane_contracts.go create mode 100644 deployment/ccip/changeset/v1_5/cs_ocr2_config.go create mode 100644 deployment/ccip/changeset/v1_5/e2e_test.go create mode 100644 deployment/ccip/changeset/v1_5/test_helpers.go create mode 100644 integration-tests/smoke/ccip/ccip_legacy_test.go diff --git a/core/services/ocr2/plugins/ccip/testhelpers/integration/jobspec.go b/core/services/ocr2/plugins/ccip/testhelpers/integration/jobspec.go index b10f51a9426..a520580e614 100644 --- a/core/services/ocr2/plugins/ccip/testhelpers/integration/jobspec.go +++ b/core/services/ocr2/plugins/ccip/testhelpers/integration/jobspec.go @@ -2,11 +2,13 @@ package integrationtesthelpers import ( "bytes" + "crypto/sha256" "fmt" "text/template" "time" "github.com/ethereum/go-ethereum/common" + "github.com/google/uuid" "github.com/lib/pq" "github.com/smartcontractkit/chainlink-common/pkg/types" @@ -28,6 +30,7 @@ type OCR2TaskJobSpec struct { ForwardingAllowed bool `toml:"forwardingAllowed"` OCR2OracleSpec job.OCR2OracleSpec ObservationSource string `toml:"observationSource"` // List of commands for the Chainlink node + ExternalJobID string `toml:"externalJobID"` } // Type returns the type of the job @@ -39,9 +42,14 @@ func (o *OCR2TaskJobSpec) String() (string, error) { if o.OCR2OracleSpec.FeedID != nil { feedID = o.OCR2OracleSpec.FeedID.Hex() } + externalID, err := ExternalJobID(o.Name) + if err != nil { + return "", err + } specWrap := struct { Name string JobType string + ExternalJobID string MaxTaskDuration string ForwardingAllowed bool ContractID string @@ -62,6 +70,7 @@ func (o *OCR2TaskJobSpec) String() (string, error) { }{ Name: o.Name, JobType: o.JobType, + ExternalJobID: externalID, ForwardingAllowed: o.ForwardingAllowed, MaxTaskDuration: o.MaxTaskDuration, ContractID: o.OCR2OracleSpec.ContractID, @@ -82,6 +91,7 @@ func (o *OCR2TaskJobSpec) String() (string, error) { ocr2TemplateString := ` type = "{{ .JobType }}" name = "{{.Name}}" +externalJobID = "{{.ExternalJobID}}" forwardingAllowed = {{.ForwardingAllowed}} {{if .MaxTaskDuration}} maxTaskDuration = "{{ .MaxTaskDuration }}" {{end}} @@ -332,3 +342,18 @@ func (c *CCIPIntegrationTestHarness) NewCCIPJobSpecParams(tokenPricesUSDPipeline USDCAttestationAPI: usdcAttestationAPI, } } + +func ExternalJobID(jobName string) (string, error) { + in := []byte(jobName) + sha256Hash := sha256.New() + sha256Hash.Write(in) + in = sha256Hash.Sum(nil)[:16] + // tag as valid UUID v4 https://github.com/google/uuid/blob/0f11ee6918f41a04c201eceeadf612a377bc7fbc/version4.go#L53-L54 + in[6] = (in[6] & 0x0f) | 0x40 // Version 4 + in[8] = (in[8] & 0x3f) | 0x80 // Variant is 10 + id, err := uuid.FromBytes(in) + if err != nil { + return "", err + } + return id.String(), nil +} diff --git a/deployment/ccip/changeset/cs_deploy_chain.go b/deployment/ccip/changeset/cs_deploy_chain.go index 065c29755b6..444f204dd0a 100644 --- a/deployment/ccip/changeset/cs_deploy_chain.go +++ b/deployment/ccip/changeset/cs_deploy_chain.go @@ -14,7 +14,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/nonce_manager" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" @@ -178,32 +177,17 @@ func deployChainContracts( } rmnProxyContract := chainState.RMNProxy if chainState.RMNProxy == nil { + e.Logger.Errorw("RMNProxy not found", "chain", chain.String()) return fmt.Errorf("rmn proxy not found for chain %s, deploy the prerequisites first", chain.String()) } - if chainState.Receiver == nil { - _, err := deployment.DeployContract(e.Logger, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver] { - receiverAddr, tx, receiver, err2 := maybe_revert_message_receiver.DeployMaybeRevertMessageReceiver( - chain.DeployerKey, - chain.Client, - false, - ) - return deployment.ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver]{ - receiverAddr, receiver, tx, deployment.NewTypeAndVersion(CCIPReceiver, deployment.Version1_0_0), err2, - } - }) - if err != nil { - e.Logger.Errorw("Failed to deploy receiver", "err", err) - return err - } - } else { - e.Logger.Infow("receiver already deployed", "addr", chainState.Receiver.Address, "chain", chain.String()) - } var rmnLegacyAddr common.Address if chainState.MockRMN != nil { rmnLegacyAddr = chainState.MockRMN.Address() } - // TODO add legacy RMN here when 1.5 contracts are available + // If RMN is deployed, set rmnLegacyAddr to the RMN address + if chainState.RMN != nil { + rmnLegacyAddr = chainState.RMN.Address() + } if rmnLegacyAddr == (common.Address{}) { e.Logger.Warnf("No legacy RMN contract found for chain %s, will not setRMN in RMNRemote", chain.String()) } diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 9e1a581112d..a72b1b1568b 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -33,6 +33,12 @@ func TestDeployChainContractsChangeset(t *testing.T) { for _, chain := range e.AllChainSelectors() { cfg[chain] = proposalutils.SingleGroupTimelockConfig(t) } + var prereqCfg []DeployPrerequisiteConfigPerChain + for _, chain := range e.AllChainSelectors() { + prereqCfg = append(prereqCfg, DeployPrerequisiteConfigPerChain{ + ChainSelector: chain, + }) + } e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), @@ -57,7 +63,7 @@ func TestDeployChainContractsChangeset(t *testing.T) { { Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), Config: DeployPrerequisiteConfig{ - ChainSelectors: selectors, + Configs: prereqCfg, }, }, { diff --git a/deployment/ccip/changeset/cs_prerequisites.go b/deployment/ccip/changeset/cs_prerequisites.go index 95ef923df83..94535df4a0f 100644 --- a/deployment/ccip/changeset/cs_prerequisites.go +++ b/deployment/ccip/changeset/cs_prerequisites.go @@ -9,8 +9,11 @@ import ( "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/maybe_revert_message_receiver" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_rmn_contract" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_proxy_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/token_admin_registry" @@ -32,7 +35,7 @@ func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfi return deployment.ChangesetOutput{}, errors.Wrapf(deployment.ErrInvalidConfig, "%v", err) } ab := deployment.NewMemoryAddressBook() - err = deployPrerequisiteChainContracts(env, ab, cfg.ChainSelectors, cfg.Opts...) + err = deployPrerequisiteChainContracts(env, ab, cfg) if err != nil { env.Logger.Errorw("Failed to deploy prerequisite contracts", "err", err, "addressBook", ab) return deployment.ChangesetOutput{ @@ -47,13 +50,23 @@ func DeployPrerequisites(env deployment.Environment, cfg DeployPrerequisiteConfi } type DeployPrerequisiteContractsOpts struct { - USDCEnabledChains []uint64 - Multicall3Enabled bool + USDCEnabled bool + Multicall3Enabled bool + LegacyDeploymentCfg *LegacyDeploymentConfig +} + +type LegacyDeploymentConfig struct { + RMNConfig *rmn_contract.RMNConfig + PriceRegStalenessThreshold uint32 } type DeployPrerequisiteConfig struct { - ChainSelectors []uint64 - Opts []PrerequisiteOpt + Configs []DeployPrerequisiteConfigPerChain +} + +type DeployPrerequisiteConfigPerChain struct { + ChainSelector uint64 + Opts []PrerequisiteOpt // TODO handle tokens and feeds in prerequisite config Tokens map[TokenSymbol]common.Address Feeds map[TokenSymbol]common.Address @@ -61,7 +74,8 @@ type DeployPrerequisiteConfig struct { func (c DeployPrerequisiteConfig) Validate() error { mapAllChainSelectors := make(map[uint64]struct{}) - for _, cs := range c.ChainSelectors { + for _, cfg := range c.Configs { + cs := cfg.ChainSelector mapAllChainSelectors[cs] = struct{}{} if err := deployment.IsValidChainSelector(cs); err != nil { return fmt.Errorf("invalid chain selector: %d - %w", cs, err) @@ -72,31 +86,41 @@ func (c DeployPrerequisiteConfig) Validate() error { type PrerequisiteOpt func(o *DeployPrerequisiteContractsOpts) -func WithUSDCChains(chains []uint64) PrerequisiteOpt { +func WithUSDCEnabled() PrerequisiteOpt { return func(o *DeployPrerequisiteContractsOpts) { - o.USDCEnabledChains = chains + o.USDCEnabled = true } } -func WithMulticall3(enabled bool) PrerequisiteOpt { +func WithMultiCall3Enabled() PrerequisiteOpt { return func(o *DeployPrerequisiteContractsOpts) { - o.Multicall3Enabled = enabled + o.Multicall3Enabled = true } } -func deployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, selectors []uint64, opts ...PrerequisiteOpt) error { +func WithLegacyDeploymentEnabled(cfg LegacyDeploymentConfig) PrerequisiteOpt { + return func(o *DeployPrerequisiteContractsOpts) { + if cfg.PriceRegStalenessThreshold == 0 { + panic("PriceRegStalenessThreshold must be set") + } + // TODO validate RMNConfig + o.LegacyDeploymentCfg = &cfg + } +} + +func deployPrerequisiteChainContracts(e deployment.Environment, ab deployment.AddressBook, cfg DeployPrerequisiteConfig) 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] + for _, c := range cfg.Configs { + chain := e.Chains[c.ChainSelector] deployGrp.Go(func() error { - err := deployPrerequisiteContracts(e, ab, state, chain, opts...) + err := deployPrerequisiteContracts(e, ab, state, chain, c.Opts...) if err != nil { - e.Logger.Errorw("Failed to deploy prerequisite contracts", "chain", sel, "err", err) + e.Logger.Errorw("Failed to deploy prerequisite contracts", "chain", chain.String(), "err", err) return err } return nil @@ -114,13 +138,6 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address 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 @@ -137,37 +154,103 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address r = chainState.Router mc3 = chainState.Multicall3 } - if rmnProxy == nil { - rmn, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] { - rmnAddr, tx2, rmn, err2 := mock_rmn_contract.DeployMockRMNContract( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{ - Address: rmnAddr, Contract: rmn, Tx: tx2, Tv: deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), Err: err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy mock RMN", "chain", chain.String(), "err", err) - return err + var rmnAddr common.Address + // if we are setting up 1.5 version, deploy RMN contract based on the config provided + // else deploy the mock RMN contract + if deployOpts.LegacyDeploymentCfg != nil && deployOpts.LegacyDeploymentCfg.RMNConfig != nil { + if chainState.RMN == nil { + rmn, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*rmn_contract.RMNContract] { + rmnAddress, tx2, rmnC, err2 := rmn_contract.DeployRMNContract( + chain.DeployerKey, + chain.Client, + *deployOpts.LegacyDeploymentCfg.RMNConfig, + ) + return deployment.ContractDeploy[*rmn_contract.RMNContract]{ + Address: rmnAddress, Contract: rmnC, Tx: tx2, Tv: deployment.NewTypeAndVersion(RMN, deployment.Version1_5_0), Err: err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy RMN", "chain", chain.String(), "err", err) + return err + } + rmnAddr = rmn.Address + } else { + lggr.Infow("RMN already deployed", "chain", chain.String(), "address", chainState.RMN.Address) + rmnAddr = chainState.RMN.Address() } + } else { + if chainState.MockRMN == nil { + rmn, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract] { + rmnAddress, tx2, rmnC, err2 := mock_rmn_contract.DeployMockRMNContract( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*mock_rmn_contract.MockRMNContract]{ + Address: rmnAddress, Contract: rmnC, Tx: tx2, Tv: deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0), Err: err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy mock RMN", "chain", chain.String(), "err", err) + return err + } + rmnAddr = rmn.Address + } else { + lggr.Infow("Mock RMN already deployed", "chain", chain.String(), "addr", chainState.MockRMN.Address) + rmnAddr = chainState.MockRMN.Address() + } + } + if rmnProxy == nil { rmnProxyContract, err := deployment.DeployContract(lggr, chain, ab, func(chain deployment.Chain) deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract] { rmnProxyAddr, tx2, rmnProxy, err2 := rmn_proxy_contract.DeployRMNProxyContract( chain.DeployerKey, chain.Client, - rmn.Address, + rmnAddr, ) return deployment.ContractDeploy[*rmn_proxy_contract.RMNProxyContract]{ - rmnProxyAddr, rmnProxy, tx2, deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), err2, + Address: rmnProxyAddr, Contract: rmnProxy, Tx: tx2, Tv: deployment.NewTypeAndVersion(ARMProxy, deployment.Version1_0_0), Err: err2, } }) if err != nil { - lggr.Errorw("Failed to deploy RMNProxyExisting", "chain", chain.String(), "err", err) + lggr.Errorw("Failed to deploy RMNProxy", "chain", chain.String(), "err", err) return err } rmnProxy = rmnProxyContract.Contract + } else { + lggr.Infow("RMNProxy already deployed", "chain", chain.String(), "addr", rmnProxy.Address) + // check if the RMNProxy is pointing to the correct RMN contract + currentRMNAddr, err := rmnProxy.GetARM(nil) + if err != nil { + lggr.Errorw("Failed to get RMN from RMNProxy", "chain", chain.String(), "err", err) + return err + } + if currentRMNAddr != rmnAddr { + lggr.Infow("RMNProxy is not pointing to the correct RMN contract, updating RMN", "chain", chain.String(), "currentRMN", currentRMNAddr, "expectedRMN", rmnAddr) + rmnOwner, err := rmnProxy.Owner(nil) + if err != nil { + lggr.Errorw("Failed to get owner of RMNProxy", "chain", chain.String(), "err", err) + return err + } + if rmnOwner != chain.DeployerKey.From { + lggr.Warnw( + "RMNProxy is not owned by the deployer and RMNProxy is not pointing to the correct RMN contract, "+ + "run SetRMNRemoteOnRMNProxy to update RMN with a proposal", + "chain", chain.String(), "owner", rmnOwner, "currentRMN", currentRMNAddr, "expectedRMN", rmnAddr) + } else { + tx, err := rmnProxy.SetARM(chain.DeployerKey, rmnAddr) + if err != nil { + lggr.Errorw("Failed to set RMN on RMNProxy", "chain", chain.String(), "err", err) + return err + } + _, err = chain.Confirm(tx) + if err != nil { + lggr.Errorw("Failed to confirm setRMN on RMNProxy", "chain", chain.String(), "err", err) + return err + } + } + } } if tokenAdminReg == nil { tokenAdminRegistry, err := deployment.DeployContract(e.Logger, chain, ab, @@ -176,7 +259,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address chain.DeployerKey, chain.Client) return deployment.ContractDeploy[*token_admin_registry.TokenAdminRegistry]{ - tokenAdminRegistryAddr, tokenAdminRegistry, tx2, deployment.NewTypeAndVersion(TokenAdminRegistry, deployment.Version1_5_0), err2, + Address: tokenAdminRegistryAddr, Contract: tokenAdminRegistry, Tx: tx2, Tv: deployment.NewTypeAndVersion(TokenAdminRegistry, deployment.Version1_5_0), Err: err2, } }) if err != nil { @@ -195,7 +278,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address chain.Client, tokenAdminReg.Address()) return deployment.ContractDeploy[*registry_module_owner_custom.RegistryModuleOwnerCustom]{ - regModAddr, regMod, tx2, deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0), err2, + Address: regModAddr, Contract: regMod, Tx: tx2, Tv: deployment.NewTypeAndVersion(RegistryModule, deployment.Version1_5_0), Err: err2, } }) if err != nil { @@ -233,7 +316,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address chain.Client, ) return deployment.ContractDeploy[*weth9.WETH9]{ - weth9Addr, weth9c, tx2, deployment.NewTypeAndVersion(WETH9, deployment.Version1_0_0), err2, + Address: weth9Addr, Contract: weth9c, Tx: tx2, Tv: deployment.NewTypeAndVersion(WETH9, deployment.Version1_0_0), Err: err2, } }) if err != nil { @@ -242,8 +325,10 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address } weth9Contract = weth.Contract } else { - lggr.Infow("weth9 already deployed", "addr", weth9Contract.Address) + lggr.Infow("weth9 already deployed", "chain", chain.String(), "addr", weth9Contract.Address) + weth9Contract = chainState.Weth9 } + // if router is not already deployed, we deploy it if r == nil { routerContract, err := deployment.DeployContract(e.Logger, chain, ab, @@ -255,7 +340,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address rmnProxy.Address(), ) return deployment.ContractDeploy[*router.Router]{ - routerAddr, routerC, tx2, deployment.NewTypeAndVersion(Router, deployment.Version1_2_0), err2, + Address: routerAddr, Contract: routerC, Tx: tx2, Tv: deployment.NewTypeAndVersion(Router, deployment.Version1_2_0), Err: err2, } }) if err != nil { @@ -275,7 +360,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address chain.Client, ) return deployment.ContractDeploy[*multicall3.Multicall3]{ - multicall3Addr, multicall3Wrapper, tx2, deployment.NewTypeAndVersion(Multicall3, deployment.Version1_0_0), err2, + Address: multicall3Addr, Contract: multicall3Wrapper, Tx: tx2, Tv: deployment.NewTypeAndVersion(Multicall3, deployment.Version1_0_0), Err: err2, } }) if err != nil { @@ -287,7 +372,7 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address e.Logger.Info("ccip multicall already deployed", "chain", chain.String(), "addr", mc3.Address) } } - if isUSDC { + if deployOpts.USDCEnabled { token, pool, messenger, transmitter, err1 := DeployUSDC(e.Logger, chain, ab, rmnProxy.Address(), r.Address()) if err1 != nil { return err1 @@ -300,5 +385,49 @@ func deployPrerequisiteContracts(e deployment.Environment, ab deployment.Address "messenger", messenger.Address(), ) } + if chainState.Receiver == nil { + _, err := deployment.DeployContract(e.Logger, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver] { + receiverAddr, tx, receiver, err2 := maybe_revert_message_receiver.DeployMaybeRevertMessageReceiver( + chain.DeployerKey, + chain.Client, + false, + ) + return deployment.ContractDeploy[*maybe_revert_message_receiver.MaybeRevertMessageReceiver]{ + Address: receiverAddr, Contract: receiver, Tx: tx, Tv: deployment.NewTypeAndVersion(CCIPReceiver, deployment.Version1_0_0), Err: err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy receiver", "chain", chain.String(), "err", err) + return err + } + } else { + e.Logger.Infow("receiver already deployed", "addr", chainState.Receiver.Address, "chain", chain.String()) + } + // Only applicable if setting up for 1.5 version, remove this once we have fully migrated to 1.6 + if deployOpts.LegacyDeploymentCfg != nil { + if chainState.PriceRegistry == nil { + _, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*price_registry_1_2_0.PriceRegistry] { + priceRegAddr, tx2, priceRegAddrC, err2 := price_registry_1_2_0.DeployPriceRegistry( + chain.DeployerKey, + chain.Client, + nil, + []common.Address{weth9Contract.Address(), chainState.LinkToken.Address()}, + deployOpts.LegacyDeploymentCfg.PriceRegStalenessThreshold, + ) + return deployment.ContractDeploy[*price_registry_1_2_0.PriceRegistry]{ + Address: priceRegAddr, Contract: priceRegAddrC, Tx: tx2, + Tv: deployment.NewTypeAndVersion(PriceRegistry, deployment.Version1_2_0), Err: err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy PriceRegistry", "chain", chain.String(), "err", err) + return err + } + } else { + lggr.Infow("PriceRegistry already deployed", "chain", chain.String(), "addr", chainState.PriceRegistry.Address) + } + } return nil } diff --git a/deployment/ccip/changeset/cs_prerequisites_test.go b/deployment/ccip/changeset/cs_prerequisites_test.go index da1ff9c83a9..5835bd41aa3 100644 --- a/deployment/ccip/changeset/cs_prerequisites_test.go +++ b/deployment/ccip/changeset/cs_prerequisites_test.go @@ -20,7 +20,11 @@ func TestDeployPrerequisites(t *testing.T) { }) newChain := e.AllChainSelectors()[0] cfg := DeployPrerequisiteConfig{ - ChainSelectors: []uint64{newChain}, + Configs: []DeployPrerequisiteConfigPerChain{ + { + ChainSelector: newChain, + }, + }, } output, err := DeployPrerequisites(e, cfg) require.NoError(t, err) diff --git a/deployment/ccip/changeset/cs_update_rmn_config_test.go b/deployment/ccip/changeset/cs_update_rmn_config_test.go index 07bf22720c2..e7543e22cb7 100644 --- a/deployment/ccip/changeset/cs_update_rmn_config_test.go +++ b/deployment/ccip/changeset/cs_update_rmn_config_test.go @@ -224,8 +224,12 @@ func TestSetRMNRemoteOnRMNProxy(t *testing.T) { allChains := e.Env.AllChainSelectors() mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) var err error + var prereqCfgs []DeployPrerequisiteConfigPerChain for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t) + prereqCfgs = append(prereqCfgs, DeployPrerequisiteConfigPerChain{ + ChainSelector: c, + }) } // 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 @@ -237,7 +241,7 @@ func TestSetRMNRemoteOnRMNProxy(t *testing.T) { { Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), Config: DeployPrerequisiteConfig{ - ChainSelectors: allChains, + Configs: prereqCfgs, }, }, { diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index ccd6176a9f7..b96279d8956 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -6,6 +6,11 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" burn_mint_token_pool "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/burn_mint_token_pool_1_4_0" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/erc20" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_messenger" @@ -25,7 +30,7 @@ import ( commoncs "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" common_v1_0 "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_rmn_contract" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/registry_module_owner_custom" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/rmn_home" @@ -49,8 +54,10 @@ import ( ) var ( - // Legacy - CommitStore deployment.ContractType = "CommitStore" + // Legacy + CommitStore deployment.ContractType = "CommitStore" + PriceRegistry deployment.ContractType = "PriceRegistry" + RMN deployment.ContractType = "RMN" // Not legacy MockRMN deployment.ContractType = "MockRMN" @@ -97,10 +104,8 @@ type CCIPChainState struct { TokenAdminRegistry *token_admin_registry.TokenAdminRegistry RegistryModule *registry_module_owner_custom.RegistryModuleOwnerCustom Router *router.Router - CommitStore *commit_store.CommitStore Weth9 *weth9.WETH9 RMNRemote *rmn_remote.RMNRemote - MockRMN *mock_rmn_contract.MockRMNContract // Map between token Descriptor (e.g. LinkSymbol, WethSymbol) // and the respective token contract // This is more of an illustration of how we'll have tokens, and it might need some work later to work properly. @@ -123,6 +128,14 @@ type CCIPChainState struct { MockUSDCTransmitter *mock_usdc_token_transmitter.MockE2EUSDCTransmitter MockUSDCTokenMessenger *mock_usdc_token_messenger.MockE2EUSDCTokenMessenger Multicall3 *multicall3.Multicall3 + + // Legacy contracts + EVM2EVMOnRamp map[uint64]*evm_2_evm_onramp.EVM2EVMOnRamp // mapping of dest chain selector -> EVM2EVMOnRamp + CommitStore map[uint64]*commit_store.CommitStore // mapping of source chain selector -> CommitStore + EVM2EVMOffRamp map[uint64]*evm_2_evm_offramp.EVM2EVMOffRamp // mapping of source chain selector -> EVM2EVMOffRamp + MockRMN *mock_rmn_contract.MockRMNContract + PriceRegistry *price_registry_1_2_0.PriceRegistry + RMN *rmn_contract.RMNContract } func (c CCIPChainState) GenerateView() (view.ChainView, error) { @@ -153,7 +166,7 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { if err != nil { return chainView, errors.Wrapf(err, "failed to generate rmn remote view for rmn remote %s", c.RMNRemote.Address().String()) } - chainView.RMN[c.RMNRemote.Address().Hex()] = rmnView + chainView.RMNRemote[c.RMNRemote.Address().Hex()] = rmnView } if c.RMNHome != nil { @@ -195,14 +208,6 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { chainView.OffRamp[c.OffRamp.Address().Hex()] = offRampView } - if c.CommitStore != nil { - commitStoreView, err := v1_5.GenerateCommitStoreView(c.CommitStore) - if err != nil { - return chainView, errors.Wrapf(err, "failed to generate commit store view for commit store %s", c.CommitStore.Address().String()) - } - chainView.CommitStore[c.CommitStore.Address().Hex()] = commitStoreView - } - if c.RMNProxy != nil { rmnProxyView, err := v1_0.GenerateRMNProxyView(c.RMNProxy) if err != nil { @@ -245,6 +250,53 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { } chainView.StaticLinkToken = staticLinkTokenView } + // Legacy contracts + if c.CommitStore != nil { + for source, commitStore := range c.CommitStore { + commitStoreView, err := v1_5.GenerateCommitStoreView(commitStore) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate commit store view for commit store %s for source %d", commitStore.Address().String(), source) + } + chainView.CommitStore[commitStore.Address().Hex()] = commitStoreView + } + } + + if c.PriceRegistry != nil { + priceRegistryView, err := v1_2.GeneratePriceRegistryView(c.PriceRegistry) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate price registry view for price registry %s", c.PriceRegistry.Address().String()) + } + chainView.PriceRegistry[c.PriceRegistry.Address().String()] = priceRegistryView + } + + if c.RMN != nil { + rmnView, err := v1_5.GenerateRMNView(c.RMN) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate rmn view for rmn %s", c.RMN.Address().String()) + } + chainView.RMN[c.RMN.Address().Hex()] = rmnView + } + + if c.EVM2EVMOffRamp != nil { + for source, offRamp := range c.EVM2EVMOffRamp { + offRampView, err := v1_5.GenerateOffRampView(offRamp) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate off ramp view for off ramp %s for source %d", offRamp.Address().String(), source) + } + chainView.EVM2EVMOffRamp[offRamp.Address().Hex()] = offRampView + } + } + + if c.EVM2EVMOnRamp != nil { + for dest, onRamp := range c.EVM2EVMOnRamp { + onRampView, err := v1_5.GenerateOnRampView(onRamp) + if err != nil { + return chainView, errors.Wrapf(err, "failed to generate on ramp view for on ramp %s for dest %d", onRamp.Address().String(), dest) + } + chainView.EVM2EVMOnRamp[onRamp.Address().Hex()] = onRampView + } + } + return chainView, nil } @@ -312,7 +364,11 @@ func (s CCIPOnChainState) View(chains []uint64) (map[string]view.ChainView, erro if err != nil { return m, err } - m[chainInfo.ChainName] = chainView + name := chainInfo.ChainName + if chainInfo.ChainName == "" { + name = fmt.Sprintf("%d", chainSelector) + } + m[name] = chainView } return m, nil } @@ -394,12 +450,6 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.RMNProxy = armProxy - case deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0).String(): - mockRMN, err := mock_rmn_contract.NewMockRMNContract(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.MockRMN = mockRMN case deployment.NewTypeAndVersion(RMNRemote, deployment.Version1_6_0_dev).String(): rmnRemote, err := rmn_remote.NewRMNRemote(common.HexToAddress(address), chain.Client) if err != nil { @@ -424,12 +474,6 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.NonceManager = nm - case deployment.NewTypeAndVersion(CommitStore, deployment.Version1_5_0).String(): - cs, err := commit_store.NewCommitStore(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.CommitStore = cs case deployment.NewTypeAndVersion(TokenAdminRegistry, deployment.Version1_5_0).String(): tm, err := token_admin_registry.NewTokenAdminRegistry(common.HexToAddress(address), chain.Client) if err != nil { @@ -555,6 +599,64 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, fmt.Errorf("failed to get token symbol of token at %s: %w", address, err) } state.BurnMintTokens677[TokenSymbol(symbol)] = tok + // legacy addresses below + case deployment.NewTypeAndVersion(OnRamp, deployment.Version1_5_0).String(): + onRampC, err := evm_2_evm_onramp.NewEVM2EVMOnRamp(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + sCfg, err := onRampC.GetStaticConfig(nil) + if err != nil { + return state, fmt.Errorf("failed to get static config chain %s: %w", chain.String(), err) + } + if state.EVM2EVMOnRamp == nil { + state.EVM2EVMOnRamp = make(map[uint64]*evm_2_evm_onramp.EVM2EVMOnRamp) + } + state.EVM2EVMOnRamp[sCfg.DestChainSelector] = onRampC + case deployment.NewTypeAndVersion(OffRamp, deployment.Version1_5_0).String(): + offRamp, err := evm_2_evm_offramp.NewEVM2EVMOffRamp(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + sCfg, err := offRamp.GetStaticConfig(nil) + if err != nil { + return state, err + } + if state.EVM2EVMOffRamp == nil { + state.EVM2EVMOffRamp = make(map[uint64]*evm_2_evm_offramp.EVM2EVMOffRamp) + } + state.EVM2EVMOffRamp[sCfg.SourceChainSelector] = offRamp + case deployment.NewTypeAndVersion(CommitStore, deployment.Version1_5_0).String(): + commitStore, err := commit_store.NewCommitStore(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + sCfg, err := commitStore.GetStaticConfig(nil) + if err != nil { + return state, err + } + if state.CommitStore == nil { + state.CommitStore = make(map[uint64]*commit_store.CommitStore) + } + state.CommitStore[sCfg.SourceChainSelector] = commitStore + case deployment.NewTypeAndVersion(PriceRegistry, deployment.Version1_2_0).String(): + pr, err := price_registry_1_2_0.NewPriceRegistry(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.PriceRegistry = pr + case deployment.NewTypeAndVersion(RMN, deployment.Version1_5_0).String(): + rmnC, err := rmn_contract.NewRMNContract(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.RMN = rmnC + case deployment.NewTypeAndVersion(MockRMN, deployment.Version1_0_0).String(): + mockRMN, err := mock_rmn_contract.NewMockRMNContract(common.HexToAddress(address), chain.Client) + if err != nil { + return state, err + } + state.MockRMN = mockRMN default: return state, fmt.Errorf("unknown contract %s", tvStr) } diff --git a/deployment/ccip/changeset/test_environment.go b/deployment/ccip/changeset/test_environment.go index 8e590da1703..1ab184573ce 100644 --- a/deployment/ccip/changeset/test_environment.go +++ b/deployment/ccip/changeset/test_environment.go @@ -43,10 +43,12 @@ type TestConfigs struct { CreateJob bool // TODO: This should be CreateContracts so the booleans make sense? CreateJobAndContracts bool - Chains int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input - NumOfUsersPerChain int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input - Nodes int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input - Bootstraps int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + LegacyDeployment bool + Chains int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + ChainIDs []uint64 // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + NumOfUsersPerChain int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + Nodes int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input + Bootstraps int // only used in memory mode, for docker mode, this is determined by the integration-test config toml input IsUSDC bool IsUSDCAttestationMissing bool IsMultiCall3 bool @@ -104,6 +106,18 @@ func WithMultiCall3() TestOps { } } +func WithLegacyDeployment() TestOps { + return func(testCfg *TestConfigs) { + testCfg.LegacyDeployment = true + } +} + +func WithChainIds(chainIDs []uint64) TestOps { + return func(testCfg *TestConfigs) { + testCfg.ChainIDs = chainIDs + } +} + func WithJobsOnly() TestOps { return func(testCfg *TestConfigs) { testCfg.CreateJobAndContracts = false @@ -219,7 +233,7 @@ func (d *DeployedEnv) SetupJobs(t *testing.T) { type MemoryEnvironment struct { DeployedEnv - chains map[uint64]deployment.Chain + Chains map[uint64]deployment.Chain } func (m *MemoryEnvironment) DeployedEnvironment() DeployedEnv { @@ -228,14 +242,29 @@ func (m *MemoryEnvironment) DeployedEnvironment() DeployedEnv { func (m *MemoryEnvironment) StartChains(t *testing.T, tc *TestConfigs) { ctx := testcontext.Get(t) - chains, users := memory.NewMemoryChains(t, tc.Chains, tc.NumOfUsersPerChain) - m.chains = chains + var chains map[uint64]deployment.Chain + var users map[uint64][]*bind.TransactOpts + if len(tc.ChainIDs) > 0 { + chains, users = memory.NewMemoryChainsWithChainIDs(t, tc.ChainIDs, tc.NumOfUsersPerChain) + if tc.Chains > len(tc.ChainIDs) { + additionalChains, additionalUsers := memory.NewMemoryChains(t, tc.Chains-len(tc.ChainIDs), tc.NumOfUsersPerChain) + for k, v := range additionalChains { + chains[k] = v + } + for k, v := range additionalUsers { + users[k] = v + } + } + } else { + chains, users = memory.NewMemoryChains(t, tc.Chains, tc.NumOfUsersPerChain) + } + m.Chains = chains homeChainSel, feedSel := allocateCCIPChainSelectors(chains) replayBlocks, err := LatestBlocksByChain(ctx, chains) require.NoError(t, err) m.DeployedEnv = DeployedEnv{ Env: deployment.Environment{ - Chains: m.chains, + Chains: m.Chains, }, HomeChainSel: homeChainSel, FeedChainSel: feedSel, @@ -245,9 +274,9 @@ func (m *MemoryEnvironment) StartChains(t *testing.T, tc *TestConfigs) { } func (m *MemoryEnvironment) StartNodes(t *testing.T, tc *TestConfigs, crConfig deployment.CapabilityRegistryConfig) { - require.NotNil(t, m.chains, "start chains first, chains are empty") + require.NotNil(t, m.Chains, "start chains first, chains are empty") require.NotNil(t, m.DeployedEnv, "start chains and initiate deployed env first before starting nodes") - nodes := memory.NewNodes(t, zapcore.InfoLevel, m.chains, tc.Nodes, tc.Bootstraps, crConfig) + nodes := memory.NewNodes(t, zapcore.InfoLevel, m.Chains, tc.Nodes, tc.Bootstraps, crConfig) ctx := testcontext.Get(t) lggr := logger.Test(t) for _, node := range nodes { @@ -256,7 +285,7 @@ func (m *MemoryEnvironment) StartNodes(t *testing.T, tc *TestConfigs, crConfig d require.NoError(t, node.App.Stop()) }) } - m.DeployedEnv.Env = memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, m.chains, nodes) + m.DeployedEnv.Env = memory.NewMemoryEnvironmentFromChainsNodes(func() context.Context { return ctx }, lggr, m.Chains, nodes) } func (m *MemoryEnvironment) MockUSDCAttestationServer(t *testing.T, isUSDCAttestationMissing bool) string { @@ -276,6 +305,9 @@ func NewMemoryEnvironment(t *testing.T, opts ...TestOps) DeployedEnv { } require.NoError(t, testCfg.Validate(), "invalid test config") env := &MemoryEnvironment{} + if testCfg.LegacyDeployment { + return NewLegacyEnvironment(t, testCfg, env) + } if testCfg.CreateJobAndContracts { return NewEnvironmentWithJobsAndContracts(t, testCfg, env) } @@ -285,6 +317,59 @@ func NewMemoryEnvironment(t *testing.T, opts ...TestOps) DeployedEnv { return NewEnvironment(t, testCfg, env) } +func NewLegacyEnvironment(t *testing.T, tc *TestConfigs, tEnv TestEnvironment) DeployedEnv { + var err error + tEnv.StartChains(t, tc) + e := tEnv.DeployedEnvironment() + require.NotEmpty(t, e.Env.Chains) + tEnv.StartNodes(t, tc, deployment.CapabilityRegistryConfig{}) + e = tEnv.DeployedEnvironment() + allChains := e.Env.AllChainSelectors() + + mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, c := range e.Env.AllChainSelectors() { + mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t) + } + var prereqCfg []DeployPrerequisiteConfigPerChain + for _, chain := range allChains { + var opts []PrerequisiteOpt + if tc != nil { + if tc.IsUSDC { + opts = append(opts, WithUSDCEnabled()) + } + if tc.IsMultiCall3 { + opts = append(opts, WithMultiCall3Enabled()) + } + } + opts = append(opts, WithLegacyDeploymentEnabled(LegacyDeploymentConfig{ + PriceRegStalenessThreshold: 60 * 60 * 24 * 14, // two weeks + })) + prereqCfg = append(prereqCfg, DeployPrerequisiteConfigPerChain{ + ChainSelector: chain, + Opts: opts, + }) + } + + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), + Config: allChains, + }, + { + Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), + Config: DeployPrerequisiteConfig{ + Configs: prereqCfg, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), + Config: mcmsCfg, + }, + }) + require.NoError(t, err) + return e +} + func NewEnvironment(t *testing.T, tc *TestConfigs, tEnv TestEnvironment) DeployedEnv { lggr := logger.Test(t) tEnv.StartChains(t, tc) @@ -322,15 +407,22 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tc *TestConfigs, tEnv Test for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = proposalutils.SingleGroupTimelockConfig(t) } - var ( - usdcChains []uint64 - isMulticall3 bool - ) - if tc != nil { - if tc.IsUSDC { - usdcChains = allChains + + var prereqCfg []DeployPrerequisiteConfigPerChain + for _, chain := range allChains { + var opts []PrerequisiteOpt + if tc != nil { + if tc.IsUSDC { + opts = append(opts, WithUSDCEnabled()) + } + if tc.IsMultiCall3 { + opts = append(opts, WithMultiCall3Enabled()) + } } - isMulticall3 = tc.IsMultiCall3 + prereqCfg = append(prereqCfg, DeployPrerequisiteConfigPerChain{ + ChainSelector: chain, + Opts: opts, + }) } // 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 @@ -342,11 +434,7 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tc *TestConfigs, tEnv Test { Changeset: commonchangeset.WrapChangeSet(DeployPrerequisites), Config: DeployPrerequisiteConfig{ - ChainSelectors: allChains, - Opts: []PrerequisiteOpt{ - WithUSDCChains(usdcChains), - WithMulticall3(isMulticall3), - }, + Configs: prereqCfg, }, }, { @@ -371,22 +459,19 @@ func NewEnvironmentWithJobsAndContracts(t *testing.T, tc *TestConfigs, tEnv Test state, err := LoadOnchainState(e.Env) require.NoError(t, err) - // Assert USDC set up as expected. - 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) - } // Assert link present require.NotNil(t, state.Chains[e.FeedChainSel].LinkToken) require.NotNil(t, state.Chains[e.FeedChainSel].Weth9) tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) var tokenDataProviders []pluginconfig.TokenDataObserverConfig - if len(usdcChains) > 0 { + if tc.IsUSDC { endpoint := tEnv.MockUSDCAttestationServer(t, tc.IsUSDCAttestationMissing) cctpContracts := make(map[cciptypes.ChainSelector]pluginconfig.USDCCCTPTokenConfig) - for _, usdcChain := range usdcChains { + for _, usdcChain := range allChains { + require.NotNil(t, state.Chains[usdcChain].MockUSDCTokenMessenger) + require.NotNil(t, state.Chains[usdcChain].MockUSDCTransmitter) + require.NotNil(t, state.Chains[usdcChain].USDCTokenPool) cctpContracts[cciptypes.ChainSelector(usdcChain)] = pluginconfig.USDCCCTPTokenConfig{ SourcePoolAddress: state.Chains[usdcChain].USDCTokenPool.Address().String(), SourceMessageTransmitterAddr: state.Chains[usdcChain].MockUSDCTransmitter.Address().String(), diff --git a/deployment/ccip/changeset/v1_5/cs_jobspec.go b/deployment/ccip/changeset/v1_5/cs_jobspec.go new file mode 100644 index 00000000000..bdb36d531f8 --- /dev/null +++ b/deployment/ccip/changeset/v1_5/cs_jobspec.go @@ -0,0 +1,149 @@ +package v1_5 + +import ( + "fmt" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/config" + integrationtesthelpers "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers/integration" +) + +type JobSpecsForLanesConfig struct { + Configs []JobSpecInput +} + +func (c JobSpecsForLanesConfig) Validate() error { + for _, cfg := range c.Configs { + if err := cfg.Validate(); err != nil { + return fmt.Errorf("invalid JobSpecInput: %w", err) + } + } + return nil +} + +type JobSpecInput struct { + SourceChainSelector uint64 + DestinationChainSelector uint64 + DestEVMChainID uint64 + DestinationStartBlock uint64 + TokenPricesUSDPipeline string + PriceGetterConfigJson string + USDCAttestationAPI string + USDCCfg *config.USDCConfig +} + +func (j JobSpecInput) Validate() error { + if err := deployment.IsValidChainSelector(j.SourceChainSelector); err != nil { + return fmt.Errorf("SourceChainSelector is invalid: %w", err) + } + if err := deployment.IsValidChainSelector(j.DestinationChainSelector); err != nil { + return fmt.Errorf("DestinationChainSelector is invalid: %w", err) + } + if j.TokenPricesUSDPipeline == "" && j.PriceGetterConfigJson == "" { + return fmt.Errorf("TokenPricesUSDPipeline or PriceGetterConfigJson is required") + } + if j.USDCCfg != nil { + if err := j.USDCCfg.ValidateUSDCConfig(); err != nil { + return fmt.Errorf("USDCCfg is invalid: %w", err) + } + if j.USDCAttestationAPI == "" { + return fmt.Errorf("USDCAttestationAPI is required") + } + } + return nil +} + +func JobSpecsForLanes(env deployment.Environment, c JobSpecsForLanesConfig) (deployment.ChangesetOutput, error) { + if err := c.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid JobSpecsForLanesConfig: %w", err) + } + state, err := changeset.LoadOnchainState(env) + if err != nil { + return deployment.ChangesetOutput{}, err + } + nodesToJobSpecs, err := jobSpecsForLane(env, state, c) + if err != nil { + return deployment.ChangesetOutput{}, err + } + return deployment.ChangesetOutput{ + JobSpecs: nodesToJobSpecs, + }, nil +} + +func jobSpecsForLane( + env deployment.Environment, + state changeset.CCIPOnChainState, + lanesCfg JobSpecsForLanesConfig, +) (map[string][]string, error) { + nodes, err := deployment.NodeInfo(env.NodeIDs, env.Offchain) + if err != nil { + return nil, err + } + nodesToJobSpecs := make(map[string][]string) + for _, node := range nodes { + var specs []string + for _, cfg := range lanesCfg.Configs { + destChainState := state.Chains[cfg.DestinationChainSelector] + sourceChain := env.Chains[cfg.SourceChainSelector] + destChain := env.Chains[cfg.DestinationChainSelector] + + ccipJobParam := integrationtesthelpers.CCIPJobSpecParams{ + OffRamp: destChainState.EVM2EVMOffRamp[cfg.SourceChainSelector].Address(), + CommitStore: destChainState.CommitStore[cfg.SourceChainSelector].Address(), + SourceChainName: sourceChain.Name(), + DestChainName: destChain.Name(), + DestEvmChainId: cfg.DestEVMChainID, + TokenPricesUSDPipeline: cfg.TokenPricesUSDPipeline, + PriceGetterConfig: cfg.PriceGetterConfigJson, + DestStartBlock: cfg.DestinationStartBlock, + USDCAttestationAPI: cfg.USDCAttestationAPI, + USDCConfig: cfg.USDCCfg, + P2PV2Bootstrappers: nodes.BootstrapLocators(), + } + if !node.IsBootstrap { + ocrCfg, found := node.OCRConfigForChainSelector(cfg.DestinationChainSelector) + if !found { + return nil, fmt.Errorf("OCR config not found for chain %s", destChain.String()) + } + ocrKeyBundleID := ocrCfg.KeyBundleID + transmitterID := ocrCfg.TransmitAccount + commitSpec, err := ccipJobParam.CommitJobSpec() + if err != nil { + return nil, fmt.Errorf("failed to generate commit job spec for source %s and destination %s: %w", + sourceChain.String(), destChain.String(), err) + } + commitSpec.OCR2OracleSpec.OCRKeyBundleID.SetValid(ocrKeyBundleID) + commitSpec.OCR2OracleSpec.TransmitterID.SetValid(string(transmitterID)) + commitSpecStr, err := commitSpec.String() + if err != nil { + return nil, fmt.Errorf("failed to convert commit job spec to string for source %s and destination %s: %w", + sourceChain.String(), destChain.String(), err) + } + execSpec, err := ccipJobParam.ExecutionJobSpec() + if err != nil { + return nil, fmt.Errorf("failed to generate execution job spec for source %s and destination %s: %w", + sourceChain.String(), destChain.String(), err) + } + execSpec.OCR2OracleSpec.OCRKeyBundleID.SetValid(ocrKeyBundleID) + execSpec.OCR2OracleSpec.TransmitterID.SetValid(string(transmitterID)) + execSpecStr, err := execSpec.String() + if err != nil { + return nil, fmt.Errorf("failed to convert execution job spec to string for source %s and destination %s: %w", + sourceChain.String(), destChain.String(), err) + } + specs = append(specs, commitSpecStr, execSpecStr) + } else { + bootstrapSpec := ccipJobParam.BootstrapJob(destChainState.CommitStore[cfg.SourceChainSelector].Address().String()) + bootstrapSpecStr, err := bootstrapSpec.String() + if err != nil { + return nil, fmt.Errorf("failed to convert bootstrap job spec to string for source %s and destination %s: %w", + sourceChain.String(), destChain.String(), err) + } + specs = append(specs, bootstrapSpecStr) + } + } + nodesToJobSpecs[node.NodeID] = append(nodesToJobSpecs[node.NodeID], specs...) + } + return nodesToJobSpecs, nil +} diff --git a/deployment/ccip/changeset/v1_5/cs_lane_contracts.go b/deployment/ccip/changeset/v1_5/cs_lane_contracts.go new file mode 100644 index 00000000000..2d6c8fcb5ed --- /dev/null +++ b/deployment/ccip/changeset/v1_5/cs_lane_contracts.go @@ -0,0 +1,288 @@ +package v1_5 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" +) + +var _ deployment.ChangeSet[DeployLanesConfig] = DeployLanes + +type DeployLanesConfig struct { + Configs []DeployLaneConfig +} + +func (c *DeployLanesConfig) Validate(e deployment.Environment, state changeset.CCIPOnChainState) error { + for _, cfg := range c.Configs { + if err := cfg.Validate(e, state); err != nil { + return err + } + } + return nil +} + +type DeployLaneConfig struct { + SourceChainSelector uint64 + DestinationChainSelector uint64 + + // onRamp specific configuration + OnRampStaticCfg evm_2_evm_onramp.EVM2EVMOnRampStaticConfig + OnRampDynamicCfg evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig + OnRampFeeTokenArgs []evm_2_evm_onramp.EVM2EVMOnRampFeeTokenConfigArgs + OnRampTransferTokenCfgs []evm_2_evm_onramp.EVM2EVMOnRampTokenTransferFeeConfigArgs + OnRampNopsAndWeight []evm_2_evm_onramp.EVM2EVMOnRampNopAndWeight + OnRampRateLimiterCfg evm_2_evm_onramp.RateLimiterConfig + + // offRamp specific configuration + OffRampRateLimiterCfg evm_2_evm_offramp.RateLimiterConfig + + // Price Registry specific configuration + InitialTokenPrices []price_registry_1_2_0.InternalTokenPriceUpdate + GasPriceUpdates []price_registry_1_2_0.InternalGasPriceUpdate +} + +func (c *DeployLaneConfig) Validate(e deployment.Environment, state changeset.CCIPOnChainState) error { + if err := deployment.IsValidChainSelector(c.SourceChainSelector); err != nil { + return err + } + if err := deployment.IsValidChainSelector(c.DestinationChainSelector); err != nil { + return err + } + sourceChain, exists := e.Chains[c.SourceChainSelector] + if !exists { + return fmt.Errorf("source chain %d not found in environment", c.SourceChainSelector) + } + destChain, exists := e.Chains[c.DestinationChainSelector] + if !exists { + return fmt.Errorf("destination chain %d not found in environment", c.DestinationChainSelector) + } + sourceChainState, exists := state.Chains[c.SourceChainSelector] + if !exists { + return fmt.Errorf("source chain %d not found in state", c.SourceChainSelector) + } + destChainState, exists := state.Chains[c.DestinationChainSelector] + if !exists { + return fmt.Errorf("destination chain %d not found in state", c.DestinationChainSelector) + } + // check for existing chain contracts on both source and destination chains + if err := arePrerequisitesMet(sourceChainState, sourceChain); err != nil { + return err + } + if err := arePrerequisitesMet(destChainState, destChain); err != nil { + return err + } + // TODO: Add rest of the config validation + return nil +} + +func DeployLanes(env deployment.Environment, c DeployLanesConfig) (deployment.ChangesetOutput, error) { + state, err := changeset.LoadOnchainState(env) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load CCIP onchain state: %w", err) + } + if err := c.Validate(env, state); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid DeployChainContractsConfig: %w", err) + } + newAddresses := deployment.NewMemoryAddressBook() + for _, cfg := range c.Configs { + if err := deployLane(env, state, newAddresses, cfg); err != nil { + return deployment.ChangesetOutput{ + AddressBook: newAddresses, + }, err + } + } + return deployment.ChangesetOutput{ + AddressBook: newAddresses, + }, nil +} + +func deployLane(e deployment.Environment, state changeset.CCIPOnChainState, ab deployment.AddressBook, cfg DeployLaneConfig) error { + // update prices on the source price registry + sourceChainState := state.Chains[cfg.SourceChainSelector] + destChainState := state.Chains[cfg.DestinationChainSelector] + sourceChain := e.Chains[cfg.SourceChainSelector] + destChain := e.Chains[cfg.DestinationChainSelector] + sourcePriceReg := sourceChainState.PriceRegistry + tx, err := sourcePriceReg.UpdatePrices(sourceChain.DeployerKey, price_registry_1_2_0.InternalPriceUpdates{ + TokenPriceUpdates: cfg.InitialTokenPrices, + GasPriceUpdates: cfg.GasPriceUpdates, + }) + if err != nil { + return err + } + _, err = sourceChain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm price update tx for chain %s: %w", sourceChain.String(), deployment.MaybeDataErr(err)) + } + // ================================================================ + // │ Deploy Lane │ + // ================================================================ + // Deploy onRamp on source chain + onRamp, onRampExists := sourceChainState.EVM2EVMOnRamp[cfg.DestinationChainSelector] + if !onRampExists { + onRampC, err := deployment.DeployContract(e.Logger, sourceChain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*evm_2_evm_onramp.EVM2EVMOnRamp] { + onRampAddress, tx2, onRampC, err2 := evm_2_evm_onramp.DeployEVM2EVMOnRamp( + sourceChain.DeployerKey, + sourceChain.Client, + cfg.OnRampStaticCfg, + cfg.OnRampDynamicCfg, + cfg.OnRampRateLimiterCfg, + cfg.OnRampFeeTokenArgs, + cfg.OnRampTransferTokenCfgs, + cfg.OnRampNopsAndWeight, + ) + return deployment.ContractDeploy[*evm_2_evm_onramp.EVM2EVMOnRamp]{ + Address: onRampAddress, Contract: onRampC, Tx: tx2, + Tv: deployment.NewTypeAndVersion(changeset.OnRamp, deployment.Version1_5_0), Err: err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy EVM2EVMOnRamp", "chain", sourceChain.String(), "err", err) + return err + } + onRamp = onRampC.Contract + } else { + e.Logger.Infow("EVM2EVMOnRamp already exists", + "source chain", sourceChain.String(), "destination chain", destChain.String(), + "address", onRamp.Address().String()) + } + + // Deploy commit store on source chain + commitStore, commitStoreExists := destChainState.CommitStore[cfg.SourceChainSelector] + if !commitStoreExists { + commitStoreC, err := deployment.DeployContract(e.Logger, destChain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*commit_store.CommitStore] { + commitStoreAddress, tx2, commitStoreC, err2 := commit_store.DeployCommitStore( + destChain.DeployerKey, + destChain.Client, + commit_store.CommitStoreStaticConfig{ + ChainSelector: destChain.Selector, + SourceChainSelector: sourceChain.Selector, + OnRamp: onRamp.Address(), + RmnProxy: destChainState.RMNProxy.Address(), + }, + ) + return deployment.ContractDeploy[*commit_store.CommitStore]{ + Address: commitStoreAddress, Contract: commitStoreC, Tx: tx2, + Tv: deployment.NewTypeAndVersion(changeset.CommitStore, deployment.Version1_5_0), Err: err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy CommitStore", "chain", sourceChain.String(), "err", err) + return err + } + commitStore = commitStoreC.Contract + } else { + e.Logger.Infow("CommitStore already exists", + "source chain", sourceChain.String(), "destination chain", destChain.String(), + "address", commitStore.Address().String()) + } + + // Deploy offRamp on destination chain + offRamp, offRampExists := destChainState.EVM2EVMOffRamp[cfg.SourceChainSelector] + if !offRampExists { + offRampC, err := deployment.DeployContract(e.Logger, destChain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*evm_2_evm_offramp.EVM2EVMOffRamp] { + offRampAddress, tx2, offRampC, err2 := evm_2_evm_offramp.DeployEVM2EVMOffRamp( + destChain.DeployerKey, + destChain.Client, + evm_2_evm_offramp.EVM2EVMOffRampStaticConfig{ + CommitStore: commitStore.Address(), + ChainSelector: destChain.Selector, + SourceChainSelector: sourceChain.Selector, + OnRamp: onRamp.Address(), + PrevOffRamp: common.HexToAddress(""), + RmnProxy: destChainState.RMNProxy.Address(), // RMN, formerly ARM + TokenAdminRegistry: destChainState.TokenAdminRegistry.Address(), + }, + cfg.OffRampRateLimiterCfg, + ) + return deployment.ContractDeploy[*evm_2_evm_offramp.EVM2EVMOffRamp]{ + Address: offRampAddress, Contract: offRampC, Tx: tx2, + Tv: deployment.NewTypeAndVersion(changeset.OffRamp, deployment.Version1_5_0), Err: err2, + } + }) + if err != nil { + e.Logger.Errorw("Failed to deploy EVM2EVMOffRamp", "chain", sourceChain.String(), "err", err) + return err + } + offRamp = offRampC.Contract + } else { + e.Logger.Infow("EVM2EVMOffRamp already exists", + "source chain", sourceChain.String(), "destination chain", destChain.String(), + "address", offRamp.Address().String()) + } + + // Apply Router updates + tx, err = sourceChainState.Router.ApplyRampUpdates(sourceChain.DeployerKey, + []router.RouterOnRamp{{DestChainSelector: destChain.Selector, OnRamp: onRamp.Address()}}, nil, nil) + if err != nil { + return fmt.Errorf("failed to apply router updates for source chain %s: %w", sourceChain.String(), deployment.MaybeDataErr(err)) + } + _, err = sourceChain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm router updates tx %s for source chain %s: %w", tx.Hash().String(), sourceChain.String(), deployment.MaybeDataErr(err)) + } + + tx, err = destChainState.Router.ApplyRampUpdates(destChain.DeployerKey, + nil, + nil, + []router.RouterOffRamp{{SourceChainSelector: sourceChain.Selector, OffRamp: offRamp.Address()}}, + ) + if err != nil { + return fmt.Errorf("failed to apply router updates for destination chain %s: %w", destChain.String(), deployment.MaybeDataErr(err)) + } + _, err = destChain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm router updates tx %s for destination chain %s: %w", tx.Hash().String(), destChain.String(), deployment.MaybeDataErr(err)) + } + + // price registry updates + _, err = destChainState.PriceRegistry.ApplyPriceUpdatersUpdates( + destChain.DeployerKey, + []common.Address{commitStore.Address()}, + []common.Address{}, + ) + if err != nil { + return fmt.Errorf("failed to apply price registry updates for destination chain %s: %w", destChain.String(), deployment.MaybeDataErr(err)) + } + _, err = destChain.Confirm(tx) + if err != nil { + return fmt.Errorf("failed to confirm price registry updates tx %s for destination chain %s: %w", tx.Hash().String(), destChain.String(), deployment.MaybeDataErr(err)) + } + return nil +} + +func arePrerequisitesMet(chainState changeset.CCIPChainState, chain deployment.Chain) error { + if chainState.Router == nil { + return fmt.Errorf("router not found for chain %s", chain.String()) + } + if chainState.PriceRegistry == nil { + return fmt.Errorf("price registry not found for chain %s", chain.String()) + } + if chainState.RMN == nil && chainState.MockRMN == nil { + return fmt.Errorf("neither RMN nor mockRMN found for chain %s", chain.String()) + } + if chainState.Weth9 == nil { + return fmt.Errorf("WETH9 not found for chain %s", chain.String()) + } + if chainState.LinkToken == nil { + return fmt.Errorf("LINK token not found for chain %s", chain.String()) + } + if chainState.TokenAdminRegistry == nil { + return fmt.Errorf("token admin registry not found for chain %s", chain.String()) + } + if chainState.RMNProxy == nil { + return fmt.Errorf("RMNProxy not found for chain %s", chain.String()) + } + return nil +} diff --git a/deployment/ccip/changeset/v1_5/cs_ocr2_config.go b/deployment/ccip/changeset/v1_5/cs_ocr2_config.go new file mode 100644 index 00000000000..497bcb53ad8 --- /dev/null +++ b/deployment/ccip/changeset/v1_5/cs_ocr2_config.go @@ -0,0 +1,329 @@ +package v1_5 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/pkg/errors" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers" +) + +type FinalOCR2Config struct { + Signers []common.Address + Transmitters []common.Address + F uint8 + OnchainConfig []byte + OffchainConfigVersion uint64 + OffchainConfig []byte +} + +type CommitOCR2ConfigParams struct { + DestinationChainSelector uint64 + SourceChainSelector uint64 + OCR2ConfigParams confighelper.PublicConfig + GasPriceHeartBeat config.Duration + DAGasPriceDeviationPPB uint32 + ExecGasPriceDeviationPPB uint32 + TokenPriceHeartBeat config.Duration + TokenPriceDeviationPPB uint32 + InflightCacheExpiry config.Duration + PriceReportingDisabled bool +} + +func (c *CommitOCR2ConfigParams) PopulateOffChainAndOnChainCfg(priceReg common.Address) error { + var err error + c.OCR2ConfigParams.ReportingPluginConfig, err = testhelpers.NewCommitOffchainConfig( + c.GasPriceHeartBeat, + c.DAGasPriceDeviationPPB, + c.ExecGasPriceDeviationPPB, + c.TokenPriceHeartBeat, + c.TokenPriceDeviationPPB, + c.InflightCacheExpiry, + c.PriceReportingDisabled, + ).Encode() + if err != nil { + return errors.Wrapf(err, "failed to encode offchain config for source chain %d and destination chain %d", + c.SourceChainSelector, c.DestinationChainSelector) + } + c.OCR2ConfigParams.OnchainConfig, err = abihelpers.EncodeAbiStruct(testhelpers.NewCommitOnchainConfig(priceReg)) + if err != nil { + return fmt.Errorf("failed to encode onchain config for source chain %d and destination chain %d: %w", + c.SourceChainSelector, c.DestinationChainSelector, err) + } + return nil +} + +func (c *CommitOCR2ConfigParams) Validate(state changeset.CCIPOnChainState) error { + if err := deployment.IsValidChainSelector(c.DestinationChainSelector); err != nil { + return fmt.Errorf("invalid DestinationChainSelector: %w", err) + } + if err := deployment.IsValidChainSelector(c.SourceChainSelector); err != nil { + return fmt.Errorf("invalid SourceChainSelector: %w", err) + } + + chain, exists := state.Chains[c.DestinationChainSelector] + if !exists { + return fmt.Errorf("chain %d does not exist in state", c.DestinationChainSelector) + } + if chain.CommitStore == nil { + return fmt.Errorf("chain %d does not have a commit store", c.DestinationChainSelector) + } + _, exists = chain.CommitStore[c.SourceChainSelector] + if !exists { + return fmt.Errorf("chain %d does not have a commit store for source chain %d", c.DestinationChainSelector, c.SourceChainSelector) + } + if chain.PriceRegistry == nil { + return fmt.Errorf("chain %d does not have a price registry", c.DestinationChainSelector) + } + return nil +} + +type ExecuteOCR2ConfigParams struct { + DestinationChainSelector uint64 + SourceChainSelector uint64 + DestOptimisticConfirmations uint32 + BatchGasLimit uint32 + RelativeBoostPerWaitHour float64 + InflightCacheExpiry config.Duration + RootSnoozeTime config.Duration + BatchingStrategyID uint32 + MessageVisibilityInterval config.Duration + ExecOnchainConfig evm_2_evm_offramp.EVM2EVMOffRampDynamicConfig + OCR2ConfigParams confighelper.PublicConfig +} + +func (e *ExecuteOCR2ConfigParams) PopulateOffChainAndOnChainCfg(router, priceReg common.Address) error { + var err error + e.OCR2ConfigParams.ReportingPluginConfig, err = testhelpers.NewExecOffchainConfig( + e.DestOptimisticConfirmations, + e.BatchGasLimit, + e.RelativeBoostPerWaitHour, + e.InflightCacheExpiry, + e.RootSnoozeTime, + e.BatchingStrategyID, + ).Encode() + if err != nil { + return fmt.Errorf("failed to encode offchain config for exec plugin, source chain %d dest chain %d :%w", + e.SourceChainSelector, e.DestinationChainSelector, err) + } + e.OCR2ConfigParams.OnchainConfig, err = abihelpers.EncodeAbiStruct(testhelpers.NewExecOnchainConfig( + e.ExecOnchainConfig.PermissionLessExecutionThresholdSeconds, + router, + priceReg, + e.ExecOnchainConfig.MaxNumberOfTokensPerMsg, + e.ExecOnchainConfig.MaxDataBytes, + )) + if err != nil { + return fmt.Errorf("failed to encode onchain config for exec plugin, source chain %d dest chain %d :%w", + e.SourceChainSelector, e.DestinationChainSelector, err) + } + return nil +} + +func (e *ExecuteOCR2ConfigParams) Validate(state changeset.CCIPOnChainState) error { + if err := deployment.IsValidChainSelector(e.SourceChainSelector); err != nil { + return fmt.Errorf("invalid SourceChainSelector: %w", err) + } + if err := deployment.IsValidChainSelector(e.DestinationChainSelector); err != nil { + return fmt.Errorf("invalid DestinationChainSelector: %w", err) + } + chain, exists := state.Chains[e.DestinationChainSelector] + if !exists { + return fmt.Errorf("chain %d does not exist in state", e.DestinationChainSelector) + } + if chain.EVM2EVMOffRamp == nil { + return fmt.Errorf("chain %d does not have an EVM2EVMOffRamp", e.DestinationChainSelector) + } + _, exists = chain.EVM2EVMOffRamp[e.SourceChainSelector] + if !exists { + return fmt.Errorf("chain %d does not have an EVM2EVMOffRamp for source chain %d", e.DestinationChainSelector, e.SourceChainSelector) + } + if chain.PriceRegistry == nil { + return fmt.Errorf("chain %d does not have a price registry", e.DestinationChainSelector) + } + if chain.Router == nil { + return fmt.Errorf("chain %d does not have a router", e.DestinationChainSelector) + } + return nil +} + +type OCR2Config struct { + CommitConfigs []CommitOCR2ConfigParams + ExecConfigs []ExecuteOCR2ConfigParams +} + +func (o OCR2Config) Validate(state changeset.CCIPOnChainState) error { + for _, c := range o.CommitConfigs { + if err := c.Validate(state); err != nil { + return err + } + } + for _, e := range o.ExecConfigs { + if err := e.Validate(state); err != nil { + return err + } + } + return nil +} + +// SetOCR2ConfigForTest sets the OCR2 config on the chain for commit and offramp +// This is currently not suitable for prod environments it's only for testing +func SetOCR2ConfigForTest(env deployment.Environment, c OCR2Config) (deployment.ChangesetOutput, error) { + state, err := changeset.LoadOnchainState(env) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to load CCIP onchain state: %w", err) + } + if err := c.Validate(state); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid OCR2 config: %w", err) + } + for _, commit := range c.CommitConfigs { + if err := commit.PopulateOffChainAndOnChainCfg(state.Chains[commit.DestinationChainSelector].PriceRegistry.Address()); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to populate offchain and onchain config for commit: %w", err) + } + finalCfg, err := deriveOCR2Config(env, commit.DestinationChainSelector, commit.OCR2ConfigParams) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to derive OCR2 config for commit: %w", err) + } + commitStore := state.Chains[commit.DestinationChainSelector].CommitStore[commit.SourceChainSelector] + chain := env.Chains[commit.DestinationChainSelector] + tx, err := commitStore.SetOCR2Config( + chain.DeployerKey, + finalCfg.Signers, + finalCfg.Transmitters, + finalCfg.F, + finalCfg.OnchainConfig, + finalCfg.OffchainConfigVersion, + finalCfg.OffchainConfig, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to set OCR2 config for commit store %s on chain %s: %w", + commitStore.Address().String(), chain.String(), deployment.MaybeDataErr(err)) + } + _, err = chain.Confirm(tx) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm OCR2 for commit store %s config on chain %s: %w", + commitStore.Address().String(), chain.String(), err) + } + } + for _, exec := range c.ExecConfigs { + if err := exec.PopulateOffChainAndOnChainCfg( + state.Chains[exec.DestinationChainSelector].Router.Address(), + state.Chains[exec.DestinationChainSelector].PriceRegistry.Address()); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to populate offchain and onchain config for offramp: %w", err) + } + finalCfg, err := deriveOCR2Config(env, exec.DestinationChainSelector, exec.OCR2ConfigParams) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to derive OCR2 config for offramp: %w", err) + } + offRamp := state.Chains[exec.DestinationChainSelector].EVM2EVMOffRamp[exec.SourceChainSelector] + chain := env.Chains[exec.DestinationChainSelector] + tx, err := offRamp.SetOCR2Config( + chain.DeployerKey, + finalCfg.Signers, + finalCfg.Transmitters, + finalCfg.F, + finalCfg.OnchainConfig, + finalCfg.OffchainConfigVersion, + finalCfg.OffchainConfig, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to set OCR2 config for offramp %s on chain %s: %w", + offRamp.Address().String(), chain.String(), err) + } + _, err = chain.Confirm(tx) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to confirm OCR2 for offramp %s config on chain %s: %w", + offRamp.Address().String(), chain.String(), err) + } + } + return deployment.ChangesetOutput{}, nil +} + +func deriveOCR2Config( + env deployment.Environment, + chainSel uint64, + ocrParams confighelper.PublicConfig, +) (FinalOCR2Config, error) { + nodeInfo, err := deployment.NodeInfo(env.NodeIDs, env.Offchain) + if err != nil { + return FinalOCR2Config{}, fmt.Errorf("failed to get node info: %w", err) + } + nodes := nodeInfo.NonBootstraps() + // Get OCR3 Config from helper + var schedule []int + var oracles []confighelper.OracleIdentityExtra + for _, node := range nodes { + schedule = append(schedule, 1) + cfg, exists := node.OCRConfigForChainSelector(chainSel) + if !exists { + return FinalOCR2Config{}, fmt.Errorf("no OCR config for chain %d", chainSel) + } + oracles = append(oracles, confighelper.OracleIdentityExtra{ + OracleIdentity: confighelper.OracleIdentity{ + OnchainPublicKey: cfg.OnchainPublicKey, + TransmitAccount: cfg.TransmitAccount, + OffchainPublicKey: cfg.OffchainPublicKey, + PeerID: cfg.PeerID.Raw(), + }, + ConfigEncryptionPublicKey: cfg.ConfigEncryptionPublicKey, + }) + } + + signers, transmitters, threshold, onchainConfig, offchainConfigVersion, offchainConfig, err := confighelper.ContractSetConfigArgsForTests( + ocrParams.DeltaProgress, + ocrParams.DeltaResend, + ocrParams.DeltaRound, + ocrParams.DeltaGrace, + ocrParams.DeltaStage, + ocrParams.RMax, + schedule, + oracles, + ocrParams.ReportingPluginConfig, + nil, + ocrParams.MaxDurationQuery, + ocrParams.MaxDurationObservation, + ocrParams.MaxDurationReport, + ocrParams.MaxDurationShouldAcceptFinalizedReport, + ocrParams.MaxDurationShouldTransmitAcceptedReport, + int(nodes.DefaultF()), + ocrParams.OnchainConfig, + ) + if err != nil { + return FinalOCR2Config{}, fmt.Errorf("failed to derive OCR2 config: %w", err) + } + var signersAddresses []common.Address + for _, signer := range signers { + if len(signer) != 20 { + return FinalOCR2Config{}, fmt.Errorf("address is not 20 bytes %s", signer) + } + signersAddresses = append(signersAddresses, common.BytesToAddress(signer)) + } + var transmittersAddresses []common.Address + for _, transmitter := range transmitters { + bytes, err := hexutil.Decode(string(transmitter)) + if err != nil { + return FinalOCR2Config{}, errors.Wrap(err, fmt.Sprintf("given address is not valid %s", transmitter)) + } + if len(bytes) != 20 { + return FinalOCR2Config{}, errors.Errorf("address is not 20 bytes %s", transmitter) + } + transmittersAddresses = append(transmittersAddresses, common.BytesToAddress(bytes)) + } + return FinalOCR2Config{ + Signers: signersAddresses, + Transmitters: transmittersAddresses, + F: threshold, + OnchainConfig: onchainConfig, + OffchainConfigVersion: offchainConfigVersion, + OffchainConfig: offchainConfig, + }, nil +} diff --git a/deployment/ccip/changeset/v1_5/e2e_test.go b/deployment/ccip/changeset/v1_5/e2e_test.go new file mode 100644 index 00000000000..11bb566c641 --- /dev/null +++ b/deployment/ccip/changeset/v1_5/e2e_test.go @@ -0,0 +1,56 @@ +package v1_5 + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + chainselectors "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" +) + +// This test only works if the destination chain id is 1337 +// Otherwise it shows error for offchain and onchain config digest mismatch +func TestE2ELegacy(t *testing.T) { + e := changeset.NewMemoryEnvironment( + t, + changeset.WithLegacyDeployment(), + changeset.WithChains(3), + changeset.WithChainIds([]uint64{chainselectors.GETH_TESTNET.EvmChainID})) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + allChains := e.Env.AllChainSelectorsExcluding([]uint64{chainselectors.GETH_TESTNET.Selector}) + require.Contains(t, e.Env.AllChainSelectors(), chainselectors.GETH_TESTNET.Selector) + require.Len(t, allChains, 2) + src, dest := allChains[1], chainselectors.GETH_TESTNET.Selector + srcChain := e.Env.Chains[src] + destChain := e.Env.Chains[dest] + pairs := []changeset.SourceDestPair{ + {SourceChainSelector: src, DestChainSelector: dest}, + } + e.Env = AddLanes(t, e.Env, state, pairs) + // reload state after adding lanes + state, err = changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + sentEvent, err := SendRequest(t, e.Env, state, + changeset.WithSourceChain(src), + changeset.WithDestChain(dest), + changeset.WithTestRouter(false), + changeset.WithEvm2AnyMessage(router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }), + ) + require.NoError(t, err) + require.NotNil(t, sentEvent) + destStartBlock, err := destChain.Client.HeaderByNumber(context.Background(), nil) + require.NoError(t, err) + WaitForCommit(t, srcChain, destChain, state.Chains[dest].CommitStore[src], sentEvent.Message.SequenceNumber) + WaitForExecute(t, srcChain, destChain, state.Chains[dest].EVM2EVMOffRamp[src], []uint64{sentEvent.Message.SequenceNumber}, destStartBlock.Number.Uint64()) +} diff --git a/deployment/ccip/changeset/v1_5/test_helpers.go b/deployment/ccip/changeset/v1_5/test_helpers.go new file mode 100644 index 00000000000..e1a03539a77 --- /dev/null +++ b/deployment/ccip/changeset/v1_5/test_helpers.go @@ -0,0 +1,384 @@ +package v1_5 + +import ( + "context" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/libocr/offchainreporting2plus/confighelper" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_2_0" + "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers" +) + +func AddLanes(t *testing.T, e deployment.Environment, state changeset.CCIPOnChainState, pairs []changeset.SourceDestPair) deployment.Environment { + addLanesCfg, commitOCR2Configs, execOCR2Configs, jobspecs := LaneConfigsForChains(t, e, state, pairs) + var err error + e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(DeployLanes), + Config: DeployLanesConfig{ + Configs: addLanesCfg, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(SetOCR2ConfigForTest), + Config: OCR2Config{ + CommitConfigs: commitOCR2Configs, + ExecConfigs: execOCR2Configs, + }, + }, + { + Changeset: commonchangeset.WrapChangeSet(JobSpecsForLanes), + Config: JobSpecsForLanesConfig{ + Configs: jobspecs, + }, + }, + }) + require.NoError(t, err) + return e +} + +func LaneConfigsForChains(t *testing.T, env deployment.Environment, state changeset.CCIPOnChainState, pairs []changeset.SourceDestPair) ( + []DeployLaneConfig, + []CommitOCR2ConfigParams, + []ExecuteOCR2ConfigParams, + []JobSpecInput, +) { + var addLanesCfg []DeployLaneConfig + var commitOCR2Configs []CommitOCR2ConfigParams + var execOCR2Configs []ExecuteOCR2ConfigParams + var jobSpecs []JobSpecInput + for _, pair := range pairs { + dest := pair.DestChainSelector + src := pair.SourceChainSelector + sourceChainState := state.Chains[src] + destChainState := state.Chains[dest] + require.NotNil(t, sourceChainState.LinkToken) + require.NotNil(t, sourceChainState.RMNProxy) + require.NotNil(t, sourceChainState.TokenAdminRegistry) + require.NotNil(t, sourceChainState.Router) + require.NotNil(t, sourceChainState.PriceRegistry) + require.NotNil(t, sourceChainState.Weth9) + require.NotNil(t, destChainState.LinkToken) + require.NotNil(t, destChainState.RMNProxy) + require.NotNil(t, destChainState.TokenAdminRegistry) + tokenPrice, _, _ := CreatePricesPipeline(t, state, src, dest) + block, err := env.Chains[dest].Client.HeaderByNumber(context.Background(), nil) + require.NoError(t, err) + destEVMChainIdStr, err := chain_selectors.GetChainIDFromSelector(dest) + require.NoError(t, err) + destEVMChainId, err := strconv.ParseUint(destEVMChainIdStr, 10, 64) + require.NoError(t, err) + jobSpecs = append(jobSpecs, JobSpecInput{ + SourceChainSelector: src, + DestinationChainSelector: dest, + DestEVMChainID: destEVMChainId, + TokenPricesUSDPipeline: tokenPrice, + DestinationStartBlock: block.Number.Uint64(), + }) + addLanesCfg = append(addLanesCfg, DeployLaneConfig{ + SourceChainSelector: src, + DestinationChainSelector: dest, + OnRampStaticCfg: evm_2_evm_onramp.EVM2EVMOnRampStaticConfig{ + LinkToken: sourceChainState.LinkToken.Address(), + ChainSelector: src, + DestChainSelector: dest, + DefaultTxGasLimit: 200_000, + MaxNopFeesJuels: big.NewInt(0).Mul(big.NewInt(100_000_000), big.NewInt(1e18)), + PrevOnRamp: common.Address{}, + RmnProxy: sourceChainState.RMNProxy.Address(), + TokenAdminRegistry: sourceChainState.TokenAdminRegistry.Address(), + }, + OnRampDynamicCfg: evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig{ + Router: sourceChainState.Router.Address(), + MaxNumberOfTokensPerMsg: 5, + DestGasOverhead: 350_000, + DestGasPerPayloadByte: 16, + DestDataAvailabilityOverheadGas: 33_596, + DestGasPerDataAvailabilityByte: 16, + DestDataAvailabilityMultiplierBps: 6840, + PriceRegistry: sourceChainState.PriceRegistry.Address(), + MaxDataBytes: 1e5, + MaxPerMsgGasLimit: 4_000_000, + DefaultTokenFeeUSDCents: 50, + DefaultTokenDestGasOverhead: 125_000, + }, + OnRampFeeTokenArgs: []evm_2_evm_onramp.EVM2EVMOnRampFeeTokenConfigArgs{ + { + Token: sourceChainState.LinkToken.Address(), + NetworkFeeUSDCents: 1_00, + GasMultiplierWeiPerEth: 1e18, + PremiumMultiplierWeiPerEth: 9e17, + Enabled: true, + }, + { + Token: sourceChainState.Weth9.Address(), + NetworkFeeUSDCents: 1_00, + GasMultiplierWeiPerEth: 1e18, + PremiumMultiplierWeiPerEth: 1e18, + Enabled: true, + }, + }, + OnRampTransferTokenCfgs: []evm_2_evm_onramp.EVM2EVMOnRampTokenTransferFeeConfigArgs{ + { + Token: sourceChainState.LinkToken.Address(), + MinFeeUSDCents: 50, // $0.5 + MaxFeeUSDCents: 1_000_000_00, // $ 1 million + DeciBps: 5_0, // 5 bps + DestGasOverhead: 350_000, + DestBytesOverhead: 32, + AggregateRateLimitEnabled: true, + }, + }, + OnRampNopsAndWeight: []evm_2_evm_onramp.EVM2EVMOnRampNopAndWeight{}, + OnRampRateLimiterCfg: evm_2_evm_onramp.RateLimiterConfig{ + IsEnabled: true, + Capacity: testhelpers.LinkUSDValue(100), + Rate: testhelpers.LinkUSDValue(1), + }, + OffRampRateLimiterCfg: evm_2_evm_offramp.RateLimiterConfig{ + IsEnabled: true, + Capacity: testhelpers.LinkUSDValue(100), + Rate: testhelpers.LinkUSDValue(1), + }, + InitialTokenPrices: []price_registry_1_2_0.InternalTokenPriceUpdate{ + { + SourceToken: sourceChainState.LinkToken.Address(), + UsdPerToken: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(20)), + }, + { + SourceToken: sourceChainState.Weth9.Address(), + UsdPerToken: new(big.Int).Mul(big.NewInt(1e18), big.NewInt(2000)), + }, + }, + GasPriceUpdates: []price_registry_1_2_0.InternalGasPriceUpdate{ + { + DestChainSelector: dest, + UsdPerUnitGas: big.NewInt(20000e9), + }, + }, + }) + commitOCR2Configs = append(commitOCR2Configs, CommitOCR2ConfigParams{ + SourceChainSelector: src, + DestinationChainSelector: dest, + OCR2ConfigParams: DefaultOCRParams(), + GasPriceHeartBeat: *config.MustNewDuration(10 * time.Second), + DAGasPriceDeviationPPB: 1, + ExecGasPriceDeviationPPB: 1, + TokenPriceHeartBeat: *config.MustNewDuration(10 * time.Second), + TokenPriceDeviationPPB: 1, + InflightCacheExpiry: *config.MustNewDuration(5 * time.Second), + PriceReportingDisabled: false, + }) + execOCR2Configs = append(execOCR2Configs, ExecuteOCR2ConfigParams{ + DestinationChainSelector: dest, + SourceChainSelector: src, + DestOptimisticConfirmations: 1, + BatchGasLimit: 5_000_000, + RelativeBoostPerWaitHour: 0.07, + InflightCacheExpiry: *config.MustNewDuration(1 * time.Minute), + RootSnoozeTime: *config.MustNewDuration(1 * time.Minute), + BatchingStrategyID: 0, + MessageVisibilityInterval: config.Duration{}, + ExecOnchainConfig: evm_2_evm_offramp.EVM2EVMOffRampDynamicConfig{ + PermissionLessExecutionThresholdSeconds: uint32(24 * time.Hour.Seconds()), + MaxDataBytes: 1e5, + MaxNumberOfTokensPerMsg: 5, + }, + OCR2ConfigParams: DefaultOCRParams(), + }) + } + return addLanesCfg, commitOCR2Configs, execOCR2Configs, jobSpecs +} + +func CreatePricesPipeline(t *testing.T, state changeset.CCIPOnChainState, source, dest uint64) (string, *httptest.Server, *httptest.Server) { + sourceRouter := state.Chains[source].Router + destRouter := state.Chains[dest].Router + destLink := state.Chains[dest].LinkToken + linkUSD := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(`{"UsdPerLink": "8000000000000000000"}`)) + require.NoError(t, err) + })) + t.Cleanup(linkUSD.Close) + + ethUSD := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(`{"UsdPerETH": "1700000000000000000000"}`)) + require.NoError(t, err) + })) + t.Cleanup(ethUSD.Close) + + sourceWrappedNative, err := sourceRouter.GetWrappedNative(nil) + require.NoError(t, err) + destWrappedNative, err := destRouter.GetWrappedNative(nil) + require.NoError(t, err) + tokenPricesUSDPipeline := fmt.Sprintf(` +// Price 1 +link [type=http method=GET url="%s"]; +link_parse [type=jsonparse path="UsdPerLink"]; +link->link_parse; +eth [type=http method=GET url="%s"]; +eth_parse [type=jsonparse path="UsdPerETH"]; +eth->eth_parse; +merge [type=merge left="{}" right="{\\\"%s\\\":$(link_parse), \\\"%s\\\":$(eth_parse), \\\"%s\\\":$(eth_parse)}"];`, + linkUSD.URL, ethUSD.URL, destLink.Address(), sourceWrappedNative, destWrappedNative) + + return tokenPricesUSDPipeline, linkUSD, ethUSD +} + +func DefaultOCRParams() confighelper.PublicConfig { + return confighelper.PublicConfig{ + DeltaProgress: 2 * time.Second, + DeltaResend: 1 * time.Second, + DeltaRound: 1 * time.Second, + DeltaGrace: 500 * time.Millisecond, + DeltaStage: 2 * time.Second, + RMax: 3, + MaxDurationInitialization: nil, + MaxDurationQuery: 50 * time.Millisecond, + MaxDurationObservation: 1 * time.Second, + MaxDurationReport: 100 * time.Millisecond, + MaxDurationShouldAcceptFinalizedReport: 100 * time.Millisecond, + MaxDurationShouldTransmitAcceptedReport: 100 * time.Millisecond, + } +} + +func SendRequest( + t *testing.T, + e deployment.Environment, + state changeset.CCIPOnChainState, + opts ...changeset.SendReqOpts, +) (*evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested, error) { + cfg := &changeset.CCIPSendReqConfig{} + for _, opt := range opts { + opt(cfg) + } + // Set default sender if not provided + if cfg.Sender == nil { + cfg.Sender = e.Chains[cfg.SourceChain].DeployerKey + } + t.Logf("Sending CCIP request from chain selector %d to chain selector %d from sender %s", + cfg.SourceChain, cfg.DestChain, cfg.Sender.From.String()) + tx, blockNum, err := changeset.CCIPSendRequest(e, state, cfg) + if err != nil { + return nil, err + } + + onRamp := state.Chains[cfg.SourceChain].EVM2EVMOnRamp[cfg.DestChain] + + it, err := onRamp.FilterCCIPSendRequested(&bind.FilterOpts{ + Start: blockNum, + End: &blockNum, + Context: context.Background(), + }) + if err != nil { + return nil, err + } + + require.True(t, it.Next()) + t.Logf("CCIP message (id %x) sent from chain selector %d to chain selector %d tx %s seqNum %d sender %s", + it.Event.Message.MessageId[:], + cfg.SourceChain, + cfg.DestChain, + tx.Hash().String(), + it.Event.Message.SequenceNumber, + it.Event.Message.Sender.String(), + ) + return it.Event, nil +} + +func WaitForCommit( + t *testing.T, + src deployment.Chain, + dest deployment.Chain, + commitStore *commit_store.CommitStore, + seqNr uint64, +) { + timer := time.NewTimer(5 * time.Minute) + defer timer.Stop() + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if backend, ok := src.Client.(*memory.Backend); ok { + backend.Commit() + } + if backend, ok := dest.Client.(*memory.Backend); ok { + backend.Commit() + } + minSeqNr, err := commitStore.GetExpectedNextSequenceNumber(nil) + require.NoError(t, err) + t.Logf("Waiting for commit for sequence number %d, current min sequence number %d", seqNr, minSeqNr) + if minSeqNr > seqNr { + t.Logf("Commit for sequence number %d found", seqNr) + return + } + case <-timer.C: + t.Fatalf("timed out waiting for commit for sequence number %d for commit store %s ", seqNr, commitStore.Address().String()) + return + } + } +} + +func WaitForExecute( + t *testing.T, + src deployment.Chain, + dest deployment.Chain, + offRamp *evm_2_evm_offramp.EVM2EVMOffRamp, + seqNrs []uint64, + blockNum uint64, +) { + timer := time.NewTimer(5 * time.Minute) + defer timer.Stop() + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if backend, ok := src.Client.(*memory.Backend); ok { + backend.Commit() + } + if backend, ok := dest.Client.(*memory.Backend); ok { + backend.Commit() + } + t.Logf("Waiting for execute for sequence numbers %v", seqNrs) + it, err := offRamp.FilterExecutionStateChanged( + &bind.FilterOpts{ + Start: blockNum, + }, seqNrs, [][32]byte{}) + require.NoError(t, err) + for it.Next() { + t.Logf("Execution state changed for sequence number=%d current state=%d", it.Event.SequenceNumber, it.Event.State) + if cciptypes.MessageExecutionState(it.Event.State) == cciptypes.ExecutionStateSuccess { + t.Logf("Execution for sequence number %d found", it.Event.SequenceNumber) + return + } + t.Logf("Execution for sequence number %d resulted in status %d", it.Event.SequenceNumber, it.Event.State) + t.Fail() + } + case <-timer.C: + t.Fatalf("timed out waiting for execute for sequence numbers %v for offramp %s ", seqNrs, offRamp.Address().String()) + return + } + } +} diff --git a/deployment/ccip/view/view.go b/deployment/ccip/view/view.go index 4f216d13008..8f698ba2277 100644 --- a/deployment/ccip/view/view.go +++ b/deployment/ccip/view/view.go @@ -19,11 +19,16 @@ type ChainView struct { // v1.5 TokenAdminRegistry map[string]v1_5.TokenAdminRegistryView `json:"tokenAdminRegistry,omitempty"` CommitStore map[string]v1_5.CommitStoreView `json:"commitStore,omitempty"` + PriceRegistry map[string]v1_2.PriceRegistryView `json:"priceRegistry,omitempty"` + EVM2EVMOnRamp map[string]v1_5.OnRampView `json:"evm2evmOnRamp,omitempty"` + EVM2EVMOffRamp map[string]v1_5.OffRampView `json:"evm2evmOffRamp,omitempty"` + RMN map[string]v1_5.RMNView `json:"rmn,omitempty"` + // v1.6 FeeQuoter map[string]v1_6.FeeQuoterView `json:"feeQuoter,omitempty"` NonceManager map[string]v1_6.NonceManagerView `json:"nonceManager,omitempty"` + RMNRemote map[string]v1_6.RMNRemoteView `json:"rmnRemote,omitempty"` RMNHome map[string]v1_6.RMNHomeView `json:"rmnHome,omitempty"` - RMN map[string]v1_6.RMNRemoteView `json:"rmn,omitempty"` OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` // TODO: Perhaps restrict to one CCIPHome/CR? Shouldn't @@ -40,15 +45,19 @@ func NewChain() ChainView { // v1.0 RMNProxy: make(map[string]v1_0.RMNProxyView), // v1.2 - Router: make(map[string]v1_2.RouterView), + Router: make(map[string]v1_2.RouterView), + PriceRegistry: make(map[string]v1_2.PriceRegistryView), // v1.5 TokenAdminRegistry: make(map[string]v1_5.TokenAdminRegistryView), CommitStore: make(map[string]v1_5.CommitStoreView), + EVM2EVMOnRamp: make(map[string]v1_5.OnRampView), + EVM2EVMOffRamp: make(map[string]v1_5.OffRampView), + RMN: make(map[string]v1_5.RMNView), // v1.6 FeeQuoter: make(map[string]v1_6.FeeQuoterView), NonceManager: make(map[string]v1_6.NonceManagerView), + RMNRemote: make(map[string]v1_6.RMNRemoteView), RMNHome: make(map[string]v1_6.RMNHomeView), - RMN: make(map[string]v1_6.RMNRemoteView), OnRamp: make(map[string]v1_6.OnRampView), OffRamp: make(map[string]v1_6.OffRampView), CapabilityRegistry: make(map[string]common_v1_0.CapabilityRegistryView), diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index ff013717d30..92822422daa 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -18,9 +18,9 @@ import ( func TestDeployMCMSWithConfig(t *testing.T) { lggr := logger.TestLogger(t) - chains := memory.NewMemoryChainsWithChainIDs(t, []uint64{ + chains, _ := memory.NewMemoryChainsWithChainIDs(t, []uint64{ chainsel.TEST_90000001.EvmChainID, - }) + }, 1) ab := deployment.NewMemoryAddressBook() _, err := internal.DeployMCMSWithConfig(types.ProposerManyChainMultisig, lggr, chains[chainsel.TEST_90000001.Selector], ab, proposalutils.SingleGroupMCMS(t)) @@ -29,9 +29,9 @@ func TestDeployMCMSWithConfig(t *testing.T) { func TestDeployMCMSWithTimelockContracts(t *testing.T) { lggr := logger.TestLogger(t) - chains := memory.NewMemoryChainsWithChainIDs(t, []uint64{ + chains, _ := memory.NewMemoryChainsWithChainIDs(t, []uint64{ chainsel.TEST_90000001.EvmChainID, - }) + }, 1) ab := deployment.NewMemoryAddressBook() _, err := internal.DeployMCMSWithTimelockContracts(lggr, chains[chainsel.TEST_90000001.Selector], diff --git a/deployment/environment.go b/deployment/environment.go index 0823404da2d..bfbeac2f0c4 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -74,6 +74,9 @@ func (c Chain) Name() string { // we should never get here, if the selector is invalid it should not be in the environment panic(err) } + if chainInfo.ChainName == "" { + return fmt.Sprintf("%d", c.Selector) + } return chainInfo.ChainName } diff --git a/deployment/environment/crib/ccip_deployer.go b/deployment/environment/crib/ccip_deployer.go index aea7ad0cb8f..4c52cc72416 100644 --- a/deployment/environment/crib/ccip_deployer.go +++ b/deployment/environment/crib/ccip_deployer.go @@ -4,13 +4,15 @@ import ( "context" "errors" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/devenv" "github.com/smartcontractkit/chainlink/v2/core/services/relay" - "math/big" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" @@ -71,8 +73,9 @@ func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig de return DeployCCIPOutput{}, fmt.Errorf("failed to initiate new environment: %w", err) } e.ExistingAddresses = ab - allChainIds := e.AllChainSelectors() + chainSelectors := e.AllChainSelectors() cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + var prereqCfgs []changeset.DeployPrerequisiteConfigPerChain for _, chain := range e.AllChainSelectors() { mcmsConfig, err := config.NewConfig(1, []common.Address{e.Chains[chain].DeployerKey.From}, []config.Config{}) if err != nil { @@ -84,6 +87,9 @@ func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig de Proposer: *mcmsConfig, TimelockMinDelay: big.NewInt(0), } + prereqCfgs = append(prereqCfgs, changeset.DeployPrerequisiteConfigPerChain{ + ChainSelector: chain, + }) } // This will not apply any proposals because we pass nil to testing. @@ -91,12 +97,12 @@ func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig de *e, err = commonchangeset.ApplyChangesets(nil, *e, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), - Config: allChainIds, + Config: chainSelectors, }, { Changeset: commonchangeset.WrapChangeSet(changeset.DeployPrerequisites), Config: changeset.DeployPrerequisiteConfig{ - ChainSelectors: allChainIds, + Configs: prereqCfgs, }, }, { @@ -106,7 +112,7 @@ func DeployCCIPAndAddLanes(ctx context.Context, lggr logger.Logger, envConfig de { Changeset: commonchangeset.WrapChangeSet(changeset.DeployChainContracts), Config: changeset.DeployChainContractsConfig{ - ChainSelectors: allChainIds, + ChainSelectors: chainSelectors, HomeChainSelector: homeChainSel, }, }, diff --git a/deployment/environment/memory/chain.go b/deployment/environment/memory/chain.go index 40a20a02416..77a8f397d39 100644 --- a/deployment/environment/memory/chain.go +++ b/deployment/environment/memory/chain.go @@ -14,6 +14,7 @@ import ( chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" ) @@ -52,10 +53,10 @@ func GenerateChains(t *testing.T, numChains int, numUsers int) map[uint64]EVMCha return chains } -func GenerateChainsWithIds(t *testing.T, chainIDs []uint64) map[uint64]EVMChain { +func GenerateChainsWithIds(t *testing.T, chainIDs []uint64, numUsers int) map[uint64]EVMChain { chains := make(map[uint64]EVMChain) for _, chainID := range chainIDs { - chains[chainID] = evmChain(t, 1) + chains[chainID] = evmChain(t, numUsers) } return chains } diff --git a/deployment/environment/memory/environment.go b/deployment/environment/memory/environment.go index d6c80a92a44..a74d23a847b 100644 --- a/deployment/environment/memory/environment.go +++ b/deployment/environment/memory/environment.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" "testing" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" @@ -58,9 +59,15 @@ func NewMemoryChains(t *testing.T, numChains int, numUsers int) (map[uint64]depl return generateMemoryChain(t, mchains), users } -func NewMemoryChainsWithChainIDs(t *testing.T, chainIDs []uint64) map[uint64]deployment.Chain { - mchains := GenerateChainsWithIds(t, chainIDs) - return generateMemoryChain(t, mchains) +func NewMemoryChainsWithChainIDs(t *testing.T, chainIDs []uint64, numUsers int) (map[uint64]deployment.Chain, map[uint64][]*bind.TransactOpts) { + mchains := GenerateChainsWithIds(t, chainIDs, numUsers) + users := make(map[uint64][]*bind.TransactOpts) + for id, chain := range mchains { + sel, err := chainsel.SelectorFromChainId(id) + require.NoError(t, err) + users[sel] = chain.Users + } + return generateMemoryChain(t, mchains), users } func generateMemoryChain(t *testing.T, inputs map[uint64]EVMChain) map[uint64]deployment.Chain { @@ -80,10 +87,13 @@ func generateMemoryChain(t *testing.T, inputs map[uint64]EVMChain) map[uint64]de } for { backend.Commit() - receipt, err := backend.TransactionReceipt(context.Background(), tx.Hash()) + receipt, err := func() (*types.Receipt, error) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + return bind.WaitMined(ctx, backend, tx) + }() if err != nil { - t.Log("failed to get receipt", "chain", chainInfo.ChainName, err) - continue + return 0, fmt.Errorf("tx %s failed to confirm: %w, chain %d", tx.Hash().Hex(), err, chainInfo.ChainSelector) } if receipt.Status == 0 { errReason, err := deployment.GetErrorReasonFromTx(chain.Backend.Client(), chain.DeployerKey.From, tx, receipt) diff --git a/deployment/environment/memory/job_client.go b/deployment/environment/memory/job_client.go index 98fb90ceffa..a3cfee41608 100644 --- a/deployment/environment/memory/job_client.go +++ b/deployment/environment/memory/job_client.go @@ -20,6 +20,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/validate" "github.com/smartcontractkit/chainlink/v2/core/services/keystore/chaintype" + ocr2validate "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/validate" + "github.com/smartcontractkit/chainlink/v2/core/services/ocrbootstrap" ) type JobClient struct { @@ -295,7 +297,27 @@ func (j JobClient) ProposeJob(ctx context.Context, in *jobv1.ProposeJobRequest, // TODO: Use FMS jb, err := validate.ValidatedCCIPSpec(in.Spec) if err != nil { - return nil, err + if !strings.Contains(err.Error(), "the only supported type is currently 'ccip'") { + return nil, err + } + // check if it's offchainreporting2 job + jb, err = ocr2validate.ValidatedOracleSpecToml( + ctx, + n.App.GetConfig().OCR2(), + n.App.GetConfig().Insecure(), + in.Spec, + nil, // not required for validation + ) + if err != nil { + if !strings.Contains(err.Error(), "the only supported type is currently 'offchainreporting2'") { + return nil, err + } + // check if it's bootstrap job + jb, err = ocrbootstrap.ValidatedBootstrapSpecToml(in.Spec) + if err != nil { + return nil, fmt.Errorf("failed to validate job spec only ccip, bootstrap and offchainreporting2 are supported: %w", err) + } + } } err = n.App.AddJobV2(ctx, &jb) if err != nil { diff --git a/deployment/go.mod b/deployment/go.mod index d618c38e838..9d71da06329 100644 --- a/deployment/go.mod +++ b/deployment/go.mod @@ -364,6 +364,7 @@ require ( github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/onsi/gomega v1.34.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e // indirect diff --git a/deployment/go.sum b/deployment/go.sum index 76546853c38..9b142dc77dd 100644 --- a/deployment/go.sum +++ b/deployment/go.sum @@ -1225,8 +1225,8 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/ginkgo/v2 v2.20.1 h1:YlVIbqct+ZmnEph770q9Q7NVAz4wwIiVNahee6JyUzo= +github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= diff --git a/deployment/multiclient.go b/deployment/multiclient.go index f1ac2f3c310..9765e0368ea 100644 --- a/deployment/multiclient.go +++ b/deployment/multiclient.go @@ -109,16 +109,16 @@ func (mc *MultiClient) NonceAt(ctx context.Context, account common.Address, bloc } func (mc *MultiClient) WaitMined(ctx context.Context, tx *types.Transaction) (*types.Receipt, error) { - mc.lggr.Debugf("Waiting for tx %s to be mined", tx.Hash().Hex()) + mc.lggr.Debugf("Waiting for tx %s to be mined for chain %s", tx.Hash().Hex(), mc.chainName) // no retries here because we want to wait for the tx to be mined resultCh := make(chan *types.Receipt) doneCh := make(chan struct{}) waitMined := func(client *ethclient.Client, tx *types.Transaction) { - mc.lggr.Debugf("Waiting for tx %s to be mined with client %v", tx.Hash().Hex(), client) + mc.lggr.Debugf("Waiting for tx %s to be mined with chain %s", tx.Hash().Hex(), mc.chainName) receipt, err := bind.WaitMined(ctx, client, tx) if err != nil { - mc.lggr.Warnf("WaitMined error %v with client %v", err, client) + mc.lggr.Warnf("WaitMined error %v with chain %s", err, mc.chainName) return } select { @@ -135,6 +135,7 @@ func (mc *MultiClient) WaitMined(ctx context.Context, tx *types.Transaction) (*t select { case receipt = <-resultCh: close(doneCh) + mc.lggr.Debugf("Tx %s mined with chain %s", tx.Hash().Hex(), mc.chainName) return receipt, nil case <-ctx.Done(): mc.lggr.Warnf("WaitMined context done %v", ctx.Err()) diff --git a/integration-tests/smoke/ccip/ccip_legacy_test.go b/integration-tests/smoke/ccip/ccip_legacy_test.go new file mode 100644 index 00000000000..2b5b6d77b58 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_legacy_test.go @@ -0,0 +1,51 @@ +package smoke + +import ( + "context" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/v1_5" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" +) + +// This test does not run in CI, it is only written as an example of how to write a test for the legacy CCIP +func TestE2ELegacy(t *testing.T) { + e, _ := testsetups.NewIntegrationEnvironment(t, changeset.WithLegacyDeployment()) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + allChains := e.Env.AllChainSelectors() + require.Len(t, allChains, 2) + src, dest := allChains[0], allChains[1] + srcChain := e.Env.Chains[src] + destChain := e.Env.Chains[dest] + pairs := []changeset.SourceDestPair{ + {SourceChainSelector: src, DestChainSelector: dest}, + } + e.Env = v1_5.AddLanes(t, e.Env, state, pairs) + // reload state after adding lanes + state, err = changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + sentEvent, err := v1_5.SendRequest(t, e.Env, state, + changeset.WithSourceChain(src), + changeset.WithDestChain(dest), + changeset.WithTestRouter(false), + changeset.WithEvm2AnyMessage(router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[dest].Receiver.Address().Bytes(), 32), + Data: []byte("hello"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }), + ) + require.NoError(t, err) + require.NotNil(t, sentEvent) + destStartBlock, err := destChain.Client.HeaderByNumber(context.Background(), nil) + require.NoError(t, err) + v1_5.WaitForCommit(t, srcChain, destChain, state.Chains[dest].CommitStore[src], sentEvent.Message.SequenceNumber) + v1_5.WaitForExecute(t, srcChain, destChain, state.Chains[dest].EVM2EVMOffRamp[src], []uint64{sentEvent.Message.SequenceNumber}, destStartBlock.Number.Uint64()) +} diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 514a232bb80..6725ac1df9b 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -157,6 +157,11 @@ func NewIntegrationEnvironment(t *testing.T, opts ...changeset.TestOps) (changes return memEnv, devenv.RMNCluster{} case changeset.Docker: dockerEnv := &DeployedLocalDevEnvironment{} + if testCfg.LegacyDeployment { + deployedEnv := changeset.NewLegacyEnvironment(t, testCfg, dockerEnv) + require.NotNil(t, dockerEnv.testEnv, "empty docker environment") + return deployedEnv, devenv.RMNCluster{} + } if testCfg.RMNEnabled { deployedEnv := changeset.NewEnvironmentWithJobsAndContracts(t, testCfg, dockerEnv) l := logging.GetTestLogger(t) @@ -440,10 +445,11 @@ func StartChainlinkNodes( cfg.NodeConfig.CommonChainConfigTOML, cfg.NodeConfig.ChainConfigTOMLByChainID, ) - - toml.Capabilities.ExternalRegistry.NetworkID = ptr.Ptr(registryConfig.NetworkType) - toml.Capabilities.ExternalRegistry.ChainID = ptr.Ptr(strconv.FormatUint(registryConfig.EVMChainID, 10)) - toml.Capabilities.ExternalRegistry.Address = ptr.Ptr(registryConfig.Contract.String()) + if registryConfig.Contract != (common.Address{}) { + toml.Capabilities.ExternalRegistry.NetworkID = ptr.Ptr(registryConfig.NetworkType) + toml.Capabilities.ExternalRegistry.ChainID = ptr.Ptr(strconv.FormatUint(registryConfig.EVMChainID, 10)) + toml.Capabilities.ExternalRegistry.Address = ptr.Ptr(registryConfig.Contract.String()) + } if err != nil { return err