From 042b5a5916d64c23f26df58e0a606aac05502779 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Mon, 25 Nov 2024 19:36:16 +0400 Subject: [PATCH 01/10] wip --- deployment/ccip/changeset/accept_ownership.go | 73 +++++++++++++++++++ .../ccip/changeset/accept_ownership_test.go | 7 ++ 2 files changed, 80 insertions(+) create mode 100644 deployment/ccip/changeset/accept_ownership.go create mode 100644 deployment/ccip/changeset/accept_ownership_test.go diff --git a/deployment/ccip/changeset/accept_ownership.go b/deployment/ccip/changeset/accept_ownership.go new file mode 100644 index 00000000000..be076502608 --- /dev/null +++ b/deployment/ccip/changeset/accept_ownership.go @@ -0,0 +1,73 @@ +package changeset + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink/deployment" +) + +type ownershipAcceptor interface { + AcceptOwnership(opts *bind.TransactOpts) (*gethtypes.Transaction, error) + Address() common.Address +} + +// NewAcceptOwnershipChangeset creates a changeset that accepts ownership of all the +// chain contracts on the given chainSelector. +// New chain contracts are: +// * OnRamp +// * OffRamp +// * FeeQuoter +// * NonceManager +// * RMNRemote +func NewAcceptOwnershipChangeset( + e deployment.Environment, + state CCIPOnChainState, + chainSelector uint64, +) (deployment.ChangesetOutput, error) { + chainState, ok := state.Chains[chainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("desired chain selector %d not found in onchain state", chainSelector) + } + + var batch timelock.BatchChainOperation + for _, contract := range []ownershipAcceptor{ + chainState.OnRamp, + chainState.OffRamp, + chainState.FeeQuoter, + chainState.NonceManager, + chainState.RMNRemote, + } { + acceptOwnershipTx, err := contract.AcceptOwnership(deployment.SimTransactOpts()) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) + } + + batch.Batch = append(batch.Batch, mcms.Operation{ + To: contract.Address(), + Data: acceptOwnershipTx.Data(), + Value: big.NewInt(0), + }) + } + + proposal, err := BuildProposalFromBatches( + state, + []timelock.BatchChainOperation{batch}, + "Accept ownership of all CCIP chain contracts", + time.Duration(0), // minDelay + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batch: %+v", err, batch) + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil +} diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go new file mode 100644 index 00000000000..224c0a88f49 --- /dev/null +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -0,0 +1,7 @@ +package changeset + +import "testing" + +func Test_AcceptOwnership(t *testing.T) { + t.Parallel() +} From 7a55173523961c8f62791f70aed025bf677efa2f Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 26 Nov 2024 00:46:59 +0400 Subject: [PATCH 02/10] add test --- deployment/ccip/changeset/accept_ownership.go | 17 +- .../ccip/changeset/accept_ownership_test.go | 163 +++++++++++++++++- 2 files changed, 172 insertions(+), 8 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership.go b/deployment/ccip/changeset/accept_ownership.go index be076502608..6a9e6e20ca6 100644 --- a/deployment/ccip/changeset/accept_ownership.go +++ b/deployment/ccip/changeset/accept_ownership.go @@ -19,6 +19,14 @@ type ownershipAcceptor interface { Address() common.Address } +type AcceptOwnershipConfig struct { + State CCIPOnChainState + ChainSelector uint64 +} + +// type assertion - comply with deployment.ChangeSet interface +var _ deployment.ChangeSet[AcceptOwnershipConfig] = NewAcceptOwnershipChangeset + // NewAcceptOwnershipChangeset creates a changeset that accepts ownership of all the // chain contracts on the given chainSelector. // New chain contracts are: @@ -29,12 +37,11 @@ type ownershipAcceptor interface { // * RMNRemote func NewAcceptOwnershipChangeset( e deployment.Environment, - state CCIPOnChainState, - chainSelector uint64, + cfg AcceptOwnershipConfig, ) (deployment.ChangesetOutput, error) { - chainState, ok := state.Chains[chainSelector] + chainState, ok := cfg.State.Chains[cfg.ChainSelector] if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("desired chain selector %d not found in onchain state", chainSelector) + return deployment.ChangesetOutput{}, fmt.Errorf("desired chain selector %d not found in onchain state", cfg.ChainSelector) } var batch timelock.BatchChainOperation @@ -58,7 +65,7 @@ func NewAcceptOwnershipChangeset( } proposal, err := BuildProposalFromBatches( - state, + cfg.State, []timelock.BatchChainOperation{batch}, "Accept ownership of all CCIP chain contracts", time.Duration(0), // minDelay diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 224c0a88f49..0b48d9f0577 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -1,7 +1,164 @@ package changeset -import "testing" +import ( + "math/big" + "testing" -func Test_AcceptOwnership(t *testing.T) { - t.Parallel() + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink/deployment" + commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" + + "github.com/smartcontractkit/chainlink/v2/core/logger" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" +) + +func Test_NewAcceptOwnershipChangeset(t *testing.T) { + ctx := tests.Context(t) + e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 2, 4) + state, err := LoadOnchainState(e.Env) + require.NoError(t, err) + + allChains := maps.Keys(e.Env.Chains) + source := allChains[0] + dest := allChains[1] + + newAddresses := deployment.NewMemoryAddressBook() + err = deployPrerequisiteChainContracts(e.Env, newAddresses, allChains, nil) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(newAddresses)) + + mcmConfig := commontypes.MCMSWithTimelockConfig{ + Canceller: commonchangeset.SingleGroupMCMS(t), + Bypasser: commonchangeset.SingleGroupMCMS(t), + Proposer: commonchangeset.SingleGroupMCMS(t), + TimelockExecutors: e.Env.AllDeployerKeys(), + TimelockMinDelay: big.NewInt(0), + } + out, err := commonchangeset.DeployMCMSWithTimelock(e.Env, map[uint64]commontypes.MCMSWithTimelockConfig{ + source: mcmConfig, + dest: mcmConfig, + }) + require.NoError(t, err) + require.NoError(t, e.Env.ExistingAddresses.Merge(out.AddressBook)) + newAddresses = deployment.NewMemoryAddressBook() + tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) + ocrParams := make(map[uint64]CCIPOCRParams) + for _, chain := range allChains { + ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil, nil) + } + err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ + HomeChainSel: e.HomeChainSel, + FeedChainSel: e.FeedChainSel, + ChainsToDeploy: allChains, + TokenConfig: tokenConfig, + OCRSecrets: deployment.XXXGenerateTestOCRSecrets(), + OCRParams: ocrParams, + }) + require.NoError(t, err) + + // at this point we have the initial deploys done, now we need to transfer ownership + // to the timelock contract + state, err = LoadOnchainState(e.Env) + require.NoError(t, err) + + for _, chain := range allChains { + deployerBalance, err := e.Env.Chains[chain].Client.BalanceAt( + ctx, e.Env.Chains[chain].DeployerKey.From, nil) + require.NoError(t, err) + t.Log("deployer balance is:", assets.NewWei(deployerBalance).String()) + + // OnRamp + tx, err := state.Chains[chain].OnRamp.TransferOwnership( + e.Env.Chains[source].DeployerKey, + state.Chains[source].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + + // OffRamp + tx, err = state.Chains[chain].OffRamp.TransferOwnership( + e.Env.Chains[source].DeployerKey, + state.Chains[source].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + + // FeeQuoter + tx, err = state.Chains[chain].FeeQuoter.TransferOwnership( + e.Env.Chains[source].DeployerKey, + state.Chains[source].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + + // NonceManager + tx, err = state.Chains[chain].NonceManager.TransferOwnership( + e.Env.Chains[source].DeployerKey, + state.Chains[source].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + + // RMNRemote + tx, err = state.Chains[chain].RMNRemote.TransferOwnership( + e.Env.Chains[source].DeployerKey, + state.Chains[source].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + } + + // now we can accept ownership via the proposal. + _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ + source: state.Chains[source].Timelock, + dest: state.Chains[dest].Timelock, + }, []commonchangeset.ChangesetApplication{ + { + Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), + Config: AcceptOwnershipConfig{State: state, ChainSelector: source}, + }, + { + Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), + Config: AcceptOwnershipConfig{State: state, ChainSelector: dest}, + }, + }) + require.NoError(t, err) + + // check that the ownership has been transferred correctly + for _, chain := range allChains { + onRampOwner, err := state.Chains[chain].OnRamp.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[source].Timelock.Address(), onRampOwner) + + offRampOwner, err := state.Chains[chain].OffRamp.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[source].Timelock.Address(), offRampOwner) + + feeQuoterOwner, err := state.Chains[chain].FeeQuoter.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[source].Timelock.Address(), feeQuoterOwner) + + nonceManagerOwner, err := state.Chains[chain].NonceManager.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[source].Timelock.Address(), nonceManagerOwner) + + rmnRemoteOwner, err := state.Chains[chain].RMNRemote.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[source].Timelock.Address(), rmnRemoteOwner) + } } From 387d972d901c43d41f16201e386aaa051e251f7b Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 26 Nov 2024 01:20:43 +0400 Subject: [PATCH 03/10] make the proposal multichain --- deployment/ccip/changeset/accept_ownership.go | 62 ++++++++++++------- .../ccip/changeset/accept_ownership_test.go | 36 +++++------ deployment/common/changeset/internal/mcms.go | 2 +- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership.go b/deployment/ccip/changeset/accept_ownership.go index 6a9e6e20ca6..dc34d7110da 100644 --- a/deployment/ccip/changeset/accept_ownership.go +++ b/deployment/ccip/changeset/accept_ownership.go @@ -20,15 +20,15 @@ type ownershipAcceptor interface { } type AcceptOwnershipConfig struct { - State CCIPOnChainState - ChainSelector uint64 + State CCIPOnChainState + ChainSelectors []uint64 } // type assertion - comply with deployment.ChangeSet interface var _ deployment.ChangeSet[AcceptOwnershipConfig] = NewAcceptOwnershipChangeset // NewAcceptOwnershipChangeset creates a changeset that accepts ownership of all the -// chain contracts on the given chainSelector. +// ccip chain contracts deployed on the given chain selectors. // New chain contracts are: // * OnRamp // * OffRamp @@ -39,12 +39,42 @@ func NewAcceptOwnershipChangeset( e deployment.Environment, cfg AcceptOwnershipConfig, ) (deployment.ChangesetOutput, error) { - chainState, ok := cfg.State.Chains[cfg.ChainSelector] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("desired chain selector %d not found in onchain state", cfg.ChainSelector) + // gen one batch per chain + var batches []timelock.BatchChainOperation + for _, chainSelector := range cfg.ChainSelectors { + chainState, ok := cfg.State.Chains[chainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found in state", chainSelector) + } + + ops, err := genAcceptProposalBatch(chainState) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership batch for chain %d: %w", + chainSelector, err) + } + + batches = append(batches, timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(chainSelector), + Batch: ops, + }) } - var batch timelock.BatchChainOperation + proposal, err := BuildProposalFromBatches( + cfg.State, + batches, + "Accept ownership of all CCIP chain contracts", + time.Duration(0), // minDelay + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batches: %+v", err, batches) + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil +} + +func genAcceptProposalBatch(chainState CCIPChainState) (ops []mcms.Operation, err error) { for _, contract := range []ownershipAcceptor{ chainState.OnRamp, chainState.OffRamp, @@ -54,27 +84,15 @@ func NewAcceptOwnershipChangeset( } { acceptOwnershipTx, err := contract.AcceptOwnership(deployment.SimTransactOpts()) if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) + return nil, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) } - batch.Batch = append(batch.Batch, mcms.Operation{ + ops = append(ops, mcms.Operation{ To: contract.Address(), Data: acceptOwnershipTx.Data(), Value: big.NewInt(0), }) } - proposal, err := BuildProposalFromBatches( - cfg.State, - []timelock.BatchChainOperation{batch}, - "Accept ownership of all CCIP chain contracts", - time.Duration(0), // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batch: %+v", err, batch) - } - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, - }, nil + return ops, nil } diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 0b48d9f0577..c7437b0e88c 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -74,42 +74,42 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { // OnRamp tx, err := state.Chains[chain].OnRamp.TransferOwnership( - e.Env.Chains[source].DeployerKey, - state.Chains[source].Timelock.Address(), + e.Env.Chains[chain].DeployerKey, + state.Chains[chain].Timelock.Address(), ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) require.NoError(t, err) // OffRamp tx, err = state.Chains[chain].OffRamp.TransferOwnership( - e.Env.Chains[source].DeployerKey, - state.Chains[source].Timelock.Address(), + e.Env.Chains[chain].DeployerKey, + state.Chains[chain].Timelock.Address(), ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) require.NoError(t, err) // FeeQuoter tx, err = state.Chains[chain].FeeQuoter.TransferOwnership( - e.Env.Chains[source].DeployerKey, - state.Chains[source].Timelock.Address(), + e.Env.Chains[chain].DeployerKey, + state.Chains[chain].Timelock.Address(), ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) require.NoError(t, err) // NonceManager tx, err = state.Chains[chain].NonceManager.TransferOwnership( - e.Env.Chains[source].DeployerKey, - state.Chains[source].Timelock.Address(), + e.Env.Chains[chain].DeployerKey, + state.Chains[chain].Timelock.Address(), ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) require.NoError(t, err) // RMNRemote tx, err = state.Chains[chain].RMNRemote.TransferOwnership( - e.Env.Chains[source].DeployerKey, - state.Chains[source].Timelock.Address(), + e.Env.Chains[chain].DeployerKey, + state.Chains[chain].Timelock.Address(), ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) require.NoError(t, err) } @@ -120,11 +120,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { }, []commonchangeset.ChangesetApplication{ { Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{State: state, ChainSelector: source}, - }, - { - Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{State: state, ChainSelector: dest}, + Config: AcceptOwnershipConfig{State: state, ChainSelectors: allChains}, }, }) require.NoError(t, err) diff --git a/deployment/common/changeset/internal/mcms.go b/deployment/common/changeset/internal/mcms.go index 1e2fb958aae..5694f83786b 100644 --- a/deployment/common/changeset/internal/mcms.go +++ b/deployment/common/changeset/internal/mcms.go @@ -95,7 +95,7 @@ func DeployMCMSWithTimelockContracts( return nil, err } - timelock, err := deployment.DeployContract[*owner_helpers.RBACTimelock](lggr, chain, ab, + timelock, err := deployment.DeployContract(lggr, chain, ab, func(chain deployment.Chain) deployment.ContractDeploy[*owner_helpers.RBACTimelock] { timelock, tx2, cc, err2 := owner_helpers.DeployRBACTimelock( chain.DeployerKey, From ac48a1e61ef516da30a77d175656986f129d429f Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 26 Nov 2024 11:22:49 +0400 Subject: [PATCH 04/10] add transfer ownership changeset use these changesets in other tests and axe duplicate code --- deployment/ccip/changeset/accept_ownership.go | 65 ++++++++-- .../ccip/changeset/accept_ownership_test.go | 115 ++++++------------ .../ccip/changeset/active_candidate_test.go | 36 ++++-- deployment/ccip/changeset/add_chain_test.go | 74 ++++------- deployment/ccip/changeset/ownership.go | 37 ------ deployment/ccip/changeset/propose.go | 67 ---------- .../ccip/changeset/transfer_ownership.go | 95 +++++++++++++++ 7 files changed, 249 insertions(+), 240 deletions(-) delete mode 100644 deployment/ccip/changeset/ownership.go create mode 100644 deployment/ccip/changeset/transfer_ownership.go diff --git a/deployment/ccip/changeset/accept_ownership.go b/deployment/ccip/changeset/accept_ownership.go index dc34d7110da..f43b75549cf 100644 --- a/deployment/ccip/changeset/accept_ownership.go +++ b/deployment/ccip/changeset/accept_ownership.go @@ -20,26 +20,40 @@ type ownershipAcceptor interface { } type AcceptOwnershipConfig struct { - State CCIPOnChainState - ChainSelectors []uint64 + State CCIPOnChainState + ChainSelectors []uint64 + HomeChainSelector uint64 } // type assertion - comply with deployment.ChangeSet interface var _ deployment.ChangeSet[AcceptOwnershipConfig] = NewAcceptOwnershipChangeset // NewAcceptOwnershipChangeset creates a changeset that accepts ownership of all the -// ccip chain contracts deployed on the given chain selectors. +// ccip contracts deployed on the given chain selectors. // New chain contracts are: // * OnRamp // * OffRamp // * FeeQuoter // * NonceManager // * RMNRemote +// Home chain contracts are: +// * CCIPHome +// * RMNHome +// * CapabilityRegistry func NewAcceptOwnershipChangeset( e deployment.Environment, cfg AcceptOwnershipConfig, ) (deployment.ChangesetOutput, error) { - // gen one batch per chain + // basic validation + if len(cfg.ChainSelectors) == 0 || cfg.HomeChainSelector == 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("no chain selectors provided") + } + + if len(cfg.State.Chains) == 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("no chains in state") + } + + // gen one batch per chain for the chain contracts var batches []timelock.BatchChainOperation for _, chainSelector := range cfg.ChainSelectors { chainState, ok := cfg.State.Chains[chainSelector] @@ -47,7 +61,7 @@ func NewAcceptOwnershipChangeset( return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found in state", chainSelector) } - ops, err := genAcceptProposalBatch(chainState) + ops, err := genAcceptOwnershipOps(chainState) if err != nil { return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership batch for chain %d: %w", chainSelector, err) @@ -59,10 +73,26 @@ func NewAcceptOwnershipChangeset( }) } + // gen separate batch chain operation for home chain contracts + homeChainState, ok := cfg.State.Chains[cfg.HomeChainSelector] + if !ok { + return deployment.ChangesetOutput{}, fmt.Errorf("home chain %d not found in state", cfg.HomeChainSelector) + } + homeChainOps, err := genHomeChainAcceptOwnershipOps(homeChainState) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership batch for home chain %d: %w", + cfg.HomeChainSelector, err) + } + + batches = append(batches, timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), + Batch: homeChainOps, + }) + proposal, err := BuildProposalFromBatches( cfg.State, batches, - "Accept ownership of all CCIP chain contracts", + "Accept ownership of all CCIP contracts", time.Duration(0), // minDelay ) if err != nil { @@ -74,7 +104,7 @@ func NewAcceptOwnershipChangeset( }, nil } -func genAcceptProposalBatch(chainState CCIPChainState) (ops []mcms.Operation, err error) { +func genAcceptOwnershipOps(chainState CCIPChainState) (ops []mcms.Operation, err error) { for _, contract := range []ownershipAcceptor{ chainState.OnRamp, chainState.OffRamp, @@ -96,3 +126,24 @@ func genAcceptProposalBatch(chainState CCIPChainState) (ops []mcms.Operation, er return ops, nil } + +func genHomeChainAcceptOwnershipOps(homeChainState CCIPChainState) (ops []mcms.Operation, err error) { + for _, contract := range []ownershipAcceptor{ + homeChainState.CapabilityRegistry, + homeChainState.CCIPHome, + homeChainState.RMNHome, + } { + acceptOwnershipTx, err := contract.AcceptOwnership(deployment.SimTransactOpts()) + if err != nil { + return nil, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) + } + + ops = append(ops, mcms.Operation{ + To: contract.Address(), + Data: acceptOwnershipTx.Data(), + Value: big.NewInt(0), + }) + } + + return ops, nil +} diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index c7437b0e88c..b13ac4e93eb 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -10,7 +10,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" - "github.com/smartcontractkit/chainlink/v2/core/chains/evm/assets" "github.com/smartcontractkit/chainlink/v2/core/logger" "github.com/stretchr/testify/require" @@ -66,95 +65,61 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { state, err = LoadOnchainState(e.Env) require.NoError(t, err) - for _, chain := range allChains { - deployerBalance, err := e.Env.Chains[chain].Client.BalanceAt( - ctx, e.Env.Chains[chain].DeployerKey.From, nil) - require.NoError(t, err) - t.Log("deployer balance is:", assets.NewWei(deployerBalance).String()) - - // OnRamp - tx, err := state.Chains[chain].OnRamp.TransferOwnership( - e.Env.Chains[chain].DeployerKey, - state.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) - require.NoError(t, err) - - // OffRamp - tx, err = state.Chains[chain].OffRamp.TransferOwnership( - e.Env.Chains[chain].DeployerKey, - state.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) - require.NoError(t, err) - - // FeeQuoter - tx, err = state.Chains[chain].FeeQuoter.TransferOwnership( - e.Env.Chains[chain].DeployerKey, - state.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) - require.NoError(t, err) - - // NonceManager - tx, err = state.Chains[chain].NonceManager.TransferOwnership( - e.Env.Chains[chain].DeployerKey, - state.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) - require.NoError(t, err) - - // RMNRemote - tx, err = state.Chains[chain].RMNRemote.TransferOwnership( - e.Env.Chains[chain].DeployerKey, - state.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[chain], tx, err) - require.NoError(t, err) - } - - // now we can accept ownership via the proposal. + // compose the transfer ownership and accept ownership changesets _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ source: state.Chains[source].Timelock, dest: state.Chains[dest].Timelock, }, []commonchangeset.ChangesetApplication{ + // note this doesn't have proposals. + { + Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), + Config: TransferOwnershipConfig{ + State: state, + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, + }, + }, + // this has proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. { Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{State: state, ChainSelectors: allChains}, + Config: AcceptOwnershipConfig{ + State: state, + ChainSelectors: allChains, + HomeChainSelector: e.HomeChainSel, + }, }, }) require.NoError(t, err) // check that the ownership has been transferred correctly for _, chain := range allChains { - onRampOwner, err := state.Chains[chain].OnRamp.Owner(&bind.CallOpts{ - Context: ctx, - }) - require.NoError(t, err) - require.Equal(t, state.Chains[source].Timelock.Address(), onRampOwner) - - offRampOwner, err := state.Chains[chain].OffRamp.Owner(&bind.CallOpts{ - Context: ctx, - }) - require.NoError(t, err) - require.Equal(t, state.Chains[source].Timelock.Address(), offRampOwner) - - feeQuoterOwner, err := state.Chains[chain].FeeQuoter.Owner(&bind.CallOpts{ - Context: ctx, - }) - require.NoError(t, err) - require.Equal(t, state.Chains[source].Timelock.Address(), feeQuoterOwner) - - nonceManagerOwner, err := state.Chains[chain].NonceManager.Owner(&bind.CallOpts{ - Context: ctx, - }) - require.NoError(t, err) - require.Equal(t, state.Chains[source].Timelock.Address(), nonceManagerOwner) + for _, contract := range []ownershipTransferrer{ + state.Chains[chain].OnRamp, + state.Chains[chain].OffRamp, + state.Chains[chain].FeeQuoter, + state.Chains[chain].NonceManager, + state.Chains[chain].RMNRemote, + } { + owner, err := contract.Owner(&bind.CallOpts{ + Context: ctx, + }) + require.NoError(t, err) + require.Equal(t, state.Chains[chain].Timelock.Address(), owner) + } + } - rmnRemoteOwner, err := state.Chains[chain].RMNRemote.Owner(&bind.CallOpts{ + // check home chain contracts ownership + homeChainTimelockAddress := state.Chains[e.HomeChainSel].Timelock.Address() + for _, contract := range []ownershipTransferrer{ + state.Chains[e.HomeChainSel].CapabilityRegistry, + state.Chains[e.HomeChainSel].CCIPHome, + state.Chains[e.HomeChainSel].RMNHome, + } { + owner, err := contract.Owner(&bind.CallOpts{ Context: ctx, }) require.NoError(t, err) - require.Equal(t, state.Chains[source].Timelock.Address(), rmnRemoteOwner) + require.Equal(t, homeChainTimelockAddress, owner) } } diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index 40c0240f3db..52ec6c41474 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -4,8 +4,10 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "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" + "golang.org/x/exp/maps" "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" @@ -30,6 +32,7 @@ func TestActiveCandidate(t *testing.T) { e := tenv.Env state, err := LoadOnchainState(tenv.Env) require.NoError(t, err) + allChains := maps.Keys(e.Chains) // Add all lanes require.NoError(t, AddLanesForAll(e, state)) @@ -80,14 +83,33 @@ func TestActiveCandidate(t *testing.T) { //Wait for all exec reports to land ConfirmExecWithSeqNrsForAll(t, e, state, expectedSeqNumExec, startBlocks) - // transfer ownership - TransferAllOwnership(t, state, tenv.HomeChainSel, e) - acceptOwnershipProposal, err := GenerateAcceptOwnershipProposal(state, tenv.HomeChainSel, e.AllChainSelectors()) - require.NoError(t, err) - acceptOwnershipExec := commonchangeset.SignProposal(t, e, acceptOwnershipProposal) - for _, sel := range e.AllChainSelectors() { - commonchangeset.ExecuteProposal(t, e, acceptOwnershipExec, state.Chains[sel].Timelock, sel) + // compose the transfer ownership and accept ownership changesets + timelocks := make(map[uint64]*gethwrappers.RBACTimelock) + for _, chain := range allChains { + timelocks[chain] = state.Chains[chain].Timelock } + _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ + // note this doesn't have proposals. + { + Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), + Config: TransferOwnershipConfig{ + State: state, + ChainSelectors: allChains, + HomeChainSelector: tenv.HomeChainSel, + }, + }, + // this has proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. + { + Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), + Config: AcceptOwnershipConfig{ + State: state, + ChainSelectors: allChains, + HomeChainSelector: tenv.HomeChainSel, + }, + }, + }) + require.NoError(t, err) // Apply the accept ownership proposal to all the chains. err = ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 2) diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 7b5ae9c43d7..1429ed380dd 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" commontypes "github.com/smartcontractkit/chainlink/deployment/common/types" @@ -111,54 +112,33 @@ func TestAddChainInbound(t *testing.T) { state, err = LoadOnchainState(e.Env) require.NoError(t, err) - // Transfer onramp/fq ownership to timelock. - // Enable the new dest on the test router. - for _, source := range initialDeploy { - tx, err := state.Chains[source].OnRamp.TransferOwnership(e.Env.Chains[source].DeployerKey, state.Chains[source].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) - require.NoError(t, err) - tx, err = state.Chains[source].FeeQuoter.TransferOwnership(e.Env.Chains[source].DeployerKey, state.Chains[source].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) - require.NoError(t, err) - tx, err = state.Chains[source].TestRouter.ApplyRampUpdates(e.Env.Chains[source].DeployerKey, []router.RouterOnRamp{ - { - DestChainSelector: newChain, - OnRamp: state.Chains[source].OnRamp.Address(), + // transfer ownership to timelock + _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ + initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, + initialDeploy[1]: state.Chains[initialDeploy[1]].Timelock, + initialDeploy[2]: state.Chains[initialDeploy[2]].Timelock, + }, []commonchangeset.ChangesetApplication{ + // note this doesn't have proposals. + { + Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), + Config: TransferOwnershipConfig{ + State: state, + ChainSelectors: initialDeploy, + HomeChainSelector: e.HomeChainSel, }, - }, nil, nil) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) - require.NoError(t, err) - } - // Transfer CR contract ownership - tx, err := state.Chains[e.HomeChainSel].CapabilityRegistry.TransferOwnership(e.Env.Chains[e.HomeChainSel].DeployerKey, state.Chains[e.HomeChainSel].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[e.HomeChainSel], tx, err) - require.NoError(t, err) - tx, err = state.Chains[e.HomeChainSel].CCIPHome.TransferOwnership(e.Env.Chains[e.HomeChainSel].DeployerKey, state.Chains[e.HomeChainSel].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Env.Chains[e.HomeChainSel], tx, err) - require.NoError(t, err) - - acceptOwnershipProposal, err := GenerateAcceptOwnershipProposal(state, e.HomeChainSel, initialDeploy) - require.NoError(t, err) - acceptOwnershipExec := commonchangeset.SignProposal(t, e.Env, acceptOwnershipProposal) - // Apply the accept ownership proposal to all the chains. - for _, sel := range initialDeploy { - commonchangeset.ExecuteProposal(t, e.Env, acceptOwnershipExec, state.Chains[sel].Timelock, sel) - } - for _, chain := range initialDeploy { - owner, err2 := state.Chains[chain].OnRamp.Owner(nil) - require.NoError(t, err2) - require.Equal(t, state.Chains[chain].Timelock.Address(), owner) - } - cfgOwner, err := state.Chains[e.HomeChainSel].CCIPHome.Owner(nil) - require.NoError(t, err) - crOwner, err := state.Chains[e.HomeChainSel].CapabilityRegistry.Owner(nil) + }, + // this has proposals, ApplyChangesets will sign & execute them. + // in practice, signing and executing are separated processes. + { + Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), + Config: AcceptOwnershipConfig{ + State: state, + ChainSelectors: initialDeploy, + HomeChainSelector: e.HomeChainSel, + }, + }, + }) require.NoError(t, err) - require.Equal(t, state.Chains[e.HomeChainSel].Timelock.Address(), cfgOwner) - require.Equal(t, state.Chains[e.HomeChainSel].Timelock.Address(), crOwner) nodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) require.NoError(t, err) @@ -206,7 +186,7 @@ func TestAddChainInbound(t *testing.T) { OnRamp: common.LeftPadBytes(state.Chains[source].OnRamp.Address().Bytes(), 32), }) } - tx, err = state.Chains[newChain].OffRamp.ApplySourceChainConfigUpdates(e.Env.Chains[newChain].DeployerKey, offRampEnables) + tx, err := state.Chains[newChain].OffRamp.ApplySourceChainConfigUpdates(e.Env.Chains[newChain].DeployerKey, offRampEnables) require.NoError(t, err) _, err = deployment.ConfirmIfNoError(e.Env.Chains[newChain], tx, err) require.NoError(t, err) diff --git a/deployment/ccip/changeset/ownership.go b/deployment/ccip/changeset/ownership.go deleted file mode 100644 index 4287363b8a6..00000000000 --- a/deployment/ccip/changeset/ownership.go +++ /dev/null @@ -1,37 +0,0 @@ -package changeset - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/smartcontractkit/chainlink/deployment" -) - -func TransferAllOwnership(t *testing.T, state CCIPOnChainState, homeCS uint64, e deployment.Environment) { - for _, source := range e.AllChainSelectors() { - if state.Chains[source].OnRamp != nil { - tx, err := state.Chains[source].OnRamp.TransferOwnership(e.Chains[source].DeployerKey, state.Chains[source].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Chains[source], tx, err) - require.NoError(t, err) - } - if state.Chains[source].FeeQuoter != nil { - tx, err := state.Chains[source].FeeQuoter.TransferOwnership(e.Chains[source].DeployerKey, state.Chains[source].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Chains[source], tx, err) - require.NoError(t, err) - } - // TODO: add offramp and commit stores - - } - // Transfer CR contract ownership - tx, err := state.Chains[homeCS].CapabilityRegistry.TransferOwnership(e.Chains[homeCS].DeployerKey, state.Chains[homeCS].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Chains[homeCS], tx, err) - require.NoError(t, err) - tx, err = state.Chains[homeCS].CCIPHome.TransferOwnership(e.Chains[homeCS].DeployerKey, state.Chains[homeCS].Timelock.Address()) - require.NoError(t, err) - _, err = deployment.ConfirmIfNoError(e.Chains[homeCS], tx, err) - require.NoError(t, err) -} diff --git a/deployment/ccip/changeset/propose.go b/deployment/ccip/changeset/propose.go index 1b7d928f414..707f6f7c056 100644 --- a/deployment/ccip/changeset/propose.go +++ b/deployment/ccip/changeset/propose.go @@ -2,81 +2,14 @@ package changeset import ( "fmt" - "math/big" "time" mapset "github.com/deckarep/golang-set/v2" "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - chainsel "github.com/smartcontractkit/chain-selectors" - - "github.com/smartcontractkit/chainlink/deployment" ) -func GenerateAcceptOwnershipProposal( - state CCIPOnChainState, - homeChain uint64, - chains []uint64, -) (*timelock.MCMSWithTimelockProposal, error) { - // TODO: Accept rest of contracts - var batches []timelock.BatchChainOperation - for _, sel := range chains { - chain, _ := chainsel.ChainBySelector(sel) - acceptOnRamp, err := state.Chains[sel].OnRamp.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, err - } - acceptFeeQuoter, err := state.Chains[sel].FeeQuoter.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, err - } - chainSel := mcms.ChainIdentifier(chain.Selector) - batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: chainSel, - Batch: []mcms.Operation{ - { - To: state.Chains[sel].OnRamp.Address(), - Data: acceptOnRamp.Data(), - Value: big.NewInt(0), - }, - { - To: state.Chains[sel].FeeQuoter.Address(), - Data: acceptFeeQuoter.Data(), - Value: big.NewInt(0), - }, - }, - }) - } - - acceptCR, err := state.Chains[homeChain].CapabilityRegistry.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, err - } - acceptCCIPConfig, err := state.Chains[homeChain].CCIPHome.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, err - } - homeChainID := mcms.ChainIdentifier(homeChain) - batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: homeChainID, - Batch: []mcms.Operation{ - { - To: state.Chains[homeChain].CapabilityRegistry.Address(), - Data: acceptCR.Data(), - Value: big.NewInt(0), - }, - { - To: state.Chains[homeChain].CCIPHome.Address(), - Data: acceptCCIPConfig.Data(), - Value: big.NewInt(0), - }, - }, - }) - - return BuildProposalFromBatches(state, batches, "accept ownership operations", 0) -} - func BuildProposalMetadata(state CCIPOnChainState, chains []uint64) (map[mcms.ChainIdentifier]common.Address, map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { tlAddressMap := make(map[mcms.ChainIdentifier]common.Address) metaDataPerChain := make(map[mcms.ChainIdentifier]mcms.ChainMetadata) diff --git a/deployment/ccip/changeset/transfer_ownership.go b/deployment/ccip/changeset/transfer_ownership.go new file mode 100644 index 00000000000..96b6714fa27 --- /dev/null +++ b/deployment/ccip/changeset/transfer_ownership.go @@ -0,0 +1,95 @@ +package changeset + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink/deployment" +) + +type ownershipTransferrer interface { + TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*gethtypes.Transaction, error) + Owner(opts *bind.CallOpts) (common.Address, error) +} + +type TransferOwnershipConfig struct { + State CCIPOnChainState + ChainSelectors []uint64 + HomeChainSelector uint64 +} + +var _ deployment.ChangeSet[TransferOwnershipConfig] = NewTransferOwnershipChangeset + +// NewTransferOwnershipChangeset creates a changeset that transfers ownership of all the +// ccip chain contracts deployed on the given chain selectors. +// New chain contracts are: +// * OnRamp +// * OffRamp +// * FeeQuoter +// * NonceManager +// * RMNRemote +// Home chain contracts are: +// * CCIPHome +// * RMNHome +// * CapabilityRegistry +// This can be composed with NewAcceptOwnershipChangeset in order to fully transfer +// ownership of all the contracts listed above. +func NewTransferOwnershipChangeset( + e deployment.Environment, + cfg TransferOwnershipConfig, +) (deployment.ChangesetOutput, error) { + // basic validation + if len(cfg.ChainSelectors) == 0 || cfg.HomeChainSelector == 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("no chain selectors provided") + } + + if len(cfg.State.Chains) == 0 { + return deployment.ChangesetOutput{}, fmt.Errorf("no chains in state") + } + + // transfer ownership of chain contracts + // these are assumed to be owned by the deployer configured in the given + // environment. + for _, chain := range cfg.ChainSelectors { + for _, contract := range []ownershipTransferrer{ + cfg.State.Chains[chain].OnRamp, + cfg.State.Chains[chain].OffRamp, + cfg.State.Chains[chain].FeeQuoter, + cfg.State.Chains[chain].NonceManager, + cfg.State.Chains[chain].RMNRemote, + } { + tx, err := contract.TransferOwnership( + e.Chains[chain].DeployerKey, + cfg.State.Chains[chain].Timelock.Address(), + ) + _, err = deployment.ConfirmIfNoError(e.Chains[chain], tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + } + + // transfer ownership of home chain contracts + homeChainTimelockAddress := cfg.State.Chains[cfg.HomeChainSelector].Timelock.Address() + for _, contract := range []ownershipTransferrer{ + cfg.State.Chains[cfg.HomeChainSelector].CapabilityRegistry, + cfg.State.Chains[cfg.HomeChainSelector].CCIPHome, + cfg.State.Chains[cfg.HomeChainSelector].RMNHome, + } { + tx, err := contract.TransferOwnership( + e.Chains[cfg.HomeChainSelector].DeployerKey, + homeChainTimelockAddress, + ) + _, err = deployment.ConfirmIfNoError(e.Chains[cfg.HomeChainSelector], tx, err) + if err != nil { + return deployment.ChangesetOutput{}, err + } + } + + // no new addresses or proposals or jobspecs, so changeset output is empty. + // NOTE: onchain state has technically changed for above contracts, maybe that should + // be captured? + return deployment.ChangesetOutput{}, nil +} From bc716e8980d83ec5e967a8188367ee4189dc0cf2 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 26 Nov 2024 11:36:18 +0400 Subject: [PATCH 05/10] fix add_chain_test.go --- deployment/ccip/changeset/add_chain_test.go | 12 ++++++++++++ deployment/ccip/changeset/test_helpers.go | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 1429ed380dd..fa8161842b6 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -112,6 +112,18 @@ func TestAddChainInbound(t *testing.T) { state, err = LoadOnchainState(e.Env) require.NoError(t, err) + // configure the testrouter appropriately on each chain + for _, source := range initialDeploy { + tx, err := state.Chains[source].TestRouter.ApplyRampUpdates(e.Env.Chains[source].DeployerKey, []router.RouterOnRamp{ + { + DestChainSelector: newChain, + OnRamp: state.Chains[source].OnRamp.Address(), + }, + }, nil, nil) + _, err = deployment.ConfirmIfNoError(e.Env.Chains[source], tx, err) + require.NoError(t, err) + } + // transfer ownership to timelock _, err = commonchangeset.ApplyChangesets(t, e.Env, map[uint64]*gethwrappers.RBACTimelock{ initialDeploy[0]: state.Chains[initialDeploy[0]].Timelock, diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 5e8705d4757..3ca02d5ebfc 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -398,7 +398,7 @@ func CCIPSendRequest( fee, err := r.GetFee( &bind.CallOpts{Context: context.Background()}, dest, msg) if err != nil { - return nil, 0, errors.Wrap(deployment.MaybeDataErr(err), "failed to get fee") + return nil, 0, fmt.Errorf("failed to get fee: %w", deployment.MaybeDataErr(err)) } if msg.FeeToken == common.HexToAddress("0x0") { e.Chains[src].DeployerKey.Value = fee From 2edd8094065e2d9ca8c41c41b0c92ecbcbdc939e Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Tue, 26 Nov 2024 11:54:17 +0400 Subject: [PATCH 06/10] extract common code to func --- .../ccip/changeset/accept_ownership_test.go | 15 +++++++++++++-- deployment/ccip/changeset/add_chain_test.go | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index b13ac4e93eb..c3fbf6b7e14 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -17,7 +17,6 @@ import ( ) func Test_NewAcceptOwnershipChangeset(t *testing.T) { - ctx := tests.Context(t) e := NewMemoryEnvironmentWithJobs(t, logger.TestLogger(t), 2, 4) state, err := LoadOnchainState(e.Env) require.NoError(t, err) @@ -92,8 +91,20 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { }) require.NoError(t, err) + assertTimelockOwnership(t, e, allChains, state) +} + +// assertTimelockOwnership asserts that the ownership of the contracts has been transferred +// to the appropriate timelock contract on each chain. +func assertTimelockOwnership( + t *testing.T, + e DeployedEnv, + chains []uint64, + state CCIPOnChainState, +) { + ctx := tests.Context(t) // check that the ownership has been transferred correctly - for _, chain := range allChains { + for _, chain := range chains { for _, contract := range []ownershipTransferrer{ state.Chains[chain].OnRamp, state.Chains[chain].OffRamp, diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index fa8161842b6..9bfdb5db98e 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -152,6 +152,8 @@ func TestAddChainInbound(t *testing.T) { }) require.NoError(t, err) + assertTimelockOwnership(t, e, initialDeploy, state) + nodes, err := deployment.NodeInfo(e.Env.NodeIDs, e.Env.Offchain) require.NoError(t, err) From 5ad33a0cc844bb72b6b4028530fdf75e8efc6f12 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Wed, 27 Nov 2024 09:28:27 +0400 Subject: [PATCH 07/10] move changeset to common Refactor the proposal helpers a bit --- deployment/ccip/changeset/accept_ownership.go | 149 ------------------ .../ccip/changeset/accept_ownership_test.go | 50 +++++- deployment/ccip/changeset/active_candidate.go | 49 +++++- .../ccip/changeset/active_candidate_test.go | 23 +-- deployment/ccip/changeset/add_chain.go | 44 +++++- deployment/ccip/changeset/add_chain_test.go | 8 +- deployment/ccip/changeset/propose.go | 61 ------- .../common/changeset/accept_ownership.go | 104 ++++++++++++ .../common/changeset/accept_ownership_test.go | 75 +++++++++ deployment/common/proposalutils/propose.go | 77 +++++++++ 10 files changed, 395 insertions(+), 245 deletions(-) delete mode 100644 deployment/ccip/changeset/accept_ownership.go delete mode 100644 deployment/ccip/changeset/propose.go create mode 100644 deployment/common/changeset/accept_ownership.go create mode 100644 deployment/common/changeset/accept_ownership_test.go create mode 100644 deployment/common/proposalutils/propose.go diff --git a/deployment/ccip/changeset/accept_ownership.go b/deployment/ccip/changeset/accept_ownership.go deleted file mode 100644 index f43b75549cf..00000000000 --- a/deployment/ccip/changeset/accept_ownership.go +++ /dev/null @@ -1,149 +0,0 @@ -package changeset - -import ( - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - "github.com/smartcontractkit/chainlink/deployment" -) - -type ownershipAcceptor interface { - AcceptOwnership(opts *bind.TransactOpts) (*gethtypes.Transaction, error) - Address() common.Address -} - -type AcceptOwnershipConfig struct { - State CCIPOnChainState - ChainSelectors []uint64 - HomeChainSelector uint64 -} - -// type assertion - comply with deployment.ChangeSet interface -var _ deployment.ChangeSet[AcceptOwnershipConfig] = NewAcceptOwnershipChangeset - -// NewAcceptOwnershipChangeset creates a changeset that accepts ownership of all the -// ccip contracts deployed on the given chain selectors. -// New chain contracts are: -// * OnRamp -// * OffRamp -// * FeeQuoter -// * NonceManager -// * RMNRemote -// Home chain contracts are: -// * CCIPHome -// * RMNHome -// * CapabilityRegistry -func NewAcceptOwnershipChangeset( - e deployment.Environment, - cfg AcceptOwnershipConfig, -) (deployment.ChangesetOutput, error) { - // basic validation - if len(cfg.ChainSelectors) == 0 || cfg.HomeChainSelector == 0 { - return deployment.ChangesetOutput{}, fmt.Errorf("no chain selectors provided") - } - - if len(cfg.State.Chains) == 0 { - return deployment.ChangesetOutput{}, fmt.Errorf("no chains in state") - } - - // gen one batch per chain for the chain contracts - var batches []timelock.BatchChainOperation - for _, chainSelector := range cfg.ChainSelectors { - chainState, ok := cfg.State.Chains[chainSelector] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("chain %d not found in state", chainSelector) - } - - ops, err := genAcceptOwnershipOps(chainState) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership batch for chain %d: %w", - chainSelector, err) - } - - batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: mcms.ChainIdentifier(chainSelector), - Batch: ops, - }) - } - - // gen separate batch chain operation for home chain contracts - homeChainState, ok := cfg.State.Chains[cfg.HomeChainSelector] - if !ok { - return deployment.ChangesetOutput{}, fmt.Errorf("home chain %d not found in state", cfg.HomeChainSelector) - } - homeChainOps, err := genHomeChainAcceptOwnershipOps(homeChainState) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership batch for home chain %d: %w", - cfg.HomeChainSelector, err) - } - - batches = append(batches, timelock.BatchChainOperation{ - ChainIdentifier: mcms.ChainIdentifier(cfg.HomeChainSelector), - Batch: homeChainOps, - }) - - proposal, err := BuildProposalFromBatches( - cfg.State, - batches, - "Accept ownership of all CCIP contracts", - time.Duration(0), // minDelay - ) - if err != nil { - return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batches: %+v", err, batches) - } - - return deployment.ChangesetOutput{ - Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, - }, nil -} - -func genAcceptOwnershipOps(chainState CCIPChainState) (ops []mcms.Operation, err error) { - for _, contract := range []ownershipAcceptor{ - chainState.OnRamp, - chainState.OffRamp, - chainState.FeeQuoter, - chainState.NonceManager, - chainState.RMNRemote, - } { - acceptOwnershipTx, err := contract.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) - } - - ops = append(ops, mcms.Operation{ - To: contract.Address(), - Data: acceptOwnershipTx.Data(), - Value: big.NewInt(0), - }) - } - - return ops, nil -} - -func genHomeChainAcceptOwnershipOps(homeChainState CCIPChainState) (ops []mcms.Operation, err error) { - for _, contract := range []ownershipAcceptor{ - homeChainState.CapabilityRegistry, - homeChainState.CCIPHome, - homeChainState.RMNHome, - } { - acceptOwnershipTx, err := contract.AcceptOwnership(deployment.SimTransactOpts()) - if err != nil { - return nil, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", contract, err) - } - - ops = append(ops, mcms.Operation{ - To: contract.Address(), - Data: acceptOwnershipTx.Data(), - Value: big.NewInt(0), - }) - } - - return ops, nil -} diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index c3fbf6b7e14..eab8a27b855 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -3,8 +3,10 @@ package changeset import ( "math/big" "testing" + "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/deployment" @@ -81,12 +83,8 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. { - Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{ - State: state, - ChainSelectors: allChains, - HomeChainSelector: e.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), + Config: genTestAcceptOwnershipConfig(e, allChains, state), }, }) require.NoError(t, err) @@ -94,6 +92,46 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { assertTimelockOwnership(t, e, allChains, state) } +func genTestAcceptOwnershipConfig( + e DeployedEnv, + chains []uint64, + state CCIPOnChainState, +) commonchangeset.AcceptOwnershipConfig { + var ( + timelocksPerChain = make(map[uint64]common.Address) + proposerMCMses = make(map[uint64]*gethwrappers.ManyChainMultiSig) + contracts = make(map[uint64][]commonchangeset.OwnershipAcceptor) + ) + for _, chain := range chains { + timelocksPerChain[chain] = state.Chains[chain].Timelock.Address() + proposerMCMses[chain] = state.Chains[chain].ProposerMcm + contracts[chain] = []commonchangeset.OwnershipAcceptor{ + state.Chains[chain].OnRamp, + state.Chains[chain].OffRamp, + state.Chains[chain].FeeQuoter, + state.Chains[chain].NonceManager, + state.Chains[chain].RMNRemote, + } + } + + // add home chain contracts. + // this overwrite should be fine. + timelocksPerChain[e.HomeChainSel] = state.Chains[e.HomeChainSel].Timelock.Address() + proposerMCMses[e.HomeChainSel] = state.Chains[e.HomeChainSel].ProposerMcm + contracts[e.HomeChainSel] = append(contracts[e.HomeChainSel], + state.Chains[e.HomeChainSel].CapabilityRegistry, + state.Chains[e.HomeChainSel].CCIPHome, + state.Chains[e.HomeChainSel].RMNHome, + ) + + return commonchangeset.AcceptOwnershipConfig{ + TimelocksPerChain: timelocksPerChain, + ProposerMCMSes: proposerMCMses, + Contracts: contracts, + MinDelay: time.Duration(0), + } +} + // assertTimelockOwnership asserts that the ownership of the contracts has been transferred // to the appropriate timelock contract on each chain. func assertTimelockOwnership( diff --git a/deployment/ccip/changeset/active_candidate.go b/deployment/ccip/changeset/active_candidate.go index ee0c4d10ebf..e3391c9226c 100644 --- a/deployment/ccip/changeset/active_candidate.go +++ b/deployment/ccip/changeset/active_candidate.go @@ -3,16 +3,20 @@ package changeset import ( "fmt" + "github.com/ethereum/go-ethereum/common" + "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" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" ) // PromoteAllCandidatesChangeset generates a proposal to call promoteCandidate on the CCIPHome through CapReg. // This needs to be called after SetCandidateProposal is executed. +// TODO: make it conform to the ChangeSet interface. func PromoteAllCandidatesChangeset( state CCIPOnChainState, homeChainSel, newChainSel uint64, @@ -28,10 +32,24 @@ func PromoteAllCandidatesChangeset( return deployment.ChangesetOutput{}, err } - prop, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), - Batch: promoteCandidateOps, - }}, "promoteCandidate for commit and execution", 0) + var ( + timelocksPerChain = map[uint64]common.Address{ + homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSel: state.Chains[homeChainSel].ProposerMcm, + } + ) + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: promoteCandidateOps, + }}, + "promoteCandidate for commit and execution", + 0, // minDelay + ) if err != nil { return deployment.ChangesetOutput{}, err } @@ -43,6 +61,7 @@ func PromoteAllCandidatesChangeset( } // SetCandidateExecPluginProposal calls setCandidate on the CCIPHome for setting up OCR3 exec Plugin config for the new chain. +// TODO: make it conform to the ChangeSet interface. func SetCandidatePluginChangeset( state CCIPOnChainState, e deployment.Environment, @@ -87,10 +106,24 @@ func SetCandidatePluginChangeset( return deployment.ChangesetOutput{}, err } - prop, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), - Batch: setCandidateMCMSOps, - }}, "SetCandidate for execution", 0) + var ( + timelocksPerChain = map[uint64]common.Address{ + homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSel: state.Chains[homeChainSel].ProposerMcm, + } + ) + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: setCandidateMCMSOps, + }}, + "SetCandidate for execution", + 0, // minDelay + ) if err != nil { return deployment.ChangesetOutput{}, err } diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index 52ec6c41474..e4485fe80d6 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/require" commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -101,12 +102,8 @@ func TestActiveCandidate(t *testing.T) { // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. { - Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{ - State: state, - ChainSelectors: allChains, - HomeChainSelector: tenv.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), + Config: genTestAcceptOwnershipConfig(tenv, allChains, state), }, }) require.NoError(t, err) @@ -162,6 +159,14 @@ func TestActiveCandidate(t *testing.T) { ) require.NoError(t, err) + var ( + timelocksPerChain = map[uint64]common.Address{ + tenv.HomeChainSel: state.Chains[tenv.HomeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + tenv.HomeChainSel: state.Chains[tenv.HomeChainSel].ProposerMcm, + } + ) setCommitCandidateOp, err := SetCandidateOnExistingDon( ocr3ConfigMap[cctypes.PluginTypeCCIPCommit], state.Chains[tenv.HomeChainSel].CapabilityRegistry, @@ -170,7 +175,7 @@ func TestActiveCandidate(t *testing.T) { nodes.NonBootstraps(), ) require.NoError(t, err) - setCommitCandidateProposal, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + setCommitCandidateProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: setCommitCandidateOp, }}, "set new candidates on commit plugin", 0) @@ -188,7 +193,7 @@ func TestActiveCandidate(t *testing.T) { ) require.NoError(t, err) - setExecCandidateProposal, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + setExecCandidateProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: setExecCandidateOp, }}, "set new candidates on commit and exec plugins", 0) @@ -215,7 +220,7 @@ func TestActiveCandidate(t *testing.T) { promoteOps, err := PromoteAllCandidatesForChainOps(state.Chains[tenv.HomeChainSel].CapabilityRegistry, state.Chains[tenv.HomeChainSel].CCIPHome, tenv.FeedChainSel, nodes.NonBootstraps()) require.NoError(t, err) - promoteProposal, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ + promoteProposal, err := proposalutils.BuildProposalFromBatches(timelocksPerChain, proposerMCMSes, []timelock.BatchChainOperation{{ ChainIdentifier: mcms.ChainIdentifier(tenv.HomeChainSel), Batch: promoteOps, }}, "promote candidates and revoke actives", 0) diff --git a/deployment/ccip/changeset/add_chain.go b/deployment/ccip/changeset/add_chain.go index bc4d8d6b97e..a31f65be54a 100644 --- a/deployment/ccip/changeset/add_chain.go +++ b/deployment/ccip/changeset/add_chain.go @@ -4,9 +4,12 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types" + "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" @@ -17,6 +20,7 @@ import ( // NewChainInboundChangeset generates a proposal // to connect the new chain to the existing chains. +// TODO: doesn't implement the ChangeSet interface. func NewChainInboundChangeset( e deployment.Environment, state CCIPOnChainState, @@ -77,7 +81,21 @@ func NewChainInboundChangeset( }, }) - prop, err := BuildProposalFromBatches(state, batches, "proposal to set new chains", 0) + var ( + timelocksPerChain = make(map[uint64]common.Address) + proposerMCMSes = make(map[uint64]*gethwrappers.ManyChainMultiSig) + ) + for _, chain := range append(sources, homeChainSel) { + timelocksPerChain[chain] = state.Chains[chain].Timelock.Address() + proposerMCMSes[chain] = state.Chains[chain].ProposerMcm + } + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + batches, + "proposal to set new chains", + 0, + ) if err != nil { return deployment.ChangesetOutput{}, err } @@ -134,12 +152,26 @@ func AddDonAndSetCandidateChangeset( return deployment.ChangesetOutput{}, err } - prop, err := BuildProposalFromBatches(state, []timelock.BatchChainOperation{{ - ChainIdentifier: mcms.ChainIdentifier(homeChainSel), - Batch: []mcms.Operation{addDonOp}, - }}, "setCandidate for commit and AddDon on new Chain", 0) + var ( + timelocksPerChain = map[uint64]common.Address{ + homeChainSel: state.Chains[homeChainSel].Timelock.Address(), + } + proposerMCMSes = map[uint64]*gethwrappers.ManyChainMultiSig{ + homeChainSel: state.Chains[homeChainSel].ProposerMcm, + } + ) + prop, err := proposalutils.BuildProposalFromBatches( + timelocksPerChain, + proposerMCMSes, + []timelock.BatchChainOperation{{ + ChainIdentifier: mcms.ChainIdentifier(homeChainSel), + Batch: []mcms.Operation{addDonOp}, + }}, + "setCandidate for commit and AddDon on new Chain", + 0, // minDelay + ) if err != nil { - return deployment.ChangesetOutput{}, err + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w", err) } return deployment.ChangesetOutput{ diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 9bfdb5db98e..83ebf56c345 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -142,12 +142,8 @@ func TestAddChainInbound(t *testing.T) { // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. { - Changeset: commonchangeset.WrapChangeSet(NewAcceptOwnershipChangeset), - Config: AcceptOwnershipConfig{ - State: state, - ChainSelectors: initialDeploy, - HomeChainSelector: e.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset), + Config: genTestAcceptOwnershipConfig(e, initialDeploy, state), }, }) require.NoError(t, err) diff --git a/deployment/ccip/changeset/propose.go b/deployment/ccip/changeset/propose.go deleted file mode 100644 index 707f6f7c056..00000000000 --- a/deployment/ccip/changeset/propose.go +++ /dev/null @@ -1,61 +0,0 @@ -package changeset - -import ( - "fmt" - "time" - - mapset "github.com/deckarep/golang-set/v2" - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" - "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" -) - -func BuildProposalMetadata(state CCIPOnChainState, chains []uint64) (map[mcms.ChainIdentifier]common.Address, map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { - tlAddressMap := make(map[mcms.ChainIdentifier]common.Address) - metaDataPerChain := make(map[mcms.ChainIdentifier]mcms.ChainMetadata) - for _, sel := range chains { - chainId := mcms.ChainIdentifier(sel) - tlAddressMap[chainId] = state.Chains[sel].Timelock.Address() - mcm := state.Chains[sel].ProposerMcm - opCount, err := mcm.GetOpCount(nil) - if err != nil { - return nil, nil, err - } - metaDataPerChain[chainId] = mcms.ChainMetadata{ - StartingOpCount: opCount.Uint64(), - MCMAddress: mcm.Address(), - } - } - return tlAddressMap, metaDataPerChain, nil -} - -// Given batches of operations, we build the metadata and timelock addresses of those opartions -// We then return a proposal that can be executed and signed -func BuildProposalFromBatches(state CCIPOnChainState, batches []timelock.BatchChainOperation, description string, minDelay time.Duration) (*timelock.MCMSWithTimelockProposal, error) { - if len(batches) == 0 { - return nil, fmt.Errorf("no operations in batch") - } - - chains := mapset.NewSet[uint64]() - for _, op := range batches { - chains.Add(uint64(op.ChainIdentifier)) - } - - tls, mcmsMd, err := BuildProposalMetadata(state, chains.ToSlice()) - if err != nil { - return nil, err - } - - return timelock.NewMCMSWithTimelockProposal( - "1", - 2004259681, // TODO: should be parameterized and based on current block timestamp. - []mcms.Signature{}, - false, - mcmsMd, - tls, - description, - batches, - timelock.Schedule, - minDelay.String(), - ) -} diff --git a/deployment/common/changeset/accept_ownership.go b/deployment/common/changeset/accept_ownership.go new file mode 100644 index 00000000000..7f89e5cb75f --- /dev/null +++ b/deployment/common/changeset/accept_ownership.go @@ -0,0 +1,104 @@ +package changeset + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "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" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/common/proposalutils" +) + +type OwnershipAcceptor interface { + AcceptOwnership(opts *bind.TransactOpts) (*gethtypes.Transaction, error) + Address() common.Address +} + +type AcceptOwnershipConfig struct { + // TimelocksPerChain is a mapping from chain selector to the timelock contract address on that chain. + TimelocksPerChain map[uint64]common.Address + + // ProposerMCMSes is a mapping from chain selector to the proposer MCMS contract on that chain. + ProposerMCMSes map[uint64]*gethwrappers.ManyChainMultiSig + + // Contracts is a mapping from chain selector to the ownership acceptors on that chain. + // Proposal will be generated for these contracts. + Contracts map[uint64][]OwnershipAcceptor + + // MinDelay is the minimum amount of time that must pass before the proposal + // can be executed onchain. + // This is typically set to 3 hours but can be set to 0 for immediate execution (useful for tests). + MinDelay time.Duration +} + +func (a AcceptOwnershipConfig) Validate() error { + // check that we have timelocks and proposer mcmses for the chains + // in the Contracts field. + for chainSelector := range a.Contracts { + if _, ok := a.TimelocksPerChain[chainSelector]; !ok { + return fmt.Errorf("missing timelock for chain %d", chainSelector) + } + if _, ok := a.ProposerMCMSes[chainSelector]; !ok { + return fmt.Errorf("missing proposer MCMS for chain %d", chainSelector) + } + } + + return nil +} + +// type assertion - comply with deployment.ChangeSet interface +var _ deployment.ChangeSet[AcceptOwnershipConfig] = NewAcceptOwnershipChangeset + +// NewAcceptOwnershipChangeset creates a changeset that contains a proposal to accept ownership of the contracts +// provided in the configuration. +func NewAcceptOwnershipChangeset( + e deployment.Environment, + cfg AcceptOwnershipConfig, +) (deployment.ChangesetOutput, error) { + if err := cfg.Validate(); err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("invalid accept ownership config: %w", err) + } + + var batches []timelock.BatchChainOperation + for chainSelector, ownershipAcceptors := range cfg.Contracts { + var ops []mcms.Operation + for _, ownershipAcceptor := range ownershipAcceptors { + tx, err := ownershipAcceptor.AcceptOwnership(deployment.SimTransactOpts()) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to generate accept ownership calldata of %T: %w", ownershipAcceptor, err) + } + + ops = append(ops, mcms.Operation{ + To: ownershipAcceptor.Address(), + Data: tx.Data(), + Value: big.NewInt(0), + }) + } + batches = append(batches, timelock.BatchChainOperation{ + ChainIdentifier: mcms.ChainIdentifier(chainSelector), + Batch: ops, + }) + } + + proposal, err := proposalutils.BuildProposalFromBatches( + cfg.TimelocksPerChain, + cfg.ProposerMCMSes, + batches, + "Accept ownership of contracts", + cfg.MinDelay, + ) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal from batch: %w, batches: %+v", err, batches) + } + + return deployment.ChangesetOutput{ + Proposals: []timelock.MCMSWithTimelockProposal{*proposal}, + }, nil +} diff --git a/deployment/common/changeset/accept_ownership_test.go b/deployment/common/changeset/accept_ownership_test.go new file mode 100644 index 00000000000..27feb6389bc --- /dev/null +++ b/deployment/common/changeset/accept_ownership_test.go @@ -0,0 +1,75 @@ +package changeset_test + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink/deployment/common/changeset" + "github.com/stretchr/testify/assert" +) + +func TestAcceptOwnershipConfig_Validate(t *testing.T) { + tests := []struct { + name string + config changeset.AcceptOwnershipConfig + wantErr bool + }{ + { + name: "valid config", + config: changeset.AcceptOwnershipConfig{ + TimelocksPerChain: map[uint64]common.Address{ + 1: common.HexToAddress("0x1"), + }, + ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{ + 1: {}, + }, + Contracts: map[uint64][]changeset.OwnershipAcceptor{ + 1: {}, + }, + MinDelay: 3 * time.Hour, + }, + wantErr: false, + }, + { + name: "missing timelock", + config: changeset.AcceptOwnershipConfig{ + TimelocksPerChain: map[uint64]common.Address{}, + ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{ + 1: {}, + }, + Contracts: map[uint64][]changeset.OwnershipAcceptor{ + 1: {}, + }, + MinDelay: 3 * time.Hour, + }, + wantErr: true, + }, + { + name: "missing proposer MCMS", + config: changeset.AcceptOwnershipConfig{ + TimelocksPerChain: map[uint64]common.Address{ + 1: common.HexToAddress("0x1"), + }, + ProposerMCMSes: map[uint64]*gethwrappers.ManyChainMultiSig{}, + Contracts: map[uint64][]changeset.OwnershipAcceptor{ + 1: {}, + }, + MinDelay: 3 * time.Hour, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.config.Validate() + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/deployment/common/proposalutils/propose.go b/deployment/common/proposalutils/propose.go new file mode 100644 index 00000000000..b4cb54af891 --- /dev/null +++ b/deployment/common/proposalutils/propose.go @@ -0,0 +1,77 @@ +package proposalutils + +import ( + "fmt" + "time" + + mapset "github.com/deckarep/golang-set/v2" + "github.com/ethereum/go-ethereum/common" + "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" +) + +func buildProposalMetadata( + chainSelectors []uint64, + proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig, +) (map[mcms.ChainIdentifier]mcms.ChainMetadata, error) { + metaDataPerChain := make(map[mcms.ChainIdentifier]mcms.ChainMetadata) + for _, selector := range chainSelectors { + proposerMcms, ok := proposerMcmsesPerChain[selector] + if !ok { + return nil, fmt.Errorf("missing proposer mcm for chain %d", selector) + } + chainId := mcms.ChainIdentifier(selector) + opCount, err := proposerMcms.GetOpCount(nil) + if err != nil { + return nil, fmt.Errorf("failed to get op count for chain %d: %w", selector, err) + } + metaDataPerChain[chainId] = mcms.ChainMetadata{ + StartingOpCount: opCount.Uint64(), + MCMAddress: proposerMcms.Address(), + } + } + return metaDataPerChain, nil +} + +// Given batches of operations, we build the metadata and timelock addresses of those opartions +// We then return a proposal that can be executed and signed +func BuildProposalFromBatches( + timelocksPerChain map[uint64]common.Address, + proposerMcmsesPerChain map[uint64]*gethwrappers.ManyChainMultiSig, + batches []timelock.BatchChainOperation, + description string, + minDelay time.Duration, +) (*timelock.MCMSWithTimelockProposal, error) { + if len(batches) == 0 { + return nil, fmt.Errorf("no operations in batch") + } + + chains := mapset.NewSet[uint64]() + for _, op := range batches { + chains.Add(uint64(op.ChainIdentifier)) + } + + mcmsMd, err := buildProposalMetadata(chains.ToSlice(), proposerMcmsesPerChain) + if err != nil { + return nil, err + } + + tlsPerChainId := make(map[mcms.ChainIdentifier]common.Address) + for chainId, tl := range timelocksPerChain { + tlsPerChainId[mcms.ChainIdentifier(chainId)] = tl + } + + return timelock.NewMCMSWithTimelockProposal( + "1", + 2004259681, // TODO: should be parameterized and based on current block timestamp. + []mcms.Signature{}, + false, + mcmsMd, + tlsPerChainId, + description, + batches, + timelock.Schedule, + minDelay.String(), + ) +} From c474490d4d1fc2c0e0c4043d13cb197f8a74b37d Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Wed, 27 Nov 2024 10:03:41 +0400 Subject: [PATCH 08/10] move transfer ownership cs to common --- .../ccip/changeset/accept_ownership_test.go | 49 ++++++++-- .../ccip/changeset/active_candidate_test.go | 8 +- deployment/ccip/changeset/add_chain_test.go | 8 +- .../ccip/changeset/transfer_ownership.go | 95 ------------------- .../common/changeset/transfer_ownership.go | 70 ++++++++++++++ 5 files changed, 115 insertions(+), 115 deletions(-) delete mode 100644 deployment/ccip/changeset/transfer_ownership.go create mode 100644 deployment/common/changeset/transfer_ownership.go diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index eab8a27b855..2be0b0315f3 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -73,12 +73,8 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { }, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { - Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), - Config: TransferOwnershipConfig{ - State: state, - ChainSelectors: allChains, - HomeChainSelector: e.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewTransferOwnershipChangeset), + Config: genTestTransferOwnershipConfig(e, allChains, state), }, // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. @@ -92,6 +88,43 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { assertTimelockOwnership(t, e, allChains, state) } +func genTestTransferOwnershipConfig( + e DeployedEnv, + chains []uint64, + state CCIPOnChainState, +) commonchangeset.TransferOwnershipConfig { + var ( + timelocksPerChain = make(map[uint64]common.Address) + contracts = make(map[uint64][]commonchangeset.OwnershipTransferrer) + ) + + // chain contracts + for _, chain := range chains { + timelocksPerChain[chain] = state.Chains[chain].Timelock.Address() + contracts[chain] = []commonchangeset.OwnershipTransferrer{ + state.Chains[chain].OnRamp, + state.Chains[chain].OffRamp, + state.Chains[chain].FeeQuoter, + state.Chains[chain].NonceManager, + state.Chains[chain].RMNRemote, + } + } + + // home chain + homeChainTimelockAddress := state.Chains[e.HomeChainSel].Timelock.Address() + timelocksPerChain[e.HomeChainSel] = homeChainTimelockAddress + contracts[e.HomeChainSel] = append(contracts[e.HomeChainSel], + state.Chains[e.HomeChainSel].CapabilityRegistry, + state.Chains[e.HomeChainSel].CCIPHome, + state.Chains[e.HomeChainSel].RMNHome, + ) + + return commonchangeset.TransferOwnershipConfig{ + TimelocksPerChain: timelocksPerChain, + Contracts: contracts, + } +} + func genTestAcceptOwnershipConfig( e DeployedEnv, chains []uint64, @@ -143,7 +176,7 @@ func assertTimelockOwnership( ctx := tests.Context(t) // check that the ownership has been transferred correctly for _, chain := range chains { - for _, contract := range []ownershipTransferrer{ + for _, contract := range []commonchangeset.OwnershipTransferrer{ state.Chains[chain].OnRamp, state.Chains[chain].OffRamp, state.Chains[chain].FeeQuoter, @@ -160,7 +193,7 @@ func assertTimelockOwnership( // check home chain contracts ownership homeChainTimelockAddress := state.Chains[e.HomeChainSel].Timelock.Address() - for _, contract := range []ownershipTransferrer{ + for _, contract := range []commonchangeset.OwnershipTransferrer{ state.Chains[e.HomeChainSel].CapabilityRegistry, state.Chains[e.HomeChainSel].CCIPHome, state.Chains[e.HomeChainSel].RMNHome, diff --git a/deployment/ccip/changeset/active_candidate_test.go b/deployment/ccip/changeset/active_candidate_test.go index e4485fe80d6..4d72ab6ccf2 100644 --- a/deployment/ccip/changeset/active_candidate_test.go +++ b/deployment/ccip/changeset/active_candidate_test.go @@ -92,12 +92,8 @@ func TestActiveCandidate(t *testing.T) { _, err = commonchangeset.ApplyChangesets(t, e, timelocks, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { - Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), - Config: TransferOwnershipConfig{ - State: state, - ChainSelectors: allChains, - HomeChainSelector: tenv.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewTransferOwnershipChangeset), + Config: genTestTransferOwnershipConfig(tenv, allChains, state), }, // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 83ebf56c345..9d6e175ddbc 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -132,12 +132,8 @@ func TestAddChainInbound(t *testing.T) { }, []commonchangeset.ChangesetApplication{ // note this doesn't have proposals. { - Changeset: commonchangeset.WrapChangeSet(NewTransferOwnershipChangeset), - Config: TransferOwnershipConfig{ - State: state, - ChainSelectors: initialDeploy, - HomeChainSelector: e.HomeChainSel, - }, + Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewTransferOwnershipChangeset), + Config: genTestTransferOwnershipConfig(e, initialDeploy, state), }, // this has proposals, ApplyChangesets will sign & execute them. // in practice, signing and executing are separated processes. diff --git a/deployment/ccip/changeset/transfer_ownership.go b/deployment/ccip/changeset/transfer_ownership.go deleted file mode 100644 index 96b6714fa27..00000000000 --- a/deployment/ccip/changeset/transfer_ownership.go +++ /dev/null @@ -1,95 +0,0 @@ -package changeset - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/deployment" -) - -type ownershipTransferrer interface { - TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*gethtypes.Transaction, error) - Owner(opts *bind.CallOpts) (common.Address, error) -} - -type TransferOwnershipConfig struct { - State CCIPOnChainState - ChainSelectors []uint64 - HomeChainSelector uint64 -} - -var _ deployment.ChangeSet[TransferOwnershipConfig] = NewTransferOwnershipChangeset - -// NewTransferOwnershipChangeset creates a changeset that transfers ownership of all the -// ccip chain contracts deployed on the given chain selectors. -// New chain contracts are: -// * OnRamp -// * OffRamp -// * FeeQuoter -// * NonceManager -// * RMNRemote -// Home chain contracts are: -// * CCIPHome -// * RMNHome -// * CapabilityRegistry -// This can be composed with NewAcceptOwnershipChangeset in order to fully transfer -// ownership of all the contracts listed above. -func NewTransferOwnershipChangeset( - e deployment.Environment, - cfg TransferOwnershipConfig, -) (deployment.ChangesetOutput, error) { - // basic validation - if len(cfg.ChainSelectors) == 0 || cfg.HomeChainSelector == 0 { - return deployment.ChangesetOutput{}, fmt.Errorf("no chain selectors provided") - } - - if len(cfg.State.Chains) == 0 { - return deployment.ChangesetOutput{}, fmt.Errorf("no chains in state") - } - - // transfer ownership of chain contracts - // these are assumed to be owned by the deployer configured in the given - // environment. - for _, chain := range cfg.ChainSelectors { - for _, contract := range []ownershipTransferrer{ - cfg.State.Chains[chain].OnRamp, - cfg.State.Chains[chain].OffRamp, - cfg.State.Chains[chain].FeeQuoter, - cfg.State.Chains[chain].NonceManager, - cfg.State.Chains[chain].RMNRemote, - } { - tx, err := contract.TransferOwnership( - e.Chains[chain].DeployerKey, - cfg.State.Chains[chain].Timelock.Address(), - ) - _, err = deployment.ConfirmIfNoError(e.Chains[chain], tx, err) - if err != nil { - return deployment.ChangesetOutput{}, err - } - } - } - - // transfer ownership of home chain contracts - homeChainTimelockAddress := cfg.State.Chains[cfg.HomeChainSelector].Timelock.Address() - for _, contract := range []ownershipTransferrer{ - cfg.State.Chains[cfg.HomeChainSelector].CapabilityRegistry, - cfg.State.Chains[cfg.HomeChainSelector].CCIPHome, - cfg.State.Chains[cfg.HomeChainSelector].RMNHome, - } { - tx, err := contract.TransferOwnership( - e.Chains[cfg.HomeChainSelector].DeployerKey, - homeChainTimelockAddress, - ) - _, err = deployment.ConfirmIfNoError(e.Chains[cfg.HomeChainSelector], tx, err) - if err != nil { - return deployment.ChangesetOutput{}, err - } - } - - // no new addresses or proposals or jobspecs, so changeset output is empty. - // NOTE: onchain state has technically changed for above contracts, maybe that should - // be captured? - return deployment.ChangesetOutput{}, nil -} diff --git a/deployment/common/changeset/transfer_ownership.go b/deployment/common/changeset/transfer_ownership.go new file mode 100644 index 00000000000..a0085fb61ca --- /dev/null +++ b/deployment/common/changeset/transfer_ownership.go @@ -0,0 +1,70 @@ +package changeset + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/smartcontractkit/chainlink/deployment" +) + +type OwnershipTransferrer interface { + TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*gethtypes.Transaction, error) + Owner(opts *bind.CallOpts) (common.Address, error) +} + +type TransferOwnershipConfig struct { + // TimelocksPerChain is a mapping from chain selector to the timelock contract address on that chain. + TimelocksPerChain map[uint64]common.Address + + // Contracts is a mapping from chain selector to the ownership transferrers on that chain. + Contracts map[uint64][]OwnershipTransferrer +} + +func (t TransferOwnershipConfig) Validate() error { + // check that we have timelocks for the chains in the Contracts field. + for chainSelector := range t.Contracts { + if _, ok := t.TimelocksPerChain[chainSelector]; !ok { + return fmt.Errorf("missing timelock for chain %d", chainSelector) + } + } + + return nil +} + +var _ deployment.ChangeSet[TransferOwnershipConfig] = NewTransferOwnershipChangeset + +// NewTransferOwnershipChangeset creates a changeset that transfers ownership of all the +// contracts in the provided configuration to the the appropriate timelock on that chain. +// If the owner is already the timelock contract, no transaction is sent. +func NewTransferOwnershipChangeset( + e deployment.Environment, + cfg TransferOwnershipConfig, +) (deployment.ChangesetOutput, error) { + if err := cfg.Validate(); err != nil { + return deployment.ChangesetOutput{}, err + } + + for chainSelector, contracts := range cfg.Contracts { + timelock := cfg.TimelocksPerChain[chainSelector] + for _, contract := range contracts { + owner, err := contract.Owner(nil) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to get owner of contract %T: %v", contract, err) + } + if owner != timelock { + tx, err := contract.TransferOwnership(e.Chains[chainSelector].DeployerKey, timelock) + _, err = deployment.ConfirmIfNoError(e.Chains[chainSelector], tx, err) + if err != nil { + return deployment.ChangesetOutput{}, fmt.Errorf("failed to transfer ownership of contract %T: %v", contract, err) + } + } + } + } + + // no new addresses or proposals or jobspecs, so changeset output is empty. + // NOTE: onchain state has technically changed for above contracts, maybe that should + // be captured? + return deployment.ChangesetOutput{}, nil +} From 19490c42ceece52a5e4c5519a9cfc6586e1463da Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Wed, 27 Nov 2024 11:05:11 +0400 Subject: [PATCH 09/10] fix --- deployment/ccip/changeset/accept_ownership_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/accept_ownership_test.go b/deployment/ccip/changeset/accept_ownership_test.go index 2be0b0315f3..c3407e0e6e7 100644 --- a/deployment/ccip/changeset/accept_ownership_test.go +++ b/deployment/ccip/changeset/accept_ownership_test.go @@ -49,7 +49,7 @@ func Test_NewAcceptOwnershipChangeset(t *testing.T) { tokenConfig := NewTestTokenConfig(state.Chains[e.FeedChainSel].USDFeeds) ocrParams := make(map[uint64]CCIPOCRParams) for _, chain := range allChains { - ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil, nil) + ocrParams[chain] = DefaultOCRParams(e.FeedChainSel, nil) } err = deployCCIPContracts(e.Env, newAddresses, NewChainsConfig{ HomeChainSel: e.HomeChainSel, From b10a0ad80d89b74d6c68e556143cd48d71f24163 Mon Sep 17 00:00:00 2001 From: Makram Kamaleddine Date: Thu, 28 Nov 2024 00:22:27 +0400 Subject: [PATCH 10/10] bump boost significantly the AddChainInbound test is consistently failing with a "message too costly" error in exec; increasing the relative boost per wait hour causes exec to significantly boost the paid fee so we can execute the message. --- deployment/ccip/changeset/internal/deploy_home_chain.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployment/ccip/changeset/internal/deploy_home_chain.go b/deployment/ccip/changeset/internal/deploy_home_chain.go index 7b45a52a436..27052b04d07 100644 --- a/deployment/ccip/changeset/internal/deploy_home_chain.go +++ b/deployment/ccip/changeset/internal/deploy_home_chain.go @@ -30,7 +30,7 @@ const ( RemoteGasPriceBatchWriteFrequency = 30 * time.Minute TokenPriceBatchWriteFrequency = 30 * time.Minute BatchGasLimit = 6_500_000 - RelativeBoostPerWaitHour = 1.5 + RelativeBoostPerWaitHour = 10000.5 InflightCacheExpiry = 10 * time.Minute RootSnoozeTime = 30 * time.Minute BatchingStrategyID = 0