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_system_interface::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_system_interface::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}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357    use solana_program_pack::Pack;
358    use solana_sdk::signature::Signer;
359
360    #[test]
361    fn test_create_funded_account() {
362        let mut svm = LiteSVM::new();
363        let lamports = 1_000_000_000;
364
365        let account = svm.create_funded_account(lamports).unwrap();
366
367        // Verify account exists and has correct balance
368        let balance = svm.get_balance(&account.pubkey()).unwrap();
369        assert_eq!(balance, lamports);
370    }
371
372    #[test]
373    fn test_create_funded_accounts() {
374        let mut svm = LiteSVM::new();
375        let count = 5;
376        let lamports = 500_000_000;
377
378        let accounts = svm.create_funded_accounts(count, lamports).unwrap();
379
380        // Verify correct number of accounts created
381        assert_eq!(accounts.len(), count);
382
383        // Verify each account has correct balance
384        for account in &accounts {
385            let balance = svm.get_balance(&account.pubkey()).unwrap();
386            assert_eq!(balance, lamports);
387        }
388
389        // Verify all accounts have unique pubkeys
390        let mut pubkeys: Vec<_> = accounts.iter().map(|k| k.pubkey()).collect();
391        pubkeys.sort();
392        pubkeys.dedup();
393        assert_eq!(pubkeys.len(), count);
394    }
395
396    #[test]
397    fn test_create_token_mint() {
398        let mut svm = LiteSVM::new();
399        let authority = svm.create_funded_account(10_000_000_000).unwrap();
400        let decimals = 9;
401
402        let mint = svm.create_token_mint(&authority, decimals).unwrap();
403
404        // Verify mint account exists
405        let mint_account = svm.get_account(&mint.pubkey()).unwrap();
406        assert_eq!(mint_account.owner, spl_token::id());
407
408        // Verify mint data is correct
409        let mint_data = spl_token::state::Mint::unpack(&mint_account.data).unwrap();
410        assert_eq!(mint_data.decimals, decimals);
411        assert_eq!(mint_data.mint_authority, Some(authority.pubkey()).into());
412        assert_eq!(mint_data.supply, 0);
413    }
414
415    #[test]
416    fn test_create_token_account() {
417        let mut svm = LiteSVM::new();
418        let owner = svm.create_funded_account(10_000_000_000).unwrap();
419        let mint = svm.create_token_mint(&owner, 9).unwrap();
420
421        let token_account = svm.create_token_account(&mint.pubkey(), &owner).unwrap();
422
423        // Verify token account exists
424        let account = svm.get_account(&token_account.pubkey()).unwrap();
425        assert_eq!(account.owner, spl_token::id());
426
427        // Verify token account data
428        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
429        assert_eq!(token_data.mint, mint.pubkey());
430        assert_eq!(token_data.owner, owner.pubkey());
431        assert_eq!(token_data.amount, 0);
432    }
433
434    #[test]
435    fn test_create_associated_token_account() {
436        let mut svm = LiteSVM::new();
437        let owner = svm.create_funded_account(10_000_000_000).unwrap();
438        let mint = svm.create_token_mint(&owner, 9).unwrap();
439
440        let ata = svm
441            .create_associated_token_account(&mint.pubkey(), &owner)
442            .unwrap();
443
444        // Verify ATA is at expected address
445        let expected_ata = get_associated_token_address(&owner.pubkey(), &mint.pubkey());
446        assert_eq!(ata, expected_ata);
447
448        // Verify ATA account exists
449        let account = svm.get_account(&ata).unwrap();
450        assert_eq!(account.owner, spl_token::id());
451
452        // Verify ATA data
453        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
454        assert_eq!(token_data.mint, mint.pubkey());
455        assert_eq!(token_data.owner, owner.pubkey());
456        assert_eq!(token_data.amount, 0);
457    }
458
459    #[test]
460    fn test_mint_to() {
461        let mut svm = LiteSVM::new();
462        let authority = svm.create_funded_account(10_000_000_000).unwrap();
463        let mint = svm.create_token_mint(&authority, 9).unwrap();
464        let token_account = svm
465            .create_associated_token_account(&mint.pubkey(), &authority)
466            .unwrap();
467
468        let amount = 1_000_000_000;
469        svm.mint_to(&mint.pubkey(), &token_account, &authority, amount)
470            .unwrap();
471
472        // Verify token account balance
473        let account = svm.get_account(&token_account).unwrap();
474        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
475        assert_eq!(token_data.amount, amount);
476
477        // Verify mint supply increased
478        let mint_account = svm.get_account(&mint.pubkey()).unwrap();
479        let mint_data = spl_token::state::Mint::unpack(&mint_account.data).unwrap();
480        assert_eq!(mint_data.supply, amount);
481    }
482
483    #[test]
484    fn test_mint_to_multiple_times() {
485        let mut svm = LiteSVM::new();
486        let authority = svm.create_funded_account(10_000_000_000).unwrap();
487        let mint = svm.create_token_mint(&authority, 9).unwrap();
488        let token_account = svm
489            .create_associated_token_account(&mint.pubkey(), &authority)
490            .unwrap();
491
492        // Mint tokens multiple times
493        svm.mint_to(&mint.pubkey(), &token_account, &authority, 100_000)
494            .unwrap();
495        svm.mint_to(&mint.pubkey(), &token_account, &authority, 200_000)
496            .unwrap();
497        svm.mint_to(&mint.pubkey(), &token_account, &authority, 300_000)
498            .unwrap();
499
500        // Verify cumulative balance
501        let account = svm.get_account(&token_account).unwrap();
502        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
503        assert_eq!(token_data.amount, 600_000);
504    }
505
506    #[test]
507    fn test_derive_pda() {
508        let svm = LiteSVM::new();
509        let program_id = Pubkey::new_unique();
510        let seeds: &[&[u8]] = &[b"test", b"seeds"];
511
512        let (pda, bump) = svm.derive_pda(seeds, &program_id);
513
514        // Verify PDA is valid (off-curve)
515        assert!(!pda.is_on_curve());
516
517        // Verify we can recreate the PDA with the bump
518        let expected_pda =
519            Pubkey::create_program_address(&[seeds[0], seeds[1], &[bump]], &program_id).unwrap();
520        assert_eq!(pda, expected_pda);
521    }
522
523    #[test]
524    fn test_get_pda() {
525        let svm = LiteSVM::new();
526        let program_id = Pubkey::new_unique();
527        let seeds: &[&[u8]] = &[b"vault", b"test"];
528
529        let pda = svm.get_pda(seeds, &program_id);
530
531        // Verify PDA matches derive_pda result
532        let (expected_pda, _) = svm.derive_pda(seeds, &program_id);
533        assert_eq!(pda, expected_pda);
534    }
535
536    #[test]
537    fn test_get_pda_with_bump() {
538        let svm = LiteSVM::new();
539        let program_id = Pubkey::new_unique();
540        let seeds: &[&[u8]] = &[b"escrow"];
541
542        let (pda, bump) = svm.get_pda_with_bump(seeds, &program_id);
543
544        // Verify matches derive_pda
545        let (expected_pda, expected_bump) = svm.derive_pda(seeds, &program_id);
546        assert_eq!(pda, expected_pda);
547        assert_eq!(bump, expected_bump);
548    }
549
550    #[test]
551    fn test_get_current_slot() {
552        let svm = LiteSVM::new();
553
554        let slot = svm.get_current_slot();
555
556        // Initial slot should be 0
557        assert_eq!(slot, 0);
558    }
559
560    #[test]
561    fn test_advance_slot() {
562        let mut svm = LiteSVM::new();
563
564        let initial_slot = svm.get_current_slot();
565        let slots_to_advance = 100;
566
567        svm.advance_slot(slots_to_advance);
568
569        let new_slot = svm.get_current_slot();
570        assert_eq!(new_slot, initial_slot + slots_to_advance);
571    }
572
573    #[test]
574    fn test_advance_slot_multiple_times() {
575        let mut svm = LiteSVM::new();
576
577        svm.advance_slot(10);
578        assert_eq!(svm.get_current_slot(), 10);
579
580        svm.advance_slot(25);
581        assert_eq!(svm.get_current_slot(), 35);
582
583        svm.advance_slot(5);
584        assert_eq!(svm.get_current_slot(), 40);
585    }
586}