Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
carte7000 committed Jan 8, 2025
1 parent d76dcea commit 305e712
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 22 deletions.
149 changes: 128 additions & 21 deletions deployment/ccip/changeset/cs_rmn_curse_uncurse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,167 @@ package changeset

import (
"testing"

"github.com/ethereum/go-ethereum/common"
commonchangeset "github.com/smartcontractkit/chainlink/deployment/common/changeset"
"github.com/stretchr/testify/require"
)

type curseAssertion struct {
chainSelector uint64
subject uint64
chainId uint64
subject uint64
global_curse bool
cursed bool
}

type CurseTestCase struct {
useMCMS bool
name string
curseActionsBuilder []CurseAction
curseActionsBuilder func(mapIdToSelectorFunc) []CurseAction
curseAssertions []curseAssertion
}

type mapIdToSelectorFunc func(uint64) uint64

func TestRMNCurse(t *testing.T) {
t.Parallel()
testCases := []CurseTestCase{
// {
// useMCMS: true,
// name: "with MCMS",
// },
{
useMCMS: false,
name: "without MCMS",
curseActionsBuilder: []CurseAction{CurseLane(0, 1)},
useMCMS: false,
name: "lane",
curseActionsBuilder: func(mapIdToSelector mapIdToSelectorFunc) []CurseAction {
return []CurseAction{CurseLane(mapIdToSelector(0), mapIdToSelector(1))}
},
curseAssertions: []curseAssertion{
{chainSelector: 0, subject: 1},
{chainSelector: 1, subject: 0},
{chainId: 0, subject: 1, cursed: true},
{chainId: 0, subject: 2, cursed: false},
{chainId: 1, subject: 0, cursed: true},
{chainId: 1, subject: 2, cursed: false},
{chainId: 2, subject: 0, cursed: false},
{chainId: 2, subject: 1, cursed: false},
},
},
{
useMCMS: false,
name: "chain",
curseActionsBuilder: func(mapIdToSelector mapIdToSelectorFunc) []CurseAction {
return []CurseAction{CurseChain(mapIdToSelector(0))}
},
curseAssertions: []curseAssertion{
{chainId: 0, global_curse: true, cursed: true},
{chainId: 1, subject: 0, cursed: true},
{chainId: 1, subject: 2, cursed: false},
{chainId: 2, subject: 0, cursed: true},
{chainId: 2, subject: 1, cursed: false},
},
},
}

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

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

mapIdToSelector := func(id uint64) uint64 {
return e.Env.AllChainSelectors()[id]
}

verifyNoActiveCurseOnAllChains(t, &e)

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

if tc.useMCMS {
config.MCMS = &MCMSConfig{
MinDelay: 0,
_, err := NewRMNCurseChangeset(e.Env, config)
require.NoError(t, err)

verifyTestCaseAssertions(t, &e, tc, mapIdToSelector)
}

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

mapIdToSelector := func(id uint64) uint64 {
return e.Env.AllChainSelectors()[id]
}

config := RMNCurseConfig{
HomeChainSelector: e.HomeChainSel,
CurseActions: tc.curseActionsBuilder(mapIdToSelector),
CurseReason: "test curse",
MCMS: &MCMSConfig{MinDelay: 0},
}

state, err := LoadOnchainState(e.Env)
require.NoError(t, err)

verifyNoActiveCurseOnAllChains(t, &e)

timelocksPerChain := buildTimelockPerChain(e.Env, state)

contractsByChain := make(map[uint64][]common.Address)
rmnRemoteAddressesByChain := buildRMNRemoteAddressPerChain(e.Env, state)
for chainSelector, rmnRemoteAddress := range rmnRemoteAddressesByChain {
contractsByChain[chainSelector] = []common.Address{rmnRemoteAddress}
}

contractsByChain[e.HomeChainSel] = append(contractsByChain[e.HomeChainSel], state.Chains[e.HomeChainSel].RMNHome.Address())

// This is required because RMN Contracts is initially owned by the deployer
_, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(commonchangeset.TransferToMCMSWithTimelock),
Config: commonchangeset.TransferToMCMSWithTimelockConfig{
ContractsByChain: contractsByChain,
MinDelay: 0,
},
},
})
require.NoError(t, err)

