VM Execution Result#

Result Kinds#

Return

Represents successful execution of a sub-VM

VMError

Represents a VM produced error, such as non-zero exit code or exceeding resource limits.

It uses predefined string error codes.

Fatal VM Error

Every VM error is either fatal or not; both draw their code from the same set. A non-fatal error of a sub-VM is returned to its caller as Sub-VM Result Encoding. A fatal one is not catchable: the caller terminates with the same VM error, and propagation continues until the topmost VM boundary

Nested transport encodes a fatal VM error with result code 4. At the topmost publication boundary, the executor MUST downgrade it to an ordinary VMError with the same payload before producing the reported result. Result code 4 is therefore forbidden in a top-level reported result. Both its Execution Hash and its Sub-VM Result Hash use VMError as the result kind

VM Error Code Format#

vm-error-code := public-code [ " # " detail ]
  • public-code — a sequence of snake_case components separated by single spaces, drawn from the predefined codes. It never contains #.

  • detail — optional free-form UTF-8 diagnostic. When present, it is separated from the public code by # surrounded by a single space on each side.

The full string, detail included, is covered by the Execution Hash, so it must be reproducible octet for octet by every implementation running the same execution. A detail MUST therefore be composed only of:

  1. octets the execution itself produced or consumed — a guest-supplied string, a decoded field of the calldata or of a runner description;

  2. values fixed by the WASM module or by this specification — a function or memory index, a limit, a constant from Constants;

  3. literal text chosen by the code that raises the error.

Anything the host or the runtime environment supplies MUST NOT appear, even indirectly. In particular: filesystem paths, operating-system error strings or numbers, host addresses and pointer values, elapsed times, locale-dependent or platform-width-dependent formatting, thread or process identifiers, and implementation build or version strings.

Beyond that constraint the detail’s content carries no compatibility promise — see VM Error Code Compatibility.

Consumers MUST compare and match VM error codes only by the public code (the part before the first `` # ). A consumer matching a known code ``P MUST treat a code as matching iff its public code equals P or extends P with further space-separated components.

UserError

Represents a user-produced error in utf-8 format.

Effects of a Non-Returning Run

Only a Return carries effects. A topmost run that ends in UserError or VMError reports no storage_deltas and no emissions, whatever it wrote or emitted before failing, and its Execution Hash covers those empty fields.

InternalError#

Not a Result Kinds but the absence of one: the executor could not run the contract to a verdict at all, because of a Host communication failure or Module unavailability.

Internal errors are not visible by the contracts and are reported only to the Host, which will most likely vote timeout if it encounters one

Non-Deterministic Block Result Encoding#

These three are the only codes a leader-proposed non-deterministic block result may carry; validators treat every other byte as a malformed leader result. A fatal VM error computed by a leader’s non-deterministic child propagates to its caller and MUST NOT be included in LeaderPublicData

Leader Output Format#

LeaderPublicData uses Calldata Encoding with the shape {"nd_outs": bytes[]}. The array contains every non-deterministic block result in execution order; each element uses Sub-VM Result Encoding. Empty bytes and all other representations are malformed

Contract Result Encoding#

Return#

Arbitrary structure in Calldata Encoding

UserError and VMError#

The error value (UserError value or VMError error code string) is reported alongside two fingerprint fields, each Calldata Encoding encoded:

backtrace — the WASM call stack captured at the failure point:

[
  {
    "module_name": "<module_name>",
    "func": "<number: function_index>"
  }
]

wasm_store_hashes — per-module memory fingerprint:

{
  "<module_name>": {
    "memories": [
      "<bytes: 32_byte_blake3_hash>"
    ]
  }
}

For sake of preventing skipping execution for error results, validators are obligated to calculate the VM fingerprint on error.

Both fields are serialized using Calldata Encoding to be deterministic, and have the following structure:

  1. backtrace frames are ordered from most recent to oldest one (most likely, _start)

  2. Function index is an index of function in WASM module

  3. Memories are ordered by their index in WASM module

  4. Memories are hashed using BLAKE3 hash function, which is cryptographically secure and provides acceptable performance

Execution Hash#

Every run produces an execution hash: a SHA3-256 digest over a Calldata Encoding encoding of the consensus-visible result, as a map with the following keys (in this order):

  1. backtrace

  2. data — the contract result value

  3. data_fees_consumed

  4. data_fees_remaining — map from each fee-bucket name to its remaining U256 balance

  5. emissions — emitted messages and events, in emission order

  6. kind — the Result Kinds result code

  7. storage_deltas

  8. subvm_hashes — see Sub-VM Result Hash

  9. wasm_store_hashes

Two runs that agree on the deterministic result produce the same execution hash, so consensus can compare a single 32-byte value instead of the full result.

A fatal VM error is committed with VMError as kind. Fatality controls propagation and is not a distinct consensus-visible outcome

emissions covers the whole content of every emitted message and event, not just its metered cost: two emissions can carry different calldata or different event topics for the same fee, so a fee-only commitment would let nodes agree on the hash while committing divergent side effects

Sub-VM Result Hash#

A deterministic run accumulates the result of each deterministic sub-VM call into a rolling SHA3-256 accumulator. The finalized accumulator is the subvm_hashes field of the parent’s Execution Hash.

For each deterministic sub-VM, its small hash is folded into the accumulator. The small hash is a SHA3-256 digest over a Calldata Encoding encoded map:

  1. kind — the exact string "Return", "UserError" or "VMError". Fatality is not an outcome of its own, only a statement about who may catch it, so a fatal result hashes as "VMError"

  2. result — for Return/UserError the result value in Calldata Encoding; for VMError the error code string

  3. subvm_hashes — that sub-VM’s own finalized accumulator, making the hash recursive over the whole deterministic call tree

  4. wasm_store_hashes

Non-deterministic sub-calls do not contribute. A run with no deterministic sub-calls finalizes its accumulator to a fixed digest, so that error and edge results hash uniformly.

The small hash of a sub-VM the host delegated to another executor (see Contract Major Version) is computed the same way, from the values that executor reports. Where the callee ran is not an input: a call that a host routes to another executor MUST fold the same value it would have folded had the callee run in-process.

Post-Execution Result Validation#

A consumer in Sync Mode or Validator Mode consumes one leader-proposed result per non-deterministic block it runs. If the leader supplied more results than the run consumed, the surplus blocks were never reached: the run’s result is replaced by leader_fault nondet_output extra, whose parameter is the first 6 characters of the GVM32 (Base32) encoding of the sha3_256 digest of the complete [result_code][data] sub-VM result buffer the run would otherwise have returned — the whole wire buffer as emitted, not a decoded payload or alternate representation. No non-deterministic disagreement is caused by this.

This check also applies when startup fails before any non-deterministic block, including when initial fees cannot be paid. An existing fatal result is preserved.