1use 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
13pub trait TestHelpers {
15 fn create_funded_account(&mut self, lamports: u64) -> Result<Keypair, Box<dyn Error>>;
25
26 fn create_funded_accounts(
37 &mut self,
38 count: usize,
39 lamports: u64,
40 ) -> Result<Vec<Keypair>, Box<dyn Error>>;
41
42 fn create_token_mint(
54 &mut self,
55 authority: &Keypair,
56 decimals: u8,
57 ) -> Result<Keypair, Box<dyn Error>>;
58
59 fn create_token_account(
72 &mut self,
73 mint: &Pubkey,
74 owner: &Keypair,
75 ) -> Result<Keypair, Box<dyn Error>>;
76
77 fn create_associated_token_account(
90 &mut self,
91 mint: &Pubkey,
92 owner: &Keypair,
93 ) -> Result<Pubkey, Box<dyn Error>>;
94
95 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 fn derive_pda(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8);
129
130 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 fn get_pda_with_bump(&self, seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
168 self.derive_pda(seeds, program_id)
169 }
170
171 fn get_current_slot(&self) -> u64;
173
174 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 let rent = self.minimum_balance_for_rent_exemption(82);
207
208 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 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 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 let rent = self.minimum_balance_for_rent_exemption(165);
248
249 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 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 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 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 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 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 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 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 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 assert_eq!(accounts.len(), count);
382
383 for account in &accounts {
385 let balance = svm.get_balance(&account.pubkey()).unwrap();
386 assert_eq!(balance, lamports);
387 }
388
389 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 let mint_account = svm.get_account(&mint.pubkey()).unwrap();
406 assert_eq!(mint_account.owner, spl_token::id());
407
408 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 let account = svm.get_account(&token_account.pubkey()).unwrap();
425 assert_eq!(account.owner, spl_token::id());
426
427 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 let expected_ata = get_associated_token_address(&owner.pubkey(), &mint.pubkey());
446 assert_eq!(ata, expected_ata);
447
448 let account = svm.get_account(&ata).unwrap();
450 assert_eq!(account.owner, spl_token::id());
451
452 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 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 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 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 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 assert!(!pda.is_on_curve());
516
517 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 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 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 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}