Skip to content

Execution

Ashen executes smart contracts in a deterministic RISC-V virtual machine with tiered execution (interpreter, JIT, AOT) and metered gas accounting.

The execution layer provides:

  • Deterministic RISC-V VM - RV64IMC + Zba/Zbb, no floating-point
  • Tiered Execution - Interpreter → Baseline JIT → Optimizing JIT/AOT
  • Metered Gas - Per-instruction accounting with identical costs across tiers
  • Sandboxed Isolation - Memory safety, no shared state between contracts
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Validate │ → │ Gas Debit │ → │ Execute │ → │ Commit │
│ sig/nonce │ │ reserve │ │ RISC-V VM │ │ or revert │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
  1. Validation - Check ed25519 signature, nonce, gas limit, balance
  2. Gas Debit - Reserve gas_limit from payer account
  3. Execution - Run contract in RISC-V VM (tier selected automatically)
  4. Gas Refund - Return unused gas to payer
  5. Fee Distribution - Credit consumed gas to block proposer

Contracts target a deterministic RISC-V profile:

ExtensionStatusPurpose
RV64I✅ EnabledBase 64-bit integer instructions
M✅ EnabledMultiply/divide
C✅ EnabledCompressed instructions (smaller code)
Zba/Zbb✅ EnabledBit manipulation
F/D/Q❌ DisabledFloating-point (use fixed-point instead)
A❌ DisabledAtomics (no threads in contracts)
V❌ DisabledVector operations

Target triple: riscv64-unknown-elf with +m,+c,+zba,+zbb,-a,-f,-d

The VM uses tiered execution to balance startup latency with steady-state performance:

┌─────────────────┐
│ Interpreter │ ← All contracts start here
│ (reference) │
└────────┬────────┘
│ hot (>10M gas or >20 calls)
┌─────────────────┐
│ Baseline JIT │ ← Fast compile, 5x speedup
│ (Tier 1) │
└────────┬────────┘
│ hotter (>200M gas or >200 calls)
┌─────────────────┐
│ Optimizing JIT │ ← Slower compile, 10-20x speedup
│ (Tier 2) │
└─────────────────┘
│ failure/eviction → demote to interpreter

The interpreter is the canonical implementation for semantics and gas costs:

  • Always available - No compilation required
  • Exact gas accounting - Per-instruction metering
  • Fallback tier - Used when JIT fails or is unavailable

All other tiers must produce identical results and gas usage.

Fast compilation with modest speedup:

  • Compile time: ≤5ms for 10k instructions
  • Speedup: ~5x vs interpreter
  • Promotion: After 10M gas executed or 20 calls

The baseline JIT performs minimal optimization—primarily translating RISC-V to native code with bounds checks.

Slower compilation with higher speedup:

  • Compile time: ≤50ms for 10k instructions
  • Speedup: 10-20x vs interpreter
  • Promotion: After 200M gas executed or 200 calls

Performs optimizations like:

  • Register allocation
  • Dead code elimination
  • Basic block reordering
  • Constant folding

For frequently-used system contracts, AOT compilation provides:

  • Zero runtime compilation - Pre-compiled at deploy time
  • Maximum optimization - Full optimization passes
  • Verified equivalence - Differential tested against interpreter

All tiers must be bit-for-bit deterministic:

PropertyRequirement
Return valuesIdentical across tiers
State writesSame keys, same values
Event logsSame topics, same data
Gas consumedExact match to interpreter
  1. Differential fuzzing - Random programs tested across all tiers
  2. Replay tests - Recorded traces replayed in each tier
  3. Conformance corpus - CI requires 0 mismatches

If any tier produces a mismatch, it falls back to interpreter.

Gas is metered identically across all tiers:

  • Per-instruction gas deduction
  • Checks after every instruction
  • Basic block metering - Precompute gas per block
  • Block entry check - if gas_left < block_cost → trap
  • Loop back-edges - Gas check on every iteration
┌─────────────────────────────────────┐
│ Basic Block Entry │
│ ───────────────── │
│ if gas_left < 150: │
│ trap(OUT_OF_GAS) │
│ gas_left -= 150 │
│ │
│ ... execute instructions ... │
│ │
│ branch to next block │
└─────────────────────────────────────┘

Compiled code is cached to avoid recompilation:

(code_hash, gas_schedule_version, vm_config_hash, tier)
  • Policy: LRU by total code size
  • Hard cap: 512 MiB (configurable)
  • Per-contract cap: 64 MiB

Code changes (new code_hash) automatically invalidate cache entries.

Contracts run in isolated sandboxes:

  • Linear address space - Flat 64-bit virtual memory
  • Guard pages - Stack/heap overflow protection
  • W^X enforcement - Pages are writable OR executable, never both
  • ecall instruction - Only way to interact with host
  • Stable ABI - Arguments in a0-a5, syscall number in a7
  • No direct I/O - All external access through syscalls
  • Route through runtime dispatcher
  • Automatic tier selection for callee
  • Copy-on-write memory snapshots for rollback

Contracts compile to RISC-V ELF binaries:

LanguageToolchainGuide
Zigzig buildAshen SDK
Rustcargo + riscv64 targetExamples
C/C++clang/gcc cross-compilerAny RISC-V toolchain
  • Format: Static ELF64, no dynamic linking
  • Relocations: None (position-independent or fixed address)
  • Deterministic: Built with SOURCE_DATE_EPOCH, no build IDs

Each contract instance has isolated memory:

┌──────────────────────────────────┐ High addresses
│ Guard Page │
├──────────────────────────────────┤
│ Stack │ ↓ grows down
│ ↓ │
├──────────────────────────────────┤
│ (unmapped) │
├──────────────────────────────────┤
│ ↑ │
│ Heap │ ↑ grows up
├──────────────────────────────────┤
│ Data / BSS │
├──────────────────────────────────┤
│ Code (read-only) │
├──────────────────────────────────┤
│ Guard Page │
└──────────────────────────────────┘ Low addresses
  • Page size: 4 KiB
  • Max memory: Configurable per transaction
  • Cleared between txs: No information leaks

The VM integrates with on-chain state through:

  1. Storage syscalls - Read/write contract storage
  2. Dirty page tracking - Efficient delta computation
  3. Journaling - Ordered log for crash recovery
  4. Merkle proofs - State provable to light clients

See Storage for details.