From 57f128a95c9c2ca5f0f87b1cf5e6f3914e36e6ff Mon Sep 17 00:00:00 2001 From: Connor Stein Date: Tue, 19 Nov 2024 18:32:29 -0500 Subject: [PATCH] Extract MCMS to deployment/common (#15288) * Extract MCMS * Use changeset * Standardize package import names * Fix mcms test imports * Fix add lane * Fix bad merge * Extract common integration helper code * Fix go mod * Similar common helper for memeroy * Comments * Fix tests * Fix new test * Fix RMN test --- deployment/ccip/add_lane_test.go | 25 +-- .../ccip/changeset/active_candidate_test.go | 115 +++++--------- deployment/ccip/changeset/add_chain_test.go | 34 +++- deployment/ccip/changeset/deploy_chain.go | 3 +- .../ccip/changeset/deploy_chain_test.go | 18 ++- .../ccip/changeset/initial_deploy_test.go | 19 ++- deployment/ccip/deploy.go | 139 ++--------------- deployment/ccip/deploy_test.go | 54 +------ deployment/ccip/propose.go | 121 +-------------- deployment/ccip/state.go | 58 +++---- deployment/ccip/test_helpers.go | 87 ++++++++++- deployment/ccip/view/types/contract_state.go | 33 ---- deployment/ccip/view/view.go | 2 + .../changeset/deploy_mcms_with_timelock.go | 20 +++ deployment/common/changeset/internal/mcms.go | 137 ++++++++++++++++ .../common/changeset/internal/mcms_test.go | 58 +++++++ .../common/changeset/mcms_test_helpers.go | 115 ++++++++++++++ deployment/common/changeset/state.go | 99 ++++++++++++ deployment/common/types/types.go | 25 +++ deployment/common/view/v1_0/mcms.go | 146 ++++++++++++++++++ deployment/environment.go | 8 + .../ccip-tests/testsetups/test_helpers.go | 92 ++++++++--- integration-tests/go.mod | 2 +- .../smoke/ccip_messaging_test.go | 41 ----- integration-tests/smoke/ccip_rmn_test.go | 18 --- integration-tests/smoke/ccip_test.go | 85 +--------- integration-tests/smoke/ccip_usdc_test.go | 97 +----------- integration-tests/smoke/fee_boosting_test.go | 42 ----- 28 files changed, 913 insertions(+), 780 deletions(-) delete mode 100644 deployment/ccip/view/types/contract_state.go create mode 100644 deployment/common/changeset/deploy_mcms_with_timelock.go create mode 100644 deployment/common/changeset/internal/mcms.go create mode 100644 deployment/common/changeset/internal/mcms_test.go create mode 100644 deployment/common/changeset/mcms_test_helpers.go create mode 100644 deployment/common/changeset/state.go create mode 100644 deployment/common/types/types.go create mode 100644 deployment/common/view/v1_0/mcms.go diff --git a/deployment/ccip/add_lane_test.go b/deployment/ccip/add_lane_test.go index 5edfdae1ab0..5c87a089a1b 100644 --- a/deployment/ccip/add_lane_test.go +++ b/deployment/ccip/add_lane_test.go @@ -21,38 +21,15 @@ import ( func TestAddLane(t *testing.T) { t.Parallel() // We add more chains to the chainlink nodes than the number of chains where CCIP is deployed. - e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 4, 4) + e := NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4) // Here we have CR + nodes set up, but no CCIP contracts deployed. state, err := LoadOnchainState(e.Env) require.NoError(t, err) selectors := e.Env.AllChainSelectors() - // deploy CCIP contracts on two chains chain1, chain2 := selectors[0], selectors[1] - feeds := state.Chains[e.FeedChainSel].USDFeeds - tokenConfig := NewTestTokenConfig(feeds) - newAddresses := deployment.NewMemoryAddressBook() - err = DeployPrerequisiteChainContracts(e.Env, newAddresses, e.Env.AllChainSelectors()) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) - - // Set up CCIP contracts and a DON per chain. - newAddresses = deployment.NewMemoryAddressBook() - err = DeployCCIPContracts(e.Env, newAddresses, DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - TokenConfig: tokenConfig, - MCMSConfig: NewTestMCMSConfig(t, e.Env), - ChainsToDeploy: []uint64{chain1, chain2}, - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) - // We expect no lanes available on any chain. - state, err = LoadOnchainState(e.Env) - require.NoError(t, err) for _, sel := range []uint64{chain1, chain2} { chain := state.Chains[sel] offRamps, err := chain.Router.GetOffRamps(nil) diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index cd1d7604817..50115389a28 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -16,9 +16,8 @@ import ( "github.com/stretchr/testify/require" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" - ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -27,47 +26,10 @@ func TestActiveCandidate(t *testing.T) { t.Skipf("to be enabled after latest cl-ccip is compatible") lggr := logger.TestLogger(t) - ctx := ccdeploy.Context(t) - tenv := ccdeploy.NewMemoryEnvironment(t, lggr, 3, 5, ccdeploy.MockLinkPrice, ccdeploy.MockWethPrice) + tenv := ccdeploy.NewMemoryEnvironmentWithJobsAndContracts(t, lggr, 3, 5) e := tenv.Env - state, err := ccdeploy.LoadOnchainState(tenv.Env) require.NoError(t, err) - require.NotNil(t, state.Chains[tenv.HomeChainSel].LinkToken) - - feeds := state.Chains[tenv.FeedChainSel].USDFeeds - tokenConfig := ccdeploy.NewTestTokenConfig(feeds) - - output, err := InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - // Get new state after migration. - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - state, err = ccdeploy.LoadOnchainState(tenv.Env) - require.NoError(t, err) - homeCS, destCS := tenv.HomeChainSel, tenv.FeedChainSel - - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } // Add all lanes require.NoError(t, ccdeploy.AddLanesForAll(e, state)) @@ -111,23 +73,23 @@ func TestActiveCandidate(t *testing.T) { ccdeploy.ConfirmExecWithSeqNrForAll(t, e, state, expectedSeqNum, startBlocks) // transfer ownership - ccdeploy.TransferAllOwnership(t, state, homeCS, e) - acceptOwnershipProposal, err := ccdeploy.GenerateAcceptOwnershipProposal(state, homeCS, e.AllChainSelectors()) + ccdeploy.TransferAllOwnership(t, state, tenv.HomeChainSel, e) + acceptOwnershipProposal, err := ccdeploy.GenerateAcceptOwnershipProposal(state, tenv.HomeChainSel, e.AllChainSelectors()) require.NoError(t, err) - acceptOwnershipExec := ccdeploy.SignProposal(t, e, acceptOwnershipProposal) + acceptOwnershipExec := commonchangeset.SignProposal(t, e, acceptOwnershipProposal) for _, sel := range e.AllChainSelectors() { - ccdeploy.ExecuteProposal(t, e, acceptOwnershipExec, state, sel) + commonchangeset.ExecuteProposal(t, e, acceptOwnershipExec, state.Chains[sel].Timelock, sel) } // Apply the accept ownership proposal to all the chains. - err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, homeCS, destCS, 2) + err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 2) require.NoError(t, err) // [ACTIVE, CANDIDATE] setup by setting candidate through cap reg - capReg, ccipHome := state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome - donID, err := ccdeploy.DonIDForChain(capReg, ccipHome, destCS) + capReg, ccipHome := state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome + donID, err := ccdeploy.DonIDForChain(capReg, ccipHome, tenv.FeedChainSel) require.NoError(t, err) - donInfo, err := state.Chains[homeCS].CapabilityRegistry.GetDON(nil, donID) + donInfo, err := state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) require.NoError(t, err) require.Equal(t, 5, len(donInfo.NodeP2PIds)) require.Equal(t, uint32(4), donInfo.ConfigCount) @@ -151,13 +113,14 @@ func TestActiveCandidate(t *testing.T) { // this will construct ocr3 configurations for the // commit and exec plugin we will be using - rmnHomeAddress := state.Chains[homeCS].RMNHome.Address() + rmnHomeAddress := state.Chains[tenv.HomeChainSel].RMNHome.Address() + tokenConfig := ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds) ocr3ConfigMap, err := ccdeploy.BuildOCR3ConfigForCCIPHome( deployment.XXXGenerateTestOCRSecrets(), - state.Chains[destCS].OffRamp, - e.Chains[destCS], - destCS, - tokenConfig.GetTokenInfo(e.Logger, state.Chains[destCS].LinkToken, state.Chains[destCS].Weth9), + state.Chains[tenv.FeedChainSel].OffRamp, + e.Chains[tenv.FeedChainSel], + tenv.FeedChainSel, + tokenConfig.GetTokenInfo(e.Logger, state.Chains[tenv.FeedChainSel].LinkToken, state.Chains[tenv.FeedChainSel].Weth9), nodes.NonBootstraps(), rmnHomeAddress, nil, @@ -166,82 +129,82 @@ func TestActiveCandidate(t *testing.T) { setCommitCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], - state.Chains[homeCS].CapabilityRegistry, - state.Chains[homeCS].CCIPHome, - destCS, + state.Chains[tenv.HomeChainSel].CapabilityRegistry, + state.Chains[tenv.HomeChainSel].CCIPHome, + tenv.FeedChainSel, nodes.NonBootstraps(), ) require.NoError(t, err) setCommitCandidateProposal, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeCS), + ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: setCommitCandidateOp, }}, "set new candidates on commit plugin", 0) require.NoError(t, err) - setCommitCandidateSigned := ccdeploy.SignProposal(t, e, setCommitCandidateProposal) - ccdeploy.ExecuteProposal(t, e, setCommitCandidateSigned, state, homeCS) + setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) + commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // create the op for the commit plugin as well setExecCandidateOp, err := ccdeploy.SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPExec], - state.Chains[homeCS].CapabilityRegistry, - state.Chains[homeCS].CCIPHome, - destCS, + state.Chains[tenv.HomeChainSel].CapabilityRegistry, + state.Chains[tenv.HomeChainSel].CCIPHome, + tenv.FeedChainSel, nodes.NonBootstraps(), ) require.NoError(t, err) setExecCandidateProposal, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeCS), + ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: setExecCandidateOp, }}, "set new candidates on commit and exec plugins", 0) require.NoError(t, err) - setExecCandidateSigned := ccdeploy.SignProposal(t, e, setExecCandidateProposal) - ccdeploy.ExecuteProposal(t, e, setExecCandidateSigned, state, homeCS) + setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) + commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // check setup was successful by confirming number of nodes from cap reg - donInfo, err = state.Chains[homeCS].CapabilityRegistry.GetDON(nil, donID) + donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) require.NoError(t, err) require.Equal(t, 4, len(donInfo.NodeP2PIds)) require.Equal(t, uint32(6), donInfo.ConfigCount) // [ACTIVE, CANDIDATE] done setup // [ACTIVE, CANDIDATE] make sure we can still send successful transaction without updating job specs - err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, homeCS, destCS, 3) + err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 3) require.NoError(t, err) // [ACTIVE, CANDIDATE] done send successful transaction on active // [NEW ACTIVE, NO CANDIDATE] promote to active // confirm by getting old candidate digest and making sure new active matches - oldCandidateDigest, err := state.Chains[homeCS].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) + oldCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) require.NoError(t, err) - promoteOps, err := ccdeploy.PromoteAllCandidatesForChainOps(state.Chains[homeCS].CapabilityRegistry, state.Chains[homeCS].CCIPHome, destCS, nodes.NonBootstraps()) + promoteOps, err := ccdeploy.PromoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps()) require.NoError(t, err) promoteProposal, err := ccdeploy.BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeCS), + ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: promoteOps, }}, "promote candidates and revoke actives", 0) require.NoError(t, err) - promoteSigned := ccdeploy.SignProposal(t, e, promoteProposal) - ccdeploy.ExecuteProposal(t, e, promoteSigned, state, homeCS) + promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) + commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) // [NEW ACTIVE, NO CANDIDATE] done promoting // [NEW ACTIVE, NO CANDIDATE] check onchain state - newActiveDigest, err := state.Chains[homeCS].CCIPHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) + newActiveDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetActiveDigest(nil, donID, uint8(cctypes.PluginTypeCCIPExec)) require.NoError(t, err) require.Equal(t, oldCandidateDigest, newActiveDigest) - newCandidateDigest, err := state.Chains[homeCS].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) + newCandidateDigest, err := state.Chains[tenv.HomeChainSel].CCIPHome.GetCandidateDigest(nil, donID, uint8(cctypes.PluginTypeCCIPCommit)) require.NoError(t, err) require.Equal(t, newCandidateDigest, [32]byte{}) // [NEW ACTIVE, NO CANDIDATE] done checking on chain state // [NEW ACTIVE, NO CANDIDATE] send successful request on new active - donInfo, err = state.Chains[homeCS].CapabilityRegistry.GetDON(nil, donID) + donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) require.NoError(t, err) require.Equal(t, uint32(8), donInfo.ConfigCount) - err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, homeCS, destCS, 4) + err = ccdeploy.ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 4) require.NoError(t, err) // [NEW ACTIVE, NO CANDIDATE] done sending successful request } diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 2d79a76005d..76104871784 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -1,11 +1,13 @@ package changeset import ( + "math/big" "testing" "time" ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" - + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" "github.com/ethereum/go-ethereum/common" @@ -41,14 +43,27 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) - tokenConfig := ccipdeployment.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + cfg := commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + out, err := commonchangeset.DeployMCMSWithTimelock(e.Env, map[uint64]commontypes.MCMSWithTimelockConfig{ + initialDeploy[0]: cfg, + initialDeploy[1]: cfg, + initialDeploy[2]: cfg, + }) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) newAddresses = deployment.NewMemoryAddressBook() + tokenConfig := ccipdeployment.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) err = ccipdeployment.DeployCCIPContracts(e.Env, newAddresses, ccipdeployment.DeployCCIPContractConfig{ HomeChainSel: e.HomeChainSel, FeedChainSel: e.FeedChainSel, ChainsToDeploy: initialDeploy, TokenConfig: tokenConfig, - MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, e.Env), OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) @@ -72,14 +87,19 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Deploy contracts to new chain + out, err = commonchangeset.DeployMCMSWithTimelock(e.Env, map[uint64]commontypes.MCMSWithTimelockConfig{ + newChain: cfg, + }) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) + newAddresses = deployment.NewMemoryAddressBook() err = ccipdeployment.DeployPrerequisiteChainContracts(e.Env, newAddresses, []uint64{newChain}) require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) newAddresses = deployment.NewMemoryAddressBook() err = ccipdeployment.DeployChainContracts(e.Env, - e.Env.Chains[newChain], newAddresses, - ccipdeployment.NewTestMCMSConfig(t, e.Env), rmnHome) + e.Env.Chains[newChain], newAddresses, rmnHome) require.NoError(t, err) require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) state, err = ccipdeployment.LoadOnchainState(e.Env) @@ -117,10 +137,10 @@ func TestAddChainInbound(t *testing.T) { acceptOwnershipProposal, err := ccipdeployment.GenerateAcceptOwnershipProposal(state, e.HomeChainSel, initialDeploy) require.NoError(t, err) - acceptOwnershipExec := ccipdeployment.SignProposal(t, e.Env, acceptOwnershipProposal) + acceptOwnershipExec := commonchangeset.SignProposal(t, e.Env, acceptOwnershipProposal) // Apply the accept ownership proposal to all the chains. for _, sel := range initialDeploy { - ccipdeployment.ExecuteProposal(t, e.Env, acceptOwnershipExec, state, sel) + commonchangeset.ExecuteProposal(t, e.Env, acceptOwnershipExec, state.Chains[sel].Timelock, sel) } for _, chain := range initialDeploy { owner, err2 := state.Chains[chain].OnRamp.Owner(nil) diff --git a/deployment/ccip/changeset/deploy_chain.go b/deployment/ccip/changeset/deploy_chain.go index 68f350a9af7..633d01bbf4c 100644 --- a/deployment/ccip/changeset/deploy_chain.go +++ b/deployment/ccip/changeset/deploy_chain.go @@ -13,7 +13,7 @@ var _ deployment.ChangeSet[DeployChainContractsConfig] = DeployChainContracts func DeployChainContracts(env deployment.Environment, c DeployChainContractsConfig) (deployment.ChangesetOutput, error) { newAddresses := deployment.NewMemoryAddressBook() - err := ccipdeployment.DeployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors, c.MCMSCfg) + err := ccipdeployment.DeployChainContractsForChains(env, newAddresses, c.HomeChainSelector, c.ChainSelectors) if err != nil { env.Logger.Errorw("Failed to deploy CCIP contracts", "err", err, "newAddresses", newAddresses) return deployment.ChangesetOutput{AddressBook: newAddresses}, deployment.MaybeDataErr(err) @@ -28,7 +28,6 @@ func DeployChainContracts(env deployment.Environment, c DeployChainContractsConf type DeployChainContractsConfig struct { ChainSelectors []uint64 HomeChainSelector uint64 - MCMSCfg ccipdeployment.MCMSConfig } func (c DeployChainContractsConfig) Validate() error { diff --git a/deployment/ccip/changeset/deploy_chain_test.go b/deployment/ccip/changeset/deploy_chain_test.go index b197c90eca5..0e5b7a8d270 100644 --- a/deployment/ccip/changeset/deploy_chain_test.go +++ b/deployment/ccip/changeset/deploy_chain_test.go @@ -1,6 +1,7 @@ package changeset import ( + "math/big" "testing" "github.com/stretchr/testify/require" @@ -8,6 +9,8 @@ import ( "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -45,11 +48,24 @@ func TestDeployChainContractsChangeset(t *testing.T) { require.NoError(t, err) require.NoError(t, e.ExistingAddresses.Merge(prerequisites.AddressBook)) + cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, chain := range e.AllChainSelectors() { + cfg[chain] = commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + } + output, err = commonchangeset.DeployMCMSWithTimelock(e, cfg) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + // deploy ccip chain contracts output, err = DeployChainContracts(e, DeployChainContractsConfig{ ChainSelectors: selectors, HomeChainSelector: homeChainSel, - MCMSCfg: ccdeploy.NewTestMCMSConfig(t, e), }) require.NoError(t, err) require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) diff --git a/deployment/ccip/changeset/initial_deploy_test.go b/deployment/ccip/changeset/initial_deploy_test.go index a3756022245..a299dd4971f 100644 --- a/deployment/ccip/changeset/initial_deploy_test.go +++ b/deployment/ccip/changeset/initial_deploy_test.go @@ -1,12 +1,16 @@ package changeset import ( + "math/big" "testing" "github.com/ethereum/go-ethereum/common" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" @@ -32,12 +36,25 @@ func TestInitialDeploy(t *testing.T) { require.NoError(t, err) require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) + cfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, chain := range e.AllChainSelectors() { + cfg[chain] = commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + } + output, err = commonchangeset.DeployMCMSWithTimelock(e, cfg) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + output, err = InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ HomeChainSel: tenv.HomeChainSel, FeedChainSel: tenv.FeedChainSel, ChainsToDeploy: tenv.Env.AllChainSelectors(), TokenConfig: ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), }) require.NoError(t, err) diff --git a/deployment/ccip/deploy.go b/deployment/ccip/deploy.go index d1f6866190d..0dea0a8b1f8 100644 --- a/deployment/ccip/deploy.go +++ b/deployment/ccip/deploy.go @@ -8,12 +8,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" - "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/ccip_home" @@ -285,10 +283,7 @@ type DeployCCIPContractConfig struct { FeedChainSel uint64 ChainsToDeploy []uint64 TokenConfig TokenConfig - // I believe it makes sense to have the same signers across all chains - // since that's the point MCMS. - MCMSConfig MCMSConfig - USDCConfig USDCConfig + USDCConfig USDCConfig // For setting OCR configuration OCRSecrets deployment.OCRSecrets } @@ -302,7 +297,10 @@ type DeployCCIPContractConfig struct { // It then deploys the rest of the CCIP chain contracts to the selected chains // registers the nodes with the capability registry and creates a DON for // each new chain. TODO: Might be better to break this down a bit? -func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c DeployCCIPContractConfig) error { +func DeployCCIPContracts( + e deployment.Environment, + ab deployment.AddressBook, + c DeployCCIPContractConfig) error { if c.OCRSecrets.IsEmpty() { return fmt.Errorf("OCR secrets are empty") } @@ -357,7 +355,7 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c } } } - err = DeployChainContractsForChains(e, ab, c.HomeChainSel, c.ChainsToDeploy, c.MCMSConfig) + err = DeployChainContractsForChains(e, ab, c.HomeChainSel, c.ChainsToDeploy) if err != nil { e.Logger.Errorw("Failed to deploy chain contracts", "err", err) return err @@ -423,114 +421,11 @@ func DeployCCIPContracts(e deployment.Environment, ab deployment.AddressBook, c return nil } -type MCMSConfig struct { - Admin config.Config - Canceller config.Config - Bypasser config.Config - Proposer config.Config - Executors []common.Address -} - -func DeployMCMSWithConfig( - contractType deployment.ContractType, - lggr logger.Logger, - chain deployment.Chain, - ab deployment.AddressBook, - mcmConfig config.Config, -) (*deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig], error) { - groupQuorums, groupParents, signerAddresses, signerGroups := mcmConfig.ExtractSetConfigInputs() - mcm, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] { - mcmAddr, tx, mcm, err2 := owner_helpers.DeployManyChainMultiSig( - chain.DeployerKey, - chain.Client, - ) - return deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig]{ - mcmAddr, mcm, tx, deployment.NewTypeAndVersion(contractType, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy mcm", "err", err) - return mcm, err - } - mcmsTx, err := mcm.Contract.SetConfig(chain.DeployerKey, - signerAddresses, - signerGroups, // Signer 1 is int group 0 (root group) with quorum 1. - groupQuorums, - groupParents, - false, - ) - if _, err := deployment.ConfirmIfNoError(chain, mcmsTx, err); err != nil { - lggr.Errorw("Failed to confirm mcm config", "err", err) - return mcm, err - } - return mcm, nil -} - -type MCMSContracts struct { - Admin *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] - Canceller *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] - Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] - Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] - Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] -} - -// DeployMCMSContracts deploys the MCMS contracts for the given configuration -// as well as the timelock. -func DeployMCMSContracts( - lggr logger.Logger, - chain deployment.Chain, +func DeployChainContractsForChains( + e deployment.Environment, ab deployment.AddressBook, - mcmConfig MCMSConfig, -) (*MCMSContracts, error) { - adminMCM, err := DeployMCMSWithConfig(AdminManyChainMultisig, lggr, chain, ab, mcmConfig.Admin) - if err != nil { - return nil, err - } - bypasser, err := DeployMCMSWithConfig(BypasserManyChainMultisig, lggr, chain, ab, mcmConfig.Bypasser) - if err != nil { - return nil, err - } - canceller, err := DeployMCMSWithConfig(CancellerManyChainMultisig, lggr, chain, ab, mcmConfig.Canceller) - if err != nil { - return nil, err - } - proposer, err := DeployMCMSWithConfig(ProposerManyChainMultisig, lggr, chain, ab, mcmConfig.Proposer) - if err != nil { - return nil, err - } - - timelock, err := deployment.DeployContract(lggr, chain, ab, - func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.RBACTimelock] { - timelock, tx2, cc, err2 := owner_helpers.DeployRBACTimelock( - chain.DeployerKey, - chain.Client, - big.NewInt(0), // minDelay - adminMCM.Address, - []common.Address{proposer.Address}, // proposers - mcmConfig.Executors, //executors - []common.Address{canceller.Address}, // cancellers - []common.Address{bypasser.Address}, // bypassers - ) - return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ - timelock, cc, tx2, deployment.NewTypeAndVersion(RBACTimelock, deployment.Version1_0_0), err2, - } - }) - if err != nil { - lggr.Errorw("Failed to deploy timelock", "err", err) - return nil, err - } - lggr.Infow("deployed timelock", "addr", timelock.Address) - return &MCMSContracts{ - Admin: adminMCM, - Canceller: canceller, - Bypasser: bypasser, - Proposer: proposer, - Timelock: timelock, - }, nil -} - -func DeployChainContractsForChains(e deployment.Environment, ab deployment.AddressBook, homeChainSel uint64, chainsToDeploy []uint64, mcmsConfig MCMSConfig) error { + homeChainSel uint64, + chainsToDeploy []uint64) error { existingState, err := LoadOnchainState(e) if err != nil { e.Logger.Errorw("Failed to load existing onchain state", "err") @@ -577,7 +472,7 @@ func DeployChainContractsForChains(e deployment.Environment, ab deployment.Addre if existingState.Chains[chainSel].LinkToken == nil || existingState.Chains[chainSel].Weth9 == nil { return fmt.Errorf("fee tokens not found for chain %d", chainSel) } - err := DeployChainContracts(e, chain, ab, mcmsConfig, rmnHome) + err := DeployChainContracts(e, chain, ab, rmnHome) if err != nil { e.Logger.Errorw("Failed to deploy chain contracts", "chain", chainSel, "err", err) return fmt.Errorf("failed to deploy chain contracts for chain %d: %w", chainSel, err) @@ -590,13 +485,8 @@ func DeployChainContracts( e deployment.Environment, chain deployment.Chain, ab deployment.AddressBook, - mcmsConfig MCMSConfig, rmnHome *rmn_home.RMNHome, ) error { - mcmsContracts, err := DeployMCMSContracts(e.Logger, chain, ab, mcmsConfig) - if err != nil { - return err - } // check for existing contracts state, err := LoadOnchainState(e) if err != nil { @@ -610,6 +500,9 @@ func DeployChainContracts( if chainState.Weth9 == nil { return fmt.Errorf("weth9 not found for chain %d, deploy the prerequisites first", chain.Selector) } + if chainState.Timelock == nil { + return fmt.Errorf("timelock not found for chain %d, deploy the mcms contracts first", chain.Selector) + } weth9Contract := chainState.Weth9 if chainState.LinkToken == nil { return fmt.Errorf("link token not found for chain %d, deploy the prerequisites first", chain.Selector) @@ -768,7 +661,7 @@ func DeployChainContracts( LinkToken: linkTokenContract.Address(), TokenPriceStalenessThreshold: uint32(24 * 60 * 60), }, - []common.Address{mcmsContracts.Timelock.Address}, // timelock should be able to update, ramps added after + []common.Address{state.Chains[chain.Selector].Timelock.Address()}, // timelock should be able to update, ramps added after []common.Address{weth9Contract.Address(), linkTokenContract.Address()}, // fee tokens []fee_quoter.FeeQuoterTokenPriceFeedUpdate{}, []fee_quoter.FeeQuoterTokenTransferFeeConfigArgs{}, // TODO: tokens diff --git a/deployment/ccip/deploy_test.go b/deployment/ccip/deploy_test.go index 2ca9901ddbf..c2b71e093de 100644 --- a/deployment/ccip/deploy_test.go +++ b/deployment/ccip/deploy_test.go @@ -6,62 +6,20 @@ import ( "testing" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/environment/memory" "github.com/smartcontractkit/chainlink/v2/core/logger" ) func TestDeployCCIPContracts(t *testing.T) { lggr := logger.TestLogger(t) - e := memory.NewMemoryEnvironment(t, lggr, zapcore.InfoLevel, memory.MemoryEnvironmentConfig{ - Bootstraps: 1, - Chains: 2, - Nodes: 4, - }) - // Deploy all the CCIP contracts. - homeChainSel, feedChainSel := allocateCCIPChainSelectors(e.Chains) - _ = DeployTestContracts(t, lggr, e.ExistingAddresses, homeChainSel, feedChainSel, e.Chains, MockLinkPrice, MockWethPrice) - - nodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) - require.NoError(t, err) - - _, err = DeployHomeChain(lggr, e, e.ExistingAddresses, e.Chains[homeChainSel], - NewTestRMNStaticConfig(), - NewTestRMNDynamicConfig(), - NewTestNodeOperator(e.Chains[homeChainSel].DeployerKey.From), - map[string][][32]byte{ - "NodeOperator": nodes.NonBootstraps().PeerIDs(), - }, + e := NewMemoryEnvironmentWithJobsAndContracts(t, lggr, + 2, + 4, ) + // Deploy all the CCIP contracts. + state, err := LoadOnchainState(e.Env) require.NoError(t, err) - // Load the state after deploying the cap reg and feeds. - s, err := LoadOnchainState(e) - require.NoError(t, err) - require.NotNil(t, s.Chains[homeChainSel].CapabilityRegistry) - require.NotNil(t, s.Chains[homeChainSel].CCIPHome) - require.NotNil(t, s.Chains[feedChainSel].USDFeeds) - - newAddresses := deployment.NewMemoryAddressBook() - err = DeployPrerequisiteChainContracts(e, newAddresses, e.AllChainSelectors()) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(newAddresses)) - - newAddresses = deployment.NewMemoryAddressBook() - err = DeployCCIPContracts(e, newAddresses, DeployCCIPContractConfig{ - HomeChainSel: homeChainSel, - FeedChainSel: feedChainSel, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: NewTokenConfig(), - MCMSConfig: NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(newAddresses)) - state, err := LoadOnchainState(e) - require.NoError(t, err) - snap, err := state.View(e.AllChainSelectors()) + snap, err := state.View(e.Env.AllChainSelectors()) require.NoError(t, err) // Assert expect every deployed address to be in the address book. diff --git a/deployment/ccip/propose.go b/deployment/ccip/propose.go index 9d6ac417968..d7baf9ab542 100644 --- a/deployment/ccip/propose.go +++ b/deployment/ccip/propose.go @@ -1,138 +1,19 @@ package ccipdeployment import ( - "bytes" - "context" - "crypto/ecdsa" "fmt" "math/big" - "testing" "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" + mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" - owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" chainsel "github.com/smartcontractkit/chain-selectors" - "github.com/stretchr/testify/require" - - mapset "github.com/deckarep/golang-set/v2" "github.com/smartcontractkit/chainlink/deployment" ) -var ( - TestXXXMCMSSigner *ecdsa.PrivateKey -) - -func init() { - key, err := crypto.GenerateKey() - if err != nil { - panic(err) - } - TestXXXMCMSSigner = key -} - -func SingleGroupMCMS(t *testing.T) config.Config { - publicKey := TestXXXMCMSSigner.Public().(*ecdsa.PublicKey) - // Convert the public key to an Ethereum address - address := crypto.PubkeyToAddress(*publicKey) - c, err := config.NewConfig(1, []common.Address{address}, []config.Config{}) - require.NoError(t, err) - return *c -} - -func NewTestMCMSConfig(t *testing.T, e deployment.Environment) MCMSConfig { - c := SingleGroupMCMS(t) - // All deployer keys can execute. - var executors []common.Address - for _, chain := range e.Chains { - executors = append(executors, chain.DeployerKey.From) - } - return MCMSConfig{ - Admin: c, - Bypasser: c, - Canceller: c, - Executors: executors, - Proposer: c, - } -} - -func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.MCMSWithTimelockProposal) *mcms.Executor { - executorClients := make(map[mcms.ChainIdentifier]mcms.ContractDeployBackend) - for _, chain := range env.Chains { - chainselc, exists := chainsel.ChainBySelector(chain.Selector) - require.True(t, exists) - chainSel := mcms.ChainIdentifier(chainselc.Selector) - executorClients[chainSel] = chain.Client - } - executor, err := proposal.ToExecutor(true) - require.NoError(t, err) - payload, err := executor.SigningHash() - require.NoError(t, err) - // Sign the payload - sig, err := crypto.Sign(payload.Bytes(), TestXXXMCMSSigner) - require.NoError(t, err) - mcmSig, err := mcms.NewSignatureFromBytes(sig) - require.NoError(t, err) - executor.Proposal.AddSignature(mcmSig) - require.NoError(t, executor.Proposal.Validate()) - return executor -} - -func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - state CCIPOnChainState, sel uint64) { - t.Log("Executing proposal on chain", sel) - // Set the root. - tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) - if err2 != nil { - require.NoError(t, deployment.MaybeDataErr(err2)) - } - _, err2 = env.Chains[sel].Confirm(tx) - require.NoError(t, err2) - - // TODO: This sort of helper probably should move to the MCMS lib. - // Execute all the transactions in the proposal which are for this chain. - for _, chainOp := range executor.Operations[mcms.ChainIdentifier(sel)] { - for idx, op := range executor.ChainAgnosticOps { - if bytes.Equal(op.Data, chainOp.Data) && op.To == chainOp.To { - opTx, err3 := executor.ExecuteOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, idx) - require.NoError(t, err3) - block, err3 := env.Chains[sel].Confirm(opTx) - require.NoError(t, err3) - t.Log("executed", chainOp) - it, err3 := state.Chains[sel].Timelock.FilterCallScheduled(&bind.FilterOpts{ - Start: block, - End: &block, - Context: context.Background(), - }, nil, nil) - require.NoError(t, err3) - var calls []owner_helpers.RBACTimelockCall - var pred, salt [32]byte - for it.Next() { - // Note these are the same for the whole batch, can overwrite - pred = it.Event.Predecessor - salt = it.Event.Salt - t.Log("scheduled", it.Event) - calls = append(calls, owner_helpers.RBACTimelockCall{ - Target: it.Event.Target, - Data: it.Event.Data, - Value: it.Event.Value, - }) - } - tx, err := state.Chains[sel].Timelock.ExecuteBatch( - env.Chains[sel].DeployerKey, calls, pred, salt) - require.NoError(t, err) - _, err = env.Chains[sel].Confirm(tx) - require.NoError(t, err) - } - } - } -} - func GenerateAcceptOwnershipProposal( state CCIPOnChainState, homeChain uint64, diff --git a/deployment/ccip/state.go b/deployment/ccip/state.go index dcbe52524cf..f7fad230cc4 100644 --- a/deployment/ccip/state.go +++ b/deployment/ccip/state.go @@ -17,6 +17,8 @@ import ( "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_2" "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_5" "github.com/smartcontractkit/chainlink/deployment/ccip/view/v1_6" + 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/ccip_config" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/commit_store" @@ -30,8 +32,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" - owner_wrappers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" - "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" @@ -46,6 +46,7 @@ import ( // CCIPChainState holds a Go binding for all the currently deployed CCIP contracts // on a chain. If a binding is nil, it means here is no such contract on the chain. type CCIPChainState struct { + commoncs.MCMSWithTimelockState OnRamp *onramp.OnRamp OffRamp *offramp.OffRamp FeeQuoter *fee_quoter.FeeQuoter @@ -74,11 +75,6 @@ type CCIPChainState struct { CapabilityRegistry *capabilities_registry.CapabilitiesRegistry CCIPHome *ccip_home.CCIPHome RMNHome *rmn_home.RMNHome - AdminMcm *owner_wrappers.ManyChainMultiSig - BypasserMcm *owner_wrappers.ManyChainMultiSig - CancellerMcm *owner_wrappers.ManyChainMultiSig - ProposerMcm *owner_wrappers.ManyChainMultiSig - Timelock *owner_wrappers.RBACTimelock // TODO remove once staging upgraded. CCIPConfig *ccip_config.CCIPConfig @@ -173,6 +169,13 @@ func (c CCIPChainState) GenerateView() (view.ChainView, error) { } chainView.CapabilityRegistry[c.CapabilityRegistry.Address().Hex()] = capRegView } + if c.MCMSWithTimelockState.Timelock != nil { + mcmsView, err := c.MCMSWithTimelockState.GenerateMCMSWithTimelockView() + if err != nil { + return chainView, err + } + chainView.MCMSWithTimelock = mcmsView + } return chainView, nil } @@ -235,41 +238,20 @@ func LoadOnchainState(e deployment.Environment) (CCIPOnChainState, error) { } // LoadChainState Loads all state for a chain into state -// Modifies map in place func LoadChainState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (CCIPChainState, error) { var state CCIPChainState + mcmsWithTimelock, err := commoncs.LoadMCMSWithTimelockState(chain, addresses) + if err != nil { + return state, err + } + state.MCMSWithTimelockState = *mcmsWithTimelock for address, tvStr := range addresses { switch tvStr.String() { - case deployment.NewTypeAndVersion(RBACTimelock, deployment.Version1_0_0).String(): - tl, err := owner_wrappers.NewRBACTimelock(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.Timelock = tl - case deployment.NewTypeAndVersion(AdminManyChainMultisig, deployment.Version1_0_0).String(): - mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.AdminMcm = mcms - case deployment.NewTypeAndVersion(ProposerManyChainMultisig, deployment.Version1_0_0).String(): - mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.ProposerMcm = mcms - case deployment.NewTypeAndVersion(BypasserManyChainMultisig, deployment.Version1_0_0).String(): - mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.BypasserMcm = mcms - case deployment.NewTypeAndVersion(CancellerManyChainMultisig, deployment.Version1_0_0).String(): - mcms, err := owner_wrappers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) - if err != nil { - return state, err - } - state.CancellerMcm = mcms + case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.ProposerManyChainMultisig, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.CancellerManyChainMultisig, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.BypasserManyChainMultisig, deployment.Version1_0_0).String(): + continue case deployment.NewTypeAndVersion(CapabilitiesRegistry, deployment.Version1_0_0).String(): cr, err := capabilities_registry.NewCapabilitiesRegistry(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index d682a72eddc..f858164e720 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -4,22 +4,30 @@ import ( "context" "fmt" "math/big" + "net/http" + "net/http/httptest" "sort" "testing" "time" mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" + + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" "go.uber.org/multierr" "go.uber.org/zap/zapcore" chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-ccip/pkg/reader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-common/pkg/logger" @@ -215,6 +223,79 @@ func NewMemoryEnvironmentWithJobs(t *testing.T, lggr logger.Logger, numChains in return e } +// mockAttestationResponse mocks the USDC attestation server, it returns random Attestation. +// We don't need to return exactly the same attestation, because our Mocked USDC contract doesn't rely on any specific +// value, but instead of that it just checks if the attestation is present. Therefore, it makes the test a bit simpler +// and doesn't require very detailed mocks. Please see tests in chainlink-ccip for detailed tests using real attestations +func mockAttestationResponse() *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + response := `{ + "status": "complete", + "attestation": "0x9049623e91719ef2aa63c55f357be2529b0e7122ae552c18aff8db58b4633c4d3920ff03d3a6d1ddf11f06bf64d7fd60d45447ac81f527ba628877dc5ca759651b08ffae25a6d3b1411749765244f0a1c131cbfe04430d687a2e12fd9d2e6dc08e118ad95d94ad832332cf3c4f7a4f3da0baa803b7be024b02db81951c0f0714de1b" + }` + + _, err := w.Write([]byte(response)) + if err != nil { + panic(err) + } + })) + return server +} + +func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int) DeployedEnv { + e := NewMemoryEnvironment(t, lggr, numChains, numNodes, MockLinkPrice, MockWethPrice) + e.SetupJobs(t) + // Take first non-home chain as the new chain. + newAddresses := deployment.NewMemoryAddressBook() + err := DeployPrerequisiteChainContracts(e.Env, newAddresses, e.Env.AllChainSelectors()) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + + cfg := commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, c := range e.Env.AllChainSelectors() { + mcmsCfg[c] = cfg + } + out, err := commonchangeset.DeployMCMSWithTimelock(e.Env, mcmsCfg) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + + newAddresses = deployment.NewMemoryAddressBook() + tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + server := mockAttestationResponse() + defer server.Close() + endpoint := server.URL + err = DeployCCIPContracts(e.Env, newAddresses, DeployCCIPContractConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: e.Env.AllChainSelectors(), + TokenConfig: tokenConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + USDCConfig: USDCConfig{ + Enabled: true, + USDCAttestationConfig: USDCAttestationConfig{ + API: endpoint, + APITimeout: commonconfig.MustNewDuration(time.Second), + APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + state, err = LoadOnchainState(e.Env) + require.NoError(t, err) + + return e +} + func NewMemoryEnvironmentWithJobsAndPrices( t *testing.T, lggr logger.Logger, @@ -516,9 +597,9 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang chains.Add(uint64(op.ChainIdentifier)) } - signed := SignProposal(t, e, &prop) + signed := commonchangeset.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - ExecuteProposal(t, e, signed, state, sel) + commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) } } } diff --git a/deployment/ccip/view/types/contract_state.go b/deployment/ccip/view/types/contract_state.go deleted file mode 100644 index f65c510af53..00000000000 --- a/deployment/ccip/view/types/contract_state.go +++ /dev/null @@ -1,33 +0,0 @@ -package types - -import ( - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" -) - -type ContractMetaData struct { - TypeAndVersion string `json:"typeAndVersion,omitempty"` - Address common.Address `json:"address,omitempty"` - Owner common.Address `json:"owner,omitempty"` -} - -func NewContractMetaData(tv Meta, addr common.Address) (ContractMetaData, error) { - tvStr, err := tv.TypeAndVersion(nil) - if err != nil { - return ContractMetaData{}, err - } - owner, err := tv.Owner(nil) - if err != nil { - return ContractMetaData{}, err - } - return ContractMetaData{ - TypeAndVersion: tvStr, - Address: addr, - Owner: owner, - }, nil -} - -type Meta interface { - TypeAndVersion(opts *bind.CallOpts) (string, error) - Owner(opts *bind.CallOpts) (common.Address, error) -} diff --git a/deployment/ccip/view/view.go b/deployment/ccip/view/view.go index 9ef8583bdf6..318e09100b9 100644 --- a/deployment/ccip/view/view.go +++ b/deployment/ccip/view/view.go @@ -26,6 +26,7 @@ type ChainView struct { OnRamp map[string]v1_6.OnRampView `json:"onRamp,omitempty"` OffRamp map[string]v1_6.OffRampView `json:"offRamp,omitempty"` CapabilityRegistry map[string]common_v1_0.CapabilityRegistryView `json:"capabilityRegistry,omitempty"` + MCMSWithTimelock common_v1_0.MCMSWithTimelockView `json:"mcmsWithTimelock,omitempty"` } func NewChain() ChainView { @@ -44,6 +45,7 @@ func NewChain() ChainView { OnRamp: make(map[string]v1_6.OnRampView), OffRamp: make(map[string]v1_6.OffRampView), CapabilityRegistry: make(map[string]common_v1_0.CapabilityRegistryView), + MCMSWithTimelock: common_v1_0.MCMSWithTimelockView{}, } } diff --git a/deployment/common/changeset/deploy_mcms_with_timelock.go b/deployment/common/changeset/deploy_mcms_with_timelock.go new file mode 100644 index 00000000000..c36e1f1575b --- /dev/null +++ b/deployment/common/changeset/deploy_mcms_with_timelock.go @@ -0,0 +1,20 @@ +package changeset + +import ( + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/types" +) + +var _ deployment.ChangeSet[map[uint64]types.MCMSWithTimelockConfig] = DeployMCMSWithTimelock + +func DeployMCMSWithTimelock(e deployment.Environment, cfgByChain map[uint64]types.MCMSWithTimelockConfig) (deployment.ChangesetOutput, error) { + newAddresses := deployment.NewMemoryAddressBook() + err := internal.DeployMCMSWithTimelockContractsBatch( + e.Logger, e.Chains, newAddresses, cfgByChain, + ) + if err != nil { + return deployment.ChangesetOutput{AddressBook: newAddresses}, err + } + return deployment.ChangesetOutput{AddressBook: newAddresses}, nil +} diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go new file mode 100644 index 00000000000..1e2fb958aae --- /dev/null +++ b/deployment/common/changeset/internal/mcms.go @@ -0,0 +1,137 @@ +package internal + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" +) + +func DeployMCMSWithConfig( + contractType deployment.ContractType, + lggr logger.Logger, + chain deployment.Chain, + ab deployment.AddressBook, + mcmConfig config.Config, +) (*deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig], error) { + groupQuorums, groupParents, signerAddresses, signerGroups := mcmConfig.ExtractSetConfigInputs() + mcm, err := deployment.DeployContract[*owner_helpers.ManyChainMultiSig](lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] { + mcmAddr, tx, mcm, err2 := owner_helpers.DeployManyChainMultiSig( + chain.DeployerKey, + chain.Client, + ) + return deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig]{ + mcmAddr, mcm, tx, deployment.NewTypeAndVersion(contractType, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy mcm", "err", err) + return mcm, err + } + mcmsTx, err := mcm.Contract.SetConfig(chain.DeployerKey, + signerAddresses, + // Signer 1 is int group 0 (root group) with quorum 1. + signerGroups, + groupQuorums, + groupParents, + false, + ) + if _, err := deployment.ConfirmIfNoError(chain, mcmsTx, err); err != nil { + lggr.Errorw("Failed to confirm mcm config", "err", err) + return mcm, err + } + return mcm, nil +} + +// MCMSWithTimelockDeploy holds a bundle of MCMS contract deploys. +type MCMSWithTimelockDeploy struct { + Canceller *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] + Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] + Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] + Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] +} + +func DeployMCMSWithTimelockContractsBatch( + lggr logger.Logger, + chains map[uint64]deployment.Chain, + ab deployment.AddressBook, + cfgByChain map[uint64]types.MCMSWithTimelockConfig, +) error { + for chainSel, cfg := range cfgByChain { + _, err := DeployMCMSWithTimelockContracts(lggr, chains[chainSel], ab, cfg) + if err != nil { + return err + } + } + return nil +} + +// DeployMCMSWithTimelockContracts deploys an MCMS for +// each of the timelock roles Bypasser, ProposerMcm, Canceller. +// MCMS contracts for the given configuration +// as well as the timelock. It's not necessarily the only way to use +// the timelock and MCMS, but its reasonable pattern. +func DeployMCMSWithTimelockContracts( + lggr logger.Logger, + chain deployment.Chain, + ab deployment.AddressBook, + config types.MCMSWithTimelockConfig, +) (*MCMSWithTimelockDeploy, error) { + bypasser, err := DeployMCMSWithConfig(types.BypasserManyChainMultisig, lggr, chain, ab, config.Bypasser) + if err != nil { + return nil, err + } + canceller, err := DeployMCMSWithConfig(types.CancellerManyChainMultisig, lggr, chain, ab, config.Canceller) + if err != nil { + return nil, err + } + proposer, err := DeployMCMSWithConfig(types.ProposerManyChainMultisig, lggr, chain, ab, config.Proposer) + if err != nil { + return nil, err + } + + timelock, err := deployment.DeployContract[*owner_helpers.RBACTimelock](lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.RBACTimelock] { + timelock, tx2, cc, err2 := owner_helpers.DeployRBACTimelock( + chain.DeployerKey, + chain.Client, + config.TimelockMinDelay, + // Deployer is the initial admin. + // TODO: Could expose this as config? + // Or keep this enforced to follow the same pattern? + chain.DeployerKey.From, + []common.Address{proposer.Address}, // proposers + config.TimelockExecutors, //executors + []common.Address{canceller.Address}, // cancellers + []common.Address{bypasser.Address}, // bypassers + ) + return deployment.ContractDeploy[*owner_helpers.RBACTimelock]{ + timelock, cc, tx2, deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy timelock", "err", err) + return nil, err + } + lggr.Infow("deployed timelock", "addr", timelock.Address) + // We grant the timelock the admin role on the MCMS contracts. + tx, err := timelock.Contract.GrantRole(chain.DeployerKey, + v1_0.ADMIN_ROLE.ID, timelock.Address) + if _, err := deployment.ConfirmIfNoError(chain, tx, err); err != nil { + lggr.Errorw("Failed to grant timelock admin role", "err", err) + return nil, err + } + // After the proposer cycle is validated, + // we can remove the deployer as an admin. + return &MCMSWithTimelockDeploy{ + Canceller: canceller, + Bypasser: bypasser, + Proposer: proposer, + Timelock: timelock, + }, nil +} diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go new file mode 100644 index 00000000000..9969a0e5bc9 --- /dev/null +++ b/deployment/common/changeset/internal/mcms_test.go @@ -0,0 +1,58 @@ +package internal_test + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestDeployMCMSWithConfig(t *testing.T) { + lggr := logger.TestLogger(t) + chains := memory.NewMemoryChainsWithChainIDs(t, []uint64{ + chainsel.TEST_90000001.EvmChainID, + }) + ab := deployment.NewMemoryAddressBook() + _, err := internal.DeployMCMSWithConfig(types.ProposerManyChainMultisig, + lggr, chains[chainsel.TEST_90000001.Selector], ab, changeset.SingleGroupMCMS(t)) + require.NoError(t, err) +} + +func TestDeployMCMSWithTimelockContracts(t *testing.T) { + lggr := logger.TestLogger(t) + chains := memory.NewMemoryChainsWithChainIDs(t, []uint64{ + chainsel.TEST_90000001.EvmChainID, + }) + ab := deployment.NewMemoryAddressBook() + _, err := internal.DeployMCMSWithTimelockContracts(lggr, + chains[chainsel.TEST_90000001.Selector], + ab, types.MCMSWithTimelockConfig{ + Canceller: changeset.SingleGroupMCMS(t), + Bypasser: changeset.SingleGroupMCMS(t), + Proposer: changeset.SingleGroupMCMS(t), + TimelockExecutors: []common.Address{ + chains[chainsel.TEST_90000001.Selector].DeployerKey.From, + }, + TimelockMinDelay: big.NewInt(0), + }) + require.NoError(t, err) + addresses, err := ab.AddressesForChain(chainsel.TEST_90000001.Selector) + require.NoError(t, err) + require.Len(t, addresses, 4) + mcmsState, err := changeset.LoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) + require.NoError(t, err) + v, err := mcmsState.GenerateMCMSWithTimelockView() + b, err := json.MarshalIndent(v, "", " ") + require.NoError(t, err) + t.Log(string(b)) +} diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/changeset/mcms_test_helpers.go new file mode 100644 index 00000000000..3951149815c --- /dev/null +++ b/deployment/common/changeset/mcms_test_helpers.go @@ -0,0 +1,115 @@ +package changeset + +import ( + "bytes" + "context" + "crypto/ecdsa" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + chainsel "github.com/smartcontractkit/chain-selectors" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/deployment" +) + +var ( + // TestXXXMCMSSigner is a throwaway private key used for signing MCMS proposals. + // in tests. + TestXXXMCMSSigner *ecdsa.PrivateKey +) + +func init() { + key, err := crypto.GenerateKey() + if err != nil { + panic(err) + } + TestXXXMCMSSigner = key +} + +func SingleGroupMCMS(t *testing.T) config.Config { + publicKey := TestXXXMCMSSigner.Public().(*ecdsa.PublicKey) + // Convert the public key to an Ethereum address + address := crypto.PubkeyToAddress(*publicKey) + c, err := config.NewConfig(1, []common.Address{address}, []config.Config{}) + require.NoError(t, err) + return *c +} + +func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.MCMSWithTimelockProposal) *mcms.Executor { + executorClients := make(map[mcms.ChainIdentifier]mcms.ContractDeployBackend) + for _, chain := range env.Chains { + chainselc, exists := chainsel.ChainBySelector(chain.Selector) + require.True(t, exists) + chainSel := mcms.ChainIdentifier(chainselc.Selector) + executorClients[chainSel] = chain.Client + } + executor, err := proposal.ToExecutor(true) + require.NoError(t, err) + payload, err := executor.SigningHash() + require.NoError(t, err) + // Sign the payload + sig, err := crypto.Sign(payload.Bytes(), TestXXXMCMSSigner) + require.NoError(t, err) + mcmSig, err := mcms.NewSignatureFromBytes(sig) + require.NoError(t, err) + executor.Proposal.AddSignature(mcmSig) + require.NoError(t, executor.Proposal.Validate()) + return executor +} + +func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, + timelock *owner_helpers.RBACTimelock, sel uint64) { + t.Log("Executing proposal on chain", sel) + // Set the root. + tx, err2 := executor.SetRootOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, mcms.ChainIdentifier(sel)) + if err2 != nil { + require.NoError(t, deployment.MaybeDataErr(err2)) + } + _, err2 = env.Chains[sel].Confirm(tx) + require.NoError(t, err2) + + // TODO: This sort of helper probably should move to the MCMS lib. + // Execute all the transactions in the proposal which are for this chain. + for _, chainOp := range executor.Operations[mcms.ChainIdentifier(sel)] { + for idx, op := range executor.ChainAgnosticOps { + if bytes.Equal(op.Data, chainOp.Data) && op.To == chainOp.To { + opTx, err3 := executor.ExecuteOnChain(env.Chains[sel].Client, env.Chains[sel].DeployerKey, idx) + require.NoError(t, err3) + block, err3 := env.Chains[sel].Confirm(opTx) + require.NoError(t, err3) + t.Log("executed", chainOp) + it, err3 := timelock.FilterCallScheduled(&bind.FilterOpts{ + Start: block, + End: &block, + Context: context.Background(), + }, nil, nil) + require.NoError(t, err3) + var calls []owner_helpers.RBACTimelockCall + var pred, salt [32]byte + for it.Next() { + // Note these are the same for the whole batch, can overwrite + pred = it.Event.Predecessor + salt = it.Event.Salt + t.Log("scheduled", it.Event) + calls = append(calls, owner_helpers.RBACTimelockCall{ + Target: it.Event.Target, + Data: it.Event.Data, + Value: it.Event.Value, + }) + } + tx, err := timelock.ExecuteBatch( + env.Chains[sel].DeployerKey, calls, pred, salt) + require.NoError(t, err) + _, err = env.Chains[sel].Confirm(tx) + require.NoError(t, err) + } + } + } +} diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go new file mode 100644 index 00000000000..38a1d02c044 --- /dev/null +++ b/deployment/common/changeset/state.go @@ -0,0 +1,99 @@ +package changeset + +import ( + "errors" + + "github.com/ethereum/go-ethereum/common" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/common/view/v1_0" +) + +// MCMSWithTimelockState holds the Go bindings +// for a MCMSWithTimelock contract deployment. +// It is public for use in product specific packages. +type MCMSWithTimelockState struct { + CancellerMcm *owner_helpers.ManyChainMultiSig + BypasserMcm *owner_helpers.ManyChainMultiSig + ProposerMcm *owner_helpers.ManyChainMultiSig + Timelock *owner_helpers.RBACTimelock +} + +func (state MCMSWithTimelockState) Validate() error { + if state.Timelock == nil { + return errors.New("timelock not found") + } + if state.CancellerMcm == nil { + return errors.New("canceller not found") + } + if state.ProposerMcm == nil { + return errors.New("proposer not found") + } + if state.BypasserMcm == nil { + return errors.New("bypasser not found") + } + return nil +} + +func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWithTimelockView, error) { + if err := state.Validate(); err != nil { + return v1_0.MCMSWithTimelockView{}, err + } + timelockView, err := v1_0.GenerateTimelockView(*state.Timelock) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } + bypasserView, err := v1_0.GenerateMCMSView(*state.BypasserMcm) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } + proposerView, err := v1_0.GenerateMCMSView(*state.ProposerMcm) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } + cancellerView, err := v1_0.GenerateMCMSView(*state.CancellerMcm) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } + return v1_0.MCMSWithTimelockView{ + Timelock: timelockView, + Bypasser: bypasserView, + Proposer: proposerView, + Canceller: cancellerView, + }, nil +} + +func LoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string]deployment.TypeAndVersion) (*MCMSWithTimelockState, error) { + state := MCMSWithTimelockState{} + for address, tvStr := range addresses { + switch tvStr.String() { + case deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0).String(): + tl, err := owner_helpers.NewRBACTimelock(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.Timelock = tl + case deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.ProposerMcm = mcms + case deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.BypasserMcm = mcms + case deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0).String(): + mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CancellerMcm = mcms + } + } + return &state, nil +} diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go new file mode 100644 index 00000000000..0efb226d73b --- /dev/null +++ b/deployment/common/types/types.go @@ -0,0 +1,25 @@ +package types + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + + "github.com/smartcontractkit/chainlink/deployment" +) + +const ( + BypasserManyChainMultisig deployment.ContractType = "BypasserManyChainMultiSig" + CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" + ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" + RBACTimelock deployment.ContractType = "RBACTimelock" +) + +type MCMSWithTimelockConfig struct { + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockExecutors []common.Address + TimelockMinDelay *big.Int +} diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go new file mode 100644 index 00000000000..25ca614a553 --- /dev/null +++ b/deployment/common/view/v1_0/mcms.go @@ -0,0 +1,146 @@ +package v1_0 + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + + "github.com/smartcontractkit/chainlink/deployment/common/view/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" +) + +type Role struct { + ID common.Hash + Name string +} + +const ( + EXECUTOR_ROLE_STR = "EXECUTOR_ROLE" + BYPASSER_ROLE_STR = "BYPASSER_ROLE" + CANCELLER_ROLE_STR = "CANCELLER_ROLE" + PROPOSER_ROLE_STR = "PROPOSER_ROLE" + ADMIN_ROLE_STR = "ADMIN_ROLE" +) + +// https://github.com/smartcontractkit/ccip-owner-contracts/blob/9d81692b324ce7ea2ef8a75e683889edbc7e2dd0/src/RBACTimelock.sol#L71 +// Just to avoid invoking the Go binding to get these. +var ( + ADMIN_ROLE = Role{ + ID: utils.MustHash(ADMIN_ROLE_STR), + Name: ADMIN_ROLE_STR, + } + PROPOSER_ROLE = Role{ + ID: utils.MustHash(PROPOSER_ROLE_STR), + Name: PROPOSER_ROLE_STR, + } + BYPASSER_ROLE = Role{ + ID: utils.MustHash(BYPASSER_ROLE_STR), + Name: BYPASSER_ROLE_STR, + } + CANCELLER_ROLE = Role{ + ID: utils.MustHash(CANCELLER_ROLE_STR), + Name: CANCELLER_ROLE_STR, + } + EXECUTOR_ROLE = Role{ + ID: utils.MustHash(EXECUTOR_ROLE_STR), + Name: EXECUTOR_ROLE_STR, + } +) + +type MCMSView struct { + types.ContractMetaData + // Note config is json marshallable. + Config config.Config `json:"config"` +} + +func GenerateMCMSView(mcms owner_helpers.ManyChainMultiSig) (MCMSView, error) { + owner, err := mcms.Owner(nil) + if err != nil { + return MCMSView{}, nil + } + c, err := mcms.GetConfig(nil) + if err != nil { + return MCMSView{}, nil + } + parsedConfig, err := config.NewConfigFromRaw(c) + if err != nil { + return MCMSView{}, nil + } + return MCMSView{ + // Has no type and version on the contract + ContractMetaData: types.ContractMetaData{ + Owner: owner, + Address: mcms.Address(), + }, + Config: *parsedConfig, + }, nil +} + +type TimelockView struct { + types.ContractMetaData + MembersByRole map[string][]common.Address `json:"membersByRole"` +} + +func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { + membersByRole := make(map[string][]common.Address) + for _, role := range []Role{ADMIN_ROLE, PROPOSER_ROLE, BYPASSER_ROLE, CANCELLER_ROLE, EXECUTOR_ROLE} { + numMembers, err := tl.GetRoleMemberCount(nil, role.ID) + if err != nil { + return TimelockView{}, nil + } + for i := int64(0); i < numMembers.Int64(); i++ { + member, err2 := tl.GetRoleMember(nil, role.ID, big.NewInt(i)) + if err2 != nil { + return TimelockView{}, nil + } + membersByRole[role.Name] = append(membersByRole[role.Name], member) + } + } + return TimelockView{ + // Has no type and version or owner. + ContractMetaData: types.ContractMetaData{ + Address: tl.Address(), + }, + MembersByRole: membersByRole, + }, nil +} + +type MCMSWithTimelockView struct { + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` +} + +func GenerateMCMSWithTimelockView( + bypasser owner_helpers.ManyChainMultiSig, + canceller owner_helpers.ManyChainMultiSig, + proposer owner_helpers.ManyChainMultiSig, + timelock owner_helpers.RBACTimelock, +) (MCMSWithTimelockView, error) { + timelockView, err := GenerateTimelockView(timelock) + if err != nil { + return MCMSWithTimelockView{}, nil + } + bypasserView, err := GenerateMCMSView(bypasser) + if err != nil { + return MCMSWithTimelockView{}, nil + } + proposerView, err := GenerateMCMSView(proposer) + if err != nil { + return MCMSWithTimelockView{}, nil + } + cancellerView, err := GenerateMCMSView(canceller) + if err != nil { + return MCMSWithTimelockView{}, nil + } + + return MCMSWithTimelockView{ + Timelock: timelockView, + Bypasser: bypasserView, + Proposer: proposerView, + Canceller: cancellerView, + }, nil +} diff --git a/deployment/environment.go b/deployment/environment.go index 5d4e782b0fe..50301173a8d 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -128,6 +128,14 @@ func (e Environment) AllChainSelectorsExcluding(excluding []uint64) []uint64 { return selectors } +func (e Environment) AllDeployerKeys() []common.Address { + var deployerKeys []common.Address + for sel := range e.Chains { + deployerKeys = append(deployerKeys, e.Chains[sel].DeployerKey.From) + } + return deployerKeys +} + func ConfirmIfNoError(chain Chain, tx *types.Transaction, err error) (uint64, error) { if err != nil { //revive:disable diff --git a/integration-tests/ccip-tests/testsetups/test_helpers.go b/integration-tests/ccip-tests/testsetups/test_helpers.go index 4a480cdaa40..ea57f056945 100644 --- a/integration-tests/ccip-tests/testsetups/test_helpers.go +++ b/integration-tests/ccip-tests/testsetups/test_helpers.go @@ -6,9 +6,12 @@ import ( "os" "strconv" "testing" + "time" chainsel "github.com/smartcontractkit/chain-selectors" + commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" + jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" ctftestenv "github.com/smartcontractkit/chainlink-testing-framework/lib/docker/test_env" @@ -18,6 +21,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/ptr" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink-testing-framework/seth" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" "github.com/smartcontractkit/chainlink/deployment" ccipdeployment "github.com/smartcontractkit/chainlink/deployment/ccip" @@ -25,6 +30,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment/environment/devenv" clclient "github.com/smartcontractkit/chainlink/deployment/environment/nodeclient" "github.com/smartcontractkit/chainlink/integration-tests/actions" + ccipactions "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/contracts" "github.com/smartcontractkit/chainlink/integration-tests/docker/test_env" "github.com/smartcontractkit/chainlink/integration-tests/testconfig" @@ -109,6 +115,7 @@ func NewLocalDevEnvironment( require.NoError(t, err) require.NotNil(t, e) e.ExistingAddresses = ab + require.NotNil(t, testEnv.MockAdapter) e.MockAdapter = testEnv.MockAdapter envNodes, err := deployment.NodeInfo(e.NodeIDs, e.Offchain) @@ -126,6 +133,69 @@ func NewLocalDevEnvironment( // fund the nodes FundNodes(t, zeroLogLggr, testEnv, cfg, don.PluginNodes()) + output, err := changeset.DeployPrerequisites(*e, changeset.DeployPrerequisiteConfig{ + ChainSelectors: e.AllChainSelectors(), + }) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) + for _, chain := range e.AllChainSelectors() { + mcmsCfg[chain] = commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + } + output, err = commonchangeset.DeployMCMSWithTimelock(*e, mcmsCfg) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + + state, err := ccipdeployment.LoadOnchainState(*e) + require.NoError(t, err) + + var endpoint string + err = ccipactions.SetMockServerWithUSDCAttestation(e.MockAdapter, nil) + require.NoError(t, err) + endpoint = e.MockAdapter.InternalEndpoint + + tokenConfig := ccipdeployment.NewTestTokenConfig(state.Chains[feedSel].USDFeeds) + // Apply migration + output, err = changeset.InitialDeploy(*e, ccipdeployment.DeployCCIPContractConfig{ + HomeChainSel: homeChainSel, + FeedChainSel: feedSel, + ChainsToDeploy: e.AllChainSelectors(), + TokenConfig: tokenConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + USDCConfig: ccipdeployment.USDCConfig{ + Enabled: true, + USDCAttestationConfig: ccipdeployment.USDCAttestationConfig{ + API: endpoint, + APITimeout: commonconfig.MustNewDuration(time.Second), + APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) + + // Ensure capreg logs are up to date. + ccipdeployment.ReplayLogs(t, e.Offchain, replayBlocks) + + // Apply the jobs. + for nodeID, jobs := range output.JobSpecs { + for _, job := range jobs { + // Note these auto-accept + _, err := e.Offchain.ProposeJob(ctx, + &jobv1.ProposeJobRequest{ + NodeId: nodeID, + Spec: job, + }) + require.NoError(t, err) + } + } + return ccipdeployment.DeployedEnv{ Env: *e, HomeChainSel: homeChainSel, @@ -140,28 +210,6 @@ func NewLocalDevEnvironmentWithRMN( numRmnNodes int, ) (ccipdeployment.DeployedEnv, devenv.RMNCluster) { tenv, dockerenv, testCfg := NewLocalDevEnvironmentWithDefaultPrice(t, lggr) - state, err := ccipdeployment.LoadOnchainState(tenv.Env) - require.NoError(t, err) - - output, err := changeset.DeployPrerequisites(tenv.Env, changeset.DeployPrerequisiteConfig{ - ChainSelectors: tenv.Env.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - - // Deploy CCIP contracts. - newAddresses := deployment.NewMemoryAddressBook() - err = ccipdeployment.DeployCCIPContracts(tenv.Env, newAddresses, ccipdeployment.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: ccipdeployment.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccipdeployment.NewTestMCMSConfig(t, tenv.Env), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(newAddresses)) - l := logging.GetTestLogger(t) config := GenerateTestRMNConfig(t, numRmnNodes, tenv, MustNetworksToRPCMap(dockerenv.EVMNetworks)) require.NotNil(t, testCfg.CCIP) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index ddcd3ae65fa..7f59031bbe5 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -36,7 +36,6 @@ require ( github.com/slack-go/slack v0.15.0 github.com/smartcontractkit/chain-selectors v1.0.29 github.com/smartcontractkit/chainlink-automation v0.8.1 - github.com/smartcontractkit/chainlink-ccip v0.0.0-20241118091009-43c2b4804cec github.com/smartcontractkit/chainlink-common v0.3.1-0.20241114134822-aadff98ef068 github.com/smartcontractkit/chainlink-protos/job-distributor v0.6.0 github.com/smartcontractkit/chainlink-testing-framework/havoc v1.50.2 @@ -414,6 +413,7 @@ require ( github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smartcontractkit/ccip-owner-contracts v0.0.0-20240926212305-a6deabdfce86 // indirect + github.com/smartcontractkit/chainlink-ccip v0.0.0-20241118091009-43c2b4804cec // indirect github.com/smartcontractkit/chainlink-cosmos v0.5.2-0.20241017133723-5277829bd53f // indirect github.com/smartcontractkit/chainlink-data-streams v0.1.1-0.20241114154055-8d29ea018b57 // indirect github.com/smartcontractkit/chainlink-feeds v0.1.1 // indirect diff --git a/integration-tests/smoke/ccip_messaging_test.go b/integration-tests/smoke/ccip_messaging_test.go index 4aa9ba34229..6bb34658e22 100644 --- a/integration-tests/smoke/ccip_messaging_test.go +++ b/integration-tests/smoke/ccip_messaging_test.go @@ -15,11 +15,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/hashutil" "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" - "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" @@ -66,44 +63,6 @@ func Test_CCIPMessaging(t *testing.T) { ", source chain selector:", sourceChain, ", dest chain selector:", destChain, ) - output, err := changeset.DeployPrerequisites(e.Env, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.Env.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(output.AddressBook)) - - tokenConfig := ccdeploy.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - // Apply migration - output, err = changeset.InitialDeploy(e.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: allChainSelectors, - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e.Env), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(output.AddressBook)) - // Get new state after migration. - state, err = ccdeploy.LoadOnchainState(e.Env) - require.NoError(t, err) - - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Env.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // connect a single lane, source to dest require.NoError(t, ccdeploy.AddLaneWithDefaultPrices(e.Env, state, sourceChain, destChain)) diff --git a/integration-tests/smoke/ccip_rmn_test.go b/integration-tests/smoke/ccip_rmn_test.go index e8e81688239..d1cadabc06a 100644 --- a/integration-tests/smoke/ccip_rmn_test.go +++ b/integration-tests/smoke/ccip_rmn_test.go @@ -13,7 +13,6 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/require" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/osutil" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -327,24 +326,7 @@ func runRmnTestCase(t *testing.T, tc rmnTestCase) { } } - jobSpecs, err := ccipdeployment.NewCCIPJobSpecs(envWithRMN.Env.NodeIDs, envWithRMN.Env.Offchain) - require.NoError(t, err) - - ctx := ccipdeployment.Context(t) - ccipdeployment.ReplayLogs(t, envWithRMN.Env.Offchain, envWithRMN.ReplayBlocks) - - for nodeID, jobs := range jobSpecs { - for _, job := range jobs { - _, err := envWithRMN.Env.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Add all lanes require.NoError(t, ccipdeployment.AddLanesForAll(envWithRMN.Env, onChainState)) diff --git a/integration-tests/smoke/ccip_test.go b/integration-tests/smoke/ccip_test.go index 007a3c37e52..36aed7d5baa 100644 --- a/integration-tests/smoke/ccip_test.go +++ b/integration-tests/smoke/ccip_test.go @@ -7,12 +7,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - - "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -21,51 +17,11 @@ import ( func TestInitialDeployOnLocal(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - ctx := ccdeploy.Context(t) tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) e := tenv.Env - - state, err := ccdeploy.LoadOnchainState(tenv.Env) - require.NoError(t, err) - - feeds := state.Chains[tenv.FeedChainSel].USDFeeds - output, err := changeset.DeployPrerequisites(tenv.Env, changeset.DeployPrerequisiteConfig{ - ChainSelectors: tenv.Env.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - - // Apply migration - output, err = changeset.InitialDeploy(tenv.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: tenv.Env.AllChainSelectors(), - TokenConfig: ccdeploy.NewTestTokenConfig(feeds), - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - // Get new state after migration. - state, err = ccdeploy.LoadOnchainState(e) + state, err := ccdeploy.LoadOnchainState(e) require.NoError(t, err) - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Add all lanes require.NoError(t, ccdeploy.AddLanesForAll(e, state)) // Need to keep track of the block number for each chain so that event subscription can be done from that block. @@ -113,34 +69,11 @@ func TestInitialDeployOnLocal(t *testing.T) { func TestTokenTransfer(t *testing.T) { t.Parallel() lggr := logger.TestLogger(t) - ctx := ccdeploy.Context(t) tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) - e := tenv.Env state, err := ccdeploy.LoadOnchainState(e) require.NoError(t, err) - output, err := changeset.DeployPrerequisites(e, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - - // Apply migration - output, err = changeset.InitialDeploy(e, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: ccdeploy.NewTestTokenConfig(state.Chains[tenv.FeedChainSel].USDFeeds), - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - // Get new state after migration and mock USDC token deployment. - state, err = ccdeploy.LoadOnchainState(e) - require.NoError(t, err) - srcToken, _, dstToken, _, err := ccdeploy.DeployTransferableToken( lggr, tenv.Env.Chains, @@ -152,22 +85,6 @@ func TestTokenTransfer(t *testing.T) { ) require.NoError(t, err) - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Add all lanes require.NoError(t, ccdeploy.AddLanesForAll(e, state)) // Need to keep track of the block number for each chain so that event subscription can be done from that block. diff --git a/integration-tests/smoke/ccip_usdc_test.go b/integration-tests/smoke/ccip_usdc_test.go index 091912b26fb..a183808f2f8 100644 --- a/integration-tests/smoke/ccip_usdc_test.go +++ b/integration-tests/smoke/ccip_usdc_test.go @@ -2,8 +2,6 @@ package smoke import ( "math/big" - "net/http" - "net/http/httptest" "testing" "time" @@ -13,16 +11,10 @@ import ( "golang.org/x/exp/maps" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/actions" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" @@ -33,20 +25,7 @@ import ( func TestUSDCTokenTransfer(t *testing.T) { lggr := logger.TestLogger(t) - ctx := ccdeploy.Context(t) - tenv, cluster, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) - - var endpoint string - // When inmemory env then spin up in memory mock server - if cluster == nil { - server := mockAttestationResponse() - defer server.Close() - endpoint = server.URL - } else { - err := actions.SetMockServerWithUSDCAttestation(tenv.Env.MockAdapter, nil) - require.NoError(t, err) - endpoint = tenv.Env.MockAdapter.InternalEndpoint - } + tenv, _, _ := testsetups.NewLocalDevEnvironmentWithDefaultPrice(t, lggr) e := tenv.Env state, err := ccdeploy.LoadOnchainState(e) @@ -56,45 +35,6 @@ func TestUSDCTokenTransfer(t *testing.T) { sourceChain := allChainSelectors[0] destChain := allChainSelectors[1] - feeds := state.Chains[tenv.FeedChainSel].USDFeeds - tokenConfig := ccdeploy.NewTokenConfig() - tokenConfig.UpsertTokenInfo(ccdeploy.LinkSymbol, - pluginconfig.TokenInfo{ - AggregatorAddress: cciptypes.UnknownEncodedAddress(feeds[ccdeploy.LinkSymbol].Address().String()), - Decimals: ccdeploy.LinkDecimals, - DeviationPPB: cciptypes.NewBigIntFromInt64(1e9), - }, - ) - - output, err := changeset.DeployPrerequisites(e, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, tenv.Env.ExistingAddresses.Merge(output.AddressBook)) - - // Apply migration - output, err = changeset.InitialDeploy(e, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: tenv.HomeChainSel, - FeedChainSel: tenv.FeedChainSel, - ChainsToDeploy: e.AllChainSelectors(), - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - USDCConfig: ccdeploy.USDCConfig{ - Enabled: true, - USDCAttestationConfig: ccdeploy.USDCAttestationConfig{ - API: endpoint, - APITimeout: commonconfig.MustNewDuration(time.Second), - APIInterval: commonconfig.MustNewDuration(500 * time.Millisecond), - }, - }, - }) - require.NoError(t, err) - require.NoError(t, e.ExistingAddresses.Merge(output.AddressBook)) - - state, err = ccdeploy.LoadOnchainState(e) - require.NoError(t, err) - srcUSDC, dstUSDC, err := ccdeploy.ConfigureUSDCTokenPools(lggr, e.Chains, sourceChain, destChain, state) require.NoError(t, err) @@ -109,22 +49,6 @@ func TestUSDCTokenTransfer(t *testing.T) { ) require.NoError(t, err) - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Offchain, tenv.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - // Add all lanes require.NoError(t, ccdeploy.AddLanesForAll(e, state)) @@ -312,25 +236,6 @@ func transferAndWaitForSuccess( ccdeploy.ConfirmExecWithSeqNrForAll(t, env, state, expectedSeqNum, startBlocks) } -// mockAttestationResponse mocks the USDC attestation server, it returns random Attestation. -// We don't need to return exactly the same attestation, because our Mocked USDC contract doesn't rely on any specific -// value, but instead of that it just checks if the attestation is present. Therefore, it makes the test a bit simpler -// and doesn't require very detailed mocks. Please see tests in chainlink-ccip for detailed tests using real attestations -func mockAttestationResponse() *httptest.Server { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - response := `{ - "status": "complete", - "attestation": "0x9049623e91719ef2aa63c55f357be2529b0e7122ae552c18aff8db58b4633c4d3920ff03d3a6d1ddf11f06bf64d7fd60d45447ac81f527ba628877dc5ca759651b08ffae25a6d3b1411749765244f0a1c131cbfe04430d687a2e12fd9d2e6dc08e118ad95d94ad832332cf3c4f7a4f3da0baa803b7be024b02db81951c0f0714de1b" - }` - - _, err := w.Write([]byte(response)) - if err != nil { - panic(err) - } - })) - return server -} - func waitForTheTokenBalance( t *testing.T, token common.Address, diff --git a/integration-tests/smoke/fee_boosting_test.go b/integration-tests/smoke/fee_boosting_test.go index 625200360e8..0e0fb094016 100644 --- a/integration-tests/smoke/fee_boosting_test.go +++ b/integration-tests/smoke/fee_boosting_test.go @@ -9,10 +9,8 @@ import ( "github.com/test-go/testify/require" "golang.org/x/exp/maps" - jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -35,8 +33,6 @@ type priceFeedPrices struct { // TODO: find a way to reuse the same test setup for all tests func Test_CCIPFeeBoosting(t *testing.T) { - ctx := ccdeploy.Context(t) - setupTestEnv := func(t *testing.T, numChains int) (ccdeploy.DeployedEnv, ccdeploy.CCIPOnChainState, []uint64) { e, _, _ := testsetups.NewLocalDevEnvironment( t, logger.TestLogger(t), @@ -48,44 +44,6 @@ func Test_CCIPFeeBoosting(t *testing.T) { allChainSelectors := maps.Keys(e.Env.Chains) require.Len(t, allChainSelectors, numChains) - - output, err := changeset.DeployPrerequisites(e.Env, changeset.DeployPrerequisiteConfig{ - ChainSelectors: e.Env.AllChainSelectors(), - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(output.AddressBook)) - - tokenConfig := ccdeploy.NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) - // Apply migration - output, err = changeset.InitialDeploy(e.Env, ccdeploy.DeployCCIPContractConfig{ - HomeChainSel: e.HomeChainSel, - FeedChainSel: e.FeedChainSel, - ChainsToDeploy: allChainSelectors, - TokenConfig: tokenConfig, - MCMSConfig: ccdeploy.NewTestMCMSConfig(t, e.Env), - OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), - }) - require.NoError(t, err) - require.NoError(t, e.Env.ExistingAddresses.Merge(output.AddressBook)) - state, err = ccdeploy.LoadOnchainState(e.Env) - require.NoError(t, err) - - // Ensure capreg logs are up to date. - ccdeploy.ReplayLogs(t, e.Env.Offchain, e.ReplayBlocks) - - // Apply the jobs. - for nodeID, jobs := range output.JobSpecs { - for _, job := range jobs { - // Note these auto-accept - _, err := e.Env.Offchain.ProposeJob(ctx, - &jobv1.ProposeJobRequest{ - NodeId: nodeID, - Spec: job, - }) - require.NoError(t, err) - } - } - return e, state, allChainSelectors }