Skip to content

Commit 8812d36

Browse files
authored
Complete fast curse tests (#1332)
* completing fast curse test * deterministic curse id
1 parent db3c7bd commit 8812d36

File tree

6 files changed

+240
-38
lines changed

6 files changed

+240
-38
lines changed

chains/evm/deployment/fastcurse_test.go

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,28 @@ import (
1111
"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
1212
"github.com/smartcontractkit/chainlink-deployments-framework/deployment"
1313
"github.com/smartcontractkit/chainlink-evm/pkg/utils"
14+
mcms_types "github.com/smartcontractkit/mcms/types"
1415

1516
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_0_0/adapters"
17+
adaptersv1_5_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_5_0/adapters"
18+
"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_6_0/rmn_remote"
1619
"github.com/smartcontractkit/chainlink-ccip/deployment/deploy"
20+
"github.com/smartcontractkit/chainlink-ccip/deployment/fastcurse"
1721
"github.com/smartcontractkit/chainlink-ccip/deployment/testhelpers"
22+
"github.com/smartcontractkit/chainlink-ccip/deployment/utils/changesets"
23+
"github.com/smartcontractkit/chainlink-ccip/deployment/utils/mcms"
1824

1925
"github.com/smartcontractkit/chainlink-deployments-framework/engine/test/environment"
2026
cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
2127
"github.com/stretchr/testify/require"
2228

2329
"github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_5_0/rmn_contract"
30+
deploymentutils "github.com/smartcontractkit/chainlink-ccip/deployment/utils"
2431

2532
"github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/utils/operations/contract"
2633
routerops1_2 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_2_0/operations/router"
2734
rmnops1_5 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_5_0/operations/rmn"
35+
adaptersv1_6_0 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_6_0/adapters"
2836
rmnremoteops1_6 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_6_0/operations/rmn_remote"
2937
)
3038

@@ -36,6 +44,7 @@ func TestFastCurse(t *testing.T) {
3644
)
3745
require.NoError(t, err)
3846
bundle := env.OperationsBundle
47+
var rmnAddress, rmnRemoteAddress common.Address
3948
// deploy RMN 1.5 on chain1 and RMN 1.6 on chain2, set up routers, etc.
4049
chain := env.BlockChains.EVMChains()[chain1]
4150
deployRMNOp, err := cldf_ops.ExecuteOperation(bundle, rmnops1_5.Deploy, chain, contract.DeployInput[rmnops1_5.ConstructorArgs]{
@@ -65,6 +74,7 @@ func TestFastCurse(t *testing.T) {
6574
ChainSelector: chain1,
6675
Address: deployRMNOp.Output.Address,
6776
}))
77+
rmnAddress = common.HexToAddress(deployRMNOp.Output.Address)
6878
// deploy RMNRemote 1.6 on chain2
6979
chain = env.BlockChains.EVMChains()[chain2]
7080
deployRMNRemoteOp, err := cldf_ops.ExecuteOperation(bundle, rmnremoteops1_6.Deploy, chain, contract.DeployInput[rmnremoteops1_6.ConstructorArgs]{
@@ -82,6 +92,7 @@ func TestFastCurse(t *testing.T) {
8292
ChainSelector: chain2,
8393
Address: deployRMNRemoteOp.Output.Address,
8494
}))
95+
rmnRemoteAddress = common.HexToAddress(deployRMNRemoteOp.Output.Address)
8596
// deploy router in both chains
8697
for _, sel := range []uint64{chain1, chain2} {
8798
evmChain := env.BlockChains.EVMChains()[sel]
@@ -168,9 +179,165 @@ func TestFastCurse(t *testing.T) {
168179
// store addresses in ds
169180
allAddrRefs, err := output.DataStore.Addresses().Fetch()
170181
require.NoError(t, err)
182+
timelockAddrs := make(map[uint64]string)
171183
for _, addrRef := range allAddrRefs {
172184
require.NoError(t, ds.Addresses().Add(addrRef))
185+
if addrRef.Type == datastore.ContractType(deploymentutils.RBACTimelock) {
186+
timelockAddrs[addrRef.ChainSelector] = addrRef.Address
187+
}
173188
}
174-
189+
// update env datastore
175190
env.DataStore = ds.Seal()
191+
// transfer ownership of RMN and RMNRemote to respective MCMS
192+
transferOwnershipInput := deploy.TransferOwnershipInput{
193+
ChainInputs: []deploy.TransferOwnershipPerChainInput{
194+
{
195+
ChainSelector: chain1,
196+
ContractRef: []datastore.AddressRef{
197+
{
198+
Type: datastore.ContractType(rmnops1_5.ContractType),
199+
Version: semver.MustParse("1.5.0"),
200+
},
201+
},
202+
ProposedOwner: timelockAddrs[chain1],
203+
},
204+
{
205+
ChainSelector: chain2,
206+
ContractRef: []datastore.AddressRef{
207+
{
208+
Type: datastore.ContractType(rmnremoteops1_6.ContractType),
209+
Version: semver.MustParse("1.6.0"),
210+
},
211+
},
212+
ProposedOwner: timelockAddrs[chain2],
213+
},
214+
},
215+
AdapterVersion: semver.MustParse("1.0.0"),
216+
MCMS: mcms.Input{
217+
OverridePreviousRoot: false,
218+
ValidUntil: 3759765795,
219+
TimelockDelay: mcms_types.MustParseDuration("0s"),
220+
TimelockAction: mcms_types.TimelockActionSchedule,
221+
MCMSAddressRef: datastore.AddressRef{
222+
Type: datastore.ContractType(deploymentutils.ProposerManyChainMultisig),
223+
Qualifier: "test",
224+
Version: semver.MustParse("1.0.0"),
225+
},
226+
TimelockAddressRef: datastore.AddressRef{
227+
Type: datastore.ContractType(deploymentutils.RBACTimelock),
228+
Qualifier: "test",
229+
Version: semver.MustParse("1.0.0"),
230+
},
231+
Description: "Transfer ownership to timelock for fast curse test",
232+
},
233+
}
234+
235+
// register chain adapter
236+
cr := deploy.GetTransferOwnershipRegistry()
237+
evmAdapter := &adapters.EVMTransferOwnershipAdapter{}
238+
cr.RegisterAdapter(chainsel.FamilyEVM, transferOwnershipInput.AdapterVersion, evmAdapter)
239+
mcmsRegistry := changesets.NewMCMSReaderRegistry()
240+
evmMCMSReader := &adapters.EVMMCMSReader{}
241+
mcmsRegistry.RegisterMCMSReader(chainsel.FamilyEVM, evmMCMSReader)
242+
transferOwnershipChangeset := deploy.TransferOwnershipChangeset(cr, mcmsRegistry)
243+
output, err = transferOwnershipChangeset.Apply(*env, transferOwnershipInput)
244+
require.NoError(t, err)
245+
require.Greater(t, len(output.Reports), 0)
246+
require.Equal(t, 1, len(output.MCMSTimelockProposals))
247+
testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false)
248+
t.Logf("Transferred ownership of RMN and RMNRemote to respective MCMS")
249+
// now generate a curse proposal
250+
curseCfg := fastcurse.RMNCurseConfig{
251+
CurseActions: []fastcurse.CurseActionInput{
252+
{
253+
IsGlobalCurse: false,
254+
ChainSelector: chain1,
255+
SubjectChainSelector: chain2,
256+
Version: semver.MustParse("1.5.0"),
257+
},
258+
{
259+
IsGlobalCurse: false,
260+
ChainSelector: chain2,
261+
SubjectChainSelector: chain1,
262+
Version: semver.MustParse("1.6.0"),
263+
},
264+
},
265+
Force: false,
266+
MCMS: mcms.Input{
267+
OverridePreviousRoot: false,
268+
ValidUntil: 3759765795,
269+
TimelockDelay: mcms_types.MustParseDuration("0s"),
270+
TimelockAction: mcms_types.TimelockActionSchedule,
271+
MCMSAddressRef: datastore.AddressRef{
272+
Type: datastore.ContractType(deploymentutils.ProposerManyChainMultisig),
273+
Qualifier: "test",
274+
Version: semver.MustParse("1.0.0"),
275+
},
276+
TimelockAddressRef: datastore.AddressRef{
277+
Type: datastore.ContractType(deploymentutils.RBACTimelock),
278+
Qualifier: "test",
279+
Version: semver.MustParse("1.0.0"),
280+
},
281+
Description: "Curse proposal for fast curse test",
282+
},
283+
}
284+
curseReg := fastcurse.GetCurseRegistry()
285+
adv1_6_0 := adaptersv1_6_0.NewCurseAdapter()
286+
adv1_5_0 := adaptersv1_5_0.NewCurseAdapter()
287+
crInput1_6_0 := fastcurse.CurseRegistryInput{
288+
CursingFamily: chainsel.FamilyEVM,
289+
CursingVersion: semver.MustParse("1.6.0"),
290+
SubjectFamily: chainsel.FamilyEVM,
291+
CurseAdapter: adaptersv1_6_0.NewCurseAdapter(),
292+
CurseSubjectAdapter: adaptersv1_6_0.NewCurseAdapter(),
293+
}
294+
crInput1_5_0 := fastcurse.CurseRegistryInput{
295+
CursingFamily: chainsel.FamilyEVM,
296+
CursingVersion: semver.MustParse("1.5.0"),
297+
SubjectFamily: chainsel.FamilyEVM,
298+
CurseAdapter: adaptersv1_5_0.NewCurseAdapter(),
299+
CurseSubjectAdapter: adaptersv1_5_0.NewCurseAdapter(),
300+
}
301+
curseReg.RegisterNewCurse(crInput1_6_0)
302+
curseReg.RegisterNewCurse(crInput1_5_0)
303+
curseChangeset := fastcurse.CurseChangeset(curseReg, mcmsRegistry)
304+
output, err = curseChangeset.Apply(*env, curseCfg)
305+
require.NoError(t, err)
306+
require.Greater(t, len(output.Reports), 0)
307+
require.Equal(t, 1, len(output.MCMSTimelockProposals))
308+
testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false)
309+
310+
// check that the subjects were actually cursed
311+
rmnC, err := rmn_contract.NewRMNContract(rmnAddress, evmChain1.Client)
312+
require.NoError(t, err)
313+
isCursed, err := rmnC.IsCursed(nil, adv1_5_0.SelectorToSubject(chain2))
314+
require.NoError(t, err)
315+
require.True(t, isCursed, "subject on chain2 should be cursed on rmn in chain1")
316+
317+
rmnRemoteC, err := rmn_remote.NewRMNRemote(rmnRemoteAddress, evmChain2.Client)
318+
require.NoError(t, err)
319+
isCursed, err = rmnRemoteC.IsCursed(nil, adv1_6_0.SelectorToSubject(chain1))
320+
require.NoError(t, err)
321+
require.True(t, isCursed, "subject on chain1 should be cursed on rmnremote in chain2")
322+
t.Logf("Subjects successfully cursed %x on chain1 %d and %x on chain2 %d", adv1_5_0.SelectorToSubject(chain2), chain1, adv1_6_0.SelectorToSubject(chain1), chain2)
323+
324+
// Now uncurse the subjects
325+
// reset the operation bundle to clear any cached values
326+
env.OperationsBundle = cldf_ops.NewBundle(env.GetContext, env.Logger, cldf_ops.NewMemoryReporter())
327+
uncurseChangeset := fastcurse.UncurseChangeset(curseReg, mcmsRegistry)
328+
output, err = uncurseChangeset.Apply(*env, curseCfg)
329+
require.NoError(t, err)
330+
require.Greater(t, len(output.Reports), 0)
331+
require.Equal(t, 1, len(output.MCMSTimelockProposals))
332+
testhelpers.ProcessTimelockProposals(t, *env, output.MCMSTimelockProposals, false)
333+
334+
// check that the subjects were actually uncursed
335+
isCursed, err = rmnC.IsCursed(nil, adv1_5_0.SelectorToSubject(chain2))
336+
require.NoError(t, err)
337+
require.False(t, isCursed, "subject on chain2 should be uncursed on rmn in chain1")
338+
339+
isCursed, err = rmnRemoteC.IsCursed(nil, adv1_6_0.SelectorToSubject(chain1))
340+
require.NoError(t, err)
341+
require.False(t, isCursed, "subject on chain1 should be uncursed on rmnremote in chain2")
342+
t.Logf("Subjects successfully uncursed %x on chain1 %d and %x on chain2 %d", adv1_5_0.SelectorToSubject(chain2), chain1, adv1_6_0.SelectorToSubject(chain1), chain2)
176343
}

