Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
carte7000 committed Jan 8, 2025
1 parent 9bcb3db commit d76dcea
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 29 deletions.
138 changes: 138 additions & 0 deletions deployment/ccip/changeset/cs_rmn_curse_uncurse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package changeset

import (
"encoding/binary"
"fmt"

"github.com/smartcontractkit/chainlink/deployment"
)

const (
GLOBAL_CURSE_SUBJECT = 0
)

type RMNCurseAction struct {
ChainSelector uint64
SubjectToCurse uint64
}

type CurseAction func(e deployment.Environment) []RMNCurseAction

type RMNCurseConfig struct {
HomeChainSelector uint64
MCMS *MCMSConfig
CurseActions []CurseAction
CurseReason string
}

func subjectToByte16(subject uint64) [16]byte {
var b [16]byte
binary.LittleEndian.PutUint64(b[:8], subject)
return b
}

func CurseLane(sourceSelector uint64, destinationSelector uint64) CurseAction {
// Bidirectional curse between two chains
return func(e deployment.Environment) []RMNCurseAction {
return []RMNCurseAction{
{
ChainSelector: sourceSelector,
SubjectToCurse: destinationSelector,
},
{
ChainSelector: destinationSelector,
SubjectToCurse: sourceSelector,
},
}
}
}

func CurseChain(chainSelector uint64) CurseAction {
return func(e deployment.Environment) []RMNCurseAction {
chainSelectors := e.AllChainSelectors()

// Curse all other chains to prevent onramp from sending message to the cursed chain
var curseActions []RMNCurseAction
for _, otherChainSelector := range chainSelectors {
if otherChainSelector != chainSelector {
curseActions = append(curseActions, RMNCurseAction{
ChainSelector: otherChainSelector,
SubjectToCurse: chainSelector,
})
}
}

// Curse the chain with a global curse to prevent any onramp or offramp message from send message in and out of the chain
curseActions = append(curseActions, RMNCurseAction{
ChainSelector: chainSelector,
SubjectToCurse: GLOBAL_CURSE_SUBJECT,
})

return curseActions
}
}

func groupRMNSubjectBySelector(rmnSubjects []RMNCurseAction) map[uint64][]uint64 {
grouped := make(map[uint64][]uint64)
for _, subject := range rmnSubjects {
grouped[subject.ChainSelector] = append(grouped[subject.ChainSelector], subject.SubjectToCurse)
}

// Only keep unique subjects, preserve only global curse if present and eliminate any curse where the selector is the same as the subject
for chainSelector, subjects := range grouped {
uniqueSubjects := make(map[uint64]struct{})
for _, subject := range subjects {
if subject == chainSelector {
continue
}
uniqueSubjects[subject] = struct{}{}
}

if _, ok := uniqueSubjects[GLOBAL_CURSE_SUBJECT]; ok {
grouped[chainSelector] = []uint64{GLOBAL_CURSE_SUBJECT}
} else {
var uniqueSubjectsSlice []uint64
for subject := range uniqueSubjects {
uniqueSubjectsSlice = append(uniqueSubjectsSlice, subject)
}
grouped[chainSelector] = uniqueSubjectsSlice
}
}

return grouped
}

func NewRMNCurseChangeset(e deployment.Environment, cfg RMNCurseConfig) (deployment.ChangesetOutput, error) {
state, err := LoadOnchainState(e)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to load onchain state: %w", err)
}
deployerGroup := NewDeployerGroup(e, state, cfg.MCMS)

// Generate curse actions
var curseActions []RMNCurseAction
for _, curseAction := range cfg.CurseActions {
curseActions = append(curseActions, curseAction(e)...)
}

// Group curse actions by chain selector
grouped := groupRMNSubjectBySelector(curseActions)

// For each chain in the environement get the RMNRemote contract and call curse
for selector, chain := range state.Chains {
deployer := deployerGroup.getDeployer(selector)
if curseSubjects, ok := grouped[selector]; ok {
subjectsByte16 := make([][16]byte, len(curseSubjects))
for i, subject := range curseSubjects {
subjectsByte16[i] = subjectToByte16(subject)
}

_, err := chain.RMNRemote.Curse0(deployer, subjectsByte16)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to curse chain %d: %w", selector, err)
}
}
}

return deployerGroup.enact(fmt.Sprintf("proposal to curse RMNs: %s", cfg.CurseReason))
}
61 changes: 61 additions & 0 deletions deployment/ccip/changeset/cs_rmn_curse_uncurse_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package changeset

import (
"testing"
)

type curseAssertion struct {
chainSelector uint64
subject uint64
}

type CurseTestCase struct {
useMCMS bool
name string
curseActionsBuilder []CurseAction
curseAssertions []curseAssertion
}

func TestRMNCurse(t *testing.T) {
t.Parallel()
testCases := []CurseTestCase{
// {
// useMCMS: true,
// name: "with MCMS",
// },
{
useMCMS: false,
name: "without MCMS",
curseActionsBuilder: []CurseAction{CurseLane(0, 1)},
curseAssertions: []curseAssertion{
{chainSelector: 0, subject: 1},
{chainSelector: 1, subject: 0},
},
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
testRmnCurse(t, tc)
})
}
}

func testRmnCurse(t *testing.T, tc CurseTestCase) {
e := NewMemoryEnvironment(t, WithChains(2))

config := RMNCurseConfig{
HomeChainSelector: e.HomeChainSel,
CurseActions: tc.curseActionsBuilder,
CurseReason: "test curse",
}

if tc.useMCMS {
config.MCMS = &MCMSConfig{
MinDelay: 0,
}
}

NewRMNCurseChangeset(e.Env, config)
}
29 changes: 0 additions & 29 deletions deployment/ccip/changeset/cs_update_rmn_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"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/ccip-owner-contracts/pkg/proposal/mcms"
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock"

