Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deployment/ccip/changeset: add transfer/accept ownership changeset #15409

Merged
merged 14 commits into from
Nov 28, 2024
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just use a generic test for this if you are moving to common changeset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the CCIP test but moved the changeset to common

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
Loading