Skip to content

Storage & Events

KV storage model

Each contract has an isolated key-value namespace. Keys are strings; values are opaque byte vectors (list<u8>).

Host functions

Function Purpose
kv-get(key) Read a value. Returns None if the key does not exist.
kv-set(key, value) Write a value. Traps during queries.
kv-delete(key) Delete a key. Traps during queries.
kv-has(key) Check existence without reading the value.

Namespace isolation

Contracts cannot read or write another contract's storage. Each contract's keys are scoped to its own address. Cross-contract data access must go through query-contract or dispatch.

Transactional overlay semantics

During execution, all reads and writes go through a KvOverlay:

  • Reads check the overlay first, then fall through to committed chain state.
  • Writes accumulate in the overlay.
  • On success, the overlay is committed atomically.
  • On failure (trap, out-of-fuel, error return), the overlay is discarded entirely.

For nested sub-messages, an OverlayStack extends this model. Each sub-message pushes a new frame. Reads search top-to-bottom through all frames before falling through to committed state. On sub-message success, the frame merges into its parent; on failure, it is discarded.

Storage limits

Per-contract storage is capped at 10 MiB (max_contract_storage_bytes in vm.toml).

Events

Emitting events

host::emit_event("transfer", &serde_json::to_vec(&transfer_data).unwrap());

emit-event takes a kind string (type tag) and arbitrary data bytes. Events are included in the transaction receipt and are queryable by block height.

Event fees

Each byte of event data costs event_byte_price fuel units (default: 5). Keep event payloads small.

Discovering events

Events appear in transaction receipts, which can be retrieved via:

  • GET /v1/tx/{hash} -- receipt for a specific transaction
  • GET /v1/block/{height}/txs -- all transaction receipts in a block

Filter by event kind on the client side.