hpsvm 0.1.20

A fast and lightweight Solana VM simulator for testing solana programs
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# HPSVM

[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/longcipher/hpsvm)
[![Context7](https://img.shields.io/badge/Website-context7.com-blue)](https://context7.com/longcipher/hpsvm)
[![crates.io](https://img.shields.io/crates/v/hpsvm.svg)](https://crates.io/crates/hpsvm)
[![docs.rs](https://docs.rs/hpsvm/badge.svg)](https://docs.rs/hpsvm)

![hpsvm](https://socialify.git.ci/longcipher/hpsvm/image?font=Source+Code+Pro&language=1&name=1&owner=1&pattern=Circuit+Board&theme=Auto)

## 📍 Overview

`hpsvm` is a fast and lightweight library for testing Solana programs. It works by creating an in-process Solana VM optimized for program developers. This makes it much faster to run and compile than alternatives like `solana-program-test` and `solana-test-validator`. In a further break from tradition, it has an ergonomic API with sane defaults and extensive configurability for those who want it.

`hpsvm` is optimized for low-overhead, in-process test execution. It does not try to emulate Sealevel-style concurrent scheduling inside a single VM instance. State-committing APIs such as `send_transaction` intentionally mutate one in-memory test environment in place.

This is a pure Rust library, making it ideal for Rust-native Solana development workflows.

## ✨ Features

- 🚀 **High Performance**: In-process VM avoids validator process and RPC overhead, so tests run significantly faster than external validators
- 🛠️ **Easy to Use**: Simple API with sensible defaults and comprehensive configuration options
- 🔧 **Pure Rust**: No external dependencies or runtime requirements beyond Rust
- 📊 **Comprehensive Testing**: Supports transactions, account management, and program execution
- 🔄 **Configurable**: Extensive options for customizing the test environment
- 📚 **Well Documented**: Full API documentation and examples

## 🚀 Getting Started

### Prerequisites

- Rust
- Solana CLI (for building test programs)

### 🔧 Installation

Add `hpsvm` as a development dependency to your Solana program project:

```sh
cargo add --dev hpsvm
```

To read through live RPC state while keeping execution local, enable the `fork` feature:

```sh
cargo add --dev hpsvm --features fork
```

### 🤖 Quick Example

Here's a minimal example that demonstrates creating a test environment, airdropping SOL, and executing a transfer transaction:

```rust
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;

// Create keypairs for testing
let from_keypair = Keypair::new();
let from = from_keypair.pubkey();
let to = Address::new_unique();

// Initialize the SVM with default configuration
let mut svm = HPSVM::new();

// Airdrop SOL to the sender account
svm.airdrop(&from, 10_000).unwrap();

// Create a transfer instruction
let instruction = transfer(&from, &to, 64);

// Build and sign the transaction
let tx = Transaction::new(
    &[&from_keypair],
    Message::new(&[instruction], Some(&from)),
    svm.latest_blockhash(),
);

// Execute the transaction
let tx_result = svm.send_transaction(tx).unwrap();

// Verify the results
let from_account = svm.get_account(&from).unwrap();
let to_account = svm.get_account(&to).unwrap();
assert_eq!(from_account.lamports, 4936);  // 10000 - 64 - fee
assert_eq!(to_account.lamports, 64);
```

### 📖 Usage

For more advanced usage, including custom configurations, program deployment, and complex transaction scenarios, see the [full documentation](https://docs.rs/hpsvm).

### Architecture Highlights

`hpsvm` keeps the `HPSVM` facade stable while exposing a few sharper seams for advanced test harnesses:

- `transact` stages a transaction in the live overlay and returns an `ExecutedTx`: `commit` keeps the post-state, `discard` rolls it back. `transact_detached` computes an owned `DetachedTx` without touching VM state.
- `with_account_source` lets the VM read missing accounts from an external source while keeping local writes in the in-memory overlay.
- `block_env` exposes the current blockhash and slot snapshot, and `with_inspector` installs lightweight top-level execution observers.

```rust
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 mut svm = HPSVM::new();
let payer = Keypair::new();
let recipient = Address::new_unique();

svm.airdrop(&payer.pubkey(), 10_000).unwrap();

let tx = Transaction::new(
    &[&payer],
    Message::new(&[transfer(&payer.pubkey(), &recipient, 64)], Some(&payer.pubkey())),
    svm.latest_blockhash(),
);

let staged = svm.transact(tx).unwrap();
assert!(staged.result().is_ok());

let commit = staged.commit();
assert!(commit.is_ok());
assert_eq!(svm.get_balance(&recipient), Some(64));
assert_eq!(svm.block_env().latest_blockhash, svm.latest_blockhash());
```

### Forking RPC State

`hpsvm` can read missing accounts through a configured account source. The `fork` feature provides an RPC-backed source with a bounded, TTL-aware local cache:

```rust
# #[cfg(feature = "fork")]
# fn main() {
use hpsvm::{HPSVM, fork::RpcForkSource};

let source = RpcForkSource::builder()
    .with_rpc_url("http://127.0.0.1:8899")
    .with_slot(1)
    .build();

let svm = HPSVM::builder().with_account_source(source).build().unwrap();
# }
# #[cfg(not(feature = "fork"))]
# fn main() {}
```

### Top-Level Instruction Inspection

Use `with_inspector` when you need lightweight transaction observation without reaching into the lower-level invocation callback APIs:

```rust
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

use hpsvm::{BaseSvmTypes, HPSVM, Inspector};
use solana_address::Address;

#[derive(Default)]
struct CountingInspector {
    seen: Arc<AtomicUsize>,
}

impl Inspector<BaseSvmTypes> for CountingInspector {
    fn as_any(&self) -> &dyn std::any::Any {
        self
    }

    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
        self
    }

    fn instruction_start(&mut self, _index: usize, _program_id: &Address) {
        self.seen.fetch_add(1, Ordering::SeqCst);
    }
}

let inspector = CountingInspector::default();
let observed = Arc::clone(&inspector.seen);
let _svm = HPSVM::new().with_inspector(inspector);

assert_eq!(observed.load(Ordering::SeqCst), 0);
```

## 🖥️ Command-line fixtures

The `hpsvm-cli` crate ships an `hpsvm` binary for recording, replaying, and
A/B-comparing transaction fixtures. A fixture bundles a transaction, the
accounts it needs, the programs it calls, and a recorded baseline snapshot, so
a program's behaviour can be frozen and checked in CI.

```sh
cargo install hpsvm-cli
```

### Record a fixture

`fixture record` executes a signed transaction against the pre-state you supply
and writes a fixture containing the observed baseline:

```sh
hpsvm fixture record \
  --transaction tx.bin \
  --accounts accounts.json \
  --output fixtures/transfer-64.json \
  --name transfer-64 \
  --tag transfers
```

`--transaction` holds a `wincode`-serialized `VersionedTransaction`. Use
`--transaction-encoding base64` or `--transaction-encoding hex` when the bytes
travel as text instead. `--accounts` is a JSON array of pre-execution accounts:

```json
[
  {
    "address": "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin",
    "lamports": 10000,
    "owner": "11111111111111111111111111111111",
    "executable": false,
    "rent_epoch": 0,
    "data": []
  }
]
```

Each address accepts either a base58 string or a 32-byte array, and the shape
matches `input.pre_accounts` inside an existing fixture — so pre-accounts can
be copied straight out of one. Programs the transaction calls are bound with
`--program <program-id>=<path-to-elf>`, which is repeatable. Further options
include `--loader`, `--slot`, `--sigverify`, `--log-bytes-limit`,
`--compute-unit-limit`, and `--ignore-compute-units` for fixtures that should
tolerate compute-unit drift. A `.json` output path writes the JSON codec; a
`.bin` path writes the binary codec.

### Replay and compare

```sh
hpsvm fixture run fixtures/transfer-64.json
hpsvm fixture run fixtures/
hpsvm fixture inspect fixtures/transfer-64.json
```

`run` replays a fixture — or every fixture in a directory, in sorted order —
and exits non-zero on the first failure. `inspect` prints the fixture as JSON.

`compare` replays the same fixture twice, once per program build, and diffs the
two execution snapshots. This is the A/B check for a program rewrite — both
mappings bind the fixture's program id, each to a different ELF:

```sh
hpsvm fixture compare fixtures/transfer-64.json \
  --baseline-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin=target/old.so \
  --candidate-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin=target/new.so
```

By default the comparison uses the checks stored in the fixture. Override them
with `--config`, whose JSON body is a `compares` list:

```json
{ "compares": ["Status", "Fee", "Accounts"] }
```

Pass `--ignore-compute-units` to drop the compute-unit check while keeping the
rest.

### Compute-unit reports

```sh
hpsvm cu report fixtures/transfer-64.json \
  --output-dir cu \
  --baseline-dir cu \
  --must-pass
```

This writes a Markdown summary (`cu-report.md`) and a JSON baseline sidecar
(`cu-report.baseline.json`) into `--output-dir`. `--baseline-dir` is
**read-only**: it supplies the previous sidecar to compute per-case
compute-unit deltas against, so pointing it at the same directory makes the
first run establish a baseline and later runs report drift:

```text
| Name       | Compute Units | Delta        | Pass |
| transfer-64 |          150 | +0 (+0.00%) | PASS |
```

Add `--must-pass` to fail the command as soon as a case's recorded expectations
no longer hold.

## 🛠️ Developing hpsvm

### Building Test Programs

The test suite uses Solana programs that need to be built first:

```bash
cd crates/hpsvm/test_programs
cargo build-sbf
```

### Running Tests

The test suite loads four SBF programs from
`crates/hpsvm/test_programs/target/deploy/`. They are gitignored build
artifacts, so build them once after checkout (CI does this automatically):

```bash
cd crates/hpsvm/test_programs && cargo build-sbf
```

`just test-all` checks for them first and reports exactly which artifact is
missing. If `cargo build-sbf` itself fails on macOS, see the note at the end of
this section.

Run the full test suite:

```bash
cargo test
```

### Running Benchmarks

```bash
cargo bench
```

Build the benchmark helper program and profile the public HPSVM surface:

```bash
HPSVM_HOTPATH=1 cargo bench -p hpsvm --features hotpath --bench core_interfaces
```

Profile the steady-state transaction execution hot path:

```bash
HPSVM_HOTPATH=1 cargo bench -p hpsvm --features hotpath --bench max_perf
```

Export a deeper trace summary for the steady-state SBF execution path:

```bash
just bench-hotpath-trace
```

`core_interfaces` covers the main public APIs: `new`, `set_account`, `get_account`,
`expire_blockhash`, `add_program`, `add_program_from_file`, `airdrop`,
`send_transaction`, `simulate_transaction`, `transact` + `commit`/`discard`,
`plan_transaction_batch`, and `send_transaction_batch`.

When `HPSVM_HOTPATH=1` is set, hotpath reports are written to `target/hotpath/*.json`.
Use `HPSVM_HOTPATH_LIMIT` to cap the number of reported functions, or `HOTPATH_OUTPUT_PATH`
to override the default report path.

When `HPSVM_TRACE_METRICS=1` is combined with the `register-tracing` feature, `just bench-hotpath-trace`
also writes `target/hotpath/max_perf.trace.json` and `target/hotpath/simple_bench.trace.json`.
These summaries report per-program invocation counts, CPI depth, register-frame counts, and
instruction-account counts so you can tell whether the `execute_instruction` hotspot is dominated
by nested CPIs or a single SBF program body in both the steady-state loop and the repeated
transaction-loop benchmark.

To compare default runtime benchmarks without hotpath overhead:

```bash
just bench-runtime
just bench-runtime-baseline-compare
```

This writes compact Criterion reports to `target/runtime-benchmarks/*.json` and a markdown summary
to `target/runtime-benchmarks/summary.md`. Use this runtime baseline before treating a hotpath-only
delta as a real VM slowdown.

To refresh the committed default runtime baselines:

```bash
just bench-runtime-baseline-refresh
```

To refresh the committed hotpath baselines:

```bash
just bench-baseline-refresh
```

To compare a fresh benchmark run with the committed baselines:

```bash
just bench-hotpath
just bench-baseline-compare
```

### Code Quality

Format code:

```bash
cargo fmt
```

Lint code:

```bash
cargo clippy
```

### Troubleshooting `cargo build-sbf`

On macOS the anza platform-tools `rustc` can emit proc-macro dylibs that `dyld`
rejects with `mis-aligned LINKEDIT string pool`, which `cargo` then reports as
`can't find crate for borsh_derive` or `can't find crate for bytemuck_derive`.
This is a toolchain bug and reproduces in a two-crate scratch project, so it is
not caused by this repository. Build the test programs on another host and copy
`crates/hpsvm/test_programs/target/deploy` back.

## 🙏 Acknowledgments

- Initially forked from [litesvm]https://github.com/LiteSVM/litesvm
- Built for the Solana ecosystem
- Inspired by the need for faster, more ergonomic testing tools
- Thanks to the Solana community for their contributions and feedback