Testing and debugging
Learn how to test Anchor programs using various test frameworks in TypeScript and Rust.
LiteSVM
Write tests for Solana programs in Rust, TS/JS or Python using LiteSVM.
litesvm is a fast and lightweight library for testing Solana programs. It works by creating an in-process Solana VM optimized for program developers, making it much faster to run and compile than alternatives like solana-program-test and solana-test-validator. litesvm is available in Rust, TS/JS, and Python (as part of the solders library).
Installation#
cargo add litesvm --devnpm i litesvm -Duv add solders # from solders import litesvmMinimal example#
use litesvm::LiteSVM;use solana_message::Message;use solana_pubkey::Pubkey;use solana_system_interface::instruction::transfer;use solana_keypair::Keypair;use solana_signer::Signer;use solana_transaction::Transaction;
let from_keypair = Keypair::new();let from = from_keypair.pubkey();let to = Pubkey::new_unique();
let mut svm = LiteSVM::new();svm.airdrop(&from, 10_000).unwrap();
let instruction = transfer(&from, &to, 64);let tx = Transaction::new( &[&from_keypair], Message::new(&[instruction], Some(&from)), svm.latest_blockhash(),);let tx_res = svm.send_transaction(tx).unwrap();
let from_account = svm.get_account(&from);let to_account = svm.get_account(&to);assert_eq!(from_account.unwrap().lamports, 4936);assert_eq!(to_account.unwrap().lamports, 64);import { LiteSVM } from 'litesvm'import { PublicKey, Transaction, SystemProgram, Keypair, LAMPORTS_PER_SOL } from '@solana/web3.js'
test('one transfer', () => { const svm = new LiteSVM() const payer = new Keypair() svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)) const receiver = PublicKey.unique() const blockhash = svm.latestBlockhash() const transferLamports = 1_000_000n const ixs = [ SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: receiver, lamports: transferLamports, }), ] const tx = new Transaction() tx.recentBlockhash = blockhash tx.add(...ixs) tx.sign(payer) svm.sendTransaction(tx) const balanceAfter = svm.getBalance(receiver) expect(balanceAfter).toBe(transferLamports)})from solders.keypair import Keypairfrom solders.litesvm import LiteSVMfrom solders.message import Messagefrom solders.pubkey import Pubkeyfrom solders.system_program import transferfrom solders.transaction import VersionedTransaction
def test_transfer() -> None: receiver = Pubkey.new_unique() client = LiteSVM() payer = Keypair() client.airdrop(payer.pubkey(), 1_000_000_000) blockhash = client.latest_blockhash() transfer_lamports = 1_000_000 ixs = [ transfer( { "from_pubkey": payer.pubkey(), "to_pubkey": receiver, "lamports": transfer_lamports, } ) ] msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash) tx = VersionedTransaction(msg, [payer]) client.send_transaction(tx) balance_after = client.get_balance(receiver) assert balance_after == transfer_lamportsDeploying programs#
Testing real programs goes beyond simple token transfers. Add a compiled program to a test with .add_program_from_file().
Tip (Pulling a program from a cluster)
To pull a Solana program from mainnet or devnet, use the solana program dump
command from the Solana CLI.
The following example uses a simple logging program from the Solana Program Library:
use { litesvm::LiteSVM, solana_instruction::{account_meta::AccountMeta, Instruction}, solana_keypair::Keypair, solana_pubkey::{pubkey, Pubkey}, solana_message::{Message, VersionedMessage}, solana_signer::Signer, solana_transaction::VersionedTransaction,};
fn test_logging() { let program_id = pubkey!("Logging111111111111111111111111111111111111"); let account_meta = AccountMeta { pubkey: Pubkey::new_unique(), is_signer: false, is_writable: true, }; let ix = Instruction { program_id, accounts: vec![account_meta], data: vec![5, 10, 11, 12, 13, 14], }; let mut svm = LiteSVM::new(); let payer = Keypair::new(); let bytes = include_bytes!("../../node-litesvm/program_bytes/spl_example_logging.so"); svm.add_program(program_id, bytes); svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap(); let blockhash = svm.latest_blockhash(); let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash); let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap(); // simulate first let sim_res = svm.simulate_transaction(tx.clone()).unwrap(); let meta = svm.send_transaction(tx).unwrap(); assert_eq!(sim_res.meta, meta); assert_eq!(meta.logs[1], "Program log: static string"); assert!(meta.compute_units_consumed < 10_000) // not being precise here in case it changes}import { LiteSVM, TransactionMetadata } from 'litesvm'import { Keypair, LAMPORTS_PER_SOL, PublicKey, Transaction, TransactionInstruction,} from '@solana/web3.js'
test('spl logging', () => { const programId = PublicKey.unique() const svm = new LiteSVM() svm.addProgramFromFile(programId, 'program_bytes/spl_example_logging.so') const payer = new Keypair() svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)) const blockhash = svm.latestBlockhash() const ixs = [ new TransactionInstruction({ programId, keys: [{ pubkey: PublicKey.unique(), isSigner: false, isWritable: false }], }), ] const tx = new Transaction() tx.recentBlockhash = blockhash tx.add(...ixs) tx.sign(payer) // simulate first const simRes = svm.simulateTransaction(tx) const sendRes = svm.sendTransaction(tx) if (sendRes instanceof TransactionMetadata) { expect(simRes.meta().logs()).toEqual(sendRes.logs()) expect(sendRes.logs()[1]).toBe('Program log: static string') } else { throw new Error('Unexpected tx failure') }})from pathlib import Path
from solders.instruction import AccountMeta, Instructionfrom solders.keypair import Keypairfrom solders.litesvm import LiteSVMfrom solders.message import Messagefrom solders.pubkey import Pubkeyfrom solders.transaction import VersionedTransactionfrom solders.transaction_metadata import TransactionMetadata
def test_logging() -> None: program_id = Pubkey.from_string("Logging111111111111111111111111111111111111") ix = Instruction( program_id, bytes([5, 10, 11, 12, 13, 14]), [AccountMeta(Pubkey.new_unique(), is_signer=False, is_writable=True)], ) client = LiteSVM() payer = Keypair() client.add_program_from_file( program_id, Path("tests/fixtures/spl_example_logging.so") ) client.airdrop(payer.pubkey(), 1_000_000_000) blockhash = client.latest_blockhash() msg = Message.new_with_blockhash([ix], payer.pubkey(), blockhash) tx = VersionedTransaction(msg, [payer]) # simulate first sim_res = client.simulate_transaction(tx) meta = client.send_transaction(tx) assert isinstance(meta, TransactionMetadata) assert sim_res.meta() == meta assert meta.logs()[1] == "Program log: static string" assert ( meta.compute_units_consumed() < 10_000 ) # not being precise here in case it changesTime travel#
Many programs rely on the Clock sysvar (for example, a mint that does not become available until after a certain time). With litesvm, dynamically overwrite the Clock sysvar using svm.set_sysvar::<Clock>() (or svm.setClock() in TS, or client.set_clock() in Python). The following example uses a program that panics if clock.unix_timestamp is greater than 100 (which is on January 1st 1970):
use { litesvm::LiteSVM, solana_clock::Clock, solana_instruction::Instruction, solana_keypair::Keypair, solana_message::{Message, VersionedMessage}, solana_pubkey::Pubkey, solana_signer::Signer, solana_transaction::VersionedTransaction,};
fn test_set_clock() { let program_id = Pubkey::new_unique(); let mut svm = LiteSVM::new(); let bytes = include_bytes!("../../node-litesvm/program_bytes/litesvm_clock_example.so"); svm.add_program(program_id, bytes); let payer = Keypair::new(); let payer_address = payer.pubkey(); svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap(); let blockhash = svm.latest_blockhash(); let ixs = [Instruction { program_id, data: vec![], accounts: vec![], }]; let msg = Message::new_with_blockhash(&ixs, Some(&payer_address), &blockhash); let versioned_msg = VersionedMessage::Legacy(msg); let tx = VersionedTransaction::try_new(versioned_msg, &[&payer]).unwrap(); // set the time to January 1st 2000 let mut initial_clock = svm.get_sysvar::<Clock>(); initial_clock.unix_timestamp = 1735689600; svm.set_sysvar::<Clock>(&initial_clock); // this will fail because it's not January 1970 anymore svm.send_transaction(tx).unwrap_err(); // turn back time let mut clock = svm.get_sysvar::<Clock>(); clock.unix_timestamp = 50; svm.set_sysvar::<Clock>(&clock); let ixs2 = [Instruction { program_id, data: vec![1], // unused, this is just to dedup the transaction accounts: vec![], }]; let msg2 = Message::new_with_blockhash(&ixs2, Some(&payer_address), &blockhash); let versioned_msg2 = VersionedMessage::Legacy(msg2); let tx2 = VersionedTransaction::try_new(versioned_msg2, &[&payer]).unwrap(); // now the transaction goes through svm.send_transaction(tx2).unwrap();}import { FailedTransactionMetadata, LiteSVM, TransactionMetadata } from 'litesvm'import { Keypair, LAMPORTS_PER_SOL, PublicKey, Transaction, TransactionInstruction,} from '@solana/web3.js'
test('clock', () => { const programId = PublicKey.unique() const svm = new LiteSVM() svm.addProgramFromFile(programId, 'program_bytes/litesvm_clock_example.so') const payer = new Keypair() svm.airdrop(payer.publicKey, BigInt(LAMPORTS_PER_SOL)) const blockhash = svm.latestBlockhash() const ixs = [new TransactionInstruction({ keys: [], programId, data: Buffer.from('') })] const tx = new Transaction() tx.recentBlockhash = blockhash tx.add(...ixs) tx.sign(payer) // set the time to January 1st 2000 const initialClock = svm.getClock() initialClock.unixTimestamp = 1735689600n svm.setClock(initialClock) // this will fail because the contract wants it to be January 1970 const failed = svm.sendTransaction(tx) if (failed instanceof FailedTransactionMetadata) { expect(failed.err().toString()).toContain('ProgramFailedToComplete') } else { throw new Error('Expected transaction failure here') } // turn back time const newClock = svm.getClock() newClock.unixTimestamp = 50n svm.setClock(newClock) const ixs2 = [ new TransactionInstruction({ keys: [], programId, data: Buffer.from('foobar'), // unused, just here to dedup the tx }), ] const tx2 = new Transaction() tx2.recentBlockhash = blockhash tx2.add(...ixs2) tx2.sign(payer) // now the transaction goes through const success = svm.sendTransaction(tx2) expect(success).toBeInstanceOf(TransactionMetadata)})from pathlib import Path
from solders.instruction import Instructionfrom solders.keypair import Keypairfrom solders.litesvm import LiteSVMfrom solders.message import Messagefrom solders.pubkey import Pubkeyfrom solders.transaction import VersionedTransactionfrom solders.transaction_metadata import FailedTransactionMetadata, TransactionMetadata
def test_set_clock() -> None: program_id = Pubkey.new_unique() client = LiteSVM() client.add_program_from_file( program_id, Path("tests/fixtures/solders_clock_example.so") ) payer = Keypair() client.airdrop(payer.pubkey(), 1_000_000_000) blockhash = client.latest_blockhash() ixs = [Instruction(program_id=program_id, data=b"", accounts=[])] msg = Message.new_with_blockhash(ixs, payer.pubkey(), blockhash) tx = VersionedTransaction(msg, [payer]) # set the time to January 1st 2000 initial_clock = client.get_clock() initial_clock.unix_timestamp = 1735689600 client.set_clock(initial_clock) # this will fail because it's not January 1970 anymore bad_res = client.send_transaction(tx) assert isinstance(bad_res, FailedTransactionMetadata) # turn back time clock = client.get_clock() clock.unix_timestamp = 50 client.set_clock(clock) ixs2 = [ Instruction( program_id=program_id, data=b"foobar", # unused, this is just to dedup the transaction accounts=[], ) ] msg2 = Message.new_with_blockhash(ixs2, payer.pubkey(), blockhash) tx2 = VersionedTransaction(msg2, [payer]) # now the transaction goes through good_res = client.send_transaction(tx2) assert isinstance(good_res, TransactionMetadata)See also: warp_to_slot(), which jumps to a future slot.
Writing arbitrary accounts#
LiteSVM writes any account data, regardless of whether the account state would even be possible.
The following example assigns an account a large USDC balance without holding the USDC mint keypair. This avoids the need to work with fake USDC in tests:
use { litesvm::LiteSVM, solana_account::Account, solana_program_option::COption, solana_program_pack::Pack, solana_pubkey::{pubkey, Pubkey}, spl_associated_token_account_client::address::get_associated_token_address, spl_token::{ state::{Account as TokenAccount, AccountState}, ID as TOKEN_PROGRAM_ID, },};
fn test_infinite_usdc_mint() { let owner = Pubkey::new_unique(); let usdc_mint = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); let ata = get_associated_token_address(&owner, &usdc_mint); let usdc_to_own = 1_000_000_000_000; let token_acc = TokenAccount { mint: usdc_mint, owner: owner, amount: usdc_to_own, delegate: COption::None, state: AccountState::Initialized, is_native: COption::None, delegated_amount: 0, close_authority: COption::None, }; let mut svm = LiteSVM::new(); let mut token_acc_bytes = [0u8; TokenAccount::LEN]; TokenAccount::pack(token_acc, &mut token_acc_bytes).unwrap(); svm.set_account( ata, Account { lamports: 1_000_000_000, data: token_acc_bytes.to_vec(), owner: TOKEN_PROGRAM_ID, executable: false, rent_epoch: 0, }, ) .unwrap(); let raw_account = svm.get_account(&ata).unwrap(); assert_eq!( TokenAccount::unpack(&raw_account.data).unwrap().amount, usdc_to_own )}import { LiteSVM } from 'litesvm'import { PublicKey } from '@solana/web3.js'import { getAssociatedTokenAddressSync, AccountLayout, ACCOUNT_SIZE, TOKEN_PROGRAM_ID,} from '@solana/spl-token'
test('infinite usdc mint', () => { const owner = PublicKey.unique() const usdcMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') const ata = getAssociatedTokenAddressSync(usdcMint, owner, true) const usdcToOwn = 1_000_000_000_000n const tokenAccData = Buffer.alloc(ACCOUNT_SIZE) AccountLayout.encode( { mint: usdcMint, owner, amount: usdcToOwn, delegateOption: 0, delegate: PublicKey.default, delegatedAmount: 0n, state: 1, isNativeOption: 0, isNative: 0n, closeAuthorityOption: 0, closeAuthority: PublicKey.default, }, tokenAccData, ) const svm = new LiteSVM() svm.setAccount(ata, { lamports: 1_000_000_000, data: tokenAccData, owner: TOKEN_PROGRAM_ID, executable: false, }) const rawAccount = svm.getAccount(ata) expect(rawAccount).not.toBeNull() const rawAccountData = rawAccount?.data const decoded = AccountLayout.decode(rawAccountData) expect(decoded.amount).toBe(usdcToOwn)})from solders.account import Accountfrom solders.litesvm import LiteSVMfrom solders.pubkey import Pubkeyfrom solders.token import ID as TOKEN_PROGRAM_IDfrom solders.token.associated import get_associated_token_addressfrom solders.token.state import TokenAccount, TokenAccountState
def test_infinite_usdc_mint() -> None: owner = Pubkey.new_unique() usdc_mint = Pubkey.from_string("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") ata = get_associated_token_address(owner, usdc_mint) usdc_to_own = 1_000_000_000_000 token_acc = TokenAccount( mint=usdc_mint, owner=owner, amount=usdc_to_own, delegate=None, state=TokenAccountState.Initialized, is_native=None, delegated_amount=0, close_authority=None, ) client = LiteSVM() client.set_account( ata, Account( lamports=1_000_000_000, data=bytes(token_acc), owner=TOKEN_PROGRAM_ID, executable=False, ), ) raw_account = client.get_account(ata) assert raw_account is not None raw_account_data = raw_account.data assert TokenAccount.from_bytes(raw_account_data).amount == usdc_to_ownCopying accounts from a live environment#
To copy accounts from mainnet or devnet, use the solana account command in the Solana CLI to save account data to a file.
Other features#
Other things to do with litesvm include:
- Changing the max compute units and other compute budget behaviour using
.with_compute_budget. - Disable transaction signature checking using
.with_sigverify(false). - Find previous transactions using
.get_transaction.
When should I use solana-test-validator?#
While litesvm is faster and more convenient, it is also less like a real RPC node. solana-test-validator is still useful when calling RPC methods that LiteSVM doesn’t support, or when testing something that depends on real-life validator behaviour rather than just program and client code.
Use litesvm wherever possible.
Mollusk
Write tests for Solana programs in Rust using Mollusk.
Mollusk is a lightweight test harness for Solana programs. It provides a simple interface for testing Solana program executions in a minified Solana Virtual Machine (SVM) environment:
mollusk.process_and_validate_instruction( &instruction, // <-- Instruction to test &accounts, // <-- Account states &checks, // <-- Checks to run on the instruction result);It does not create any semblance of a validator runtime, but instead provisions a program execution pipeline directly from lower-level SVM components.
The main processor, process_instruction, creates minified instances of Agave’s program cache, transaction context, and invoke context. It uses these components to directly execute the provided program’s ELF using the BPF Loader.
Because it does not use AccountsDB, Bank, or any other large Agave components, the harness is exceptionally fast, but it requires the user to provide an explicit list of accounts to use, since it has nowhere to load them from.
The test environment can be further configured by adjusting the compute budget, feature set, or sysvars. These configurations are stored directly on the test harness (the Mollusk struct), but can be manipulated through a handful of helpers.
The harness exposes four main API methods:
process_instructionprocesses an instruction and returns the result.process_and_validate_instructionprocesses an instruction and runs a series of checks on the result, panicking if any fail.process_instruction_chainprocesses a chain of instructions and returns the final result.process_and_validate_instruction_chainprocesses a chain of instructions and runs a series of checks on each result, panicking if any fail.
Single instructions#
process_instruction processes a single instruction and returns the result. process_and_validate_instruction does the same, then runs a series of checks against that result before returning it:
use { mollusk_svm::Mollusk, solana_account::Account, solana_sdk::{instruction::{AccountMeta, Instruction}, pubkey::Pubkey},};
let program_id = Pubkey::new_unique();let key1 = Pubkey::new_unique();let key2 = Pubkey::new_unique();
let instruction = Instruction::new_with_bytes( program_id, &[], vec![ AccountMeta::new(key1, false), AccountMeta::new_readonly(key2, false), ],);
let accounts = vec![ (key1, Account::default()), (key2, Account::default()),];
let mollusk = Mollusk::new(&program_id, "my_program");
// Execute the instruction and get the result.let result = mollusk.process_instruction(&instruction, &accounts);To apply checks via process_and_validate_instruction, use the Check enum, which provides a set of common checks:
use { mollusk_svm::{Mollusk, result::Check}, solana_account::Account, solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, system_instruction, system_program, },};
let sender = Pubkey::new_unique();let recipient = Pubkey::new_unique();
let base_lamports = 100_000_000u64;let transfer_amount = 42_000u64;
let instruction = system_instruction::transfer(&sender, &recipient, transfer_amount);let accounts = [ ( sender, Account::new(base_lamports, 0, &system_program::id()), ), ( recipient, Account::new(base_lamports, 0, &system_program::id()), ),];let checks = vec![ Check::success(), Check::compute_units(system_processor::DEFAULT_COMPUTE_UNITS), Check::account(&sender) .lamports(base_lamports - transfer_amount) .build(), Check::account(&recipient) .lamports(base_lamports + transfer_amount) .build(),];
Mollusk::default().process_and_validate_instruction( &instruction, &accounts, &checks,);Note
Mollusk::default() creates a new Mollusk instance without adding any provided BPF programs. It still contains a subset of the default builtin programs. For more builtin programs, add them yourself or use the all-builtins feature.
Instruction chains#
process_instruction_chain processes each instruction in a chain and returns the final result. process_and_validate_instruction_chain does the same, then runs a series of checks on each intermediate result before returning the final one:
use { mollusk_svm::Mollusk, solana_account::Account, solana_sdk::{pubkey::Pubkey, system_instruction},};
let mollusk = Mollusk::default();
let alice = Pubkey::new_unique();let bob = Pubkey::new_unique();let carol = Pubkey::new_unique();let dave = Pubkey::new_unique();
let starting_lamports = 500_000_000;
let alice_to_bob = 100_000_000;let bob_to_carol = 50_000_000;let bob_to_dave = 50_000_000;
mollusk.process_instruction_chain( &[ system_instruction::transfer(&alice, &bob, alice_to_bob), system_instruction::transfer(&bob, &carol, bob_to_carol), system_instruction::transfer(&bob, &dave, bob_to_dave), ], &[ (alice, system_account_with_lamports(starting_lamports)), (bob, system_account_with_lamports(starting_lamports)), (carol, system_account_with_lamports(starting_lamports)), (dave, system_account_with_lamports(starting_lamports)), ],);process_and_validate_instruction_chain takes a slice of tuples, where each tuple pairs an instruction with a slice of checks. This applies specific checks to each instruction in the chain, and the method returns the result of the last instruction:
use { mollusk_svm::{Mollusk, result::Check}, solana_account::Account, solana_sdk::{pubkey::Pubkey, system_instruction},};
let mollusk = Mollusk::default();
let alice = Pubkey::new_unique();let bob = Pubkey::new_unique();let carol = Pubkey::new_unique();let dave = Pubkey::new_unique();
let starting_lamports = 500_000_000;
let alice_to_bob = 100_000_000;let bob_to_carol = 50_000_000;let bob_to_dave = 50_000_000;
mollusk.process_and_validate_instruction_chain( &[ ( // 0: Alice to Bob &system_instruction::transfer(&alice, &bob, alice_to_bob), &[ Check::success(), Check::account(&alice) .lamports(starting_lamports - alice_to_bob) // Alice pays .build(), Check::account(&bob) .lamports(starting_lamports + alice_to_bob) // Bob receives .build(), Check::account(&carol) .lamports(starting_lamports) // Unchanged .build(), Check::account(&dave) .lamports(starting_lamports) // Unchanged .build(), ], ), ( // 1: Bob to Carol &system_instruction::transfer(&bob, &carol, bob_to_carol), &[ Check::success(), Check::account(&alice) .lamports(starting_lamports - alice_to_bob) // Unchanged .build(), Check::account(&bob) .lamports(starting_lamports + alice_to_bob - bob_to_carol) // Bob pays .build(), Check::account(&carol) .lamports(starting_lamports + bob_to_carol) // Carol receives .build(), Check::account(&dave) .lamports(starting_lamports) // Unchanged .build(), ], ), ( // 2: Bob to Dave &system_instruction::transfer(&bob, &dave, bob_to_dave), &[ Check::success(), Check::account(&alice) .lamports(starting_lamports - alice_to_bob) // Unchanged .build(), Check::account(&bob) .lamports(starting_lamports + alice_to_bob - bob_to_carol - bob_to_dave) // Bob pays .build(), Check::account(&carol) .lamports(starting_lamports + bob_to_carol) // Unchanged .build(), Check::account(&dave) .lamports(starting_lamports + bob_to_dave) // Dave receives .build(), ], ), ], &[ (alice, system_account_with_lamports(starting_lamports)), (bob, system_account_with_lamports(starting_lamports)), (carol, system_account_with_lamports(starting_lamports)), (dave, system_account_with_lamports(starting_lamports)), ],);Important (Chains aren't transactions)
Instruction chains are not equivalent to Solana transactions. Mollusk does not impose transaction-level constraints such as loaded account keys or size limits. Use chains for testing program execution, not for modeling transaction semantics.
Benchmarking compute units#
The Mollusk Compute Unit Bencher benchmarks the compute unit usage of Solana programs. It provides a simple API for writing benchmarks that can be re-run as the program changes.
A markdown file is generated containing every compute unit benchmark. If a benchmark has a previous value, the delta is also recorded, making it easy to track the impact of program changes on compute unit usage:
use { mollusk_svm_bencher::MolluskComputeUnitBencher, mollusk_svm::Mollusk, /* ... */};
// Optionally disable logging.solana_logger::setup_with("");
/* Instruction & accounts setup ... */
let mollusk = Mollusk::new(&program_id, "my_program");
MolluskComputeUnitBencher::new(mollusk) .bench(("bench0", &instruction0, &accounts0)) .bench(("bench1", &instruction1, &accounts1)) .bench(("bench2", &instruction2, &accounts2)) .bench(("bench3", &instruction3, &accounts3)) .must_pass(true) .out_dir("../target/benches") .execute();Pass must_pass(true) to trigger a panic if any defined benchmark tests do not pass. out_dir specifies the directory where the markdown file will be written.
Invoke this benchmark test with cargo bench. You may need to add a bench entry to the project’s Cargo.toml:
[[bench]]name = "compute_units"harness = falseThe markdown file will contain entries according to the defined benchmarks:
| Name | CUs | Delta || ------ | ----- | ------ || bench0 | 450 | -- || bench1 | 579 | -129 || bench2 | 1,204 | +754 || bench3 | 2,811 | +2,361 |