Overview

Every transaction runs under a resource budget: a set of per-dimension caps on how much work and memory it may use. A transaction aborts the instant any one dimension is exceeded. The budget exists so a hostile or buggy caller cannot drive unbounded recursion, allocation, or computation on a node processing the transaction.

Because every node that processes a transaction must reach the same accept/abort decision, every limit is a fixed count, identical on every target. No limit is derived from the amount of memory, the pointer width, or the wall-clock speed of a particular machine — a machine-dependent limit would let two nodes disagree on the same transaction.

The metered dimensions

A budget has nine dimensions. Each has a single canonical name — the same name is used everywhere a limit is written (a transaction limits(...) clause, a packet governance update, a signed envelope, or the SDK).

Name Counts Kind
fuel_ops weighted operations (the length-weighted fuel cost model) consumed total
eval_calls evaluator node evaluations consumed total
constructed_elems evaluator-constructed runtime values consumed total
call_depth deepest lambda-call nesting reached structural peak
value_height deepest value nesting constructed structural peak
elem_count most elements in one container structural peak
string_bytes longest string/blob byte length structural peak
packet_elems runtime elements in one packet structural peak
render_bytes bytes produced by one value render (_str, error text, …) structural peak

A dimension that is not set anywhere inherits the global default. The set of names is closed: any other name is rejected wherever a limit is written.

How a limit is composed

The effective cap for a dimension is the tightest value produced by four layers. Each layer is owned by a different party:

Layer Who sets it Direction
Global default / ceiling the protocol fixed; every other layer resolves within it
Packet governance the packet author (a governance update) raise or tighten the default, clamped to the hard bound
Source clause the contract author (a limits(...) clause) tighten only
Signed envelope the submitter (a per-call __limits) tighten only

Governance is the only layer that may raise a dimension above the default; it composes by replacement (the governance value wins in either direction) and is then clamped to the dimension’s hard bound. The two untrusted inner layers — the source clause and the signed envelope — are tighten-only: they compose by minimum and can never loosen a dimension above the value the outer layers already fixed. Adding an inner layer can only ever lower the effective cap.

Equivalently, for each dimension:

effective = min( clause, envelope, min( governance-over-default, hard_bound ) )

where an unset layer contributes nothing (it defers to the others).

What the budget counts

The budget is metered over the whole transaction, not just the code you wrote. When a transaction is invoked, the meters are armed before the signed envelope is decoded and unpacked, and the consumed tally runs continuously from that point through the transaction body. So the work the system does to decode and validate the envelope is charged against the same budget as the body.

This is deliberate. The clause and signed-envelope layers tighten the limit that applies once the body runs, but they do not reset the tally — the envelope-processing cost is already on the meter when the body’s tighter limit takes effect. A declared limit therefore has to accommodate the whole transaction, envelope processing included. That is a small, fixed per-transaction overhead, so in practice it only matters for a budget set deliberately close to the transaction’s true cost.

Hard bounds

Five dimensions carry a hard bound — a format cap that no layer, including governance, can push past:

Dimension Default Hard bound
elem_count 16,777,216 (2^24) 16,777,216 (2^24)
string_bytes 4,294,967,296 (2^32) 4,294,967,296 (2^32)
constructed_elems 1,048,576 (2^20) 1,048,576 (2^20)
packet_elems 16,777,216 (2^24) 16,777,216 (2^24)
render_bytes 16,777,216 (16 MiB) 4,294,967,296 (2^32)

For the first four the default is the hard bound, so governance cannot raise them. render_bytes is the exception: its default (16 MiB) sits below its hard bound (4 GiB), so governance may raise the effective value toward the ceiling — while the clause and envelope layers can still only tighten.

A governance value outside the valid range [1, ceiling] for a dimension is rejected when it is set, with an out-of-range error.

The remaining four dimensions — fuel_ops, eval_calls, call_depth, and value_height — have no fixed hard bound and may be raised freely by governance. Raising fuel_ops or eval_calls only spends more deterministic fuel. Raising call_depth or value_height past a machine’s stack requires reconfiguring that machine’s process stack (an out-of-band operational step), so there is no fixed count that must hold.

These are format bounds on counts, not memory bounds. A host with less memory than a cap implies will run out of memory before the cap is reached.

Named tiers

Three vetted bundles can be selected by name in a limits(...) clause instead of writing each dimension out:

Tier fuel_ops call_depth constructed_elems
$limits::small 10,000 64 4,000
$limits::standard 100,000 256 40,000
$limits::heavy 500,000 1,024 200,000

A tier resolves at compile time to a limit set; an explicit dimension written after a tier overrides that tier entry. See the limits clause for the syntax.

packet_elems is enforced at seal

packet_elems bounds the number of runtime elements in one packet. Unlike the other dimensions it is checked when the packet is sealed, and it honors only the global default and packet governance — a transaction source clause and a signed envelope do not apply to it.

render_bytes and how a render responds to it

render_bytes bounds the number of bytes produced by a single value render — _str, _code_str, JSON rendering, an error/diagnostic string, and the like. The byte count is charged as each byte is produced, and the render stops the instant the running total exceeds the limit, so a structurally shared value whose rendered form would expand far beyond its in-memory size never materializes the blow-up.

What happens on overflow depends on where the render feeds:

  • A render whose result becomes part of committed transaction state (a value a consumer keeps) aborts the transaction on overflow. It is never silently truncated — a truncated value would differ across nodes with different budgets and split the network.
  • A diagnostic render (a display, log line, or error-text string) instead emits a sentinel marker and stops. Because the truncation point is a fixed cross-target limit, the truncated text is identical everywhere, so it is safe even when that text is itself hashed within the transaction.

render_bytes is a configurable dimension like any other: it can be set in a limits(...) clause, a governance update, or a signed envelope, and (uniquely) governance may raise it above the 16 MiB default toward the 4 GiB hard bound.

Where to go next