Skip to content

Commit

Permalink
deployment/ccip/changeset: add transfer/accept ownership changeset (#…
Browse files Browse the repository at this point in the history
…15409)

* wip

* add test

* make the proposal multichain

* add transfer ownership changeset

use these changesets in other tests and axe duplicate code

* fix add_chain_test.go

* extract common code to func

* move changeset to common

Refactor the proposal helpers a bit

* move transfer ownership cs to common

* fix

* 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.
  • Loading branch information
makramkd authored Nov 28, 2024
1 parent 539674a commit 9c7b487
Show file tree
Hide file tree
Showing 14 changed files with 674 additions and 232 deletions.
207 changes: 207 additions & 0 deletions deployment/ccip/changeset/accept_ownership_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
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"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
commontypes "github.com/smartcontractkit/chainlink/deployment/common/types"

"github.com/smartcontractkit/chainlink/v2/core/logger"
"github.com/stretchr/testify/require"
"golang.org/x/exp/maps"
)

func Test_NewAcceptOwnershipChangeset(t *testing.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)
}
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)

// 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(commonchangeset.NewTransferOwnershipChangeset),
Config: genTestTransferOwnershipConfig(e, allChains, state),
},
// this has proposals, ApplyChangesets will sign & execute them.
// in practice, signing and executing are separated processes.
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset),
Config: genTestAcceptOwnershipConfig(e, allChains, state),
},
})
require.NoError(t, err)

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,
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(
t *testing.T,
e DeployedEnv,
chains []uint64,
state CCIPOnChainState,
) {
ctx := tests.Context(t)
// check that the ownership has been transferred correctly
for _, chain := range chains {
for _, contract := range []commonchangeset.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)
}
}

// check home chain contracts ownership
homeChainTimelockAddress := state.Chains[e.HomeChainSel].Timelock.Address()
for _, contract := range []commonchangeset.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, homeChainTimelockAddress, owner)
}
}
49 changes: 41 additions & 8 deletions deployment/ccip/changeset/active_candidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
}
Expand All @@ -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,
Expand Down Expand Up @@ -86,10 +105,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
}
Expand Down
43 changes: 33 additions & 10 deletions deployment/ccip/changeset/active_candidate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -18,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"
)
Expand All @@ -30,6 +33,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))
Expand Down Expand Up @@ -80,14 +84,25 @@ 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(commonchangeset.NewTransferOwnershipChangeset),
Config: genTestTransferOwnershipConfig(tenv, allChains, state),
},
// this has proposals, ApplyChangesets will sign & execute them.
// in practice, signing and executing are separated processes.
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.NewAcceptOwnershipChangeset),
Config: genTestAcceptOwnershipConfig(tenv, allChains, state),
},
})
require.NoError(t, err)
// Apply the accept ownership proposal to all the chains.

err = ConfirmRequestOnSourceAndDest(t, e, state, tenv.HomeChainSel, tenv.FeedChainSel, 2)
Expand Down Expand Up @@ -139,6 +154,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,
Expand All @@ -147,7 +170,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)
Expand All @@ -165,7 +188,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)
Expand All @@ -192,7 +215,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)
Expand Down
Loading

0 comments on commit 9c7b487

Please sign in to comment.