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        let make_cus_consumed = result.compute_units_consumed;
238        // Check program succeeded
239        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        // Check vault is owned by token program
243        assert_eq!(vault_acct.owner, token_program);
244        // Check escrow is owned by our program
245        assert_eq!(escrow_acct.owner, PROGRAM_ID);
246
247        // TAKE
248        let result = runtime.run(&take_instruction, &accounts)?;
249        let take_cus_consumed = result.compute_units_consumed;
250        // Check program succeeded
251        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        // Check that our vault is closed
255        assert_eq!(vault_acct.owner, system_program);
256        assert_eq!(vault_acct.lamports, 0);
257        // Check that our escrow is closed
258        assert_eq!(escrow_acct.owner, system_program);
259        assert_eq!(escrow_acct.lamports, 0);
260
261        // 2. Run same tests using Mollusk.
262        mollusk.process_and_validate_instruction_chain(
263            &[
264                (
265                    &make_instruction,
266                    &[
267                        // Check tests all passed
268                        Check::success(),
269                        // Check vault is owned by token program
270                        Check::account(&vault).owner(&token_program).build(),
271                        // Check escrow is owned by our program
272                        Check::account(&escrow).owner(&PROGRAM_ID).build(),
273                        // Check consumed CUs match with runtime
274                        Check::compute_units(make_cus_consumed),
275                    ],
276                ),
277                (
278                    &take_instruction,
279                    &[
280                        // Check tests all passed
281                        Check::success(),
282                        // Check that our vault is closed
283                        Check::account(&vault).owner(&system_program).build(),
284                        Check::account(&vault).lamports(0).build(),
285                        // Check that our escrow is closed
286                        Check::account(&escrow).owner(&system_program).build(),
287                        Check::account(&escrow).lamports(0).build(),
288                        // Check consumed CUs match with runtime
289                        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        // 1. Run tests using sbpf runtime.
432        let mut runtime = setup_sbpf_runtime();
433
434        // MAKE
435        let result = runtime.run(&make_instruction, &accounts)?;
436        let make_cus_consumed = result.compute_units_consumed;
437        // Check program succeeded
438        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        // Check vault is owned by token program
442        assert_eq!(vault_acct.owner, token_program);
443        // Check vault balance matches
444        assert_eq!(&vault_acct.data[64..72], &[13, 37, 0, 0, 0, 0, 0, 0]);
445        // Check escrow is owned by our program
446        assert_eq!(escrow_acct.owner, PROGRAM_ID);
447        // Check escrow amount_out matches
448        assert_eq!(&escrow_acct.data[96..104], &[13, 37, 0, 0, 0, 0, 0, 0]);
449
450        // REFUND
451        let result = runtime.run(&refund_instruction, &accounts)?;
452        let refund_cus_consumed = result.compute_units_consumed;
453        // Check program succeeded
454        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        // Check that our vault is closed
458        assert_eq!(vault_acct.owner, system_program);
459        assert_eq!(vault_acct.lamports, 0);
460        // Check that our escrow is closed
461        assert_eq!(escrow_acct.owner, system_program);
462        assert_eq!(escrow_acct.lamports, 0);
463
464        // 2. Run same tests using Mollusk.
465        mollusk.process_and_validate_instruction_chain(
466            &[
467                (
468                    &make_instruction,
469                    &[
470                        // Check tests all passed
471                        Check::success(),
472                        // Check vault is owned by token program
473                        Check::account(&vault).owner(&token_program).build(),
474                        // Check vault balance matches
475                        Check::account(&vault)
476                            .data_slice(64, &[13, 37, 0, 0, 0, 0, 0, 0])
477                            .build(),
478                        // Check escrow is owned by our program
479                        Check::account(&escrow).owner(&PROGRAM_ID).build(),
480                        // Check escrow amount_out matches
481                        Check::account(&escrow)
482                            .data_slice(96, &[13, 37, 0, 0, 0, 0, 0, 0])
483                            .build(),
484                        // Check consumed CUs match with runtime
485                        Check::compute_units(make_cus_consumed),
486                    ],
487                ),
488                (
489                    &refund_instruction,
490                    &[
491                        // Check tests all passed
492                        Check::success(),
493                        // Check that our vault is closed
494                        Check::account(&vault).owner(&system_program).build(),
495                        Check::account(&vault).lamports(0).build(),
496                        // Check that our escrow is closed
497                        Check::account(&escrow).owner(&system_program).build(),
498                        Check::account(&escrow).lamports(0).build(),
499                        // Check consumed CUs match with runtime
500                        Check::compute_units(refund_cus_consumed),
501                    ],
502                ),
503            ],
504            &accounts,
505        );
506
507        Ok(())
508    }
509}