mollusk.mdx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. ---
  2. title: Mollusk
  3. description: Write tests for Solana programs in Rust using Mollusk.
  4. ---
  5. [Mollusk](https://github.com/anza-xyz/mollusk) is a lightweight test harness for
  6. Solana programs. It provides a simple interface for testing Solana program
  7. executions in a minified Solana Virtual Machine (SVM) environment.
  8. ```rust
  9. mollusk.process_and_validate_instruction(
  10. &instruction, // <-- Instruction to test
  11. &accounts, // <-- Account states
  12. &checks, // <-- Checks to run on the instruction result
  13. );
  14. ```
  15. It does not create any semblance of a validator runtime, but instead provisions
  16. a program execution pipeline directly from lower-level SVM components.
  17. In summary, the main processor - `process_instruction` - creates minified
  18. instances of Agave's program cache, transaction context, and invoke context. It
  19. uses these components to directly execute the provided program's ELF using the
  20. BPF Loader.
  21. Because it does not use AccountsDB, Bank, or any other large Agave components,
  22. the harness is exceptionally fast. However, it does require the user to provide
  23. an explicit list of accounts to use, since it has nowhere to load them from.
  24. The test environment can be further configured by adjusting the compute budget,
  25. feature set, or sysvars. These configurations are stored directly on the test
  26. harness (the `Mollusk` struct), but can be manipulated through a handful of
  27. helpers.
  28. Four main API methods are offered:
  29. - `process_instruction`: Process an instruction and return the result.
  30. - `process_and_validate_instruction`: Process an instruction and perform a
  31. series of checks on the result, panicking if any checks fail.
  32. - `process_instruction_chain`: Process a chain of instructions and return the
  33. result.
  34. - `process_and_validate_instruction_chain`: Process a chain of instructions and
  35. perform a series of checks on each result, panicking if any checks fail.
  36. ## Single Instructions
  37. Both `process_instruction` and `process_and_validate_instruction` deal with
  38. single instructions. The former simply processes the instruction and returns the
  39. result, while the latter processes the instruction and then performs a series of
  40. checks on the result. In both cases, the result is also returned.
  41. ```rust
  42. use {
  43. mollusk_svm::Mollusk,
  44. solana_sdk::{account::Account, instruction::{AccountMeta, Instruction}, pubkey::Pubkey},
  45. };
  46. let program_id = Pubkey::new_unique();
  47. let key1 = Pubkey::new_unique();
  48. let key2 = Pubkey::new_unique();
  49. let instruction = Instruction::new_with_bytes(
  50. program_id,
  51. &[],
  52. vec![
  53. AccountMeta::new(key1, false),
  54. AccountMeta::new_readonly(key2, false),
  55. ],
  56. );
  57. let accounts = vec![
  58. (key1, Account::default()),
  59. (key2, Account::default()),
  60. ];
  61. let mollusk = Mollusk::new(&program_id, "my_program");
  62. // Execute the instruction and get the result.
  63. let result = mollusk.process_instruction(&instruction, &accounts);
  64. ```
  65. To apply checks via `process_and_validate_instruction`, developers can use the
  66. `Check` enum, which provides a set of common checks.
  67. ```rust
  68. use {
  69. mollusk_svm::{Mollusk, result::Check},
  70. solana_sdk::{
  71. account::Account,
  72. instruction::{AccountMeta, Instruction},
  73. pubkey::Pubkey
  74. system_instruction,
  75. system_program,
  76. },
  77. };
  78. let sender = Pubkey::new_unique();
  79. let recipient = Pubkey::new_unique();
  80. let base_lamports = 100_000_000u64;
  81. let transfer_amount = 42_000u64;
  82. let instruction = system_instruction::transfer(&sender, &recipient, transfer_amount);
  83. let accounts = [
  84. (
  85. sender,
  86. Account::new(base_lamports, 0, &system_program::id()),
  87. ),
  88. (
  89. recipient,
  90. Account::new(base_lamports, 0, &system_program::id()),
  91. ),
  92. ];
  93. let checks = vec![
  94. Check::success(),
  95. Check::compute_units(system_processor::DEFAULT_COMPUTE_UNITS),
  96. Check::account(&sender)
  97. .lamports(base_lamports - transfer_amount)
  98. .build(),
  99. Check::account(&recipient)
  100. .lamports(base_lamports + transfer_amount)
  101. .build(),
  102. ];
  103. Mollusk::default().process_and_validate_instruction(
  104. &instruction,
  105. &accounts,
  106. &checks,
  107. );
  108. ```
  109. Note: `Mollusk::default()` will create a new `Mollusk` instance without adding
  110. any provided BPF programs. It will still contain a subset of the default builtin
  111. programs. For more builtin programs, you can add them yourself or use the
  112. `all-builtins` feature.
  113. ## Instruction Chains
  114. Both `process_instruction_chain` and `process_and_validate_instruction_chain`
  115. deal with chains of instructions. The former processes each instruction in the
  116. chain and returns the final result, while the latter processes each instruction
  117. in the chain and then performs a series of checks on each result. In both cases,
  118. the final result is also returned.
  119. ```rust
  120. use {
  121. mollusk_svm::Mollusk,
  122. solana_sdk::{account::Account, pubkey::Pubkey, system_instruction},
  123. };
  124. let mollusk = Mollusk::default();
  125. let alice = Pubkey::new_unique();
  126. let bob = Pubkey::new_unique();
  127. let carol = Pubkey::new_unique();
  128. let dave = Pubkey::new_unique();
  129. let starting_lamports = 500_000_000;
  130. let alice_to_bob = 100_000_000;
  131. let bob_to_carol = 50_000_000;
  132. let bob_to_dave = 50_000_000;
  133. mollusk.process_instruction_chain(
  134. &[
  135. system_instruction::transfer(&alice, &bob, alice_to_bob),
  136. system_instruction::transfer(&bob, &carol, bob_to_carol),
  137. system_instruction::transfer(&bob, &dave, bob_to_dave),
  138. ],
  139. &[
  140. (alice, system_account_with_lamports(starting_lamports)),
  141. (bob, system_account_with_lamports(starting_lamports)),
  142. (carol, system_account_with_lamports(starting_lamports)),
  143. (dave, system_account_with_lamports(starting_lamports)),
  144. ],
  145. );
  146. ```
  147. Just like with `process_and_validate_instruction`, developers can use the
  148. `Check` enum to apply checks via `process_and_validate_instruction_chain`.
  149. Notice that `process_and_validate_instruction_chain` takes a slice of tuples,
  150. where each tuple contains an instruction and a slice of checks. This allows the
  151. developer to apply specific checks to each instruction in the chain. The result
  152. returned by the method is the final result of the last instruction in the chain.
  153. ```rust
  154. use {
  155. mollusk_svm::{Mollusk, result::Check},
  156. solana_sdk::{account::Account, pubkey::Pubkey, system_instruction},
  157. };
  158. let mollusk = Mollusk::default();
  159. let alice = Pubkey::new_unique();
  160. let bob = Pubkey::new_unique();
  161. let carol = Pubkey::new_unique();
  162. let dave = Pubkey::new_unique();
  163. let starting_lamports = 500_000_000;
  164. let alice_to_bob = 100_000_000;
  165. let bob_to_carol = 50_000_000;
  166. let bob_to_dave = 50_000_000;
  167. mollusk.process_and_validate_instruction_chain(
  168. &[
  169. (
  170. // 0: Alice to Bob
  171. &system_instruction::transfer(&alice, &bob, alice_to_bob),
  172. &[
  173. Check::success(),
  174. Check::account(&alice)
  175. .lamports(starting_lamports - alice_to_bob) // Alice pays
  176. .build(),
  177. Check::account(&bob)
  178. .lamports(starting_lamports + alice_to_bob) // Bob receives
  179. .build(),
  180. Check::account(&carol)
  181. .lamports(starting_lamports) // Unchanged
  182. .build(),
  183. Check::account(&dave)
  184. .lamports(starting_lamports) // Unchanged
  185. .build(),
  186. ],
  187. ),
  188. (
  189. // 1: Bob to Carol
  190. &system_instruction::transfer(&bob, &carol, bob_to_carol),
  191. &[
  192. Check::success(),
  193. Check::account(&alice)
  194. .lamports(starting_lamports - alice_to_bob) // Unchanged
  195. .build(),
  196. Check::account(&bob)
  197. .lamports(starting_lamports + alice_to_bob - bob_to_carol) // Bob pays
  198. .build(),
  199. Check::account(&carol)
  200. .lamports(starting_lamports + bob_to_carol) // Carol receives
  201. .build(),
  202. Check::account(&dave)
  203. .lamports(starting_lamports) // Unchanged
  204. .build(),
  205. ],
  206. ),
  207. (
  208. // 2: Bob to Dave
  209. &system_instruction::transfer(&bob, &dave, bob_to_dave),
  210. &[
  211. Check::success(),
  212. Check::account(&alice)
  213. .lamports(starting_lamports - alice_to_bob) // Unchanged
  214. .build(),
  215. Check::account(&bob)
  216. .lamports(starting_lamports + alice_to_bob - bob_to_carol - bob_to_dave) // Bob pays
  217. .build(),
  218. Check::account(&carol)
  219. .lamports(starting_lamports + bob_to_carol) // Unchanged
  220. .build(),
  221. Check::account(&dave)
  222. .lamports(starting_lamports + bob_to_dave) // Dave receives
  223. .build(),
  224. ],
  225. ),
  226. ],
  227. &[
  228. (alice, system_account_with_lamports(starting_lamports)),
  229. (bob, system_account_with_lamports(starting_lamports)),
  230. (carol, system_account_with_lamports(starting_lamports)),
  231. (dave, system_account_with_lamports(starting_lamports)),
  232. ],
  233. );
  234. ```
  235. It's important to understand that instruction chains _should not_ be considered
  236. equivalent to Solana transactions. Mollusk does not impose constraints on
  237. instruction chains, such as loaded account keys or size. Developers should
  238. recognize that instruction chains are primarily used for testing program
  239. execution.
  240. ## Benchmarking Compute Units
  241. The Mollusk Compute Unit Bencher can be used to benchmark the compute unit usage
  242. of Solana programs. It provides a simple API for developers to write benchmarks
  243. for their programs, which can be checked while making changes to the program.
  244. A markdown file is generated, which captures all of the compute unit benchmarks.
  245. If a benchmark has a previous value, the delta is also recorded. This can be
  246. useful for developers to check the implications of changes to the program on
  247. compute unit usage.
  248. ```rust
  249. use {
  250. mollusk_svm_bencher::MolluskComputeUnitBencher,
  251. mollusk_svm::Mollusk,
  252. /* ... */
  253. };
  254. // Optionally disable logging.
  255. solana_logger::setup_with("");
  256. /* Instruction & accounts setup ... */
  257. let mollusk = Mollusk::new(&program_id, "my_program");
  258. MolluskComputeUnitBencher::new(mollusk)
  259. .bench(("bench0", &instruction0, &accounts0))
  260. .bench(("bench1", &instruction1, &accounts1))
  261. .bench(("bench2", &instruction2, &accounts2))
  262. .bench(("bench3", &instruction3, &accounts3))
  263. .must_pass(true)
  264. .out_dir("../target/benches")
  265. .execute();
  266. ```
  267. The `must_pass` argument can be provided to trigger a panic if any defined
  268. benchmark tests do not pass. `out_dir` specifies the directory where the
  269. markdown file will be written.
  270. Developers can invoke this benchmark test with `cargo bench`. They may need to
  271. add a bench to the project's `Cargo.toml`.
  272. ```toml
  273. [[bench]]
  274. name = "compute_units"
  275. harness = false
  276. ```
  277. The markdown file will contain entries according to the defined benchmarks.
  278. ```markdown
  279. | Name | CUs | Delta |
  280. | ------ | ----- | ------ |
  281. | bench0 | 450 | -- |
  282. | bench1 | 579 | -129 |
  283. | bench2 | 1,204 | +754 |
  284. | bench3 | 2,811 | +2,361 |
  285. ```