diff --git a/chains/evm/deployment/fastcurse_test.go b/chains/evm/deployment/fastcurse_test.go index f003527aad..83ae44310f 100644 --- a/chains/evm/deployment/fastcurse_test.go +++ b/chains/evm/deployment/fastcurse_test.go @@ -13,10 +13,11 @@ import ( "github.com/smartcontractkit/chainlink-evm/pkg/utils" mcms_types "github.com/smartcontractkit/mcms/types" + "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/rmn_remote" + "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/adapters" rmnproxyops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/rmn_proxy" adaptersv1_5_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_5_0/adapters" - "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/rmn_remote" "github.com/smartcontractkit/chainlink-ccip/deployment/deploy" "github.com/smartcontractkit/chainlink-ccip/deployment/fastcurse" "github.com/smartcontractkit/chainlink-ccip/deployment/testhelpers" @@ -28,6 +29,7 @@ import ( "github.com/stretchr/testify/require" "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_5_0/rmn_contract" + deploymentutils "github.com/smartcontractkit/chainlink-ccip/deployment/utils" "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/utils/operations/contract" @@ -160,16 +162,14 @@ func TestFastCurse(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, chain2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, }, }) @@ -216,7 +216,7 @@ func TestFastCurse(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership to timelock for fast curse test", }, } @@ -251,7 +251,7 @@ func TestFastCurse(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Curse proposal for fast curse test", }, } @@ -440,14 +440,12 @@ func TestFastCurseGlobalCurseOnChain(t *testing.T) { cs := deploy.DeployMCMS(dReg, nil) mcmsChainInput := make(map[uint64]deploy.MCMSDeploymentConfigPerChain) for _, sel := range []uint64{chain1, chain2, chain3} { - evmChain := env.BlockChains.EVMChains()[sel] mcmsChainInput[sel] = deploy.MCMSDeploymentConfigPerChain{ Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), } } output, err := cs.Apply(*env, deploy.MCMSDeploymentConfig{ @@ -507,7 +505,7 @@ func TestFastCurseGlobalCurseOnChain(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership to timelock for fast curse test", }, } @@ -536,7 +534,7 @@ func TestFastCurseGlobalCurseOnChain(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Curse proposal for fast curse test", }, } @@ -652,7 +650,7 @@ func TestFastCurseGlobalCurseOnChain(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Curse proposal for fast curse test", }, } diff --git a/chains/evm/deployment/v1_0_0/adapters/deployer.go b/chains/evm/deployment/v1_0_0/adapters/deployer.go index 5974543267..0c3936f9af 100644 --- a/chains/evm/deployment/v1_0_0/adapters/deployer.go +++ b/chains/evm/deployment/v1_0_0/adapters/deployer.go @@ -8,13 +8,16 @@ import ( cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain" "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract" + cldf_datastore "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations" ccipapi "github.com/smartcontractkit/chainlink-ccip/deployment/deploy" "github.com/smartcontractkit/chainlink-ccip/deployment/utils" + datastore_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils/datastore" sequtil "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences" + evm_datastore_utils "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/utils/datastore" ops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations" seq "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/sequences" ) @@ -150,6 +153,7 @@ func (d *EVMDeployer) DeployMCMS() *cldf_ops.Sequence[ccipapi.MCMSDeploymentConf } output.Addresses = append(output.Addresses, report.Output.Addresses...) cancellerAddr := report.Output.Addresses[0] + // deploy timelock — always use the deployer key as the initial admin // so we can manage roles immediately after deployment timelockAddr, err := contract.MaybeDeployContract(b, ops.OpDeployTimelock, evmChain, contract.DeployInput[ops.OpDeployTimelockInput]{ @@ -200,11 +204,36 @@ func (d *EVMDeployer) DeployMCMS() *cldf_ops.Sequence[ccipapi.MCMSDeploymentConf b.Logger.Infof("Granted Executor role on Timelock %s to Call Proxy %s on chain %s", timelockAddr, callProxyAddr, evmChain.Name) // Determine the final ADMIN_ROLE holder for the timelock. - // If in.TimelockAdmin is zero, the timelock becomes its own admin. - // Otherwise, hand admin to the specified address. - finalAdmin := common.HexToAddress(timelockAddr.Address) - if in.TimelockAdmin != (common.Address{}) { - finalAdmin = in.TimelockAdmin + // CLLCCIP is always self-governed (its timelock is its own admin). + // Every other MCMS instance MUST use the existing CLLCCIP RBACTimelock as its + // admin — if CLLCCIP has not been deployed yet, fail fast. + isCLLCCIP := in.Qualifier != nil && *in.Qualifier == utils.CLLQualifier + finalAdmin := common.HexToAddress(timelockAddr.Address) // default: self-governed (CLLCCIP case) + if !isCLLCCIP { + existingDS := cldf_datastore.NewMemoryDataStore() + for _, ref := range in.ExistingAddresses { + _ = existingDS.Addresses().Add(ref) + } + cllTimelockAddr, lookupErr := datastore_utils.FindAndFormatRef( + existingDS.Seal(), + cldf_datastore.AddressRef{ + Type: cldf_datastore.ContractType(utils.RBACTimelock), + Qualifier: utils.CLLQualifier, + }, + in.ChainSelector, + evm_datastore_utils.ToEVMAddress, + ) + if lookupErr != nil || cllTimelockAddr == (common.Address{}) { + qualifier := "" + if in.Qualifier != nil { + qualifier = *in.Qualifier + } + return sequtil.OnChainOutput{}, fmt.Errorf( + "cannot deploy MCMS with qualifier %q on chain %d: CLLCCIP RBACTimelock must be deployed first (it will be set as admin)", + qualifier, in.ChainSelector, + ) + } + finalAdmin = cllTimelockAddr } // If finalAdmin differs from the deployer key we need to transfer the ADMIN_ROLE. diff --git a/chains/evm/deployment/v1_0_0/adapters/deployer_test.go b/chains/evm/deployment/v1_0_0/adapters/deployer_test.go index ab939b7552..519372b39f 100644 --- a/chains/evm/deployment/v1_0_0/adapters/deployer_test.go +++ b/chains/evm/deployment/v1_0_0/adapters/deployer_test.go @@ -36,12 +36,12 @@ func TestDeployMCMS(t *testing.T) { require.NoError(t, err) env.Logger = logger.Test(t) evmChain1 := env.BlockChains.EVMChains()[selector1] - evmChain2 := env.BlockChains.EVMChains()[selector2] evmDeployer := &adapters.EVMDeployer{} dReg := deployops.GetRegistry() dReg.RegisterDeployer(chainsel.FamilyEVM, deployops.MCMSVersion, evmDeployer) cs := deployops.DeployMCMS(dReg, nil) + output, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ AdapterVersion: deployops.MCMSVersion, Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ @@ -50,19 +50,18 @@ func TestDeployMCMS(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(utils.CLLQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(utils.CLLQualifier), }, }, }) + require.NoError(t, err) require.Greater(t, len(output.Reports), 0) env.DataStore = output.DataStore.Seal() @@ -70,7 +69,7 @@ func TestDeployMCMS(t *testing.T) { proposerRef := env.DataStore.Addresses().Filter( datastore.AddressRefByChainSelector(selector1), datastore.AddressRefByType(datastore.ContractType(utils.ProposerManyChainMultisig)), - datastore.AddressRefByQualifier("test"), + datastore.AddressRefByQualifier(utils.CLLQualifier), ) require.Len(t, proposerRef, 1) require.NotEqual(t, common.Address{}, proposerRef[0].Address) @@ -78,7 +77,7 @@ func TestDeployMCMS(t *testing.T) { bypasserRef := env.DataStore.Addresses().Filter( datastore.AddressRefByChainSelector(selector1), datastore.AddressRefByType(datastore.ContractType(utils.BypasserManyChainMultisig)), - datastore.AddressRefByQualifier("test"), + datastore.AddressRefByQualifier(utils.CLLQualifier), ) require.Len(t, bypasserRef, 1) require.NotEqual(t, common.Address{}, bypasserRef[0].Address) @@ -86,7 +85,7 @@ func TestDeployMCMS(t *testing.T) { cancellerRef := env.DataStore.Addresses().Filter( datastore.AddressRefByChainSelector(selector1), datastore.AddressRefByType(datastore.ContractType(utils.CancellerManyChainMultisig)), - datastore.AddressRefByQualifier("test"), + datastore.AddressRefByQualifier(utils.CLLQualifier), ) require.Len(t, cancellerRef, 1) require.NotEqual(t, common.Address{}, cancellerRef[0].Address) @@ -94,7 +93,7 @@ func TestDeployMCMS(t *testing.T) { timelockRef := env.DataStore.Addresses().Filter( datastore.AddressRefByChainSelector(selector1), datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), - datastore.AddressRefByQualifier("test"), + datastore.AddressRefByQualifier(utils.CLLQualifier), ) require.Len(t, timelockRef, 1) require.NotEqual(t, common.Address{}, timelockRef[0].Address) @@ -102,7 +101,7 @@ func TestDeployMCMS(t *testing.T) { callProxyRef := env.DataStore.Addresses().Filter( datastore.AddressRefByChainSelector(selector1), datastore.AddressRefByType(datastore.ContractType(utils.CallProxy)), - datastore.AddressRefByQualifier("test"), + datastore.AddressRefByQualifier(utils.CLLQualifier), ) require.Len(t, callProxyRef, 1) require.NotEqual(t, common.Address{}, callProxyRef[0].Address) @@ -125,17 +124,18 @@ func TestDeployMCMS(t *testing.T) { require.NoError(t, err) require.True(t, hasRole, "Call Proxy should have admin role for EXECUTOR_ROLE") - // When TimelockAdmin == deployer key, deployer should retain ADMIN_ROLE (no transfer) + // CLLCCIP timelock is self-governed. adminRoleAdmin, err := timelockC.GetRoleAdmin(&bind.CallOpts{Context: t.Context()}, ops.ADMIN_ROLE.ID) require.NoError(t, err) - deployerIsAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, evmChain1.DeployerKey.From) + timelockAddr := common.HexToAddress(timelockRef[0].Address) + selfAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, timelockAddr) require.NoError(t, err) - require.True(t, deployerIsAdmin, "deployer should retain ADMIN_ROLE when TimelockAdmin == deployer key") + require.True(t, selfAdmin, "CLLCCIP timelock should be self-governed") } -// TestDeployMCMS_TimelockAdminRoleTransfer verifies the admin-role handover logic inside DeployMCMS: -// - zero TimelockAdmin → timelock itself holds ADMIN_ROLE; deployer has renounced it -// - external address → that address holds ADMIN_ROLE; deployer has renounced it +// TestDeployMCMS_TimelockAdminRoleTransfer verifies the admin-role logic inside DeployMCMS: +// - non-CLLCCIP qualifier with no CLLCCIP deployed → changeset fails with an error +// - CLLCCIP qualifier (bootstrap) → timelock itself holds ADMIN_ROLE (self-sovereign) func TestDeployMCMS_TimelockAdminRoleTransfer(t *testing.T) { t.Parallel() selector := chainsel.TEST_90000001.Selector @@ -146,81 +146,154 @@ func TestDeployMCMS_TimelockAdminRoleTransfer(t *testing.T) { env.Logger = logger.Test(t) evmChain := env.BlockChains.EVMChains()[selector] - externalAdmin := common.HexToAddress("0x1111111111111111111111111111111111111111") - evmDeployer := &adapters.EVMDeployer{} dReg := deployops.GetRegistry() dReg.RegisterDeployer(chainsel.FamilyEVM, deployops.MCMSVersion, evmDeployer) cs := deployops.DeployMCMS(dReg, nil) - tests := []struct { - name string - qualifier string - timelockAdmin common.Address // zero means self-sovereign - expectAdmin func(timelockAddr common.Address) common.Address - deployerIsAdmin bool - }{ - { - name: "zero TimelockAdmin makes timelock self-sovereign", - qualifier: "zero-admin", - timelockAdmin: common.Address{}, - expectAdmin: func(timelockAddr common.Address) common.Address { return timelockAddr }, - deployerIsAdmin: false, - }, - { - name: "external TimelockAdmin receives ADMIN_ROLE", - qualifier: "external-admin", - timelockAdmin: externalAdmin, - expectAdmin: func(_ common.Address) common.Address { return externalAdmin }, - deployerIsAdmin: false, - }, - } + t.Run("non-CLLCCIP with no CLLCCIP deployed fails", func(t *testing.T) { + _, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ + AdapterVersion: deployops.MCMSVersion, + Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ + selector: { + Canceller: testhelpers.SingleGroupMCMS(), + Bypasser: testhelpers.SingleGroupMCMS(), + Proposer: testhelpers.SingleGroupMCMS(), + TimelockMinDelay: big.NewInt(0), + Qualifier: ptr.String("no-cllccip"), + }, + }, + }) + require.Error(t, err) + require.ErrorContains(t, err, "CLLCCIP RBACTimelock must be deployed first") + }) - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - output, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ - AdapterVersion: deployops.MCMSVersion, - Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ - selector: { - Canceller: testhelpers.SingleGroupMCMS(), - Bypasser: testhelpers.SingleGroupMCMS(), - Proposer: testhelpers.SingleGroupMCMS(), - TimelockMinDelay: big.NewInt(0), - Qualifier: new(tc.qualifier), - TimelockAdmin: tc.timelockAdmin, - }, + t.Run("CLLCCIP bootstrap is self-governed", func(t *testing.T) { + output, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ + AdapterVersion: deployops.MCMSVersion, + Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ + selector: { + Canceller: testhelpers.SingleGroupMCMS(), + Bypasser: testhelpers.SingleGroupMCMS(), + Proposer: testhelpers.SingleGroupMCMS(), + TimelockMinDelay: big.NewInt(0), + Qualifier: ptr.String(utils.CLLQualifier), }, - }) - require.NoError(t, err) - env.DataStore = output.DataStore.Seal() + }, + }) + require.NoError(t, err) + env.DataStore = output.DataStore.Seal() - timelockRef := env.DataStore.Addresses().Filter( - datastore.AddressRefByChainSelector(selector), - datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), - datastore.AddressRefByQualifier(tc.qualifier), - ) - require.Len(t, timelockRef, 1) - timelockAddr := common.HexToAddress(timelockRef[0].Address) + timelockRef := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(selector), + datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), + datastore.AddressRefByQualifier(utils.CLLQualifier), + ) + require.Len(t, timelockRef, 1) + timelockAddr := common.HexToAddress(timelockRef[0].Address) - timelockC, err := bindings.NewRBACTimelock(timelockAddr, evmChain.Client) - require.NoError(t, err) + timelockC, err := bindings.NewRBACTimelock(timelockAddr, evmChain.Client) + require.NoError(t, err) - adminRoleAdmin, err := timelockC.GetRoleAdmin(&bind.CallOpts{Context: t.Context()}, ops.ADMIN_ROLE.ID) - require.NoError(t, err) + adminRoleAdmin, err := timelockC.GetRoleAdmin(&bind.CallOpts{Context: t.Context()}, ops.ADMIN_ROLE.ID) + require.NoError(t, err) - expectedAdmin := tc.expectAdmin(timelockAddr) - expectedIsAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, expectedAdmin) - require.NoError(t, err) - require.True(t, expectedIsAdmin, "expected %s to hold ADMIN_ROLE", expectedAdmin) + timelockIsSelfAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, timelockAddr) + require.NoError(t, err) + require.True(t, timelockIsSelfAdmin, "CLLCCIP timelock should be self-governed") - deployerStillAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, evmChain.DeployerKey.From) - require.NoError(t, err) - require.Equal(t, tc.deployerIsAdmin, deployerStillAdmin, - "deployer ADMIN_ROLE mismatch: expected deployerIsAdmin=%v", tc.deployerIsAdmin) - }) - } + deployerStillAdmin, err := timelockC.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, evmChain.DeployerKey.From) + require.NoError(t, err) + require.False(t, deployerStillAdmin, "deployer should never hold ADMIN_ROLE after deployment") + }) } +// TestDeployMCMS_DefaultsToExistingCLLCCIPTimelock verifies that when a new non-CLLCCIP +// MCMS is deployed, the deployer automatically uses the +// existing CLLCCIP RBACTimelock as the admin (so it is governed by CLL from the start). +func TestDeployMCMS_DefaultsToExistingCLLCCIPTimelock(t *testing.T) { + t.Parallel() + selector := chainsel.TEST_90000001.Selector + env, err := environment.New(t.Context(), + environment.WithEVMSimulated(t, []uint64{selector}), + ) + require.NoError(t, err) + env.Logger = logger.Test(t) + evmChain := env.BlockChains.EVMChains()[selector] + + evmDeployer := &adapters.EVMDeployer{} + dReg := deployops.GetRegistry() + dReg.RegisterDeployer(chainsel.FamilyEVM, deployops.MCMSVersion, evmDeployer) + cs := deployops.DeployMCMS(dReg, nil) + + // Step 1: deploy the CLLCCIP MCMS instance + cllOutput, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ + AdapterVersion: deployops.MCMSVersion, + Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ + selector: { + Canceller: testhelpers.SingleGroupMCMS(), + Bypasser: testhelpers.SingleGroupMCMS(), + Proposer: testhelpers.SingleGroupMCMS(), + TimelockMinDelay: big.NewInt(0), + Qualifier: new(utils.CLLQualifier), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, cllOutput.DataStore.Merge(env.DataStore)) + env.DataStore = cllOutput.DataStore.Seal() + + // Resolve the CLLCCIP timelock address for later assertion. + cllTimelockRefs := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(selector), + datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), + datastore.AddressRefByQualifier(utils.CLLQualifier), + ) + require.Len(t, cllTimelockRefs, 1) + cllTimelockAddr := common.HexToAddress(cllTimelockRefs[0].Address) + + // Step 2: deploy a second (non-CLLCCIP) MCMS instance. + // The deployer should automatically pick the CLLCCIP timelock as the admin. + rmnOutput, err := cs.Apply(*env, deployops.MCMSDeploymentConfig{ + AdapterVersion: deployops.MCMSVersion, + Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ + selector: { + Canceller: testhelpers.SingleGroupMCMS(), + Bypasser: testhelpers.SingleGroupMCMS(), + Proposer: testhelpers.SingleGroupMCMS(), + TimelockMinDelay: big.NewInt(0), + Qualifier: new(utils.RMNTimelockQualifier), + }, + }, + }) + require.NoError(t, err) + require.NoError(t, rmnOutput.DataStore.Merge(env.DataStore)) + env.DataStore = rmnOutput.DataStore.Seal() + + rmnTimelockRefs := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(selector), + datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), + datastore.AddressRefByQualifier(utils.RMNTimelockQualifier), + ) + require.Len(t, rmnTimelockRefs, 1) + rmnTimelockAddr := common.HexToAddress(rmnTimelockRefs[0].Address) + + rmnTimelock, err := bindings.NewRBACTimelock(rmnTimelockAddr, evmChain.Client) + require.NoError(t, err) + + adminRoleAdmin, err := rmnTimelock.GetRoleAdmin(&bind.CallOpts{Context: t.Context()}, ops.ADMIN_ROLE.ID) + require.NoError(t, err) + + // The CLLCCIP timelock should be the admin of the new RMNMCMS timelock. + cllIsAdmin, err := rmnTimelock.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, cllTimelockAddr) + require.NoError(t, err) + require.True(t, cllIsAdmin, "CLLCCIP timelock should be admin of the RMNMCMS timelock") + + // The deployer EOA must have renounced ADMIN_ROLE. + deployerIsAdmin, err := rmnTimelock.HasRole(&bind.CallOpts{Context: t.Context()}, adminRoleAdmin, evmChain.DeployerKey.From) + require.NoError(t, err) + require.False(t, deployerIsAdmin, "deployer should have renounced ADMIN_ROLE after transfer to CLLCCIP timelock") +} func TestUpdateMCMSConfig(t *testing.T) { t.Parallel() @@ -231,8 +304,6 @@ func TestUpdateMCMSConfig(t *testing.T) { ) require.NoError(t, err) env.Logger = logger.Test(t) - evmChain1 := env.BlockChains.EVMChains()[selector1] - evmChain2 := env.BlockChains.EVMChains()[selector2] evmDeployer := &adapters.EVMDeployer{} dReg := deployops.GetRegistry() @@ -248,16 +319,14 @@ func TestUpdateMCMSConfig(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("CLLCCIP"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: new("CLLCCIP"), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("CLLCCIP"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: new("CLLCCIP"), }, }, }) @@ -361,7 +430,10 @@ func TestUpdateMCMSConfig(t *testing.T) { } } -func TestGrantAdminRoleToTimelock(t *testing.T) { +// TestDeployMCMS_CLLCCIPAutoAdminMultiChain verifies that on multiple chains, when the CLLCCIP +// instance is deployed first and a second MCMS set is deployed afterwards, DeployMCMS +// automatically sets the CLLCCIP timelock as the admin of the new timelock. +func TestDeployMCMS_CLLCCIPAutoAdminMultiChain(t *testing.T) { t.Parallel() selector1 := chainsel.TEST_90000001.Selector selector2 := chainsel.TEST_90000002.Selector @@ -370,16 +442,15 @@ func TestGrantAdminRoleToTimelock(t *testing.T) { ) require.NoError(t, err) env.Logger = logger.Test(t) - evmChain1 := env.BlockChains.EVMChains()[selector1] - evmChain2 := env.BlockChains.EVMChains()[selector2] evmDeployer := &adapters.EVMDeployer{} dReg := deployops.GetRegistry() dReg.RegisterDeployer(chainsel.FamilyEVM, deployops.MCMSVersion, evmDeployer) - // deploy two timelocks on each chain so we can set one as the admin of the other deployMCMS := deployops.DeployMCMS(dReg, nil) - output, err := deployMCMS.Apply(*env, deployops.MCMSDeploymentConfig{ + + // Step 1: deploy CLLCCIP on both chains (bootstrap — each self-governed). + cllOutput, err := deployMCMS.Apply(*env, deployops.MCMSDeploymentConfig{ AdapterVersion: deployops.MCMSVersion, Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ selector1: { @@ -387,24 +458,37 @@ func TestGrantAdminRoleToTimelock(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("testQual"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: new(utils.CLLQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("testQual"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: new(utils.CLLQualifier), }, }, }) require.NoError(t, err) - require.Greater(t, len(output.Reports), 0) - ds := output.DataStore + require.Greater(t, len(cllOutput.Reports), 0) + require.NoError(t, cllOutput.DataStore.Merge(env.DataStore)) + env.DataStore = cllOutput.DataStore.Seal() + + // Collect CLLCCIP timelock addresses for assertion. + cllTimelockAddrs := make(map[uint64]common.Address) + for _, sel := range []uint64{selector1, selector2} { + refs := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(sel), + datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), + datastore.AddressRefByQualifier(utils.CLLQualifier), + ) + require.Len(t, refs, 1) + cllTimelockAddrs[sel] = common.HexToAddress(refs[0].Address) + } - output, err = deployMCMS.Apply(*env, deployops.MCMSDeploymentConfig{ + // Step 2: deploy a second MCMS set (testQual) with CLLCCIP already in datastore. + // DeployMCMS should automatically use CLLCCIP timelock as admin. + testOutput, err := deployMCMS.Apply(*env, deployops.MCMSDeploymentConfig{ AdapterVersion: deployops.MCMSVersion, Chains: map[uint64]deployops.MCMSDeploymentConfigPerChain{ selector1: { @@ -412,133 +496,47 @@ func TestGrantAdminRoleToTimelock(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("CLLCCIP"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: new("testQual"), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("CLLCCIP"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: new("testQual"), }, }, }) require.NoError(t, err) - require.Greater(t, len(output.Reports), 0) - addresses, err := output.DataStore.Addresses().Fetch() - require.NoError(t, err) - for _, addr := range addresses { - t.Logf("Adding address %s of type %s on chain %d to datastore", addr.Address, addr.Type, addr.ChainSelector) - require.NoError(t, ds.Addresses().Add(addr)) - } - env.DataStore = ds.Seal() + require.Greater(t, len(testOutput.Reports), 0) + require.NoError(t, testOutput.DataStore.Merge(env.DataStore)) + env.DataStore = testOutput.DataStore.Seal() - // get recently deployed timelock addresses - timelockAddrs := make(map[uint64]string) - newAdminTimelockAddrs := make(map[uint64]string) - for _, sel := range []uint64{selector1, selector2} { - timelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ - ChainSelector: sel, - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "testQual", - Version: semver.MustParse("1.0.0"), - }, sel, datastore_utils.FullRef) - require.NoError(t, err) - timelockAddrs[sel] = timelockRef.Address - newAdminTimelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ - ChainSelector: sel, - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "CLLCCIP", - Version: semver.MustParse("1.0.0"), - }, sel, datastore_utils.FullRef) - require.NoError(t, err) - newAdminTimelockAddrs[sel] = newAdminTimelockRef.Address - } - - // check that admin of timelock is deployer eoa for _, sel := range []uint64{selector1, selector2} { evmChain := env.BlockChains.EVMChains()[sel] - timelock, err := bindings.NewRBACTimelock(common.HexToAddress(timelockAddrs[sel]), evmChain.Client) - require.NoError(t, err) - roleAdmin, err := timelock.GetRoleAdmin(&bind.CallOpts{ - Context: t.Context(), - }, ops.ADMIN_ROLE.ID) - require.NoError(t, err) - callerIsAdmin, err := timelock.HasRole(&bind.CallOpts{ - Context: t.Context(), - }, roleAdmin, evmChain.DeployerKey.From) - require.NoError(t, err) - require.True(t, callerIsAdmin) - } - - // grant admin role to timelock - grantAdminRoleMCMS := deployops.GrantAdminRoleToTimelock(dReg, nil) - output, err = grantAdminRoleMCMS.Apply(*env, deployops.GrantAdminRoleToTimelockConfig{ - AdapterVersion: semver.MustParse("1.0.0"), - Chains: map[uint64]deployops.GrantAdminRoleToTimelockConfigPerChain{ - selector1: { - TimelockToTransferRef: datastore.AddressRef{ - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Version: semver.MustParse("1.0.0"), - Qualifier: "testQual", - }, - NewAdminTimelockRef: datastore.AddressRef{ - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Version: semver.MustParse("1.0.0"), - Qualifier: "CLLCCIP", - }, - }, - selector2: { - TimelockToTransferRef: datastore.AddressRef{ - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Version: semver.MustParse("1.0.0"), - Qualifier: "testQual", - }, - NewAdminTimelockRef: datastore.AddressRef{ - Type: datastore.ContractType(deploymentutils.RBACTimelock), - Version: semver.MustParse("1.0.0"), - Qualifier: "CLLCCIP", - }, - }, - }, - }) + testQualRefs := env.DataStore.Addresses().Filter( + datastore.AddressRefByChainSelector(sel), + datastore.AddressRefByType(datastore.ContractType(utils.RBACTimelock)), + datastore.AddressRefByQualifier("testQual"), + ) + require.Len(t, testQualRefs, 1) + testQualTimelockAddr := common.HexToAddress(testQualRefs[0].Address) - // check that "CLLCCIP" timelocks have admin role - for _, sel := range []uint64{selector1, selector2} { - evmChain := env.BlockChains.EVMChains()[sel] - timelock, err := bindings.NewRBACTimelock(common.HexToAddress(timelockAddrs[sel]), evmChain.Client) - require.NoError(t, err) - roleAdmin, err := timelock.GetRoleAdmin(&bind.CallOpts{ - Context: t.Context(), - }, ops.ADMIN_ROLE.ID) + timelock, err := bindings.NewRBACTimelock(testQualTimelockAddr, evmChain.Client) require.NoError(t, err) - - timelockIsAdmin, err := timelock.HasRole(&bind.CallOpts{ - Context: t.Context(), - }, roleAdmin, common.HexToAddress(newAdminTimelockAddrs[sel])) + roleAdmin, err := timelock.GetRoleAdmin(&bind.CallOpts{Context: t.Context()}, ops.ADMIN_ROLE.ID) require.NoError(t, err) - require.True(t, timelockIsAdmin, "timelock does not have admin role on chain %d", sel) - t.Logf("Timelock with address %s on chain %d was successfully granted admin role of timelock with address %s", newAdminTimelockAddrs[sel], sel, timelockAddrs[sel]) - } - // check that deployer eoa no longer has admin role - for _, sel := range []uint64{selector1, selector2} { - evmChain := env.BlockChains.EVMChains()[sel] - timelock, err := bindings.NewRBACTimelock(common.HexToAddress(timelockAddrs[sel]), evmChain.Client) - require.NoError(t, err) - roleAdmin, err := timelock.GetRoleAdmin(&bind.CallOpts{ - Context: t.Context(), - }, ops.ADMIN_ROLE.ID) + // CLLCCIP timelock must be admin of testQual timelock. + cllIsAdmin, err := timelock.HasRole(&bind.CallOpts{Context: t.Context()}, roleAdmin, cllTimelockAddrs[sel]) require.NoError(t, err) + require.True(t, cllIsAdmin, "CLLCCIP timelock should be admin of testQual timelock on chain %d", sel) + t.Logf("CLLCCIP timelock %s is admin of testQual timelock %s on chain %d", cllTimelockAddrs[sel], testQualTimelockAddr, sel) - timelockIsAdmin, err := timelock.HasRole(&bind.CallOpts{ - Context: t.Context(), - }, roleAdmin, evmChain.DeployerKey.From) + // Deployer must NOT be admin. + deployerIsAdmin, err := timelock.HasRole(&bind.CallOpts{Context: t.Context()}, roleAdmin, evmChain.DeployerKey.From) require.NoError(t, err) - require.False(t, timelockIsAdmin, "deployer eoa still has admin role on chain %d", sel) - t.Logf("The deployer EOA on chain %d has successfully renounced admin role of timelock with address %s", sel, timelockAddrs[sel]) + require.False(t, deployerIsAdmin, "deployer should not hold ADMIN_ROLE on chain %d", sel) } } diff --git a/chains/evm/deployment/v1_0_0/adapters/transfer_ownership_test.go b/chains/evm/deployment/v1_0_0/adapters/transfer_ownership_test.go index bd976bdd22..9b4b59d5cb 100644 --- a/chains/evm/deployment/v1_0_0/adapters/transfer_ownership_test.go +++ b/chains/evm/deployment/v1_0_0/adapters/transfer_ownership_test.go @@ -18,10 +18,10 @@ import ( mcms_types "github.com/smartcontractkit/mcms/types" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/adapters" "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_2_0/router" "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/utils/operations/contract" + "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/adapters" routerops1_2 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_2_0/operations/router" "github.com/smartcontractkit/chainlink-ccip/deployment/deploy" "github.com/smartcontractkit/chainlink-ccip/deployment/testhelpers" @@ -43,13 +43,13 @@ func TestTransferOwnership(t *testing.T) { ) require.NoError(t, err) env.Logger = logger.Test(t) - evmChain1 := env.BlockChains.EVMChains()[selector1] - evmChain2 := env.BlockChains.EVMChains()[selector2] evmDeployer := &adapters.EVMDeployer{} dReg := deploy.GetRegistry() dReg.RegisterDeployer(chainsel.FamilyEVM, deploy.MCMSVersion, evmDeployer) deployMCMS := deploy.DeployMCMS(dReg, nil) + + // Deploy CLLCCIP (bootstrap). output, err := deployMCMS.Apply(*env, deploy.MCMSDeploymentConfig{ AdapterVersion: semver.MustParse("1.0.0"), Chains: map[uint64]deploy.MCMSDeploymentConfigPerChain{ @@ -58,24 +58,24 @@ func TestTransferOwnership(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, }, }) require.NoError(t, err) require.Greater(t, len(output.Reports), 0) ds := output.DataStore + require.NoError(t, ds.Merge(env.DataStore)) + env.DataStore = ds.Seal() - // deploy another timelock so that later we can transfer ownership to it from first timelock + // Deploy a second MCMS set (RMNMCMS) so we can transfer ownership to it later. output, err = deployMCMS.Apply(*env, deploy.MCMSDeploymentConfig{ AdapterVersion: semver.MustParse("1.0.0"), Chains: map[uint64]deploy.MCMSDeploymentConfigPerChain{ @@ -84,16 +84,14 @@ func TestTransferOwnership(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test1"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.RMNTimelockQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test1"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.RMNTimelockQualifier), }, }, }) @@ -137,7 +135,7 @@ func TestTransferOwnership(t *testing.T) { timelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ ChainSelector: sel, Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Version: semver.MustParse("1.0.0"), }, sel, datastore_utils.FullRef) require.NoError(t, err) @@ -145,7 +143,7 @@ func TestTransferOwnership(t *testing.T) { newTimelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ ChainSelector: sel, Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "test1", + Qualifier: deploymentutils.RMNTimelockQualifier, Version: semver.MustParse("1.0.0"), }, sel, datastore_utils.FullRef) require.NoError(t, err) @@ -180,7 +178,7 @@ func TestTransferOwnership(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership test", }, } @@ -211,8 +209,7 @@ func TestTransferOwnership(t *testing.T) { t.Logf("Ownership of router on chain %d successfully transferred to timelock %s", sel, timelockAddrs[sel]) } - // now transfer ownership from first timelock to second timelock - // the mcms input should denote the first timelock address + // now transfer ownership from CLLCCIP timelock to RMNMCMS timelock transferOwnershipInput = deploy.TransferOwnershipInput{ ChainInputs: []deploy.TransferOwnershipPerChainInput{ { @@ -242,7 +239,7 @@ func TestTransferOwnership(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership test", }, } @@ -254,13 +251,13 @@ func TestTransferOwnership(t *testing.T) { testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false) - // now accept ownership from the new timelock, the mcms input should denote new timelock address + // now accept ownership from the RMNMCMS timelock transferOwnershipInput.MCMS = mcms.Input{ OverridePreviousRoot: false, ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test1", + Qualifier: deploymentutils.RMNTimelockQualifier, Description: "Transfer ownership test", } acceptOwnershipChangeset := deploy.AcceptOwnershipChangeset(cr, mcmsRegistry) diff --git a/chains/evm/deployment/v1_0_0/transfer_ownership_test.go b/chains/evm/deployment/v1_0_0/transfer_ownership_test.go index 9632ebf9a8..444e27ea67 100644 --- a/chains/evm/deployment/v1_0_0/transfer_ownership_test.go +++ b/chains/evm/deployment/v1_0_0/transfer_ownership_test.go @@ -42,13 +42,13 @@ func TestTransferOwnership(t *testing.T) { ) require.NoError(t, err) env.Logger = logger.Test(t) - evmChain1 := env.BlockChains.EVMChains()[selector1] - evmChain2 := env.BlockChains.EVMChains()[selector2] evmDeployer := &adapters.EVMDeployer{} dReg := deploy.GetRegistry() dReg.RegisterDeployer(chainsel.FamilyEVM, deploy.MCMSVersion, evmDeployer) deployMCMS := deploy.DeployMCMS(dReg, nil) + + // Deploy CLLCCIP (bootstrap). output, err := deployMCMS.Apply(*env, deploy.MCMSDeploymentConfig{ AdapterVersion: deploy.MCMSVersion, Chains: map[uint64]deploy.MCMSDeploymentConfigPerChain{ @@ -57,24 +57,24 @@ func TestTransferOwnership(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.CLLQualifier), }, }, }) require.NoError(t, err) require.Greater(t, len(output.Reports), 0) ds := output.DataStore + require.NoError(t, ds.Merge(env.DataStore)) + env.DataStore = ds.Seal() - // deploy another timelock so that later we can transfer ownership to it from first timelock + // Deploy a second MCMS set (RMNMCMS) so we can transfer ownership to it later. output, err = deployMCMS.Apply(*env, deploy.MCMSDeploymentConfig{ AdapterVersion: deploy.MCMSVersion, Chains: map[uint64]deploy.MCMSDeploymentConfigPerChain{ @@ -83,16 +83,14 @@ func TestTransferOwnership(t *testing.T) { Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test1"), - TimelockAdmin: evmChain1.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.RMNTimelockQualifier), }, selector2: { Canceller: testhelpers.SingleGroupMCMS(), Bypasser: testhelpers.SingleGroupMCMS(), Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), - Qualifier: ptr.String("test1"), - TimelockAdmin: evmChain2.DeployerKey.From, + Qualifier: ptr.String(deploymentutils.RMNTimelockQualifier), }, }, }) @@ -135,14 +133,14 @@ func TestTransferOwnership(t *testing.T) { for _, sel := range []uint64{selector1, selector2} { timelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Version: semver.MustParse("1.0.0"), }, sel, datastore_utils.FullRef) require.NoError(t, err) timelockAddrs[sel] = timelockRef.Address newTimelockRef, err := datastore_utils.FindAndFormatRef(env.DataStore, datastore.AddressRef{ Type: datastore.ContractType(deploymentutils.RBACTimelock), - Qualifier: "test1", + Qualifier: deploymentutils.RMNTimelockQualifier, Version: semver.MustParse("1.0.0"), }, sel, datastore_utils.FullRef) require.NoError(t, err) @@ -177,7 +175,7 @@ func TestTransferOwnership(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership test", }, } @@ -208,8 +206,7 @@ func TestTransferOwnership(t *testing.T) { t.Logf("Ownership of router on chain %d successfully transferred to timelock %s", sel, timelockAddrs[sel]) } - // now transfer ownership from first timelock to second timelock - // the mcms input should denote the first timelock address + // now transfer ownership from CLLCCIP timelock to RMNMCMS timelock transferOwnershipInput = deploy.TransferOwnershipInput{ ChainInputs: []deploy.TransferOwnershipPerChainInput{ { @@ -239,7 +236,7 @@ func TestTransferOwnership(t *testing.T) { ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test", + Qualifier: deploymentutils.CLLQualifier, Description: "Transfer ownership test", }, } @@ -251,13 +248,13 @@ func TestTransferOwnership(t *testing.T) { testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false) - // now accept ownership from the new timelock, the mcms input should denote new timelock address + // now accept ownership from the RMNMCMS timelock transferOwnershipInput.MCMS = mcms.Input{ OverridePreviousRoot: false, ValidUntil: 3759765795, TimelockDelay: mcms_types.MustParseDuration("0s"), TimelockAction: mcms_types.TimelockActionSchedule, - Qualifier: "test1", // new qualifier + Qualifier: deploymentutils.RMNTimelockQualifier, Description: "Transfer ownership test", } acceptOwnershipChangeset := deploy.AcceptOwnershipChangeset(cr, mcmsRegistry) diff --git a/deployment/deploy/mcms.go b/deployment/deploy/mcms.go index bf4b92c5f1..d766a5cd32 100644 --- a/deployment/deploy/mcms.go +++ b/deployment/deploy/mcms.go @@ -7,7 +7,6 @@ import ( "slices" "github.com/Masterminds/semver/v3" - "github.com/ethereum/go-ethereum/common" chain_selectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" @@ -37,7 +36,6 @@ type MCMSDeploymentConfigPerChain struct { TimelockMinDelay *big.Int `json:"timelockMinDelay"` Label *string `json:"label"` Qualifier *string `json:"qualifier"` - TimelockAdmin common.Address `json:"timelockAdmin"` ContractVersion string `json:"contractVersion"` } diff --git a/deployment/docs/types.md b/deployment/docs/types.md index 0057ed349c..cdc0fb7cb4 100644 --- a/deployment/docs/types.md +++ b/deployment/docs/types.md @@ -108,7 +108,6 @@ type MCMSDeploymentConfigPerChain struct { TimelockMinDelay *big.Int // Minimum delay for timelock operations Label *string // Optional label for the MCMS instance Qualifier *string // Optional qualifier for the MCMS instance - TimelockAdmin common.Address // Admin address for the timelock ContractVersion string // Contract version string } ``` diff --git a/devenv/common/implcommon.go b/devenv/common/implcommon.go index febf6b04fa..3c76938607 100644 --- a/devenv/common/implcommon.go +++ b/devenv/common/implcommon.go @@ -176,12 +176,6 @@ func DeployContractsForSelector(ctx context.Context, env *deployment.Environment tmp.Merge(out.DataStore.Seal()) runningDS.Merge(out.DataStore.Seal()) - // For EVM only, set the timelock admin - var timelockAdmin common.Address - chain1, ok := env.BlockChains.EVMChains()[selector] - if ok { - timelockAdmin = chain1.DeployerKey.From - } qualifier := "CLLCCIP" cs := deployops.DeployMCMS(dReg, nil) fcs := deployops.FinalizeDeployMCMS(dReg, nil) @@ -194,7 +188,6 @@ func DeployContractsForSelector(ctx context.Context, env *deployment.Environment Proposer: SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(qualifier), - TimelockAdmin: timelockAdmin, }, }, }) @@ -214,7 +207,6 @@ func DeployContractsForSelector(ctx context.Context, env *deployment.Environment Proposer: SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(qualifier), - TimelockAdmin: timelockAdmin, }, }, }) diff --git a/integration-tests/deployment/fastcurse_test.go b/integration-tests/deployment/fastcurse_test.go index f1b26e9112..02f242de4e 100644 --- a/integration-tests/deployment/fastcurse_test.go +++ b/integration-tests/deployment/fastcurse_test.go @@ -210,8 +210,6 @@ func TestFastCurseSolanaAndEVM(t *testing.T) { // deploy mcms cs := deploy.DeployMCMS(dReg, nil) - evmChain1 := env.BlockChains.EVMChains()[chain1] - evmChain2 := env.BlockChains.EVMChains()[chain2] output, err := cs.Apply(*env, deploy.MCMSDeploymentConfig{ AdapterVersion: semver.MustParse("1.0.0"), Chains: map[uint64]deploy.MCMSDeploymentConfigPerChain{ @@ -221,7 +219,6 @@ func TestFastCurseSolanaAndEVM(t *testing.T) { Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(deploymentutils.CLLQualifier), - TimelockAdmin: evmChain1.DeployerKey.From, }, chain2: { Canceller: testhelpers.SingleGroupMCMS(), @@ -229,7 +226,6 @@ func TestFastCurseSolanaAndEVM(t *testing.T) { Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(deploymentutils.CLLQualifier), - TimelockAdmin: evmChain2.DeployerKey.From, }, }, }) @@ -340,6 +336,8 @@ func TestFastCurseSolanaAndEVM(t *testing.T) { testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false) // check that the subjects were actually cursed + evmChain1 := env.BlockChains.EVMChains()[chain1] + evmChain2 := env.BlockChains.EVMChains()[chain2] rmnC, err := rmn_contract.NewRMNContract(rmnAddress, evmChain1.Client) require.NoError(t, err) isCursed, err := rmnC.IsCursed(nil, adv1_5_0.SelectorToSubject(chain2)) diff --git a/integration-tests/deployment/helpers.go b/integration-tests/deployment/helpers.go index e2e2fef13a..7ce7e6e362 100644 --- a/integration-tests/deployment/helpers.go +++ b/integration-tests/deployment/helpers.go @@ -13,11 +13,14 @@ import ( "github.com/aws/smithy-go/ptr" "github.com/ethereum/go-ethereum/common" "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" + evm_contract "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations/contract" "github.com/smartcontractkit/chainlink-deployments-framework/datastore" cldf_deployment "github.com/smartcontractkit/chainlink-deployments-framework/deployment" "github.com/smartcontractkit/chainlink-deployments-framework/operations" - "github.com/stretchr/testify/require" + bnmERC20Bindings "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/burn_mint_erc20" + mcms_types "github.com/smartcontractkit/mcms/types" bnmERC20Operations "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/operations/burn_mint_erc20" evmrouterops "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_2_0/operations/router" @@ -36,22 +39,12 @@ import ( "github.com/smartcontractkit/chainlink-ccip/deployment/testhelpers" tokensapi "github.com/smartcontractkit/chainlink-ccip/deployment/tokens" common_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils" - "github.com/smartcontractkit/chainlink-ccip/deployment/utils/mcms" - bnmERC20Bindings "github.com/smartcontractkit/chainlink-evm/gethwrappers/shared/generated/initial/burn_mint_erc20" - - mcms_types "github.com/smartcontractkit/mcms/types" - mcmsreaderapi "github.com/smartcontractkit/chainlink-ccip/deployment/utils/changesets" datastore_utils "github.com/smartcontractkit/chainlink-ccip/deployment/utils/datastore" + "github.com/smartcontractkit/chainlink-ccip/deployment/utils/mcms" ) func DeployMCMS(t *testing.T, e *cldf_deployment.Environment, selector uint64, qualifiers []string) { - // For EVM only, set the timelock admin - var timelockAdmin common.Address - chain1, ok := e.BlockChains.EVMChains()[selector] - if ok { - timelockAdmin = chain1.DeployerKey.From - } dReg := mcmsapi.GetRegistry() version := semver.MustParse("1.6.0") cs := mcmsapi.DeployMCMS(dReg, nil) @@ -66,7 +59,6 @@ func DeployMCMS(t *testing.T, e *cldf_deployment.Environment, selector uint64, q Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(qualifier), - TimelockAdmin: timelockAdmin, }, }, }) @@ -83,7 +75,6 @@ func DeployMCMS(t *testing.T, e *cldf_deployment.Environment, selector uint64, q Proposer: testhelpers.SingleGroupMCMS(), TimelockMinDelay: big.NewInt(0), Qualifier: ptr.String(qualifier), - TimelockAdmin: timelockAdmin, }, }, })