Skip to main content
Mollusk is a lightweight test harness for Solana programs written in Rust. It provides a simple interface for testing Solana program executions in a minified Solana Virtual Machine (SVM) environment.
Mollusk is designed for Rust-only testing. For multi-language testing, see [LiteSVMtesting/litesvm).

Overview

Mollusk takes a minimalist approach to testing:
mollusk.process_and_validate_instruction(
    &instruction,   // Instruction to test
    &accounts,      // Account states
    &checks,        // Assertions to verify
);
It creates a minimal SVM environment by provisioning:
  • Program cache
  • Transaction context
  • Invoke context
These components directly execute your program’s ELF using the BPF Loader, without the overhead of a full validator.

Key Features

  • Exceptionally Fast - No validator overhead, direct program execution
  • Explicit Account Management - You provide all accounts needed
  • Configurable Environment - Adjust compute budget, features, and sysvars
  • Built-in Assertions - Ergonomic Check API for validating results
  • Instruction Chains - Test sequences of instructions
  • Compute Unit Benchmarking - Built-in CU usage tracking

When to Use Mollusk

Mollusk is ideal for:
  • Pure Rust testing - When your test suite is entirely in Rust
  • Unit tests - Testing individual instructions in isolation
  • Compute unit optimization - Benchmarking and tracking CU usage
  • Fast CI/CD - Minimal dependencies and fast compilation
  • Anchor programs - Testing with or without Anchor test templates

Installation

Add Mollusk to your test dependencies:
[dev-dependencies]
mollusk-svm = "0.1"
You can also initialize an Anchor project with Mollusk tests:
anchor init --test-template mollusk my-program

Basic Usage

Single Instruction Test

Here’s a simple test that processes one instruction:
use mollusk_svm::Mollusk;
use solana_sdk::{
    account::Account,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
};

#[test]
fn test_initialize() {
    let program_id = Pubkey::new_unique();
    let key1 = Pubkey::new_unique();
    let key2 = Pubkey::new_unique();

    // Create the instruction
    let instruction = Instruction::new_with_bytes(
        program_id,
        &[0], // instruction data
        vec![
            AccountMeta::new(key1, false),
            AccountMeta::new_readonly(key2, false),
        ],
    );

    // Set up account states
    let accounts = vec![
        (key1, Account::default()),
        (key2, Account::default()),
    ];

    // Create Mollusk instance with your program
    let mollusk = Mollusk::new(&program_id, "my_program");

    // Execute the instruction
    let result = mollusk.process_instruction(&instruction, &accounts);
    
    assert!(result.result.is_ok());
}

With Validation Checks

Mollusk provides a Check API for ergonomic result validation:
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
    account::Account,
    instruction::Instruction,
    pubkey::Pubkey,
    system_instruction,
    system_program,
};

#[test]
fn test_transfer_with_checks() {
    let sender = Pubkey::new_unique();
    let recipient = Pubkey::new_unique();
    
    let base_lamports = 100_000_000u64;
    let transfer_amount = 42_000u64;

    // Create transfer instruction
    let instruction = system_instruction::transfer(
        &sender,
        &recipient,
        transfer_amount,
    );
    
    // Set up accounts
    let accounts = [
        (
            sender,
            Account::new(base_lamports, 0, &system_program::id()),
        ),
        (
            recipient,
            Account::new(base_lamports, 0, &system_program::id()),
        ),
    ];
    
    // Define checks
    let checks = vec![
        Check::success(),
        Check::account(&sender)
            .lamports(base_lamports - transfer_amount)
            .build(),
        Check::account(&recipient)
            .lamports(base_lamports + transfer_amount)
            .build(),
    ];

    // Execute with validation
    Mollusk::default().process_and_validate_instruction(
        &instruction,
        &accounts,
        &checks,
    );
}
Mollusk::default() creates an instance without loading custom programs, but includes default builtin programs. Use Mollusk::new() to load your program.

Testing Anchor Programs

