Skip to content

Commit fde41bb

Browse files
committed
better builder idiom
1 parent 695ce95 commit fde41bb

File tree

5 files changed

+215
-185
lines changed

5 files changed

+215
-185
lines changed

program/tests/helpers/context.rs

Lines changed: 113 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,75 @@
11
use {
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
2873
pub struct StakeTestContext {
2974
pub mollusk: Mollusk,
3075
pub rent_exempt_reserve: u64,
@@ -33,10 +78,8 @@ pub struct StakeTestContext {
3378
}
3479

3580
impl 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+
}

program/tests/helpers/execution.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use {
2+
super::context::StakeTestContext, mollusk_svm::result::Check,
3+
solana_account::AccountSharedData, solana_instruction::Instruction, solana_pubkey::Pubkey,
4+
};
5+
6+
/// Wrapper for executing with specific checks
7+
///
8+
/// Usage: `ctx.checks(&checks).test_missing_signers(false).execute(instruction, accounts)`
9+
pub struct ExecutionWithChecks<'a, 'b> {
10+
pub(crate) ctx: &'a mut StakeTestContext,
11+
pub(crate) checks: &'b [Check<'b>],
12+
pub(crate) test_missing_signers: bool,
13+
}
14+
15+
impl<'a, 'b> ExecutionWithChecks<'a, 'b> {
16+
pub fn new(ctx: &'a mut StakeTestContext, checks: &'b [Check<'b>]) -> Self {
17+
Self {
18+
ctx,
19+
checks,
20+
test_missing_signers: true, // default: test missing signers
21+
}
22+
}
23+
24+
pub fn test_missing_signers(mut self, test: bool) -> Self {
25+
self.test_missing_signers = test;
26+
self
27+
}
28+
29+
pub fn execute(
30+
self,
31+
instruction: Instruction,
32+
accounts: &[(&Pubkey, &AccountSharedData)],
33+
) -> mollusk_svm::result::InstructionResult {
34+
self.ctx.execute_internal(
35+
instruction,
36+
accounts,
37+
self.checks,
38+
self.test_missing_signers,
39+
)
40+
}
41+
}

program/tests/helpers/instruction_builders.rs

Lines changed: 0 additions & 61 deletions
This file was deleted.

program/tests/helpers/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
#![allow(dead_code)]
33

44
pub mod context;
5-
pub mod instruction_builders;
5+
pub mod execution;
66
pub mod lifecycle;
77
pub mod utils;

0 commit comments

Comments
 (0)