1use std::{
10 env,
11 path::{Path, PathBuf},
12};
13
14use anyhow::*;
15use litesvm::LiteSVM;
16use solana_sdk::{
17 clock::Clock,
18 pubkey::Pubkey,
19 signature::{Keypair, Signer},
20 transaction::Transaction,
21};
22
23pub use solana_address_book::AddressBook;
24
25mod tx_result;
26pub use tx_result::{TXError, TXResult};
27
28mod account_ref;
29pub use account_ref::AccountRef;
30
31mod litesvm_helpers;
32use litesvm_helpers::new_funded_account;
33
34pub mod prelude;
35
36pub struct TestSVM {
38 pub svm: LiteSVM,
40 pub default_fee_payer: Keypair,
42 pub address_book: AddressBook,
44}
45
46impl TestSVM {
47 pub fn init() -> Result<Self> {
49 let mut svm = LiteSVM::new();
50 let default_fee_payer = new_funded_account(&mut svm, 1000 * 1_000_000_000)?;
51 let mut address_book = AddressBook::new();
52 address_book.add_default_accounts()?;
53
54 address_book.add_wallet(default_fee_payer.pubkey(), "default_fee_payer".to_string())?;
56
57 Ok(Self {
58 svm,
59 default_fee_payer,
60 address_book,
61 })
62 }
63
64 pub fn execute_transaction(&mut self, transaction: Transaction) -> TXResult {
66 match self.svm.send_transaction(transaction.clone()) {
67 Result::Ok(tx_result) => Result::Ok(tx_result),
68 Err(e) => Err(Box::new(TXError {
69 transaction,
70 metadata: e.clone(),
71 address_book: self.address_book.clone(),
72 })),
73 }
74 }
75
76 pub fn execute_ixs(
78 &mut self,
79 instructions: &[solana_sdk::instruction::Instruction],
80 ) -> TXResult {
81 self.execute_ixs_with_signers(instructions, &[])
82 }
83
84 pub fn execute_ixs_with_signers(
86 &mut self,
87 instructions: &[solana_sdk::instruction::Instruction],
88 signers: &[&Keypair],
89 ) -> TXResult {
90 let mut all_signers = vec![&self.default_fee_payer];
91 all_signers.extend_from_slice(signers);
92
93 let transaction = Transaction::new_signed_with_payer(
94 instructions,
95 Some(&self.default_fee_payer.pubkey()),
96 &all_signers,
97 self.svm.latest_blockhash(),
98 );
99
100 self.execute_transaction(transaction)
101 }
102
103 pub fn new_wallet(&mut self, name: &str) -> Result<Keypair> {
105 let keypair = new_funded_account(&mut self.svm, 10 * 1_000_000_000)?; let label = format!("wallet:{name}");
107 self.address_book.add_wallet(keypair.pubkey(), label)?;
108 Ok(keypair)
109 }
110
111 pub fn default_fee_payer(&self) -> Pubkey {
113 self.default_fee_payer.pubkey()
114 }
115
116 pub fn add_program_from_path(
118 &mut self,
119 label: &str,
120 pubkey: Pubkey,
121 path: impl AsRef<Path>,
122 ) -> Result<()> {
123 self.svm.add_program_from_file(pubkey, path)?;
124 self.address_book.add_program(pubkey, label)
125 }
126
127 pub fn add_program_fixture(&mut self, fixture_name: &str, pubkey: Pubkey) -> Result<()> {
132 let path = env::var("CARGO_MANIFEST_DIR")
133 .map(PathBuf::from)
134 .map_err(|e| anyhow!("Failed to get environment variable `CARGO_MANIFEST_DIR`: {e}"))?
135 .ancestors()
136 .find_map(|ancestor| {
137 let fixtures_dir = ancestor.join("fixtures");
138 fixtures_dir.exists().then_some(fixtures_dir)
139 })
140 .ok_or_else(|| anyhow!("`fixtures` directory not found"))
141 .map(|fixtures_dir| {
142 fixtures_dir
143 .join("programs")
144 .join(fixture_name)
145 .with_extension("so")
146 })?;
147
148 self.add_program_from_path(fixture_name, pubkey, &path)?;
149 Ok(())
150 }
151
152 pub fn get_pda<T: anchor_lang::AccountDeserialize>(
154 &mut self,
155 label: &str,
156 seeds: &[&[u8]],
157 program_id: Pubkey,
158 ) -> Result<AccountRef<T>> {
159 let (pda, _) = self.get_pda_with_bump(label, seeds, program_id)?;
160 Ok(pda)
161 }
162
163 pub fn get_pda_with_bump<T: anchor_lang::AccountDeserialize>(
165 &mut self,
166 label: &str,
167 seeds: &[&[u8]],
168 program_id: Pubkey,
169 ) -> Result<(AccountRef<T>, u8)> {
170 let (pubkey, bump) = self
171 .address_book
172 .find_pda_with_bump(label, seeds, program_id)?;
173 Ok((AccountRef::new(pubkey), bump))
174 }
175
176 pub fn advance_time(&mut self, seconds: u64) {
179 let mut clock = self.svm.get_sysvar::<Clock>();
180 clock.unix_timestamp += seconds as i64;
181 let num_slots = seconds / 450;
183 clock.slot += num_slots;
184 self.svm.set_sysvar(&clock);
185 }
186
187 pub fn advance_slots(&mut self, num_slots: u32) {
190 let current_slot = self.svm.get_sysvar::<solana_sdk::clock::Clock>().slot;
191 let target_slot = current_slot + num_slots as u64;
192
193 self.svm.warp_to_slot(target_slot);
194 }
195}