Skip to main content

sbpf_runtime/
lib.rs

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        // 1. Run tests using sbpf runtime.
233        let mut runtime = setup_sbpf_runtime();
234
235        // MAKE
236        let result = runtime.run(&make_instruction, &accounts)?;
237        // Check program succeeded
238        assert_eq!(result.exit_code, Some(0));
239        let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
240        let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
241        // Check vault is owned by token program
242        assert_eq!(vault_acct.owner, token_program);
243        // Check escrow is owned by our program
244        assert_eq!(escrow_acct.owner, PROGRAM_ID);
245
246        // TAKE
247        let result = runtime.run(&take_instruction, &accounts)?;
248        // Check program succeeded
249        assert_eq!(result.exit_code, Some(0));
250        let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
251        let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
252        // Check that our vault is closed
253        assert_eq!(vault_acct.owner, system_program);
254        assert_eq!(vault_acct.lamports, 0);
255        // Check that our escrow is closed
256        assert_eq!(escrow_acct.owner, system_program);
257        assert_eq!(escrow_acct.lamports, 0);
258
259        // 2. Run same tests using Mollusk.
260        mollusk.process_and_validate_instruction_chain(
261            &[
262                (
263                    &make_instruction,
264                    &[
265                        // Check tests all passed
266                        Check::success(),
267                        // Check vault is owned by token program
268                        Check::account(&vault).owner(&token_program).build(),
269                        // Check escrow is owned by our program
270                        Check::account(&escrow).owner(&PROGRAM_ID).build(),
271                    ],
272                ),
273                (
274                    &take_instruction,
275                    &[
276                        // Check tests all passed
277                        Check::success(),
278                        // Check that our vault is closed
279                        Check::account(&vault).owner(&system_program).build(),
280                        Check::account(&vault).lamports(0).build(),
281                        // Check that our escrow is closed
282                        Check::account(&escrow).owner(&system_program).build(),
283                        Check::account(&escrow).lamports(0).build(),
284                    ],
285                ),
286            ],
287            &accounts,
288        );
289
290        Ok(())
291    }
292
293    #[test]
294    pub fn make_and_refund() -> Result<(), Box<dyn Error>> {
295        let mollusk = setup_mollusk();
296
297        let maker = Address::new_unique();
298        let mint_a = Address::new_unique();
299        let mint_b = Address::new_unique();
300
301        let mint_a_data = spl_token_interface::state::Mint {
302            mint_authority: None.into(),
303            supply: 1_000_000_000_000_000,
304            decimals: 6,
305            is_initialized: true,
306            freeze_authority: None.into(),
307        };
308
309        let mint_b_data = spl_token_interface::state::Mint {
310            mint_authority: None.into(),
311            supply: 1_000_000_000_000_000,
312            decimals: 6,
313            is_initialized: true,
314            freeze_authority: None.into(),
315        };
316
317        let maker_token_a_data = spl_token_interface::state::Account {
318            mint: mint_a.to_bytes().into(),
319            owner: maker.to_bytes().into(),
320            amount: 100_000_000,
321            delegate: None.into(),
322            state: spl_token_interface::state::AccountState::Initialized,
323            is_native: None.into(),
324            delegated_amount: 0,
325            close_authority: None.into(),
326        };
327
328        let (maker_token_a, maker_token_a_account) =
329            mollusk_svm_programs_token::associated_token::create_account_for_associated_token_account(maker_token_a_data);
330
331        let mut mint_a_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
332        let mut mint_b_data_bytes = vec![0u8; spl_token_interface::state::Mint::LEN];
333
334        mint_a_data.pack_into_slice(&mut mint_a_data_bytes);
335        mint_b_data.pack_into_slice(&mut mint_b_data_bytes);
336
337        let maker_account = Account::new(10_000_000_000, 0, &Address::default());
338        let mint_a_account = Account {
339            lamports: mollusk
340                .sysvars
341                .rent
342                .minimum_balance(spl_token_interface::state::Mint::LEN),
343            data: mint_a_data_bytes,
344            owner: mollusk_svm_programs_token::token::ID,
345            executable: false,
346            rent_epoch: 0,
347        };
348
349        let mint_b_account = Account {
350            lamports: mollusk
351                .sysvars
352                .rent
353                .minimum_balance(spl_token_interface::state::Mint::LEN),
354            data: mint_b_data_bytes,
355            owner: mollusk_svm_programs_token::token::ID,
356            executable: false,
357            rent_epoch: 0,
358        };
359
360        let (system_program, system_program_account) =
361            mollusk_svm::program::keyed_account_for_system_program();
362        let (token_program, token_program_account) =
363            mollusk_svm_programs_token::token::keyed_account();
364        let (associated_token_program, associated_token_program_account) =
365            mollusk_svm_programs_token::associated_token::keyed_account();
366
367        let seeds: &[&[u8]] = &[
368            ESCROW_SEED.as_ref(),
369            maker.as_ref(),
370            mint_a.as_ref(),
371            mint_b.as_ref(),
372        ];
373        let escrow = Address::find_program_address(seeds, &PROGRAM_ID).0;
374        let escrow_account = Account::default();
375
376        let vault = spl_associated_token_account_interface::address::get_associated_token_address(
377            &escrow, &mint_a,
378        );
379        let vault_account = Account::default();
380
381        let make_instruction = &Instruction {
382            program_id: PROGRAM_ID,
383            accounts: vec![
384                AccountMeta::new(maker, true),
385                AccountMeta::new_readonly(mint_a, false),
386                AccountMeta::new_readonly(mint_b, false),
387                AccountMeta::new(maker_token_a, false),
388                AccountMeta::new(escrow, false),
389                AccountMeta::new(vault, false),
390                AccountMeta::new_readonly(token_program, false),
391                AccountMeta::new_readonly(associated_token_program, false),
392                AccountMeta::new_readonly(system_program, false),
393            ],
394            data: vec![0, 13, 37, 0, 0, 0, 0, 0, 0, 13, 37, 0, 0, 0, 0, 0, 0],
395        };
396
397        let refund_instruction = &Instruction {
398            program_id: PROGRAM_ID,
399            accounts: vec![
400                AccountMeta::new(maker, true),
401                AccountMeta::new(maker_token_a, false),
402                AccountMeta::new(escrow, false),
403                AccountMeta::new(vault, false),
404                AccountMeta::new_readonly(token_program, false),
405                AccountMeta::new_readonly(system_program, false),
406            ],
407            data: vec![2],
408        };
409
410        let accounts = &vec![
411            (maker, maker_account.clone()),
412            (mint_a, mint_a_account.clone()),
413            (mint_b, mint_b_account.clone()),
414            (maker_token_a, maker_token_a_account.clone()),
415            (escrow, escrow_account.clone()),
416            (vault, vault_account.clone()),
417            (token_program, token_program_account.clone()),
418            (
419                associated_token_program,
420                associated_token_program_account.clone(),
421            ),
422            (system_program, system_program_account.clone()),
423        ];
424
425        // 1. Run tests using sbpf runtime.
426        let mut runtime = setup_sbpf_runtime();
427
428        // MAKE
429        let result = runtime.run(&make_instruction, &accounts)?;
430        // Check program succeeded
431        assert_eq!(result.exit_code, Some(0));
432        let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
433        let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
434        // Check vault is owned by token program
435        assert_eq!(vault_acct.owner, token_program);
436        // Check vault balance matches
437        assert_eq!(&vault_acct.data[64..72], &[13, 37, 0, 0, 0, 0, 0, 0]);
438        // Check escrow is owned by our program
439        assert_eq!(escrow_acct.owner, PROGRAM_ID);
440        // Check escrow amount_out matches
441        assert_eq!(&escrow_acct.data[96..104], &[13, 37, 0, 0, 0, 0, 0, 0]);
442
443        // REFUND
444        let result = runtime.run(&refund_instruction, &accounts)?;
445        // Check program succeeded
446        assert_eq!(result.exit_code, Some(0));
447        let vault_acct = runtime.get_account(&vault).ok_or("vault not found")?;
448        let escrow_acct = runtime.get_account(&escrow).ok_or("escrow not found")?;
449        // Check that our vault is closed
450        assert_eq!(vault_acct.owner, system_program);
451        assert_eq!(vault_acct.lamports, 0);
452        // Check that our escrow is closed
453        assert_eq!(escrow_acct.owner, system_program);
454        assert_eq!(escrow_acct.lamports, 0);
455
456        // 2. Run same tests using Mollusk.
457        mollusk.process_and_validate_instruction_chain(
458            &[
459                (
460                    &make_instruction,
461                    &[
462                        // Check tests all passed
463                        Check::success(),
464                        // Check vault is owned by token program
465                        Check::account(&vault).owner(&token_program).build(),
466                        // Check vault balance matches
467                        Check::account(&vault)
468                            .data_slice(64, &[13, 37, 0, 0, 0, 0, 0, 0])
469                            .build(),
470                        // Check escrow is owned by our program
471                        Check::account(&escrow).owner(&PROGRAM_ID).build(),
472                        // Check escrow amount_out matches
473                        Check::account(&escrow)
474                            .data_slice(96, &[13, 37, 0, 0, 0, 0, 0, 0])
475                            .build(),
476                    ],
477                ),
478                (
479                    &refund_instruction,
480                    &[
481                        // Check tests all passed
482                        Check::success(),
483                        // Check that our vault is closed
484                        Check::account(&vault).owner(&system_program).build(),
485                        Check::account(&vault).lamports(0).build(),
486                        // Check that our escrow is closed
487                        Check::account(&escrow).owner(&system_program).build(),
488                        Check::account(&escrow).lamports(0).build(),
489                    ],
490                ),
491            ],
492            &accounts,
493        );
494
495        Ok(())
496    }
497}