@@ -5,15 +5,18 @@ package solanae2e
55
66import (
77 "context"
8+ "testing"
9+ "time"
810
911 "github.com/gagliardetto/solana-go"
1012 "github.com/gagliardetto/solana-go/programs/token"
1113 "github.com/gagliardetto/solana-go/rpc"
1214 "github.com/smartcontractkit/chainlink-ccip/chains/solana/contracts/tests/testutils"
13- "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/access_controller"
15+ "github.com/stretchr/testify/require"
16+
1417 "github.com/smartcontractkit/chainlink-ccip/chains/solana/gobindings/timelock"
15- timelockutils "github.com/smartcontractkit/chainlink-ccip/chains/solana/utils/timelock"
1618
19+ e2eutils "github.com/smartcontractkit/mcms/e2e/utils/solana"
1720 mcmsSolana "github.com/smartcontractkit/mcms/sdk/solana"
1821 "github.com/smartcontractkit/mcms/types"
1922)
@@ -25,117 +28,61 @@ const BatchAddAccessChunkSize = 24
2528// Test_Solana_TimelockExecute tests the timelock Execute functionality by scheduling a mint tokens transaction and
2629// executing it via the timelock ExecuteBatch
2730func (s * SolanaTestSuite ) Test_Solana_TimelockExecute () {
31+ // --- arrange ---
32+ ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
33+ s .T ().Cleanup (cancel )
34+
35+ token .SetProgramID (solana .Token2022ProgramID )
36+ s .SetupMCM (testTimelockExecuteID )
2837 s .SetupTimelock (testTimelockExecuteID , 1 )
29- // Get required programs and accounts
30- ctx := context .Background ()
31- timelock .SetProgramID (s .TimelockProgramID )
32- access_controller .SetProgramID (s .AccessControllerProgramID )
3338
34- // Fund the auth private key
39+ // Create auth and executor private keys
3540 auth , err := solana .PrivateKeyFromBase58 (privateKey )
3641 s .Require ().NoError (err )
37-
38- // Setup SPL token for testing a mint via timelock
39- mintKeypair , err := solana .NewRandomPrivateKey ()
40- s .Require ().NoError (err )
41- mint := mintKeypair .PublicKey ()
42- // set up the token program
43- signerPDA , err := mcmsSolana .FindTimelockSignerPDA (s .TimelockProgramID , testTimelockExecuteID )
42+ proposerAndExecutorKey , err := solana .NewRandomPrivateKey ()
4443 s .Require ().NoError (err )
45- receiverATA := s .setupTokenProgram (ctx , auth , signerPDA , mintKeypair )
46-
47- // Get receiverATA initial balance
48- initialBalance , err := s .SolanaClient .GetTokenAccountBalance (
49- context .Background (),
50- receiverATA , // The associated token account address
51- rpc .CommitmentProcessed ,
52- )
44+ mintKey , err := solana .NewRandomPrivateKey ()
5345 s .Require ().NoError (err )
5446
55- // Set propose roles
56- proposerAndExecutorKey := s .setProposerAndExecutor (ctx , auth , s .Roles )
57- s .Require ().NotNil (proposerAndExecutorKey )
47+ e2eutils .FundAccounts (s .T (), ctx , []solana.PublicKey {auth .PublicKey (), proposerAndExecutorKey .PublicKey ()}, 1 , s .SolanaClient )
5848
59- // Schedule the mint tx
60- var predecessor [32 ]byte
61- salt := [32 ]byte {123 }
62- mintIx , operationID := s .scheduleMintTx (ctx ,
63- mint ,
64- receiverATA ,
65- s .Roles [timelock .Proposer_Role ].AccessController .PublicKey (),
66- signerPDA ,
67- * proposerAndExecutorKey ,
68- predecessor ,
69- salt )
49+ s .AssignRoleToAccounts (ctx , testTimelockExecuteID , auth , []solana.PublicKey {proposerAndExecutorKey .PublicKey ()},
50+ timelock .Proposer_Role )
51+ s .AssignRoleToAccounts (ctx , testTimelockExecuteID , auth , []solana.PublicKey {proposerAndExecutorKey .PublicKey ()},
52+ timelock .Executor_Role )
7053
71- // --- act: call Timelock Execute ---
72- executor := mcmsSolana .NewTimelockExecutor (s .SolanaClient , * proposerAndExecutorKey )
73- contractID := mcmsSolana .ContractAddress (s .TimelockProgramID , testTimelockExecuteID )
74- ixData , err := mintIx .Data ()
75- s .Require ().NoError (err )
76- accounts := mintIx .Accounts ()
77- accounts = append (accounts , & solana.AccountMeta {PublicKey : solana .Token2022ProgramID , IsSigner : false , IsWritable : false })
78- solanaTx , err := mcmsSolana .NewTransaction (solana .Token2022ProgramID .String (), ixData , nil , accounts , "Token" , []string {})
79- s .Require ().NoError (err )
80- batchOp := types.BatchOperation {
81- Transactions : []types.Transaction {solanaTx },
82- ChainSelector : s .ChainSelector ,
83- }
84- // --- Wait for the operation to be ready ---
85- s .waitForOperationToBeReady (ctx , testTimelockExecuteID , operationID )
86- signature , err := executor .Execute (ctx , batchOp , contractID , predecessor , salt )
87- s .Require ().NoError (err )
88- s .Require ().NotEqual (signature , "" )
89-
90- // --- assert balances
91- finalBalance , err := s .SolanaClient .GetTokenAccountBalance (
92- ctx ,
93- receiverATA ,
94- rpc .CommitmentProcessed ,
95- )
54+ signerPDA , err := mcmsSolana .FindTimelockSignerPDA (s .TimelockProgramID , testTimelockExecuteID )
9655 s .Require ().NoError (err )
9756
98- // final balance should be 1000000000000 more units
99- s .Require ().Equal (initialBalance .Value .Amount , "0" )
100- s .Require ().Equal (finalBalance .Value .Amount , "1000000000000" )
101- }
57+ // Setup SPL token for testing a mint via timelock
58+ receiverATA := s .setupTokenProgram (ctx , auth , signerPDA , mintKey )
59+
60+ predecessor := [32 ]byte {}
61+ salt := [32 ]byte {123 }
62+ executor := mcmsSolana .NewTimelockExecutor (s .SolanaClient , proposerAndExecutorKey )
63+ timelockAddress := mcmsSolana .ContractAddress (s .TimelockProgramID , testTimelockExecuteID )
10264
103- // setProposerAndExecutor sets the proposer for the timelock
104- func (s * SolanaTestSuite ) setProposerAndExecutor (ctx context.Context , auth solana.PrivateKey , roleMap timelockutils.RoleMap ) * solana.PrivateKey {
105- proposerAndExecutorKey := solana .NewWallet ()
106- testutils .FundAccounts (ctx , []solana.PrivateKey {proposerAndExecutorKey .PrivateKey }, s .SolanaClient , s .T ())
65+ initialBalance := getBalance (ctx , s .T (), s .SolanaClient , receiverATA )
10766
108- // Add proposers to the timelock program
109- batchAddAccessIxs , err := timelockutils .GetBatchAddAccessIxs (
110- ctx ,
111- testTimelockExecuteID ,
112- roleMap [timelock .Proposer_Role ].AccessController .PublicKey (),
113- timelock .Proposer_Role ,
114- []solana.PublicKey {proposerAndExecutorKey .PublicKey ()},
115- auth ,
116- BatchAddAccessChunkSize ,
117- s .SolanaClient )
67+ // schedule mint transaction and build batch operation from the returned ScheduleBatch instruction
68+ mintIx , operationID := s .scheduleMintTx (ctx , mintKey .PublicKey (), receiverATA ,
69+ s .Roles [timelock .Proposer_Role ].AccessController .PublicKey (), signerPDA , proposerAndExecutorKey ,
70+ predecessor , salt )
71+ s .waitForOperationToBeReady (ctx , testTimelockExecuteID , operationID )
72+
73+ // --- act: call Timelock Execute ---
74+ mcmsTransaction , err := mcmsSolana .NewTransactionFromInstruction (mintIx , "Token" , nil )
11875 s .Require ().NoError (err )
119- for _ , ix := range batchAddAccessIxs {
120- testutils .SendAndConfirm (ctx , s .T (), s .SolanaClient , []solana.Instruction {ix }, auth , rpc .CommitmentConfirmed )
121- }
76+ batchOp := types.BatchOperation {Transactions : []types.Transaction {mcmsTransaction }, ChainSelector : s .ChainSelector }
12277
123- // Add executor to the timelock program
124- batchAddAccessIxs , err = timelockutils .GetBatchAddAccessIxs (
125- ctx ,
126- testTimelockExecuteID ,
127- roleMap [timelock .Executor_Role ].AccessController .PublicKey (),
128- timelock .Executor_Role ,
129- []solana.PublicKey {proposerAndExecutorKey .PublicKey ()},
130- auth ,
131- BatchAddAccessChunkSize ,
132- s .SolanaClient )
78+ signature , err := executor .Execute (ctx , batchOp , timelockAddress , predecessor , salt )
13379 s .Require ().NoError (err )
134- for _ , ix := range batchAddAccessIxs {
135- testutils .SendAndConfirm (ctx , s .T (), s .SolanaClient , []solana.Instruction {ix }, auth , rpc .CommitmentConfirmed )
136- }
80+ s .Require ().NotEqual ("" , signature )
13781
138- return & proposerAndExecutorKey .PrivateKey
82+ // --- assert ---
83+ finalBalance := getBalance (ctx , s .T (), s .SolanaClient , receiverATA )
84+ s .Require ().Equal ("0" , initialBalance )
85+ s .Require ().Equal ("1000000000000" , finalBalance )
13986}
14087
14188// scheduleMintTx schedules a MintTx on the timelock
@@ -151,7 +98,9 @@ func (s *SolanaTestSuite) scheduleMintTx(
15198 // and not the deployer account.
15299 authPublicKey solana.PublicKey ,
153100 auth solana.PrivateKey , // The account to sign the init, append schedule instructions.
154- predecessor , salt [32 ]byte ) (instruction * token.Instruction , operationID [32 ]byte ) {
101+ predecessor ,
102+ salt [32 ]byte ,
103+ ) (instruction * token.Instruction , operationID [32 ]byte ) {
155104 amount := 1000 * solana .LAMPORTS_PER_SOL
156105 mintIx , err := token .NewMintToInstruction (amount , mint , receiverATA , authPublicKey , nil ).ValidateAndBuild ()
157106 s .Require ().NoError (err )
@@ -160,31 +109,27 @@ func (s *SolanaTestSuite) scheduleMintTx(
160109 acc .IsSigner = false
161110 }
162111 }
163- s . Require (). NoError ( err )
112+
164113 // Get the operation ID
165114 ixData , err := mintIx .Data ()
166115 s .Require ().NoError (err )
167- accounts := make ([]timelock.InstructionAccount , 0 , len (mintIx .Accounts ())+ 1 )
168- for _ , account := range mintIx .Accounts () {
169- accounts = append ( accounts , timelock.InstructionAccount {
116+ accounts := make ([]timelock.InstructionAccount , len (mintIx .Accounts ()))
117+ for i , account := range mintIx .Accounts () {
118+ accounts [ i ] = timelock.InstructionAccount {
170119 Pubkey : account .PublicKey ,
171120 IsSigner : account .IsSigner ,
172121 IsWritable : account .IsWritable ,
173- })
122+ }
174123 }
175- accounts = append (accounts , timelock.InstructionAccount {
176- Pubkey : solana .Token2022ProgramID ,
177- IsSigner : false ,
178- IsWritable : false ,
179- })
180- opInstructions := []timelock.InstructionData {{Data : ixData , ProgramId : solana .Token2022ProgramID , Accounts : accounts }}
124+ opInstructions := []timelock.InstructionData {{Data : ixData , ProgramId : mintIx .ProgramID (), Accounts : accounts }}
125+
181126 operationID , err = mcmsSolana .HashOperation (opInstructions , predecessor , salt )
182127 s .Require ().NoError (err )
128+
183129 operationPDA , err := mcmsSolana .FindTimelockOperationPDA (s .TimelockProgramID , testTimelockExecuteID , operationID )
184130 s .Require ().NoError (err )
185131 configPDA , err := mcmsSolana .FindTimelockConfigPDA (s .TimelockProgramID , testTimelockExecuteID )
186132 s .Require ().NoError (err )
187-
188133 proposerAC := s .Roles [timelock .Proposer_Role ].AccessController .PublicKey ()
189134
190135 // Preload and Init Operation
@@ -257,6 +202,7 @@ func (s *SolanaTestSuite) scheduleMintTx(
257202 s .Require ().NoError (err )
258203 ixs = append (ixs , finOpIx )
259204 testutils .SendAndConfirm (ctx , s .T (), s .SolanaClient , ixs , auth , rpc .CommitmentConfirmed )
205+
260206 // Schedule the operation
261207 scheduleIx , err := timelock .NewScheduleBatchInstruction (
262208 testTimelockExecuteID ,
@@ -272,3 +218,12 @@ func (s *SolanaTestSuite) scheduleMintTx(
272218
273219 return mintIx , operationID
274220}
221+
222+ func getBalance (ctx context.Context , t * testing.T , client * rpc.Client , account solana.PublicKey ) string {
223+ t .Helper ()
224+
225+ balance , err := client .GetTokenAccountBalance (ctx , account , rpc .CommitmentProcessed )
226+ require .NoError (t , err )
227+
228+ return balance .Value .Amount
229+ }
0 commit comments