Skip to content

Commit 8834869

Browse files
wip: better integration tests with rmnremote program
1 parent 8b99a31 commit 8834869

File tree

6 files changed

+198
-88
lines changed

6 files changed

+198
-88
lines changed

pkg/timelock/operations_solana.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"strings"
78

89
"github.com/gagliardetto/solana-go"
910
"github.com/gagliardetto/solana-go/rpc"
11+
"github.com/samber/lo"
1012
mcmssolanasdk "github.com/smartcontractkit/mcms/sdk/solana"
1113
mcmstypes "github.com/smartcontractkit/mcms/types"
1214

@@ -94,6 +96,10 @@ func (tw *WorkerSolana) getTransactionsInBatchOperation(
9496
IsSigner: account.IsSigner,
9597
})
9698
}
99+
accountsStr := strings.Join(lo.Map(instruction.Accounts, func(account timelockbindings.InstructionAccount, _ int) string {
100+
return fmt.Sprintf("%s W:%t S:%t", account.Pubkey, account.IsWritable, account.IsSigner)
101+
}), "\n ")
102+
tw.logger.Infof("OPERATIONS SOLANA - EXECUTE - ADDING TRANSACTION %d - ACCOUNTS:\n %s\n", i, accountsStr)
97103

98104
marshaledAdditionalFields, err := json.Marshal(additionalFields)
99105
if err != nil {

tests/integration/solana/compile-timelock-programs.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ if [[ -z "$COMMIT_HASH" ]]; then
4545
fi
4646

4747
# Programs to build
48-
PROGRAMS=("mcm" "timelock" "access-controller" "external-program-cpi-stub")
48+
PROGRAMS=("mcm" "timelock" "access-controller" "external-program-cpi-stub" "rmn-remote")
4949

5050
cd "${PROJECT_ROOT}"
5151

tests/integration/solana/config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ timelock = "DoajfR5tK24xVw51fWcawUZWhAXD8yrBJVacc13neVQA"
1010
mcm = "5vNJx78mz7KVMjhuipyr9jKBKcMrKYGdjGkgE4LUmjKk"
1111
external_program_cpi_stub = "2zZwzyptLqwFJFEFxjPvrdhiGpH9pJ3MfrrmZX6NTKxm"
1212
access_controller = "6KsN58MTnRQ8FfPaXHiFPPFGDRioikj9CdPvPxZJdCjb"
13+
rmn_remote = "RmnXLft1mSEwDgMKu2okYuHkiazxntFFcZFrrcXxYg7"

tests/integration/solana/suite.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type solanaIntegrationTestSuite struct {
3636
McmProgramID solana.PublicKey
3737
TimelockProgramID solana.PublicKey
3838
StubProgramID solana.PublicKey
39+
RmnRemoteProgramID solana.PublicKey
3940
AccessControllerProgramID solana.PublicKey
4041
RoleMap timelockutils.RoleMap
4142
ProposerAccessController solana.PublicKey
@@ -70,6 +71,7 @@ func (s *solanaIntegrationTestSuite) SetupSuite() {
7071
s.McmProgramID = solana.MustPublicKeyFromBase58(in.SolanaChain.SolanaPrograms["mcm"])
7172
s.TimelockProgramID = solana.MustPublicKeyFromBase58(in.SolanaChain.SolanaPrograms["timelock"])
7273
s.StubProgramID = solana.MustPublicKeyFromBase58(in.SolanaChain.SolanaPrograms["external_program_cpi_stub"])
74+
s.RmnRemoteProgramID = solana.MustPublicKeyFromBase58(in.SolanaChain.SolanaPrograms["rmn_remote"])
7375
s.AccessControllerProgramID = solana.MustPublicKeyFromBase58(in.SolanaChain.SolanaPrograms["access_controller"])
7476
s.TestPrivateKey = solana.MustPrivateKeyFromBase58(privateKey)
7577
}

tests/integration/solana/utils.go

Lines changed: 145 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package solana
22

33
import (
44
"context"
5+
"crypto/ecdsa"
56
"fmt"
67
"testing"
78
"time"
@@ -21,16 +22,60 @@ import (
2122
"github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
2223
cpistubbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/external_program_cpi_stub"
2324
mcmbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/mcm"
25+
rmnremotebindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/rmn_remote"
2426
timelockbindings "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
2527
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/accesscontroller"
2628
"github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/common"
2729
timelockutils "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/timelock"
30+
"github.com/smartcontractkit/mcms"
31+
mcmssdk "github.com/smartcontractkit/mcms/sdk"
2832
solanasdk "github.com/smartcontractkit/mcms/sdk/solana"
2933
mcmstypes "github.com/smartcontractkit/mcms/types"
3034

3135
"github.com/smartcontractkit/timelock-worker/pkg/timelock"
36+
evmtests "github.com/smartcontractkit/timelock-worker/tests/integration/evm"
3237
)
3338

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+
3479
// getBatchAddAccessIxs returns a slice of instructions to batch add access for multiple addresses to a specific role in the Solana timelock instance.
3580
func (s *solanaIntegrationTestSuite) getBatchAddAccessIxs(
3681
ctx context.Context, timelockID [32]byte, roleAcAccount solana.PublicKey, role timelockbindings.Role,
@@ -70,14 +115,13 @@ func (s *solanaIntegrationTestSuite) getBatchAddAccessIxs(
70115
return ixs
71116
}
72117

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,
77121
) {
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)
81125
}
82126

83127
func (s *solanaIntegrationTestSuite) initializeAccessController(auth solana.PrivateKey) {
@@ -108,17 +152,6 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
108152
s.initializeAccessController(admin)
109153

110154
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-
122155
configPDA, err2 := solanasdk.FindTimelockConfigPDA(s.TimelockProgramID, pdaSeed)
123156
s.Require().NoError(err2)
124157

@@ -129,7 +162,7 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
129162
admin.PublicKey(),
130163
solana.SystemProgramID,
131164
s.TimelockProgramID,
132-
programData.Address,
165+
s.getProgramDataAddress(s.TimelockProgramID),
133166
s.AccessControllerProgramID,
134167
s.ProposerAccessController,
135168
s.ExecutorAccessController,
@@ -159,7 +192,7 @@ func (s *solanaIntegrationTestSuite) initializeTimelock(pdaSeed solanasdk.PDASee
159192
accounts = append(accounts, account.PublicKey())
160193
}
161194

162-
s.AssignRoleToAccounts(s.Ctx, pdaSeed, admin, accounts, roleAccounts.Role)
195+
s.assignRoleToAccounts(pdaSeed, accounts, roleAccounts.Role)
163196

164197
for _, account := range roleAccounts.Accounts {
165198
found, err := accesscontroller.HasAccess(s.Ctx, s.solanaClient, roleAccounts.AccessController.PublicKey(),
@@ -185,18 +218,8 @@ func (s *solanaIntegrationTestSuite) initializeMcm(pdaSeed [32]byte, signer eth.
185218
s.Require().NoError(err)
186219
chainSelector := chainsel.SOLANA_DEVNET.Selector
187220

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-
198221
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,
200223
expiringRootAndOpCountPDA).ValidateAndBuild()
201224
s.Require().NoError(err)
202225

@@ -238,7 +261,7 @@ func (s *solanaIntegrationTestSuite) getInitAccessControllersIxs(ctx context.Con
238261
return ixs
239262
}
240263

241-
func (s *solanaIntegrationTestSuite) setupCPIStub(ctx context.Context) {
264+
func (s *solanaIntegrationTestSuite) initializeCPIStub() {
242265
cpistubbindings.SetProgramID(s.StubProgramID)
243266

244267
admin, err := solana.PrivateKeyFromBase58(privateKey)
@@ -251,8 +274,82 @@ func (s *solanaIntegrationTestSuite) setupCPIStub(ctx context.Context) {
251274
ValidateAndBuild()
252275
s.Require().NoError(err)
253276

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)
255330
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)
256353
}
257354

258355
// runTimelockWorkerSolana runs the Solana timelock worker with the provided parameters.
@@ -410,6 +507,21 @@ func (s *solanaIntegrationTestSuite) scheduleTestIx(
410507
return ixStub, operationID
411508
}
412509

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+
413525
func sendAndConfirm(
414526
ctx context.Context, t *testing.T, client *rpc.Client, txPayer solana.PrivateKey, instructions []solana.Instruction,
415527
) (solana.Signature, error) {

0 commit comments

Comments
 (0)