1pub mod config;
2pub mod cpi;
3pub mod elf;
4pub mod errors;
5pub mod runtime;
6pub mod serialize;
7pub mod syscalls;
8
9pub use {
10 runtime::{ElfSource, ExecutionResult, LogCollector, Runtime},
11 sbpf_common::instruction::Instruction,
12 sbpf_vm::vm::CallFrame,
13};
14
15#[cfg(test)]
16mod tests {
17 use {
18 crate::{Runtime, config::RuntimeConfig},
19 mollusk_svm::{Mollusk, result::Check},
20 solana_account::Account,
21 solana_address::Address,
22 solana_instruction::{AccountMeta, Instruction},
23 solana_program_pack::Pack,
24 std::{error::Error, path::PathBuf},
25 };
26
27 pub const ESCROW_SEED: &[u8] = b"escrow";
28 pub const PROGRAM_ID: Address =
29 Address::from_str_const("22222222222222222222222222222222222222222222");
30
31 fn fixtures_dir() -> PathBuf {
32 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures")
33 }
34
35 fn setup_mollusk() -> Mollusk {
36 let fixtures = fixtures_dir();
37 let escrow_elf_path = fixtures.join("libupstream_pinocchio_escrow");
38 let mut mollusk = Mollusk::new(&PROGRAM_ID, escrow_elf_path.to_str().unwrap());
39 mollusk_svm_programs_token::token::add_program(&mut mollusk);
40 mollusk_svm_programs_token::associated_token::add_program(&mut mollusk);
41 mollusk
42 }
43
44 fn setup_sbpf_runtime() -> Runtime {
45 let fixtures = fixtures_dir();
46 let escrow_elf_path = fixtures.join("libupstream_pinocchio_escrow.so");
47 let token_elf_path = fixtures.join("token.so");
48 let associated_token_elf_path = fixtures.join("associated_token.so");
49 let config = RuntimeConfig {
50 compute_budget: 1_400_000,
51 max_cpi_depth: 4,
52 ..RuntimeConfig::default()
53 };
54 let mut runtime =
55 Runtime::new(PROGRAM_ID, escrow_elf_path.to_str().unwrap(), config).unwrap();
56 runtime.add_program(&spl_token_interface::ID, token_elf_path.to_str().unwrap());
57 runtime.add_program(
58 &Address::from_str_const("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"),
59 associated_token_elf_path.to_str().unwrap(),
60 );
61 runtime
62 }
63
64 #[test]
65 pub fn make_and_take() -> Result<(), Box<dyn Error>> {
66 let mollusk = setup_mollusk();
67
68 let maker = Address::new_unique();
69 let taker = Address::new_unique();
70 let mint_a = Address::new_unique();
71 let mint_b = Address::new_unique();
72
73 let mint_a_data = spl_token_interface::state::Mint {
74 mint_authority: None.into(),
75 supply: 1_000_000_000_000_000,
76 decimals: 6,
77 is_initialized: true,
78 freeze_authority: None.into(),
79 };
80
81 let mint_b_data = spl_token_interface::state::Mint {
82 mint_authority: None.into(),
83 supply: 1_000_000_000_000_000,
84 decimals: 6,
85 is_initialized: true,
86 freeze_authority: None.into(),
87 };
88
89 let maker_token_a_data = spl_token_interface::state::Account {
90 mint: mint_a.to_bytes().into(),
91 owner: maker.to_bytes().into(),
92 amount: 100_000_000,
93 delegate: None.into(),
94 state: spl_token_interface::state::AccountState::Initialized,
95 is_native: None.into(),
96 delegated_amount: 0,
97 close_authority: None.into(),
98 };
99
100 let taker_token_b_data = spl_token_interface::state::Account {
101 mint: mint_b.to_bytes().into(),
102 owner: taker.to_bytes().into(),
103 amount: 100_000_000,
104 delegate: None.into(),
105 state: spl_token_interface::state::AccountState::Initialized,
106 is_native: None.into(),
107 delegated_amount: 0,
108 close_authority: None.into(),
109 };
110
111 let (maker_token_a, maker_token_a_account) =
112 mollusk_svm_programs_token::associated_token::create_account_for_associated_token_account(maker_token_a_data);
113 let (taker_token_b, taker_token_b_account) =
114 mollusk_svm_programs_token::associated_token::create_account_for_associated_token_account(taker_token_b_data);
115
116 let mut mint_a_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
117 let mut mint_b_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
118
119 mint_a_data.pack_into_slice(&mut mint_a_data_bytes);
120 mint_b_data.pack_into_slice(&mut mint_b_data_bytes);
121
122 let maker_account = Account::new(10_000_000_000, 0, &Address::default());
123 let taker_account = Account::new(10_000_000_000, 0, &Address::default());
124 let mint_a_account = Account {
125 lamports: mollusk
126 .sysvars
127 .rent
128 .minimum_balance(spl_token_interface::state::Mint::LEN),
129 data: mint_a_data_bytes,
130 owner: mollusk_svm_programs_token::token::ID,
131 executable: false,
132 rent_epoch: 0,
133 };
134
135 let mint_b_account = Account {
136 lamports: mollusk
137 .sysvars
138 .rent
139 .minimum_balance(spl_token_interface::state::Mint::LEN),
140 data: mint_b_data_bytes,
141 owner: mollusk_svm_programs_token::token::ID,
142 executable: false,
143 rent_epoch: 0,
144 };
145
146 let (system_program, system_program_account) =
147 mollusk_svm::program::keyed_account_for_system_program();
148 let (token_program, token_program_account) =
149 mollusk_svm_programs_token::token::keyed_account();
150 let (associated_token_program, associated_token_program_account) =
151 mollusk_svm_programs_token::associated_token::keyed_account();
152
153 let seeds: &[&[u8]] = &[
154 ESCROW_SEED.as_ref(),
155 maker.as_ref(),
156 mint_a.as_ref(),
157 mint_b.as_ref(),
158 ];
159 let escrow = Address::find_program_address(seeds, &PROGRAM_ID).0;
160 let escrow_account = Account::default();
161
162 let vault = spl_associated_token_account_interface::address::get_associated_token_address(
163 &escrow, &mint_a,
164 );
165 let vault_account = Account::default();
166
167 let maker_token_b =
168 spl_associated_token_account_interface::address::get_associated_token_address(
169 &maker, &mint_b,
170 );
171 let maker_token_b_account = Account::default();
172 let taker_token_a =
173 spl_associated_token_account_interface::address::get_associated_token_address(
174 &taker, &mint_a,
175 );
176 let taker_token_a_account = Account::default();
177
178 let make_instruction = &Instruction {
179 program_id: PROGRAM_ID,
180 accounts: vec![
181 AccountMeta::new(maker, true),
182 AccountMeta::new_readonly(mint_a, false),
183 AccountMeta::new_readonly(mint_b, false),
184 AccountMeta::new(maker_token_a, false),
185 AccountMeta::new(escrow, false),
186 AccountMeta::new(vault, false),
187 AccountMeta::new_readonly(token_program, false),
188 AccountMeta::new_readonly(associated_token_program, false),
189 AccountMeta::new_readonly(system_program, false),
190 ],
191 data: vec![0, 13, 37, 0, 0, 0, 0, 0, 0, 13, 37, 0, 0, 0, 0, 0, 0],
192 };
193
194 let take_instruction = &Instruction {
195 program_id: PROGRAM_ID,
196 accounts: vec![
197 AccountMeta::new(taker, true),
198 AccountMeta::new(maker, false),
199 AccountMeta::new_readonly(mint_a, false),
200 AccountMeta::new_readonly(mint_b, false),
201 AccountMeta::new(maker_token_b, false),
202 AccountMeta::new(taker_token_a, false),
203 AccountMeta::new(taker_token_b, false),
204 AccountMeta::new(escrow, false),
205 AccountMeta::new(vault, false),
206 AccountMeta::new_readonly(token_program, false),
207 AccountMeta::new_readonly(associated_token_program, false),
208 AccountMeta::new_readonly(system_program, false),
209 ],
210 data: vec![1],
211 };
212
213 let accounts = &vec![
214 (maker, maker_account.clone()),
215 (taker, taker_account.clone()),
216 (mint_a, mint_a_account.clone()),
217 (mint_b, mint_b_account.clone()),
218 (maker_token_a, maker_token_a_account.clone()),
219 (maker_token_b, maker_token_b_account.clone()),
220 (taker_token_a, taker_token_a_account.clone()),
221 (taker_token_b, taker_token_b_account.clone()),
222 (escrow, escrow_account.clone()),
223 (vault, vault_account.clone()),
224 (token_program, token_program_account.clone()),
225 (
226 associated_token_program,
227 associated_token_program_account.clone(),
228 ),
229 (system_program, system_program_account.clone()),
230 ];
231
232 let mut runtime = setup_sbpf_runtime();
234
235 let result = runtime.run(&make_instruction, &accounts)?;
237 let make_cus_consumed = result.compute_units_consumed;
238 assert_eq!(result.exit_code, Some(0));
240 let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
241 let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
242 assert_eq!(vault_acct.owner, token_program);
244 assert_eq!(escrow_acct.owner, PROGRAM_ID);
246
247 let result = runtime.run(&take_instruction, &accounts)?;
249 let take_cus_consumed = result.compute_units_consumed;
250 assert_eq!(result.exit_code, Some(0));
252 let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
253 let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
254 assert_eq!(vault_acct.owner, system_program);
256 assert_eq!(vault_acct.lamports, 0);
257 assert_eq!(escrow_acct.owner, system_program);
259 assert_eq!(escrow_acct.lamports, 0);
260
261 mollusk.process_and_validate_instruction_chain(
263 &[
264 (
265 &make_instruction,
266 &[
267 Check::success(),
269 Check::account(&vault).owner(&token_program).build(),
271 Check::account(&escrow).owner(&PROGRAM_ID).build(),
273 Check::compute_units(make_cus_consumed),
275 ],
276 ),
277 (
278 &take_instruction,
279 &[
280 Check::success(),
282 Check::account(&vault).owner(&system_program).build(),
284 Check::account(&vault).lamports(0).build(),
285 Check::account(&escrow).owner(&system_program).build(),
287 Check::account(&escrow).lamports(0).build(),
288 Check::compute_units(take_cus_consumed),
290 ],
291 ),
292 ],
293 &accounts,
294 );
295
296 Ok(())
297 }
298
299 #[test]
300 pub fn make_and_refund() -> Result<(), Box<dyn Error>> {
301 let mollusk = setup_mollusk();
302
303 let maker = Address::new_unique();
304 let mint_a = Address::new_unique();
305 let mint_b = Address::new_unique();
306
307 let mint_a_data = spl_token_interface::state::Mint {
308 mint_authority: None.into(),
309 supply: 1_000_000_000_000_000,
310 decimals: 6,
311 is_initialized: true,
312 freeze_authority: None.into(),
313 };
314
315 let mint_b_data = spl_token_interface::state::Mint {
316 mint_authority: None.into(),
317 supply: 1_000_000_000_000_000,
318 decimals: 6,
319 is_initialized: true,
320 freeze_authority: None.into(),
321 };
322
323 let maker_token_a_data = spl_token_interface::state::Account {
324 mint: mint_a.to_bytes().into(),
325 owner: maker.to_bytes().into(),
326 amount: 100_000_000,
327 delegate: None.into(),
328 state: spl_token_interface::state::AccountState::Initialized,
329 is_native: None.into(),
330 delegated_amount: 0,
331 close_authority: None.into(),
332 };
333
334 let (maker_token_a, maker_token_a_account) =
335 mollusk_svm_programs_token::associated_token::create_account_for_associated_token_account(maker_token_a_data);
336
337 let mut mint_a_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
338 let mut mint_b_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
339
340 mint_a_data.pack_into_slice(&mut mint_a_data_bytes);
341 mint_b_data.pack_into_slice(&mut mint_b_data_bytes);
342
343 let maker_account = Account::new(10_000_000_000, 0, &Address::default());
344 let mint_a_account = Account {
345 lamports: mollusk
346 .sysvars
347 .rent
348 .minimum_balance(spl_token_interface::state::Mint::LEN),
349 data: mint_a_data_bytes,
350 owner: mollusk_svm_programs_token::token::ID,
351 executable: false,
352 rent_epoch: 0,
353 };
354
355 let mint_b_account = Account {
356 lamports: mollusk
357 .sysvars
358 .rent
359 .minimum_balance(spl_token_interface::state::Mint::LEN),
360 data: mint_b_data_bytes,
361 owner: mollusk_svm_programs_token::token::ID,
362 executable: false,
363 rent_epoch: 0,
364 };
365
366 let (system_program, system_program_account) =
367 mollusk_svm::program::keyed_account_for_system_program();
368 let (token_program, token_program_account) =
369 mollusk_svm_programs_token::token::keyed_account();
370 let (associated_token_program, associated_token_program_account) =
371 mollusk_svm_programs_token::associated_token::keyed_account();
372
373 let seeds: &[&[u8]] = &[
374 ESCROW_SEED.as_ref(),
375 maker.as_ref(),
376 mint_a.as_ref(),
377 mint_b.as_ref(),
378 ];
379 let escrow = Address::find_program_address(seeds, &PROGRAM_ID).0;
380 let escrow_account = Account::default();
381
382 let vault = spl_associated_token_account_interface::address::get_associated_token_address(
383 &escrow, &mint_a,
384 );
385 let vault_account = Account::default();
386
387 let make_instruction = &Instruction {
388 program_id: PROGRAM_ID,
389 accounts: vec![
390 AccountMeta::new(maker, true),
391 AccountMeta::new_readonly(mint_a, false),
392 AccountMeta::new_readonly(mint_b, false),
393 AccountMeta::new(maker_token_a, false),
394 AccountMeta::new(escrow, false),
395 AccountMeta::new(vault, false),
396 AccountMeta::new_readonly(token_program, false),
397 AccountMeta::new_readonly(associated_token_program, false),
398 AccountMeta::new_readonly(system_program, false),
399 ],
400 data: vec![0, 13, 37, 0, 0, 0, 0, 0, 0, 13, 37, 0, 0, 0, 0, 0, 0],
401 };
402
403 let refund_instruction = &Instruction {
404 program_id: PROGRAM_ID,
405 accounts: vec![
406 AccountMeta::new(maker, true),
407 AccountMeta::new(maker_token_a, false),
408 AccountMeta::new(escrow, false),
409 AccountMeta::new(vault, false),
410 AccountMeta::new_readonly(token_program, false),
411 AccountMeta::new_readonly(system_program, false),
412 ],
413 data: vec![2],
414 };
415
416 let accounts = &vec![
417 (maker, maker_account.clone()),
418 (mint_a, mint_a_account.clone()),
419 (mint_b, mint_b_account.clone()),
420 (maker_token_a, maker_token_a_account.clone()),
421 (escrow, escrow_account.clone()),
422 (vault, vault_account.clone()),
423 (token_program, token_program_account.clone()),
424 (
425 associated_token_program,
426 associated_token_program_account.clone(),
427 ),
428 (system_program, system_program_account.clone()),
429 ];
430
431 let mut runtime = setup_sbpf_runtime();
433
434 let result = runtime.run(&make_instruction, &accounts)?;
436 let make_cus_consumed = result.compute_units_consumed;
437 assert_eq!(result.exit_code, Some(0));
439 let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
440 let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
441 assert_eq!(vault_acct.owner, token_program);
443 assert_eq!(&vault_acct.data[64..72], &[13, 37, 0, 0, 0, 0, 0, 0]);
445 assert_eq!(escrow_acct.owner, PROGRAM_ID);
447 assert_eq!(&escrow_acct.data[96..104], &[13, 37, 0, 0, 0, 0, 0, 0]);
449
450 let result = runtime.run(&refund_instruction, &accounts)?;
452 let refund_cus_consumed = result.compute_units_consumed;
453 assert_eq!(result.exit_code, Some(0));
455 let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
456 let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
457 assert_eq!(vault_acct.owner, system_program);
459 assert_eq!(vault_acct.lamports, 0);
460 assert_eq!(escrow_acct.owner, system_program);
462 assert_eq!(escrow_acct.lamports, 0);
463
464 mollusk.process_and_validate_instruction_chain(
466 &[
467 (
468 &make_instruction,
469 &[
470 Check::success(),
472 Check::account(&vault).owner(&token_program).build(),
474 Check::account(&vault)
476 .data_slice(64, &[13, 37, 0, 0, 0, 0, 0, 0])
477 .build(),
478 Check::account(&escrow).owner(&PROGRAM_ID).build(),
480 Check::account(&escrow)
482 .data_slice(96, &[13, 37, 0, 0, 0, 0, 0, 0])
483 .build(),
484 Check::compute_units(make_cus_consumed),
486 ],
487 ),
488 (
489 &refund_instruction,
490 &[
491 Check::success(),
493 Check::account(&vault).owner(&system_program).build(),
495 Check::account(&vault).lamports(0).build(),
496 Check::account(&escrow).owner(&system_program).build(),
498 Check::account(&escrow).lamports(0).build(),
499 Check::compute_units(refund_cus_consumed),
501 ],
502 ),
503 ],
504 &accounts,
505 );
506
507 Ok(())
508 }
509}