_, err = commonchangeset.ApplyChangesets(t, e.Env, timelocksPerChain, []commonchangeset.ChangesetApplication{
{
Changeset: commonchangeset.WrapChangeSet(NewRMNCurseChangeset),
Config: config,
},
})
require.NoError(t, err)

verifyTestCaseAssertions(t, &e, tc, mapIdToSelector)
}

func verifyTestCaseAssertions(t *testing.T, e *DeployedEnv, tc CurseTestCase, mapIdToSelector mapIdToSelectorFunc) {
state, err := LoadOnchainState(e.Env)
require.NoError(t, err)

for _, assertion := range tc.curseAssertions {
cursedSubject := subjectToByte16(mapIdToSelector(assertion.subject))
if assertion.global_curse {
cursedSubject = subjectToByte16(GLOBAL_CURSE_SUBJECT)
}

isCursed, err := state.Chains[mapIdToSelector(assertion.chainId)].RMNRemote.IsCursed(nil, cursedSubject)
require.NoError(t, err)
require.Equal(t, assertion.cursed, isCursed, "chain %d subject %d", assertion.chainId, assertion.subject)
}
}

NewRMNCurseChangeset(e.Env, config)
func verifyNoActiveCurseOnAllChains(t *testing.T, e *DeployedEnv) {
state, err := LoadOnchainState(e.Env)
require.NoError(t, err)

for _, chain := range e.Env.Chains {
isCursed, err := state.Chains[chain.Selector].RMNRemote.IsCursed0(nil)
require.NoError(t, err)
require.False(t, isCursed, "chain %d", chain.Selector)
}
}
37 changes: 36 additions & 1 deletion deployment/ccip/changeset/deployer_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ type DeployerGroup struct {
transactions map[uint64][]*types.Transaction
}

/*
DeployerGroup is an abstraction that lets developers write their changeset
without needing to know if it's executed using a DeployerKey or an MCMS proposal.
Example usage:
deployerGroup := NewDeployerGroup(e, state, mcmConfig)
selector := 0
# Get the right deployer key for the chain
deployer := deployerGroup.getDeployer(selector)
state.Chains[selector].RMNRemote.Curse()
# Execute the transaction or create the proposal
deployerGroup.enact("Curse RMNRemote")
*/
func NewDeployerGroup(e deployment.Environment, state CCIPOnChainState, mcmConfig *MCMSConfig) *DeployerGroup {
return &DeployerGroup{
e: e,
Expand All @@ -31,7 +45,23 @@ func NewDeployerGroup(e deployment.Environment, state CCIPOnChainState, mcmConfi
}

func (d *DeployerGroup) getDeployer(chain uint64) *bind.TransactOpts {
sim := deployment.SimTransactOpts()
txOpts := d.e.Chains[chain].DeployerKey
if d.mcmConfig != nil {
txOpts = deployment.SimTransactOpts()
}
sim := &bind.TransactOpts{
From: txOpts.From,
Signer: txOpts.Signer,
GasLimit: txOpts.GasLimit,
GasPrice: txOpts.GasPrice,
Nonce: txOpts.Nonce,
Value: txOpts.Value,
GasFeeCap: txOpts.GasFeeCap,
GasTipCap: txOpts.GasTipCap,
Context: txOpts.Context,
AccessList: txOpts.AccessList,
NoSend: true,
}
oldSigner := sim.Signer
sim.Signer = func(a common.Address, t *types.Transaction) (*types.Transaction, error) {
tx, err := oldSigner(a, t)
Expand Down Expand Up @@ -97,6 +127,11 @@ func (d *DeployerGroup) enactDeployer() (deployment.ChangesetOutput, error) {
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("failed to send transaction: %w", err)
}

_, err = d.e.Chains[selector].Confirm(tx)
if err != nil {
return deployment.ChangesetOutput{}, fmt.Errorf("waiting for tx to be mined failed: %w", err)
}
}
}
return deployment.ChangesetOutput{}, nil
Expand Down

0 comments on commit 305e712

Please sign in to comment.