diff --git a/mcms/evm/set-config/operation.go b/mcms/evm/set-config/operation.go index 86974f7..fef3d40 100644 --- a/mcms/evm/set-config/operation.go +++ b/mcms/evm/set-config/operation.go @@ -33,7 +33,6 @@ type OpEVMSetConfigInput struct { } // OpEVMSetConfigMCM sets MCMS config on an EVM MCM contract via the MCMS SDK configurer. -// TODO: this may be removed if we generate operations via operations gen tool. var OpEVMSetConfigMCM = operations.NewOperation( "evm-mcm-set-config", semver.MustParse("1.0.0"), diff --git a/mcms/evm/set-config/sequence.go b/mcms/evm/set-config/sequence.go index 2b5118f..16c5715 100644 --- a/mcms/evm/set-config/sequence.go +++ b/mcms/evm/set-config/sequence.go @@ -3,6 +3,7 @@ package evmsetconfig import ( "fmt" "math/big" + "strconv" "github.com/Masterminds/semver/v3" "github.com/ethereum/go-ethereum/common" @@ -11,75 +12,11 @@ import ( cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" "github.com/smartcontractkit/chainlink-deployments-framework/operations" - mcmsTypes "github.com/smartcontractkit/mcms/types" + mcmstypes "github.com/smartcontractkit/mcms/types" setconfig "github.com/smartcontractkit/cld-changesets/mcms/changesets/set-config" ) -// SeqEVMSetConfigInput is the input for the generic EVM set-config sequence. -type SeqEVMSetConfigInput struct { - ChainSelector uint64 `json:"chainSelector"` - NoSend bool `json:"noSend"` - GasBoostConfig *cldfproposalutils.GasBoostConfig `json:"gasBoostConfig,omitempty"` - Targets []MCMSetConfigTarget `json:"targets"` -} - -// SeqEVMSetConfig sets config on each provided MCM contract via OpEVMSetConfigMCM. -var SeqEVMSetConfig = operations.NewSequence( - "seq-evm-mcm-set-config", - semver.MustParse("1.0.0"), - "Sets MCMS config on one or more MCM contracts", - func(b operations.Bundle, deps cldf_evm.Chain, in SeqEVMSetConfigInput) (sequenceutils.OnChainOutput, error) { - if in.ChainSelector != deps.Selector { - return sequenceutils.OnChainOutput{}, fmt.Errorf("mismatch between inputted chain selector and selector defined within dependencies: %d != %d", in.ChainSelector, deps.Selector) - } - - var outs []EVMCallOutput - if in.NoSend { - outs = make([]EVMCallOutput, 0, len(in.Targets)) - } - - for _, target := range in.Targets { - opReport, err := operations.ExecuteOperation( - b, - OpEVMSetConfigMCM, - deps, - OpEVMSetConfigInput{ - Target: target, - NoSend: in.NoSend, - }, - retrySetConfigWithGasBoost(in.GasBoostConfig), - ) - if err != nil { - return sequenceutils.OnChainOutput{}, err - } - - if !in.NoSend { - continue - } - - out := opReport.Output - out.ContractType = target.ContractType - outs = append(outs, out) - } - - out := sequenceutils.OnChainOutput{} - if !in.NoSend { - return out, nil - } - - batch, err := evmCallOutputsToBatch(in.ChainSelector, outs) - if err != nil { - return sequenceutils.OnChainOutput{}, err - } - if len(batch.Transactions) > 0 { - out.BatchOps = []mcmsTypes.BatchOperation{batch} - } - - return out, nil - }, -) - var seqSetConfig = operations.NewSequence( "seq-evm-set-config-mcms", semver.MustParse("1.0.0"), @@ -102,21 +39,52 @@ func runEVMSetConfig( return sequenceutils.OnChainOutput{}, err } - seqReport, err := operations.ExecuteSequence( - b, - SeqEVMSetConfig, - chain, - SeqEVMSetConfigInput{ - ChainSelector: in.ChainSelector, - NoSend: in.MCMS != nil, - Targets: targets, - }, - ) + useMCMS := in.MCMS != nil + + var outs []EVMCallOutput + if useMCMS { + outs = make([]EVMCallOutput, 0, len(targets)) + } + + for _, target := range targets { + opReport, execErr := operations.ExecuteOperation( + b, + OpEVMSetConfigMCM, + chain, + OpEVMSetConfigInput{ + Target: target, + NoSend: useMCMS, + }, + retrySetConfigWithGasBoost(nil), + operations.WithIdempotencyKey[OpEVMSetConfigInput, cldf_evm.Chain](strconv.FormatUint(chain.Selector, 10)+":"+target.Address.Hex()), + ) + if execErr != nil { + return sequenceutils.OnChainOutput{}, execErr + } + + if !useMCMS { + continue + } + + out := opReport.Output + out.ContractType = target.ContractType + outs = append(outs, out) + } + + out := sequenceutils.OnChainOutput{} + if !useMCMS { + return out, nil + } + + batch, err := evmCallOutputsToBatch(in.ChainSelector, outs) if err != nil { - return sequenceutils.OnChainOutput{}, fmt.Errorf("failed to execute EVM set config sequence: %w", err) + return sequenceutils.OnChainOutput{}, err + } + if len(batch.Transactions) > 0 { + out.BatchOps = []mcmstypes.BatchOperation{batch} } - return seqReport.Output, nil + return out, nil } func setConfigTargets(e cldf.Environment, configs []setconfig.ContractSetConfig) ([]MCMSetConfigTarget, error) { @@ -141,10 +109,10 @@ func setConfigTargets(e cldf.Environment, configs []setconfig.ContractSetConfig) return targets, nil } -func evmCallOutputsToBatch(chainSelector uint64, outs []EVMCallOutput) (mcmsTypes.BatchOperation, error) { - result := mcmsTypes.BatchOperation{ - ChainSelector: mcmsTypes.ChainSelector(chainSelector), - Transactions: []mcmsTypes.Transaction{}, +func evmCallOutputsToBatch(chainSelector uint64, outs []EVMCallOutput) (mcmstypes.BatchOperation, error) { + result := mcmstypes.BatchOperation{ + ChainSelector: mcmstypes.ChainSelector(chainSelector), + Transactions: []mcmstypes.Transaction{}, } for _, out := range outs { @@ -161,7 +129,7 @@ func evmCallOutputsToBatch(chainSelector uint64, outs []EVMCallOutput) (mcmsType []string{}, ) if err != nil { - return mcmsTypes.BatchOperation{}, fmt.Errorf("failed to create batch operation for chain %d: %w", chainSelector, err) + return mcmstypes.BatchOperation{}, fmt.Errorf("failed to create batch operation for chain %d: %w", chainSelector, err) } result.Transactions = append(result.Transactions, batchOperation.Transactions...) } diff --git a/mcms/evm/set-config/sequence_test.go b/mcms/evm/set-config/sequence_test.go index 02f711e..4d6f979 100644 --- a/mcms/evm/set-config/sequence_test.go +++ b/mcms/evm/set-config/sequence_test.go @@ -21,7 +21,6 @@ import ( cldftesthelpers "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils/testhelpers" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/runtime" - "github.com/smartcontractkit/chainlink-deployments-framework/operations" "github.com/smartcontractkit/chainlink-deployments-framework/operations/optest" "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger" mcmsevm "github.com/smartcontractkit/mcms/sdk/evm" @@ -37,15 +36,15 @@ import ( evmreaders "github.com/smartcontractkit/cld-changesets/mcms/evm/readers" ) -func TestSeqEVMSetConfig(t *testing.T) { +func TestRunEVMSetConfig(t *testing.T) { t.Parallel() tests := []struct { - name string - noSend bool + name string + transferToMCMS bool }{ - {name: "direct send", noSend: false}, - {name: "MCMS proposal", noSend: true}, + {name: "direct send", transferToMCMS: false}, + {name: "MCMS proposal", transferToMCMS: true}, } for _, tt := range tests { @@ -57,7 +56,7 @@ func TestSeqEVMSetConfig(t *testing.T) { refs := evmSetConfigRefs(t, rt.Environment(), selector) chain := rt.Environment().BlockChains.EVMChains()[selector] - if tt.noSend { + if tt.transferToMCMS { transferEVMMCMSToTimelock(t, rt, selector, refs) } @@ -73,53 +72,58 @@ func TestSeqEVMSetConfig(t *testing.T) { cancellerCfg.Signers = append(cancellerCfg.Signers, refs.Bypasser) cancellerCfg.Quorum = 2 - targets := []MCMSetConfigTarget{ + targets := []setconfig.ContractSetConfig{ { - Address: refs.Proposer, - Config: proposerCfg, - ContractType: mcmscontracts.ProposerManyChainMultisig, + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.ProposerManyChainMultisig), &semvers.V1_0_0, ""), + Config: proposerCfg, }, { - Address: refs.Bypasser, - Config: bypasserCfg, - ContractType: mcmscontracts.BypasserManyChainMultisig, + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.BypasserManyChainMultisig), &semvers.V1_0_0, ""), + Config: bypasserCfg, }, } - if tt.noSend { - targets = []MCMSetConfigTarget{ + var mcmsInput *cldf.MCMSTimelockProposalInput + if tt.transferToMCMS { + targets = []setconfig.ContractSetConfig{ { - Address: refs.Canceller, - Config: cancellerCfg, - ContractType: mcmscontracts.CancellerManyChainMultisig, + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.CancellerManyChainMultisig), &semvers.V1_0_0, ""), + Config: cancellerCfg, }, } + mcmsInput = &cldf.MCMSTimelockProposalInput{ + TimelockAction: mcmstypes.TimelockActionBypass, + ValidUntil: uint32(time.Now().Add(2 * time.Hour).UTC().Unix()), //nolint:gosec // test timestamp + TimelockDelay: mcmstypes.NewDuration(0), + } } - report, err := operations.ExecuteSequence( + out, err := runEVMSetConfig( rt.Environment().OperationsBundle, - SeqEVMSetConfig, - chain, - SeqEVMSetConfigInput{ + setconfig.Deps{ + BlockChains: rt.Environment().BlockChains, + DataStore: rt.Environment().DataStore, + }, + setconfig.ChainInput{ ChainSelector: selector, - NoSend: tt.noSend, Targets: targets, + MCMS: mcmsInput, }, ) require.NoError(t, err) - if tt.noSend { - require.Len(t, report.Output.BatchOps, 1) - require.Len(t, report.Output.BatchOps[0].Transactions, 1) + if tt.transferToMCMS { + require.Len(t, out.BatchOps, 1) + require.Len(t, out.BatchOps[0].Transactions, 1) require.NoError(t, rt.Exec( - newTimelockProposalTask(report.Output.BatchOps, "set config sequence test"), + newTimelockProposalTask(out.BatchOps, "set config sequence test"), runtime.SignAndExecuteProposalsTask([]*ecdsa.PrivateKey{cldftesthelpers.TestXXXMCMSSigner}), )) } else { - require.Empty(t, report.Output.BatchOps) + require.Empty(t, out.BatchOps) } inspector := mcmsevm.NewInspector(chain.Client) - if tt.noSend { + if tt.transferToMCMS { assertEVMConfigEquals(t, inspector, refs.Canceller, cancellerCfg) } else { assertEVMConfigEquals(t, inspector, refs.Proposer, proposerCfg) @@ -243,22 +247,6 @@ func assertEVMConfigEquals(t *testing.T, inspector *mcmsevm.Inspector, address c require.Equal(t, want.Quorum, got.Quorum) } -func TestSeqEVMSetConfig_chainSelectorMismatch(t *testing.T) { - t.Parallel() - - selector := chainselectors.TEST_90000001.Selector - _, err := operations.ExecuteSequence( - optest.NewBundle(t), - SeqEVMSetConfig, - cldf_evm.Chain{Selector: selector}, - SeqEVMSetConfigInput{ - ChainSelector: chainselectors.TEST_90000002.Selector, - NoSend: true, - }, - ) - require.ErrorContains(t, err, "mismatch between inputted chain selector") -} - func TestSetConfigTargets(t *testing.T) { t.Parallel() diff --git a/mcms/solana/set-config/operation_test.go b/mcms/solana/set-config/operation_test.go index ee1db55..d346aac 100644 --- a/mcms/solana/set-config/operation_test.go +++ b/mcms/solana/set-config/operation_test.go @@ -20,7 +20,7 @@ import ( //nolint:paralleltest // global mcm.SetProgramID state; serialized via soltestutils.PreloadMCMS lock func TestSolanaSetConfig(t *testing.T) { t.Run("operation", testOpSolanaSetConfigMCM) - t.Run("sequence", testSeqSolanaSetConfig) + t.Run("sequence", testRunSolanaSetConfig) } func testOpSolanaSetConfigMCM(t *testing.T) { diff --git a/mcms/solana/set-config/sequence.go b/mcms/solana/set-config/sequence.go index e21c6ba..6fa5fac 100644 --- a/mcms/solana/set-config/sequence.go +++ b/mcms/solana/set-config/sequence.go @@ -2,6 +2,7 @@ package solsetconfig import ( "fmt" + "strconv" solanago "github.com/gagliardetto/solana-go" chainselectors "github.com/smartcontractkit/chain-selectors" @@ -18,54 +19,6 @@ import ( familysolana "github.com/smartcontractkit/cld-changesets/pkg/family/solana" ) -// SeqSolanaSetConfigInput is the input for the generic Solana set-config sequence. -type SeqSolanaSetConfigInput struct { - ChainSelector uint64 `json:"chainSelector"` - NoSend bool `json:"noSend"` - AuthorityAccount solanago.PublicKey `json:"authorityAccount"` - Targets []MCMSetConfigTarget `json:"targets"` -} - -// SeqSolanaSetConfig sets config on each provided Solana MCM account via OpSolanaSetConfigMCM. -// When NoSend is true, one batch operation is returned per target to respect Solana transaction size limits. -var SeqSolanaSetConfig = operations.NewSequence( - "seq-solana-mcm-set-config", - &semvers.V1_0_0, - "Sets MCMS config on one or more Solana MCM accounts", - func(b operations.Bundle, deps cldfsol.Chain, in SeqSolanaSetConfigInput) (sequenceutils.OnChainOutput, error) { - if in.ChainSelector != deps.Selector { - return sequenceutils.OnChainOutput{}, fmt.Errorf("mismatch between inputted chain selector and selector defined within dependencies: %d != %d", in.ChainSelector, deps.Selector) - } - - var batchOps []mcmstypes.BatchOperation - if in.NoSend { - batchOps = make([]mcmstypes.BatchOperation, 0, len(in.Targets)) - } - - for _, target := range in.Targets { - opReport, err := operations.ExecuteOperation( - b, - OpSolanaSetConfigMCM, - deps, - OpSolanaSetConfigInput{ - Target: target, - NoSend: in.NoSend, - AuthorityAccount: in.AuthorityAccount, - }, - ) - if err != nil { - return sequenceutils.OnChainOutput{}, err - } - - if in.NoSend { - batchOps = append(batchOps, opReport.Output.BatchOperation) - } - } - - return sequenceutils.OnChainOutput{BatchOps: batchOps}, nil - }, -) - var seqSetConfig = operations.NewSequence( "seq-solana-set-config-mcms", &semvers.V1_0_0, @@ -112,22 +65,33 @@ func runSolanaSetConfig( return sequenceutils.OnChainOutput{}, err } - seqReport, err := operations.ExecuteSequence( - b, - SeqSolanaSetConfig, - chain, - SeqSolanaSetConfigInput{ - ChainSelector: in.ChainSelector, - NoSend: useMCMS, - AuthorityAccount: authorityAccount, - Targets: targets, - }, - ) - if err != nil { - return sequenceutils.OnChainOutput{}, fmt.Errorf("failed to execute Solana set config sequence: %w", err) + var batchOps []mcmstypes.BatchOperation + if useMCMS { + batchOps = make([]mcmstypes.BatchOperation, 0, len(targets)) + } + + for _, target := range targets { + opReport, execErr := operations.ExecuteOperation( + b, + OpSolanaSetConfigMCM, + chain, + OpSolanaSetConfigInput{ + Target: target, + NoSend: useMCMS, + AuthorityAccount: authorityAccount, + }, + operations.WithIdempotencyKey[OpSolanaSetConfigInput, cldfsol.Chain](strconv.FormatUint(chain.Selector, 10)+":"+target.Address), + ) + if execErr != nil { + return sequenceutils.OnChainOutput{}, execErr + } + + if useMCMS { + batchOps = append(batchOps, opReport.Output.BatchOperation) + } } - return seqReport.Output, nil + return sequenceutils.OnChainOutput{BatchOps: batchOps}, nil } func setConfigTargets(e cldf.Environment, configs []setconfig.ContractSetConfig) ([]MCMSetConfigTarget, error) { diff --git a/mcms/solana/set-config/sequence_test.go b/mcms/solana/set-config/sequence_test.go index f08d9ef..7896e9f 100644 --- a/mcms/solana/set-config/sequence_test.go +++ b/mcms/solana/set-config/sequence_test.go @@ -22,7 +22,6 @@ import ( cldftesthelpers "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils/testhelpers" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment" "github.com/smartcontractkit/chainlink-deployments-framework/engine/test/runtime" - "github.com/smartcontractkit/chainlink-deployments-framework/operations" "github.com/smartcontractkit/chainlink-deployments-framework/operations/optest" "github.com/smartcontractkit/chainlink-deployments-framework/pkg/logger" mcmssolana "github.com/smartcontractkit/mcms/sdk/solana" @@ -42,7 +41,7 @@ import ( ) //nolint:paralleltest // global mcm.SetProgramID state; serialized via soltestutils.PreloadMCMS lock -func testSeqSolanaSetConfig(t *testing.T) { +func testRunSolanaSetConfig(t *testing.T) { tests := []struct { name string noSend bool @@ -59,11 +58,9 @@ func testSeqSolanaSetConfig(t *testing.T) { refs := solanaSetConfigRefs(t, rt.Environment(), selector) fundSolanaSignerPDAs(t, chain, refs) - authorityAccount := solanago.PublicKey{} if tt.noSend { transferSolanaMCMSToTimelock(t, rt, selector) fundSolanaSignerPDAs(t, chain, refs) - authorityAccount = refs.TimelockSigner } proposerCfg := cldftesthelpers.SingleGroupMCMS(t) @@ -74,54 +71,54 @@ func testSeqSolanaSetConfig(t *testing.T) { cancellerCfg.Signers = append(cancellerCfg.Signers, common.HexToAddress("0x0000000000000000000000000000000000000202")) cancellerCfg.Quorum = 2 - bypasserCfg := cldftesthelpers.SingleGroupMCMS(t) - bypasserCfg.Signers = append(bypasserCfg.Signers, common.HexToAddress("0x0000000000000000000000000000000000000303")) - bypasserCfg.Quorum = 2 - - targets := []MCMSetConfigTarget{ + targets := []setconfig.ContractSetConfig{ { - Address: refs.Proposer, - Config: proposerCfg, - ContractType: string(mcmscontracts.ProposerManyChainMultisig), + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.ProposerManyChainMultisig), &semvers.V1_0_0, ""), + Config: proposerCfg, }, { - Address: refs.Canceller, - Config: cancellerCfg, - ContractType: string(mcmscontracts.CancellerManyChainMultisig), + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.CancellerManyChainMultisig), &semvers.V1_0_0, ""), + Config: cancellerCfg, }, } + var mcmsInput *cldf.MCMSTimelockProposalInput if tt.noSend { - targets = []MCMSetConfigTarget{ + targets = []setconfig.ContractSetConfig{ { - Address: refs.Canceller, - Config: cancellerCfg, - ContractType: string(mcmscontracts.CancellerManyChainMultisig), + Ref: refkey.New(selector, datastore.ContractType(mcmscontracts.CancellerManyChainMultisig), &semvers.V1_0_0, ""), + Config: cancellerCfg, }, } + mcmsInput = &cldf.MCMSTimelockProposalInput{ + TimelockAction: mcmstypes.TimelockActionSchedule, + ValidUntil: uint32(time.Now().Add(2 * time.Hour).UTC().Unix()), //nolint:gosec // test timestamp + TimelockDelay: mcmstypes.NewDuration(time.Second), + } } - report, err := operations.ExecuteSequence( + out, err := runSolanaSetConfig( rt.Environment().OperationsBundle, - SeqSolanaSetConfig, - chain, - SeqSolanaSetConfigInput{ - ChainSelector: selector, - NoSend: tt.noSend, - AuthorityAccount: authorityAccount, - Targets: targets, + setconfig.Deps{ + BlockChains: rt.Environment().BlockChains, + DataStore: rt.Environment().DataStore, + }, + setconfig.ChainInput{ + ChainSelector: selector, + Targets: targets, + MCMS: mcmsInput, }, ) require.NoError(t, err) if tt.noSend { - require.Len(t, report.Output.BatchOps, 1) - require.NotEmpty(t, report.Output.BatchOps[0].Transactions) + require.Len(t, out.BatchOps, 1) + require.NotEmpty(t, out.BatchOps[0].Transactions) require.NoError(t, rt.Exec( - newTimelockProposalTask(report.Output.BatchOps, "solana set config sequence test"), + newTimelockProposalTask(out.BatchOps, "solana set config sequence test"), runtime.SignAndExecuteProposalsTask([]*ecdsa.PrivateKey{cldftesthelpers.TestXXXMCMSSigner}), )) } else { - require.Empty(t, report.Output.BatchOps) + require.Empty(t, out.BatchOps) } inspector := mcmssolana.NewInspector(chain.Client)