diff --git a/legacy/mcms/changesets/deploy_mcms_with_timelock_test.go b/legacy/mcms/changesets/deploy_mcms_with_timelock_test.go index 1601386..ed35bef 100644 --- a/legacy/mcms/changesets/deploy_mcms_with_timelock_test.go +++ b/legacy/mcms/changesets/deploy_mcms_with_timelock_test.go @@ -37,31 +37,35 @@ import ( "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/onchain" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/runtime" - - cldfchangesetutil "github.com/smartcontractkit/cld-changesets/pkg/cldfutil/changeset" ) func TestGrantRoleInTimeLock(t *testing.T) { t.Parallel() + // Initialize a runtime with a single EVM chain with one additional account + // + // The additional account will be used later on to replace the deployer key selector := chain_selectors.TEST_90000001.Selector - env, err := environment.New(t.Context(), + rt, err := runtime.New(t.Context(), runtime.WithEnvOpts( environment.WithEVMSimulatedWithConfig(t, []uint64{selector}, onchain.EVMSimLoaderConfig{ NumAdditionalAccounts: 1, }), - ) + environment.WithLogger(logger.Test(t)), + )) require.NoError(t, err) - // deploy the MCMS with timelock contracts - configuredChangeset := cldfchangesetutil.Configure( - cldf.CreateLegacyChangeSet(mcmschangesets.DeployMCMSWithTimelockV2), - map[uint64]cldfproposalutils.MCMSWithTimelockConfig{ + // Deploy MCMS with timelock contracts + err = rt.Exec( + runtime.ChangesetTask(cldf.CreateLegacyChangeSet(mcmschangesets.DeployMCMSWithTimelockV2), map[uint64]cldfproposalutils.MCMSWithTimelockConfig{ selector: cldftesthelpers.SingleGroupTimelockConfig(t), - }, + }), ) - updatedEnv, err := cldfchangesetutil.Apply(t, *env, configuredChangeset) require.NoError(t, err) - mcmsState, err := evmstate.MaybeLoadMCMSWithTimelockState(updatedEnv, []uint64{selector}) + + // Get the environment from the runtime because we need to make changes to it + env := rt.Environment() + + mcmsState, err := evmstate.MaybeLoadMCMSWithTimelockState(env, []uint64{selector}) require.NoError(t, err) // change the environment to remove proposer from the timelock, so that we can deploy new proposer @@ -70,12 +74,12 @@ func TestGrantRoleInTimeLock(t *testing.T) { ab := cldf.NewMemoryAddressBook() require.NoError(t, ab.Save(selector, existingProposer.Address().String(), cldf.NewTypeAndVersion(mcmscontracts.ProposerManyChainMultisig, semvers.V1_0_0))) - require.NoError(t, updatedEnv.ExistingAddresses.Remove(ab)) //nolint:staticcheck // test removes legacy AddressBook entry while verifying DataStore migration behavior + require.NoError(t, env.ExistingAddresses.Remove(ab)) //nolint:staticcheck // test removes legacy AddressBook entry while verifying DataStore migration behavior // remove from DataStore since deployment now uses DataStore // Since DataStore is immutable, create a new one without the proposer newDataStore := datastore.NewMemoryDataStore() - refs, err := updatedEnv.DataStore.Addresses().Fetch() + refs, err := env.DataStore.Addresses().Fetch() require.NoError(t, err) // Copy all address refs except the proposer we want to remove @@ -90,36 +94,46 @@ func TestGrantRoleInTimeLock(t *testing.T) { } // Replace the DataStore in the environment - updatedEnv.DataStore = newDataStore.Seal() + env.DataStore = newDataStore.Seal() // change the deployer key, so that we can deploy proposer with a new key // the new deployer key will not be admin of the timelock // we can test granting roles through proposal - evmChains := updatedEnv.BlockChains.EVMChains() + evmChains := env.BlockChains.EVMChains() chain := evmChains[selector] - chain.DeployerKey = evmChains[selector].Users[0] + chain.DeployerKey = evmChains[selector].Users[0] // Changes it to the additional account + evmChains[selector] = chain + + // Initialize a runtime again with the new environment so we can execute the changeset against + // the new environment + rt = runtime.NewFromEnvironment(env) // now deploy MCMS again so that only the proposer is new - updatedEnv, err = cldfchangesetutil.Apply(t, updatedEnv, configuredChangeset) - require.NoError(t, err) - mcmsState, err = evmstate.MaybeLoadMCMSWithTimelockState(updatedEnv, []uint64{selector}) + err = rt.Exec( + runtime.ChangesetTask(cldf.CreateLegacyChangeSet(mcmschangesets.DeployMCMSWithTimelockV2), map[uint64]cldfproposalutils.MCMSWithTimelockConfig{ + selector: cldftesthelpers.SingleGroupTimelockConfig(t), + }), + ) require.NoError(t, err) + mcmsState, err = evmstate.MaybeLoadMCMSWithTimelockState(rt.Environment(), []uint64{selector}) + require.NoError(t, err) require.NotEqual(t, existingProposer.Address(), mcmsState[selector].ProposerMcm.Address()) - updatedEnv, err = cldfchangesetutil.Apply(t, updatedEnv, cldfchangesetutil.Configure( - mcmschangesets.GrantRoleInTimeLock, - mcmschangesets.GrantRoleInput{ + + err = rt.Exec( + runtime.ChangesetTask(mcmschangesets.GrantRoleInTimeLock, mcmschangesets.GrantRoleInput{ ExistingProposerByChain: map[uint64]common.Address{ selector: existingProposer.Address(), }, MCMS: &cldfproposalutils.TimelockConfig{MinDelay: 0}, - }, - )) + }), + ) require.NoError(t, err) - mcmsState, err = evmstate.MaybeLoadMCMSWithTimelockState(updatedEnv, []uint64{selector}) + + mcmsState, err = evmstate.MaybeLoadMCMSWithTimelockState(rt.Environment(), []uint64{selector}) require.NoError(t, err) - evmTimelockInspector := mcmsevmsdk.NewTimelockInspector(updatedEnv.BlockChains.EVMChains()[selector].Client) + evmTimelockInspector := mcmsevmsdk.NewTimelockInspector(rt.Environment().BlockChains.EVMChains()[selector].Client) proposers, err := evmTimelockInspector.GetProposers(t.Context(), mcmsState[selector].Timelock.Address().Hex()) require.NoError(t, err) diff --git a/pkg/cldfutil/changeset/test_helpers.go b/pkg/cldfutil/changeset/test_helpers.go index 15223b5..1358a6b 100644 --- a/pkg/cldfutil/changeset/test_helpers.go +++ b/pkg/cldfutil/changeset/test_helpers.go @@ -1,195 +1,18 @@ package changeset import ( - "fmt" "math/big" "testing" - mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - cldftesthelpers "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils/testhelpers" - - evmstate "github.com/smartcontractkit/cld-changesets/legacy/pkg/family/evm" - - "github.com/smartcontractkit/chainlink-evm/pkg/utils" - cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" - - "github.com/smartcontractkit/chainlink-deployments-framework/operations" - - mcmsTypes "github.com/smartcontractkit/mcms/types" - - "github.com/smartcontractkit/chainlink-deployments-framework/datastore" - cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" -) - -type ConfiguredChangeSet interface { - Apply(e cldf.Environment) (cldf.ChangesetOutput, error) -} - -func Configure[C any]( - changeset cldf.ChangeSetV2[C], - config C, -) ConfiguredChangeSet { - return configuredChangeSetImpl[C]{ - changeset: changeset, - config: config, - } -} - -type configuredChangeSetImpl[C any] struct { - changeset cldf.ChangeSetV2[C] - config C -} - -func (ca configuredChangeSetImpl[C]) Apply(e cldf.Environment) (cldf.ChangesetOutput, error) { - err := ca.changeset.VerifyPreconditions(e, ca.config) - if err != nil { - return cldf.ChangesetOutput{}, err - } - - return ca.changeset.Apply(e, ca.config) -} - -// Apply applies the changeset applications to the environment and returns the updated environment. This is the -// variadic function equivalent of ApplyChangesets, but allowing you to simply pass in one or more changesets as -// parameters at the end of the function. e.g. `changeset.Apply(t, e, nil, configuredCS1, configuredCS2)` etc. -func Apply(t *testing.T, e cldf.Environment, first ConfiguredChangeSet, rest ...ConfiguredChangeSet) (cldf.Environment, error) { - t.Helper() - - env, _, err := ApplyChangesets(t, e, append([]ConfiguredChangeSet{first}, rest...)) - return env, err -} - -type applyChangesetOptions struct { - realBackend bool -} - -type ApplyChangesetsOptions func(*applyChangesetOptions) *applyChangesetOptions - -func WithRealBackend() ApplyChangesetsOptions { - return func(o *applyChangesetOptions) *applyChangesetOptions { - o.realBackend = true - return o - } -} - -// ApplyChangesets applies the changeset applications to the environment and returns the updated environment. -func ApplyChangesets(t *testing.T, e cldf.Environment, changesetApplications []ConfiguredChangeSet, opts ...ApplyChangesetsOptions) (cldf.Environment, []cldf.ChangesetOutput, error) { - t.Helper() - - opt := applyChangesetOptions{} - for _, o := range opts { - opt = *o(&opt) - } - - currentEnv := e - outputs := make([]cldf.ChangesetOutput, 0, len(changesetApplications)) - for i, csa := range changesetApplications { - out, err := csa.Apply(currentEnv) - if err != nil { - return e, nil, fmt.Errorf("failed to apply changeset at index %d: %w", i, err) - } - outputs = append(outputs, out) - var addresses cldf.AddressBook - if out.AddressBook != nil { //nolint:staticcheck // legacy AddressBook is still supported by test helper while changesets migrate to DataStore - addresses = out.AddressBook //nolint:staticcheck // legacy AddressBook is still supported by test helper while changesets migrate to DataStore - if err = addresses.Merge(currentEnv.ExistingAddresses); err != nil { //nolint:staticcheck // merge legacy addresses for compatibility - return e, nil, fmt.Errorf("failed to merge address book: %w", err) - } - } else { - addresses = currentEnv.ExistingAddresses //nolint:staticcheck // preserve legacy AddressBook state in test helper - } - - // Collect expected DataStore state after changeset is applied - var ds datastore.DataStore - if out.DataStore != nil { - ds1 := datastore.NewMemoryDataStore() - // New Addresses - if err = ds1.Merge(out.DataStore.Seal()); err != nil { - return e, nil, fmt.Errorf("failed to merge new addresses into datastore: %w", err) - } - // Existing Addresses - err = ds1.Merge(currentEnv.DataStore) - if err != nil { - return e, nil, fmt.Errorf("failed to merge current addresses into datastore: %w", err) - } - ds = ds1.Seal() - } else { - ds = currentEnv.DataStore - } - - if out.Jobs != nil { //nolint:revive,staticcheck // we want the empty block as documentation - // do nothing, as these jobs auto-accept. - } - - // Updated environment may be required before executing proposals when proposals involve new addresses - // Ex. changesets[0] deploys MCMS, changesets[1] generates a proposal with the new MCMS addresses - currentEnv = cldf.Environment{ - Name: e.Name, - Logger: e.Logger, - ExistingAddresses: addresses, //nolint:staticcheck // preserve legacy AddressBook state in test helper - DataStore: ds, - NodeIDs: e.NodeIDs, - Offchain: e.Offchain, - OCRSecrets: e.OCRSecrets, - GetContext: e.GetContext, - OperationsBundle: operations.NewBundle(e.GetContext, e.Logger, operations.NewMemoryReporter()), // to ensure that each migration is run in a clean environment - BlockChains: e.BlockChains, - } - - if out.MCMSTimelockProposals != nil { - for _, prop := range out.MCMSTimelockProposals { - chains := mapset.NewSet[uint64]() - for _, op := range prop.Operations { - chains.Add(uint64(op.ChainSelector)) - } - - // We need to supply a salt override, otherwise the validUntil timestamp will be used to generate the salt. - // In tests, validUntil is not always guaranteed to produce a unique operation ID because proposals often get generated within the same second. - // This has been a cause of flakiness in the past (caused an AlreadyScheduled error). - saltOverride := utils.RandomHash() - prop.SaltOverride = &saltOverride - - p := cldftesthelpers.SignMCMSTimelockProposal(t, currentEnv, &prop, opt.realBackend) - err = cldftesthelpers.ExecuteMCMSProposalV2(t, currentEnv, p) - if err != nil { - return cldf.Environment{}, nil, err - } - if prop.Action != mcmsTypes.TimelockActionSchedule { - // We don't need to execute the proposal if it's not a schedule action - // because the proposal is already executed in the previous step. - return currentEnv, outputs, nil - } - err = cldftesthelpers.ExecuteMCMSTimelockProposalV2(t, currentEnv, &prop) - if err != nil { - return cldf.Environment{}, nil, err - } - } - } - if out.MCMSProposals != nil { - for _, prop := range out.MCMSProposals { - chains := mapset.NewSet[uint64]() - for _, op := range prop.Operations { - chains.Add(uint64(op.ChainSelector)) - } - - p := cldftesthelpers.SignMCMSProposal(t, currentEnv, &prop) - err = cldftesthelpers.ExecuteMCMSProposalV2(t, currentEnv, p) - if err != nil { - return cldf.Environment{}, nil, err - } - } - } - } - - return currentEnv, outputs, nil -} + evmstate "github.com/smartcontractkit/cld-changesets/legacy/pkg/family/evm" +) func MustFundAddressWithLink(t *testing.T, e cldf.Environment, chain cldf_evm.Chain, to common.Address, amount int64) { t.Helper() diff --git a/pkg/family/evm/operations/utils_test.go b/pkg/family/evm/operations/utils_test.go index c33ebce..789c9fe 100644 --- a/pkg/family/evm/operations/utils_test.go +++ b/pkg/family/evm/operations/utils_test.go @@ -32,8 +32,6 @@ import ( "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/operations" "github.com/smartcontractkit/chainlink-deployments-framework/operations/optest" - - cldfchangesetutil "github.com/smartcontractkit/cld-changesets/pkg/cldfutil/changeset" ) func TestCloneTransactOptsWithGas(t *testing.T) { @@ -201,19 +199,18 @@ func TestAddEVMCallSequenceToCSOutput_ProposalCombination(t *testing.T) { // Deploy MCMS+Timelock to both chains. Real deployments are required because // AddEVMCallSequenceToCSOutput → BuildProposalFromBatchesV2 reads OpCount from - env, err := cldfchangesetutil.Apply(t, rt.Environment(), cldfchangesetutil.Configure( - cldf.CreateLegacyChangeSet(mcmschangesets.DeployMCMSWithTimelockV2), - map[uint64]cldfproposalutils.MCMSWithTimelockConfig{ + err = rt.Exec( + runtime.ChangesetTask(cldf.CreateLegacyChangeSet(mcmschangesets.DeployMCMSWithTimelockV2), map[uint64]cldfproposalutils.MCMSWithTimelockConfig{ selector1: cldftesthelpers.SingleGroupTimelockConfig(t), selector2: cldftesthelpers.SingleGroupTimelockConfig(t), - }, - )) + }), + ) require.NoError(t, err) // Build the MCMS state map directly from the EVM state package — this is the // product-agnostic, MCMS-only equivalent of CCIP's stateview.LoadOnchainState // + chainState.EVMMCMSStateByChain(). - mcmsStateByChain := loadEVMMCMSStateByChain(t, env, selectors) + mcmsStateByChain := loadEVMMCMSStateByChain(t, rt.Environment(), selectors) // Two pre-existing proposals (one per chain) to exercise the aggregation path. existingProposal1 := mcmslib.TimelockProposal{ @@ -282,7 +279,7 @@ func TestAddEVMCallSequenceToCSOutput_ProposalCombination(t *testing.T) { } result, err := opsevm.AddEVMCallSequenceToCSOutput( - env, + rt.Environment(), csOutput, seqReport, nil,