Expand description
Fast, lightweight, in-process Solana VM for testing programs.
hpsvm executes Solana transactions in-process against a single mutable
environment: fund accounts, stage transactions, then keep or roll back
their post-state. Hpsvm is the ready-made instantiation (the base type
family); Svm is the generic host behind it, parameterized by a
SvmTypesHost family.
§Minimal example
use hpsvm::Hpsvm;
use solana_address::Address;
use solana_keypair::Keypair;
use solana_message::Message;
use solana_signer::Signer;
use solana_system_interface::instruction::transfer;
use solana_transaction::Transaction;
let from_keypair = Keypair::new();
let from = from_keypair.pubkey();
let to = Address::new_unique();
let mut svm = Hpsvm::new();
svm.airdrop(&from, 1_000_000_000).unwrap();
svm.airdrop(&to, 1_000_000_000).unwrap();
let instruction = transfer(&from, &to, 64);
let tx = Transaction::new(
&[&from_keypair],
Message::new(&[instruction], Some(&from)),
svm.latest_blockhash(),
);
svm.transact(tx).expect("transact should stage").commit().unwrap();
assert_eq!(svm.get_balance(&from).unwrap(), 999_994_936);
assert_eq!(svm.get_balance(&to).unwrap(), 1_000_000_064);
// A second transfer can be staged and then rolled back.
let retry = transfer(&from, &to, 100);
let tx2 = Transaction::new(
&[&from_keypair],
Message::new(&[retry], Some(&from)),
svm.latest_blockhash(),
);
svm.transact(tx2).expect("transact should stage").discard().unwrap();
assert_eq!(svm.get_balance(&from).unwrap(), 999_994_936);
assert_eq!(svm.get_balance(&to).unwrap(), 1_000_000_064);§Architecture
§Type family
Svm is generic over a SvmTypesHost family selecting the spec id,
transaction envelope, host, and extension slots. BaseSvmTypes is the
stock family and Hpsvm names Svm<'static, BaseSvmTypes>. A custom
family implements SvmTypesHost (plus SvmTypes for the defaulted
slots) and runs anywhere the base family does.
§Execution protocol
Version is the execution-shaping protocol configuration: feature set,
signature verification, blockhash checks, compute budget, rent, limits, and
the ClusterSpecId profile. Inspect it with Svm::version; change it
with the set_* reconfigurers (Svm::set_feature_set,
Svm::set_sigverify, Svm::set_compute_budget). Host is the
execution-facing view of the VM that every handler receives.
§Staged execution
Svm::transact executes and stages the post-state in the live overlay,
returning an ExecutedTx that borrows the VM. ExecutedTx::commit
keeps the staged state and records history, ExecutedTx::discard rolls
it back, and ExecutedTx::commit_with streams each account change into
a StateChangeSink first. Svm::try_transact and
Svm::transact_detached return the owned readonly DetachedTx
without staging; Svm::send_transaction is transact plus commit in
one call.
§Transaction registry
Every entry point dispatches through a TxRegistry keyed by TxTag.
Svm::register_tx_handler replaces the TxHandler for one tag, which
is how tests and forks intercept execution; the handler helper builds
a handler from a validate/execute pair.
§Precompiles
A PrecompileProvider answers which program ids run as precompiles.
PrecompileSet is the map-backed provider, NoPrecompiles disables
dispatch, and Svm::set_precompile_provider swaps the active provider
(plus Precompile for a single entry).
§Inspectors
An Inspector observes execution through &mut self hooks. Install one
with Svm::set_inspector (or Svm::with_inspector) and recover it
with Svm::clear_inspector. OriginFiltered scopes hooks by
TransactionOrigin via OriginFilter; InspectMode is the
const-generic switch that compiles hook dispatch away when no inspector is
live.
§Declaring instructions
hpsvm_macros::svm_instruction declares a builtin instruction from a
function, threaded through SvmInstructionCtx with declared
SvmInstructionAccounts (see examples/custom_svm.rs below).
§Errors
Setup and fatal staging failures surface as
HPSVMError; per-transaction outcomes are
TransactionResults carrying
TransactionMetadata or
FailedTransactionMetadata,
with ExecutionDiagnostics for
pre/post diffs. Handler failures use HandlerError, registry misuse
TxRegistryError, validation TxValidationError, and host violations
HostError.
§State model
Staging journals overlay mutations behind an internal checkpoint (rolled
back by discard); caches and history derive from the overlay. The
checkpoint type and the account database are crate-internal; the public
state views are AccountsView and StateChangeSet.
§Deploying programs
Beyond transfers, add compiled programs with Hpsvm::add_program (or
Hpsvm::add_program_from_file) and exercise them through staged or
direct transactions. Pull released programs with solana program dump.
use hpsvm::Hpsvm;
use solana_address::{Address, address};
use solana_instruction::{Instruction, account_meta::AccountMeta};
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
fn test_logging() {
let program_id = address!("Logging111111111111111111111111111111111111");
let account_meta =
AccountMeta { pubkey: Address::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 = Hpsvm::new();
let payer = Keypair::new();
// The `counter` test program is a separate SBF build; skip when absent.
let Some(bytes) = hpsvm_test_support::read_program(hpsvm_test_support::COUNTER) else {
return;
};
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();
// Let's simulate it first
let sim_res = svm.simulate_transaction(tx.clone()).unwrap();
let meta = svm.send_transaction(tx).unwrap();
assert_eq!(sim_res.meta, meta);
// The program should log something
assert!(meta.logs.len() > 1);
assert!(meta.compute_units_consumed < 10_000); // not being precise here in case it changes
}§Compiled-program tiering
Program compilation is tiered, cached and observable — the harness around
agave’s solana-sbpf JIT, not a fork of it:
JitPolicyonVersionselects the tier (Eagercompiles every program at load time on x86_64;Lazyinterprets until a program crosses its hot threshold, then promotes it on a background pool;Offforces the interpreter even on x86_64). The default isEager, byte-identical to the pre-tiering implementation.- The resident entry store (content-keyed by environment identity, ELF hash, loader and slot
bucket) re-links already-compiled entries across VMs and across
rebuild_program_cacheinstead of recompiling them. Svm::prewarm_programsfront-loads a batch stage’s program set before its first transaction (and schedules promotion underLazy).Svm::program_tier_statsreports policy, resident/cache entries, agave compilations and invocation splits, plus promotion outcomes — all from atomics, withOffas the effective policy off x86_64.
use hpsvm::{Hpsvm, JitPolicy};
let mut svm = Hpsvm::new().with_jit_policy(JitPolicy::Off);
assert_eq!(svm.version().jit_policy, JitPolicy::Off);
svm.prewarm_programs(&[]).expect("empty prewarm is a no-op");
let stats = svm.program_tier_stats();
// `Off` is effective on every target, so this doctest holds on x86_64
// and elsewhere alike.
assert_eq!(stats.effective_policy, JitPolicy::Off);
assert_eq!(stats.promotions_attempted, 0);§Time travel
Programs reading the Clock sysvar can be tested across time with
Hpsvm::set_sysvar / Hpsvm::get_sysvar, or jumped across slots with
Hpsvm::warp_to_slot. Here’s an example using a program that panics if
clock.unix_timestamp is greater than 100 (January 1st 1970):
use hpsvm::Hpsvm;
use solana_address::Address;
use solana_clock::Clock;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
fn test_set_clock() {
let program_id = Address::new_unique();
let mut svm = Hpsvm::new();
// The clock example is a separate SBF build; skip when absent.
let Some(bytes) = hpsvm_test_support::read_program(hpsvm_test_support::CLOCK_EXAMPLE)
else {
return;
};
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).expect("clock sysvar override should succeed");
// This will fail because the program expects early 1970 timestamp
let _err = svm.send_transaction(tx.clone()).unwrap_err();
// So let's turn back time
let mut clock = svm.get_sysvar::<Clock>();
clock.unix_timestamp = 50;
svm.set_sysvar::<Clock>(&clock).expect("clock sysvar override should succeed");
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();
}§Custom environments
examples/custom_svm.rs stands up a custom SvmTypesHost family end to
end: custom spec id, block-environment extension, transaction handler,
macro-declared instruction, and custom precompile, each asserted observable
through commit / commit_with flows. Start there when extending the VM.
§Migrating from the pre-refactor API
The refactor replaces the previous surface outright:
HPSVMis nowHpsvm, theSvm<'static, BaseSvmTypes>alias; new code uses genericSvmwith aSvmTypesHostfamily.SvmCfg/RuntimeEnvare nowVersion(seeSvm::version).ExecutionOutcome/commit_transactionare nowExecutedTx:Svm::transactstages,ExecutedTx::commitkeeps,ExecutedTx::discardrolls back, andExecutedTx::commit_withstreams changes into aStateChangeSink.Arc<dyn Inspector>is nowOption<Box<..>>plusSvm::set_inspector(recover withSvm::clear_inspector); scope origins withOriginFiltered.- The boolean precompile switch is now a
PrecompileProvider(seeSvm::set_precompile_provider).
Re-exports§
pub use builder::HpsvmBuilder;pub use error::AnyError;pub use error::DbError;pub use error::DbResult;pub use error::ExecutionError;pub use error::HaltReason;pub use error::HandlerError;pub use error::HostError;pub use error::LoadError;pub use error::TxError;pub use error::TxValidationError;pub use executed::DetachedTx;pub use executed::ExecutedTx;pub use state_change_sink::AccountChange;pub use state_change_sink::AccountChangeRecord;pub use state_change_sink::StateChangeSink;
Modules§
- batch
- Transaction batch planning and parallel execution.
- error
- Error types for the SVM.
- executed
- Staged (
ExecutedTx) and detached (DetachedTx) transaction execution. Staged transaction execution:ExecutedTxandDetachedTx. - fork
fork - RPC-backed account source for forking live cluster state. RPC-backed account source for forking live cluster state.
- instruction
- Solana instruction types and helpers.
- loader
loader - Loader v3 helpers (set_upgrade_authority, deploy_upgradeable_program).
Loader operations for
Svm. - register_
tracing register-tracing - state_
change_ sink - State change sink: lets tooling observe account changes as they commit, without a second pass over live state.
- token
token - SPL token operation helpers (transfer, mint, ATA, freeze, etc.).
Token operations for
Svm. - types
- Public return types and execution metadata.
Structs§
- Account
Source Error - Accounts
View - Read-only facade over the VM accounts database.
- Base
SvmTypes - The base type family: every extension slot is the unit type, except the
spec identifier, which is the
ClusterSpecIdthe VM’sVersiontracks. - Block
Env - Block-scoped runtime state exposed by the VM.
- Cluster
Info - Identity of the cluster profile a
Versionwas derived from. - Default
Execution Handler - The default execution handler: the pre-registry readonly execution path.
- Empty
Invocation Inspect Callback invocation-inspect-callback - Hpsvm
Builder - Typed builder for
Hpsvm. - Native
Precompiles precompiles - The agave-backed default set: today’s precompile behaviour as a provider.
- NoPrecompiles
- The empty provider: no program id dispatches as a precompile.
- Origin
Filtered - Origin-aware decorator for an
Inspector. - Precompile
Set - The active precompile set: program id to handler.
- Program
Tier Stats - Observable compiled-tier execution state.
- Runtime
Limits - Numeric runtime limits shaping execution.
- State
Change Set - An ordered slice of journal mutations produced by one transaction execution (typically a parallel batch worker).
- Svm
- The core Solana Virtual Machine simulator, parameterized by its type family.
- SvmInstruction
Account - One instruction account, as an owned snapshot.
- SvmInstruction
Ctx - Owned handler context for one builtin-instruction invocation.
- TxEnv
- Transaction-scoped environment.
- TxFeature
Flags - Feature-gate decisions for one transaction, resolved once via
TxFeatureFlags::load. - TxRegistry
- Tag-to-handler map consulted by every transaction entry point.
- Version
- All execution-shaping configuration in one value.
Enums§
- Cluster
Spec Id - Built-in cluster profiles, each indexing one
Version::baseentry. - Feature
Gate - Static feature gates resolved once per transaction into
TxFeatureFlags. - Inspect
Mode - Which execution path the VM monomorphizes for inspector dispatch.
- JitPolicy
- Compiled-program tiering policy.
- Origin
Filter - Which transaction origins an
OriginFiltereddecorator forwards. - Transaction
Origin - Origin of a transaction observed by an
Inspector. - TxRegistry
Error - Dispatch failure: either no handler owns the tag, or the owning handler declined the transaction.
- TxTag
- Classifies a transaction envelope for handler dispatch.
Traits§
- Account
Source - Host
- Host capabilities a type family runs against.
- Inspector
- Observes transaction execution and accumulates inspection state.
- Invocation
Inspect Callback invocation-inspect-callback - Low-level execution hook, parameterized by the VM’s type family.
- Precompile
- A single precompile entry: the program id it handles plus its verifier.
- Precompile
Provider - Per-instance precompile dispatch, queried by program id.
- SvmTypes
- Blanket marker for a family whose host is the VM itself.
- SvmTypes
Host - One associated-type family describing every swappable axis of the VM.
- TxHandler
- A typed transaction handler: validation plus execution against the host.
Functions§
- handler
- Adapts a
validateclosure and anexecuteclosure into one handler. - tag_of
- Derives the dispatch tag for a transaction envelope.
Type Aliases§
- HPSVM
- The pre-refactor name for
Svm<'static, BaseSvmTypes>. - Hpsvm
- Ergonomic name for the base VM instantiation.