chains/evm/deployment/utils/operations/contract/write.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package contract
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67

@@ -130,7 +131,7 @@ func NewWrite[ARGS any, C any](params WriteParams[ARGS, C]) *operations.Operatio
130131
},
131132
To: input.Address.Hex(),
132133
Data: tx.Data(),
133-
AdditionalFields: []byte{0x7B, 0x7D}, // "{}" in bytes
134+
AdditionalFields: json.RawMessage(`{"value": 0}`),
134135
},
135136
}, nil
136137
},

chains/evm/deployment/v1_5_0/adapters/fastcurse.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package adapters
22

33
import (
4-
"crypto/rand"
4+
"crypto/sha256"
55
"encoding/binary"
66
"fmt"
77

@@ -32,19 +32,28 @@ func NewCurseAdapter() *CurseAdapter {
3232
return &CurseAdapter{}
3333
}
3434

35-
func (ca *CurseAdapter) Initialize(e cldf.Environment) error {
36-
ca.rmnAddressCache = make(map[uint64]common.Address)
37-
ca.routerAddressCache = make(map[uint64]common.Address)
38-
for _, chain := range e.BlockChains.EVMChains() {
35+
func (ca *CurseAdapter) Initialize(e cldf.Environment, selector uint64) error {
36+
if ca.rmnAddressCache == nil {
37+
ca.rmnAddressCache = make(map[uint64]common.Address)
38+
}
39+
if ca.routerAddressCache == nil {
40+
ca.routerAddressCache = make(map[uint64]common.Address)
41+
}
42+
chain, ok := e.BlockChains.EVMChains()[selector]
43+
if !ok {
44+
return fmt.Errorf("no EVM chain found for selector %d", selector)
45+
}
46+
if _, exists := ca.rmnAddressCache[chain.Selector]; !exists {
3947
rmnAddr, err := datastore_utils.FindAndFormatRef(e.DataStore, datastore.AddressRef{
4048
Type: datastore.ContractType(ops.ContractType),
41-
Version: semver.MustParse("1.6.0"),
49+
Version: semver.MustParse("1.5.0"),
4250
}, chain.ChainSelector(), evmds.ToEVMAddress)
4351
if err != nil {
4452
return err
4553
}
4654
ca.rmnAddressCache[chain.Selector] = rmnAddr
47-
55+
}
56+
if _, exists := ca.routerAddressCache[chain.Selector]; !exists {
4857
routerAddr, err := datastore_utils.FindAndFormatRef(e.DataStore, datastore.AddressRef{
4958
Type: datastore.ContractType(routerops.ContractType),
5059
Version: semver.MustParse("1.2.0"),
@@ -67,7 +76,6 @@ func (ca *CurseAdapter) IsSubjectCursedOnChain(e cldf.Environment, selector uint
6776
if !ok {
6877
return false, fmt.Errorf("no EVM chain found for selector %d", selector)
6978
}
70-
7179
isCursedRep, err := cldf_ops.ExecuteOperation(e.OperationsBundle, ops.IsCursed, chain, contract.FunctionInput[api.Subject]{
7280
ChainSelector: chain.Selector,
7381
Address: rmnAddr,
@@ -126,7 +134,7 @@ func (ca *CurseAdapter) Curse() *cldf_ops.Sequence[api.CurseInput, sequences.OnC
126134
if err != nil {
127135
return sequences.OnChainOutput{}, fmt.Errorf("failed to get config details for RMN at %s on chain %d: %w", rmnAddr.String(), chain.Selector, err)
128136
}
129-
curseID, err := generateCurseID(cfgDetailsOp.Output.Version)
137+
curseID, err := generateCurseID(cfgDetailsOp.Output.Version, in.Subjects)
130138
if err != nil {
131139
return sequences.OnChainOutput{}, fmt.Errorf("failed to generate curse ID for RMN at %s on chain %d: %w", rmnAddr.String(), chain.Selector, err)
132140
}
@@ -201,13 +209,27 @@ func (ca *CurseAdapter) Uncurse() *cldf_ops.Sequence[api.CurseInput, sequences.O
201209
})
202210
}
203211

204-
func generateCurseID(cfgVersion uint32) ([16]byte, error) {
212+
func generateCurseID(cfgVersion uint32, subjects [][16]byte) ([16]byte, error) {
205213
var out [16]byte
206214

207-
_, err := rand.Read(out[4:])
215+
h := sha256.New()
216+
217+
// Include cfgVersion
218+
err := binary.Write(h, binary.BigEndian, cfgVersion)
208219
if err != nil {
209-
return [16]byte{}, fmt.Errorf("failed to generate random bytes for curse ID: %w", err)
220+
return [16]byte{}, err
221+
}
222+
223+
// Include all subjects in deterministic order
224+
for _, s := range subjects {
225+
h.Write(s[:])
210226
}
227+
228+
sum := h.Sum(nil)
229+
230+
// Copy first 16 bytes of hash into output
231+
copy(out[:], sum[:16])
232+
211233
binary.BigEndian.PutUint32(out[0:4], cfgVersion)
212234

213235
return out, nil

chains/evm/deployment/v1_6_0/adapters/fastcurse.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,26 @@ func NewCurseAdapter() *CurseAdapter {
2929
return &CurseAdapter{}
3030
}
3131

32-
func (ca *CurseAdapter) Initialize(e cldf.Environment) error {
33-
ca.rmnAddressCache = make(map[uint64]common.Address)
34-
ca.routerAddressCache = make(map[uint64]common.Address)
32+
func (ca *CurseAdapter) Initialize(e cldf.Environment, selector uint64) error {
33+
if ca.rmnAddressCache == nil {
34+
ca.rmnAddressCache = make(map[uint64]common.Address)
35+
}
36+
if ca.routerAddressCache == nil {
37+
ca.routerAddressCache = make(map[uint64]common.Address)
38+
}
3539

36-
for _, chain := range e.BlockChains.EVMChains() {
40+
chain, ok := e.BlockChains.EVMChains()[selector]
41+
if !ok {
42+
return fmt.Errorf("no EVM chain found for selector %d", selector)
43+
}
44+
if _, exists := ca.rmnAddressCache[chain.Selector]; !exists {
3745
rmnAddr, err := rmnAddressOnChain(e, chain.Selector)
3846
if err != nil {
3947
return fmt.Errorf("failed to find RMN address on chain %d: %w", chain.Selector, err)
4048
}
4149
ca.rmnAddressCache[chain.Selector] = rmnAddr
42-
50+
}
51+
if _, exists := ca.routerAddressCache[chain.Selector]; !exists {
4352
routerAddr, err := routerAddressOnChain(e, chain.Selector)
4453
if err != nil {
4554
return fmt.Errorf("failed to find router address on chain %d: %w", chain.Selector, err)
@@ -90,7 +99,7 @@ func (ca *CurseAdapter) IsCurseEnabledForChain(e cldf.Environment, selector uint
9099
// locate rmn address on chain
91100
_, ok := ca.rmnAddressCache[selector]
92101
if !ok {
93-
return false, fmt.Errorf("no RMN address cached for chain %d", selector)
102+
return false, fmt.Errorf("no RMNRemote address cached for chain %d", selector)
94103
}
95104
return true, nil
96105
}

0 commit comments

Comments
 (0)