Zig Guide
Overview
Section titled “Overview”Ashen contracts can be written in Zig and compiled to the Ashen VM’s RV64 freestanding target. This guide walks through the canonical contract layout, entrypoint patterns, and build flow using the Ashen Zig SDK.
Prerequisites
Section titled “Prerequisites”- Use the pinned toolchain. Build with the exact Zig toolchain provided by
devenv(or the Docker image built from it). Validators validate the ELF output from the canonical compilers; mismatched toolchains are rejected. - Ashen SDK: Contracts must use
contracts/ashen-sdk. Do not reimplement storage, dispatch, or syscall helpers. - IDL file: Every contract has a
*.idlfile that defines its interface.
Project Layout
Section titled “Project Layout”A typical Zig contract lives under contracts/<name>/:
contracts/my_contract/├── build.zig├── linker.ld├── my_contract.idl└── src/ ├── main.zig ├── abi.zig └── my_contract.manifest.v1.jsonbuild.zigtargets RV64 freestanding and wires inashen-sdk.linker.ldkeeps code/data at low addresses for the VM.abi.zig+*.manifest.v1.jsonare generated from IDL.
Contract Skeleton
Section titled “Contract Skeleton”The recommended pattern is to use the SDK dispatch helper with IDL-generated stubs:
const sdk = @import("ashen-sdk");const abi = @import("abi.zig");
const Counter = struct { pub fn get(self: *Counter, args: abi.GetArgs) abi.ContractResult(abi.GetResult) { _ = self; _ = args; const value: u128 = sdk.storage.readU128OrDefault("count", 0); return .{ .Ok = .{ .value = value } }; }
pub fn inc(self: *Counter, args: abi.IncArgs) abi.ContractResult(abi.IncResult) { _ = self; _ = args; const value: u128 = sdk.storage.readU128OrDefault("count", 0) + 1; _ = sdk.storage.writeU128("count", value); return .{ .Ok = .{ .value = value } }; }};
export fn _start(calldata_ptr: [*]const u8, calldata_len: usize) sdk.ByteSlice { return sdk.dispatch.run(Counter, abi.dispatch, calldata_ptr, calldata_len);}
pub const panic = sdk.panic;Notes:
sdk.dispatch.runhandles heap reset, calldata slicing, and error encoding.- The entrypoint must be named
_start. - Always export
pub const panic = sdk.panic;.
Concrete Walkthrough: Counter Contract
Section titled “Concrete Walkthrough: Counter Contract”This is a minimal end-to-end example you can copy into a new contract.
1) Create the contract folder + IDL
Section titled “1) Create the contract folder + IDL”mkdir -p contracts/counter/srccontracts/counter/counter.idl:
namespace counter;
interface Counter { fn get() -> u128; fn inc() -> u128;}2) Generate Zig stubs from IDL
Section titled “2) Generate Zig stubs from IDL”cargo run -p idl-abi-gen -- \ --idl contracts/counter/counter.idl \ --out-dir contracts/counter/src \ --zig-stubs3) Implement the contract
Section titled “3) Implement the contract”contracts/counter/src/main.zig:
const sdk = @import("ashen-sdk");const abi = @import("abi.zig");
const Counter = struct { pub fn get(self: *Counter, args: abi.GetArgs) abi.ContractResult(abi.GetResult) { _ = self; _ = args; const value: u128 = sdk.storage.readU128OrDefault("count", 0); return .{ .Ok = .{ .value = value } }; }
pub fn inc(self: *Counter, args: abi.IncArgs) abi.ContractResult(abi.IncResult) { _ = self; _ = args; const value: u128 = sdk.storage.readU128OrDefault("count", 0) + 1; _ = sdk.storage.writeU128("count", value); return .{ .Ok = .{ .value = value } }; }};
export fn _start(calldata_ptr: [*]const u8, calldata_len: usize) sdk.ByteSlice { return sdk.dispatch.run(Counter, abi.dispatch, calldata_ptr, calldata_len);}
pub const panic = sdk.panic;4) Build
Section titled “4) Build”cd contracts/counterzig build -Doptimize=ReleaseSmallOutput: zig-out/bin/counter
5) Bundle + deploy
Section titled “5) Bundle + deploy”node contract bundle \ --elf contracts/counter/zig-out/bin/counter \ --idl contracts/counter/counter.idl \ --out ./counter.bundle
node contract deploy --bundle ./counter.bundle --key $ASHEN_PRIVATE_KEY --wait6) Call it
Section titled “6) Call it”ashen call 0xCONTRACT get '[]' --key $ASHEN_PRIVATE_KEY --waitashen call 0xCONTRACT inc '[]' --key $ASHEN_PRIVATE_KEY --waitIDL + Zig Stubs
Section titled “IDL + Zig Stubs”Generate Zig ABI stubs from the contract IDL:
cargo run -p idl-abi-gen -- \ --idl contracts/my_contract/my_contract.idl \ --out-dir contracts/my_contract/src \ --zig-stubsThis produces:
src/abi.zig(selectors, args/results, dispatch)src/my_contract.manifest.v1.json(deploy manifest metadata)
Regenerate these files whenever you change the IDL.
cd contracts/my_contractzig build -Doptimize=ReleaseSmallOutput: zig-out/bin/my_contract
Bundle + Deploy
Section titled “Bundle + Deploy”Use the deployment workflow in the CLI guide:
- Build the Zig ELF (
zig build -Doptimize=ReleaseSmall) - Bundle with IDL (
node contract bundle) - Deploy (
node contract deploy)
See: /guides/deploying-contracts/
Common Patterns
Section titled “Common Patterns”Storage helpers
Section titled “Storage helpers”const balance = sdk.storage.readU128("balance") orelse 0;_ = sdk.storage.writeU128("balance", balance + 1);Events
Section titled “Events”const events = sdk.events;const Transfer = events.define("Transfer(address,address,uint256)");Transfer.emit2(from, to, events.toTopicU128(amount), &[_]u8{});Reentrancy guard
Section titled “Reentrancy guard”try sdk.guards.enterNonReentrant();defer sdk.guards.exitNonReentrant();Next Steps
Section titled “Next Steps”/contracts/ashen-sdk/for the SDK API surface/contracts/idl-and-abi/for interface definitions/contracts/examples/for production-grade Zig contracts