diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 796db6aed09..45a6fc491cf 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -33,6 +33,10 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { source: state.Chains[source].Timelock, dest: state.Chains[dest].Timelock, } + callProxies := map[uint64]*gethwrappers.CallProxy{ + source: state.Chains[source].CallProxy, + dest: state.Chains[dest].CallProxy, + } // at this point we have the initial deploys done, now we need to transfer ownership // to the timelock contract @@ -40,7 +44,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { require.NoError(t, err) // compose the transfer ownership and accept ownership changesets - _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, e.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), diff --git a/deployment/ccip/changeset/cs_active_candidate_test.go b/deployment/ccip/changeset/cs_active_candidate_test.go index 4bd0c9fd7a4..21a74b68b47 100644 --- a/deployment/ccip/changeset/cs_active_candidate_test.go +++ b/deployment/ccip/changeset/cs_active_candidate_test.go @@ -95,7 +95,11 @@ func TestActiveCandidate(t *testing.T) { for _, chain := range allChains { timelocks[chain] = state.Chains[chain].Timelock } - _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ + callProxies := make(map[uint64]*gethwrappers.CallProxy) + for _, chain := range allChains { + callProxies[chain] = state.Chains[chain].CallProxy + } + _, err = commonchangeset.ApplyChangesets(t, e, timelocks, callProxies, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -177,7 +181,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit plugin", 0) require.NoError(t, err) setCommitCandidateSigned := commonchangeset.SignProposal(t, e, setCommitCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setCommitCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // create the op for the commit plugin as well setExecCandidateOp, err := setCandidateOnExistingDon( @@ -195,7 +199,7 @@ func TestActiveCandidate(t *testing.T) { }}, "set new candidates on commit and exec plugins", 0) require.NoError(t, err) setExecCandidateSigned := commonchangeset.SignProposal(t, e, setExecCandidateProposal) - commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, setExecCandidateSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // check setup was successful by confirming number of nodes from cap reg donInfo, err = state.Chains[tenv.HomeChainSel].CapabilityRegistry.GetDON(nil, donID) @@ -222,7 +226,7 @@ func TestActiveCandidate(t *testing.T) { }}, "promote candidates and revoke actives", 0) require.NoError(t, err) promoteSigned := commonchangeset.SignProposal(t, e, promoteProposal) - commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, tenv.HomeChainSel) + commonchangeset.ExecuteProposal(t, e, promoteSigned, state.Chains[tenv.HomeChainSel].Timelock, state.Chains[tenv.HomeChainSel].CallProxy, tenv.HomeChainSel) // [NEW ACTIVE, NO CANDIDATE] done promoting // [NEW ACTIVE, NO CANDIDATE] check onchain state diff --git a/deployment/ccip/changeset/cs_add_chain_test.go b/deployment/ccip/changeset/cs_add_chain_test.go index f6e1c04c469..2c660ac0b3f 100644 --- a/deployment/ccip/changeset/cs_add_chain_test.go +++ b/deployment/ccip/changeset/cs_add_chain_test.go @@ -52,13 +52,12 @@ func TestAddChainInbound(t *testing.T) { 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), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: initialDeploy, @@ -107,7 +106,7 @@ func TestAddChainInbound(t *testing.T) { require.NoError(t, err) // Deploy contracts to new chain - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: []uint64{newChain}, @@ -150,6 +149,10 @@ func TestAddChainInbound(t *testing.T) { initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + initialDeploy[0]: state.Chains[initialDeploy[0]].CallProxy, + initialDeploy[1]: state.Chains[initialDeploy[1]].CallProxy, + initialDeploy[2]: state.Chains[initialDeploy[2]].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -182,6 +185,9 @@ func TestAddChainInbound(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ e.HomeChainSel: state.Chains[e.HomeChainSel].Timelock, newChain: state.Chains[newChain].Timelock, + }, map[uint64]*gethwrappers.CallProxy{ + e.HomeChainSel: state.Chains[e.HomeChainSel].CallProxy, + newChain: state.Chains[newChain].CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(AddDonAndSetCandidateChangeset), diff --git a/deployment/ccip/changeset/cs_deploy_chain_test.go b/deployment/ccip/changeset/cs_deploy_chain_test.go index 234d73cc4b5..3dd1d6c6103 100644 --- a/deployment/ccip/changeset/cs_deploy_chain_test.go +++ b/deployment/ccip/changeset/cs_deploy_chain_test.go @@ -31,14 +31,13 @@ func TestDeployChainContractsChangeset(t *testing.T) { 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), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } - e, err = commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err = commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(DeployHomeChain), Config: DeployHomeChainConfig{ diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 22ae59fc360..122ce8ec13c 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -311,6 +311,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type for address, tvStr := range addresses { switch tvStr.String() { case deployment.NewTypeAndVersion(commontypes.RBACTimelock, deployment.Version1_0_0).String(), + deployment.NewTypeAndVersion(commontypes.CallProxy, 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(), diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 1ee8b0d0e42..422e0933176 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -279,11 +279,10 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, mcmsCfg := make(map[uint64]commontypes.MCMSWithTimelockConfig) for _, c := range e.Env.AllChainSelectors() { mcmsCfg[c] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: e.Env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } var ( @@ -298,7 +297,7 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, } // Need to deploy prerequisites first so that we can form the USDC config // no proposals to be made, timelock can be passed as nil here - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: allChains, @@ -367,13 +366,15 @@ func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, // Build the per chain config. chainConfigs := make(map[uint64]CCIPOCRParams) timelocksPerChain := make(map[uint64]*gethwrappers.RBACTimelock) + callProxiesPerChain := make(map[uint64]*gethwrappers.CallProxy) for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock + callProxiesPerChain[chain] = state.Chains[chain].CallProxy tokenInfo := tokenConfig.GetTokenInfo(e.Env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) chainConfigs[chain] = DefaultOCRParams(e.FeedChainSel, tokenInfo, tokenDataProviders) } // Deploy second set of changesets to deploy and configure the CCIP contracts. - e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{ + e.Env, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, callProxiesPerChain, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(ConfigureNewChains), Config: NewChainsConfig{ @@ -823,7 +824,7 @@ func ProcessChangeset(t *testing.T, e deployment.Environment, c deployment.Chang signed := commonchangeset.SignProposal(t, e, &prop) for _, sel := range chains.ToSlice() { - commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, sel) + commonchangeset.ExecuteProposal(t, e, signed, state.Chains[sel].Timelock, state.Chains[sel].CallProxy, sel) } } } diff --git a/deployment/common/changeset/deploy_link_token_test.go b/deployment/common/changeset/deploy_link_token_test.go index a61743e9bf4..5eb3d0a1e34 100644 --- a/deployment/common/changeset/deploy_link_token_test.go +++ b/deployment/common/changeset/deploy_link_token_test.go @@ -18,7 +18,7 @@ func TestDeployLinkToken(t *testing.T) { Chains: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := changeset.ApplyChangesets(t, e, nil, []changeset.ChangesetApplication{ + e, err := changeset.ApplyChangesets(t, e, nil, nil, []changeset.ChangesetApplication{ { Changeset: changeset.WrapChangeSet(changeset.DeployLinkToken), Config: []uint64{chain1}, diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 281f43924f4..39c319f0cd1 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -55,6 +55,7 @@ type MCMSWithTimelockDeploy struct { Bypasser *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Proposer *deployment.ContractDeploy[*owner_helpers.ManyChainMultiSig] Timelock *deployment.ContractDeploy[*owner_helpers.RBACTimelock] + CallProxy *deployment.ContractDeploy[*owner_helpers.CallProxy] } func DeployMCMSWithTimelockContractsBatch( @@ -106,10 +107,10 @@ func DeployMCMSWithTimelockContracts( // 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 + []common.Address{proposer.Address}, // proposers + []common.Address{}, // executors + []common.Address{canceller.Address, proposer.Address, bypasser.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, @@ -119,6 +120,37 @@ func DeployMCMSWithTimelockContracts( lggr.Errorw("Failed to deploy timelock", "chain", chain.String(), "err", err) return nil, err } + + callProxy, err := deployment.DeployContract(lggr, chain, ab, + func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.CallProxy] { + callProxy, tx2, cc, err2 := owner_helpers.DeployCallProxy( + chain.DeployerKey, + chain.Client, + timelock.Address, + ) + return deployment.ContractDeploy[*owner_helpers.CallProxy]{ + callProxy, cc, tx2, deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0), err2, + } + }) + if err != nil { + lggr.Errorw("Failed to deploy call proxy", "chain", chain.String(), "err", err) + return nil, err + } + + grantRoleTx, err := timelock.Contract.GrantRole( + chain.DeployerKey, + v1_0.EXECUTOR_ROLE.ID, + callProxy.Address, + ) + if err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } + + if _, err := deployment.ConfirmIfNoError(chain, grantRoleTx, err); err != nil { + lggr.Errorw("Failed to grant timelock executor role", "chain", chain.String(), "err", err) + return nil, err + } // 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) @@ -133,5 +165,6 @@ func DeployMCMSWithTimelockContracts( Bypasser: bypasser, Proposer: proposer, Timelock: timelock, + CallProxy: callProxy, }, nil } diff --git a/deployment/common/changeset/internal/mcms_test.go b/deployment/common/changeset/internal/mcms_test.go index 2269911f4cd..10fb1d980de 100644 --- a/deployment/common/changeset/internal/mcms_test.go +++ b/deployment/common/changeset/internal/mcms_test.go @@ -5,7 +5,6 @@ import ( "math/big" "testing" - "github.com/ethereum/go-ethereum/common" chainsel "github.com/smartcontractkit/chain-selectors" "github.com/stretchr/testify/require" @@ -37,18 +36,15 @@ func TestDeployMCMSWithTimelockContracts(t *testing.T) { _, 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, - }, + Canceller: changeset.SingleGroupMCMS(t), + Bypasser: changeset.SingleGroupMCMS(t), + Proposer: changeset.SingleGroupMCMS(t), 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) + require.Len(t, addresses, 5) mcmsState, err := changeset.MaybeLoadMCMSWithTimelockState(chains[chainsel.TEST_90000001.Selector], addresses) require.NoError(t, err) v, err := mcmsState.GenerateMCMSWithTimelockView() diff --git a/deployment/common/changeset/mcms_test_helpers.go b/deployment/common/changeset/mcms_test_helpers.go index 3951149815c..b1fd4d878fc 100644 --- a/deployment/common/changeset/mcms_test_helpers.go +++ b/deployment/common/changeset/mcms_test_helpers.go @@ -65,7 +65,7 @@ func SignProposal(t *testing.T, env deployment.Environment, proposal *timelock.M } func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Executor, - timelock *owner_helpers.RBACTimelock, sel uint64) { + timelock *owner_helpers.RBACTimelock, callProxy *owner_helpers.CallProxy, 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)) @@ -104,7 +104,8 @@ func ExecuteProposal(t *testing.T, env deployment.Environment, executor *mcms.Ex Value: it.Event.Value, }) } - tx, err := timelock.ExecuteBatch( + timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(callProxy.Address(), env.Chains[sel].Client) + tx, err := timelockExecutorProxy.ExecuteBatch( env.Chains[sel].DeployerKey, calls, pred, salt) require.NoError(t, err) _, err = env.Chains[sel].Confirm(tx) diff --git a/deployment/common/changeset/state.go b/deployment/common/changeset/state.go index 0055c908f8d..a580c13b40b 100644 --- a/deployment/common/changeset/state.go +++ b/deployment/common/changeset/state.go @@ -23,6 +23,7 @@ type MCMSWithTimelockState struct { BypasserMcm *owner_helpers.ManyChainMultiSig ProposerMcm *owner_helpers.ManyChainMultiSig Timelock *owner_helpers.RBACTimelock + CallProxy *owner_helpers.CallProxy } // Validate checks that all fields are non-nil, ensuring it's ready @@ -40,6 +41,9 @@ func (state MCMSWithTimelockState) Validate() error { if state.BypasserMcm == nil { return errors.New("bypasser not found") } + if state.CallProxy == nil { + return errors.New("call proxy not found") + } return nil } @@ -51,6 +55,10 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith if err != nil { return v1_0.MCMSWithTimelockView{}, nil } + callProxyView, err := v1_0.GenerateCallProxyView(*state.CallProxy) + if err != nil { + return v1_0.MCMSWithTimelockView{}, nil + } bypasserView, err := v1_0.GenerateMCMSView(*state.BypasserMcm) if err != nil { return v1_0.MCMSWithTimelockView{}, nil @@ -68,6 +76,7 @@ func (state MCMSWithTimelockState) GenerateMCMSWithTimelockView() (v1_0.MCMSWith Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } @@ -82,6 +91,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string state := MCMSWithTimelockState{} // We expect one of each contract on the chain. timelock := deployment.NewTypeAndVersion(types.RBACTimelock, deployment.Version1_0_0) + callProxy := deployment.NewTypeAndVersion(types.CallProxy, deployment.Version1_0_0) proposer := deployment.NewTypeAndVersion(types.ProposerManyChainMultisig, deployment.Version1_0_0) canceller := deployment.NewTypeAndVersion(types.CancellerManyChainMultisig, deployment.Version1_0_0) bypasser := deployment.NewTypeAndVersion(types.BypasserManyChainMultisig, deployment.Version1_0_0) @@ -89,7 +99,7 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string // Ensure we either have the bundle or not. _, err := deployment.AddressesContainBundle(addresses, map[deployment.TypeAndVersion]struct{}{ - timelock: {}, proposer: {}, canceller: {}, bypasser: {}, + timelock: {}, proposer: {}, canceller: {}, bypasser: {}, callProxy: {}, }) if err != nil { return nil, fmt.Errorf("unable to check MCMS contracts on chain %s error: %w", chain.Name(), err) @@ -103,6 +113,12 @@ func MaybeLoadMCMSWithTimelockState(chain deployment.Chain, addresses map[string return nil, err } state.Timelock = tl + case callProxy: + cp, err := owner_helpers.NewCallProxy(common.HexToAddress(address), chain.Client) + if err != nil { + return nil, err + } + state.CallProxy = cp case proposer: mcms, err := owner_helpers.NewManyChainMultiSig(common.HexToAddress(address), chain.Client) if err != nil { diff --git a/deployment/common/changeset/test_helpers.go b/deployment/common/changeset/test_helpers.go index 913b4544f30..0d068ade971 100644 --- a/deployment/common/changeset/test_helpers.go +++ b/deployment/common/changeset/test_helpers.go @@ -33,7 +33,7 @@ func WrapChangeSet[C any](fn deployment.ChangeSet[C]) func(e deployment.Environm } // ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, changesetApplications []ChangesetApplication) (deployment.Environment, error) { +func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain map[uint64]*gethwrappers.RBACTimelock, callProxiesPerChain map[uint64]*gethwrappers.CallProxy, changesetApplications []ChangesetApplication) (deployment.Environment, error) { currentEnv := e for i, csa := range changesetApplications { out, err := csa.Changeset(currentEnv, csa.Config) @@ -79,7 +79,12 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelocksPerChain m if !ok || timelock == nil { return deployment.Environment{}, fmt.Errorf("timelock not found for chain %d", sel) } - ExecuteProposal(t, e, signed, timelock, sel) + + callProxy, ok := callProxiesPerChain[sel] + if !ok || callProxy == nil { + return deployment.Environment{}, fmt.Errorf("call proxy not found for chain %d", sel) + } + ExecuteProposal(t, e, signed, timelock, callProxy, sel) } } } diff --git a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go index 6cdff286707..fd50c9ad66f 100644 --- a/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go +++ b/deployment/common/changeset/transfer_to_mcms_with_timelock_test.go @@ -21,7 +21,7 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Nodes: 1, }) chain1 := e.AllChainSelectors()[0] - e, err := ApplyChangesets(t, e, nil, []ChangesetApplication{ + e, err := ApplyChangesets(t, e, nil, nil, []ChangesetApplication{ { Changeset: WrapChangeSet(DeployLinkToken), Config: []uint64{chain1}, @@ -30,11 +30,10 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { Changeset: WrapChangeSet(DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ chain1: { - Canceller: SingleGroupMCMS(t), - Bypasser: SingleGroupMCMS(t), - Proposer: SingleGroupMCMS(t), - TimelockExecutors: e.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: SingleGroupMCMS(t), + Bypasser: SingleGroupMCMS(t), + Proposer: SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -48,6 +47,8 @@ func TestTransferToMCMSWithTimelock(t *testing.T) { require.NoError(t, err) e, err = ApplyChangesets(t, e, map[uint64]*owner_helpers.RBACTimelock{ chain1: state.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + chain1: state.CallProxy, }, []ChangesetApplication{ { Changeset: WrapChangeSet(TransferToMCMSWithTimelock), diff --git a/deployment/common/types/types.go b/deployment/common/types/types.go index 386ef8fbb36..0f04421af43 100644 --- a/deployment/common/types/types.go +++ b/deployment/common/types/types.go @@ -5,7 +5,6 @@ import ( "math/big" "time" - "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" "github.com/smartcontractkit/chainlink/deployment" @@ -16,6 +15,7 @@ const ( CancellerManyChainMultisig deployment.ContractType = "CancellerManyChainMultiSig" ProposerManyChainMultisig deployment.ContractType = "ProposerManyChainMultiSig" RBACTimelock deployment.ContractType = "RBACTimelock" + CallProxy deployment.ContractType = "CallProxy" // LinkToken is the burn/mint link token. It should be used everywhere for // new deployments. Corresponds to // https://github.com/smartcontractkit/chainlink/blob/develop/core/gethwrappers/shared/generated/link_token/link_token.go#L34 @@ -29,11 +29,10 @@ const ( ) type MCMSWithTimelockConfig struct { - Canceller config.Config - Bypasser config.Config - Proposer config.Config - TimelockExecutors []common.Address - TimelockMinDelay *big.Int + Canceller config.Config + Bypasser config.Config + Proposer config.Config + TimelockMinDelay *big.Int } type OCRParameters struct { diff --git a/deployment/common/view/v1_0/mcms.go b/deployment/common/view/v1_0/mcms.go index 25ca614a553..bc971623545 100644 --- a/deployment/common/view/v1_0/mcms.go +++ b/deployment/common/view/v1_0/mcms.go @@ -107,11 +107,24 @@ func GenerateTimelockView(tl owner_helpers.RBACTimelock) (TimelockView, error) { }, nil } +type CallProxyView struct { + types.ContractMetaData +} + +func GenerateCallProxyView(cp owner_helpers.CallProxy) (CallProxyView, error) { + return CallProxyView{ + ContractMetaData: types.ContractMetaData{ + Address: cp.Address(), + }, + }, nil +} + type MCMSWithTimelockView struct { - Bypasser MCMSView `json:"bypasser"` - Canceller MCMSView `json:"canceller"` - Proposer MCMSView `json:"proposer"` - Timelock TimelockView `json:"timelock"` + Bypasser MCMSView `json:"bypasser"` + Canceller MCMSView `json:"canceller"` + Proposer MCMSView `json:"proposer"` + Timelock TimelockView `json:"timelock"` + CallProxy CallProxyView `json:"callProxy"` } func GenerateMCMSWithTimelockView( @@ -124,6 +137,10 @@ func GenerateMCMSWithTimelockView( if err != nil { return MCMSWithTimelockView{}, nil } + callProxyView, err := GenerateCallProxyView(owner_helpers.CallProxy{}) + if err != nil { + return MCMSWithTimelockView{}, nil + } bypasserView, err := GenerateMCMSView(bypasser) if err != nil { return MCMSWithTimelockView{}, nil @@ -142,5 +159,6 @@ func GenerateMCMSWithTimelockView( Bypasser: bypasserView, Proposer: proposerView, Canceller: cancellerView, + CallProxy: callProxyView, }, nil } diff --git a/deployment/keystone/changeset/accept_ownership_test.go b/deployment/keystone/changeset/accept_ownership_test.go index f205adda496..64f3b3c86fb 100644 --- a/deployment/keystone/changeset/accept_ownership_test.go +++ b/deployment/keystone/changeset/accept_ownership_test.go @@ -25,7 +25,7 @@ func TestAcceptAllOwnership(t *testing.T) { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) registrySel := env.AllChainSelectors()[0] - env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err := commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.DeployCapabilityRegistry), Config: registrySel, @@ -42,11 +42,10 @@ func TestAcceptAllOwnership(t *testing.T) { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ registrySel: { - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), }, }, }, @@ -59,6 +58,8 @@ func TestAcceptAllOwnership(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*owner_helpers.RBACTimelock{ registrySel: timelock.Timelock, + }, map[uint64]*owner_helpers.CallProxy{ + registrySel: timelock.CallProxy, }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.AcceptAllOwnershipsProposal), diff --git a/deployment/keystone/changeset/deploy_forwarder_test.go b/deployment/keystone/changeset/deploy_forwarder_test.go index 32a53f1cf08..2c449000397 100644 --- a/deployment/keystone/changeset/deploy_forwarder_test.go +++ b/deployment/keystone/changeset/deploy_forwarder_test.go @@ -118,11 +118,14 @@ func TestConfigureForwarders(t *testing.T) { require.Nil(t, csOut.AddressBook) timelocks := make(map[uint64]*gethwrappers.RBACTimelock) + callProxies := make(map[uint64]*gethwrappers.CallProxy) for selector, contractSet := range te.ContractSets() { require.NotNil(t, contractSet.Timelock) timelocks[selector] = contractSet.Timelock + require.NotNil(t, contractSet.CallProxy) + callProxies[selector] = contractSet.CallProxy } - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureForwardContracts), Config: cfg, diff --git a/deployment/keystone/changeset/deploy_ocr3_test.go b/deployment/keystone/changeset/deploy_ocr3_test.go index 0d49af68823..8184351fcde 100644 --- a/deployment/keystone/changeset/deploy_ocr3_test.go +++ b/deployment/keystone/changeset/deploy_ocr3_test.go @@ -130,10 +130,13 @@ func TestConfigureOCR3(t *testing.T) { var timelocks = map[uint64]*gethwrappers.RBACTimelock{ te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].Timelock, } + var callProxies = map[uint64]*gethwrappers.CallProxy{ + te.RegistrySelector: contractSetsResp.ContractSets[te.RegistrySelector].CallProxy, + } // now apply the changeset such that the proposal is signed and execed w2 := &bytes.Buffer{} cfg.WriteGeneratedConfig = w2 - _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, []commonchangeset.ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, te.Env, timelocks, callProxies, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(changeset.ConfigureOCR3Contract), Config: cfg, diff --git a/deployment/keystone/changeset/helpers_test.go b/deployment/keystone/changeset/helpers_test.go index d4435d8f7a6..769e4a38132 100644 --- a/deployment/keystone/changeset/helpers_test.go +++ b/deployment/keystone/changeset/helpers_test.go @@ -130,7 +130,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { Chains: chains, ExistingAddresses: deployment.NewMemoryAddressBook(), } - e, err := commonchangeset.ApplyChangesets(t, e, nil, []commonchangeset.ChangesetApplication{ + e, err := commonchangeset.ApplyChangesets(t, e, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.DeployCapabilityRegistry), Config: registryChainSel, @@ -265,14 +265,13 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { for sel := range env.Chains { t.Logf("Enabling MCMS on chain %d", sel) timelockCfgs[sel] = commontypes.MCMSWithTimelockConfig{ - Canceller: commonchangeset.SingleGroupMCMS(t), - Bypasser: commonchangeset.SingleGroupMCMS(t), - Proposer: commonchangeset.SingleGroupMCMS(t), - TimelockExecutors: env.AllDeployerKeys(), - TimelockMinDelay: big.NewInt(0), + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockMinDelay: big.NewInt(0), } } - env, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, nil, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: timelockCfgs, @@ -291,7 +290,7 @@ func SetupTestEnv(t *testing.T, c TestConfig) TestEnv { require.NoError(t, mcms.Validate()) // transfer ownership of all contracts to the MCMS - env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, []commonchangeset.ChangesetApplication{ + env, err = commonchangeset.ApplyChangesets(t, env, map[uint64]*gethwrappers.RBACTimelock{sel: mcms.Timelock}, map[uint64]*gethwrappers.CallProxy{sel: mcms.CallProxy}, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(kschangeset.AcceptAllOwnershipsProposal), Config: &kschangeset.AcceptAllOwnershipRequest{