11use {
22 super :: {
3- instruction_builders :: InstructionExecution ,
3+ execution :: ExecutionWithChecks ,
44 lifecycle:: StakeLifecycle ,
55 utils:: { add_sysvars, STAKE_RENT_EXEMPTION } ,
66 } ,
77 mollusk_svm:: { result:: Check , Mollusk } ,
88 solana_account:: AccountSharedData ,
99 solana_instruction:: Instruction ,
10+ solana_program_error:: ProgramError ,
1011 solana_pubkey:: Pubkey ,
12+ solana_stake_interface:: state:: Lockup ,
1113 solana_stake_program:: id,
1214} ;
1315
1416/// Builder for creating stake accounts with customizable parameters
15- pub struct StakeAccountBuilder {
17+ pub struct StakeAccountBuilder < ' a > {
18+ ctx : & ' a mut StakeTestContext ,
1619 lifecycle : StakeLifecycle ,
20+ staked_amount : u64 ,
21+ stake_authority : Option < Pubkey > ,
22+ withdraw_authority : Option < Pubkey > ,
23+ lockup : Option < Lockup > ,
24+ vote_account : Option < Pubkey > ,
25+ stake_pubkey : Option < Pubkey > ,
1726}
1827
19- impl StakeAccountBuilder {
28+ impl < ' a > StakeAccountBuilder < ' a > {
29+ /// Set the staked amount (lamports delegated to validator)
30+ pub fn staked_amount ( mut self , amount : u64 ) -> Self {
31+ self . staked_amount = amount;
32+ self
33+ }
34+
35+ /// Set a custom stake authority (defaults to ctx.staker)
36+ pub fn stake_authority ( mut self , authority : & Pubkey ) -> Self {
37+ self . stake_authority = Some ( * authority) ;
38+ self
39+ }
40+
41+ /// Set a custom withdraw authority (defaults to ctx.withdrawer)
42+ pub fn withdraw_authority ( mut self , authority : & Pubkey ) -> Self {
43+ self . withdraw_authority = Some ( * authority) ;
44+ self
45+ }
46+
47+ /// Set a custom lockup (defaults to Lockup::default())
48+ pub fn lockup ( mut self , lockup : & Lockup ) -> Self {
49+ self . lockup = Some ( * lockup) ;
50+ self
51+ }
52+
53+ /// Set a custom vote account (defaults to ctx.vote_account)
54+ pub fn vote_account ( mut self , vote_account : & Pubkey ) -> Self {
55+ self . vote_account = Some ( * vote_account) ;
56+ self
57+ }
58+
59+ /// Set a specific stake account pubkey (defaults to Pubkey::new_unique())
60+ pub fn stake_pubkey ( mut self , pubkey : & Pubkey ) -> Self {
61+ self . stake_pubkey = Some ( * pubkey) ;
62+ self
63+ }
64+
65+ /// Build the stake account and return (pubkey, account_data)
2066 pub fn build ( self ) -> ( Pubkey , AccountSharedData ) {
21- let stake_pubkey = Pubkey :: new_unique ( ) ;
67+ let stake_pubkey = self . stake_pubkey . unwrap_or_else ( Pubkey :: new_unique) ;
2268 let account = self . lifecycle . create_uninitialized_account ( ) ;
2369 ( stake_pubkey, account)
2470 }
2571}
2672
27- /// Consolidated test context for stake account tests
2873pub struct StakeTestContext {
2974 pub mollusk : Mollusk ,
3075 pub rent_exempt_reserve : u64 ,
@@ -33,10 +78,8 @@ pub struct StakeTestContext {
3378}
3479
3580impl StakeTestContext {
36- /// Create a new test context with all standard setup
3781 pub fn new ( ) -> Self {
3882 let mollusk = Mollusk :: new ( & id ( ) , "solana_stake_program" ) ;
39-
4083 Self {
4184 mollusk,
4285 rent_exempt_reserve : STAKE_RENT_EXEMPTION ,
@@ -50,60 +93,62 @@ impl StakeTestContext {
5093 /// Example:
5194 /// ```
5295 /// let (stake, account) = ctx
53- /// .stake_account(StakeLifecycle::Uninitialized)
96+ /// .stake_account(StakeLifecycle::Active)
97+ /// .staked_amount(1_000_000)
5498 /// .build();
5599 /// ```
56- pub fn stake_account ( & mut self , lifecycle : StakeLifecycle ) -> StakeAccountBuilder {
57- StakeAccountBuilder { lifecycle }
100+ pub fn stake_account ( & mut self , lifecycle : StakeLifecycle ) -> StakeAccountBuilder < ' _ > {
101+ StakeAccountBuilder {
102+ ctx : self ,
103+ lifecycle,
104+ staked_amount : 0 ,
105+ stake_authority : None ,
106+ withdraw_authority : None ,
107+ lockup : None ,
108+ vote_account : None ,
109+ stake_pubkey : None ,
110+ }
111+ }
112+
113+ /// Configure execution with specific checks, then call .execute(instruction, accounts)
114+ ///
115+ /// Usage: `ctx.checks(&checks).execute(instruction, accounts)`
116+ pub fn checks < ' a , ' b > ( & ' a mut self , checks : & ' b [ Check < ' b > ] ) -> ExecutionWithChecks < ' a , ' b > {
117+ ExecutionWithChecks :: new ( self , checks)
58118 }
59119
60- /// Process an instruction with account data provided as a slice of (pubkey, data) pairs.
61- /// Sysvars are auto-resolved - only provide data for accounts that need it.
62- pub fn process < ' b > (
63- & self ,
120+ /// Execute an instruction with default success checks and missing signer testing
121+ ///
122+ /// Usage: `ctx.execute(instruction, accounts)`
123+ pub fn execute (
124+ & mut self ,
64125 instruction : Instruction ,
65126 accounts : & [ ( & Pubkey , & AccountSharedData ) ] ,
66- ) -> InstructionExecution < ' _ , ' b > {
67- let accounts_vec = accounts
68- . iter ( )
69- . map ( |( pk, data) | ( * * pk, ( * data) . clone ( ) ) )
70- . collect ( ) ;
71- InstructionExecution :: new ( instruction, accounts_vec, self )
127+ ) -> mollusk_svm:: result:: InstructionResult {
128+ self . execute_internal ( instruction, accounts, & [ Check :: success ( ) ] , true )
72129 }
73130
74- /// Process an instruction with optional missing signer testing
75- pub ( crate ) fn process_instruction_maybe_test_signers (
76- & self ,
77- instruction : & Instruction ,
78- accounts : Vec < ( Pubkey , AccountSharedData ) > ,
131+ /// Internal: execute with given checks and current config
132+ pub ( crate ) fn execute_internal (
133+ & mut self ,
134+ instruction : Instruction ,
135+ accounts : & [ ( & Pubkey , & AccountSharedData ) ] ,
79136 checks : & [ Check ] ,
80137 test_missing_signers : bool ,
81138 ) -> mollusk_svm:: result:: InstructionResult {
139+ let accounts_vec: Vec < ( Pubkey , AccountSharedData ) > = accounts
140+ . iter ( )
141+ . map ( |( pk, data) | ( * * pk, ( * data) . clone ( ) ) )
142+ . collect ( ) ;
143+
82144 if test_missing_signers {
83- use solana_program_error:: ProgramError ;
84-
85- // Test that removing each signer causes failure
86- for i in 0 ..instruction. accounts . len ( ) {
87- if instruction. accounts [ i] . is_signer {
88- let mut modified_instruction = instruction. clone ( ) ;
89- modified_instruction. accounts [ i] . is_signer = false ;
90-
91- let accounts_with_sysvars =
92- add_sysvars ( & self . mollusk , & modified_instruction, accounts. clone ( ) ) ;
93-
94- self . mollusk . process_and_validate_instruction (
95- & modified_instruction,
96- & accounts_with_sysvars,
97- & [ Check :: err ( ProgramError :: MissingRequiredSignature ) ] ,
98- ) ;
99- }
100- }
145+ verify_all_signers_required ( & self . mollusk , & instruction, & accounts_vec) ;
101146 }
102147
103148 // Process with all signers present
104- let accounts_with_sysvars = add_sysvars ( & self . mollusk , instruction, accounts ) ;
149+ let accounts_with_sysvars = add_sysvars ( & self . mollusk , & instruction, accounts_vec ) ;
105150 self . mollusk
106- . process_and_validate_instruction ( instruction, & accounts_with_sysvars, checks)
151+ . process_and_validate_instruction ( & instruction, & accounts_with_sysvars, checks)
107152 }
108153}
109154
@@ -112,3 +157,26 @@ impl Default for StakeTestContext {
112157 Self :: new ( )
113158 }
114159}
160+
161+ /// Verify that removing any signer from the instruction causes MissingRequiredSignature error
162+ fn verify_all_signers_required (
163+ mollusk : & Mollusk ,
164+ instruction : & Instruction ,
165+ accounts : & [ ( Pubkey , AccountSharedData ) ] ,
166+ ) {
167+ for i in 0 ..instruction. accounts . len ( ) {
168+ if instruction. accounts [ i] . is_signer {
169+ let mut modified_instruction = instruction. clone ( ) ;
170+ modified_instruction. accounts [ i] . is_signer = false ;
171+
172+ let accounts_with_sysvars =
173+ add_sysvars ( mollusk, & modified_instruction, accounts. to_vec ( ) ) ;
174+
175+ mollusk. process_and_validate_instruction (
176+ & modified_instruction,
177+ & accounts_with_sysvars,
178+ & [ Check :: err ( ProgramError :: MissingRequiredSignature ) ] ,
179+ ) ;
180+ }
181+ }
182+ }
0 commit comments