@@ -2,6 +2,7 @@ package solana
2
2
3
3
import (
4
4
"context"
5
+ "crypto/ecdsa"
5
6
"fmt"
6
7
"testing"
7
8
"time"
@@ -21,16 +22,60 @@ import (
21
22
"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
22
23
cpistubbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/external_program_cpi_stub"
23
24
mcmbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
25
+ rmnremotebindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/rmn_remote"
24
26
timelockbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
25
27
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/accesscontroller"
26
28
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common"
27
29
timelockutils "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/timelock"
30
+ "github.com/smartcontractkit/mcms"
31
+ mcmssdk "github.com/smartcontractkit/mcms/sdk"
28
32
solanasdk "github.com/smartcontractkit/mcms/sdk/solana"
29
33
mcmstypes "github.com/smartcontractkit/mcms/types"
30
34
31
35
"github.com/smartcontractkit/timelock-worker/pkg/timelock"
36
+ evmtests "github.com/smartcontractkit/timelock-worker/tests/integration/evm"
32
37
)
33
38
39
+ var solChainSelector = mcmstypes .ChainSelector (chainsel .SOLANA_DEVNET .Selector )
40
+
41
+ func (s * solanaIntegrationTestSuite ) scheduleProposal (
42
+ proposal * mcms.TimelockProposal , signerKey * ecdsa.PrivateKey , auth solana.PrivateKey ,
43
+ ) {
44
+ converters := map [mcmstypes.ChainSelector ]mcmssdk.TimelockConverter {solChainSelector : solanasdk.TimelockConverter {}}
45
+ mcmProposal , _ , err := proposal .Convert (s .Ctx , converters )
46
+ s .Require ().NoError (err )
47
+
48
+ inspectors := map [mcmstypes.ChainSelector ]mcmssdk.Inspector {solChainSelector : solanasdk .NewInspector (s .solanaClient )}
49
+ encoders , err := mcmProposal .GetEncoders ()
50
+ s .Require ().NoError (err )
51
+ encoder := encoders [solChainSelector ]
52
+ executor := solanasdk .NewExecutor (encoder .(* solanasdk.Encoder ), s .solanaClient , auth )
53
+ executors := map [mcmstypes.ChainSelector ]mcmssdk.Executor {solChainSelector : executor }
54
+
55
+ signable , err := mcms .NewSignable (& mcmProposal , inspectors )
56
+ s .Require ().NoError (err )
57
+ s .Require ().NotNil (signable )
58
+ _ , err = signable .SignAndAppend (mcms .NewPrivateKeySigner (signerKey ))
59
+ s .Require ().NoError (err )
60
+
61
+ executable , err := mcms .NewExecutable (& mcmProposal , executors )
62
+ s .Require ().NoError (err )
63
+
64
+ signature , err := executable .SetRoot (s .Ctx , solChainSelector )
65
+ s .Require ().NoError (err )
66
+ _ , err = solana .SignatureFromBase58 (signature .Hash )
67
+ s .Require ().NoError (err )
68
+ s .Logf ("mcm.SetRoot()" )
69
+
70
+ for i := range mcmProposal .Operations {
71
+ s .Logf ("mcm.Execute(%d)" , i )
72
+ signature , err = executable .Execute (s .Ctx , i )
73
+ s .Require ().NoError (err )
74
+ _ , err = solana .SignatureFromBase58 (signature .Hash )
75
+ s .Require ().NoError (err )
76
+ }
77
+ }
78
+
34
79
// getBatchAddAccessIxs returns a slice of instructions to batch add access for multiple addresses to a specific role in the Solana timelock instance.
35
80
func (s * solanaIntegrationTestSuite ) getBatchAddAccessIxs (
36
81
ctx context.Context , timelockID [32 ]byte , roleAcAccount solana.PublicKey , role timelockbindings.Role ,
@@ -70,14 +115,13 @@ func (s *solanaIntegrationTestSuite) getBatchAddAccessIxs(
70
115
return ixs
71
116
}
72
117
73
- // AssignRoleToAccounts assigns the specified role to the provided accounts in the Solana timelock instance.
74
- func (s * solanaIntegrationTestSuite ) AssignRoleToAccounts (
75
- ctx context.Context , pdaSeed solanasdk.PDASeed , auth solana.PrivateKey ,
76
- accounts []solana.PublicKey , role timelockbindings.Role ,
118
+ // assignRoleToAccounts assigns the specified role to the provided accounts in the Solana timelock instance.
119
+ func (s * solanaIntegrationTestSuite ) assignRoleToAccounts (
120
+ pdaSeed solanasdk.PDASeed , accounts []solana.PublicKey , role timelockbindings.Role ,
77
121
) {
78
- instructions := s .getBatchAddAccessIxs (ctx , pdaSeed , s .RoleMap [role ].AccessController .PublicKey (),
79
- role , accounts , auth , 1 )
80
- testutils .SendAndConfirm (ctx , s .T (), s .solanaClient , instructions , auth , rpc .CommitmentConfirmed )
122
+ instructions := s .getBatchAddAccessIxs (s . Ctx , pdaSeed , s .RoleMap [role ].AccessController .PublicKey (),
123
+ role , accounts , s . TestPrivateKey , 1 )
124
+ testutils .SendAndConfirm (s . Ctx , s .T (), s .solanaClient , instructions , s . TestPrivateKey , rpc .CommitmentConfirmed )
81
125
}
82
126
83
127
func (s * solanaIntegrationTestSuite ) initializeAccessController (auth solana.PrivateKey ) {
@@ -108,17 +152,6 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
108
152
s .initializeAccessController (admin )
109
153
110
154
s .Run ("init timelock" , func () {
111
- data , accErr := s .solanaClient .GetAccountInfoWithOpts (s .Ctx , s .TimelockProgramID , & rpc.GetAccountInfoOpts {
112
- Commitment : rpc .CommitmentConfirmed ,
113
- })
114
- s .Require ().NoError (accErr )
115
-
116
- var programData struct {
117
- DataType uint32
118
- Address solana.PublicKey
119
- }
120
- s .Require ().NoError (bin .UnmarshalBorsh (& programData , data .Bytes ()))
121
-
122
155
configPDA , err2 := solanasdk .FindTimelockConfigPDA (s .TimelockProgramID , pdaSeed )
123
156
s .Require ().NoError (err2 )
124
157
@@ -129,7 +162,7 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
129
162
admin .PublicKey (),
130
163
solana .SystemProgramID ,
131
164
s .TimelockProgramID ,
132
- programData . Address ,
165
+ s . getProgramDataAddress ( s . TimelockProgramID ) ,
133
166
s .AccessControllerProgramID ,
134
167
s .ProposerAccessController ,
135
168
s .ExecutorAccessController ,
@@ -159,7 +192,7 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
159
192
accounts = append (accounts , account .PublicKey ())
160
193
}
161
194
162
- s .AssignRoleToAccounts ( s . Ctx , pdaSeed , admin , accounts , roleAccounts .Role )
195
+ s .assignRoleToAccounts ( pdaSeed , accounts , roleAccounts .Role )
163
196
164
197
for _ , account := range roleAccounts .Accounts {
165
198
found , err := accesscontroller .HasAccess (s .Ctx , s .solanaClient , roleAccounts .AccessController .PublicKey (),
@@ -185,18 +218,8 @@ func (s *solanaIntegrationTestSuite) initializeMcm(pdaSeed [32]byte, signer eth.
185
218
s .Require ().NoError (err )
186
219
chainSelector := chainsel .SOLANA_DEVNET .Selector
187
220
188
- var programData struct {
189
- DataType uint32
190
- Address solana.PublicKey
191
- }
192
- data , err := s .solanaClient .GetAccountInfoWithOpts (s .Ctx , s .McmProgramID ,
193
- & rpc.GetAccountInfoOpts {Commitment : rpc .CommitmentConfirmed })
194
- s .Require ().NoError (err )
195
- err = bin .UnmarshalBorsh (& programData , data .Bytes ())
196
- s .Require ().NoError (err )
197
-
198
221
ix , err := mcmbindings .NewInitializeInstruction (chainSelector , pdaSeed , configPDA , auth .PublicKey (),
199
- solana .SystemProgramID , s .McmProgramID , programData . Address , rootMetadataPDA ,
222
+ solana .SystemProgramID , s .McmProgramID , s . getProgramDataAddress ( s . McmProgramID ) , rootMetadataPDA ,
200
223
expiringRootAndOpCountPDA ).ValidateAndBuild ()
201
224
s .Require ().NoError (err )
202
225
@@ -238,7 +261,7 @@ func (s *solanaIntegrationTestSuite) getInitAccessControllersIxs(ctx context.Con
238
261
return ixs
239
262
}
240
263
241
- func (s * solanaIntegrationTestSuite ) setupCPIStub ( ctx context. Context ) {
264
+ func (s * solanaIntegrationTestSuite ) initializeCPIStub ( ) {
242
265
cpistubbindings .SetProgramID (s .StubProgramID )
243
266
244
267
admin , err := solana .PrivateKeyFromBase58 (privateKey )
@@ -251,8 +274,82 @@ func (s *solanaIntegrationTestSuite) setupCPIStub(ctx context.Context) {
251
274
ValidateAndBuild ()
252
275
s .Require ().NoError (err )
253
276
254
- _ , err = sendAndConfirm (ctx , s .T (), s .solanaClient , admin , []solana.Instruction {instruction })
277
+ _ , err = sendAndConfirm (s .Ctx , s .T (), s .solanaClient , admin , []solana.Instruction {instruction })
278
+ s .Require ().NoError (err )
279
+ }
280
+
281
+ func (s * solanaIntegrationTestSuite ) initializeRmnRemote () {
282
+ rmnremotebindings .SetProgramID (s .RmnRemoteProgramID )
283
+
284
+ auth , err := solana .PrivateKeyFromBase58 (privateKey )
285
+ s .Require ().NoError (err )
286
+ configPDA , _ , err := solana .FindProgramAddress ([][]byte {[]byte ("config" )}, s .RmnRemoteProgramID )
287
+ s .Require ().NoError (err )
288
+ cursesPDA , _ , err := solana .FindProgramAddress ([][]byte {[]byte ("curses" )}, s .RmnRemoteProgramID )
289
+ s .Require ().NoError (err )
290
+ // s.Logf("ACCOUNTS:\n%v\n%v\n%v\n%v\n%v\n%v\n", configPDA, cursesPDA, auth.PublicKey(),
291
+ // solana.SystemProgramID, s.RmnRemoteProgramID, s.getProgramDataAddress(s.RmnRemoteProgramID))
292
+
293
+ instruction , err := rmnremotebindings .NewInitializeInstruction (configPDA , cursesPDA , auth .PublicKey (),
294
+ solana .SystemProgramID , s .RmnRemoteProgramID , s .getProgramDataAddress (s .RmnRemoteProgramID )).ValidateAndBuild ()
295
+ s .Require ().NoError (err )
296
+
297
+ _ , err = sendAndConfirm (s .Ctx , s .T (), s .solanaClient , auth , []solana.Instruction {instruction })
298
+ s .Require ().NoError (err )
299
+ }
300
+
301
+ func (s * solanaIntegrationTestSuite ) transferOwnershipRmnRemote (seed solanasdk.PDASeed , signer evmtests.TestAccount ) {
302
+ configPDA , _ , err := solana .FindProgramAddress ([][]byte {[]byte ("config" )}, s .RmnRemoteProgramID )
303
+ s .Require ().NoError (err )
304
+ cursesPDA , _ , err := solana .FindProgramAddress ([][]byte {[]byte ("curses" )}, s .RmnRemoteProgramID )
305
+ s .Require ().NoError (err )
306
+ timelockSignerPDA , err := solanasdk .FindTimelockSignerPDA (s .TimelockProgramID , seed )
307
+ s .Require ().NoError (err )
308
+
309
+ transferInstruction , err := rmnremotebindings .NewTransferOwnershipInstruction (timelockSignerPDA , configPDA ,
310
+ cursesPDA , s .TestPrivateKey .PublicKey ()).ValidateAndBuild ()
311
+ s .Require ().NoError (err )
312
+
313
+ acceptInstruction , err := rmnremotebindings .NewAcceptOwnershipInstruction (configPDA ,
314
+ timelockSignerPDA ).ValidateAndBuild ()
315
+ s .Require ().NoError (err )
316
+
317
+ s .transferOwnership (transferInstruction , acceptInstruction , seed , signer )
318
+ s .Logf ("transferred ownership of RMNRemote to timelock" )
319
+ }
320
+
321
+ func (s * solanaIntegrationTestSuite ) transferOwnership (
322
+ transferInstruction , acceptInstruction solana.Instruction , seed solanasdk.PDASeed , signer evmtests.TestAccount ,
323
+ ) {
324
+ // --- transfer ---
325
+ _ , err := sendAndConfirm (s .Ctx , s .T (), s .solanaClient , s .TestPrivateKey , []solana.Instruction {transferInstruction })
326
+ s .Require ().NoError (err )
327
+
328
+ // --- accept ---
329
+ acceptOwnershipTransaction , err := solanasdk .NewTransactionFromInstruction (acceptInstruction , "" , nil )
255
330
s .Require ().NoError (err )
331
+
332
+ chainMetadata , err := solanasdk .NewChainMetadata (0 , s .McmProgramID , seed , s .ProposerAccessController ,
333
+ s .CancellerAccessController , s .BypasserAccessController )
334
+ s .Require ().NoError (err )
335
+ timelockAddress := solanasdk .ContractAddress (s .TimelockProgramID , seed )
336
+
337
+ proposal , err := mcms .NewTimelockProposalBuilder ().
338
+ SetValidUntil (uint32 (2051222400 )). // 2035-01-01T12:00:00 UTC
339
+ SetDescription ("proposal to accept ownership" ).
340
+ SetOverridePreviousRoot (true ).
341
+ SetVersion ("v1" ).
342
+ SetAction (mcmstypes .TimelockActionBypass ).
343
+ SetChainMetadata (map [mcmstypes.ChainSelector ]mcmstypes.ChainMetadata {solChainSelector : chainMetadata }).
344
+ AddTimelockAddress (solChainSelector , timelockAddress ).
345
+ AddOperation (mcmstypes.BatchOperation {
346
+ ChainSelector : solChainSelector ,
347
+ Transactions : []mcmstypes.Transaction {acceptOwnershipTransaction },
348
+ }).
349
+ Build ()
350
+ s .Require ().NoError (err )
351
+
352
+ s .scheduleProposal (proposal , signer .PrivateKey , s .TestPrivateKey )
256
353
}
257
354
258
355
// runTimelockWorkerSolana runs the Solana timelock worker with the provided parameters.
@@ -410,6 +507,21 @@ func (s *solanaIntegrationTestSuite) scheduleTestIx(
410
507
return ixStub , operationID
411
508
}
412
509
510
+ func (s * solanaIntegrationTestSuite ) getProgramDataAddress (programID solana.PublicKey ) solana.PublicKey {
511
+ opts := & rpc.GetAccountInfoOpts {Commitment : rpc .CommitmentConfirmed }
512
+ data , accErr := s .solanaClient .GetAccountInfoWithOpts (s .Ctx , programID , opts )
513
+ s .Require ().NoError (accErr )
514
+
515
+ var programData struct {
516
+ DataType uint32
517
+ Address solana.PublicKey
518
+ }
519
+ err := bin .UnmarshalBorsh (& programData , data .Bytes ())
520
+ s .Require ().NoError (err )
521
+
522
+ return programData .Address
523
+ }
524
+
413
525
func sendAndConfirm (
414
526
ctx context.Context , t * testing.T , client * rpc.Client , txPayer solana.PrivateKey , instructions []solana.Instruction ,
415
527
) (solana.Signature , error ) {
0 commit comments