From f15b2074cf15a628fc11b4b858f30c2d915b162d Mon Sep 17 00:00:00 2001 From: Pablo Date: Fri, 13 Dec 2024 08:56:33 -0600 Subject: [PATCH 1/6] feat: add changeset for set config on all 3 mcms contracts --- .../common/changeset/set_config_mcms.go | 206 +++++++++++ .../common/changeset/set_config_mcms_test.go | 327 ++++++++++++++++++ 2 files changed, 533 insertions(+) create mode 100644 deployment/common/changeset/set_config_mcms.go create mode 100644 deployment/common/changeset/set_config_mcms_test.go diff --git a/deployment/common/changeset/set_config_mcms.go b/deployment/common/changeset/set_config_mcms.go new file mode 100644 index 00000000000..5f940b053e9 --- /dev/null +++ b/deployment/common/changeset/set_config_mcms.go @@ -0,0 +1,206 @@ +package changeset + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + chain_selectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" +) + +type ConfigPerRole struct { + Proposer config.Config + Canceller config.Config + Bypasser config.Config +} +type ProposalConfig struct { + MinDelay time.Duration // delay for timelock worker to execute the transfers. +} + +type SetConfigParams struct { + ConfigsPerChain map[uint64]ConfigPerRole + ProposalConfig *ProposalConfig +} + +var _ deployment.ChangeSet[SetConfigParams] = SetConfigMCMS + +// Validate checks that the SetConfigParams is valid +func (cfg SetConfigParams) Validate(e deployment.Environment, selectors []uint64) error { + // configs should have at least one chain + state, err := MaybeLoadMCMSWithTimelockState(e, selectors) + if err != nil { + return err + } + if len(cfg.ConfigsPerChain) == 0 { + return errors.New("no chain configs provided") + } + for chainSelector, c := range cfg.ConfigsPerChain { + family, err := chain_selectors.GetSelectorFamily(chainSelector) + if err != nil { + return err + } + if family != chain_selectors.FamilyEVM { + return fmt.Errorf("chain selector: %d is not an ethereum chain", chainSelector) + } + _, ok := e.Chains[chainSelector] + if !ok { + return fmt.Errorf("chain selector: %d not found in environment", chainSelector) + } + _, ok = state[chainSelector] + if !ok { + return fmt.Errorf("chain selector: %d not found for MCMS state", chainSelector) + } + if err := c.Proposer.Validate(); err != nil { + return err + } + if err := c.Canceller.Validate(); err != nil { + return err + } + if err := c.Bypasser.Validate(); err != nil { + return err + } + } + return nil +} + +// setConfigOrTxData executes set config tx or gets the tx data for the MCMS proposal +func setConfigOrTxData(chain deployment.Chain, cfg config.Config, contract *gethwrappers.ManyChainMultiSig, useMCMS bool) (*types.Transaction, error) { + groupQuorums, groupParents, signerAddresses, signerGroups := cfg.ExtractSetConfigInputs() + opts := deployment.SimTransactOpts() + if !useMCMS { + opts = chain.DeployerKey + } + tx, err := contract.SetConfig(opts, signerAddresses, signerGroups, groupQuorums, groupParents, false) + if err != nil { + return nil, err + } + if !useMCMS { + _, err = deployment.ConfirmIfNoError(chain, tx, err) + if err != nil { + return nil, err + } + } + return tx, nil +} + +type setConfigTxs struct { + proposerTx *types.Transaction + cancellerTx *types.Transaction + bypasserTx *types.Transaction +} + +// setConfigPerRole sets the configuration for each of the MCMS contract roles on the mcmsState. +func setConfigPerRole(chain deployment.Chain, cfg ConfigPerRole, mcmsState *MCMSWithTimelockState, useMCMS bool) (setConfigTxs, error) { + // Proposer set config + proposerTx, err := setConfigOrTxData(chain, cfg.Proposer, mcmsState.ProposerMcm, useMCMS) + if err != nil { + return setConfigTxs{}, err + } + // Canceller set config + cancellerTx, err := setConfigOrTxData(chain, cfg.Canceller, mcmsState.CancellerMcm, useMCMS) + if err != nil { + return setConfigTxs{}, err + } + // Bypasser set config + bypasserTx, err := setConfigOrTxData(chain, cfg.Bypasser, mcmsState.BypasserMcm, useMCMS) + + if err != nil { + return setConfigTxs{}, err + } + return setConfigTxs{ + proposerTx: proposerTx, + cancellerTx: cancellerTx, + bypasserTx: bypasserTx, + }, nil +} +func addTxsToProposalBatch(setConfigTxsChain setConfigTxs, chainSelector uint64, state MCMSWithTimelockState) timelock.BatchChainOperation { + result := timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(chainSelector), + Batch: []mcms.Operation{}, + } + result.Batch = append(result.Batch, mcms.Operation{ + To: state.ProposerMcm.Address(), + Data: setConfigTxsChain.proposerTx.Data(), + Value: big.NewInt(0), + ContractType: string(commontypes.ProposerManyChainMultisig), + }) + result.Batch = append(result.Batch, mcms.Operation{ + To: state.CancellerMcm.Address(), + Data: setConfigTxsChain.cancellerTx.Data(), + Value: big.NewInt(0), + ContractType: string(commontypes.CancellerManyChainMultisig), + }) + result.Batch = append(result.Batch, mcms.Operation{ + To: state.BypasserMcm.Address(), + Data: setConfigTxsChain.bypasserTx.Data(), + Value: big.NewInt(0), + ContractType: string(commontypes.BypasserManyChainMultisig), + }) + return result +} + +// SetConfigMCMS sets the configuration of the MCMS contract on the chain identified by the chainSelector. +func SetConfigMCMS(e deployment.Environment, cfg SetConfigParams) (deployment.ChangesetOutput, error) { + selectors := []uint64{} + for chainSelector := range cfg.ConfigsPerChain { + selectors = append(selectors, chainSelector) + } + useMCMS := cfg.ProposalConfig != nil + err := cfg.Validate(e, selectors) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + batches := []timelock.BatchChainOperation{} + timelocksPerChain := map[uint64]common.Address{} + proposerMcmsPerChain := map[uint64]*gethwrappers.ManyChainMultiSig{} + + mcmsStatePerChain, err := MaybeLoadMCMSWithTimelockState(e, selectors) + if err != nil { + return deployment.ChangesetOutput{}, err + } + + for chainSelector, c := range cfg.ConfigsPerChain { + chain, ok := e.Chains[chainSelector] + if !ok { + return deployment.ChangesetOutput{}, errors.New("chain not found in environment") + } + state, ok := mcmsStatePerChain[chainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("MCMS state not found for chain selector: %d", chainSelector) + } + timelocksPerChain[chainSelector] = state.Timelock.Address() + proposerMcmsPerChain[chainSelector] = state.ProposerMcm + setConfigTxsChain, err := setConfigPerRole(chain, c, state, useMCMS) + if err != nil { + return deployment.ChangesetOutput{}, err + } + if useMCMS { + batch := addTxsToProposalBatch(setConfigTxsChain, chainSelector, *state) + batches = append(batches, batch) + } + + } + + if useMCMS { + // Create MCMS with timelock proposal + proposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMcmsPerChain, batches, "Set config proposal", cfg.ProposalConfig.MinDelay) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w", err) + } + return deployment.ChangesetOutput{Proposals: []timelock.MCMSWithTimelockProposal{*proposal}}, nil + } + + return deployment.ChangesetOutput{}, nil +} diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go new file mode 100644 index 00000000000..fcd5db98a8c --- /dev/null +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -0,0 +1,327 @@ +package changeset + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + chain_selectors "github.com/smartcontractkit/chain-selectors" + + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" + "github.com/smartcontractkit/chainlink/v2/core/logger" + + "github.com/stretchr/testify/require" + "go.uber.org/zap/zapcore" + + "github.com/smartcontractkit/chainlink/deployment" + + "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/deployment/environment/memory" +) + +// setupSetConfigTestEnv deploys all required contracts for the setConfig MCMS contract call. +func setupSetConfigTestEnv(t *testing.T) deployment.Environment { + + lggr := logger.TestLogger(t) + cfg := memory.MemoryEnvironmentConfig{ + Nodes: 1, + Chains: 2, + } + env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) + chainSelector := env.AllChainSelectors()[0] + config := proposalutils.SingleGroupMCMS(t) + + // Deploy MCMS and Timelock + env, err := ApplyChangesets(t, env, nil, []ChangesetApplication{ + { + Changeset: WrapChangeSet(DeployLinkToken), + Config: []uint64{chainSelector}, + }, + { + Changeset: WrapChangeSet(DeployMCMSWithTimelock), + Config: map[uint64]types.MCMSWithTimelockConfig{ + chainSelector: { + Canceller: config, + Bypasser: config, + Proposer: config, + TimelockMinDelay: big.NewInt(0), + }, + }, + }, + }) + require.NoError(t, err) + return env +} + +// TestSetConfigMCMS tests the SetConfigMCMS changeset by calling SetConfig and checking the config values. +func TestSetConfigMCMS(t *testing.T) { + t.Parallel() + ctx := context.Background() + + env := setupSetConfigTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + + timelockAddress := mcmsState.Timelock.Address() + cfg := proposalutils.SingleGroupMCMS(t) + // Add the timelock as a signer to check state changes + cfg.Signers = append(cfg.Signers, timelockAddress) + cfg.Quorum = 2 // quorum should change to 2 out of 2 signers + // Transfer ownership to timelock + // Apply the changesets + _, err = ApplyChangesets(t, env, nil, []ChangesetApplication{ + { + Changeset: WrapChangeSet(SetConfigMCMS), + Config: SetConfigParams{ + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + }) + require.NoError(t, err) + // Check new State + expected := cfg.ToRawConfig() + opts := &bind.CallOpts{Context: ctx} + newConf, err := mcmsState.ProposerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) + + newConf, err = mcmsState.BypasserMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) + + newConf, err = mcmsState.CancellerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) +} + +// TestSetConfigMCMSProposal tests the SetConfigMCMS changeset proposal generation by calling SetConfig and checking the config values. +func TestSetConfigMCMSProposal(t *testing.T) { + t.Parallel() + ctx := context.Background() + + env := setupSetConfigTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + + mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + + timelockAddress := mcmsState.Timelock.Address() + timelockMap := map[uint64]*proposalutils.TimelockExecutionContracts{ + chainSelector: { + Timelock: mcmsState.Timelock, + CallProxy: mcmsState.CallProxy, + }, + } + cfg := proposalutils.SingleGroupMCMS(t) + // Add the timelock as a signer to check state changes + cfg.Signers = append(cfg.Signers, timelockAddress) + cfg.Quorum = 2 // quorum should change to 2 out of 2 signers + // Apply the changeset + _, err = ApplyChangesets(t, env, timelockMap, []ChangesetApplication{ + { + Changeset: WrapChangeSet(TransferToMCMSWithTimelock), + Config: TransferToMCMSWithTimelockConfig{ + ContractsByChain: map[uint64][]common.Address{ + chainSelector: {mcmsState.ProposerMcm.Address(), mcmsState.BypasserMcm.Address(), mcmsState.CancellerMcm.Address()}, + }, + }, + }, + { + Changeset: WrapChangeSet(SetConfigMCMS), + Config: SetConfigParams{ + ProposalConfig: &ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + }) + require.NoError(t, err) + // Check new State + expected := cfg.ToRawConfig() + opts := &bind.CallOpts{Context: ctx} + newConf, err := mcmsState.ProposerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) + + newConf, err = mcmsState.BypasserMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) + + newConf, err = mcmsState.CancellerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) +} + +func TestValidate(t *testing.T) { + env := setupSetConfigTestEnv(t) + + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) + mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + cfg := proposalutils.SingleGroupMCMS(t) + timelockAddress := mcmsState.Timelock.Address() + // Add the timelock as a signer to check state changes + cfg.Signers = append(cfg.Signers, timelockAddress) + cfg.Quorum = 2 // quorum + + cfgInvalid := proposalutils.SingleGroupMCMS(t) + cfgInvalid.Quorum = 0 + require.NoError(t, err) + tests := []struct { + name string + cfg SetConfigParams + errorMsg string + }{ + { + name: "valid config", + cfg: SetConfigParams{ + ProposalConfig: &ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + { + name: "valid non mcms config", + cfg: SetConfigParams{ + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + { + name: "no chain configurations", + cfg: SetConfigParams{ + ConfigsPerChain: map[uint64]ConfigPerRole{}, + }, + errorMsg: "no chain configs provided", + }, + { + name: "non evm chain", + cfg: SetConfigParams{ + ConfigsPerChain: map[uint64]ConfigPerRole{ + chain_selectors.APTOS_MAINNET.Selector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + errorMsg: "chain selector: 4741433654826277614 is not an ethereum chain", + }, + { + name: "chain selector not found in environment", + cfg: SetConfigParams{ + ConfigsPerChain: map[uint64]ConfigPerRole{ + 123: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + errorMsg: "unknown chain selector 123", + }, + { + name: "invalid proposer config", + cfg: SetConfigParams{ + ProposalConfig: &ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfgInvalid, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + errorMsg: "invalid MCMS config: Quorum must be greater than 0", + }, + { + name: "invalid canceller config", + cfg: SetConfigParams{ + ProposalConfig: &ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfgInvalid, + Bypasser: cfg, + }, + }, + }, + errorMsg: "invalid MCMS config: Quorum must be greater than 0", + }, + { + name: "invalid bypasser config", + cfg: SetConfigParams{ + ProposalConfig: &ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]ConfigPerRole{ + chainSelector: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfgInvalid, + }, + }, + }, + errorMsg: "invalid MCMS config: Quorum must be greater than 0", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + selectors := []uint64{chainSelector} + + err := tt.cfg.Validate(env, selectors) + if tt.errorMsg != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMsg) + } else { + require.NoError(t, err) + } + }) + } +} From f12253f6984727db67444588bb4721511ed4acff Mon Sep 17 00:00:00 2001 From: Pablo Date: Fri, 13 Dec 2024 15:52:42 -0600 Subject: [PATCH 2/6] fix: use _test package and change ctx function. --- .../common/changeset/set_config_mcms_test.go | 85 ++++++++++--------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go index fcd5db98a8c..c9f6edf4472 100644 --- a/deployment/common/changeset/set_config_mcms_test.go +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -1,14 +1,15 @@ -package changeset +package changeset_test import ( - "context" "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -34,13 +35,13 @@ func setupSetConfigTestEnv(t *testing.T) deployment.Environment { config := proposalutils.SingleGroupMCMS(t) // Deploy MCMS and Timelock - env, err := ApplyChangesets(t, env, nil, []ChangesetApplication{ + env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { - Changeset: WrapChangeSet(DeployLinkToken), + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployLinkToken), Config: []uint64{chainSelector}, }, { - Changeset: WrapChangeSet(DeployMCMSWithTimelock), + Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ chainSelector: { Canceller: config, @@ -58,7 +59,7 @@ func setupSetConfigTestEnv(t *testing.T) deployment.Environment { // TestSetConfigMCMS tests the SetConfigMCMS changeset by calling SetConfig and checking the config values. func TestSetConfigMCMS(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := tests.Context(t) env := setupSetConfigTestEnv(t) chainSelector := env.AllChainSelectors()[0] @@ -67,7 +68,7 @@ func TestSetConfigMCMS(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) timelockAddress := mcmsState.Timelock.Address() @@ -77,11 +78,11 @@ func TestSetConfigMCMS(t *testing.T) { cfg.Quorum = 2 // quorum should change to 2 out of 2 signers // Transfer ownership to timelock // Apply the changesets - _, err = ApplyChangesets(t, env, nil, []ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { - Changeset: WrapChangeSet(SetConfigMCMS), - Config: SetConfigParams{ - ConfigsPerChain: map[uint64]ConfigPerRole{ + Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), + Config: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfg, @@ -111,7 +112,7 @@ func TestSetConfigMCMS(t *testing.T) { // TestSetConfigMCMSProposal tests the SetConfigMCMS changeset proposal generation by calling SetConfig and checking the config values. func TestSetConfigMCMSProposal(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := tests.Context(t) env := setupSetConfigTestEnv(t) chainSelector := env.AllChainSelectors()[0] @@ -120,7 +121,7 @@ func TestSetConfigMCMSProposal(t *testing.T) { require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) timelockAddress := mcmsState.Timelock.Address() @@ -135,22 +136,22 @@ func TestSetConfigMCMSProposal(t *testing.T) { cfg.Signers = append(cfg.Signers, timelockAddress) cfg.Quorum = 2 // quorum should change to 2 out of 2 signers // Apply the changeset - _, err = ApplyChangesets(t, env, timelockMap, []ChangesetApplication{ + _, err = commonchangeset.ApplyChangesets(t, env, timelockMap, []commonchangeset.ChangesetApplication{ { - Changeset: WrapChangeSet(TransferToMCMSWithTimelock), - Config: TransferToMCMSWithTimelockConfig{ + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: commonchangeset.TransferToMCMSWithTimelockConfig{ ContractsByChain: map[uint64][]common.Address{ chainSelector: {mcmsState.ProposerMcm.Address(), mcmsState.BypasserMcm.Address(), mcmsState.CancellerMcm.Address()}, }, }, }, { - Changeset: WrapChangeSet(SetConfigMCMS), - Config: SetConfigParams{ - ProposalConfig: &ProposalConfig{ + Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), + Config: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ MinDelay: 0, }, - ConfigsPerChain: map[uint64]ConfigPerRole{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfg, @@ -185,7 +186,7 @@ func TestValidate(t *testing.T) { addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) require.NoError(t, err) require.Len(t, addrs, 6) - mcmsState, err := MaybeLoadMCMSWithTimelockChainState(chain, addrs) + mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) cfg := proposalutils.SingleGroupMCMS(t) timelockAddress := mcmsState.Timelock.Address() @@ -198,16 +199,16 @@ func TestValidate(t *testing.T) { require.NoError(t, err) tests := []struct { name string - cfg SetConfigParams + cfg commonchangeset.SetConfigParams errorMsg string }{ { name: "valid config", - cfg: SetConfigParams{ - ProposalConfig: &ProposalConfig{ + cfg: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ MinDelay: 0, }, - ConfigsPerChain: map[uint64]ConfigPerRole{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfg, @@ -218,8 +219,8 @@ func TestValidate(t *testing.T) { }, { name: "valid non mcms config", - cfg: SetConfigParams{ - ConfigsPerChain: map[uint64]ConfigPerRole{ + cfg: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfg, @@ -230,15 +231,15 @@ func TestValidate(t *testing.T) { }, { name: "no chain configurations", - cfg: SetConfigParams{ - ConfigsPerChain: map[uint64]ConfigPerRole{}, + cfg: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{}, }, errorMsg: "no chain configs provided", }, { name: "non evm chain", - cfg: SetConfigParams{ - ConfigsPerChain: map[uint64]ConfigPerRole{ + cfg: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chain_selectors.APTOS_MAINNET.Selector: { Proposer: cfg, Canceller: cfg, @@ -250,8 +251,8 @@ func TestValidate(t *testing.T) { }, { name: "chain selector not found in environment", - cfg: SetConfigParams{ - ConfigsPerChain: map[uint64]ConfigPerRole{ + cfg: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ 123: { Proposer: cfg, Canceller: cfg, @@ -263,11 +264,11 @@ func TestValidate(t *testing.T) { }, { name: "invalid proposer config", - cfg: SetConfigParams{ - ProposalConfig: &ProposalConfig{ + cfg: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ MinDelay: 0, }, - ConfigsPerChain: map[uint64]ConfigPerRole{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfgInvalid, Canceller: cfg, @@ -279,11 +280,11 @@ func TestValidate(t *testing.T) { }, { name: "invalid canceller config", - cfg: SetConfigParams{ - ProposalConfig: &ProposalConfig{ + cfg: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ MinDelay: 0, }, - ConfigsPerChain: map[uint64]ConfigPerRole{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfgInvalid, @@ -295,11 +296,11 @@ func TestValidate(t *testing.T) { }, { name: "invalid bypasser config", - cfg: SetConfigParams{ - ProposalConfig: &ProposalConfig{ + cfg: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ MinDelay: 0, }, - ConfigsPerChain: map[uint64]ConfigPerRole{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, Canceller: cfg, From d8cc495842e298e8a6b212fdb5c3bea4bd0929b5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 16 Dec 2024 08:23:47 -0600 Subject: [PATCH 3/6] fix: formatting and comments --- deployment/common/changeset/set_config_mcms.go | 9 +++++---- .../common/changeset/set_config_mcms_test.go | 14 ++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/deployment/common/changeset/set_config_mcms.go b/deployment/common/changeset/set_config_mcms.go index 5f940b053e9..a0c80f41f0b 100644 --- a/deployment/common/changeset/set_config_mcms.go +++ b/deployment/common/changeset/set_config_mcms.go @@ -37,14 +37,14 @@ var _ deployment.ChangeSet[SetConfigParams] = SetConfigMCMS // Validate checks that the SetConfigParams is valid func (cfg SetConfigParams) Validate(e deployment.Environment, selectors []uint64) error { + if len(cfg.ConfigsPerChain) == 0 { + return errors.New("no chain configs provided") + } // configs should have at least one chain state, err := MaybeLoadMCMSWithTimelockState(e, selectors) if err != nil { return err } - if len(cfg.ConfigsPerChain) == 0 { - return errors.New("no chain configs provided") - } for chainSelector, c := range cfg.ConfigsPerChain { family, err := chain_selectors.GetSelectorFamily(chainSelector) if err != nil { @@ -114,16 +114,17 @@ func setConfigPerRole(chain deployment.Chain, cfg ConfigPerRole, mcmsState *MCMS } // Bypasser set config bypasserTx, err := setConfigOrTxData(chain, cfg.Bypasser, mcmsState.BypasserMcm, useMCMS) - if err != nil { return setConfigTxs{}, err } + return setConfigTxs{ proposerTx: proposerTx, cancellerTx: cancellerTx, bypasserTx: bypasserTx, }, nil } + func addTxsToProposalBatch(setConfigTxsChain setConfigTxs, chainSelector uint64, state MCMSWithTimelockState) timelock.BatchChainOperation { result := timelock.BatchChainOperation{ ChainIdentifier: mcms.ChainIdentifier(chainSelector), diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go index c9f6edf4472..a934735168d 100644 --- a/deployment/common/changeset/set_config_mcms_test.go +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -1,7 +1,6 @@ package changeset_test import ( - "math/big" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -32,8 +31,8 @@ func setupSetConfigTestEnv(t *testing.T) deployment.Environment { } env := memory.NewMemoryEnvironment(t, lggr, zapcore.DebugLevel, cfg) chainSelector := env.AllChainSelectors()[0] - config := proposalutils.SingleGroupMCMS(t) + config := proposalutils.SingleGroupTimelockConfig(t) // Deploy MCMS and Timelock env, err := commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { @@ -43,12 +42,7 @@ func setupSetConfigTestEnv(t *testing.T) deployment.Environment { { Changeset: commonchangeset.WrapChangeSet(commonchangeset.DeployMCMSWithTimelock), Config: map[uint64]types.MCMSWithTimelockConfig{ - chainSelector: { - Canceller: config, - Bypasser: config, - Proposer: config, - TimelockMinDelay: big.NewInt(0), - }, + chainSelector: config, }, }, }) @@ -76,8 +70,8 @@ func TestSetConfigMCMS(t *testing.T) { // Add the timelock as a signer to check state changes cfg.Signers = append(cfg.Signers, timelockAddress) cfg.Quorum = 2 // quorum should change to 2 out of 2 signers - // Transfer ownership to timelock - // Apply the changesets + + // Set config on all 3 MCMS contracts _, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), From e16dee8d4a1798d860afcd7db960309f5159f0e5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 16 Dec 2024 14:34:22 -0600 Subject: [PATCH 4/6] fix: unit tests set config --- .../common/changeset/set_config_mcms_test.go | 196 ++++++++---------- 1 file changed, 90 insertions(+), 106 deletions(-) diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go index a934735168d..cab7bdba512 100644 --- a/deployment/common/changeset/set_config_mcms_test.go +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/config" chain_selectors "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" @@ -50,126 +51,109 @@ func setupSetConfigTestEnv(t *testing.T) deployment.Environment { return env } -// TestSetConfigMCMS tests the SetConfigMCMS changeset by calling SetConfig and checking the config values. -func TestSetConfigMCMS(t *testing.T) { - t.Parallel() - ctx := tests.Context(t) +// TestSetConfigMCMSVariants tests the SetConfigMCMS changeset variants. +func TestSetConfigMCMSVariants(t *testing.T) { - env := setupSetConfigTestEnv(t) - chainSelector := env.AllChainSelectors()[0] - chain := env.Chains[chainSelector] - addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) - require.NoError(t, err) - require.Len(t, addrs, 6) - - mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) - require.NoError(t, err) - - timelockAddress := mcmsState.Timelock.Address() - cfg := proposalutils.SingleGroupMCMS(t) // Add the timelock as a signer to check state changes - cfg.Signers = append(cfg.Signers, timelockAddress) - cfg.Quorum = 2 // quorum should change to 2 out of 2 signers + for _, tc := range []struct { + name string + changeSets func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication + }{ + { + name: "MCMS disabled", + changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication { - // Set config on all 3 MCMS contracts - _, err = commonchangeset.ApplyChangesets(t, env, nil, []commonchangeset.ChangesetApplication{ + return []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), + Config: commonchangeset.SetConfigParams{ + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ + chainSel: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + } + }, + }, { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), - Config: commonchangeset.SetConfigParams{ - ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ - chainSelector: { - Proposer: cfg, - Canceller: cfg, - Bypasser: cfg, + name: "MCMS enabled", + changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication { + return []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), + Config: commonchangeset.TransferToMCMSWithTimelockConfig{ + ContractsByChain: map[uint64][]common.Address{ + chainSel: {mcmsState.ProposerMcm.Address(), mcmsState.BypasserMcm.Address(), mcmsState.CancellerMcm.Address()}, + }, + }, }, - }, + { + Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), + Config: commonchangeset.SetConfigParams{ + ProposalConfig: &commonchangeset.ProposalConfig{ + MinDelay: 0, + }, + ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ + chainSel: { + Proposer: cfg, + Canceller: cfg, + Bypasser: cfg, + }, + }, + }, + }, + } }, }, - }) - require.NoError(t, err) - // Check new State - expected := cfg.ToRawConfig() - opts := &bind.CallOpts{Context: ctx} - newConf, err := mcmsState.ProposerMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) + } { + t.Run(tc.name, func(t *testing.T) { + ctx := tests.Context(t) - newConf, err = mcmsState.BypasserMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) + env := setupSetConfigTestEnv(t) + chainSelector := env.AllChainSelectors()[0] + chain := env.Chains[chainSelector] + addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) + require.NoError(t, err) + require.Len(t, addrs, 6) - newConf, err = mcmsState.CancellerMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) -} + mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) + require.NoError(t, err) + timelockAddress := mcmsState.Timelock.Address() + cfg := proposalutils.SingleGroupMCMS(t) + cfg.Signers = append(cfg.Signers, timelockAddress) + cfg.Quorum = 2 // quorum should change to 2 out of 2 signers + timelockMap := map[uint64]*proposalutils.TimelockExecutionContracts{ + chainSelector: { + Timelock: mcmsState.Timelock, + CallProxy: mcmsState.CallProxy, + }, + } -// TestSetConfigMCMSProposal tests the SetConfigMCMS changeset proposal generation by calling SetConfig and checking the config values. -func TestSetConfigMCMSProposal(t *testing.T) { - t.Parallel() - ctx := tests.Context(t) + // Set config on all 3 MCMS contracts + changesetsToApply := tc.changeSets(mcmsState, chainSelector, cfg) + _, err = commonchangeset.ApplyChangesets(t, env, timelockMap, changesetsToApply) + require.NoError(t, err) - env := setupSetConfigTestEnv(t) - chainSelector := env.AllChainSelectors()[0] - chain := env.Chains[chainSelector] - addrs, err := env.ExistingAddresses.AddressesForChain(chainSelector) - require.NoError(t, err) - require.Len(t, addrs, 6) + // Check new State + expected := cfg.ToRawConfig() + opts := &bind.CallOpts{Context: ctx} + newConf, err := mcmsState.ProposerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) - mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) - require.NoError(t, err) + newConf, err = mcmsState.BypasserMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) - timelockAddress := mcmsState.Timelock.Address() - timelockMap := map[uint64]*proposalutils.TimelockExecutionContracts{ - chainSelector: { - Timelock: mcmsState.Timelock, - CallProxy: mcmsState.CallProxy, - }, + newConf, err = mcmsState.CancellerMcm.GetConfig(opts) + require.NoError(t, err) + require.Equal(t, expected, newConf) + }) } - cfg := proposalutils.SingleGroupMCMS(t) - // Add the timelock as a signer to check state changes - cfg.Signers = append(cfg.Signers, timelockAddress) - cfg.Quorum = 2 // quorum should change to 2 out of 2 signers - // Apply the changeset - _, err = commonchangeset.ApplyChangesets(t, env, timelockMap, []commonchangeset.ChangesetApplication{ - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), - Config: commonchangeset.TransferToMCMSWithTimelockConfig{ - ContractsByChain: map[uint64][]common.Address{ - chainSelector: {mcmsState.ProposerMcm.Address(), mcmsState.BypasserMcm.Address(), mcmsState.CancellerMcm.Address()}, - }, - }, - }, - { - Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), - Config: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ - MinDelay: 0, - }, - ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ - chainSelector: { - Proposer: cfg, - Canceller: cfg, - Bypasser: cfg, - }, - }, - }, - }, - }) - require.NoError(t, err) - // Check new State - expected := cfg.ToRawConfig() - opts := &bind.CallOpts{Context: ctx} - newConf, err := mcmsState.ProposerMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) - - newConf, err = mcmsState.BypasserMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) - - newConf, err = mcmsState.CancellerMcm.GetConfig(opts) - require.NoError(t, err) - require.Equal(t, expected, newConf) } func TestValidate(t *testing.T) { From 5f3af61e23988e0d94271b002835e5e7060fb11d Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 16 Dec 2024 14:43:05 -0600 Subject: [PATCH 5/6] fix: check different configs per role. --- .../common/changeset/set_config_mcms_test.go | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go index cab7bdba512..c5c5a8377a7 100644 --- a/deployment/common/changeset/set_config_mcms_test.go +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -57,11 +57,11 @@ func TestSetConfigMCMSVariants(t *testing.T) { // Add the timelock as a signer to check state changes for _, tc := range []struct { name string - changeSets func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication + changeSets func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfgProp, cfgCancel, cfgBypass config.Config) []commonchangeset.ChangesetApplication }{ { name: "MCMS disabled", - changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication { + changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfgProp, cfgCancel, cfgBypass config.Config) []commonchangeset.ChangesetApplication { return []commonchangeset.ChangesetApplication{ { @@ -69,9 +69,9 @@ func TestSetConfigMCMSVariants(t *testing.T) { Config: commonchangeset.SetConfigParams{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSel: { - Proposer: cfg, - Canceller: cfg, - Bypasser: cfg, + Proposer: cfgProp, + Canceller: cfgCancel, + Bypasser: cfgBypass, }, }, }, @@ -81,7 +81,7 @@ func TestSetConfigMCMSVariants(t *testing.T) { }, { name: "MCMS enabled", - changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfg config.Config) []commonchangeset.ChangesetApplication { + changeSets: func(mcmsState *commonchangeset.MCMSWithTimelockState, chainSel uint64, cfgProp, cfgCancel, cfgBypass config.Config) []commonchangeset.ChangesetApplication { return []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock), @@ -99,9 +99,9 @@ func TestSetConfigMCMSVariants(t *testing.T) { }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSel: { - Proposer: cfg, - Canceller: cfg, - Bypasser: cfg, + Proposer: cfgProp, + Canceller: cfgCancel, + Bypasser: cfgBypass, }, }, }, @@ -123,32 +123,39 @@ func TestSetConfigMCMSVariants(t *testing.T) { mcmsState, err := commonchangeset.MaybeLoadMCMSWithTimelockChainState(chain, addrs) require.NoError(t, err) timelockAddress := mcmsState.Timelock.Address() - cfg := proposalutils.SingleGroupMCMS(t) - cfg.Signers = append(cfg.Signers, timelockAddress) - cfg.Quorum = 2 // quorum should change to 2 out of 2 signers + cfgProposer := proposalutils.SingleGroupMCMS(t) + cfgProposer.Signers = append(cfgProposer.Signers, timelockAddress) + cfgProposer.Quorum = 2 // quorum should change to 2 out of 2 signers timelockMap := map[uint64]*proposalutils.TimelockExecutionContracts{ chainSelector: { Timelock: mcmsState.Timelock, CallProxy: mcmsState.CallProxy, }, } + cfgCanceller := proposalutils.SingleGroupMCMS(t) + cfgBypasser := proposalutils.SingleGroupMCMS(t) + cfgBypasser.Signers = append(cfgBypasser.Signers, timelockAddress) + cfgBypasser.Signers = append(cfgBypasser.Signers, mcmsState.ProposerMcm.Address()) + cfgBypasser.Quorum = 3 // quorum should change to 3 out of 3 signers // Set config on all 3 MCMS contracts - changesetsToApply := tc.changeSets(mcmsState, chainSelector, cfg) + changesetsToApply := tc.changeSets(mcmsState, chainSelector, cfgProposer, cfgCanceller, cfgBypasser) _, err = commonchangeset.ApplyChangesets(t, env, timelockMap, changesetsToApply) require.NoError(t, err) // Check new State - expected := cfg.ToRawConfig() + expected := cfgProposer.ToRawConfig() opts := &bind.CallOpts{Context: ctx} newConf, err := mcmsState.ProposerMcm.GetConfig(opts) require.NoError(t, err) require.Equal(t, expected, newConf) + expected = cfgBypasser.ToRawConfig() newConf, err = mcmsState.BypasserMcm.GetConfig(opts) require.NoError(t, err) require.Equal(t, expected, newConf) + expected = cfgCanceller.ToRawConfig() newConf, err = mcmsState.CancellerMcm.GetConfig(opts) require.NoError(t, err) require.Equal(t, expected, newConf) From 0f4cbf369a6f66423b89a2ffdd68add367691c1e Mon Sep 17 00:00:00 2001 From: Pablo Date: Mon, 16 Dec 2024 15:06:00 -0600 Subject: [PATCH 6/6] fix: pass context and add info logs for success --- .../common/changeset/set_config_mcms.go | 44 ++++++++++--------- .../common/changeset/set_config_mcms_test.go | 32 +++++++------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/deployment/common/changeset/set_config_mcms.go b/deployment/common/changeset/set_config_mcms.go index a0c80f41f0b..3ba5d2db4b6 100644 --- a/deployment/common/changeset/set_config_mcms.go +++ b/deployment/common/changeset/set_config_mcms.go @@ -1,6 +1,7 @@ package changeset import ( + "context" "errors" "fmt" "math/big" @@ -14,6 +15,8 @@ import ( "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" chain_selectors "github.com/smartcontractkit/chain-selectors" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -24,19 +27,19 @@ type ConfigPerRole struct { Canceller config.Config Bypasser config.Config } -type ProposalConfig struct { +type TimelockConfig struct { MinDelay time.Duration // delay for timelock worker to execute the transfers. } -type SetConfigParams struct { +type MCMSConfig struct { ConfigsPerChain map[uint64]ConfigPerRole - ProposalConfig *ProposalConfig + ProposalConfig *TimelockConfig } -var _ deployment.ChangeSet[SetConfigParams] = SetConfigMCMS +var _ deployment.ChangeSet[MCMSConfig] = SetConfigMCMS -// Validate checks that the SetConfigParams is valid -func (cfg SetConfigParams) Validate(e deployment.Environment, selectors []uint64) error { +// Validate checks that the MCMSConfig is valid +func (cfg MCMSConfig) Validate(e deployment.Environment, selectors []uint64) error { if len(cfg.ConfigsPerChain) == 0 { return errors.New("no chain configs provided") } @@ -75,12 +78,13 @@ func (cfg SetConfigParams) Validate(e deployment.Environment, selectors []uint64 } // setConfigOrTxData executes set config tx or gets the tx data for the MCMS proposal -func setConfigOrTxData(chain deployment.Chain, cfg config.Config, contract *gethwrappers.ManyChainMultiSig, useMCMS bool) (*types.Transaction, error) { +func setConfigOrTxData(ctx context.Context, lggr logger.Logger, chain deployment.Chain, cfg config.Config, contract *gethwrappers.ManyChainMultiSig, useMCMS bool) (*types.Transaction, error) { groupQuorums, groupParents, signerAddresses, signerGroups := cfg.ExtractSetConfigInputs() opts := deployment.SimTransactOpts() if !useMCMS { opts = chain.DeployerKey } + opts.Context = ctx tx, err := contract.SetConfig(opts, signerAddresses, signerGroups, groupQuorums, groupParents, false) if err != nil { return nil, err @@ -90,6 +94,7 @@ func setConfigOrTxData(chain deployment.Chain, cfg config.Config, contract *geth if err != nil { return nil, err } + lggr.Infow("SetConfigMCMS tx confirmed", "txHash", tx.Hash().Hex()) } return tx, nil } @@ -101,19 +106,19 @@ type setConfigTxs struct { } // setConfigPerRole sets the configuration for each of the MCMS contract roles on the mcmsState. -func setConfigPerRole(chain deployment.Chain, cfg ConfigPerRole, mcmsState *MCMSWithTimelockState, useMCMS bool) (setConfigTxs, error) { +func setConfigPerRole(ctx context.Context, lggr logger.Logger, chain deployment.Chain, cfg ConfigPerRole, mcmsState *MCMSWithTimelockState, useMCMS bool) (setConfigTxs, error) { // Proposer set config - proposerTx, err := setConfigOrTxData(chain, cfg.Proposer, mcmsState.ProposerMcm, useMCMS) + proposerTx, err := setConfigOrTxData(ctx, lggr, chain, cfg.Proposer, mcmsState.ProposerMcm, useMCMS) if err != nil { return setConfigTxs{}, err } // Canceller set config - cancellerTx, err := setConfigOrTxData(chain, cfg.Canceller, mcmsState.CancellerMcm, useMCMS) + cancellerTx, err := setConfigOrTxData(ctx, lggr, chain, cfg.Canceller, mcmsState.CancellerMcm, useMCMS) if err != nil { return setConfigTxs{}, err } // Bypasser set config - bypasserTx, err := setConfigOrTxData(chain, cfg.Bypasser, mcmsState.BypasserMcm, useMCMS) + bypasserTx, err := setConfigOrTxData(ctx, lggr, chain, cfg.Bypasser, mcmsState.BypasserMcm, useMCMS) if err != nil { return setConfigTxs{}, err } @@ -152,8 +157,10 @@ func addTxsToProposalBatch(setConfigTxsChain setConfigTxs, chainSelector uint64, } // SetConfigMCMS sets the configuration of the MCMS contract on the chain identified by the chainSelector. -func SetConfigMCMS(e deployment.Environment, cfg SetConfigParams) (deployment.ChangesetOutput, error) { +func SetConfigMCMS(e deployment.Environment, cfg MCMSConfig) (deployment.ChangesetOutput, error) { selectors := []uint64{} + lggr := e.Logger + ctx := e.GetContext() for chainSelector := range cfg.ConfigsPerChain { selectors = append(selectors, chainSelector) } @@ -173,17 +180,11 @@ func SetConfigMCMS(e deployment.Environment, cfg SetConfigParams) (deployment.Ch } for chainSelector, c := range cfg.ConfigsPerChain { - chain, ok := e.Chains[chainSelector] - if !ok { - return deployment.ChangesetOutput{}, errors.New("chain not found in environment") - } - state, ok := mcmsStatePerChain[chainSelector] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("MCMS state not found for chain selector: %d", chainSelector) - } + chain := e.Chains[chainSelector] + state := mcmsStatePerChain[chainSelector] timelocksPerChain[chainSelector] = state.Timelock.Address() proposerMcmsPerChain[chainSelector] = state.ProposerMcm - setConfigTxsChain, err := setConfigPerRole(chain, c, state, useMCMS) + setConfigTxsChain, err := setConfigPerRole(ctx, lggr, chain, c, state, useMCMS) if err != nil { return deployment.ChangesetOutput{}, err } @@ -200,6 +201,7 @@ func SetConfigMCMS(e deployment.Environment, cfg SetConfigParams) (deployment.Ch if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w", err) } + lggr.Infow("SetConfigMCMS proposal created", "proposal", proposal) return deployment.ChangesetOutput{Proposals: []timelock.MCMSWithTimelockProposal{*proposal}}, nil } diff --git a/deployment/common/changeset/set_config_mcms_test.go b/deployment/common/changeset/set_config_mcms_test.go index c5c5a8377a7..7220bdd755a 100644 --- a/deployment/common/changeset/set_config_mcms_test.go +++ b/deployment/common/changeset/set_config_mcms_test.go @@ -66,7 +66,7 @@ func TestSetConfigMCMSVariants(t *testing.T) { return []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), - Config: commonchangeset.SetConfigParams{ + Config: commonchangeset.MCMSConfig{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSel: { Proposer: cfgProp, @@ -93,8 +93,8 @@ func TestSetConfigMCMSVariants(t *testing.T) { }, { Changeset: commonchangeset.WrapChangeSet(commonchangeset.SetConfigMCMS), - Config: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ + Config: commonchangeset.MCMSConfig{ + ProposalConfig: &commonchangeset.TimelockConfig{ MinDelay: 0, }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ @@ -184,13 +184,13 @@ func TestValidate(t *testing.T) { require.NoError(t, err) tests := []struct { name string - cfg commonchangeset.SetConfigParams + cfg commonchangeset.MCMSConfig errorMsg string }{ { name: "valid config", - cfg: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ + cfg: commonchangeset.MCMSConfig{ + ProposalConfig: &commonchangeset.TimelockConfig{ MinDelay: 0, }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ @@ -204,7 +204,7 @@ func TestValidate(t *testing.T) { }, { name: "valid non mcms config", - cfg: commonchangeset.SetConfigParams{ + cfg: commonchangeset.MCMSConfig{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chainSelector: { Proposer: cfg, @@ -216,14 +216,14 @@ func TestValidate(t *testing.T) { }, { name: "no chain configurations", - cfg: commonchangeset.SetConfigParams{ + cfg: commonchangeset.MCMSConfig{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{}, }, errorMsg: "no chain configs provided", }, { name: "non evm chain", - cfg: commonchangeset.SetConfigParams{ + cfg: commonchangeset.MCMSConfig{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ chain_selectors.APTOS_MAINNET.Selector: { Proposer: cfg, @@ -236,7 +236,7 @@ func TestValidate(t *testing.T) { }, { name: "chain selector not found in environment", - cfg: commonchangeset.SetConfigParams{ + cfg: commonchangeset.MCMSConfig{ ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ 123: { Proposer: cfg, @@ -249,8 +249,8 @@ func TestValidate(t *testing.T) { }, { name: "invalid proposer config", - cfg: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ + cfg: commonchangeset.MCMSConfig{ + ProposalConfig: &commonchangeset.TimelockConfig{ MinDelay: 0, }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ @@ -265,8 +265,8 @@ func TestValidate(t *testing.T) { }, { name: "invalid canceller config", - cfg: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ + cfg: commonchangeset.MCMSConfig{ + ProposalConfig: &commonchangeset.TimelockConfig{ MinDelay: 0, }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{ @@ -281,8 +281,8 @@ func TestValidate(t *testing.T) { }, { name: "invalid bypasser config", - cfg: commonchangeset.SetConfigParams{ - ProposalConfig: &commonchangeset.ProposalConfig{ + cfg: commonchangeset.MCMSConfig{ + ProposalConfig: &commonchangeset.TimelockConfig{ MinDelay: 0, }, ConfigsPerChain: map[uint64]commonchangeset.ConfigPerRole{