When testing Anchor programs, you’ll work with the compiled program binary:
use anchor_lang::prelude::*;
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
    account::Account,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
    system_program,
};

#[test]
fn test_anchor_initialize() {
    // Your Anchor program ID
    let program_id = Pubkey::new_from_array([
        /* program ID bytes */
    ]);
    
    // Create Mollusk with your program
    let mollusk = Mollusk::new(&program_id, "my_anchor_program");
    
    // Set up accounts
    let user = Pubkey::new_unique();
    let my_account = Pubkey::new_unique();
    
    let accounts = vec![
        (
            my_account,
            Account::new(
                0,
                8 + 32, // discriminator + data
                &program_id,
            ),
        ),
        (
            user,
            Account::new(1_000_000_000, 0, &system_program::id()),
        ),
        (
            system_program::id(),
            Account::default(),
        ),
    ];
    
    // Create instruction with Anchor discriminator
    let mut instruction_data = vec![];
    instruction_data.extend_from_slice(
        &anchor_lang::idl::IdlInstruction::discriminator("initialize")
    );
    
    let instruction = Instruction::new_with_bytes(
        program_id,
        &instruction_data,
        vec![
            AccountMeta::new(my_account, false),
            AccountMeta::new(user, true),
            AccountMeta::new_readonly(system_program::id(), false),
        ],
    );
    
    // Execute and validate
    let checks = vec![
        Check::success(),
        Check::account(&my_account)
            .owner(&program_id)
            .build(),
    ];
    
    mollusk.process_and_validate_instruction(
        &instruction,
        &accounts,
        &checks,
    );
}

Instruction Chains

Mollusk can process sequences of instructions, useful for testing workflows:
use mollusk_svm::Mollusk;
use solana_sdk::{
    system_instruction,
    pubkey::Pubkey,
};

#[test]
fn test_transfer_chain() {
    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;
    
    // Execute chain of transfers
    mollusk.process_instruction_chain(
        &[
            system_instruction::transfer(&alice, &bob, 100_000_000),
            system_instruction::transfer(&bob, &carol, 50_000_000),
            system_instruction::transfer(&bob, &dave, 50_000_000),
        ],
        &[
            (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)),
        ],
    );
}

fn system_account_with_lamports(lamports: u64) -> solana_sdk::account::Account {
    solana_sdk::account::Account::new(
        lamports,
        0,
        &solana_sdk::system_program::id(),
    )
}

Validated Instruction Chains

You can validate state after each instruction in a chain:
use mollusk_svm::{Mollusk, result::Check};

#[test]
fn test_validated_chain() {
    let mollusk = Mollusk::default();
    
    let alice = Pubkey::new_unique();
    let bob = Pubkey::new_unique();
    let starting_lamports = 500_000_000;
    let transfer_amount = 100_000_000;
    
    mollusk.process_and_validate_instruction_chain(
        &[
            (
                &system_instruction::transfer(&alice, &bob, transfer_amount),
                &[
                    Check::success(),
                    Check::account(&alice)
                        .lamports(starting_lamports - transfer_amount)
                        .build(),
                    Check::account(&bob)
                        .lamports(starting_lamports + transfer_amount)
                        .build(),
                ],
            ),
            // More instructions with checks...
        ],
        &[
            (alice, system_account_with_lamports(starting_lamports)),
            (bob, system_account_with_lamports(starting_lamports)),
        ],
    );
}
Instruction chains in Mollusk are not equivalent to Solana transactions. They don’t enforce transaction constraints like account limits or size restrictions.

Compute Unit Benchmarking

Mollusk includes a built-in bencher for tracking compute unit usage:
use mollusk_svm_bencher::MolluskComputeUnitBencher;
use mollusk_svm::Mollusk;

