litesvm_utils/test_helpers.rs
1//! Test helper utilities for common account operations
2//!
3//! This module provides convenient methods for creating and managing test accounts,
4//! token mints, and associated token accounts.
5
6use litesvm::LiteSVM;
7use solana_program::pubkey::Pubkey;
8use solana_sdk::signature::{Keypair, Signer};
9use solana_sdk::transaction::Transaction;
10use spl_associated_token_account::get_associated_token_address;
11use std::error::Error;
12
13/// Test helper methods for LiteSVM
14pub trait TestHelpers {
15 /// Create a new funded keypair
16 ///
17 /// # Example
18 /// ```no_run
19 /// # use litesvm_utils::TestHelpers;
20 /// # use litesvm::LiteSVM;
21 /// # let mut svm = LiteSVM::new();
22 /// let account = svm.create_funded_account(1_000_000_000).unwrap();
23 /// ```
24 fn create_funded_account(&mut self, lamports: u64) -> Result<Keypair, Box<dyn Error>>;
25
26 /// Create multiple funded keypairs
27 ///
28 /// # Example
29 /// ```no_run
30 /// # use litesvm_utils::TestHelpers;
31 /// # use litesvm::LiteSVM;
32 /// # let mut svm = LiteSVM::new();
33 /// let accounts = svm.create_funded_accounts(3, 1_000_000_000).unwrap();
34 /// assert_eq!(accounts.len(), 3);
35 /// ```
36 fn create_funded_accounts(
37 &mut self,
38 count: usize,
39 lamports: u64,
40 ) -> Result<Vec<Keypair>, Box<dyn Error>>;
41
42 /// Create and initialize a token mint
43 ///
44 /// # Example
45 /// ```no_run
46 /// # use litesvm_utils::TestHelpers;
47 /// # use litesvm::LiteSVM;
48 /// # use solana_sdk::signature::Keypair;
49 /// # let mut svm = LiteSVM::new();
50 /// # let authority = Keypair::new();
51 /// let mint = svm.create_token_mint(&authority, 9).unwrap();
52 /// ```
53 fn create_token_mint(
54 &mut self,
55 authority: &Keypair,
56 decimals: u8,
57 ) -> Result<Keypair, Box<dyn Error>>;
58
59 /// Create a token account for a mint
60 ///
61 /// # Example
62 /// ```no_run
63 /// # use litesvm_utils::TestHelpers;
64 /// # use litesvm::LiteSVM;
65 /// # use solana_sdk::signature::{Keypair, Signer};
66 /// # let mut svm = LiteSVM::new();
67 /// # let owner = Keypair::new();
68 /// # let mint = Keypair::new();
69 /// let token_account = svm.create_token_account(&mint.pubkey(), &owner).unwrap();
70 /// ```
71 fn create_token_account(
72 &mut self,
73 mint: &Pubkey,
74 owner: &Keypair,
75 ) -> Result<Keypair, Box<dyn Error>>;
76
77 /// Create an associated token account
78 ///
79 /// # Example
80 /// ```no_run
81 /// # use litesvm_utils::TestHelpers;
82 /// # use litesvm::LiteSVM;
83 /// # use solana_sdk::signature::{Keypair, Signer};
84 /// # let mut svm = LiteSVM::new();
85 /// # let owner = Keypair::new();
86 /// # let mint = Keypair::new();
87 /// let ata = svm.create_associated_token_account(&mint.pubkey(), &owner).unwrap();
88 /// ```
89 fn create_associated_token_account(
90 &mut self,
91 mint: &Pubkey,
92 owner: &Keypair,
93 ) -> Result<Pubkey, Box<dyn Error>>;
94
95 /// Mint tokens to an account
96 ///
97 /// # Example
98 /// ```no_run
99 /// # use litesvm_utils::TestHelpers;
100 /// # use litesvm::LiteSVM;
101 /// # use solana_sdk::signature::{Keypair, Signer};
102 /// # use solana_program::pubkey::Pubkey;
103 /// # let mut svm = LiteSVM::new();
104 /// # let mint = Keypair::new();
105 /// # let token_account = Pubkey::new_unique();
106 /// # let authority = Keypair::new();
107 /// svm.mint_to(&mint.pubkey(), &token_account, &authority, 1_000_000_000).unwrap();
108 /// ```
109 fn mint_to(
110 &mut self,
111 mint: &Pubkey,
112 account: &Pubkey,
113 authority: &Keypair,
114 amount: u64,
115 ) -> Result<(), Box<dyn Error>>;
116
117 /// Derive a program-derived address
118 ///
119 /// # Example
120 /// ```no_run
121 /// # use litesvm_utils::TestHelpers;
122 /// # use litesvm::LiteSVM;
123 /// # use solana_program::pubkey::Pubkey;
124 /// # let svm = LiteSVM::new();
125 /// # let program_id = Pubkey::new_unique();
126 /// let (pda, bump) = svm.derive_pda(&[b"seed"], &program_id);
127 /// ```
128 fn derive_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8);
129
130 /// Get a program-derived address (convenience wrapper for Pubkey::find_program_address)
131 ///
132 /// This is a more convenient version that returns just the PDA without the bump.
133 /// Use this when you don't need the bump seed.
134 ///
135 /// # Example
136 /// ```no_run
137 /// # use litesvm_utils::TestHelpers;
138 /// # use litesvm::LiteSVM;
139 /// # use solana_program::pubkey::Pubkey;
140 /// # use solana_sdk::signature::{Keypair, Signer};
141 /// # let svm = LiteSVM::new();
142 /// # let program_id = Pubkey::new_unique();
143 /// # let maker = Keypair::new();
144 /// # let seed = 42u64;
145 /// // Simple usage with multiple seeds
146 /// let escrow_pda = svm.get_pda(
147 /// &[b"escrow", maker.pubkey().as_ref(), &seed.to_le_bytes()],
148 /// &program_id
149 /// );
150 /// ```
151 fn get_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> Pubkey {
152 let (pda, _bump) = self.derive_pda(seeds, program_id);
153 pda
154 }
155
156 /// Get a program-derived address with bump (alias for derive_pda for consistency)
157 ///
158 /// # Example
159 /// ```no_run
160 /// # use litesvm_utils::TestHelpers;
161 /// # use litesvm::LiteSVM;
162 /// # use solana_program::pubkey::Pubkey;
163 /// # let svm = LiteSVM::new();
164 /// # let program_id = Pubkey::new_unique();
165 /// let (pda, bump) = svm.get_pda_with_bump(&[b"seed"], &program_id);
166 /// ```
167 fn get_pda_with_bump(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
168 self.derive_pda(seeds, program_id)
169 }
170
171 /// Get the current slot
172 fn get_current_slot(&self) -> u64;
173
174 /// Advance the slot by a specified amount
175 fn advance_slot(&mut self, slots: u64);
176}
177
178impl TestHelpers for LiteSVM {
179 fn create_funded_account(&mut self, lamports: u64) -> Result<Keypair, Box<dyn Error>> {
180 let keypair = Keypair::new();
181 self.airdrop(&keypair.pubkey(), lamports)
182 .map_err(|e| format!("Failed to airdrop: {:?}", e))?;
183 Ok(keypair)
184 }
185
186 fn create_funded_accounts(
187 &mut self,
188 count: usize,
189 lamports: u64,
190 ) -> Result<Vec<Keypair>, Box<dyn Error>> {
191 let mut accounts = Vec::with_capacity(count);
192 for _ in 0..count {
193 accounts.push(self.create_funded_account(lamports)?);
194 }
195 Ok(accounts)
196 }
197
198 fn create_token_mint(
199 &mut self,
200 authority: &Keypair,
201 decimals: u8,
202 ) -> Result<Keypair, Box<dyn Error>> {
203 let mint = Keypair::new();
204
205 // Calculate rent for mint account
206 let rent = self.minimum_balance_for_rent_exemption(82);
207
208 // Create mint account
209 let create_account_ix = solana_program::system_instruction::create_account(
210 &authority.pubkey(),
211 &mint.pubkey(),
212 rent,
213 82,
214 &spl_token::id(),
215 );
216
217 // Initialize mint
218 let init_mint_ix = spl_token::instruction::initialize_mint(
219 &spl_token::id(),
220 &mint.pubkey(),
221 &authority.pubkey(),
222 None,
223 decimals,
224 )?;
225
226 // Send transaction
227 let tx = Transaction::new_signed_with_payer(
228 &[create_account_ix, init_mint_ix],
229 Some(&authority.pubkey()),
230 &[authority, &mint],
231 self.latest_blockhash(),
232 );
233
234 self.send_transaction(tx)
235 .map_err(|e| format!("Failed to create mint: {:?}", e.err))?;
236 Ok(mint)
237 }
238
239 fn create_token_account(
240 &mut self,
241 mint: &Pubkey,
242 owner: &Keypair,
243 ) -> Result<Keypair, Box<dyn Error>> {
244 let token_account = Keypair::new();
245
246 // Calculate rent for token account
247 let rent = self.minimum_balance_for_rent_exemption(165);
248
249 // Create account
250 let create_account_ix = solana_program::system_instruction::create_account(
251 &owner.pubkey(),
252 &token_account.pubkey(),
253 rent,
254 165,
255 &spl_token::id(),
256 );
257
258 // Initialize token account
259 let init_account_ix = spl_token::instruction::initialize_account(
260 &spl_token::id(),
261 &token_account.pubkey(),
262 mint,
263 &owner.pubkey(),
264 )?;
265
266 // Send transaction
267 let tx = Transaction::new_signed_with_payer(
268 &[create_account_ix, init_account_ix],
269 Some(&owner.pubkey()),
270 &[owner, &token_account],
271 self.latest_blockhash(),
272 );
273
274 self.send_transaction(tx)
275 .map_err(|e| format!("Failed to create token account: {:?}", e.err))?;
276 Ok(token_account)
277 }
278
279 fn create_associated_token_account(
280 &mut self,
281 mint: &Pubkey,
282 owner: &Keypair,
283 ) -> Result<Pubkey, Box<dyn Error>> {
284 let ata = get_associated_token_address(&owner.pubkey(), mint);
285
286 // Create ATA instruction
287 let create_ata_ix = spl_associated_token_account::instruction::create_associated_token_account(
288 &owner.pubkey(),
289 &owner.pubkey(),
290 mint,
291 &spl_token::id(),
292 );
293
294 // Send transaction
295 let tx = Transaction::new_signed_with_payer(
296 &[create_ata_ix],
297 Some(&owner.pubkey()),
298 &[owner],
299 self.latest_blockhash(),
300 );
301
302 self.send_transaction(tx)
303 .map_err(|e| format!("Failed to create ATA: {:?}", e.err))?;
304 Ok(ata)
305 }
306
307 fn mint_to(
308 &mut self,
309 mint: &Pubkey,
310 account: &Pubkey,
311 authority: &Keypair,
312 amount: u64,
313 ) -> Result<(), Box<dyn Error>> {
314 // Create mint_to instruction
315 let mint_to_ix = spl_token::instruction::mint_to(
316 &spl_token::id(),
317 mint,
318 account,
319 &authority.pubkey(),
320 &[],
321 amount,
322 )?;
323
324 // Send transaction
325 let tx = Transaction::new_signed_with_payer(
326 &[mint_to_ix],
327 Some(&authority.pubkey()),
328 &[authority],
329 self.latest_blockhash(),
330 );
331
332 self.send_transaction(tx)
333 .map_err(|e| format!("Failed to mint tokens: {:?}", e.err))?;
334 Ok(())
335 }
336
337 fn derive_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
338 Pubkey::find_program_address(seeds, program_id)
339 }
340
341 fn get_current_slot(&self) -> u64 {
342 // LiteSVM doesn't have get_clock, use slot directly
343 self.get_sysvar::<solana_program::clock::Clock>().slot
344 }
345
346 fn advance_slot(&mut self, slots: u64) {
347 let current_slot = self.get_sysvar::<solana_program::clock::Clock>().slot;
348 for i in 0..slots {
349 self.warp_to_slot(current_slot + i + 1);
350 }
351 }
352}