Quasar
Clients & Testing

Testing

Testing Quasar programs with QuasarSVM and Mollusk.

QuasarSVM

QuasarSVM is a local Solana VM harness -- no validator required. SPL Token, Token-2022, and ATA programs are loaded by default. Bindings for both Rust and TypeScript.

Rust

Add QuasarSVM as a dev-dependency:

[dev-dependencies]
quasar-svm = { git = "https://github.com/blueshift-gg/quasar-svm" }
solana-account = "3.4.0"
solana-instruction = { version = "3.2.0", features = ["bincode"] }

Load your compiled .so and process instructions:

use quasar_svm::{Account, ExecutionStatus, Instruction, Pubkey, QuasarSvm};
use solana_address::Address;

fn setup() -> QuasarSvm {
    let elf = include_bytes!("../target/deploy/my_program.so");
    QuasarSvm::new()
        .with_program(&Pubkey::from(crate::ID), elf)
}

#[test]
fn test_deposit() {
    let mut svm = setup();

    let user = Pubkey::new_unique();
    let (vault, _bump) = Address::find_program_address(
        &[b"vault", Address::from(user.to_bytes()).as_ref()],
        &crate::ID,
    );

    let instruction: Instruction = DepositInstruction {
        user: Address::from(user.to_bytes()),
        vault,
        system_program: Address::from(quasar_svm::system_program::ID.to_bytes()),
        amount: 1_000_000_000,
    }.into();

    let result = svm.process_instruction(
        &instruction,
        &[quasar_svm::token::create_keyed_system_account(&user, 10_000_000_000)],
    );

    result.assert_success();

    let vault_after = result.account(&Pubkey::from(vault)).unwrap();
    assert_eq!(vault_after.lamports, 1_000_000_000);
}

process_instruction takes a single instruction and a slice of Account structs (each with address, lamports, data, owner). Accounts not provided default to empty system-owned accounts.

The result gives you:

  • status() → ExecutionStatus::Success or ExecutionStatus::Err(ProgramError)
  • is_ok() / assert_success() / assert_error(expected)
  • accounts → Vec<Account> with post-execution state
  • account(&pubkey) → lookup by address
  • compute_units_consumed, logs, execution_trace

For multiple instructions in sequence, use process_instruction_chain. For dry runs without state changes, use simulate_instruction.

Multi-Step Tests

Feed resulting accounts from one instruction into the next:

let deposit_result = svm.process_instruction(&deposit_ix, &accounts);
deposit_result.assert_success();

let withdraw_result = svm.process_instruction(
    &withdraw_ix,
    &deposit_result.accounts,  // use updated state
);
withdraw_result.assert_success();

Account Factories

quasar_svm::token provides helpers for building pre-initialized accounts:

use quasar_svm::token::*;

create_keyed_system_account(&address, lamports)
create_keyed_mint_account(&address, &mint)
create_keyed_token_account(&address, &token_account)
create_keyed_associated_token_account(&wallet, &mint, amount)

Each has a _with_program variant for Token-2022.

Builder Methods

let svm = QuasarSvm::new()
    .with_program(&program_id, &elf)
    .with_account(account)
    .with_airdrop(&pubkey, 10_000_000_000)
    .with_slot(100)
    .with_compute_budget(200_000);

TypeScript

QuasarSVM has TypeScript bindings for both @solana/web3.js and @solana/kit:

{
  "dependencies": {
    "@blueshift-gg/quasar-svm": "^0.1",
    "@solana/web3.js": "github:blueshift-gg/web3.js#v2"
  }
}

Import the generated client and create a VM:

import { Address, Keypair, KeyedAccountInfo } from "@solana/web3.js";
import { MyProgramClient } from "../target/client/typescript/my_program/web3.js";
import { QuasarSvm, createKeyedSystemAccount } from "@blueshift-gg/quasar-svm/web3.js";
import { readFile } from "node:fs/promises";

const MyProgram = new MyProgramClient();

const vm = new QuasarSvm()
  .addProgram(
    new Address(MyProgramClient.programId),
    await readFile("target/deploy/my_program.so"),
  );

const payer = new Address(Keypair.generate().publicKey);

const instruction = MyProgram.createInitializeInstruction({ payer });
const accounts = [createKeyedSystemAccount(payer)];

const result = vm.processInstruction(instruction, accounts);
result.assertSuccess();

console.log("CU used:", result.computeUnits);

SPL programs are loaded by default -- no extra setup needed for token operations.

The result gives you:

  • status → { ok: true } or { ok: false, error: ProgramError }
  • isSuccess() / assertSuccess() / assertError(expected) / assertCustomError(code)
  • accounts → KeyedAccountInfo[] with post-execution state
  • account(address) → lookup by address, optionally with a decoder
  • computeUnits, logs, executionTrace

The Kit variant (@blueshift-gg/quasar-svm/kit) uses Account<Uint8Array> instead of KeyedAccountInfo but the VM API is identical.

Account Factories

import {
  createKeyedSystemAccount,
  createKeyedMintAccount,
  createKeyedTokenAccount,
  createKeyedAssociatedTokenAccount,
  createKeyedAccount,
} from "@blueshift-gg/quasar-svm/web3.js";

createKeyedSystemAccount(address)                    // 1 SOL default
createKeyedSystemAccount(address, 5_000_000_000n)    // custom lamports
createKeyedMintAccount(address, { decimals: 6 })
createKeyedTokenAccount(address, { mint, owner, amount: 1000n })
createKeyedAssociatedTokenAccount(wallet, mint, 0n)

Running Tests

quasar test                    # build + generate client + run tests
quasar test --filter deposit   # filter by name

Or directly:

cargo test -- --nocapture                           # Rust
npx mocha --require tsx --delay tests/*.test.ts     # TypeScript

Mollusk

Mollusk is a lightweight alternative that processes individual instructions using solana_instruction::Instruction and (Address, solana_account::Account) tuples. No SPL programs bundled -- you wire everything manually:

[dev-dependencies]
mollusk-svm = "0.10.3"
solana-account = "3.4.0"
solana-instruction = { version = "3.2.0", features = ["bincode"] }
use mollusk_svm::{program::keyed_account_for_system_program, Mollusk};

fn setup() -> Mollusk {
    Mollusk::new(&crate::ID, "../../target/deploy/my_program")
}

#[test]
fn test_deposit() {
    let mollusk = setup();
    let (system_program, system_program_account) = keyed_account_for_system_program();

    let user = Address::new_unique();
    let instruction: Instruction = DepositInstruction {
        user, vault, system_program,
        amount: 1_000_000_000,
    }.into();

    let result = mollusk.process_instruction(
        &instruction,
        &[
            (user, Account::new(10_000_000_000, 0, &system_program)),
            (vault, Account::new(0, 0, &system_program)),
            (system_program, system_program_account),
        ],
    );

    assert!(result.program_result.is_ok());
}

QuasarSVM is the recommended default -- it ships with SPL programs, has a richer API (assert_success, account() lookup, account factories, execution_trace), and supports TypeScript. Use Mollusk when you want minimal dependencies and single-instruction isolation.

On this page