Skip to content

VM Tooling & Test Harness

Ashen ships two developer-friendly tools for smart contract work:

  • vm-tooling: a CLI for ELF validation, disassembly, traces, gas profiling, and code cache introspection.
  • vm-test-harness: a Rust crate that runs Zig/Rust contracts in-memory without a full node.

This guide covers both, with practical examples.

Run from the repo root:

Terminal window
cargo run -p vm-tooling -- --help
CommandPurpose
deploy-manifestEmit a v1 deploy manifest
elf-validateValidate a contract ELF against the VM rules
disasmDisassemble a contract ELF
traceExecute and trace a contract call
gasPrint the active gas schedule
cache-statsShow code cache stats and promotion thresholds
predecodePredecode ELF for JIT/AOT tiers
gas-profileProfile per-basic-block gas usage
gas-budget-checkCheck gas profiles against .gas-budgets.toml
corpus-freezeGenerate a conformance corpus
corpus-runExecute a corpus against a tier
Terminal window
cargo run -p vm-tooling -- elf-validate --elf ./contracts/my_token/zig-out/bin/my_token
Terminal window
cargo run -p vm-tooling -- disasm --file ./contracts/my_token/zig-out/bin/my_token
Terminal window
cargo run -p vm-tooling -- trace \
--file ./contracts/my_token/zig-out/bin/my_token \
--calldata-hex 0x50494e47 \
--gas 1000000 \
--out ./trace.json

--calldata-hex expects ABI v1 bytes: selector || borsh(args).

Terminal window
cargo run -p vm-tooling -- gas-profile \
--file ./contracts/my_token/zig-out/bin/my_token \
--trace-out ./gas-trace.json
Terminal window
cargo run -p vm-tooling -- gas-budget-check --config ./.gas-budgets.toml
Terminal window
cargo run -p vm-tooling -- corpus-freeze --out ./corpus --cases 100
cargo run -p vm-tooling -- corpus-run --dir ./corpus --tier interpreter

Valid tiers: interpreter, jit, aot (and native when the cranelift-native feature is enabled).

vm-test-harness is a Rust crate for running contracts in-memory with a deterministic host. It is ideal for unit tests and fast iteration.

Key components:

  • TestHost: in-memory storage, logs, balances, and block context
  • ContractHarness: loads and executes contract ELFs
  • Fixtures & assertions: storage helpers, event checks, snapshots
use vm_test_harness::{ContractHarness, TestHost};
use vm_test_harness::{assert_call_ok, build_calldata_no_args};
let mut host = TestHost::new();
let harness = ContractHarness::from_zig_artifact("contracts/my_token/zig-out/bin/my_token")
.expect("artifact exists")
.expect("load contract");
let calldata = build_calldata_no_args(*b"PING");
let result = harness.call(&mut host, b"origin", b"my_token", &calldata, 1_000_000);
assert_call_ok(&result, "PING should succeed");
  • Use ContractHarness::from_zig_artifact_or_skip in CI to skip gracefully when the ELF is missing.
  • Use StorageFixture helpers to seed storage and verify state changes.
  • Use TestHost::snapshot() / restore_snapshot() for complex flows.

For a more complete walkthrough, see /guides/contract-testing/.

  • /reference/rpc-api/ for node-side RPC call types
  • /guides/contract-testing/ for harness patterns and fixtures