Skip to main content

quasar_svm/
lib.rs

1mod error;
2mod program_cache;
3mod svm;
4mod sysvars;
5pub mod token;
6
7pub use solana_clock::Clock;
8pub use solana_instruction::{AccountMeta, Instruction};
9pub use solana_instruction_error::InstructionError;
10pub use solana_pubkey::Pubkey;
11pub use solana_rent::Rent;
12pub use solana_sdk_ids;
13
14/// Convenience alias so users can write `quasar_svm::system_program::ID`.
15pub use solana_sdk_ids::system_program;
16
17pub use crate::error::ProgramError;
18pub use crate::program_cache::loader_keys;
19pub use crate::svm::{ExecutionResult, ExecutionTrace, ExecutedInstruction, QuasarSvm, QuasarSvmConfig};
20pub use crate::sysvars::Sysvars;
21pub use std::collections::HashMap;
22
23// ---------------------------------------------------------------------------
24// Account
25// ---------------------------------------------------------------------------
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Account {
29    pub address: Pubkey,
30    pub lamports: u64,
31    pub data: Vec<u8>,
32    pub owner: Pubkey,
33    pub executable: bool,
34}
35
36impl Account {
37    pub fn from_pair(address: Pubkey, account: solana_account::Account) -> Self {
38        Self {
39            address,
40            lamports: account.lamports,
41            data: account.data,
42            owner: account.owner,
43            executable: account.executable,
44        }
45    }
46
47    pub fn to_pair(&self) -> (Pubkey, solana_account::Account) {
48        (
49            self.address,
50            solana_account::Account {
51                lamports: self.lamports,
52                data: self.data.clone(),
53                owner: self.owner,
54                executable: self.executable,
55                rent_epoch: 0,
56            },
57        )
58    }
59}
60
61// ---------------------------------------------------------------------------
62// AccountDiff
63// ---------------------------------------------------------------------------
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct AccountDiff {
67    pub address: Pubkey,
68    pub pre: Account,
69    pub post: Account,
70}
71
72// ---------------------------------------------------------------------------
73// Bundled SPL programs
74// ---------------------------------------------------------------------------
75
76pub const SPL_TOKEN_PROGRAM_ID: Pubkey =
77    solana_pubkey::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
78
79pub const SPL_TOKEN_2022_PROGRAM_ID: Pubkey =
80    solana_pubkey::pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
81
82pub const SPL_ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey =
83    solana_pubkey::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
84
85// ---------------------------------------------------------------------------
86// Builder-style helpers on QuasarSvm
87// ---------------------------------------------------------------------------
88
89impl QuasarSvm {
90    /// Load a BPF program from an ELF byte slice (loader v3 / upgradeable).
91    pub fn with_program(self, program_id: &Pubkey, elf: &[u8]) -> Self {
92        self.add_program(program_id, &loader_keys::LOADER_V3, elf);
93        self
94    }
95
96    /// Load a BPF program with a specific loader version.
97    pub fn with_program_loader(self, program_id: &Pubkey, loader: &Pubkey, elf: &[u8]) -> Self {
98        self.add_program(program_id, loader, elf);
99        self
100    }
101
102    /// Load the bundled SPL Token program.
103    pub fn with_token_program(self) -> Self {
104        let elf = include_bytes!("../programs/spl_token.so");
105        self.with_program_loader(&SPL_TOKEN_PROGRAM_ID, &loader_keys::LOADER_V2, elf)
106    }
107
108    /// Load the bundled SPL Token 2022 program.
109    pub fn with_token_2022_program(self) -> Self {
110        let elf = include_bytes!("../programs/spl_token_2022.so");
111        self.with_program(&SPL_TOKEN_2022_PROGRAM_ID, elf)
112    }
113
114    /// Load the bundled SPL Associated Token program.
115    pub fn with_associated_token_program(self) -> Self {
116        let elf = include_bytes!("../programs/spl_associated_token.so");
117        self.with_program_loader(
118            &SPL_ASSOCIATED_TOKEN_PROGRAM_ID,
119            &loader_keys::LOADER_V2,
120            elf,
121        )
122    }
123
124    /// Pre-populate an account in the SVM's account database.
125    pub fn with_account(mut self, account: Account) -> Self {
126        self.set_account(account);
127        self
128    }
129
130    /// Set the clock slot (convenience for `sysvars.warp_to_slot`).
131    pub fn with_slot(mut self, slot: u64) -> Self {
132        self.sysvars.warp_to_slot(slot);
133        self
134    }
135
136    /// Set the compute budget (builder-style).
137    pub fn with_compute_budget(mut self, max_units: u64) -> Self {
138        self.compute_budget.compute_unit_limit = max_units;
139        self
140    }
141
142    /// Give lamports to an account (builder-style).
143    pub fn with_airdrop(mut self, pubkey: &Pubkey, lamports: u64) -> Self {
144        self.airdrop(pubkey, lamports);
145        self
146    }
147
148    /// Create a rent-exempt account (builder-style).
149    pub fn with_create_account(mut self, pubkey: &Pubkey, space: usize, owner: &Pubkey) -> Self {
150        self.create_account(pubkey, space, owner);
151        self
152    }
153}
154
155// ---------------------------------------------------------------------------
156// ExecutionStatus
157// ---------------------------------------------------------------------------
158
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum ExecutionStatus {
161    Success,
162    Err(ProgramError),
163}
164
165// ---------------------------------------------------------------------------
166// ExecutionResult
167// ---------------------------------------------------------------------------
168
169impl ExecutionResult {
170    /// Returns `ExecutionStatus::Success` or `ExecutionStatus::Err(ProgramError)`.
171    pub fn status(&self) -> ExecutionStatus {
172        match &self.raw_result {
173            Ok(()) => ExecutionStatus::Success,
174            Err(e) => ExecutionStatus::Err(ProgramError::from(e.clone())),
175        }
176    }
177
178    pub fn is_ok(&self) -> bool {
179        self.raw_result.is_ok()
180    }
181
182    pub fn is_err(&self) -> bool {
183        self.raw_result.is_err()
184    }
185
186    /// Panics with the error and program logs if execution failed.
187    pub fn unwrap(&self) {
188        if let Err(ref e) = self.raw_result {
189            panic!("{}", self.format_error(e));
190        }
191    }
192
193    /// Panics with a custom message, error, and program logs.
194    pub fn expect(&self, msg: &str) {
195        if let Err(ref e) = self.raw_result {
196            panic!("{msg}: {}", self.format_error(e));
197        }
198    }
199
200    /// Look up a resulting account by address.
201    pub fn account(&self, address: &Pubkey) -> Option<&Account> {
202        self.accounts.iter().find(|a| a.address == *address)
203    }
204
205    /// Print transaction logs to stdout, nicely formatted.
206    pub fn print_logs(&self) {
207        for log in &self.logs {
208            println!("  {log}");
209        }
210    }
211
212    /// Panic if execution did not succeed.
213    pub fn assert_success(&self) {
214        if let Err(ref e) = self.raw_result {
215            panic!("expected success, got: {}", self.format_error(e));
216        }
217    }
218
219    /// Panic if execution did not fail with the expected error.
220    pub fn assert_error(&self, expected: ProgramError) {
221        match &self.raw_result {
222            Ok(()) => panic!("expected error {:?}, but execution succeeded", expected),
223            Err(e) => {
224                let actual = ProgramError::from(e.clone());
225                assert_eq!(
226                    actual, expected,
227                    "expected error {:?}, got {:?}",
228                    expected, actual
229                );
230            }
231        }
232    }
233
234    fn format_error(&self, e: &InstructionError) -> String {
235        let err = ProgramError::from(e.clone());
236        if self.logs.is_empty() {
237            format!("{err}")
238        } else {
239            format!(
240                "{err}\n\nProgram logs:\n{}",
241                self.logs
242                    .iter()
243                    .map(|l| format!("  {l}"))
244                    .collect::<Vec<_>>()
245                    .join("\n")
246            )
247        }
248    }
249}