Precompiles & Syscalls
Overview
Section titled “Overview”Ashen uses a RISC-V syscall model rather than EVM-style address-based
precompiles. Each operation is identified by a numeric syscall ID invoked via
the RISC-V ecall instruction. Contracts access syscalls through the Ashen SDK;
the IDs themselves are part of the stable ABI defined in vm-spec.
All syscall costs include a dispatch base of 30 cycles. Variable-cost
syscalls charge additional cycles based on input/output size using 32-byte word
rounding: words32 = ceil(bytes / 32).
Syscall Table
Section titled “Syscall Table”Storage
Section titled “Storage”| ID | Name | Base | Variable | Description |
|---|---|---|---|---|
| 1 | storage_read | 200 (warm) / 600 (cold) | +4/8 per 32B value | Read key-value from contract storage |
| 2 | storage_write | 400 | +8 per 32B value | Write key-value to contract storage |
| 22 | storage_scan_range | 800 | +4 per 32B input, +8 per 32B output | Range scan over storage keys |
| ID | Name | Base | Description |
|---|---|---|---|
| 3 | access_list_add | 5 | Hint: add key to access list (does not warm) |
| 4 | prefetch_key | 5 | Hint: prefetch a storage key |
Calls & Deployment
Section titled “Calls & Deployment”| ID | Name | Base | Variable | Description |
|---|---|---|---|---|
| 5 | call | 500 | +4 per 32B input, +4 per 32B return | Call another contract (state-changing) |
| 6 | static_call | 400 | +4 per 32B input, +4 per 32B return | Call another contract (read-only) |
| 21 | create | 2000 | +20 per 32B code | Deploy a new contract |
Events
Section titled “Events”| ID | Name | Base | Variable | Description |
|---|---|---|---|---|
| 7 | emit_log | 150 | +20 per topic, +4 per 32B data | Emit an event log |
Context & Queries
Section titled “Context & Queries”| ID | Name | Base | Description |
|---|---|---|---|
| 13 | read_context | 20 | Read sender, value, block metadata |
| 14 | random_beacon | 40 | Read protocol randomness beacon (block-scoped) |
| 15 | gas_left | 5 | Query remaining gas budget |
| 17 | gas_limit | 5 | Query initial gas limit for current frame |
| 18 | balance_of | 50 | Read native balance for an address |
| 19 | self_balance | 30 | Read native balance for current contract |
| 20 | block_hash | 80 | Read historical block hash (256-block window) |
| 25 | block_header | 100 | Read block hash + state_root (64 bytes, 256-block window) |
Precompiles: Hashing
Section titled “Precompiles: Hashing”| ID | Name | Base | Variable | Description |
|---|---|---|---|---|
| 8 | keccak256 | 800 | +20 per 32B input | Ethereum-compatible Keccak-256 |
| 9 | sha2_256 | 800 | +20 per 32B input | SHA-256 |
| 10 | blake3 | 400 | +10 per 32B input | BLAKE3 (fastest) |
Precompiles: Signatures
Section titled “Precompiles: Signatures”| ID | Name | Base | Variable | Description |
|---|---|---|---|---|
| 12 | verify_ed25519 | 6000 | +20 per 32B input | Ed25519 EDDSA signature verification |
| 16 | verify_secp256k1 | 6000 | +20 per 32B input | ECDSA over secp256k1 |
Precompiles: VRF & Recovery
Section titled “Precompiles: VRF & Recovery”| ID | Name | Base | Description |
|---|---|---|---|
| 23 | verify_vrf_ed25519 | 8000 | ECVRF-EDWARDS25519-SHA512-TAI (RFC 9381) |
| 24 | ecrecover | — | Secp256k1 public key recovery (Ethereum-style) |
Precompile Details
Section titled “Precompile Details”Hash Functions
Section titled “Hash Functions”All hash precompiles accept variable-length input and return a fixed 32-byte digest. Gas scales linearly with input size.
total_gas = dispatch_base (30) + base + ceil(input_bytes / 32) * per_32bBLAKE3 is the cheapest hash at half the base cost and per-word rate of Keccak/SHA-256. Prefer it unless cross-chain compatibility requires a specific algorithm.
Signature Verification
Section titled “Signature Verification”verify_ed25519 and verify_secp256k1 share the same gas schedule
(verify_sig). Both accept a message hash, signature, and public key.
Return values:
STATUS_OK(0): valid signatureSTATUS_VERIFY_FAIL(16): invalid signature, encoding, or key format
Ed25519 inputs: message_hash (32B), signature (64B), public_key (32B).
Secp256k1 inputs: message_hash (32B), signature (64B compact r||s), public_key (33-65B compressed or uncompressed).
VRF Verification
Section titled “VRF Verification”verify_vrf_ed25519 verifies ECVRF proofs per RFC 9381. Fixed cost (no
per-byte scaling).
Inputs: alpha (variable-length message), public_key (32B), proof (80B: gamma
- c + s).
Output: 32-byte VRF output written to caller-provided buffer on success.
ecrecover
Section titled “ecrecover”Secp256k1 public key recovery, Ethereum-compatible.
Inputs: message_hash (32B), signature (64B r||s), recovery_id (0 or 1).
Output: uncompressed public key (64B, x||y) on success.
Feature Flags
Section titled “Feature Flags”Precompiles are gated by feature flags, controllable at both compile time and
runtime via the PrecompileRegistry:
| Flag | Precompiles |
|---|---|
precompile-hashing | keccak256, sha2_256, blake3 |
precompile-signatures | verify_ed25519, verify_secp256k1 |
precompile-vrf | verify_vrf_ed25519 |
precompile-recovery | ecrecover |
All flags are enabled by default. Node operators can selectively disable
precompiles via RegistryBuilder:
let registry = RegistryBuilder::new() .with_feature("precompile-hashing") .with_feature("precompile-signatures") // VRF and recovery disabled .build();Resource Limits
Section titled “Resource Limits”The VM enforces per-call, per-transaction, and per-block limits on precompile input data to prevent DoS:
| Limit | Default |
|---|---|
| Per-call precompile input | 256 KB |
| Per-tx precompile input | 512 KB |
| Per-block precompile input | 8 MB |
Other relevant limits:
| Limit | Default |
|---|---|
| Max call depth | 64 |
| Max code size | 1 MB |
| Stack per frame | 128 KB |
| Heap pages per frame | 640 (2.5 MB) |
| Storage writes per tx | 4,096 |
| Log bytes per tx | 64 KB |
Exceeding precompile input caps returns PRECOMPILE_CAP or
PRECOMPILE_CAP_PER_BLOCK errors.
SDK Usage
Section titled “SDK Usage”const sdk = @import("ashen-sdk");const crypto = sdk.crypto;
// Hashingconst digest = crypto.keccak256(data);const sha_digest = crypto.sha256(data);const b3_digest = crypto.blake3(data);
// Signature verificationconst valid = crypto.ed25519Verify(msg_hash, signature, pubkey);
// Key recoveryconst recovered_key = crypto.ecrecover(msg_hash, signature, recovery_id);use contract_sdk::Host;
// Hashinglet digest = Host::keccak256(data)?;let sha_digest = Host::sha2_256(data)?;let b3_digest = Host::blake3(data)?;
// Signature verificationlet valid = Host::verify_ed25519(msg_hash, signature, pubkey)?;Canonical Source
Section titled “Canonical Source”The gas schedule is locked in docs/gas-schedules/gas-v1.json. Syscall IDs are
defined in crates/vm-spec/src/lib.rs and are part of the stable ABI — changing
an ID is consensus-breaking.
Related
Section titled “Related”- Gas Schedule — full opcode and memory cost tables
- Gas Throughput — block gas budgets and throughput
- Feature Flags — runtime feature gating
- Ashen SDK — Zig SDK reference
- SDK Reference — multi-language SDK overview