Skip to main content

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_keypair::Keypair;
8use solana_program::pubkey::Pubkey;
9use solana_signer::Signer;
10use solana_transaction::Transaction;
11use spl_associated_token_account::get_associated_token_address;
12use std::error::Error;
13
14/// Test helper methods for LiteSVM
15pub trait TestHelpers {
16    /// Create a new funded keypair
17    ///
18    /// # Example
19    /// ```no_run
20    /// # use litesvm_utils::TestHelpers;
21    /// # use litesvm::LiteSVM;
22    /// # let mut svm = LiteSVM::new();
23    /// let account = svm.create_funded_account(1_000_000_000).unwrap();
24    /// ```
25    fn create_funded_account(&mut self, lamports: u64) -> Result<Keypair, Box<dyn Error>>;
26
27    /// Create multiple funded keypairs
28    ///
29    /// # Example
30    /// ```no_run
31    /// # use litesvm_utils::TestHelpers;
32    /// # use litesvm::LiteSVM;
33    /// # let mut svm = LiteSVM::new();
34    /// let accounts = svm.create_funded_accounts(3, 1_000_000_000).unwrap();
35    /// assert_eq!(accounts.len(), 3);
36    /// ```
37    fn create_funded_accounts(
38        &mut self,
39        count: usize,
40        lamports: u64,
41    ) -> Result<Vec<Keypair>, Box<dyn Error>>;
42
43    /// Create and initialize a token mint
44    ///
45    /// # Example
46    /// ```no_run
47    /// # use litesvm_utils::TestHelpers;
48    /// # use litesvm::LiteSVM;
49    /// # use solana_keypair::Keypair;
50    /// # let mut svm = LiteSVM::new();
51    /// # let authority = Keypair::new();
52    /// let mint = svm.create_token_mint(&authority, 9).unwrap();
53    /// ```
54    fn create_token_mint(
55        &mut self,
56        authority: &Keypair,
57        decimals: u8,
58    ) -> Result<Keypair, Box<dyn Error>>;
59
60    /// Create a token account for a mint
61    ///
62    /// # Example
63    /// ```no_run
64    /// # use litesvm_utils::TestHelpers;
65    /// # use litesvm::LiteSVM;
66    /// # use solana_keypair::Keypair;
67    /// # use solana_signer::Signer;
68    /// # let mut svm = LiteSVM::new();
69    /// # let owner = Keypair::new();
70    /// # let mint = Keypair::new();
71    /// let token_account = svm.create_token_account(&mint.pubkey(), &owner).unwrap();
72    /// ```
73    fn create_token_account(
74        &mut self,
75        mint: &Pubkey,
76        owner: &Keypair,
77    ) -> Result<Keypair, Box<dyn Error>>;
78
79    /// Create an associated token account
80    ///
81    /// # Example
82    /// ```no_run
83    /// # use litesvm_utils::TestHelpers;
84    /// # use litesvm::LiteSVM;
85    /// # use solana_keypair::Keypair;
86    /// # use solana_signer::Signer;
87    /// # let mut svm = LiteSVM::new();
88    /// # let owner = Keypair::new();
89    /// # let mint = Keypair::new();
90    /// let ata = svm.create_associated_token_account(&mint.pubkey(), &owner).unwrap();
91    /// ```
92    fn create_associated_token_account(
93        &mut self,
94        mint: &Pubkey,
95        owner: &Keypair,
96    ) -> Result<Pubkey, Box<dyn Error>>;
97
98    /// Mint tokens to an account
99    ///
100    /// # Example
101    /// ```no_run
102    /// # use litesvm_utils::TestHelpers;
103    /// # use litesvm::LiteSVM;
104    /// # use solana_keypair::Keypair;
105    /// # use solana_signer::Signer;
106    /// # use solana_program::pubkey::Pubkey;
107    /// # let mut svm = LiteSVM::new();
108    /// # let mint = Keypair::new();
109    /// # let token_account = Pubkey::new_unique();
110    /// # let authority = Keypair::new();
111    /// svm.mint_to(&mint.pubkey(), &token_account, &authority, 1_000_000_000).unwrap();
112    /// ```
113    fn mint_to(
114        &mut self,
115        mint: &Pubkey,
116        account: &Pubkey,
117        authority: &Keypair,
118        amount: u64,
119    ) -> Result<(), Box<dyn Error>>;
120
121    /// Derive a program-derived address
122    ///
123    /// # Example
124    /// ```no_run
125    /// # use litesvm_utils::TestHelpers;
126    /// # use litesvm::LiteSVM;
127    /// # use solana_program::pubkey::Pubkey;
128    /// # let svm = LiteSVM::new();
129    /// # let program_id = Pubkey::new_unique();
130    /// let (pda, bump) = svm.derive_pda(&[b"seed"], &program_id);
131    /// ```
132    fn derive_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8);
133
134    /// Get a program-derived address (convenience wrapper for Pubkey::find_program_address)
135    ///
136    /// This is a more convenient version that returns just the PDA without the bump.
137    /// Use this when you don't need the bump seed.
138    ///
139    /// # Example
140    /// ```no_run
141    /// # use litesvm_utils::TestHelpers;
142    /// # use litesvm::LiteSVM;
143    /// # use solana_program::pubkey::Pubkey;
144    /// # use solana_keypair::Keypair;
145    /// # use solana_signer::Signer;
146    /// # let svm = LiteSVM::new();
147    /// # let program_id = Pubkey::new_unique();
148    /// # let maker = Keypair::new();
149    /// # let seed = 42u64;
150    /// // Simple usage with multiple seeds
151    /// let escrow_pda = svm.get_pda(
152    ///     &[b"escrow", maker.pubkey().as_ref(), &seed.to_le_bytes()],
153    ///     &program_id
154    /// );
155    /// ```
156    fn get_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> Pubkey {
157        let (pda, _bump) = self.derive_pda(seeds, program_id);
158        pda
159    }
160
161    /// Get a program-derived address with bump (alias for derive_pda for consistency)
162    ///
163    /// # Example
164    /// ```no_run
165    /// # use litesvm_utils::TestHelpers;
166    /// # use litesvm::LiteSVM;
167    /// # use solana_program::pubkey::Pubkey;
168    /// # let svm = LiteSVM::new();
169    /// # let program_id = Pubkey::new_unique();
170    /// let (pda, bump) = svm.get_pda_with_bump(&[b"seed"], &program_id);
171    /// ```
172    fn get_pda_with_bump(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
173        self.derive_pda(seeds, program_id)
174    }
175
176    /// Get the current slot
177    fn get_current_slot(&self) -> u64;
178
179    /// Advance the slot by a specified amount
180    fn advance_slot(&mut self, slots: u64);
181}
182
183impl TestHelpers for LiteSVM {
184    fn create_funded_account(&mut self, lamports: u64) -> Result<Keypair, Box<dyn Error>> {
185        let keypair = Keypair::new();
186        self.airdrop(&keypair.pubkey(), lamports)
187            .map_err(|e| format!("Failed to airdrop: {:?}", e))?;
188        Ok(keypair)
189    }
190
191    fn create_funded_accounts(
192        &mut self,
193        count: usize,
194        lamports: u64,
195    ) -> Result<Vec<Keypair>, Box<dyn Error>> {
196        let mut accounts = Vec::with_capacity(count);
197        for _ in 0..count {
198            accounts.push(self.create_funded_account(lamports)?);
199        }
200        Ok(accounts)
201    }
202
203    fn create_token_mint(
204        &mut self,
205        authority: &Keypair,
206        decimals: u8,
207    ) -> Result<Keypair, Box<dyn Error>> {
208        let mint = Keypair::new();
209
210        // Calculate rent for mint account
211        let rent = self.minimum_balance_for_rent_exemption(82);
212
213        // Create mint account
214        let create_account_ix = solana_system_interface::instruction::create_account(
215            &authority.pubkey(),
216            &mint.pubkey(),
217            rent,
218            82,
219            &spl_token::id(),
220        );
221
222        // Initialize mint
223        let init_mint_ix = spl_token::instruction::initialize_mint(
224            &spl_token::id(),
225            &mint.pubkey(),
226            &authority.pubkey(),
227            None,
228            decimals,
229        )?;
230
231        // Send transaction
232        let tx = Transaction::new_signed_with_payer(
233            &[create_account_ix, init_mint_ix],
234            Some(&authority.pubkey()),
235            &[authority, &mint],
236            self.latest_blockhash(),
237        );
238
239        self.send_transaction(tx)
240            .map_err(|e| format!("Failed to create mint: {:?}", e.err))?;
241        Ok(mint)
242    }
243
244    fn create_token_account(
245        &mut self,
246        mint: &Pubkey,
247        owner: &Keypair,
248    ) -> Result<Keypair, Box<dyn Error>> {
249        let token_account = Keypair::new();
250
251        // Calculate rent for token account
252        let rent = self.minimum_balance_for_rent_exemption(165);
253
254        // Create account
255        let create_account_ix = solana_system_interface::instruction::create_account(
256            &owner.pubkey(),
257            &token_account.pubkey(),
258            rent,
259            165,
260            &spl_token::id(),
261        );
262
263        // Initialize token account
264        let init_account_ix = spl_token::instruction::initialize_account(
265            &spl_token::id(),
266            &token_account.pubkey(),
267            mint,
268            &owner.pubkey(),
269        )?;
270
271        // Send transaction
272        let tx = Transaction::new_signed_with_payer(
273            &[create_account_ix, init_account_ix],
274            Some(&owner.pubkey()),
275            &[owner, &token_account],
276            self.latest_blockhash(),
277        );
278
279        self.send_transaction(tx)
280            .map_err(|e| format!("Failed to create token account: {:?}", e.err))?;
281        Ok(token_account)
282    }
283
284    fn create_associated_token_account(
285        &mut self,
286        mint: &Pubkey,
287        owner: &Keypair,
288    ) -> Result<Pubkey, Box<dyn Error>> {
289        let ata = get_associated_token_address(&owner.pubkey(), mint);
290
291        // Create ATA instruction
292        let create_ata_ix =
293            spl_associated_token_account::instruction::create_associated_token_account(
294                &owner.pubkey(),
295                &owner.pubkey(),
296                mint,
297                &spl_token::id(),
298            );
299
300        // Send transaction
301        let tx = Transaction::new_signed_with_payer(
302            &[create_ata_ix],
303            Some(&owner.pubkey()),
304            &[owner],
305            self.latest_blockhash(),
306        );
307
308        self.send_transaction(tx)
309            .map_err(|e| format!("Failed to create ATA: {:?}", e.err))?;
310        Ok(ata)
311    }
312
313    fn mint_to(
314        &mut self,
315        mint: &Pubkey,
316        account: &Pubkey,
317        authority: &Keypair,
318        amount: u64,
319    ) -> Result<(), Box<dyn Error>> {
320        // Create mint_to instruction
321        let mint_to_ix = spl_token::instruction::mint_to(
322            &spl_token::id(),
323            mint,
324            account,
325            &authority.pubkey(),
326            &[],
327            amount,
328        )?;
329
330        // Send transaction
331        let tx = Transaction::new_signed_with_payer(
332            &[mint_to_ix],
333            Some(&authority.pubkey()),
334            &[authority],
335            self.latest_blockhash(),
336        );
337
338        self.send_transaction(tx)
339            .map_err(|e| format!("Failed to mint tokens: {:?}", e.err))?;
340        Ok(())
341    }
342
343    fn derive_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
344        Pubkey::find_program_address(seeds, program_id)
345    }
346
347    fn get_current_slot(&self) -> u64 {
348        // LiteSVM doesn't have get_clock, use slot directly
349        self.get_sysvar::<solana_program::clock::Clock>().slot
350    }
351
352    fn advance_slot(&mut self, slots: u64) {
353        let current_slot = self.get_sysvar::<solana_program::clock::Clock>().slot;
354        for i in 0..slots {
355            self.warp_to_slot(current_slot + i + 1);
356        }
357    }
358}
359
360#[cfg(test)]
361mod tests {
362    use super::*;
363    use solana_program_pack::Pack;
364    use solana_signer::Signer;
365
366    #[test]
367    fn test_create_funded_account() {
368        let mut svm = LiteSVM::new();
369        let lamports = 1_000_000_000;
370
371        let account = svm.create_funded_account(lamports).unwrap();
372
373        // Verify account exists and has correct balance
374        let balance = svm.get_balance(&account.pubkey()).unwrap();
375        assert_eq!(balance, lamports);
376    }
377
378    #[test]
379    fn test_create_funded_accounts() {
380        let mut svm = LiteSVM::new();
381        let count = 5;
382        let lamports = 500_000_000;
383
384        let accounts = svm.create_funded_accounts(count, lamports).unwrap();
385
386        // Verify correct number of accounts created
387        assert_eq!(accounts.len(), count);
388
389        // Verify each account has correct balance
390        for account in &accounts {
391            let balance = svm.get_balance(&account.pubkey()).unwrap();
392            assert_eq!(balance, lamports);
393        }
394
395        // Verify all accounts have unique pubkeys
396        let mut pubkeys: Vec<_> = accounts.iter().map(|k| k.pubkey()).collect();
397        pubkeys.sort();
398        pubkeys.dedup();
399        assert_eq!(pubkeys.len(), count);
400    }
401
402    #[test]
403    fn test_create_token_mint() {
404        let mut svm = LiteSVM::new();
405        let authority = svm.create_funded_account(10_000_000_000).unwrap();
406        let decimals = 9;
407
408        let mint = svm.create_token_mint(&authority, decimals).unwrap();
409
410        // Verify mint account exists
411        let mint_account = svm.get_account(&mint.pubkey()).unwrap();
412        assert_eq!(mint_account.owner, spl_token::id());
413
414        // Verify mint data is correct
415        let mint_data = spl_token::state::Mint::unpack(&mint_account.data).unwrap();
416        assert_eq!(mint_data.decimals, decimals);
417        assert_eq!(mint_data.mint_authority, Some(authority.pubkey()).into());
418        assert_eq!(mint_data.supply, 0);
419    }
420
421    #[test]
422    fn test_create_token_account() {
423        let mut svm = LiteSVM::new();
424        let owner = svm.create_funded_account(10_000_000_000).unwrap();
425        let mint = svm.create_token_mint(&owner, 9).unwrap();
426
427        let token_account = svm.create_token_account(&mint.pubkey(), &owner).unwrap();
428
429        // Verify token account exists
430        let account = svm.get_account(&token_account.pubkey()).unwrap();
431        assert_eq!(account.owner, spl_token::id());
432
433        // Verify token account data
434        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
435        assert_eq!(token_data.mint, mint.pubkey());
436        assert_eq!(token_data.owner, owner.pubkey());
437        assert_eq!(token_data.amount, 0);
438    }
439
440    #[test]
441    fn test_create_associated_token_account() {
442        let mut svm = LiteSVM::new();
443        let owner = svm.create_funded_account(10_000_000_000).unwrap();
444        let mint = svm.create_token_mint(&owner, 9).unwrap();
445
446        let ata = svm
447            .create_associated_token_account(&mint.pubkey(), &owner)
448            .unwrap();
449
450        // Verify ATA is at expected address
451        let expected_ata = get_associated_token_address(&owner.pubkey(), &mint.pubkey());
452        assert_eq!(ata, expected_ata);
453
454        // Verify ATA account exists
455        let account = svm.get_account(&ata).unwrap();
456        assert_eq!(account.owner, spl_token::id());
457
458        // Verify ATA data
459        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
460        assert_eq!(token_data.mint, mint.pubkey());
461        assert_eq!(token_data.owner, owner.pubkey());
462        assert_eq!(token_data.amount, 0);
463    }
464
465    #[test]
466    fn test_mint_to() {
467        let mut svm = LiteSVM::new();
468        let authority = svm.create_funded_account(10_000_000_000).unwrap();
469        let mint = svm.create_token_mint(&authority, 9).unwrap();
470        let token_account = svm
471            .create_associated_token_account(&mint.pubkey(), &authority)
472            .unwrap();
473
474        let amount = 1_000_000_000;
475        svm.mint_to(&mint.pubkey(), &token_account, &authority, amount)
476            .unwrap();
477
478        // Verify token account balance
479        let account = svm.get_account(&token_account).unwrap();
480        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
481        assert_eq!(token_data.amount, amount);
482
483        // Verify mint supply increased
484        let mint_account = svm.get_account(&mint.pubkey()).unwrap();
485        let mint_data = spl_token::state::Mint::unpack(&mint_account.data).unwrap();
486        assert_eq!(mint_data.supply, amount);
487    }
488
489    #[test]
490    fn test_mint_to_multiple_times() {
491        let mut svm = LiteSVM::new();
492        let authority = svm.create_funded_account(10_000_000_000).unwrap();
493        let mint = svm.create_token_mint(&authority, 9).unwrap();
494        let token_account = svm
495            .create_associated_token_account(&mint.pubkey(), &authority)
496            .unwrap();
497
498        // Mint tokens multiple times
499        svm.mint_to(&mint.pubkey(), &token_account, &authority, 100_000)
500            .unwrap();
501        svm.mint_to(&mint.pubkey(), &token_account, &authority, 200_000)
502            .unwrap();
503        svm.mint_to(&mint.pubkey(), &token_account, &authority, 300_000)
504            .unwrap();
505
506        // Verify cumulative balance
507        let account = svm.get_account(&token_account).unwrap();
508        let token_data = spl_token::state::Account::unpack(&account.data).unwrap();
509        assert_eq!(token_data.amount, 600_000);
510    }
511
512    #[test]
513    fn test_derive_pda() {
514        let svm = LiteSVM::new();
515        let program_id = Pubkey::new_unique();
516        let seeds: &[&[u8]] = &[b"test", b"seeds"];
517
518        let (pda, bump) = svm.derive_pda(seeds, &program_id);
519
520        // Verify PDA is valid (off-curve)
521        assert!(!pda.is_on_curve());
522
523        // Verify we can recreate the PDA with the bump
524        let expected_pda =
525            Pubkey::create_program_address(&[seeds[0], seeds[1], &[bump]], &program_id).unwrap();
526        assert_eq!(pda, expected_pda);
527    }
528
529    #[test]
530    fn test_get_pda() {
531        let svm = LiteSVM::new();
532        let program_id = Pubkey::new_unique();
533        let seeds: &[&[u8]] = &[b"vault", b"test"];
534
535        let pda = svm.get_pda(seeds, &program_id);
536
537        // Verify PDA matches derive_pda result
538        let (expected_pda, _) = svm.derive_pda(seeds, &program_id);
539        assert_eq!(pda, expected_pda);
540    }
541
542    #[test]
543    fn test_get_pda_with_bump() {
544        let svm = LiteSVM::new();
545        let program_id = Pubkey::new_unique();
546        let seeds: &[&[u8]] = &[b"escrow"];
547
548        let (pda, bump) = svm.get_pda_with_bump(seeds, &program_id);
549
550        // Verify matches derive_pda
551        let (expected_pda, expected_bump) = svm.derive_pda(seeds, &program_id);
552        assert_eq!(pda, expected_pda);
553        assert_eq!(bump, expected_bump);
554    }
555
556    #[test]
557    fn test_get_current_slot() {
558        let svm = LiteSVM::new();
559
560        let slot = svm.get_current_slot();
561
562        // Initial slot should be 0
563        assert_eq!(slot, 0);
564    }
565
566    #[test]
567    fn test_advance_slot() {
568        let mut svm = LiteSVM::new();
569
570        let initial_slot = svm.get_current_slot();
571        let slots_to_advance = 100;
572
573        svm.advance_slot(slots_to_advance);
574
575        let new_slot = svm.get_current_slot();
576        assert_eq!(new_slot, initial_slot + slots_to_advance);
577    }
578
579    #[test]
580    fn test_advance_slot_multiple_times() {
581        let mut svm = LiteSVM::new();
582
583        svm.advance_slot(10);
584        assert_eq!(svm.get_current_slot(), 10);
585
586        svm.advance_slot(25);
587        assert_eq!(svm.get_current_slot(), 35);
588
589        svm.advance_slot(5);
590        assert_eq!(svm.get_current_slot(), 40);
591    }
592}