Rust Guide
Overview
Section titled “Overview”Ashen supports Rust contracts for developers who want tight control, no_std
performance, and the contract-sdk storage/ABI helpers. This guide shows the
canonical layout, entrypoint wiring, and build flow.
Prerequisites
Section titled “Prerequisites”- Pinned toolchain: Use the
devenvtoolchain (or its Docker image). The network validates ELF output from the canonical compilers. - RISC-V target:
riscv64imac-unknown-none-elf - No std: Contracts are
no_std+alloc.
Project Layout
Section titled “Project Layout”A typical Rust contract looks like contracts/sft_v1:
contracts/my_contract/├── Cargo.toml├── my_contract.idl└── src/ ├── main.rs ├── lib.rs └── abi.rssrc/main.rsdefines the entrypoint.src/lib.rscontains the contract logic and ABI implementation.src/abi.rsis generated from the IDL (do not hand-edit).
IDL + Codegen
Section titled “IDL + Codegen”Generate Rust ABI bindings from the IDL:
cargo run -p idl-abi-gen -- \ --idl contracts/my_contract/my_contract.idl \ --out-dir contracts/my_contract/src \ --rust-contractThis creates src/abi.rs with:
- selector constants
- request/response structs
- a
dispatchfunction - a trait you implement for your contract
Minimal Counter Contract
Section titled “Minimal Counter Contract”IDL (contracts/counter/counter.idl):
namespace counter;
interface Counter { fn get() -> u128; fn inc() -> u128;}Cargo.toml (minimal deps):
[dependencies]borsh = { version = "1.5.5", default-features = false, features = ["derive"] }contract-rt = { path = "../../crates/contract-rt" }contract-sdk = { path = "../../crates/contract-sdk" }lib.rs (logic + ABI implementation):
#![cfg_attr(target_arch = "riscv64", no_std)]
extern crate alloc;
mod abi;
use contract_sdk::{define_storage, ContractErrorV1, Item};
define_storage! { namespace: "counter", pub struct Storage { pub value: Item<u128>, }}
pub struct Contract { storage: Storage,}
impl Contract { pub fn new() -> Result<Self, ContractErrorV1> { Ok(Self { storage: Storage::new()?, }) }}
impl abi::Counter for Contract { fn get(&mut self) -> Result<abi::U128Result, ContractErrorV1> { let value = self.storage.value.get()?.unwrap_or(0); Ok(abi::U128Result { value }) }
fn inc(&mut self) -> Result<abi::U128Result, ContractErrorV1> { let value = self.storage.value.get()?.unwrap_or(0) + 1; self.storage.value.set(&value)?; Ok(abi::U128Result { value }) }}main.rs (entrypoint):
#![cfg_attr(target_arch = "riscv64", no_std)]#![cfg_attr(target_arch = "riscv64", no_main)]
// Replace `counter` with your library crate name.contract_rt::entrypoint_v1!(counter::Contract, counter::abi::dispatch);node contract build --manifest-path contracts/counter/Cargo.tomlOutput: target/riscv64imac-unknown-none-elf/release/counter
Bundle + Deploy
Section titled “Bundle + Deploy”node contract bundle \ --elf target/riscv64imac-unknown-none-elf/release/counter \ --idl contracts/counter/counter.idl \ --out ./counter.bundle
node contract deploy --bundle ./counter.bundle --key $ASHEN_PRIVATE_KEY --waitStorage Collections
Section titled “Storage Collections”The Rust SDK provides typed storage helpers in contract_sdk::storage:
| Type | Use | Notes |
|---|---|---|
Item<T> | Single value | get/set/exists/clear |
Map<K, V> | Key-value map | get/set/remove/contains_key |
Set<T> | Membership set | Backed by Map<T, u8> |
StorageVec<T> | Append-only vector | Tracks length + element keys |
CountedMap<K, V> | Map + O(1) size | Maintains entry count |
IndexedList<T> | List + index map | Swap-remove deletions |
LazyItem/Map/Set | Cached wrappers | Avoid redundant reads |
Example:
use contract_sdk::{define_storage, Item, Map, Set, StorageVec};
define_storage! { namespace: "example", pub struct Storage { pub owner: Item<[u8; 32]>, pub balances: Map<[u8; 32], u128>, pub allowlist: Set<[u8; 32]>, pub history: StorageVec<[u8; 32]>, }}Access Lists and Prefetch
Section titled “Access Lists and Prefetch”Access lists are hints that help the VM avoid cold storage penalties. The SDK
exposes declare_access and prefetch on storage types:
// Map access hint and prefetchstorage.balances.declare_access(&caller)?;storage.balances.prefetch(&caller)?;
// StorageVec access hintstorage.history.declare_len_access()?;storage.history.prefetch_element(0)?;Use these before hot loops or when you know the keys you will touch.
Error Patterns
Section titled “Error Patterns”Use typed error codes for deterministic failures:
use contract_sdk::{ContractErrorV1, require};
const ERR_UNAUTHORIZED: u32 = 1;const ERR_ZERO_AMOUNT: u32 = 2;
require!(amount > 0, ERR_ZERO_AMOUNT);require!(caller == owner, ERR_UNAUTHORIZED);
return Err(ContractErrorV1::code(ERR_UNAUTHORIZED));For manual ABI paths, you can return raw error payloads:
use contract_sdk::{revert_code, revert_other};
let bytes = revert_code(ERR_UNAUTHORIZED);let bytes = revert_other("bad input".to_string());Keystore Usage
Section titled “Keystore Usage”For deploys/calls, you can point ASHEN_PRIVATE_KEY at:
- a key file:
export ASHEN_PRIVATE_KEY=@./dev.key.json - a keystore handle:
export ASHEN_PRIVATE_KEY=keystore:my-key
Create a keystore key:
ashen keystore initashen keystore add --label my-keyRicher Example: Owner-Gated Faucet
Section titled “Richer Example: Owner-Gated Faucet”This shows a small contract with:
- owner-only mint
- balances map
- event emission
- access list hints
use contract_sdk::{define_storage, ContractErrorV1, Host, Item, Map, Emittable, require};use borsh::{BorshDeserialize, BorshSerialize};
const ERR_UNAUTHORIZED: u32 = 1;
#[derive(BorshSerialize, BorshDeserialize)]pub struct MintEvent { pub to: [u8; 32], pub amount: u128,}
define_storage! { namespace: "faucet", pub struct Storage { pub owner: Item<[u8; 32]>, pub balances: Map<[u8; 32], u128>, }}
pub struct Contract { storage: Storage }
impl Contract { pub fn new() -> Result<Self, ContractErrorV1> { Ok(Self { storage: Storage::new()? }) }
fn require_owner(&self, caller: [u8; 32]) -> Result<(), ContractErrorV1> { let owner = self.storage.owner.get()?.unwrap_or([0u8; 32]); require!(caller == owner, ERR_UNAUTHORIZED); Ok(()) }}
// ABI methods would live here (generated in abi.rs)impl Contract { pub fn mint(&mut self, to: [u8; 32], amount: u128) -> Result<(), ContractErrorV1> { let caller = Host::caller_key()?; self.require_owner(caller)?;
self.storage.balances.declare_access(&to)?; self.storage.balances.prefetch(&to)?;
let balance = self.storage.balances.get(&to)?.unwrap_or(0) + amount; self.storage.balances.set(&to, &balance)?;
MintEvent { to, amount }.emit(b"Mint")?; Ok(()) }}Cross-Contract Call + Multi-Topic Event
Section titled “Cross-Contract Call + Multi-Topic Event”This example shows:
- building calldata for another contract
Host::callwith typed decode- emitting a log with two indexed topics
extern crate alloc;
use alloc::vec::Vec;use contract_sdk::{Address, ContractErrorV1, Host, HostError};use borsh::BorshSerialize;
// Simple event payload#[derive(BorshSerialize)]pub struct SwapEvent { pub sender: [u8; 32], pub amount_in: u128, pub amount_out: u128,}
pub fn swap_through_router( router: Address, sender: [u8; 32], amount_in: u128,) -> Result<u128, ContractErrorV1> { // ABI selector + args (example; use IDL-generated helpers when available) let mut calldata = Vec::new(); calldata.extend_from_slice(&0x1234_5678u32.to_be_bytes()); // selector calldata.extend_from_slice(&borsh::to_vec(&amount_in).unwrap());
// Call router contract let amount_out: u128 = Host::call(&router, 0, 500_000, &calldata) .map_err(|e| e.into_contract_error())?;
// Emit event with two topics: event sig + sender let mut topics = [[0u8; 32]; 2]; topics[0] = Host::blake3(b"Swap(address,uint128,uint128)")?; topics[1] = sender;
let event = SwapEvent { sender, amount_in, amount_out, }; let data = borsh::to_vec(&event).map_err(|_| HostError::BorshEncode)?; Host::emit_log(&topics, &data)?;
Ok(amount_out)}Notes:
- Prefer IDL-generated helpers instead of manual selector encoding when possible.
Host::calldecodesResult<T, ContractErrorV1>and surfaces reverts asCallError.
Next Steps
Section titled “Next Steps”/contracts/idl-and-abi/for interface definitions/contracts/examples/for production Rust + Zig contracts/reference/sdk/for SDK API details