Skip to content

Commit

Permalink
fix(mcms): update keystones changesets to new MCMS (#16443)
Browse files Browse the repository at this point in the history
Update the existing keystone changesets to use the [new MCMS library](https://github.com/smartcontractkit/mcms) types. We want to eventually remove the legacy MCMS type.

**Do not be alarm by the amount of file changes, all the changes are kind of the same**
- Update type `timelock.BatchChainOperation` -> `mcmstypes.BatchOperation`
- use `out.MCMSTimelockProposals` instead of `out.Proposals`
- Update type `Batch` -> `Transactions`

There will be breaking changes on the CLD side due to the types being change, i have an [accompany PR](smartcontractkit/chainlink-deployments#774) for that so it goes in together.

JIRA: https://smartcontract-it.atlassian.net/browse/DPA-1476
  • Loading branch information
graham-chainlink authored Feb 19, 2025
1 parent 30fa644 commit 66099a4
Show file tree
Hide file tree
Showing 27 changed files with 273 additions and 205 deletions.
17 changes: 14 additions & 3 deletions deployment/common/changeset/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,25 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelockContractsPe
if out.MCMSTimelockProposals != nil {
for _, prop := range out.MCMSTimelockProposals {
mcmProp := proposalutils.SignMCMSTimelockProposal(t, e, &prop)
proposalutils.ExecuteMCMSProposalV2(t, e, mcmProp)
proposalutils.ExecuteMCMSTimelockProposalV2(t, e, &prop)
// return the error so devs can ensure expected reversions
err = proposalutils.ExecuteMCMSProposalV2(t, e, mcmProp)
if err != nil {
return deployment.Environment{}, err
}
err = proposalutils.ExecuteMCMSTimelockProposalV2(t, e, &prop)
if err != nil {
return deployment.Environment{}, err
}
}
}
if out.MCMSProposals != nil {
for _, prop := range out.MCMSProposals {
p := proposalutils.SignMCMSProposal(t, e, &prop)
proposalutils.ExecuteMCMSProposalV2(t, e, p)
// return the error so devs can ensure expected reversions
err = proposalutils.ExecuteMCMSProposalV2(t, e, p)
if err != nil {
return deployment.Environment{}, err
}
}
}
currentEnv = deployment.Environment{
Expand Down
33 changes: 25 additions & 8 deletions deployment/common/proposalutils/mcms_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package proposalutils

import (
"crypto/ecdsa"
"fmt"
"math/big"
"testing"
"time"
Expand Down Expand Up @@ -182,7 +183,7 @@ func SignMCMSProposal(t *testing.T, env deployment.Environment, proposal *mcmsli
}

// ExecuteMCMSProposalV2 - Executes an MCMS proposal on a chain. For timelock proposal, use ExecuteMCMSTimelockProposalV2 instead.
func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *mcmslib.Proposal) {
func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *mcmslib.Proposal) error {
t.Log("Executing proposal")

encoders, err := proposal.GetEncoders()
Expand Down Expand Up @@ -226,7 +227,9 @@ func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *m
for chainSelector := range executorsMap {
t.Logf("[ExecuteMCMSProposalV2] Setting root on chain %d...", chainSelector)
root, err := executable.SetRoot(env.GetContext(), chainSelector)
require.NoError(t, deployment.MaybeDataErr(err), "[ExecuteMCMSProposalV2] SetRoot failed")
if err != nil {
return fmt.Errorf("[ExecuteMCMSProposalV2] SetRoot failed: %w", err)
}

family, err := chainsel.GetSelectorFamily(uint64(chainSelector))
require.NoError(t, err)
Expand All @@ -237,15 +240,19 @@ func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *m
evmTransaction := root.RawTransaction.(*gethtypes.Transaction)
t.Logf("[ExecuteMCMSProposalV2] SetRoot EVM tx hash: %s", evmTransaction.Hash().String())
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)
if err != nil {
return fmt.Errorf("[ExecuteMCMSProposalV2] Confirm failed: %w", err)
}
}
}

// execute each operation sequentially
for i, op := range proposal.Operations {
t.Logf("[ExecuteMCMSProposalV2] Executing operation index=%d on chain %d...", i, uint64(op.ChainSelector))
result, err := executable.Execute(env.GetContext(), i)
require.NoError(t, err)
if err != nil {
return fmt.Errorf("[ExecuteMCMSProposalV2] Execute failed: %w", err)
}

family, err := chainsel.GetSelectorFamily(uint64(op.ChainSelector))
require.NoError(t, err)
Expand All @@ -255,14 +262,18 @@ func ExecuteMCMSProposalV2(t *testing.T, env deployment.Environment, proposal *m
evmTransaction := result.RawTransaction.(*gethtypes.Transaction)
t.Logf("[ExecuteMCMSProposalV2] Operation %d EVM tx hash: %s", i, evmTransaction.Hash().String())
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)
if err != nil {
return fmt.Errorf("[ExecuteMCMSProposalV2] Confirm failed: %w", err)
}
}
}

return nil
}

// ExecuteMCMSTimelockProposalV2 - Includes an option to set callProxy to execute the calls through a proxy.
// If the callProxy is not set, the calls will be executed directly to the timelock.
func ExecuteMCMSTimelockProposalV2(t *testing.T, env deployment.Environment, timelockProposal *mcmslib.TimelockProposal, opts ...mcmslib.Option) {
func ExecuteMCMSTimelockProposalV2(t *testing.T, env deployment.Environment, timelockProposal *mcmslib.TimelockProposal, opts ...mcmslib.Option) error {
t.Log("Executing timelock proposal")

// build a "chainSelector => executor" map
Expand Down Expand Up @@ -296,7 +307,9 @@ func ExecuteMCMSTimelockProposalV2(t *testing.T, env deployment.Environment, tim
var tx = mcmstypes.TransactionResult{}
for i, op := range timelockProposal.Operations {
tx, err = timelockExecutable.Execute(env.GetContext(), i, opts...)
require.NoError(t, err)
if err != nil {
return fmt.Errorf("[ExecuteMCMSTimelockProposalV2] Execute failed: %w", err)
}

family, err := chainsel.GetSelectorFamily(uint64(op.ChainSelector))
require.NoError(t, err)
Expand All @@ -306,9 +319,13 @@ func ExecuteMCMSTimelockProposalV2(t *testing.T, env deployment.Environment, tim
chain := env.Chains[uint64(op.ChainSelector)]
evmTransaction := tx.RawTransaction.(*gethtypes.Transaction)
_, err = chain.Confirm(evmTransaction)
require.NoError(t, err)
if err != nil {
return fmt.Errorf("[ExecuteMCMSTimelockProposalV2] Confirm failed: %w", err)
}
}
}

return nil
}

func SingleGroupTimelockConfig(t *testing.T) commontypes.MCMSWithTimelockConfig {
Expand Down
29 changes: 19 additions & 10 deletions deployment/keystone/changeset/add_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"errors"
"fmt"

gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/mcms"
mcmssdk "github.com/smartcontractkit/mcms/sdk"
mcmstypes "github.com/smartcontractkit/mcms/types"

kcr "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/keystone/generated/capabilities_registry_1_1_0"

Expand Down Expand Up @@ -58,24 +58,33 @@ func AddCapabilities(env deployment.Environment, req *AddCapabilitiesRequest) (d
if ops == nil {
return out, errors.New("expected MCMS operation to be non-nil")
}
timelocksPerChain := map[uint64]gethcommon.Address{
registryChain.Selector: contractSet.Timelock.Address(),
timelocksPerChain := map[uint64]string{
registryChain.Selector: contractSet.Timelock.Address().Hex(),
}
proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{
registryChain.Selector: contractSet.ProposerMcm,
proposerMCMSes := map[uint64]string{
registryChain.Selector: contractSet.ProposerMcm.Address().Hex(),
}
inspector, err := proposalutils.McmsInspectorForChain(env, req.RegistryChainSel)
if err != nil {
return deployment.ChangesetOutput{}, err
}
inspectorPerChain := map[uint64]mcmssdk.Inspector{
req.RegistryChainSel: inspector,
}

proposal, err := proposalutils.BuildProposalFromBatches(
proposal, err := proposalutils.BuildProposalFromBatchesV2(
env.GetContext(),
timelocksPerChain,
proposerMCMSes,
[]timelock.BatchChainOperation{*ops},
inspectorPerChain,
[]mcmstypes.BatchOperation{*ops},
"proposal to add capabilities",
req.MCMSConfig.MinDuration,
)
if err != nil {
return out, fmt.Errorf("failed to build proposal: %w", err)
}
out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal}
out.MCMSTimelockProposals = []mcms.TimelockProposal{*proposal}
}
return out, nil
}
2 changes: 1 addition & 1 deletion deployment/keystone/changeset/add_capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestAddCapabilities(t *testing.T) {
}
csOut, err := changeset.AddCapabilities(te.Env, req)
require.NoError(t, err)
require.Len(t, csOut.Proposals, 1)
require.Len(t, csOut.MCMSTimelockProposals, 1)
require.Nil(t, csOut.AddressBook)

// now apply the changeset such that the proposal is signed and execed
Expand Down
31 changes: 20 additions & 11 deletions deployment/keystone/changeset/add_nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"errors"
"fmt"

gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/mcms"
mcmssdk "github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
Expand Down Expand Up @@ -236,28 +236,37 @@ func AddNodes(env deployment.Environment, req *AddNodesRequest) (deployment.Chan
// create mcms proposal if needed
out := deployment.ChangesetOutput{}
if useMCMS {
if resp.Ops == nil || len(resp.Ops.Batch) == 0 {
if resp.Ops == nil || len(resp.Ops.Transactions) == 0 {
return out, errors.New("expected MCMS operation to be non-nil")
}
timelocksPerChain := map[uint64]gethcommon.Address{
registryChain.Selector: registryChainContracts.Timelock.Address(),
timelocksPerChain := map[uint64]string{
registryChain.Selector: registryChainContracts.Timelock.Address().Hex(),
}
proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{
registryChain.Selector: registryChainContracts.ProposerMcm,
proposerMCMSes := map[uint64]string{
registryChain.Selector: registryChainContracts.ProposerMcm.Address().Hex(),
}
inspector, err := proposalutils.McmsInspectorForChain(env, req.RegistryChainSel)
if err != nil {
return deployment.ChangesetOutput{}, err
}
inspectorPerChain := map[uint64]mcmssdk.Inspector{
req.RegistryChainSel: inspector,
}

proposal, err := proposalutils.BuildProposalFromBatches(
proposal, err := proposalutils.BuildProposalFromBatchesV2(
env.GetContext(),
timelocksPerChain,
proposerMCMSes,
[]timelock.BatchChainOperation{*resp.Ops},
inspectorPerChain,
[]types.BatchOperation{*resp.Ops},
"proposal to add nodes",
req.MCMSConfig.MinDuration,
)
if err != nil {
return out, fmt.Errorf("failed to build proposal: %w", err)
}

out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal} //nolint:staticcheck //SA1019 ignoring deprecated field for compatibility; we don't have tools to generate the new field
out.MCMSTimelockProposals = []mcms.TimelockProposal{*proposal}
}
return out, nil
}
4 changes: 2 additions & 2 deletions deployment/keystone/changeset/add_nodes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,8 @@ func TestAddNodes(t *testing.T) {
tc.checkErr(t, useMCMS, err)
return
}
require.NotNil(t, r.Proposals) //nolint:staticcheck //SA1019 ignoring deprecated field for compatibility; we don't have tools to generate the new field
require.Len(t, r.Proposals, 1) //nolint:staticcheck //SA1019 ignoring deprecated field for compatibility; we don't have tools to generate the new field
require.NotNil(t, r.MCMSTimelockProposals)
require.Len(t, r.MCMSTimelockProposals, 1)
applyErr := applyProposal(
t,
tc.input.te,
Expand Down
29 changes: 19 additions & 10 deletions deployment/keystone/changeset/append_node_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
"github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
Expand Down Expand Up @@ -34,24 +34,33 @@ func AppendNodeCapabilities(env deployment.Environment, req *AppendNodeCapabilit
if r.Ops == nil {
return out, errors.New("expected MCMS operation to be non-nil")
}
timelocksPerChain := map[uint64]common.Address{
c.Chain.Selector: contractSet.Timelock.Address(),
timelocksPerChain := map[uint64]string{
c.Chain.Selector: contractSet.Timelock.Address().Hex(),
}
proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{
c.Chain.Selector: contractSet.ProposerMcm,
proposerMCMSes := map[uint64]string{
c.Chain.Selector: contractSet.ProposerMcm.Address().Hex(),
}
inspector, err := proposalutils.McmsInspectorForChain(env, req.RegistryChainSel)
if err != nil {
return deployment.ChangesetOutput{}, err
}
inspectorPerChain := map[uint64]sdk.Inspector{
req.RegistryChainSel: inspector,
}

proposal, err := proposalutils.BuildProposalFromBatches(
proposal, err := proposalutils.BuildProposalFromBatchesV2(
env.GetContext(),
timelocksPerChain,
proposerMCMSes,
[]timelock.BatchChainOperation{*r.Ops},
inspectorPerChain,
[]types.BatchOperation{*r.Ops},
"proposal to set update node capabilities",
req.MCMSConfig.MinDuration,
)
if err != nil {
return out, fmt.Errorf("failed to build proposal: %w", err)
}
out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal}
out.MCMSTimelockProposals = []mcms.TimelockProposal{*proposal}
}
return out, nil
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func TestAppendNodeCapabilities(t *testing.T) {

csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg)
require.NoError(t, err)
require.Empty(t, csOut.Proposals)
require.Empty(t, csOut.MCMSTimelockProposals)
require.Nil(t, csOut.AddressBook)

validateCapabilityAppends(t, te, newCapabilities)
Expand Down Expand Up @@ -78,9 +78,9 @@ func TestAppendNodeCapabilities(t *testing.T) {

csOut, err := changeset.AppendNodeCapabilities(te.Env, &cfg)
require.NoError(t, err)
require.Len(t, csOut.Proposals, 1)
require.Len(t, csOut.Proposals[0].Transactions, 1)
require.Len(t, csOut.Proposals[0].Transactions[0].Batch, 2) // add capabilities, update nodes
require.Len(t, csOut.MCMSTimelockProposals, 1)
require.Len(t, csOut.MCMSTimelockProposals[0].Operations, 1)
require.Len(t, csOut.MCMSTimelockProposals[0].Operations[0].Transactions, 2) // add capabilities, update nodes
require.Nil(t, csOut.AddressBook)

// now apply the changeset such that the proposal is signed and execed
Expand Down
29 changes: 19 additions & 10 deletions deployment/keystone/changeset/deploy_ocr3.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import (
"io"

"github.com/ethereum/go-ethereum/common"

"github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"
"github.com/smartcontractkit/mcms"
"github.com/smartcontractkit/mcms/sdk"
mcmstypes "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
Expand Down Expand Up @@ -92,24 +92,33 @@ func ConfigureOCR3Contract(env deployment.Environment, cfg ConfigureOCR3Config)
return out, fmt.Errorf("failed to get contract sets: %w", err)
}
contracts := r.ContractSets[cfg.ChainSel]
timelocksPerChain := map[uint64]common.Address{
cfg.ChainSel: contracts.Timelock.Address(),
timelocksPerChain := map[uint64]string{
cfg.ChainSel: contracts.Timelock.Address().Hex(),
}
proposerMCMSes := map[uint64]*gethwrappers.ManyChainMultiSig{
cfg.ChainSel: contracts.ProposerMcm,
proposerMCMSes := map[uint64]string{
cfg.ChainSel: contracts.ProposerMcm.Address().Hex(),
}

proposal, err := proposalutils.BuildProposalFromBatches(
inspector, err := proposalutils.McmsInspectorForChain(env, cfg.ChainSel)
if err != nil {
return deployment.ChangesetOutput{}, err
}
inspectorPerChain := map[uint64]sdk.Inspector{
cfg.ChainSel: inspector,
}
proposal, err := proposalutils.BuildProposalFromBatchesV2(
env.GetContext(),
timelocksPerChain,
proposerMCMSes,
[]timelock.BatchChainOperation{*resp.Ops},
inspectorPerChain,
[]mcmstypes.BatchOperation{*resp.Ops},
"proposal to set OCR3 config",
cfg.MCMSConfig.MinDuration,
)
if err != nil {
return out, fmt.Errorf("failed to build proposal: %w", err)
}
out.Proposals = []timelock.MCMSWithTimelockProposal{*proposal}
out.MCMSTimelockProposals = []mcms.TimelockProposal{*proposal}
}
return out, nil
}
Loading

0 comments on commit 66099a4

Please sign in to comment.