Changelog#

User-observable changes to the specification, grouped by executor line (see Executor Versioning). Internal refactors are omitted; see the repository history for those

v0.2#

Breaking#

  1. LeaderPublicData uses calldata {"nd_outs": bytes[]} instead of an RLP list with a trailing padded sentinel. Its gas accounting uses a conservative 64-byte frame plus a 64-byte header and 32-byte-padded data for each output

  2. Fee-bucket references are human-readable names. The execution request’s bucket_totals and result’s data_fees_remaining fields are maps keyed by bucket name, and executor fee rules use buckets with 1 name or an array of names. Numeric bucket arrays and the former bucket_no config field are rejected

v0.3#

Breaking#

  1. ZIP runner loads charge per-entry metadata in addition to the raw archive size and base load cost (see runner load charges)

  2. RAM overheads increase for runner loads, emissions, message-fee rotations and nondeterministic outputs

  3. Each sub-VM is limited to max_runners loaded runners; exceeding the cap exits with out_of memory

  4. VFS paths are limited to vfs_path_len UTF-8 octets

  5. LeaderPublicData uses calldata {"nd_outs": bytes[]} instead of an RLP list with a trailing padded sentinel. Its fee and output-cap accounting uses a conservative 64-byte frame plus a 64-byte header and 32-byte-padded data for each output

  6. Each outbound message consumes 1 unit from the submitted_messages_count bucket in addition to its byte and gas charges. Submitted-message byte accounting includes the canonical ABI array frame, struct head, fee parameters, payload, and allocation subtree. Receipt-gas accounting includes the host-provided receiptWrapperBytes governance value

  7. Message-reveal gas and ABI-array overhead are charged on the first successfully emitted message rather than at execution startup. Message-free executions do not consume that reveal allowance

  8. Fee-bucket references are human-readable names. The execution request’s bucket_totals and result’s data_fees_remaining fields are maps keyed by bucket name, and executor fee rules use buckets with 1 name or an array of names. Numeric bucket arrays and the former bucket_no config field are rejected

  9. The pre-finalization state is spelled decided everywhere it is named. storage_view reads latest_finalized (1) and latest_decided (2) instead of latest_final and latest_non_final, the on field of the EmitInternalMessage and EmitInternalDeployMessage gl_call payloads (gl_call Messages) takes "decided" instead of "accepted", and a chain: runner id selects it with d rather than a (see chain: State Visibility). Numeric enum values are unchanged; none of the old spellings is accepted

  10. A chain: runner id resolved while a contract is being deployed canonicalizes to i, which previously spelled the deploy state as d

  11. The When action’s cond field spells the non-deterministic mode !det; the previous nondet spelling is rejected, with no back-compat alias. See the When action in Runners

  12. runner.json rejects unknown top-level and nested fields; a runner that relied on an extra field being silently ignored fails to load. The single top-level $schema string annotation is still accepted. See runner.json JSON Schema and the action definitions in Runners

  13. ZIP-packaged runners are accepted under narrower rules; an archive that previously loaded despite violating one of them now fails. See the “ZIP Archive” layout in Runners:

    1. Compression must read stored in both the central directory and the entry’s local header

    2. A stored entry’s compressed and uncompressed sizes must be equal

    3. Every entry’s CRC-32 is validated against its declared value

    4. A directory entry must carry no contents; it is skipped rather than added to the file list

    5. Entry names are validated at parse time: no empty name, no leading /, no backslash, no empty, . or .. path component, no trailing / on a file

    6. Entries sharing a name resolve to the last of them

  14. Hosts must provide separate minProposeTimeout, maxProposeTimeout, minCommitTimeout and maxCommitTimeout gas-data values. The former minTimeUnitsPerPhase value is no longer read

Changed#

  1. Every internal message declares its minimum primary fee plus its direct child allocation budgets, independent of whether it is emitted on acceptance or finalization. Balance-funded messages have no allocation subtree and declare only the primary fee; external messages declare zero

  2. Unless both time-unit allocations are zero, internal message emission checks leader units against propose bounds and validator units against commit bounds; violations report fee below_minimum

  3. A missing or malformed runner archive/comment header is now reported as invalid_contract runner absent or invalid_contract runner malformed respectively, instead of the former invalid_contract absent_runner_comment and invalid_contract malformed_runner codes. A malformed runner archive also now reports the precise invalid_contract runner malformed code where it previously surfaced a bare invalid_contract

  4. A RunNondet Message block starts on a budget seeded with its caller’s remaining RAM instead of a fresh 4 GiB one, so it can no longer use more RAM than the deterministic caller had left. See Resource Limiting

  5. Emitted messages, events, and leader nondeterministic outputs consume RAM for their retained representations. Their charges, like storage write charges, remain until execution ends and transfer to a caller that adopts a sandbox child’s retained data. See Resource Limiting

  6. A resource error raised while a module is being instantiated keeps its own code — e.g. exhausting the memory budget there reports out_of memory wasm_memory — instead of being reported as a bare invalid_contract

  7. storage_read and storage_write bound an access by the Storage Slot length instead of by u32 overflow, so a slot’s final octet is addressable; every access naming it was previously refused. See Functions