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}