#[test]
fn bench_compute_units() {
    // Optionally disable logging
    solana_logger::setup_with("");
    
    let program_id = Pubkey::new_unique();
    let mollusk = Mollusk::new(&program_id, "my_program");
    
    // Set up your instructions and accounts
    let instruction0 = /* ... */;
    let accounts0 = /* ... */;
    
    let instruction1 = /* ... */;
    let accounts1 = /* ... */;
    
    // Run benchmarks
    MolluskComputeUnitBencher::new(mollusk)
        .bench(("initialize", &instruction0, &accounts0))
        .bench(("process", &instruction1, &accounts1))
        .must_pass(true) // Panic if any test fails
        .out_dir("../target/benches")
        .execute();
}
This generates a markdown file with compute unit usage:
| Name       | CUs   | Delta  |
| ---------- | ----- | ------ |
| initialize | 450   | --     |
| process    | 1,204 | +754   |
To run the benchmark:
cargo bench
Add this to your Cargo.toml:
[[bench]]
name = "compute_units"
harness = false

Check API Reference

The Check enum provides several validation methods:
// Success/failure checks
Check::success();
Check::err(program_error);

// Account state checks
Check::account(&pubkey)
    .lamports(amount)
    .data(&expected_data)
    .owner(&program_id)
    .executable(true)
    .build();

// Compute unit checks
Check::compute_units(expected_units);

// Log checks
Check::logs_contain("Expected log message");

Advanced Configuration

Compute Budget

let mollusk = Mollusk::new(&program_id, "my_program")
    .with_compute_budget(1_400_000); // Set max CUs

Feature Set

let mut mollusk = Mollusk::new(&program_id, "my_program");
mollusk.feature_set.activate(&feature_id, 0);

Sysvars

use solana_sdk::clock::Clock;

let mut mollusk = Mollusk::new(&program_id, "my_program");
let mut clock = Clock::default();
clock.unix_timestamp = 1234567890;
mollusk.sysvars.set_clock(clock);

Testing Best Practices

  1. Test one instruction at a time - Keep tests focused and isolated
  2. Use descriptive test names - Make test failures easy to understand
  3. Set up minimal accounts - Only include accounts needed for the test
  4. Validate expected outcomes - Use Check API for comprehensive validation
  5. Benchmark critical paths - Track compute units for expensive operations
  6. Test error cases - Verify your program fails correctly

Example: Full Anchor Test Suite

use anchor_lang::prelude::*;
use mollusk_svm::{Mollusk, result::Check};
use solana_sdk::{
    account::Account,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
};

mod tests {
    use super::*;
    
    fn setup() -> (Mollusk, Pubkey) {
        let program_id = Pubkey::new_unique();
        let mollusk = Mollusk::new(&program_id, "my_program");
        (mollusk, program_id)
    }
    
    #[test]
    fn test_initialize_success() {
        let (mollusk, program_id) = setup();
        
        // Test implementation
        // ...
    }
    
    #[test]
    fn test_initialize_already_initialized() {
        let (mollusk, program_id) = setup();
        
        // Test error case
        // ...
    }
    
    #[test]
    fn test_update_authorized() {
        let (mollusk, program_id) = setup();
        
        // Test update logic
        // ...
    }
}

Limitations

  • Rust only - No TypeScript or Python support
  • Explicit accounts - Must provide all accounts manually
  • No RPC - Direct program execution only
  • Minimal validator behavior - Doesn’t simulate full validator

Comparison with Other Frameworks

FeatureMolluskLiteSVMsolana-program-test
LanguageRust onlyRust, TS, PythonRust only
SpeedFastestVery fastFast
SetupMinimalMinimalModerate
Account ManagementManualAutomaticAutomatic
AssertionsBuilt-in CheckManualManual
Best ForRust unit testsMulti-lang testingRust integration

Next Steps

  • Explore [LiteSVMtesting/litesvm) for multi-language testing
  • Read [Testing Overviewtesting/overview) for testing strategies
  • Check out Mollusk GitHub for more examples
  • Learn about [Compute Unitsreferences/compute-units) optimization

Build docs developers (and LLMs) love