Skip to content

Precompiles & Syscalls

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).

IDNameBaseVariableDescription
1storage_read200 (warm) / 600 (cold)+4/8 per 32B valueRead key-value from contract storage
2storage_write400+8 per 32B valueWrite key-value to contract storage
22storage_scan_range800+4 per 32B input, +8 per 32B outputRange scan over storage keys
IDNameBaseDescription
3access_list_add5Hint: add key to access list (does not warm)
4prefetch_key5Hint: prefetch a storage key
IDNameBaseVariableDescription
5call500+4 per 32B input, +4 per 32B returnCall another contract (state-changing)
6static_call400+4 per 32B input, +4 per 32B returnCall another contract (read-only)
21create2000+20 per 32B codeDeploy a new contract
IDNameBaseVariableDescription
7emit_log150+20 per topic, +4 per 32B dataEmit an event log
IDNameBaseDescription
13read_context20Read sender, value, block metadata
14random_beacon40Read protocol randomness beacon (block-scoped)
15gas_left5Query remaining gas budget
17gas_limit5Query initial gas limit for current frame
18balance_of50Read native balance for an address
19self_balance30Read native balance for current contract
20block_hash80Read historical block hash (256-block window)
25block_header100Read block hash + state_root (64 bytes, 256-block window)
IDNameBaseVariableDescription
8keccak256800+20 per 32B inputEthereum-compatible Keccak-256
9sha2_256800+20 per 32B inputSHA-256
10blake3400+10 per 32B inputBLAKE3 (fastest)
IDNameBaseVariableDescription
12verify_ed255196000+20 per 32B inputEd25519 EDDSA signature verification
16verify_secp256k16000+20 per 32B inputECDSA over secp256k1
IDNameBaseDescription
23verify_vrf_ed255198000ECVRF-EDWARDS25519-SHA512-TAI (RFC 9381)
24ecrecoverSecp256k1 public key recovery (Ethereum-style)

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_32b

BLAKE3 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.

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 signature
  • STATUS_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).

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.

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.

Precompiles are gated by feature flags, controllable at both compile time and runtime via the PrecompileRegistry:

FlagPrecompiles
precompile-hashingkeccak256, sha2_256, blake3
precompile-signaturesverify_ed25519, verify_secp256k1
precompile-vrfverify_vrf_ed25519
precompile-recoveryecrecover

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();

The VM enforces per-call, per-transaction, and per-block limits on precompile input data to prevent DoS:

LimitDefault
Per-call precompile input256 KB
Per-tx precompile input512 KB
Per-block precompile input8 MB

Other relevant limits:

LimitDefault
Max call depth64
Max code size1 MB
Stack per frame128 KB
Heap pages per frame640 (2.5 MB)
Storage writes per tx4,096
Log bytes per tx64 KB

Exceeding precompile input caps returns PRECOMPILE_CAP or PRECOMPILE_CAP_PER_BLOCK errors.

const sdk = @import("ashen-sdk");
const crypto = sdk.crypto;
// Hashing
const digest = crypto.keccak256(data);
const sha_digest = crypto.sha256(data);
const b3_digest = crypto.blake3(data);
// Signature verification
const valid = crypto.ed25519Verify(msg_hash, signature, pubkey);
// Key recovery
const recovered_key = crypto.ecrecover(msg_hash, signature, recovery_id);
use contract_sdk::Host;
// Hashing
let digest = Host::keccak256(data)?;
let sha_digest = Host::sha2_256(data)?;
let b3_digest = Host::blake3(data)?;
// Signature verification
let valid = Host::verify_ed25519(msg_hash, signature, pubkey)?;

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.