Resource Limiting#

Deterministic Mode and Non-Deterministic Mode have separate RAM budgets: what one consumes is never charged to the other. The deterministic budget starts at 4294967295 octets (4 GiB). Every RunNondet Message gets its own non-deterministic budget, starting at what its caller had remaining at the moment of the call, so a nondet block never gets more RAM than its caller had left. A sub-VM does not share its caller’s budget. It starts from a copy of what the caller had remaining and spends from that copy, so what a sub-VM spends never reduces what its caller may still spend, and a caller that spawns many children in sequence is not drained by them. Only the charges for data the caller keeps after a child returns move to the caller; see RAM Release.

RAM Consumption#

Every resource allocation subtracts from the RAM budget of the current VM Execution Modes. When an allocation would cause the remaining budget to become negative, the sub-VM exits with VMError carrying the out_of memory message, or one of its out_of memory wasm_memory / out_of memory wasm_table variants for the corresponding WASM allocations. memory.grow and table.grow are the exception: at runtime they are recoverable rather than fatal (see the two bullets below).

The following operations consume RAM:

  • WASM memory growth: each page (65536 octets) costs its size in bytes. A runtime memory.grow that would exceed the budget is not fatal: following the WASM specification it leaves memory unchanged and evaluates to \(-1\), so the guest can react. Only the memory’s initial, instantiation-time reservation being unmet makes the sub-VM exit with out_of memory wasm_memory.

  • WASM table growth: each table entry costs table_entry octets. As with memory, a runtime table.grow beyond the budget evaluates to \(-1\); only the instantiation-time reservation being unmet exits with out_of memory wasm_table.

  • File mapping: file_mapping octets base cost plus the length of the filename in bytes

  • File descriptor allocation: fd_allocation octets per descriptor

  • Runner loading: the first load of a runner in a sub-VM consumes its load charge, including ZIP metadata. A runner already in that sub-VM’s loaded set costs nothing, and the charge is released when the sub-VM finishes, like any other charge. Loading covers spawning the entry-point runner, Depends/With actions, the MapFile and RegisterRunner gl_calls, and receiving a custom-runner grant at sub-VM creation (see Runners and Custom-Runner Grants). A sub-VM holds at most max_runners runners: a load past that fails the same way an exhausted budget does, and charges nothing

  • Storage writes: writing to a 32-octet aligned region of a Storage Slot costs new_storage_page octets the first time that region is written. Regions the sub-VM inherited already written from its caller, and repeated writes to a region, cost nothing

  • Emissions: each emitted message or event costs execution_emission_base_size octets, plus the encoded length of each payload it retains — calldata, code, allocation subtree, topics and event data — and message_fee_rotation_element_size octets per retained message-fee rotation. Calldata is charged by its encoded length, so positional and keyword arguments carry no charge of their own

  • Nondeterministic outputs: each output costs nondet_output_base_size octets plus its encoded length on every role

  • Sub-VM creation: each new sub-VM costs vm_spawn_cost octets, plus storage_page_inherited octets for every 32-octet region already written in the storage it inherits. Both are charged to the new sub-VM at creation (see Startup) and released when it finishes

The runner load cost (runner_load_cost) is a fixed per-load overhead

Nondeterministic Output Caps#

Before entering a non-deterministic sub-VM, the caller must have enough RAM to retain the canonical memory-limit and non-deterministic-output fee-limit results and return either as a file descriptor. It also checks whether both results can be charged against the non-deterministic-output fee; when either cannot, the sub-VM is not entered

An output that cannot fit its non-deterministic-output fee charge is first replaced with a out_of receipt nondet_output result. If that result, or an output within the fee limit, cannot fit its RAM charge, it is replaced with a out_of memory result. The leader publishes the replacement and an honest replay charges the same encoded result

A validator rejects a leader proposal that differs from its post-cap result as a fatal leader_fault nondet_output malformed result without entering the validator sub-VM

RAM Release#

File content memory is released when the corresponding file descriptor is closed via fd_close. When a sub-VM finishes execution, its copy of the budget is discarded, so every charge it still held is released at once. This applies to runner charges as well: memory consumed by loading or registering a runner is released when the registering sub-VM finishes, like any other charge.

Charges for retained storage, emissions, and nondeterministic outputs are permanent. When a Sandbox Message child returns and its caller takes over the child’s retained data, those charges are transferred to the caller rather than released.

Other Limits#

In addition to the RAM budget, the following hard limits apply:

Exceeding any of these limits causes the sub-VM to exit with VMError.