Expand Down Expand Up @@ -408,34 +407,6 @@ func NewPromoteCandidateConfigChangeset(e deployment.Environment, config Promote
}, nil
}

func buildTimelockPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*proposalutils.TimelockExecutionContracts {
timelocksPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts)
for _, chain := range e.Chains {
timelocksPerChain[chain.Selector] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[chain.Selector].Timelock,
CallProxy: state.Chains[chain.Selector].CallProxy,
}
}
return timelocksPerChain
}

func buildTimelockAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address {
timelocksPerChain := buildTimelockPerChain(e, state)
timelockAddressPerChain := make(map[uint64]common.Address)
for chain, timelock := range timelocksPerChain {
timelockAddressPerChain[chain] = timelock.Timelock.Address()
}
return timelockAddressPerChain
}

func buildProposerPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*gethwrappers.ManyChainMultiSig {
proposerPerChain := make(map[uint64]*gethwrappers.ManyChainMultiSig)
for _, chain := range e.Chains {
proposerPerChain[chain.Selector] = state.Chains[chain.Selector].ProposerMcm
}
return proposerPerChain
}

func buildRMNRemotePerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*rmn_remote.RMNRemote {
timelocksPerChain := make(map[uint64]*rmn_remote.RMNRemote)
for _, chain := range e.Chains {
Expand Down
131 changes: 131 additions & 0 deletions deployment/ccip/changeset/deployer_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package changeset

import (
"context"
"fmt"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"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 DeployerGroup struct {
e deployment.Environment
state CCIPOnChainState
mcmConfig *MCMSConfig
transactions map[uint64][]*types.Transaction
}

func NewDeployerGroup(e deployment.Environment, state CCIPOnChainState, mcmConfig *MCMSConfig) *DeployerGroup {
return &DeployerGroup{
e: e,
mcmConfig: mcmConfig,
state: state,
transactions: make(map[uint64][]*types.Transaction),
}
}

func (d *DeployerGroup) getDeployer(chain uint64) *bind.TransactOpts {
sim := deployment.SimTransactOpts()
oldSigner := sim.Signer
sim.Signer = func(a common.Address, t *types.Transaction) (*types.Transaction, error) {
tx, err := oldSigner(a, t)
if err != nil {
return nil, err
}
d.transactions[chain] = append(d.transactions[chain], tx)
return tx, nil
}
return sim
}

func (d *DeployerGroup) enact(deploymentDescription string) (deployment.ChangesetOutput, error) {
if d.mcmConfig != nil {
return d.enactMcms(deploymentDescription)
} else {
return d.enactDeployer()
}
}

func (d *DeployerGroup) enactMcms(deploymentDescription string) (deployment.ChangesetOutput, error) {
batches := make([]timelock.BatchChainOperation, 0)
for selector, txs := range d.transactions {
mcmOps := make([]mcms.Operation, len(txs))
for i, tx := range txs {
mcmOps[i] = mcms.Operation{
To: *tx.To(),
Data: tx.Data(),
Value: tx.Value(),
}
}
batches = append(batches, timelock.BatchChainOperation{
ChainIdentifier: mcms.ChainIdentifier(selector),
Batch: mcmOps,
})
}

timelocksPerChain := buildTimelockAddressPerChain(d.e, d.state)

proposerMCMSes := buildProposerPerChain(d.e, d.state)

prop, err := proposalutils.BuildProposalFromBatches(
timelocksPerChain,
proposerMCMSes,
batches,
deploymentDescription,
d.mcmConfig.MinDelay,
)

if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to build proposal %w", err)
}

return deployment.ChangesetOutput{
Proposals: []timelock.MCMSWithTimelockProposal{*prop},
}, nil
}

func (d *DeployerGroup) enactDeployer() (deployment.ChangesetOutput, error) {
for selector, txs := range d.transactions {
for _, tx := range txs {
err := d.e.Chains[selector].Client.SendTransaction(context.Background(), tx)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to send transaction: %w", err)
}
}
}
return deployment.ChangesetOutput{}, nil
}

func buildTimelockPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*proposalutils.TimelockExecutionContracts {
timelocksPerChain := make(map[uint64]*proposalutils.TimelockExecutionContracts)
for _, chain := range e.Chains {
timelocksPerChain[chain.Selector] = &proposalutils.TimelockExecutionContracts{
Timelock: state.Chains[chain.Selector].Timelock,
CallProxy: state.Chains[chain.Selector].CallProxy,
}
}
return timelocksPerChain
}

func buildTimelockAddressPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]common.Address {
timelocksPerChain := buildTimelockPerChain(e, state)
timelockAddressPerChain := make(map[uint64]common.Address)
for chain, timelock := range timelocksPerChain {
timelockAddressPerChain[chain] = timelock.Timelock.Address()
}
return timelockAddressPerChain
}

func buildProposerPerChain(e deployment.Environment, state CCIPOnChainState) map[uint64]*gethwrappers.ManyChainMultiSig {
proposerPerChain := make(map[uint64]*gethwrappers.ManyChainMultiSig)
for _, chain := range e.Chains {
proposerPerChain[chain.Selector] = state.Chains[chain.Selector].ProposerMcm
}
return proposerPerChain
}

0 comments on commit d76dcea

Please sign in to comment.