Testing
Testing Quasar programs with QuasarSVM and Mollusk.

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::SuccessorExecutionStatus::Err(ProgramError)is_ok()/assert_success()/assert_error(expected)accounts→Vec<Account>with post-execution stateaccount(&pubkey)→ lookup by addresscompute_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 stateaccount(address)→ lookup by address, optionally with a decodercomputeUnits,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 nameOr directly:
cargo test -- --nocapture # Rust
npx mocha --require tsx --delay tests/*.test.ts # TypeScriptMollusk
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.
