From e604bdb7d28ba06ae1480d6d24b552f25fe0ef53 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:25:50 +0100 Subject: [PATCH 01/67] docs: freeze v1 compatibility baseline --- docs/adr/0001-v2-versioning.md | 44 +++++++++++++++++++++ docs/v2/RELEASE_BASELINE.md | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 docs/adr/0001-v2-versioning.md create mode 100644 docs/v2/RELEASE_BASELINE.md diff --git a/docs/adr/0001-v2-versioning.md b/docs/adr/0001-v2-versioning.md new file mode 100644 index 0000000..376c48c --- /dev/null +++ b/docs/adr/0001-v2-versioning.md @@ -0,0 +1,44 @@ +# ADR 0001: Versioned Tonolith v2 beside v1 + +## Status + +Accepted for implementation. + +## Context + +Tonolith v1 is a deployed testnet artifact with published ROM, static, code, +StateInit, and state-hash commitments. Changing its storage, ABI, ISA, or hash +preimages would make existing local and public evidence ambiguous. + +Tonolith v2 adds asynchronous ports, epochs, bounded queues, reliable delivery, +and measured batching. Those features require new persistent fields and new +message semantics. + +## Decision + +Implement v2 beside v1 using new versioned namespaces and schemas: + +- v1 contracts, tools, tests, artifacts, and evidence remain valid; +- v2 uses `schemaVersion = 2`, `isaVersion = 2`, `abiVersion = 2`, and + `protocolVersion = 2` in its own implementation surface; +- v2 commitment domains are distinct from v1 domains; +- v1 programs may be used as v2 compatibility fixtures only after independent + v2 decoding proves identical architectural transitions; +- deployed v1 instances are never migrated in place; +- a v2 program or route change produces a new content-addressed StateInit; +- v2 code has no privileged runtime mutation path. + +## Consequences + +The repository carries two implementations and two sets of fixtures during the +transition. Shared utilities may be extracted only when bit-for-bit v1 tests +remain passing and the extraction has compatibility vectors. + +The v2 verifier must identify the version from code/data commitments rather than +from a human-readable name or an indexer label. + +## Release boundary + +V2 implementation and testnet work are permitted. Mainnet deployment requires +the separate production gates, external reviews, current network-economics +review, and explicit project-owner approval in the v2 engineering guide. diff --git a/docs/v2/RELEASE_BASELINE.md b/docs/v2/RELEASE_BASELINE.md new file mode 100644 index 0000000..b1d81bc --- /dev/null +++ b/docs/v2/RELEASE_BASELINE.md @@ -0,0 +1,70 @@ +# Tonolith v1 baseline for v2 work + +This record freezes the evidence reproduced before v2 implementation began. +It is a local engineering baseline, not a new deployment claim. + +## Source and tools + +- branch at baseline: `main`; +- baseline commit: `8663815`; +- Acton: `1.0.0 (3a4f0dc 2026-05-11)`; +- local Tolk executable: `1.3.0`; +- Node.js: `v26.7.0`; +- npm: `12.0.2`; +- effective Git identity: `ef-code` / + `111162892+EF-Code@users.noreply.github.com`. + +## Reproduced local gates + +The following commands passed before v2 edits: + +```text +acton fmt --check +acton check +acton build +acton test --fuzz-seed 42 +acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42 +acton test --snapshot benchmarks/gas-v1.json --fuzz-seed 42 +acton test --mutate --mutate-contract TonolithCpu --mutation-levels critical,major --mutation-workers 2 --fuzz-seed 42 +npm run typecheck +npm test +npm run build +``` + +Results: + +- 18/18 Acton contract tests pass; +- 21 TypeScript tests pass; +- 378/379 lines and 299/374 branches are covered by Acton; +- blended Acton score is 89.9%; +- mutation run: 223 killed, 9 survived, 6 compile-invalid, 238 total; +- accepted v1 `Advance`: 1,588 minimum, 19,527 maximum, 15,266 average gas; +- v1 batch cap: one instruction; +- Fibonacci output: `1, 1, 2, 3, 5, 8, 13`; +- Fibonacci final state hash: + `16925a27b1db66171cebb3d43c74702f06f4a362e302754f24026cca0a6bf2be`. + +The mutation survivors are retained as baseline evidence. They concern the +Acton external-message shim and defensive out-of-range selectors that v1's +typed address derivation cannot reach. V2 must test equivalent paths through +its own validator-compatible boundary rather than silently inheriting the gap. + +## Untouched guide hashes at phase start + +These values were recorded before implementation edits: + +```text +TONOLITH_IMPLEMENTATION_GUIDE.md +1cd946c1765258962ffb198fabbc3f857160e680e23799cfb15070cb7e723d74 + +TONOLITH_V2_ENGINEERING_GUIDE.md +c87e421658a3fa48d115550bcfc5c0db8c1fb525a526e4c107e57907dc25b620 +``` + +The files are intentionally excluded from v2 commits. + +## Public v1 reference + +The existing testnet Fibonacci contract and its transaction evidence remain the +v1 public reference. V2 reports must label new local, local-validator, and +testnet results separately from that run. From 28a8b4bab2b79dfd35b09c02f6eba610ec60e529 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:28:15 +0100 Subject: [PATCH 02/67] feat: define versioned v2 protocol constants --- packages/isa/src/constants.ts | 109 ++++++++++++++++++++++++++++++++++ packages/isa/src/index.ts | 1 + spec/v2/protocol.json | 101 +++++++++++++++++++++++++++++++ tests/v2/constants.test.ts | 59 ++++++++++++++++++ tsconfig.json | 2 +- 5 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 packages/isa/src/constants.ts create mode 100644 packages/isa/src/index.ts create mode 100644 spec/v2/protocol.json create mode 100644 tests/v2/constants.test.ts diff --git a/packages/isa/src/constants.ts b/packages/isa/src/constants.ts new file mode 100644 index 0000000..0d341b7 --- /dev/null +++ b/packages/isa/src/constants.ts @@ -0,0 +1,109 @@ +/** Versioned constants mirrored from spec/v2/protocol.json. */ +export const SCHEMA_VERSION = 2 as const; +export const ISA_VERSION = 2 as const; +export const ABI_VERSION = 2 as const; +export const PROTOCOL_VERSION = 2 as const; +export const TONOLITH_MAGIC = 0x544e4c32 as const; + +export const WORD_BITS = 16 as const; +export const ROM_WORDS = 1024 as const; +export const RAM_NIBBLES = 256 as const; +export const MAX_PORTS = 16 as const; +export const MAX_INPUT_RECORDS = 16 as const; +export const MAX_OUTPUT_RECORDS = 16 as const; +export const MAX_RECORDS_PER_EPOCH = 16 as const; +export const MAX_REPLAY_EPOCHS = 2 as const; +export const MAX_ACTIONS_PER_ADVANCE = 32 as const; +export const INITIAL_MAX_STEPS_PER_ADVANCE = 1 as const; +export const PROJECT_GAS_CEILING = 400_000 as const; + +export const DOMAIN = { + program: 0x544c5032, + static: 0x544c5332, + state: 0x544c4332, + epoch: 0x544c4532, + output: 0x544c4f32, + input: 0x544c4932, + route: 0x544c5232, + batch: 0x544c4232, + acknowledgement: 0x544c4132, +} as const; + +export const MESSAGE_PREFIX = { + advance: 0x544e4c21, + dispatchOutput: 0x544e4c22, + deliverInput: 0x544e4c23, + inputAccepted: 0x544e4c24, + topUp: 0x544e4c25, +} as const; + +export const EVENT_PREFIX = { + batchAdvanced: 0x544e4ca1, + outputCommitted: 0x544e4ca2, + inputCommitted: 0x544e4ca3, + outputAcknowledged: 0x544e4ca4, +} as const; + +export const STATUS = { + running: 0, + waitingInput: 1, + backpressured: 2, + halted: 3, + faulted: 4, +} as const; + +export const STOP_REASON = { + requestLimit: 0, + halted: 1, + faulted: 2, + inputUnavailable: 3, + outputBackpressure: 4, + yielded: 5, + outputEventLimit: 6, +} as const; + +export const SYS = { + HALT: 0, + OUT: 1, + CLC: 2, + STC: 3, + NOT: 4, + SHL: 5, + SHR: 6, + INC: 7, + DEC: 8, + IN: 9, + OUTP: 10, + YIELD: 11, + TRAP: 12, +} as const; + +export const ERROR = { + unknownMessage: 100, + invalidAbi: 101, + invalidVersion: 102, + invalidBody: 103, + invalidValue: 104, + staleAdvance: 120, + stateHashMismatch: 121, + invalidRun: 122, + invalidStaticCommitment: 123, + invalidStatus: 124, + invalidInstruction: 140, + invalidPc: 141, + architecturalInvariant: 142, + unauthorizedPeer: 160, + invalidRoute: 161, + invalidEpoch: 162, + invalidSequence: 163, + conflictingReplay: 164, + queueFull: 165, + notFound: 166, + insufficientReserve: 180, + insufficientValue: 181, + invalidStorage: 190, + externalMessageRejected: 191, +} as const; + +export type CoreStatus = (typeof STATUS)[keyof typeof STATUS]; +export type StopReason = (typeof STOP_REASON)[keyof typeof STOP_REASON]; diff --git a/packages/isa/src/index.ts b/packages/isa/src/index.ts new file mode 100644 index 0000000..548d69e --- /dev/null +++ b/packages/isa/src/index.ts @@ -0,0 +1 @@ +export * from "./constants.js"; diff --git a/spec/v2/protocol.json b/spec/v2/protocol.json new file mode 100644 index 0000000..a2af035 --- /dev/null +++ b/spec/v2/protocol.json @@ -0,0 +1,101 @@ +{ + "schemaVersion": 2, + "isaVersion": 2, + "abiVersion": 2, + "protocolVersion": 2, + "magic": "0x544e4c32", + "limits": { + "wordBits": 16, + "romWords": 1024, + "ramNibbles": 256, + "maxPorts": 16, + "maxInputRecords": 16, + "maxOutputRecords": 16, + "maxRecordsPerEpoch": 16, + "maxReplayEpochs": 2, + "maxActionsPerAdvance": 32, + "initialMaxStepsPerAdvance": 1, + "projectGasCeiling": 400000 + }, + "domains": { + "program": "0x544c5032", + "static": "0x544c5332", + "state": "0x544c4332", + "epoch": "0x544c4532", + "output": "0x544c4f32", + "input": "0x544c4932", + "route": "0x544c5232", + "batch": "0x544c4232", + "acknowledgement": "0x544c4132" + }, + "messages": { + "advance": "0x544e4c21", + "dispatchOutput": "0x544e4c22", + "deliverInput": "0x544e4c23", + "inputAccepted": "0x544e4c24", + "topUp": "0x544e4c25" + }, + "events": { + "batchAdvanced": "0x544e4ca1", + "outputCommitted": "0x544e4ca2", + "inputCommitted": "0x544e4ca3", + "outputAcknowledged": "0x544e4ca4" + }, + "statuses": { + "running": 0, + "waitingInput": 1, + "backpressured": 2, + "halted": 3, + "faulted": 4 + }, + "stopReasons": { + "requestLimit": 0, + "halted": 1, + "faulted": 2, + "inputUnavailable": 3, + "outputBackpressure": 4, + "yielded": 5, + "outputEventLimit": 6 + }, + "errors": { + "unknownMessage": 100, + "invalidAbi": 101, + "invalidVersion": 102, + "invalidBody": 103, + "invalidValue": 104, + "staleAdvance": 120, + "stateHashMismatch": 121, + "invalidRun": 122, + "invalidStaticCommitment": 123, + "invalidStatus": 124, + "invalidInstruction": 140, + "invalidPc": 141, + "architecturalInvariant": 142, + "unauthorizedPeer": 160, + "invalidRoute": 161, + "invalidEpoch": 162, + "invalidSequence": 163, + "conflictingReplay": 164, + "queueFull": 165, + "notFound": 166, + "insufficientReserve": 180, + "insufficientValue": 181, + "invalidStorage": 190, + "externalMessageRejected": 191 + }, + "sys": { + "halt": 0, + "out": 1, + "clc": 2, + "stc": 3, + "not": 4, + "shl": 5, + "shr": 6, + "inc": 7, + "dec": 8, + "in": 9, + "outp": 10, + "yield": 11, + "trap": 12 + } +} diff --git a/tests/v2/constants.test.ts b/tests/v2/constants.test.ts new file mode 100644 index 0000000..4870a0e --- /dev/null +++ b/tests/v2/constants.test.ts @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import { + ABI_VERSION, + DOMAIN, + EVENT_PREFIX, + ISA_VERSION, + MESSAGE_PREFIX, + PROTOCOL_VERSION, + SCHEMA_VERSION, + STATUS, + STOP_REASON, + TONOLITH_MAGIC, +} from "../../packages/isa/src/constants.js"; + +interface ProtocolSpec { + schemaVersion: number; + isaVersion: number; + abiVersion: number; + protocolVersion: number; + magic: string; + domains: Record; + messages: Record; + events: Record; + statuses: Record; + stopReasons: Record; +} + +test("v2 TypeScript constants match the machine-readable protocol specification", async () => { + const source = await readFile(new URL("../../spec/v2/protocol.json", import.meta.url), "utf8"); + const spec = JSON.parse(source) as ProtocolSpec; + + assert.equal(spec.schemaVersion, SCHEMA_VERSION); + assert.equal(spec.isaVersion, ISA_VERSION); + assert.equal(spec.abiVersion, ABI_VERSION); + assert.equal(spec.protocolVersion, PROTOCOL_VERSION); + assert.equal(spec.magic, `0x${TONOLITH_MAGIC.toString(16)}`); + assert.deepEqual(toHexRecord(DOMAIN), spec.domains); + assert.deepEqual(toHexRecord(MESSAGE_PREFIX), spec.messages); + assert.deepEqual(toHexRecord(EVENT_PREFIX), spec.events); + assert.deepEqual(STATUS, spec.statuses); + assert.deepEqual(STOP_REASON, spec.stopReasons); +}); + +test("v2 domains, message prefixes, and events are unique", () => { + const values = [ + ...Object.values(DOMAIN), + ...Object.values(MESSAGE_PREFIX), + ...Object.values(EVENT_PREFIX), + ]; + assert.equal(new Set(values).size, values.length); +}); + +function toHexRecord(values: Record): Record { + return Object.fromEntries( + Object.entries(values).map(([key, value]) => [key, `0x${value.toString(16)}`]), + ); +} diff --git a/tsconfig.json b/tsconfig.json index 6c95183..a068c69 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ "rootDir": ".", "types": ["node"] }, - "include": ["tools/**/*.ts", "tests/**/*.ts"], + "include": ["tools/**/*.ts", "packages/**/*.ts", "tests/**/*.ts"], "exclude": ["node_modules", "dist", "build", "gen"] } From f9869d1ca93f652b87c1934a1c09ff76469b112d Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:29:28 +0100 Subject: [PATCH 03/67] test: include recursive v2 test suites --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index dba0ec0..4160160 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "build": "tsc -p tsconfig.json", "prepare": "npm run build", "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "node --import tsx --test tests/*.test.ts", - "test:coverage": "c8 --reporter=text --reporter=lcov node --import tsx --test tests/*.test.ts", + "test": "node --import tsx --test 'tests/**/*.test.ts'", + "test:coverage": "c8 --reporter=text --reporter=lcov node --import tsx --test 'tests/**/*.test.ts'", "assemble:fibonacci": "tsx tools/assembler/cli.ts programs/fibonacci.tasm programs/fibonacci.json", "test:contract": "acton test --fuzz-seed 42", "test:contract:coverage": "acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42", From 7ef7f5dffe0d6bf99104926422f3aa6ae0121cc6 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:31:39 +0100 Subject: [PATCH 04/67] feat: add independent v2 instruction codec --- packages/isa/src/codec.ts | 158 ++++++++++++++++++++++++++++++++++ packages/isa/src/constants.ts | 2 + packages/isa/src/index.ts | 2 + packages/isa/src/types.ts | 52 +++++++++++ 4 files changed, 214 insertions(+) create mode 100644 packages/isa/src/codec.ts create mode 100644 packages/isa/src/types.ts diff --git a/packages/isa/src/codec.ts b/packages/isa/src/codec.ts new file mode 100644 index 0000000..eeab621 --- /dev/null +++ b/packages/isa/src/codec.ts @@ -0,0 +1,158 @@ +import { + NIBBLE_MASK, + PC_MASK, + RAM_NIBBLES, + ROM_WORDS, + SYS, + WORD_BITS, +} from "./constants.js"; +import { Opcode, type DecodedInstruction, type InstructionShape, type V2SysOp } from "./types.js"; + +export class V2IsaError extends Error { + public readonly code: "INVALID_WORD" | "INVALID_OPERAND" | "NON_CANONICAL" | "INVALID_SYS_SUBOP"; + + public constructor( + code: V2IsaError["code"], + message: string, + ) { + super(message); + this.name = "V2IsaError"; + this.code = code; + } +} + +export function encodeInstruction(opcode: Opcode, operand = 0): number { + if (!Number.isInteger(operand) || operand < 0 || operand >= 1 << 12) { + throw new V2IsaError("INVALID_OPERAND", "instruction operand must be 12-bit unsigned"); + } + + switch (opcode) { + case Opcode.NOP: + require(opcode, operand === 0, "NOP operand must be zero"); + break; + case Opcode.LDI: + require(opcode, operand <= NIBBLE_MASK, "LDI immediate must be 4 bits"); + break; + case Opcode.LDR: + case Opcode.STR: + case Opcode.ADD: + case Opcode.ADC: + case Opcode.SUB: + case Opcode.AND: + case Opcode.OR: + case Opcode.XOR: + require(opcode, operand <= 0x0f, "register operand must be 4 bits"); + break; + case Opcode.LDM: + case Opcode.STM: + require(opcode, operand < RAM_NIBBLES, "RAM address must be 8 bits"); + break; + case Opcode.JMP: + case Opcode.JZ: + case Opcode.JC: + require(opcode, operand < ROM_WORDS, "jump target must be 10 bits"); + break; + case Opcode.SYS: + validateSysOperand(operand); + break; + default: + throw new V2IsaError("INVALID_OPERAND", `unknown opcode ${String(opcode)}`); + } + + return ((opcode & NIBBLE_MASK) << (WORD_BITS - 4)) | operand; +} + +export function decodeInstruction(word: number): DecodedInstruction { + if (!Number.isInteger(word) || word < 0 || word >= 1 << WORD_BITS) { + throw new V2IsaError("INVALID_WORD", "instruction word must be 16-bit unsigned"); + } + + const opcode = (word >>> (WORD_BITS - 4)) as Opcode; + const operand = word & 0x0fff; + const canonical = encodeInstruction(opcode, operand); + if (canonical !== word) { + throw new V2IsaError("NON_CANONICAL", `instruction 0x${word.toString(16).padStart(4, "0")} is non-canonical`); + } + + if (opcode !== Opcode.SYS) { + return { word, opcode, operand }; + } + + const sysSubop = (operand & 0x0f) as V2SysOp; + if (sysSubop === SYS.IN || sysSubop === SYS.OUTP) { + return { word, opcode, operand, sysSubop, port: operand >>> 4 }; + } + if (sysSubop === SYS.TRAP) { + return { word, opcode, operand, sysSubop, trapCode: operand >>> 4 }; + } + return { word, opcode, operand, sysSubop }; +} + +export function encodeShape(shape: InstructionShape): number { + return encodeInstruction(shape.opcode, shape.operand ?? 0); +} + +export function encodePortInstruction(op: typeof SYS.IN | typeof SYS.OUTP, port: number): number { + requirePort(port); + return encodeInstruction(Opcode.SYS, (port << 4) | op); +} + +export function encodeTrap(code: number): number { + if (!Number.isInteger(code) || code < 0 || code > 0xff) { + throw new V2IsaError("INVALID_OPERAND", "TRAP code must be an unsigned 8-bit value"); + } + return encodeInstruction(Opcode.SYS, (code << 4) | SYS.TRAP); +} + +export function disassembleWord(word: number): string { + const instruction = decodeInstruction(word); + if (instruction.opcode === Opcode.NOP) return "NOP"; + if (instruction.opcode === Opcode.LDI) return `LDI 0x${instruction.operand.toString(16).toUpperCase()}`; + if (instruction.opcode >= Opcode.LDR && instruction.opcode <= Opcode.XOR) { + return `${Opcode[instruction.opcode]} R${instruction.operand}`; + } + if (instruction.opcode === Opcode.LDM || instruction.opcode === Opcode.STM) { + return `${Opcode[instruction.opcode]} 0x${instruction.operand.toString(16).toUpperCase()}`; + } + if (instruction.opcode >= Opcode.JMP && instruction.opcode <= Opcode.JC) { + return `${Opcode[instruction.opcode]} 0x${instruction.operand.toString(16).toUpperCase()}`; + } + if (instruction.sysSubop === SYS.IN || instruction.sysSubop === SYS.OUTP) { + return `${instruction.sysSubop === SYS.IN ? "IN" : "OUTP"} ${instruction.port}`; + } + if (instruction.sysSubop === SYS.TRAP) return `TRAP 0x${instruction.trapCode!.toString(16).toUpperCase()}`; + return `SYS ${sysName(instruction.sysSubop!)}`; +} + +export function sysName(op: V2SysOp): string { + const entry = Object.entries(SYS).find(([, value]) => value === op); + return entry?.[0] ?? `0x${op.toString(16).toUpperCase()}`; +} + +function validateSysOperand(operand: number): void { + const subop = operand & 0x0f; + if (subop <= SYS.DEC || subop === SYS.YIELD) { + require(Opcode.SYS, operand === subop, "legacy SYS and YIELD operands must have zero argument bits"); + return; + } + if (subop === SYS.IN || subop === SYS.OUTP) { + requirePort(operand >>> 4); + return; + } + if (subop === SYS.TRAP) { + return; + } + throw new V2IsaError("INVALID_SYS_SUBOP", `reserved SYS suboperation 0x${subop.toString(16)}`); +} + +function requirePort(port: number): void { + if (!Number.isInteger(port) || port < 0 || port >= 16) { + throw new V2IsaError("INVALID_OPERAND", "port must be in 0..15"); + } +} + +function require(opcode: Opcode, condition: boolean, message: string): void { + if (!condition) { + throw new V2IsaError("NON_CANONICAL", `${Opcode[opcode]}: ${message}`); + } +} diff --git a/packages/isa/src/constants.ts b/packages/isa/src/constants.ts index 0d341b7..64e2438 100644 --- a/packages/isa/src/constants.ts +++ b/packages/isa/src/constants.ts @@ -6,8 +6,10 @@ export const PROTOCOL_VERSION = 2 as const; export const TONOLITH_MAGIC = 0x544e4c32 as const; export const WORD_BITS = 16 as const; +export const NIBBLE_MASK = 0x0f as const; export const ROM_WORDS = 1024 as const; export const RAM_NIBBLES = 256 as const; +export const PC_MASK = ROM_WORDS - 1; export const MAX_PORTS = 16 as const; export const MAX_INPUT_RECORDS = 16 as const; export const MAX_OUTPUT_RECORDS = 16 as const; diff --git a/packages/isa/src/index.ts b/packages/isa/src/index.ts index 548d69e..c416183 100644 --- a/packages/isa/src/index.ts +++ b/packages/isa/src/index.ts @@ -1 +1,3 @@ export * from "./constants.js"; +export * from "./types.js"; +export * from "./codec.js"; diff --git a/packages/isa/src/types.ts b/packages/isa/src/types.ts new file mode 100644 index 0000000..c0f87a3 --- /dev/null +++ b/packages/isa/src/types.ts @@ -0,0 +1,52 @@ +import { SYS } from "./constants.js"; + +export enum Opcode { + NOP = 0x0, + LDI = 0x1, + LDR = 0x2, + STR = 0x3, + ADD = 0x4, + ADC = 0x5, + SUB = 0x6, + AND = 0x7, + OR = 0x8, + XOR = 0x9, + LDM = 0xa, + STM = 0xb, + JMP = 0xc, + JZ = 0xd, + JC = 0xe, + SYS = 0xf, +} + +export type LegacySysOp = + | typeof SYS.HALT + | typeof SYS.OUT + | typeof SYS.CLC + | typeof SYS.STC + | typeof SYS.NOT + | typeof SYS.SHL + | typeof SYS.SHR + | typeof SYS.INC + | typeof SYS.DEC; + +export type V2SysOp = + | LegacySysOp + | typeof SYS.IN + | typeof SYS.OUTP + | typeof SYS.YIELD + | typeof SYS.TRAP; + +export interface DecodedInstruction { + readonly word: number; + readonly opcode: Opcode; + readonly operand: number; + readonly sysSubop?: V2SysOp; + readonly port?: number; + readonly trapCode?: number; +} + +export interface InstructionShape { + readonly opcode: Opcode; + readonly operand?: number; +} From c07ee35d182a203fdf6b5d13d55f733486ce0a95 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:31:51 +0100 Subject: [PATCH 05/67] test: cover v2 port and trap encoding --- tests/v2/isa.test.ts | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/v2/isa.test.ts diff --git a/tests/v2/isa.test.ts b/tests/v2/isa.test.ts new file mode 100644 index 0000000..d469246 --- /dev/null +++ b/tests/v2/isa.test.ts @@ -0,0 +1,71 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { SYS } from "../../packages/isa/src/constants.js"; +import { + decodeInstruction, + disassembleWord, + encodeInstruction, + encodePortInstruction, + encodeTrap, + V2IsaError, +} from "../../packages/isa/src/codec.js"; +import { Opcode } from "../../packages/isa/src/types.js"; + +test("v2 retains canonical encoding for every v1 opcode family", () => { + const words = [ + encodeInstruction(Opcode.NOP), + encodeInstruction(Opcode.LDI, 15), + encodeInstruction(Opcode.LDR, 15), + encodeInstruction(Opcode.STR, 15), + encodeInstruction(Opcode.ADD, 15), + encodeInstruction(Opcode.ADC, 15), + encodeInstruction(Opcode.SUB, 15), + encodeInstruction(Opcode.AND, 15), + encodeInstruction(Opcode.OR, 15), + encodeInstruction(Opcode.XOR, 15), + encodeInstruction(Opcode.LDM, 255), + encodeInstruction(Opcode.STM, 255), + encodeInstruction(Opcode.JMP, 1023), + encodeInstruction(Opcode.JZ, 1023), + encodeInstruction(Opcode.JC, 1023), + encodeInstruction(Opcode.SYS, SYS.DEC), + ]; + for (const word of words) { + assert.equal(decodeInstruction(word).word, word); + } +}); + +test("v2 port and trap instructions expose structured operands", () => { + const input = encodePortInstruction(SYS.IN, 7); + assert.deepEqual(decodeInstruction(input), { + word: input, + opcode: Opcode.SYS, + operand: 0x79, + sysSubop: SYS.IN, + port: 7, + }); + const output = encodePortInstruction(SYS.OUTP, 15); + assert.equal(disassembleWord(output), "OUTP 15"); + const trap = encodeTrap(0xab); + assert.deepEqual(decodeInstruction(trap), { + word: trap, + opcode: Opcode.SYS, + operand: 0xabc, + sysSubop: SYS.TRAP, + trapCode: 0xab, + }); +}); + +test("v2 rejects noncanonical and invalid SYS operands", () => { + const invalid: Array<() => number> = [ + () => encodeInstruction(Opcode.NOP, 1), + () => encodeInstruction(Opcode.LDI, 16), + () => encodeInstruction(Opcode.SYS, SYS.YIELD | 0x10), + () => encodeInstruction(Opcode.SYS, 0x0d), + () => encodePortInstruction(SYS.IN, 16), + () => encodeTrap(256), + ]; + for (const make of invalid) { + assert.throws(make, (error: unknown) => error instanceof V2IsaError); + } +}); From dc84e9314ffeb7d534f3612c8ef483904110d769 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:35:37 +0100 Subject: [PATCH 06/67] feat: add bounded v2 emulator state model --- packages/emulator/src/model.ts | 200 +++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 packages/emulator/src/model.ts diff --git a/packages/emulator/src/model.ts b/packages/emulator/src/model.ts new file mode 100644 index 0000000..95def61 --- /dev/null +++ b/packages/emulator/src/model.ts @@ -0,0 +1,200 @@ +import { + MAX_INPUT_RECORDS, + MAX_OUTPUT_RECORDS, + MAX_PORTS, + RAM_NIBBLES, + ROM_WORDS, + STATUS, + type CoreStatus, +} from "../../isa/src/constants.js"; + +export type Hash256 = string; + +export interface Route { + readonly sourceCoreId: number; + readonly sourcePort: number; + readonly destinationCoreId: number; + readonly destinationPort: number; + readonly delay: number; + readonly maxRecordsPerEpoch: number; +} + +export interface InputQuota { + readonly port: number; + readonly count: number; +} + +export interface CoreConfig { + readonly runId: Hash256; + readonly coreId: number; + readonly programId: Hash256; + readonly romRoot: Hash256; + readonly initialRamRoot: Hash256; + readonly routeRoot: Hash256; + readonly staticCommitment: Hash256; + readonly routes: readonly Route[]; + readonly requiredInputs: readonly InputQuota[]; + readonly maxStepsPerAdvance: number; + readonly maxInputRecords?: number; + readonly maxOutputRecords?: number; +} + +export interface InputRecord { + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly sourceEpoch: bigint; + readonly destinationEpoch: bigint; + readonly sourcePort: number; + readonly destinationPort: number; + readonly sequence: number; + readonly value: number; + readonly sourceInstructionCount: bigint; + readonly sourceStateHash: Hash256; + readonly consumed: boolean; +} + +export interface OutputRecord { + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly sourceEpoch: bigint; + readonly destinationEpoch: bigint; + readonly sourcePort: number; + readonly destinationPort: number; + readonly sequence: number; + readonly value: number; + readonly sourceInstructionCount: bigint; + readonly sourceStateHash: Hash256; +} + +export interface LegacyOutput { + readonly outputIndex: bigint; + readonly instructionCount: bigint; + readonly value: number; + readonly outputCommitment: Hash256; +} + +export interface V2State { + readonly config: CoreConfig; + advanceCount: bigint; + instructionCount: bigint; + outputCount: bigint; + inputCount: bigint; + epoch: bigint; + pc: number; + accumulator: number; + flags: number; + status: CoreStatus; + faultCode: number; + registers: number[]; + ram: Uint8Array; + outputCommitment: Hash256; + inputCommitment: Hash256; + epochHistoryCommitment: Hash256; + nextOutputSequence: number[]; + inbox: InputRecord[]; + outbox: OutputRecord[]; +} + +export function createInitialState(config: CoreConfig): V2State { + validateConfig(config); + return { + config, + advanceCount: 0n, + instructionCount: 0n, + outputCount: 0n, + inputCount: 0n, + epoch: 0n, + pc: 0, + accumulator: 0, + flags: 0, + status: STATUS.running, + faultCode: 0, + registers: Array.from({ length: 16 }, () => 0), + ram: new Uint8Array(RAM_NIBBLES), + outputCommitment: zeroHash(), + inputCommitment: zeroHash(), + epochHistoryCommitment: zeroHash(), + nextOutputSequence: Array.from({ length: MAX_PORTS }, () => 0), + inbox: [], + outbox: [], + }; +} + +export function cloneState(state: V2State): V2State { + return { + ...state, + config: { + ...state.config, + routes: state.config.routes.map((route) => ({ ...route })), + requiredInputs: state.config.requiredInputs.map((quota) => ({ ...quota })), + }, + registers: [...state.registers], + ram: state.ram.slice(), + nextOutputSequence: [...state.nextOutputSequence], + inbox: state.inbox.map((record) => ({ ...record })), + outbox: state.outbox.map((record) => ({ ...record })), + }; +} + +export function zeroHash(): Hash256 { + return "0".repeat(64); +} + +export function validateConfig(config: CoreConfig): void { + assertHash(config.runId, "runId"); + assertHash(config.programId, "programId"); + assertHash(config.romRoot, "romRoot"); + assertHash(config.initialRamRoot, "initialRamRoot"); + assertHash(config.routeRoot, "routeRoot"); + assertHash(config.staticCommitment, "staticCommitment"); + assertInteger(config.coreId, 0, 0xffff, "coreId"); + assertInteger(config.maxStepsPerAdvance, 1, 0xffff, "maxStepsPerAdvance"); + assertInteger(config.maxInputRecords ?? MAX_INPUT_RECORDS, 0, MAX_INPUT_RECORDS, "maxInputRecords"); + assertInteger(config.maxOutputRecords ?? MAX_OUTPUT_RECORDS, 0, MAX_OUTPUT_RECORDS, "maxOutputRecords"); + const seenPorts = new Set(); + for (const quota of config.requiredInputs) { + assertInteger(quota.port, 0, MAX_PORTS - 1, "input port"); + assertInteger(quota.count, 0, MAX_INPUT_RECORDS, "input quota"); + if (seenPorts.has(quota.port)) throw new RangeError(`duplicate input quota port ${quota.port}`); + seenPorts.add(quota.port); + } + for (const route of config.routes) { + assertInteger(route.sourceCoreId, 0, 0xffff, "sourceCoreId"); + assertInteger(route.destinationCoreId, 0, 0xffff, "destinationCoreId"); + assertInteger(route.sourcePort, 0, MAX_PORTS - 1, "sourcePort"); + assertInteger(route.destinationPort, 0, MAX_PORTS - 1, "destinationPort"); + assertInteger(route.delay, 1, 0xff, "route delay"); + assertInteger(route.maxRecordsPerEpoch, 1, MAX_INPUT_RECORDS, "route record quota"); + } +} + +export function assertHash(value: string, field: string): void { + if (!/^[0-9a-f]{64}$/i.test(value)) throw new RangeError(`${field} must be a 256-bit hex value`); +} + +export function assertInteger(value: number, minimum: number, maximum: number, field: string): void { + if (!Number.isInteger(value) || value < minimum || value > maximum) { + throw new RangeError(`${field} must be an integer in ${minimum}..${maximum}`); + } +} + +export function assertNibble(value: number, field: string): void { + assertInteger(value, 0, 15, field); +} + +export function assertStateBounds(state: V2State): void { + assertInteger(state.pc, 0, ROM_WORDS - 1, "pc"); + assertNibble(state.accumulator, "accumulator"); + assertInteger(state.flags, 0, 3, "flags"); + assertInteger(state.faultCode, 0, 0xff, "faultCode"); + if (state.registers.length !== 16) throw new RangeError("register file must contain 16 entries"); + state.registers.forEach((value, index) => assertNibble(value, `register ${index}`)); + if (state.ram.length !== RAM_NIBBLES) throw new RangeError("RAM must contain 256 nibbles"); + state.ram.forEach((value, index) => assertNibble(value, `RAM ${index}`)); + if (state.nextOutputSequence.length !== MAX_PORTS) throw new RangeError("sequence table must contain 16 ports"); + state.nextOutputSequence.forEach((value, index) => assertInteger(value, 0, 255, `sequence ${index}`)); + if (state.inbox.length > (state.config.maxInputRecords ?? MAX_INPUT_RECORDS)) throw new RangeError("inbox full"); + if (state.outbox.length > (state.config.maxOutputRecords ?? MAX_OUTPUT_RECORDS)) throw new RangeError("outbox full"); +} From f3b0a713cb69b73e9cb2d28969f5af94c10ad049 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:35:47 +0100 Subject: [PATCH 07/67] test: validate v2 state bounds and cloning --- tests/v2/model.test.ts | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/v2/model.test.ts diff --git a/tests/v2/model.test.ts b/tests/v2/model.test.ts new file mode 100644 index 0000000..49f67cf --- /dev/null +++ b/tests/v2/model.test.ts @@ -0,0 +1,56 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { createInitialState, assertStateBounds, cloneState, zeroHash, type CoreConfig } from "../../packages/emulator/src/model.js"; + +const config: CoreConfig = { + runId: "11".repeat(32), + coreId: 0, + programId: "22".repeat(32), + romRoot: "33".repeat(32), + initialRamRoot: "44".repeat(32), + routeRoot: "55".repeat(32), + staticCommitment: "66".repeat(32), + routes: [{ + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 1, + destinationPort: 0, + delay: 1, + maxRecordsPerEpoch: 4, + }], + requiredInputs: [{ port: 0, count: 1 }], + maxStepsPerAdvance: 1, +}; + +test("v2 initial state is bounded and fully initialized", () => { + const state = createInitialState(config); + assertStateBounds(state); + assert.equal(state.status, 0); + assert.equal(state.inbox.length, 0); + assert.equal(state.outbox.length, 0); + assert.equal(state.outputCommitment, zeroHash()); + assert.equal(state.nextOutputSequence.length, 16); +}); + +test("v2 state cloning does not alias mutable protocol data", () => { + const original = createInitialState(config); + const clone = cloneState(original); + clone.registers[0] = 15; + clone.ram[0] = 14; + clone.nextOutputSequence[0] = 1; + assert.equal(original.registers[0], 0); + assert.equal(original.ram[0], 0); + assert.equal(original.nextOutputSequence[0], 0); +}); + +test("v2 configuration rejects duplicate quotas and malformed identities", () => { + assert.throws(() => createInitialState({ ...config, runId: "bad" })); + assert.throws(() => createInitialState({ + ...config, + requiredInputs: [{ port: 0, count: 1 }, { port: 0, count: 1 }], + })); + assert.throws(() => createInitialState({ + ...config, + routes: [{ ...config.routes[0]!, delay: 0 }], + })); +}); From 98003e23b9433ca239038e033f5e2ff5a518204c Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:43:16 +0100 Subject: [PATCH 08/67] feat: add canonical v2 commitment cells --- packages/emulator/src/commitments.ts | 385 +++++++++++++++++++++++++++ packages/emulator/src/index.ts | 2 + 2 files changed, 387 insertions(+) create mode 100644 packages/emulator/src/commitments.ts create mode 100644 packages/emulator/src/index.ts diff --git a/packages/emulator/src/commitments.ts b/packages/emulator/src/commitments.ts new file mode 100644 index 0000000..82d0930 --- /dev/null +++ b/packages/emulator/src/commitments.ts @@ -0,0 +1,385 @@ +import { beginCell, type Cell } from "@ton/core"; +import { DOMAIN, STATUS } from "../../isa/src/constants.js"; +import { + assertHash, + assertInteger, + assertNibble, + type Hash256, + type InputRecord, + type OutputRecord, + type Route, + type V2State, + zeroHash, +} from "./model.js"; + +export function hashHex(cell: Cell): Hash256 { + return cell.hash().toString("hex"); +} + +export function hashBigInt(cell: Cell): bigint { + return BigInt(`0x${hashHex(cell)}`); +} + +export function toBigInt(hash: Hash256): bigint { + assertHash(hash, "hash"); + return BigInt(`0x${hash}`); +} + +export function hashField(builder: ReturnType, hash: Hash256): ReturnType { + return builder.storeUint(toBigInt(hash), 256); +} + +export function programCommitmentCell( + artifactVersion: number, + isaVersion: number, + wordBits: number, + romWords: number, + ramNibbles: number, + entryPc: number, + romRoot: Hash256, + initialRamRoot: Hash256, +): Cell { + return hashField( + beginCell() + .storeUint(DOMAIN.program, 32) + .storeUint(artifactVersion, 16) + .storeUint(isaVersion, 16) + .storeUint(wordBits, 8) + .storeUint(romWords, 16) + .storeUint(ramNibbles, 16) + .storeUint(entryPc, 16), + romRoot, + ).storeUint(toBigInt(initialRamRoot), 256).endCell(); +} + +export interface ProgramCommitmentFields { + artifactVersion: number; + isaVersion: number; + wordBits: number; + romWords: number; + ramNibbles: number; + entryPc: number; + romRoot: Hash256; + initialRamRoot: Hash256; +} + +export function programId(fields: ProgramCommitmentFields): Hash256 { + return hashHex(programCommitmentCell( + fields.artifactVersion, + fields.isaVersion, + fields.wordBits, + fields.romWords, + fields.ramNibbles, + fields.entryPc, + fields.romRoot, + fields.initialRamRoot, + )); +} + +export function staticCommitmentCell(fields: { + schemaVersion: number; + isaVersion: number; + abiVersion: number; + protocolVersion: number; + runId: Hash256; + coreId: number; + programId: Hash256; + romRoot: Hash256; + routeRoot: Hash256; + limitsHash: Hash256; +}): Cell { + const programPayload = beginCell() + .storeUint(toBigInt(fields.programId), 256) + .storeUint(toBigInt(fields.romRoot), 256) + .storeUint(toBigInt(fields.routeRoot), 256) + .endCell(); + const limitsPayload = beginCell().storeUint(toBigInt(fields.limitsHash), 256).endCell(); + return beginCell() + .storeUint(DOMAIN.static, 32) + .storeUint(fields.schemaVersion, 16) + .storeUint(fields.isaVersion, 16) + .storeUint(fields.abiVersion, 16) + .storeUint(fields.protocolVersion, 16) + .storeUint(toBigInt(fields.runId), 256) + .storeUint(fields.coreId, 16) + .storeRef(programPayload) + .storeRef(limitsPayload) + .endCell(); +} + +export function outputRecordCell(record: OutputRecord): Cell { + return beginCell() + .storeUint(DOMAIN.output, 32) + .storeUint(toBigInt(record.outputId), 256) + .storeUint(record.sourceCoreId, 16) + .storeUint(record.destinationCoreId, 16) + .storeUint(record.sourceEpoch, 64) + .storeUint(record.destinationEpoch, 64) + .storeUint(record.sourcePort, 4) + .storeUint(record.destinationPort, 4) + .storeUint(record.sequence, 8) + .storeUint(record.value, 4) + .storeUint(record.sourceInstructionCount, 64) + .storeUint(toBigInt(record.sourceStateHash), 256) + .endCell(); +} + +export function inputRecordCell(record: InputRecord): Cell { + return beginCell() + .storeUint(DOMAIN.input, 32) + .storeUint(toBigInt(record.outputId), 256) + .storeUint(record.sourceCoreId, 16) + .storeUint(record.destinationCoreId, 16) + .storeUint(record.sourceEpoch, 64) + .storeUint(record.destinationEpoch, 64) + .storeUint(record.sourcePort, 4) + .storeUint(record.destinationPort, 4) + .storeUint(record.sequence, 8) + .storeUint(record.value, 4) + .storeUint(record.sourceInstructionCount, 64) + .storeUint(toBigInt(record.sourceStateHash), 256) + .storeBit(record.consumed) + .endCell(); +} + +export function routeCell(route: Route): Cell { + return beginCell() + .storeUint(DOMAIN.route, 32) + .storeUint(route.sourceCoreId, 16) + .storeUint(route.sourcePort, 4) + .storeUint(route.destinationCoreId, 16) + .storeUint(route.destinationPort, 4) + .storeUint(route.delay, 8) + .storeUint(route.maxRecordsPerEpoch, 8) + .endCell(); +} + +export function routeRootCell(routes: readonly Route[]): Cell { + const sorted = [...routes].sort(compareRoutes); + return boundedRecordRoot(sorted.map(routeCell)); +} + +export function routeRoot(routes: readonly Route[]): Hash256 { + return hashHex(routeRootCell(routes)); +} + +export function inputRootCell(records: readonly InputRecord[]): Cell { + const sorted = [...records].sort(compareInputs); + return boundedRecordRoot(sorted.map(inputRecordCell)); +} + +export function outputRootCell(records: readonly OutputRecord[]): Cell { + const sorted = [...records].sort(compareOutputs); + return boundedRecordRoot(sorted.map(outputRecordCell)); +} + +export function registersCell(registers: readonly number[]): Cell { + if (registers.length !== 16) throw new RangeError("register file must contain 16 entries"); + let packed = 0n; + registers.forEach((value, index) => { + assertNibble(value, `register ${index}`); + packed |= BigInt(value) << BigInt(index * 4); + }); + return beginCell().storeUint(packed, 64).endCell(); +} + +export function ramRootCell(ram: Uint8Array): Cell { + if (ram.length !== 256) throw new RangeError("RAM must contain 256 nibbles"); + const pages = Array.from({ length: 4 }, (_, page) => { + let packed = 0n; + for (let offset = 0; offset < 64; offset += 1) { + const value = ram[page * 64 + offset] ?? 0; + assertNibble(value, `RAM ${page * 64 + offset}`); + packed |= BigInt(value) << BigInt(offset * 4); + } + return beginCell().storeUint(packed, 256).endCell(); + }); + return beginCell() + .storeRef(pages[0]!) + .storeRef(pages[1]!) + .storeRef(pages[2]!) + .storeRef(pages[3]!) + .endCell(); +} + +export function nextOutputCommitment(previous: Hash256, record: OutputRecord): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.output, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(toBigInt(record.outputId), 256) + .storeUint(record.sourceInstructionCount, 64) + .storeUint(record.value, 4) + .endCell(), + ); +} + +export function nextInputCommitment(previous: Hash256, record: InputRecord): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.input, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(toBigInt(record.outputId), 256) + .storeUint(record.destinationEpoch, 64) + .storeUint(record.sequence, 8) + .storeUint(record.value, 4) + .storeBit(record.consumed) + .endCell(), + ); +} + +export function nextEpochCommitment( + previous: Hash256, + epoch: bigint, + stateHash: Hash256, + outputCommitment: Hash256, + inputCommitment: Hash256, +): Hash256 { + const epochPayload = beginCell() + .storeUint(toBigInt(stateHash), 256) + .storeUint(toBigInt(outputCommitment), 256) + .storeUint(toBigInt(inputCommitment), 256) + .endCell(); + return hashHex( + beginCell() + .storeUint(DOMAIN.epoch, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(epoch, 64) + .storeRef(epochPayload) + .endCell(), + ); +} + +export function outputId(record: Omit): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.output, 32) + .storeUint(toBigInt(record.sourceStateHash), 256) + .storeUint(record.sourceCoreId, 16) + .storeUint(record.destinationCoreId, 16) + .storeUint(record.sourceEpoch, 64) + .storeUint(record.destinationEpoch, 64) + .storeUint(record.sourcePort, 4) + .storeUint(record.destinationPort, 4) + .storeUint(record.sequence, 8) + .storeUint(record.value, 4) + .storeUint(record.sourceInstructionCount, 64) + .endCell(), + ); +} + +export function batchCommitment(fields: { + previousStateHash: Hash256; + nextStateHash: Hash256; + startInstructionCount: bigint; + endInstructionCount: bigint; + stepsExecuted: number; + outputsProduced: number; + stopReason: number; +}): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.batch, 32) + .storeUint(toBigInt(fields.previousStateHash), 256) + .storeUint(toBigInt(fields.nextStateHash), 256) + .storeUint(fields.startInstructionCount, 64) + .storeUint(fields.endInstructionCount, 64) + .storeUint(fields.stepsExecuted, 16) + .storeUint(fields.outputsProduced, 8) + .storeUint(fields.stopReason, 8) + .endCell(), + ); +} + +export function coreStateCell(state: V2State): Cell { + assertStateForHash(state); + const commitmentCell = beginCell() + .storeUint(toBigInt(state.outputCommitment), 256) + .storeUint(toBigInt(state.inputCommitment), 256) + .storeUint(toBigInt(state.epochHistoryCommitment), 256) + .endCell(); + const memoryCell = beginCell() + .storeRef(registersCell(state.registers)) + .storeRef(ramRootCell(state.ram)) + .endCell(); + const queueCell = beginCell() + .storeRef(inputRootCell(state.inbox)) + .storeRef(outputRootCell(state.outbox)) + .endCell(); + return beginCell() + .storeUint(DOMAIN.state, 32) + .storeUint(toBigInt(state.config.staticCommitment), 256) + .storeUint(state.status, 3) + .storeUint(state.faultCode, 8) + .storeUint(state.pc, 10) + .storeUint(state.accumulator, 4) + .storeUint(state.flags, 2) + .storeUint(state.epoch, 64) + .storeUint(state.instructionCount, 64) + .storeUint(state.advanceCount, 64) + .storeUint(state.outputCount, 64) + .storeUint(state.inputCount, 64) + .storeUint(packSequences(state.nextOutputSequence), 128) + .storeRef(commitmentCell) + .storeRef(memoryCell) + .storeRef(queueCell) + .endCell(); +} + +export function stateHash(state: V2State): Hash256 { + return hashHex(coreStateCell(state)); +} + +function boundedRecordRoot(records: readonly Cell[]): Cell { + if (records.length > 16) throw new RangeError("record root supports at most 16 records"); + const pages = Array.from({ length: 4 }, (_, pageIndex) => { + const builder = beginCell().storeUint(Math.min(Math.max(records.length - pageIndex * 4, 0), 4), 3); + for (let offset = 0; offset < 4; offset += 1) { + const record = records[pageIndex * 4 + offset]; + if (record !== undefined) builder.storeRef(record); + } + return builder.endCell(); + }); + return beginCell() + .storeUint(records.length, 5) + .storeRef(pages[0]!) + .storeRef(pages[1]!) + .storeRef(pages[2]!) + .storeRef(pages[3]!) + .endCell(); +} + +function packSequences(sequences: readonly number[]): bigint { + if (sequences.length !== 16) throw new RangeError("sequence table must contain 16 entries"); + return sequences.reduce((packed, sequence, index) => { + assertInteger(sequence, 0, 255, `sequence ${index}`); + return packed | (BigInt(sequence) << BigInt(index * 8)); + }, 0n); +} + +function assertStateForHash(state: V2State): void { + assertInteger(state.status, STATUS.running, STATUS.faulted, "status"); + assertInteger(state.pc, 0, 1023, "pc"); + assertInteger(state.faultCode, 0, 255, "faultCode"); + assertNibble(state.accumulator, "accumulator"); + assertInteger(state.flags, 0, 3, "flags"); + [state.config.staticCommitment, state.outputCommitment, state.inputCommitment, state.epochHistoryCommitment] + .forEach((hash) => assertHash(hash, "commitment")); + if (state.epoch < 0n || state.instructionCount < 0n || state.advanceCount < 0n || state.outputCount < 0n || state.inputCount < 0n) { + throw new RangeError("state counters must be unsigned"); + } +} + +function compareRoutes(a: Route, b: Route): number { + return a.sourceCoreId - b.sourceCoreId || a.sourcePort - b.sourcePort || a.destinationCoreId - b.destinationCoreId || a.destinationPort - b.destinationPort; +} + +function compareInputs(a: InputRecord, b: InputRecord): number { + return Number(a.destinationEpoch - b.destinationEpoch) || a.destinationPort - b.destinationPort || a.sequence - b.sequence; +} + +function compareOutputs(a: OutputRecord, b: OutputRecord): number { + return Number(a.sourceEpoch - b.sourceEpoch) || a.sourcePort - b.sourcePort || a.sequence - b.sequence; +} + +export const EMPTY_COMMITMENT = zeroHash(); diff --git a/packages/emulator/src/index.ts b/packages/emulator/src/index.ts new file mode 100644 index 0000000..322d935 --- /dev/null +++ b/packages/emulator/src/index.ts @@ -0,0 +1,2 @@ +export * from "./model.js"; +export * from "./commitments.js"; From 6a10c6d6e967abc055baf2ea2e58bf1ecfdd495d Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:43:28 +0100 Subject: [PATCH 09/67] test: cover v2 commitment vectors --- tests/v2/commitments.test.ts | 104 +++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/v2/commitments.test.ts diff --git a/tests/v2/commitments.test.ts b/tests/v2/commitments.test.ts new file mode 100644 index 0000000..57a02bb --- /dev/null +++ b/tests/v2/commitments.test.ts @@ -0,0 +1,104 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { DOMAIN } from "../../packages/isa/src/constants.js"; +import { + coreStateCell, + inputRecordCell, + nextEpochCommitment, + outputId, + outputRecordCell, + programId, + routeRoot, + stateHash, + staticCommitmentCell, +} from "../../packages/emulator/src/commitments.js"; +import { createInitialState, type CoreConfig, type OutputRecord } from "../../packages/emulator/src/model.js"; + +const config: CoreConfig = { + runId: "11".repeat(32), + coreId: 0, + programId: "22".repeat(32), + romRoot: "33".repeat(32), + initialRamRoot: "44".repeat(32), + routeRoot: "55".repeat(32), + staticCommitment: "66".repeat(32), + routes: [{ sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 4 }], + requiredInputs: [], + maxStepsPerAdvance: 1, +}; + +test("program and static commitments use fixed-width domain-separated cells", () => { + const id = programId({ + artifactVersion: 2, + isaVersion: 2, + wordBits: 16, + romWords: 1024, + ramNibbles: 256, + entryPc: 0, + romRoot: config.romRoot, + initialRamRoot: config.initialRamRoot, + }); + assert.match(id, /^[0-9a-f]{64}$/); + const staticCell = staticCommitmentCell({ + schemaVersion: 2, + isaVersion: 2, + abiVersion: 2, + protocolVersion: 2, + runId: config.runId, + coreId: 0, + programId: id, + romRoot: config.romRoot, + routeRoot: config.routeRoot, + limitsHash: "77".repeat(32), + }); + assert.equal(staticCell.beginParse().loadUint(32), DOMAIN.static); + assert.notEqual(staticCell.hash().toString("hex"), staticCommitmentCell({ + schemaVersion: 2, + isaVersion: 2, + abiVersion: 2, + protocolVersion: 2, + runId: config.runId, + coreId: 1, + programId: id, + romRoot: config.romRoot, + routeRoot: config.routeRoot, + limitsHash: "77".repeat(32), + }).hash().toString("hex")); +}); + +test("state, record, route, and epoch commitments are deterministic", () => { + const state = createInitialState(config); + const first = stateHash(state); + assert.equal(first, stateHash(state)); + state.accumulator = 1; + assert.notEqual(first, stateHash(state)); + + const outputBase: Omit = { + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0n, + destinationEpoch: 1n, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 9, + sourceInstructionCount: 4n, + sourceStateHash: first, + }; + const record = { ...outputBase, outputId: outputId(outputBase) }; + assert.equal(outputRecordCell(record).beginParse().loadUint(32), DOMAIN.output); + assert.equal(inputRecordCell({ ...record, consumed: false }).beginParse().loadUint(32), DOMAIN.input); + assert.match(routeRoot(config.routes), /^[0-9a-f]{64}$/); + assert.match(nextEpochCommitment("00".repeat(32), 0n, first, "11".repeat(32), "22".repeat(32)), /^[0-9a-f]{64}$/); +}); + +test("the core state cell remains a bounded three-reference root", () => { + const state = createInitialState(config); + const slice = coreStateCell(state).beginParse(); + slice.loadUint(32); + slice.loadUintBig(256); + for (const width of [3, 8, 10, 4, 2, 64, 64, 64, 64, 64, 128]) { + slice.loadUint(width); + } + assert.equal(slice.remainingRefs, 3); +}); From cbfb899f0cab08a5eca3dc97582387050215dfe3 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:00:29 +0100 Subject: [PATCH 10/67] feat: bind protocol counters in v2 state commitments --- packages/emulator/src/commitments.ts | 82 ++++++++++++++++++++++++---- packages/emulator/src/model.ts | 2 + packages/isa/src/constants.ts | 2 + tests/v2/commitments.test.ts | 4 +- 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/emulator/src/commitments.ts b/packages/emulator/src/commitments.ts index 82d0930..7b826b6 100644 --- a/packages/emulator/src/commitments.ts +++ b/packages/emulator/src/commitments.ts @@ -214,6 +214,23 @@ export function nextOutputCommitment(previous: Hash256, record: OutputRecord): H ); } +export function nextLegacyOutputCommitment( + previous: Hash256, + outputIndex: bigint, + instructionCount: bigint, + value: number, +): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.output, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(outputIndex, 64) + .storeUint(instructionCount, 64) + .storeUint(value, 4) + .endCell(), + ); +} + export function nextInputCommitment(previous: Hash256, record: InputRecord): Hash256 { return hashHex( beginCell() @@ -250,10 +267,11 @@ export function nextEpochCommitment( ); } -export function outputId(record: Omit): Hash256 { +export function outputId(runId: Hash256, record: Omit): Hash256 { return hashHex( beginCell() .storeUint(DOMAIN.output, 32) + .storeUint(toBigInt(runId), 256) .storeUint(toBigInt(record.sourceStateHash), 256) .storeUint(record.sourceCoreId, 16) .storeUint(record.destinationCoreId, 16) @@ -268,6 +286,31 @@ export function outputId(record: Omit): Hash256 { ); } +export function nextAcknowledgedOutputCommitment(previous: Hash256, outputIdValue: Hash256): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.acknowledgement, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(toBigInt(outputIdValue), 256) + .endCell(), + ); +} + +export function traceCommitment( + previous: Hash256, + instructionWord: number, + nextStateHash: Hash256, +): Hash256 { + return hashHex( + beginCell() + .storeUint(DOMAIN.batch, 32) + .storeUint(toBigInt(previous), 256) + .storeUint(instructionWord, 16) + .storeUint(toBigInt(nextStateHash), 256) + .endCell(), + ); +} + export function batchCommitment(fields: { previousStateHash: Hash256; nextStateHash: Hash256; @@ -276,17 +319,22 @@ export function batchCommitment(fields: { stepsExecuted: number; outputsProduced: number; stopReason: number; + traceCommitment: Hash256; }): Hash256 { + const detail = beginCell() + .storeUint(toBigInt(fields.previousStateHash), 256) + .storeUint(toBigInt(fields.nextStateHash), 256) + .storeUint(fields.startInstructionCount, 64) + .storeUint(fields.endInstructionCount, 64) + .storeUint(fields.stepsExecuted, 16) + .storeUint(fields.outputsProduced, 8) + .storeUint(fields.stopReason, 8) + .storeUint(toBigInt(fields.traceCommitment), 256) + .endCell(); return hashHex( beginCell() .storeUint(DOMAIN.batch, 32) - .storeUint(toBigInt(fields.previousStateHash), 256) - .storeUint(toBigInt(fields.nextStateHash), 256) - .storeUint(fields.startInstructionCount, 64) - .storeUint(fields.endInstructionCount, 64) - .storeUint(fields.stepsExecuted, 16) - .storeUint(fields.outputsProduced, 8) - .storeUint(fields.stopReason, 8) + .storeRef(detail) .endCell(), ); } @@ -317,6 +365,7 @@ export function coreStateCell(state: V2State): Cell { .storeUint(state.epoch, 64) .storeUint(state.instructionCount, 64) .storeUint(state.advanceCount, 64) + .storeUint(state.acceptedMessageCount, 64) .storeUint(state.outputCount, 64) .storeUint(state.inputCount, 64) .storeUint(packSequences(state.nextOutputSequence), 128) @@ -365,7 +414,14 @@ function assertStateForHash(state: V2State): void { assertInteger(state.flags, 0, 3, "flags"); [state.config.staticCommitment, state.outputCommitment, state.inputCommitment, state.epochHistoryCommitment] .forEach((hash) => assertHash(hash, "commitment")); - if (state.epoch < 0n || state.instructionCount < 0n || state.advanceCount < 0n || state.outputCount < 0n || state.inputCount < 0n) { + if ( + state.epoch < 0n || + state.instructionCount < 0n || + state.advanceCount < 0n || + state.acceptedMessageCount < 0n || + state.outputCount < 0n || + state.inputCount < 0n + ) { throw new RangeError("state counters must be unsigned"); } } @@ -375,11 +431,15 @@ function compareRoutes(a: Route, b: Route): number { } function compareInputs(a: InputRecord, b: InputRecord): number { - return Number(a.destinationEpoch - b.destinationEpoch) || a.destinationPort - b.destinationPort || a.sequence - b.sequence; + return compareBigInt(a.destinationEpoch, b.destinationEpoch) || a.destinationPort - b.destinationPort || a.sequence - b.sequence; } function compareOutputs(a: OutputRecord, b: OutputRecord): number { - return Number(a.sourceEpoch - b.sourceEpoch) || a.sourcePort - b.sourcePort || a.sequence - b.sequence; + return compareBigInt(a.sourceEpoch, b.sourceEpoch) || a.sourcePort - b.sourcePort || a.sequence - b.sequence; +} + +function compareBigInt(a: bigint, b: bigint): number { + return a < b ? -1 : a > b ? 1 : 0; } export const EMPTY_COMMITMENT = zeroHash(); diff --git a/packages/emulator/src/model.ts b/packages/emulator/src/model.ts index 95def61..03e6d3b 100644 --- a/packages/emulator/src/model.ts +++ b/packages/emulator/src/model.ts @@ -78,6 +78,7 @@ export interface LegacyOutput { export interface V2State { readonly config: CoreConfig; advanceCount: bigint; + acceptedMessageCount: bigint; instructionCount: bigint; outputCount: bigint; inputCount: bigint; @@ -102,6 +103,7 @@ export function createInitialState(config: CoreConfig): V2State { return { config, advanceCount: 0n, + acceptedMessageCount: 0n, instructionCount: 0n, outputCount: 0n, inputCount: 0n, diff --git a/packages/isa/src/constants.ts b/packages/isa/src/constants.ts index 64e2438..fe97b2e 100644 --- a/packages/isa/src/constants.ts +++ b/packages/isa/src/constants.ts @@ -7,6 +7,8 @@ export const TONOLITH_MAGIC = 0x544e4c32 as const; export const WORD_BITS = 16 as const; export const NIBBLE_MASK = 0x0f as const; +export const FLAG_ZERO = 1 as const; +export const FLAG_CARRY = 2 as const; export const ROM_WORDS = 1024 as const; export const RAM_NIBBLES = 256 as const; export const PC_MASK = ROM_WORDS - 1; diff --git a/tests/v2/commitments.test.ts b/tests/v2/commitments.test.ts index 57a02bb..b5d0865 100644 --- a/tests/v2/commitments.test.ts +++ b/tests/v2/commitments.test.ts @@ -85,7 +85,7 @@ test("state, record, route, and epoch commitments are deterministic", () => { sourceInstructionCount: 4n, sourceStateHash: first, }; - const record = { ...outputBase, outputId: outputId(outputBase) }; + const record = { ...outputBase, outputId: outputId(config.runId, outputBase) }; assert.equal(outputRecordCell(record).beginParse().loadUint(32), DOMAIN.output); assert.equal(inputRecordCell({ ...record, consumed: false }).beginParse().loadUint(32), DOMAIN.input); assert.match(routeRoot(config.routes), /^[0-9a-f]{64}$/); @@ -97,7 +97,7 @@ test("the core state cell remains a bounded three-reference root", () => { const slice = coreStateCell(state).beginParse(); slice.loadUint(32); slice.loadUintBig(256); - for (const width of [3, 8, 10, 4, 2, 64, 64, 64, 64, 64, 128]) { + for (const width of [3, 8, 10, 4, 2, 64, 64, 64, 64, 64, 64, 128]) { slice.loadUint(width); } assert.equal(slice.remainingRefs, 3); From b4d712537fa22714681774d2a6a0f3955b89f11c Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:00:37 +0100 Subject: [PATCH 11/67] feat: implement independent v2 emulator executor --- packages/emulator/src/executor.ts | 777 ++++++++++++++++++++++++++++++ packages/emulator/src/index.ts | 1 + 2 files changed, 778 insertions(+) create mode 100644 packages/emulator/src/executor.ts diff --git a/packages/emulator/src/executor.ts b/packages/emulator/src/executor.ts new file mode 100644 index 0000000..5511c17 --- /dev/null +++ b/packages/emulator/src/executor.ts @@ -0,0 +1,777 @@ +import { + FLAG_CARRY, + FLAG_ZERO, + MAX_INPUT_RECORDS, + MAX_OUTPUT_RECORDS, + MAX_PORTS, + NIBBLE_MASK, + PC_MASK, + ROM_WORDS, + STATUS, + STOP_REASON, + SYS, + type CoreStatus, + type StopReason, +} from "../../isa/src/constants.js"; +import { + decodeInstruction, +} from "../../isa/src/codec.js"; +import { Opcode, type DecodedInstruction } from "../../isa/src/types.js"; +import { + assertHash, + assertStateBounds, + cloneState, + type Hash256, + type InputRecord, + type LegacyOutput, + type OutputRecord, + type V2State, +} from "./model.js"; +import { + batchCommitment, + nextAcknowledgedOutputCommitment, + nextEpochCommitment, + nextInputCommitment, + nextLegacyOutputCommitment, + nextOutputCommitment, + outputId, + stateHash, + traceCommitment, +} from "./commitments.js"; + +export interface V2ExecutionEnvironment { + readonly rom: readonly number[]; +} + +export interface V2AdvanceRequest { + readonly queryId?: bigint; + readonly expectedAdvanceCount: bigint; + readonly expectedStateHash: Hash256; + readonly maxInstructions: number; + readonly maxOutputs: number; +} + +export interface V2StepResult { + readonly state: V2State; + readonly executed: boolean; + readonly word: number; + readonly instruction: DecodedInstruction; + readonly output?: OutputRecord; + readonly legacyOutput?: LegacyOutput; + readonly stopReason?: StopReason; +} + +export interface V2BatchResult { + readonly state: V2State; + readonly previousStateHash: Hash256; + readonly nextStateHash: Hash256; + readonly traceCommitment: Hash256; + readonly batchCommitment: Hash256; + readonly outputs: readonly OutputRecord[]; + readonly legacyOutputs: readonly LegacyOutput[]; + readonly executed: number; + readonly stopReason: StopReason; +} + +export interface DispatchOutputRequest { + readonly queryId?: bigint; + readonly expectedStateHash: Hash256; + readonly outputId: Hash256; +} + +export interface DeliverInputMessage { + readonly queryId?: bigint; + readonly runId: Hash256; + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly sourceEpoch: bigint; + readonly destinationEpoch: bigint; + readonly sourcePort: number; + readonly destinationPort: number; + readonly sequence: number; + readonly value: number; + readonly sourceInstructionCount: bigint; + readonly sourceStateHash: Hash256; +} + +export interface InputAcknowledgement { + readonly queryId?: bigint; + readonly runId: Hash256; + readonly outputId: Hash256; + readonly destinationCoreId: number; + readonly destinationEpoch: bigint; + readonly destinationStateHash: Hash256; +} + +export interface DispatchResult { + readonly state: V2State; + readonly message: DeliverInputMessage; +} + +export interface DeliveryResult { + readonly state: V2State; + readonly acknowledgement: InputAcknowledgement; + readonly duplicate: boolean; +} + +export interface AcknowledgementResult { + readonly state: V2State; + readonly duplicate: boolean; +} + +export class V2ExecutionError extends Error { + public readonly code: string; + + public constructor(code: string, message: string) { + super(message); + this.name = "V2ExecutionError"; + this.code = code; + } +} + +/** Execute one canonical instruction without invoking the contract or Acton. */ +export function step(state: V2State, environment: V2ExecutionEnvironment): V2StepResult { + assertStateBounds(state); + if (state.status !== STATUS.running) { + throw new V2ExecutionError("INVALID_STATUS", "step requires a RUNNING core"); + } + if (environment.rom.length > ROM_WORDS) { + throw new V2ExecutionError("INVALID_ROM", `ROM contains more than ${ROM_WORDS} words`); + } + const word = environment.rom[state.pc]; + if (word === undefined) { + throw new V2ExecutionError("INVALID_PC", `PC ${state.pc} is outside the supplied ROM`); + } + + let instruction: DecodedInstruction; + try { + instruction = decodeInstruction(word); + } catch (error) { + throw new V2ExecutionError( + "INVALID_INSTRUCTION", + error instanceof Error ? error.message : String(error), + ); + } + + const beforeStateHash = stateHash(state); + const next = cloneState(state); + next.pc = (next.pc + 1) & PC_MASK; + let output: OutputRecord | undefined; + let legacyOutput: LegacyOutput | undefined; + let stopReason: StopReason | undefined; + let closesEpoch = false; + + switch (instruction.opcode) { + case Opcode.NOP: + break; + case Opcode.LDI: + next.accumulator = instruction.operand; + next.flags = setZeroFlag(next.flags, next.accumulator); + break; + case Opcode.LDR: + next.accumulator = readRegister(next, instruction.operand); + next.flags = setZeroFlag(next.flags, next.accumulator); + break; + case Opcode.STR: + writeRegister(next, instruction.operand, next.accumulator); + break; + case Opcode.ADD: + add(next, readRegister(next, instruction.operand), false); + break; + case Opcode.ADC: + add(next, readRegister(next, instruction.operand), hasFlag(next.flags, FLAG_CARRY)); + break; + case Opcode.SUB: + subtract(next, readRegister(next, instruction.operand)); + break; + case Opcode.AND: + logical(next, next.accumulator & readRegister(next, instruction.operand)); + break; + case Opcode.OR: + logical(next, next.accumulator | readRegister(next, instruction.operand)); + break; + case Opcode.XOR: + logical(next, next.accumulator ^ readRegister(next, instruction.operand)); + break; + case Opcode.LDM: + next.accumulator = readRam(next, instruction.operand); + next.flags = setZeroFlag(next.flags, next.accumulator); + break; + case Opcode.STM: + writeRam(next, instruction.operand, next.accumulator); + break; + case Opcode.JMP: + next.pc = instruction.operand; + break; + case Opcode.JZ: + if (hasFlag(next.flags, FLAG_ZERO)) next.pc = instruction.operand; + break; + case Opcode.JC: + if (hasFlag(next.flags, FLAG_CARRY)) next.pc = instruction.operand; + break; + case Opcode.SYS: { + const sysResult = executeSys(next, state, instruction, beforeStateHash); + if (sysResult.blocked) { + next.pc = state.pc; + next.status = sysResult.status; + const blockedResult = { + state: next, + executed: false, + word, + instruction, + }; + return sysResult.stopReason === undefined + ? blockedResult + : { ...blockedResult, stopReason: sysResult.stopReason }; + } + output = sysResult.output; + legacyOutput = sysResult.legacyOutput; + stopReason = sysResult.stopReason; + closesEpoch = sysResult.closesEpoch; + break; + } + default: + throw new V2ExecutionError("INVALID_INSTRUCTION", `unsupported opcode ${instruction.opcode}`); + } + + next.instructionCount += 1n; + if (output !== undefined) { + next.outputCount += 1n; + next.outputCommitment = nextOutputCommitment(next.outputCommitment, output); + } + if (legacyOutput !== undefined) { + next.outputCount += 1n; + next.outputCommitment = nextLegacyOutputCommitment( + next.outputCommitment, + legacyOutput.outputIndex, + legacyOutput.instructionCount, + legacyOutput.value, + ); + legacyOutput = { + ...legacyOutput, + instructionCount: next.instructionCount, + outputCommitment: next.outputCommitment, + }; + } + + if (closesEpoch) { + const closedEpoch = next.epoch; + const closedStateHash = stateHash(next); + next.epochHistoryCommitment = nextEpochCommitment( + next.epochHistoryCommitment, + closedEpoch, + closedStateHash, + next.outputCommitment, + next.inputCommitment, + ); + next.epoch += 1n; + next.status = isEpochReady(next, next.epoch) ? STATUS.running : STATUS.waitingInput; + } + + assertStateBounds(next); + const result: V2StepResult = { + state: next, + executed: true, + word, + instruction, + ...(output === undefined ? {} : { output }), + ...(legacyOutput === undefined ? {} : { legacyOutput }), + ...(stopReason === undefined ? {} : { stopReason }), + }; + return result; +} + +/** Apply an authenticated bounded AdvanceV2 request to a cloned architectural state. */ +export function advance( + state: V2State, + environment: V2ExecutionEnvironment, + request: V2AdvanceRequest, +): V2BatchResult { + assertStateBounds(state); + assertHash(request.expectedStateHash, "expectedStateHash"); + if (request.expectedAdvanceCount !== state.advanceCount) { + throw new V2ExecutionError("STALE_ADVANCE", "expected advance count does not match state"); + } + const previousStateHash = stateHash(state); + if (request.expectedStateHash.toLowerCase() !== previousStateHash) { + throw new V2ExecutionError("STATE_HASH_MISMATCH", "expected state hash does not match state"); + } + if (state.status === STATUS.halted || state.status === STATUS.faulted) { + throw new V2ExecutionError("INVALID_STATUS", "a halted or faulted core cannot advance"); + } + assertStepLimit(request.maxInstructions, state.config.maxStepsPerAdvance); + assertOutputLimit(request.maxOutputs, state.config.maxOutputRecords ?? MAX_OUTPUT_RECORDS); + + let current = cloneState(state); + if (current.status === STATUS.waitingInput || current.status === STATUS.backpressured) { + current.status = STATUS.running; + } + const outputs: OutputRecord[] = []; + const legacyOutputs: LegacyOutput[] = []; + let trace = previousStateHash; + let executed = 0; + let stopReason: StopReason = STOP_REASON.requestLimit; + + while (executed < request.maxInstructions) { + if (current.status !== STATUS.running) { + stopReason = current.status === STATUS.halted ? STOP_REASON.halted : STOP_REASON.faulted; + break; + } + const result = step(current, environment); + current = result.state; + if (!result.executed) { + stopReason = result.stopReason ?? STOP_REASON.requestLimit; + break; + } + executed += 1; + trace = traceCommitment(trace, result.word, stateHash(current)); + if (result.output !== undefined) outputs.push(result.output); + if (result.legacyOutput !== undefined) legacyOutputs.push(result.legacyOutput); + const produced = outputs.length + legacyOutputs.length; + if (produced >= request.maxOutputs) { + stopReason = STOP_REASON.outputEventLimit; + break; + } + if (result.stopReason !== undefined) { + stopReason = result.stopReason; + break; + } + } + + current.advanceCount += 1n; + current.acceptedMessageCount += 1n; + assertStateBounds(current); + const nextStateHash = stateHash(current); + const acceptedBatch = batchCommitment({ + previousStateHash, + nextStateHash, + startInstructionCount: state.instructionCount, + endInstructionCount: current.instructionCount, + stepsExecuted: executed, + outputsProduced: outputs.length + legacyOutputs.length, + stopReason, + traceCommitment: trace, + }); + return { + state: current, + previousStateHash, + nextStateHash, + traceCommitment: trace, + batchCommitment: acceptedBatch, + outputs, + legacyOutputs, + executed, + stopReason, + }; +} + +/** Select a pending output without mutating architectural state. */ +export function dispatchOutput(state: V2State, request: DispatchOutputRequest): DispatchResult { + assertHash(request.expectedStateHash, "expectedStateHash"); + const currentHash = stateHash(state); + if (request.expectedStateHash.toLowerCase() !== currentHash) { + throw new V2ExecutionError("STATE_HASH_MISMATCH", "expected state hash does not match state"); + } + const record = state.outbox.find((candidate) => candidate.outputId === request.outputId.toLowerCase()); + if (record === undefined) { + throw new V2ExecutionError("NOT_FOUND", "pending output was not found"); + } + const message: DeliverInputMessage = { + runId: state.config.runId, + outputId: record.outputId, + sourceCoreId: record.sourceCoreId, + destinationCoreId: record.destinationCoreId, + sourceEpoch: record.sourceEpoch, + destinationEpoch: record.destinationEpoch, + sourcePort: record.sourcePort, + destinationPort: record.destinationPort, + sequence: record.sequence, + value: record.value, + sourceInstructionCount: record.sourceInstructionCount, + sourceStateHash: record.sourceStateHash, + }; + if (request.queryId !== undefined) { + (message as { queryId: bigint }).queryId = request.queryId; + } + return { state: cloneState(state), message }; +} + +/** Deliver one output to the destination's bounded, deduplicating inbox. */ +export function deliverInput( + state: V2State, + message: DeliverInputMessage, + authenticatedSourceCoreId = message.sourceCoreId, +): DeliveryResult { + validateDeliveryEnvelope(state, message, authenticatedSourceCoreId); + const existing = state.inbox.find((record) => record.outputId === message.outputId.toLowerCase()); + if (existing !== undefined) { + if (!sameInput(existing, message)) { + throw new V2ExecutionError("CONFLICTING_REPLAY", "output identifier is bound to different input data"); + } + return { + state: cloneState(state), + acknowledgement: makeAcknowledgement(state, message), + duplicate: true, + }; + } + + const slotConflict = state.inbox.find( + (record) => + record.destinationEpoch === message.destinationEpoch && + record.destinationPort === message.destinationPort && + record.sequence === message.sequence, + ); + if (slotConflict !== undefined) { + throw new V2ExecutionError("CONFLICTING_REPLAY", "input slot is already occupied by another output"); + } + if (state.inbox.length >= (state.config.maxInputRecords ?? MAX_INPUT_RECORDS)) { + throw new V2ExecutionError("QUEUE_FULL", "input queue is full"); + } + + const next = cloneState(state); + const input: InputRecord = { + ...message, + outputId: message.outputId.toLowerCase(), + consumed: false, + }; + next.inbox.push(input); + next.inbox.sort(compareInputs); + next.inputCount += 1n; + next.acceptedMessageCount += 1n; + next.inputCommitment = nextInputCommitment(next.inputCommitment, input); + if (next.status === STATUS.waitingInput && hasAvailableInput(next, next.epoch)) { + next.status = STATUS.running; + } + assertStateBounds(next); + return { + state: next, + acknowledgement: makeAcknowledgement(next, message), + duplicate: false, + }; +} + +/** Consume an acknowledgement and retire exactly one pending output. */ +export function acknowledgeOutput( + state: V2State, + acknowledgement: InputAcknowledgement, + authenticatedDestinationCoreId = acknowledgement.destinationCoreId, +): AcknowledgementResult { + if (acknowledgement.runId.toLowerCase() !== state.config.runId.toLowerCase()) { + throw new V2ExecutionError("INVALID_RUN", "acknowledgement run ID does not match state"); + } + if (authenticatedDestinationCoreId !== acknowledgement.destinationCoreId) { + throw new V2ExecutionError("UNAUTHORIZED_PEER", "acknowledgement sender is not the destination core"); + } + const index = state.outbox.findIndex((record) => record.outputId === acknowledgement.outputId.toLowerCase()); + if (index < 0) return { state: cloneState(state), duplicate: true }; + const record = state.outbox[index]; + if (record === undefined || record.destinationCoreId !== acknowledgement.destinationCoreId || record.destinationEpoch !== acknowledgement.destinationEpoch) { + throw new V2ExecutionError("UNAUTHORIZED_PEER", "acknowledgement does not match pending output"); + } + const next = cloneState(state); + next.outbox.splice(index, 1); + next.acceptedMessageCount += 1n; + next.outputCommitment = nextAcknowledgedOutputCommitment(next.outputCommitment, record.outputId); + if (next.status === STATUS.backpressured && next.outbox.length < (next.config.maxOutputRecords ?? MAX_OUTPUT_RECORDS)) { + next.status = STATUS.running; + } + assertStateBounds(next); + return { state: next, duplicate: false }; +} + +function executeSys( + next: V2State, + before: V2State, + instruction: DecodedInstruction, + beforeStateHash: Hash256, +): { + blocked: boolean; + status: CoreStatus; + stopReason?: StopReason; + output?: OutputRecord; + legacyOutput?: LegacyOutput; + closesEpoch: boolean; +} { + const subop = instruction.sysSubop; + if (subop === undefined) throw new V2ExecutionError("INVALID_INSTRUCTION", "SYS instruction has no suboperation"); + switch (subop) { + case SYS.HALT: + next.status = STATUS.halted; + return { blocked: false, status: next.status, stopReason: STOP_REASON.halted, closesEpoch: false }; + case SYS.OUT: { + const legacyOutput: LegacyOutput = { + outputIndex: next.outputCount, + instructionCount: before.instructionCount + 1n, + value: next.accumulator, + outputCommitment: next.outputCommitment, + }; + return { blocked: false, status: next.status, legacyOutput, closesEpoch: false }; + } + case SYS.CLC: + next.flags &= ~FLAG_CARRY; + return { blocked: false, status: next.status, closesEpoch: false }; + case SYS.STC: + next.flags |= FLAG_CARRY; + return { blocked: false, status: next.status, closesEpoch: false }; + case SYS.NOT: + logical(next, (~next.accumulator) & NIBBLE_MASK); + return { blocked: false, status: next.status, closesEpoch: false }; + case SYS.SHL: { + const previous = next.accumulator; + next.flags = previous & 0x8 ? next.flags | FLAG_CARRY : next.flags & ~FLAG_CARRY; + next.accumulator = (previous << 1) & NIBBLE_MASK; + next.flags = setZeroFlag(next.flags, next.accumulator); + return { blocked: false, status: next.status, closesEpoch: false }; + } + case SYS.SHR: { + const previous = next.accumulator; + next.flags = previous & 0x1 ? next.flags | FLAG_CARRY : next.flags & ~FLAG_CARRY; + next.accumulator = previous >>> 1; + next.flags = setZeroFlag(next.flags, next.accumulator); + return { blocked: false, status: next.status, closesEpoch: false }; + } + case SYS.INC: { + const previous = next.accumulator; + next.accumulator = (previous + 1) & NIBBLE_MASK; + next.flags = previous === NIBBLE_MASK ? next.flags | FLAG_CARRY : next.flags & ~FLAG_CARRY; + next.flags = setZeroFlag(next.flags, next.accumulator); + return { blocked: false, status: next.status, closesEpoch: false }; + } + case SYS.DEC: { + const previous = next.accumulator; + next.accumulator = (previous - 1) & NIBBLE_MASK; + next.flags = previous !== 0 ? next.flags | FLAG_CARRY : next.flags & ~FLAG_CARRY; + next.flags = setZeroFlag(next.flags, next.accumulator); + return { blocked: false, status: next.status, closesEpoch: false }; + } + case SYS.IN: { + const port = instruction.port; + if (port === undefined || port >= MAX_PORTS) throw new V2ExecutionError("INVALID_INSTRUCTION", "IN port is invalid"); + const candidate = next.inbox + .filter((record) => !record.consumed && record.destinationEpoch === next.epoch && record.destinationPort === port) + .sort(compareInputs)[0]; + if (candidate === undefined) { + return { + blocked: true, + status: STATUS.waitingInput, + stopReason: STOP_REASON.inputUnavailable, + closesEpoch: false, + }; + } + const index = next.inbox.findIndex((record) => record.outputId === candidate.outputId); + const consumed = next.inbox[index]; + if (index < 0 || consumed === undefined) throw new V2ExecutionError("ARCHITECTURAL_INVARIANT", "input cursor disappeared"); + const updated: InputRecord = { ...consumed, consumed: true }; + next.inbox[index] = updated; + next.inbox.sort(compareInputs); + next.accumulator = updated.value; + next.flags = setZeroFlag(next.flags, next.accumulator); + next.inputCommitment = nextInputCommitment(next.inputCommitment, updated); + return { blocked: false, status: next.status, closesEpoch: false }; + } + case SYS.OUTP: { + const port = instruction.port; + if (port === undefined || port >= MAX_PORTS) throw new V2ExecutionError("INVALID_INSTRUCTION", "OUTP port is invalid"); + const limit = next.config.maxOutputRecords ?? MAX_OUTPUT_RECORDS; + if (next.outbox.length >= limit) { + return { + blocked: true, + status: STATUS.backpressured, + stopReason: STOP_REASON.outputBackpressure, + closesEpoch: false, + }; + } + const route = next.config.routes.find((candidate) => candidate.sourceCoreId === next.config.coreId && candidate.sourcePort === port); + if (route === undefined) throw new V2ExecutionError("INVALID_ROUTE", `no route configured for output port ${port}`); + const sequence = next.nextOutputSequence[port]; + if (sequence === undefined || sequence >= 0xff) throw new V2ExecutionError("INVALID_SEQUENCE", "output sequence exhausted"); + const recordBase: Omit = { + sourceCoreId: next.config.coreId, + destinationCoreId: route.destinationCoreId, + sourceEpoch: next.epoch, + destinationEpoch: next.epoch + BigInt(route.delay), + sourcePort: port, + destinationPort: route.destinationPort, + sequence, + value: next.accumulator, + sourceInstructionCount: before.instructionCount + 1n, + sourceStateHash: beforeStateHash, + }; + const record: OutputRecord = { + ...recordBase, + outputId: outputId(next.config.runId, recordBase), + }; + next.outbox.push(record); + next.nextOutputSequence[port] = sequence + 1; + return { blocked: false, status: next.status, output: record, closesEpoch: false }; + } + case SYS.YIELD: + return { blocked: false, status: next.status, stopReason: STOP_REASON.yielded, closesEpoch: true }; + case SYS.TRAP: + next.status = STATUS.faulted; + next.faultCode = instruction.trapCode ?? 0; + return { blocked: false, status: next.status, stopReason: STOP_REASON.faulted, closesEpoch: false }; + default: + throw new V2ExecutionError("INVALID_INSTRUCTION", `unsupported SYS suboperation ${String(subop)}`); + } +} + +function validateDeliveryEnvelope(state: V2State, message: DeliverInputMessage, authenticatedSourceCoreId: number): void { + if (message.runId.toLowerCase() !== state.config.runId.toLowerCase()) { + throw new V2ExecutionError("INVALID_RUN", "input run ID does not match state"); + } + if (authenticatedSourceCoreId !== message.sourceCoreId) { + throw new V2ExecutionError("UNAUTHORIZED_PEER", "input sender is not the claimed source core"); + } + if (message.destinationCoreId !== state.config.coreId) { + throw new V2ExecutionError("UNAUTHORIZED_PEER", "input is addressed to another core"); + } + if (message.destinationEpoch < state.epoch || message.destinationEpoch > state.epoch + 1n) { + throw new V2ExecutionError("INVALID_EPOCH", "input is outside the two-epoch acceptance window"); + } + if (message.sourcePort < 0 || message.sourcePort >= MAX_PORTS || message.destinationPort < 0 || message.destinationPort >= MAX_PORTS) { + throw new V2ExecutionError("INVALID_ROUTE", "input port is outside the v2 range"); + } + if (message.value < 0 || message.value > NIBBLE_MASK || message.sequence < 0 || message.sequence > 0xff) { + throw new V2ExecutionError("INVALID_SEQUENCE", "input payload or sequence is outside the v2 range"); + } + const route = state.config.routes.find( + (candidate) => + candidate.sourceCoreId === message.sourceCoreId && + candidate.sourcePort === message.sourcePort && + candidate.destinationCoreId === message.destinationCoreId && + candidate.destinationPort === message.destinationPort, + ); + if (route === undefined || message.destinationEpoch !== message.sourceEpoch + BigInt(route.delay) || message.sequence >= route.maxRecordsPerEpoch) { + throw new V2ExecutionError("INVALID_ROUTE", "input does not match the immutable route table"); + } + const expectedId = outputId(state.config.runId, { + sourceCoreId: message.sourceCoreId, + destinationCoreId: message.destinationCoreId, + sourceEpoch: message.sourceEpoch, + destinationEpoch: message.destinationEpoch, + sourcePort: message.sourcePort, + destinationPort: message.destinationPort, + sequence: message.sequence, + value: message.value, + sourceInstructionCount: message.sourceInstructionCount, + sourceStateHash: message.sourceStateHash, + }); + if (expectedId !== message.outputId.toLowerCase()) { + throw new V2ExecutionError("INVALID_ROUTE", "input output identifier does not match its canonical fields"); + } +} + +function makeAcknowledgement(state: V2State, message: DeliverInputMessage): InputAcknowledgement { + const acknowledgement: InputAcknowledgement = { + runId: state.config.runId, + outputId: message.outputId.toLowerCase(), + destinationCoreId: state.config.coreId, + destinationEpoch: message.destinationEpoch, + destinationStateHash: stateHash(state), + }; + if (message.queryId !== undefined) { + (acknowledgement as { queryId: bigint }).queryId = message.queryId; + } + return acknowledgement; +} + +function sameInput(record: InputRecord, message: DeliverInputMessage): boolean { + return ( + record.sourceCoreId === message.sourceCoreId && + record.destinationCoreId === message.destinationCoreId && + record.sourceEpoch === message.sourceEpoch && + record.destinationEpoch === message.destinationEpoch && + record.sourcePort === message.sourcePort && + record.destinationPort === message.destinationPort && + record.sequence === message.sequence && + record.value === message.value && + record.sourceInstructionCount === message.sourceInstructionCount && + record.sourceStateHash === message.sourceStateHash + ); +} + +function assertStepLimit(value: number, configuredMaximum: number): void { + if (!Number.isInteger(value) || value < 1 || value > configuredMaximum || value > 0xffff) { + throw new V2ExecutionError("INVALID_BODY", "requested instruction batch is outside the configured bound"); + } +} + +function assertOutputLimit(value: number, configuredMaximum: number): void { + if (!Number.isInteger(value) || value < 1 || value > configuredMaximum || value > MAX_OUTPUT_RECORDS) { + throw new V2ExecutionError("INVALID_BODY", "requested output event bound is outside the configured bound"); + } +} + +function hasAvailableInput(state: V2State, epoch: bigint): boolean { + return state.inbox.some((record) => !record.consumed && record.destinationEpoch === epoch); +} + +function isEpochReady(state: V2State, epoch: bigint): boolean { + return state.config.requiredInputs.every((quota) => { + const available = state.inbox.filter( + (record) => !record.consumed && record.destinationEpoch === epoch && record.destinationPort === quota.port, + ).length; + return available >= quota.count; + }); +} + +function compareInputs(a: InputRecord, b: InputRecord): number { + return compareBigInt(a.destinationEpoch, b.destinationEpoch) || a.destinationPort - b.destinationPort || a.sequence - b.sequence; +} + +function compareBigInt(a: bigint, b: bigint): number { + return a < b ? -1 : a > b ? 1 : 0; +} + +function readRegister(state: V2State, index: number): number { + const value = state.registers[index]; + if (value === undefined) throw new V2ExecutionError("ARCHITECTURAL_INVARIANT", `register ${index} is unavailable`); + return value; +} + +function writeRegister(state: V2State, index: number, value: number): void { + if (state.registers[index] === undefined) throw new V2ExecutionError("ARCHITECTURAL_INVARIANT", `register ${index} is unavailable`); + state.registers[index] = value & NIBBLE_MASK; +} + +function readRam(state: V2State, address: number): number { + const value = state.ram[address]; + if (value === undefined) throw new V2ExecutionError("ARCHITECTURAL_INVARIANT", `RAM address ${address} is unavailable`); + return value; +} + +function writeRam(state: V2State, address: number, value: number): void { + if (state.ram[address] === undefined) throw new V2ExecutionError("ARCHITECTURAL_INVARIANT", `RAM address ${address} is unavailable`); + state.ram[address] = value & NIBBLE_MASK; +} + +function add(state: V2State, operand: number, includeCarry: boolean): void { + const carry = includeCarry && hasFlag(state.flags, FLAG_CARRY) ? 1 : 0; + const sum = state.accumulator + operand + carry; + state.accumulator = sum & NIBBLE_MASK; + state.flags = sum >= 16 ? state.flags | FLAG_CARRY : state.flags & ~FLAG_CARRY; + state.flags = setZeroFlag(state.flags, state.accumulator); +} + +function subtract(state: V2State, operand: number): void { + const before = state.accumulator; + state.accumulator = (before - operand) & NIBBLE_MASK; + state.flags = before >= operand ? state.flags | FLAG_CARRY : state.flags & ~FLAG_CARRY; + state.flags = setZeroFlag(state.flags, state.accumulator); +} + +function logical(state: V2State, value: number): void { + state.accumulator = value & NIBBLE_MASK; + state.flags &= ~FLAG_CARRY; + state.flags = setZeroFlag(state.flags, state.accumulator); +} + +function setZeroFlag(flags: number, value: number): number { + return value === 0 ? flags | FLAG_ZERO : flags & ~FLAG_ZERO; +} + +function hasFlag(flags: number, flag: number): boolean { + return (flags & flag) !== 0; +} diff --git a/packages/emulator/src/index.ts b/packages/emulator/src/index.ts index 322d935..bb240c1 100644 --- a/packages/emulator/src/index.ts +++ b/packages/emulator/src/index.ts @@ -1,2 +1,3 @@ export * from "./model.js"; export * from "./commitments.js"; +export * from "./executor.js"; From 95b41ba341f81eb30bd4518a7423fbf95f6b61c3 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:00:48 +0100 Subject: [PATCH 12/67] test: exercise v2 emulator protocol transitions --- tests/v2/emulator.test.ts | 280 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 tests/v2/emulator.test.ts diff --git a/tests/v2/emulator.test.ts b/tests/v2/emulator.test.ts new file mode 100644 index 0000000..d7d9aa2 --- /dev/null +++ b/tests/v2/emulator.test.ts @@ -0,0 +1,280 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { assemble } from "../../tools/assembler/assembler.js"; +import { encodeInstruction, encodePortInstruction, encodeTrap } from "../../packages/isa/src/codec.js"; +import { Opcode } from "../../packages/isa/src/types.js"; +import { STATUS, STOP_REASON, SYS } from "../../packages/isa/src/constants.js"; +import { + acknowledgeOutput, + advance, + deliverInput, + dispatchOutput, + step, + type DeliverInputMessage, +} from "../../packages/emulator/src/executor.js"; +import { outputId, stateHash } from "../../packages/emulator/src/commitments.js"; +import { createInitialState, type CoreConfig, type OutputRecord } from "../../packages/emulator/src/model.js"; + +const RUN_ID = "11".repeat(32); + +function config(overrides: Partial = {}): CoreConfig { + return { + runId: RUN_ID, + coreId: 0, + programId: "22".repeat(32), + romRoot: "33".repeat(32), + initialRamRoot: "44".repeat(32), + routeRoot: "55".repeat(32), + staticCommitment: "66".repeat(32), + routes: [], + requiredInputs: [], + maxStepsPerAdvance: 1, + ...overrides, + }; +} + +function rom(...words: number[]): number[] { + return [...words, ...Array.from({ length: 1024 - words.length }, () => 0)]; +} + +function inputMessage(overrides: Partial = {}): DeliverInputMessage { + const fields = { + runId: RUN_ID, + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: 0n, + destinationEpoch: 1n, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 7, + sourceInstructionCount: 1n, + sourceStateHash: "aa".repeat(32), + ...overrides, + }; + return { + ...fields, + outputId: outputId(fields.runId, fields), + }; +} + +test("v2 executes every inherited opcode with v1 arithmetic and flag semantics", () => { + const cases: Array<{ name: string; word: number; prepare?: (state: ReturnType) => void; check: (state: ReturnType) => void }> = [ + { name: "NOP", word: encodeInstruction(Opcode.NOP), check: (state) => assert.equal(state.pc, 1) }, + { name: "LDI", word: encodeInstruction(Opcode.LDI, 9), check: (state) => assert.equal(state.accumulator, 9) }, + { name: "LDR", word: encodeInstruction(Opcode.LDR, 2), prepare: (state) => { state.registers[2] = 8; }, check: (state) => assert.equal(state.accumulator, 8) }, + { name: "STR", word: encodeInstruction(Opcode.STR, 2), prepare: (state) => { state.accumulator = 8; }, check: (state) => assert.equal(state.registers[2], 8) }, + { name: "ADD", word: encodeInstruction(Opcode.ADD, 2), prepare: (state) => { state.accumulator = 8; state.registers[2] = 9; }, check: (state) => { assert.equal(state.accumulator, 1); assert.equal(state.flags & 2, 2); } }, + { name: "ADC", word: encodeInstruction(Opcode.ADC, 2), prepare: (state) => { state.accumulator = 8; state.registers[2] = 7; state.flags = 2; }, check: (state) => { assert.equal(state.accumulator, 0); assert.equal(state.flags & 3, 3); } }, + { name: "SUB", word: encodeInstruction(Opcode.SUB, 2), prepare: (state) => { state.accumulator = 2; state.registers[2] = 3; }, check: (state) => { assert.equal(state.accumulator, 15); assert.equal(state.flags & 2, 0); } }, + { name: "AND", word: encodeInstruction(Opcode.AND, 2), prepare: (state) => { state.accumulator = 0xc; state.registers[2] = 0xa; }, check: (state) => assert.equal(state.accumulator, 8) }, + { name: "OR", word: encodeInstruction(Opcode.OR, 2), prepare: (state) => { state.accumulator = 0xc; state.registers[2] = 0x3; }, check: (state) => assert.equal(state.accumulator, 15) }, + { name: "XOR", word: encodeInstruction(Opcode.XOR, 2), prepare: (state) => { state.accumulator = 0xc; state.registers[2] = 0xa; }, check: (state) => assert.equal(state.accumulator, 6) }, + { name: "LDM", word: encodeInstruction(Opcode.LDM, 7), prepare: (state) => { state.ram[7] = 6; }, check: (state) => assert.equal(state.accumulator, 6) }, + { name: "STM", word: encodeInstruction(Opcode.STM, 7), prepare: (state) => { state.accumulator = 6; }, check: (state) => assert.equal(state.ram[7], 6) }, + { name: "JMP", word: encodeInstruction(Opcode.JMP, 8), check: (state) => assert.equal(state.pc, 8) }, + { name: "JZ", word: encodeInstruction(Opcode.JZ, 8), prepare: (state) => { state.flags = 1; }, check: (state) => assert.equal(state.pc, 8) }, + { name: "JC", word: encodeInstruction(Opcode.JC, 8), prepare: (state) => { state.flags = 2; }, check: (state) => assert.equal(state.pc, 8) }, + { name: "CLC", word: encodeInstruction(Opcode.SYS, SYS.CLC), prepare: (state) => { state.flags = 2; }, check: (state) => assert.equal(state.flags & 2, 0) }, + { name: "STC", word: encodeInstruction(Opcode.SYS, SYS.STC), check: (state) => assert.equal(state.flags & 2, 2) }, + { name: "NOT", word: encodeInstruction(Opcode.SYS, SYS.NOT), prepare: (state) => { state.accumulator = 5; }, check: (state) => assert.equal(state.accumulator, 10) }, + { name: "SHL", word: encodeInstruction(Opcode.SYS, SYS.SHL), prepare: (state) => { state.accumulator = 9; }, check: (state) => { assert.equal(state.accumulator, 2); assert.equal(state.flags & 2, 2); } }, + { name: "SHR", word: encodeInstruction(Opcode.SYS, SYS.SHR), prepare: (state) => { state.accumulator = 9; }, check: (state) => { assert.equal(state.accumulator, 4); assert.equal(state.flags & 2, 2); } }, + { name: "INC", word: encodeInstruction(Opcode.SYS, SYS.INC), prepare: (state) => { state.accumulator = 15; }, check: (state) => { assert.equal(state.accumulator, 0); assert.equal(state.flags & 3, 3); } }, + { name: "DEC", word: encodeInstruction(Opcode.SYS, SYS.DEC), prepare: (state) => { state.accumulator = 0; }, check: (state) => { assert.equal(state.accumulator, 15); assert.equal(state.flags & 2, 0); } }, + ]; + + for (const testCase of cases) { + const state = createInitialState(config()); + testCase.prepare?.(state); + const result = step(state, { rom: rom(testCase.word) }); + assert.equal(result.executed, true, testCase.name); + assert.equal(result.state.instructionCount, 1n, testCase.name); + testCase.check(result.state); + } +}); + +test("IN waits without consuming a step and then consumes the lowest ordered input", () => { + const destination = createInitialState(config({ + routes: [{ sourceCoreId: 1, sourcePort: 0, destinationCoreId: 0, destinationPort: 3, delay: 1, maxRecordsPerEpoch: 4 }], + })); + destination.epoch = 1n; + const waiting = step(destination, { rom: rom(encodePortInstruction(SYS.IN, 3)) }); + assert.equal(waiting.executed, false); + assert.equal(waiting.stopReason, STOP_REASON.inputUnavailable); + assert.equal(waiting.state.status, STATUS.waitingInput); + assert.equal(waiting.state.pc, 0); + assert.equal(waiting.state.instructionCount, 0n); + + const delivered = deliverInput(waiting.state, inputMessage({ destinationEpoch: 1n, destinationPort: 3, value: 12 })); + assert.equal(delivered.duplicate, false); + const consumed = step(delivered.state, { rom: rom(encodePortInstruction(SYS.IN, 3)) }); + assert.equal(consumed.executed, true); + assert.equal(consumed.state.accumulator, 12); + assert.equal(consumed.state.inbox[0]?.consumed, true); + assert.equal(consumed.state.instructionCount, 1n); +}); + +test("OUTP creates a routed pending record and backpressure leaves PC untouched", () => { + const state = createInitialState(config({ + routes: [{ sourceCoreId: 0, sourcePort: 2, destinationCoreId: 1, destinationPort: 4, delay: 1, maxRecordsPerEpoch: 4 }], + maxOutputRecords: 1, + })); + state.accumulator = 11; + const produced = step(state, { rom: rom(encodePortInstruction(SYS.OUTP, 2)) }); + assert.equal(produced.executed, true); + assert.equal(produced.output?.value, 11); + assert.equal(produced.output?.destinationEpoch, 1n); + assert.equal(produced.state.outbox.length, 1); + + const full = produced.state; + full.pc = 0; + const blocked = step(full, { rom: rom(encodePortInstruction(SYS.OUTP, 2)) }); + assert.equal(blocked.executed, false); + assert.equal(blocked.stopReason, STOP_REASON.outputBackpressure); + assert.equal(blocked.state.status, STATUS.backpressured); + assert.equal(blocked.state.pc, 0); + assert.equal(blocked.state.instructionCount, 1n); +}); + +test("YIELD closes an epoch, TRAP is architectural, and acknowledgement wakes backpressure", () => { + const yieldedState = createInitialState(config({ + requiredInputs: [{ port: 1, count: 1 }], + routes: [{ sourceCoreId: 1, sourcePort: 0, destinationCoreId: 0, destinationPort: 1, delay: 1, maxRecordsPerEpoch: 4 }], + })); + const yielded = step(yieldedState, { rom: rom(encodeInstruction(Opcode.SYS, SYS.YIELD)) }); + assert.equal(yielded.executed, true); + assert.equal(yielded.stopReason, STOP_REASON.yielded); + assert.equal(yielded.state.epoch, 1n); + assert.equal(yielded.state.status, STATUS.waitingInput); + const awakened = deliverInput(yielded.state, inputMessage({ destinationEpoch: 1n, destinationPort: 1 })); + assert.equal(awakened.state.status, STATUS.running); + + const trapped = step(createInitialState(config()), { rom: rom(encodeTrap(0xab)) }); + assert.equal(trapped.state.status, STATUS.faulted); + assert.equal(trapped.state.faultCode, 0xab); + assert.equal(trapped.stopReason, STOP_REASON.faulted); + + const routed = createInitialState(config({ + routes: [{ sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 4 }], + })); + const output = step(routed, { rom: rom(encodePortInstruction(SYS.OUTP, 0)) }).output; + assert.ok(output); + const full = { ...routed, outbox: [output], pc: 0, status: STATUS.backpressured }; + const acknowledged = acknowledgeOutput(full, { + runId: RUN_ID, + outputId: output.outputId, + destinationCoreId: 1, + destinationEpoch: output.destinationEpoch, + destinationStateHash: "bb".repeat(32), + }); + assert.equal(acknowledged.duplicate, false); + assert.equal(acknowledged.state.outbox.length, 0); + assert.equal(acknowledged.state.status, STATUS.running); +}); + +test("batched execution is equivalent to repeated single steps except protocol counters", () => { + const words = assemble("LDI 1\nINC\nOUT\nHALT\n").words; + const initial = createInitialState(config({ maxStepsPerAdvance: 4 })); + const batched = advance(initial, { rom: rom(...words) }, { + expectedAdvanceCount: 0n, + expectedStateHash: stateHash(initial), + maxInstructions: 4, + maxOutputs: 16, + }); + let repeated = initial; + const outputs: number[] = []; + for (let index = 0; index < 4; index += 1) { + const result = step(repeated, { rom: rom(...words) }); + repeated = result.state; + if (result.legacyOutput !== undefined) outputs.push(result.legacyOutput.value); + } + assert.deepEqual(outputs, [2]); + assert.equal(batched.executed, 4); + assert.equal(batched.stopReason, STOP_REASON.halted); + assert.deepEqual({ pc: batched.state.pc, accumulator: batched.state.accumulator, flags: batched.state.flags, status: batched.state.status, instructionCount: batched.state.instructionCount, outputCount: batched.state.outputCount, outputCommitment: batched.state.outputCommitment }, { pc: repeated.pc, accumulator: repeated.accumulator, flags: repeated.flags, status: repeated.status, instructionCount: repeated.instructionCount, outputCount: repeated.outputCount, outputCommitment: repeated.outputCommitment }); +}); + +test("stale and racing advances fail before architectural mutation", () => { + const initial = createInitialState(config()); + const expected = stateHash(initial); + assert.throws(() => advance(initial, { rom: rom(encodeInstruction(Opcode.NOP)) }, { + expectedAdvanceCount: 1n, + expectedStateHash: expected, + maxInstructions: 1, + maxOutputs: 1, + }), (error: unknown) => error instanceof Error && "code" in error && error.code === "STALE_ADVANCE"); + assert.equal(stateHash(initial), expected); + + const accepted = advance(initial, { rom: rom(encodeInstruction(Opcode.NOP)) }, { + expectedAdvanceCount: 0n, + expectedStateHash: expected, + maxInstructions: 1, + maxOutputs: 1, + }); + assert.throws(() => advance(accepted.state, { rom: rom(encodeInstruction(Opcode.NOP)) }, { + expectedAdvanceCount: 0n, + expectedStateHash: expected, + maxInstructions: 1, + maxOutputs: 1, + }), (error: unknown) => error instanceof Error && "code" in error && error.code === "STALE_ADVANCE"); +}); + +test("Fibonacci regression produces 1, 1, 2, 3, 5, 8, 13", () => { + const words = assemble(` + LDI 1 + STR R0 + LDI 1 + STR R1 + LDI 7 + STR R2 + LOOP: + LDR R0 + OUT + LDR R2 + DEC + STR R2 + JZ DONE + LDR R0 + ADD R1 + STR R3 + LDR R1 + STR R0 + LDR R3 + STR R1 + JMP LOOP + DONE: + HALT + `).words; + const initial = createInitialState(config()); + let current = initial; + const outputs: number[] = []; + for (let guard = 0; guard < 200 && current.status === STATUS.running; guard += 1) { + const result = advance(current, { rom: rom(...words) }, { + expectedAdvanceCount: current.advanceCount, + expectedStateHash: stateHash(current), + maxInstructions: 1, + maxOutputs: 1, + }); + current = result.state; + outputs.push(...result.legacyOutputs.map((output) => output.value)); + } + assert.deepEqual(outputs, [1, 1, 2, 3, 5, 8, 13]); + assert.equal(current.status, STATUS.halted); + assert.equal(current.instructionCount, 97n); + assert.equal(current.advanceCount, 97n); + assert.equal(current.outputCount, 7n); +}); + +test("dispatch reconstructs the authenticated input message and rejects stale state", () => { + const state = createInitialState(config({ + routes: [{ sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 4 }], + })); + const output = step(state, { rom: rom(encodePortInstruction(SYS.OUTP, 0)) }).output; + assert.ok(output); + const source = step(state, { rom: rom(encodePortInstruction(SYS.OUTP, 0)) }).state; + const dispatched = dispatchOutput(source, { expectedStateHash: stateHash(source), outputId: output.outputId }); + assert.equal(dispatched.message.outputId, output.outputId); + assert.equal(dispatched.message.destinationCoreId, 1); + assert.throws(() => dispatchOutput(source, { expectedStateHash: stateHash(state), outputId: output.outputId }), /state hash/); +}); From 0f1c05a6bda736890218e72efa7e7b948c9d811a Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:03:06 +0100 Subject: [PATCH 13/67] feat: add v2 memory and route commitment builders --- packages/artifact/src/memory.ts | 119 ++++++++++++++++++++++++++++++++ packages/artifact/src/routes.ts | 76 ++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 packages/artifact/src/memory.ts create mode 100644 packages/artifact/src/routes.ts diff --git a/packages/artifact/src/memory.ts b/packages/artifact/src/memory.ts new file mode 100644 index 0000000..ae6a453 --- /dev/null +++ b/packages/artifact/src/memory.ts @@ -0,0 +1,119 @@ +import { beginCell, type Cell } from "@ton/core"; +import { RAM_NIBBLES, ROM_WORDS } from "../../isa/src/constants.js"; + +const WORDS_PER_LEAF = 32; +const WORDS_PER_HALF = 16; +const LEAVES_PER_LEVEL1 = 4; +const LEVEL1_PER_LEVEL2 = 4; +const HALF_MASK = (1n << 256n) - 1n; + +export const V2_MEMORY_LAYOUT = { + romWords: ROM_WORDS, + romWordsPerLeaf: WORDS_PER_LEAF, + romWordsPerHalf: WORDS_PER_HALF, + leavesPerLevel1: LEAVES_PER_LEVEL1, + level1PerLevel2: LEVEL1_PER_LEVEL2, + ramNibbles: RAM_NIBBLES, + ramNibblesPerPage: 64, + ramPages: 4, +} as const; + +export function normalizeRom(words: readonly number[]): number[] { + if (words.length > ROM_WORDS) throw new RangeError(`ROM contains ${words.length} words; maximum is ${ROM_WORDS}`); + return Array.from({ length: ROM_WORDS }, (_, index) => { + const word = words[index] ?? 0; + if (!Number.isInteger(word) || word < 0 || word > 0xffff) throw new RangeError(`ROM word ${index} must be 16-bit unsigned`); + return word; + }); +} + +export function romRootCell(words: readonly number[] = []): Cell { + const normalized = normalizeRom(words); + const leaves = Array.from({ length: ROM_WORDS / WORDS_PER_LEAF }, (_, index) => { + const start = index * WORDS_PER_LEAF; + return romLeafCell(normalized.slice(start, start + WORDS_PER_LEAF)); + }); + const level1 = group4(leaves, romLevel1Cell); + const level2 = group4(level1, romLevel2Cell); + const zeroLeaf = romLeafCell(Array.from({ length: WORDS_PER_LEAF }, () => 0)); + const zeroLevel1 = romLevel1Cell(Array.from({ length: LEAVES_PER_LEVEL1 }, () => zeroLeaf)); + const zeroLevel2 = romLevel2Cell(Array.from({ length: LEVEL1_PER_LEVEL2 }, () => zeroLevel1)); + return beginCell() + .storeRef(level2[0]!) + .storeRef(level2[1]!) + .storeRef(zeroLevel2) + .storeRef(zeroLevel2) + .endCell(); +} + +export function romRoot(words: readonly number[] = []): string { + return romRootCell(words).hash().toString("hex"); +} + +export function romWord(words: readonly number[], address: number): number { + if (!Number.isInteger(address) || address < 0 || address >= ROM_WORDS) throw new RangeError("ROM address must be 0..1023"); + return normalizeRom(words)[address] ?? 0; +} + +export function normalizeRam(ram: readonly number[] | Uint8Array): Uint8Array { + if (ram.length > RAM_NIBBLES) throw new RangeError(`RAM contains ${ram.length} nibbles; maximum is ${RAM_NIBBLES}`); + const normalized = new Uint8Array(RAM_NIBBLES); + for (let index = 0; index < ram.length; index += 1) { + const value = ram[index] ?? 0; + if (!Number.isInteger(value) || value < 0 || value > 0xf) throw new RangeError(`RAM nibble ${index} must be 0..15`); + normalized[index] = value; + } + return normalized; +} + +export function ramPageCell(ram: readonly number[] | Uint8Array, pageIndex: number): Cell { + if (!Number.isInteger(pageIndex) || pageIndex < 0 || pageIndex >= 4) throw new RangeError("RAM page must be 0..3"); + const normalized = normalizeRam(ram); + let packed = 0n; + for (let offset = 0; offset < 64; offset += 1) { + packed |= BigInt(normalized[pageIndex * 64 + offset] ?? 0) << BigInt(offset * 4); + } + return beginCell().storeUint(packed, 256).endCell(); +} + +export function ramRootCell(ram: readonly number[] | Uint8Array = new Uint8Array(RAM_NIBBLES)): Cell { + const normalized = normalizeRam(ram); + return beginCell() + .storeRef(ramPageCell(normalized, 0)) + .storeRef(ramPageCell(normalized, 1)) + .storeRef(ramPageCell(normalized, 2)) + .storeRef(ramPageCell(normalized, 3)) + .endCell(); +} + +export function ramRoot(ram: readonly number[] | Uint8Array = new Uint8Array(RAM_NIBBLES)): string { + return ramRootCell(ram).hash().toString("hex"); +} + +function romLeafCell(words: readonly number[]): Cell { + if (words.length !== WORDS_PER_LEAF) throw new RangeError("ROM leaf must contain 32 words"); + return beginCell() + .storeUint(packHalf(words.slice(0, WORDS_PER_HALF)), 256) + .storeUint(packHalf(words.slice(WORDS_PER_HALF)), 256) + .endCell(); +} + +function packHalf(words: readonly number[]): bigint { + if (words.length !== WORDS_PER_HALF) throw new RangeError("ROM half must contain 16 words"); + return words.reduce((packed, word) => ((packed << 16n) | BigInt(word)) & HALF_MASK, 0n); +} + +function romLevel1Cell(children: readonly Cell[]): Cell { + if (children.length !== 4) throw new RangeError("ROM level 1 node must contain four leaves"); + return beginCell().storeRef(children[0]!).storeRef(children[1]!).storeRef(children[2]!).storeRef(children[3]!).endCell(); +} + +function romLevel2Cell(children: readonly Cell[]): Cell { + if (children.length !== 4) throw new RangeError("ROM level 2 node must contain four level 1 nodes"); + return beginCell().storeRef(children[0]!).storeRef(children[1]!).storeRef(children[2]!).storeRef(children[3]!).endCell(); +} + +function group4(values: readonly T[], builder: (children: readonly T[]) => Cell): Cell[] { + if (values.length % 4 !== 0) throw new RangeError("tree level is not divisible by four"); + return Array.from({ length: values.length / 4 }, (_, index) => builder(values.slice(index * 4, index * 4 + 4))); +} diff --git a/packages/artifact/src/routes.ts b/packages/artifact/src/routes.ts new file mode 100644 index 0000000..735f9a8 --- /dev/null +++ b/packages/artifact/src/routes.ts @@ -0,0 +1,76 @@ +import { beginCell, type Cell } from "@ton/core"; +import { DOMAIN, MAX_INPUT_RECORDS, MAX_PORTS } from "../../isa/src/constants.js"; + +export interface RouteDescriptor { + readonly sourceCoreId: number; + readonly sourcePort: number; + readonly destinationCoreId: number; + readonly destinationPort: number; + readonly delay: number; + readonly maxRecordsPerEpoch: number; +} + +export function normalizeRoutes(routes: readonly RouteDescriptor[]): RouteDescriptor[] { + if (routes.length > MAX_INPUT_RECORDS) throw new RangeError("v2 route table supports at most 16 routes"); + const sorted = routes.map((route) => ({ ...route })).sort(compareRoutes); + const seenSources = new Set(); + for (const route of sorted) { + assertU16(route.sourceCoreId, "sourceCoreId"); + assertU16(route.destinationCoreId, "destinationCoreId"); + assertPort(route.sourcePort, "sourcePort"); + assertPort(route.destinationPort, "destinationPort"); + if (!Number.isInteger(route.delay) || route.delay < 1 || route.delay > 0xff) throw new RangeError("route delay must be 1..255"); + if (!Number.isInteger(route.maxRecordsPerEpoch) || route.maxRecordsPerEpoch < 1 || route.maxRecordsPerEpoch > MAX_INPUT_RECORDS) { + throw new RangeError("route maxRecordsPerEpoch must be 1..16"); + } + const source = `${route.sourceCoreId}:${route.sourcePort}`; + if (seenSources.has(source)) throw new RangeError(`duplicate source route ${source}`); + seenSources.add(source); + } + return sorted; +} + +export function routeCell(route: RouteDescriptor): Cell { + return beginCell() + .storeUint(DOMAIN.route, 32) + .storeUint(route.sourceCoreId, 16) + .storeUint(route.sourcePort, 4) + .storeUint(route.destinationCoreId, 16) + .storeUint(route.destinationPort, 4) + .storeUint(route.delay, 8) + .storeUint(route.maxRecordsPerEpoch, 8) + .endCell(); +} + +export function routeRootCell(routes: readonly RouteDescriptor[]): Cell { + const normalized = normalizeRoutes(routes); + const pages = Array.from({ length: 4 }, (_, pageIndex) => { + const page = normalized.slice(pageIndex * 4, pageIndex * 4 + 4); + const builder = beginCell().storeUint(page.length, 3); + page.forEach((route) => builder.storeRef(routeCell(route))); + return builder.endCell(); + }); + return beginCell() + .storeUint(normalized.length, 5) + .storeRef(pages[0]!) + .storeRef(pages[1]!) + .storeRef(pages[2]!) + .storeRef(pages[3]!) + .endCell(); +} + +export function routeRoot(routes: readonly RouteDescriptor[]): string { + return routeRootCell(routes).hash().toString("hex"); +} + +function compareRoutes(a: RouteDescriptor, b: RouteDescriptor): number { + return a.sourceCoreId - b.sourceCoreId || a.sourcePort - b.sourcePort || a.destinationCoreId - b.destinationCoreId || a.destinationPort - b.destinationPort; +} + +function assertU16(value: number, field: string): void { + if (!Number.isInteger(value) || value < 0 || value > 0xffff) throw new RangeError(`${field} must be 0..65535`); +} + +function assertPort(value: number, field: string): void { + if (!Number.isInteger(value) || value < 0 || value >= MAX_PORTS) throw new RangeError(`${field} must be 0..15`); +} From f98ce01611a760425bd29c3bfc927b3efbe57bfe Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:03:14 +0100 Subject: [PATCH 14/67] test: compare v2 memory and route roots --- tests/v2/memory.test.ts | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/v2/memory.test.ts diff --git a/tests/v2/memory.test.ts b/tests/v2/memory.test.ts new file mode 100644 index 0000000..7cd58ba --- /dev/null +++ b/tests/v2/memory.test.ts @@ -0,0 +1,34 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { romRoot, romRootCell, ramRoot, ramRootCell } from "../../packages/artifact/src/memory.js"; +import { routeRoot, routeRootCell } from "../../packages/artifact/src/routes.js"; +import { ramRootCell as emulatorRamRootCell, routeRoot as emulatorRouteRoot } from "../../packages/emulator/src/commitments.js"; + +test("v2 ROM and RAM trees are fixed-size, zero-filled, and deterministic", () => { + const emptyRom = romRoot(); + assert.equal(emptyRom, romRoot([])); + assert.notEqual(emptyRom, romRoot([1])); + assert.notEqual(romRoot([0x1234]), romRoot([0x1235])); + assert.equal(romRootCell([0x1234]).hash().toString("hex"), romRoot([0x1234])); + + const emptyRam = ramRoot(); + const ram = new Uint8Array(256); + ram[0] = 0xa; + ram[255] = 0xb; + assert.equal(ramRootCell(ram).hash().toString("hex"), ramRoot(ram)); + assert.notEqual(emptyRam, ramRoot(ram)); + assert.equal(emulatorRamRootCell(ram).hash().toString("hex"), ramRoot(ram)); +}); + +test("route roots sort descriptors and reject ambiguous source ports", () => { + const routes = [ + { sourceCoreId: 1, sourcePort: 2, destinationCoreId: 3, destinationPort: 4, delay: 2, maxRecordsPerEpoch: 4 }, + { sourceCoreId: 0, sourcePort: 1, destinationCoreId: 2, destinationPort: 3, delay: 1, maxRecordsPerEpoch: 4 }, + ]; + const reversed = [...routes].reverse(); + assert.equal(routeRoot(routes), routeRoot(reversed)); + assert.equal(routeRootCell(routes).hash().toString("hex"), routeRoot(routes)); + assert.equal(emulatorRouteRoot(routes), routeRoot(routes)); + assert.throws(() => routeRoot([...routes, { ...routes[0]! }]), /duplicate source route/); + assert.throws(() => routeRoot([{ ...routes[0]!, delay: 0 }]), /route delay/); +}); From 09bae7290c43efc307a602cd7bbb211bb2e082f0 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:08:25 +0100 Subject: [PATCH 15/67] feat: add versioned v2 assembler --- packages/artifact/src/assembler.ts | 310 +++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 packages/artifact/src/assembler.ts diff --git a/packages/artifact/src/assembler.ts b/packages/artifact/src/assembler.ts new file mode 100644 index 0000000..646cfa3 --- /dev/null +++ b/packages/artifact/src/assembler.ts @@ -0,0 +1,310 @@ +import { readFileSync } from "node:fs"; +import { dirname, isAbsolute, relative, resolve } from "node:path"; +import { decodeInstruction, encodeInstruction, encodePortInstruction, encodeTrap, V2IsaError } from "../../isa/src/codec.js"; +import { Opcode } from "../../isa/src/types.js"; +import { RAM_NIBBLES, ROM_WORDS, SYS } from "../../isa/src/constants.js"; + +export interface V2SourceMapEntry { + readonly address: number; + readonly file: string; + readonly line: number; + readonly column: number; + readonly source: string; +} + +export interface V2AssemblyResult { + readonly words: number[]; + readonly ram: Uint8Array; + readonly entryPc: number; + readonly labels: Readonly>; + readonly exports: Readonly>; + readonly sourceMap: readonly V2SourceMapEntry[]; + readonly canonicalSource: string; +} + +export interface AssembleFileOptions { + readonly rootDir?: string; +} + +interface SourceLine { + readonly file: string; + readonly line: number; + readonly text: string; +} + +interface PendingInstruction { + readonly address: number; + readonly source: SourceLine; + readonly text: string; +} + +export class V2AssemblyError extends Error { + public readonly code: string; + public readonly file: string; + public readonly line: number; + public readonly column: number; + + public constructor(code: string, message: string, source: SourceLine, column = 1) { + super(`${source.file}:${source.line}:${column}: ${message}`); + this.name = "V2AssemblyError"; + this.code = code; + this.file = source.file; + this.line = source.line; + this.column = column; + } +} + +export function assembleV2(source: string, file = "program.tasm"): V2AssemblyResult { + return assembleLines(source.split(/\r?\n/).map((text, index) => ({ file, line: index + 1, text }))); +} + +export function assembleFileV2(entryFile: string, options: AssembleFileOptions = {}): V2AssemblyResult { + const rootDir = resolve(options.rootDir ?? dirname(resolve(entryFile))); + const lines = expandFile(resolve(entryFile), rootDir, new Set()); + return assembleLines(lines); +} + +function assembleLines(lines: readonly SourceLine[]): V2AssemblyResult { + const words = Array.from({ length: ROM_WORDS }, () => 0); + const ram = new Uint8Array(RAM_NIBBLES); + const claimedRom = new Set(); + const claimedRam = new Set(); + const labels: Record = {}; + const pending: PendingInstruction[] = []; + const exportedNames: string[] = []; + let romCursor = 0; + let entryToken: string | undefined; + + for (const source of lines) { + const stripped = source.text.replace(/[;#].*$/, "").trim(); + if (stripped.length === 0) continue; + let remainder = stripped; + const labelMatch = /^([A-Za-z_][A-Za-z0-9_]*):/.exec(remainder); + if (labelMatch !== null) { + const label = labelMatch[1]; + if (label === undefined) throw new V2AssemblyError("INVALID_LABEL", "label is missing", source); + if (labels[label] !== undefined) throw new V2AssemblyError("DUPLICATE_SYMBOL", `duplicate symbol ${label}`, source); + labels[label] = romCursor; + remainder = remainder.slice(labelMatch[0].length).trim(); + } + if (remainder.length === 0) continue; + const parts = splitParts(remainder); + const directive = parts[0]?.toLowerCase(); + if (directive === undefined) throw new V2AssemblyError("INVALID_LINE", "empty statement", source); + + switch (directive) { + case ".org": + requireParts(parts, 2, source); + romCursor = parseRange(parts[1], 0, ROM_WORDS - 1, source, "ROM origin"); + break; + case ".word": { + requireParts(parts, 2, source); + const word = parseRange(parts[1], 0, 0xffff, source, "word"); + claimRom(claimedRom, romCursor, source); + words[romCursor] = assertCanonicalWord(word, source); + pending.push({ address: romCursor, source, text: remainder }); + romCursor += 1; + break; + } + case ".nibble": + case ".ram": { + requireParts(parts, 3, source); + const address = parseRange(parts[1], 0, RAM_NIBBLES - 1, source, "RAM address"); + const value = parseRange(parts[2], 0, 0xf, source, "RAM nibble"); + if (claimedRam.has(address)) throw new V2AssemblyError("OVERLAPPING_SEGMENT", `RAM address ${address} is already initialized`, source); + claimedRam.add(address); + ram[address] = value; + break; + } + case ".entry": + requireParts(parts, 2, source); + if (entryToken !== undefined) throw new V2AssemblyError("DUPLICATE_ENTRY", "entry point is already declared", source); + entryToken = parts[1]; + break; + case ".export": + requireParts(parts, 2, source); + if (exportedNames.includes(parts[1]!)) throw new V2AssemblyError("DUPLICATE_SYMBOL", `duplicate export ${parts[1]}`, source); + exportedNames.push(parts[1]!); + break; + case ".include": + throw new V2AssemblyError("INCLUDE_REQUIRES_FILE", ".include requires assembleFileV2 so its root can be constrained", source); + default: + claimRom(claimedRom, romCursor, source); + pending.push({ address: romCursor, source, text: remainder }); + romCursor += 1; + break; + } + } + + const sourceMap: V2SourceMapEntry[] = []; + for (const item of pending) { + const word = item.text.toLowerCase().startsWith(".word") + ? words[item.address]! + : parseInstruction(item.text, labels, item.source); + words[item.address] = assertCanonicalWord(word, item.source); + sourceMap.push({ + address: item.address, + file: item.source.file, + line: item.source.line, + column: Math.max(1, item.source.text.indexOf(item.text) + 1), + source: item.text, + }); + } + + const entryPc = entryToken === undefined ? 0 : resolveSymbolOrNumber(entryToken, labels, 0, ROM_WORDS - 1, lines[0] ?? { file: "program.tasm", line: 1, text: "" }, "entry point"); + if (!claimedRom.has(entryPc)) { + throw new V2AssemblyError("INVALID_ENTRY", `entry point ${entryPc} does not reference an occupied ROM word`, lines[0] ?? { file: "program.tasm", line: 1, text: "" }); + } + const exports: Record = {}; + for (const name of exportedNames) { + const value = labels[name]; + if (value === undefined) throw new V2AssemblyError("UNKNOWN_SYMBOL", `unknown export ${name}`, lines[0] ?? { file: "program.tasm", line: 1, text: "" }); + exports[name] = value; + } + return { + words, + ram, + entryPc, + labels, + exports, + sourceMap, + canonicalSource: lines.map((line) => `${line.file}\n${line.line}\n${line.text.replace(/\r$/, "")}`).join("\n"), + }; +} + +function parseInstruction(text: string, labels: Readonly>, source: SourceLine): number { + const parts = splitParts(text); + const mnemonic = parts[0]?.toUpperCase(); + if (mnemonic === undefined) throw new V2AssemblyError("INVALID_INSTRUCTION", "missing mnemonic", source); + const operand = parts[1]; + if (parts.length > 2) throw new V2AssemblyError("INVALID_INSTRUCTION", "expected at most one operand", source); + try { + switch (mnemonic) { + case "NOP": return noOperand(mnemonic, operand, Opcode.NOP, source); + case "LDI": return encodeInstruction(Opcode.LDI, parseRange(operand, 0, 0xf, source, "immediate")); + case "LDR": return encodeInstruction(Opcode.LDR, parseRegister(operand, source)); + case "STR": return encodeInstruction(Opcode.STR, parseRegister(operand, source)); + case "ADD": return encodeInstruction(Opcode.ADD, parseRegister(operand, source)); + case "ADC": return encodeInstruction(Opcode.ADC, parseRegister(operand, source)); + case "SUB": return encodeInstruction(Opcode.SUB, parseRegister(operand, source)); + case "AND": return encodeInstruction(Opcode.AND, parseRegister(operand, source)); + case "OR": return encodeInstruction(Opcode.OR, parseRegister(operand, source)); + case "XOR": return encodeInstruction(Opcode.XOR, parseRegister(operand, source)); + case "LDM": return encodeInstruction(Opcode.LDM, parseRange(operand, 0, 0xff, source, "RAM address")); + case "STM": return encodeInstruction(Opcode.STM, parseRange(operand, 0, 0xff, source, "RAM address")); + case "JMP": return encodeInstruction(Opcode.JMP, resolveSymbolOrNumber(operand, labels, 0, ROM_WORDS - 1, source, "jump target")); + case "JZ": return encodeInstruction(Opcode.JZ, resolveSymbolOrNumber(operand, labels, 0, ROM_WORDS - 1, source, "jump target")); + case "JC": return encodeInstruction(Opcode.JC, resolveSymbolOrNumber(operand, labels, 0, ROM_WORDS - 1, source, "jump target")); + case "HALT": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.HALT); + case "OUT": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.OUT); + case "CLC": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.CLC); + case "STC": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.STC); + case "NOT": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.NOT); + case "SHL": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.SHL); + case "SHR": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.SHR); + case "INC": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.INC); + case "DEC": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.DEC); + case "IN": return encodePortInstruction(SYS.IN, parseRange(operand, 0, 15, source, "input port")); + case "OUTP": return encodePortInstruction(SYS.OUTP, parseRange(operand, 0, 15, source, "output port")); + case "YIELD": return noOperand(mnemonic, operand, Opcode.SYS, source, SYS.YIELD); + case "TRAP": return encodeTrap(parseRange(operand, 0, 0xff, source, "trap code")); + case "SYS": return parseSys(operand, source); + default: throw new V2AssemblyError("UNKNOWN_MNEMONIC", `unknown mnemonic ${mnemonic}`, source); + } + } catch (error) { + if (error instanceof V2AssemblyError) throw error; + if (error instanceof V2IsaError) throw new V2AssemblyError(error.code, error.message, source); + throw new V2AssemblyError("INVALID_INSTRUCTION", error instanceof Error ? error.message : String(error), source); + } +} + +function parseSys(operand: string | undefined, source: SourceLine): number { + if (operand === undefined) throw new V2AssemblyError("MISSING_OPERAND", "SYS requires a suboperation", source); + const named: Record = { + HALT: SYS.HALT, + OUT: SYS.OUT, + CLC: SYS.CLC, + STC: SYS.STC, + NOT: SYS.NOT, + SHL: SYS.SHL, + SHR: SYS.SHR, + INC: SYS.INC, + DEC: SYS.DEC, + YIELD: SYS.YIELD, + }; + if (named[operand.toUpperCase()] !== undefined) return encodeInstruction(Opcode.SYS, named[operand.toUpperCase()]!); + return encodeInstruction(Opcode.SYS, parseRange(operand, 0, 0xfff, source, "SYS operand")); +} + +function noOperand(mnemonic: string, operand: string | undefined, opcode: Opcode, source: SourceLine, value = 0): number { + if (operand !== undefined) throw new V2AssemblyError("UNEXPECTED_OPERAND", `${mnemonic} does not take an operand`, source); + return encodeInstruction(opcode, value); +} + +function parseRegister(value: string | undefined, source: SourceLine): number { + if (value === undefined) throw new V2AssemblyError("MISSING_OPERAND", "register is required", source); + const match = /^R(1[0-5]|[0-9])$/i.exec(value); + if (match === null || match[1] === undefined) throw new V2AssemblyError("INVALID_REGISTER", `invalid register ${value}`, source); + return Number(match[1]); +} + +function resolveSymbolOrNumber(value: string | undefined, labels: Readonly>, minimum: number, maximum: number, source: SourceLine, field: string): number { + if (value === undefined) throw new V2AssemblyError("MISSING_OPERAND", `${field} is required`, source); + if (/^(?:0x[0-9a-f]+|0b[01]+|[0-9]+)$/i.test(value)) return parseRange(value, minimum, maximum, source, field); + const resolved = labels[value]; + if (resolved === undefined) throw new V2AssemblyError("UNKNOWN_SYMBOL", `unknown symbol ${value}`, source); + if (resolved < minimum || resolved > maximum) throw new V2AssemblyError("OUT_OF_RANGE", `${field} is outside ${minimum}..${maximum}`, source); + return resolved; +} + +function parseRange(value: string | undefined, minimum: number, maximum: number, source: SourceLine, field: string): number { + if (value === undefined || !/^(?:0x[0-9a-f]+|0b[01]+|[0-9]+)$/i.test(value)) throw new V2AssemblyError("INVALID_NUMBER", `${field} must be decimal, binary, or hexadecimal`, source); + const parsed = Number(value); + if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) throw new V2AssemblyError("OUT_OF_RANGE", `${field} must be in ${minimum}..${maximum}`, source); + return parsed; +} + +function splitParts(text: string): string[] { + return text.split(/[\s,]+/).filter(Boolean); +} + +function requireParts(parts: readonly string[], count: number, source: SourceLine): void { + if (parts.length !== count) throw new V2AssemblyError("INVALID_DIRECTIVE", `expected ${count - 1} argument(s)`, source); +} + +function claimRom(claimed: Set, address: number, source: SourceLine): void { + if (address < 0 || address >= ROM_WORDS) throw new V2AssemblyError("OUT_OF_RANGE", `ROM address ${address} is outside 0..${ROM_WORDS - 1}`, source); + if (claimed.has(address)) throw new V2AssemblyError("OVERLAPPING_SEGMENT", `ROM address ${address} is already occupied`, source); + claimed.add(address); +} + +function assertCanonicalWord(word: number, source: SourceLine): number { + try { + decodeInstruction(word); + return word; + } catch (error) { + if (error instanceof V2IsaError) throw new V2AssemblyError(error.code, error.message, source); + throw error; + } +} + +function expandFile(file: string, rootDir: string, active: Set): SourceLine[] { + const normalized = resolve(file); + const relativeName = relative(rootDir, normalized); + if (relativeName.startsWith("..") || isAbsolute(relativeName)) throw new Error(`include escapes assembler root: ${relativeName}`); + if (active.has(normalized)) throw new Error(`cyclic include: ${relativeName}`); + active.add(normalized); + const contents = readFileSync(normalized, "utf8"); + const result: SourceLine[] = []; + contents.split(/\r?\n/).forEach((text, index) => { + const include = /^\s*\.include\s+["']([^"']+)["']\s*(?:[;#].*)?$/i.exec(text); + if (include?.[1] !== undefined) { + const child = resolve(dirname(normalized), include[1]); + result.push(...expandFile(child, rootDir, active)); + } else { + result.push({ file: relativeName || ".", line: index + 1, text }); + } + }); + active.delete(normalized); + return result; +} From 324a40438d7f6f3ffe0b4cfdc192afb7ca8200fe Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:08:35 +0100 Subject: [PATCH 16/67] test: validate v2 assembler diagnostics --- tests/v2/assembler.test.ts | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/v2/assembler.test.ts diff --git a/tests/v2/assembler.test.ts b/tests/v2/assembler.test.ts new file mode 100644 index 0000000..45f7eaf --- /dev/null +++ b/tests/v2/assembler.test.ts @@ -0,0 +1,55 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { assembleV2, V2AssemblyError } from "../../packages/artifact/src/assembler.js"; + +test("v2 assembler supports labels, forward references, directives, and v2 SYS syntax", () => { + const result = assembleV2(` + .entry start + .export start + .ram 0x10, 0xA + .nibble 0xFF 0xB + start: + JMP next + IN 3 + next: + OUTP 2 + YIELD + TRAP 0x7f + .org 8 + .word 0x0000 + `); + assert.equal(result.entryPc, 0); + assert.equal(result.labels.start, 0); + assert.equal(result.labels.next, 2); + assert.equal(result.exports.start, 0); + assert.equal(result.words[0], 0xc002); + assert.equal(result.words[1], 0xf039); + assert.equal(result.words[2], 0xf02a); + assert.equal(result.words[3], 0xf00b); + assert.equal(result.words[4], 0xf7fc); + assert.equal(result.words[8], 0); + assert.equal(result.ram[0x10], 0xa); + assert.equal(result.ram[0xff], 0xb); + assert.equal(result.sourceMap.length, 6); + assert.equal(result.sourceMap[0]?.address, 0); +}); + +test("v2 assembler rejects overlapping segments, unknown symbols, and noncanonical words", () => { + assert.throws(() => assembleV2(".org 1\n.word 0\n.org 1\nNOP"), (error: unknown) => error instanceof V2AssemblyError && error.code === "OVERLAPPING_SEGMENT"); + assert.throws(() => assembleV2("JMP missing"), (error: unknown) => error instanceof V2AssemblyError && error.code === "UNKNOWN_SYMBOL"); + assert.throws(() => assembleV2(".word 0xf00d"), (error: unknown) => error instanceof V2AssemblyError && error.code === "INVALID_SYS_SUBOP"); + assert.throws(() => assembleV2(".ram 0 1\n.nibble 0 2"), (error: unknown) => error instanceof V2AssemblyError && error.code === "OVERLAPPING_SEGMENT"); +}); + +test("v2 assembler output is deterministic and zero-fills omitted ROM and RAM", () => { + const source = ".entry 0\nLDI 1\n"; + const first = assembleV2(source); + const second = assembleV2(source); + assert.deepEqual(first.words, second.words); + assert.deepEqual([...first.ram], [...second.ram]); + assert.equal(first.words.length, 1024); + assert.equal(first.ram.length, 256); + assert.equal(first.words.slice(1).every((word) => word === 0), true); + assert.equal(first.ram.every((value) => value === 0), true); + assert.equal(first.canonicalSource, second.canonicalSource); +}); From 2cfc1381900be9b535827a1312672a244a4b4dea Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:10:17 +0100 Subject: [PATCH 17/67] spec: reserve v2 run and limits domains --- packages/isa/src/constants.ts | 2 ++ spec/v2/protocol.json | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/isa/src/constants.ts b/packages/isa/src/constants.ts index fe97b2e..8304347 100644 --- a/packages/isa/src/constants.ts +++ b/packages/isa/src/constants.ts @@ -31,6 +31,8 @@ export const DOMAIN = { route: 0x544c5232, batch: 0x544c4232, acknowledgement: 0x544c4132, + run: 0x544c4432, + limits: 0x544c4c32, } as const; export const MESSAGE_PREFIX = { diff --git a/spec/v2/protocol.json b/spec/v2/protocol.json index a2af035..a7aa582 100644 --- a/spec/v2/protocol.json +++ b/spec/v2/protocol.json @@ -26,7 +26,9 @@ "input": "0x544c4932", "route": "0x544c5232", "batch": "0x544c4232", - "acknowledgement": "0x544c4132" + "acknowledgement": "0x544c4132", + "run": "0x544c4432", + "limits": "0x544c4c32" }, "messages": { "advance": "0x544e4c21", From 7a8077c4f80e51964a542bf8aba33cdf708db44d Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:14:29 +0100 Subject: [PATCH 18/67] feat: add v2 artifact commitment primitives --- packages/artifact/src/commitments.ts | 112 +++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 packages/artifact/src/commitments.ts diff --git a/packages/artifact/src/commitments.ts b/packages/artifact/src/commitments.ts new file mode 100644 index 0000000..be55cb9 --- /dev/null +++ b/packages/artifact/src/commitments.ts @@ -0,0 +1,112 @@ +import { beginCell, type Cell } from "@ton/core"; +import { ABI_VERSION, DOMAIN, ISA_VERSION, PROTOCOL_VERSION, SCHEMA_VERSION } from "../../isa/src/constants.js"; + +export type Hash256 = string; + +export interface ProgramFields { + readonly artifactVersion: number; + readonly isaVersion: number; + readonly wordBits: number; + readonly romWords: number; + readonly ramNibbles: number; + readonly entryPc: number; + readonly romRoot: Hash256; + readonly initialRamRoot: Hash256; +} + +export interface LimitsV2 { + readonly maxStepsPerAdvance: number; + readonly maxInputRecords: number; + readonly maxOutputRecords: number; + readonly maxActionsPerAdvance: number; + readonly maxOutputsPerAdvance: number; +} + +export function programCommitmentCell(fields: ProgramFields): Cell { + return beginCell() + .storeUint(DOMAIN.program, 32) + .storeUint(fields.artifactVersion, 16) + .storeUint(fields.isaVersion, 16) + .storeUint(fields.wordBits, 8) + .storeUint(fields.romWords, 16) + .storeUint(fields.ramNibbles, 16) + .storeUint(fields.entryPc, 16) + .storeUint(toBigInt(fields.romRoot), 256) + .storeUint(toBigInt(fields.initialRamRoot), 256) + .endCell(); +} + +export function programId(fields: ProgramFields): Hash256 { + return programCommitmentCell(fields).hash().toString("hex"); +} + +export function limitsCell(limits: LimitsV2): Cell { + return beginCell() + .storeUint(DOMAIN.limits, 32) + .storeUint(limits.maxStepsPerAdvance, 16) + .storeUint(limits.maxInputRecords, 8) + .storeUint(limits.maxOutputRecords, 8) + .storeUint(limits.maxActionsPerAdvance, 8) + .storeUint(limits.maxOutputsPerAdvance, 8) + .endCell(); +} + +export function limitsHash(limits: LimitsV2): Hash256 { + return limitsCell(limits).hash().toString("hex"); +} + +export function staticCommitmentCell(fields: { + readonly schemaVersion: number; + readonly isaVersion: number; + readonly abiVersion: number; + readonly protocolVersion: number; + readonly runId: Hash256; + readonly coreId: number; + readonly programId: Hash256; + readonly romRoot: Hash256; + readonly routeRoot: Hash256; + readonly limitsHash: Hash256; +}): Cell { + const programPayload = beginCell() + .storeUint(toBigInt(fields.programId), 256) + .storeUint(toBigInt(fields.romRoot), 256) + .storeUint(toBigInt(fields.routeRoot), 256) + .endCell(); + const limitsPayload = beginCell().storeUint(toBigInt(fields.limitsHash), 256).endCell(); + return beginCell() + .storeUint(DOMAIN.static, 32) + .storeUint(fields.schemaVersion, 16) + .storeUint(fields.isaVersion, 16) + .storeUint(fields.abiVersion, 16) + .storeUint(fields.protocolVersion, 16) + .storeUint(toBigInt(fields.runId), 256) + .storeUint(fields.coreId, 16) + .storeRef(programPayload) + .storeRef(limitsPayload) + .endCell(); +} + +export function staticCommitment(fields: Parameters[0]): Hash256 { + return staticCommitmentCell(fields).hash().toString("hex"); +} + +export function deriveRunId(programIds: readonly Hash256[], routeRoot: Hash256, salt: Hash256): Hash256 { + if (programIds.length < 1 || programIds.length > 0xff) throw new RangeError("run must contain 1..255 program IDs"); + const builder = beginCell() + .storeUint(DOMAIN.run, 32) + .storeUint(PROTOCOL_VERSION, 16) + .storeUint(programIds.length, 8) + .storeUint(toBigInt(routeRoot), 256) + .storeUint(toBigInt(salt), 256); + programIds.forEach((id, index) => builder.storeUint(index, 16).storeUint(toBigInt(id), 256)); + return builder.endCell().hash().toString("hex"); +} + +export function protocolVersions(): { schemaVersion: number; isaVersion: number; abiVersion: number; protocolVersion: number } { + return { schemaVersion: SCHEMA_VERSION, isaVersion: ISA_VERSION, abiVersion: ABI_VERSION, protocolVersion: PROTOCOL_VERSION }; +} + +function toBigInt(value: Hash256): bigint { + if (!/^[0-9a-f]{64}$/i.test(value)) throw new RangeError("commitment must be a 256-bit hexadecimal value"); + return BigInt(`0x${value}`); +} From f553a035cbca9cfa887b605531b98f2224cd983c Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:14:38 +0100 Subject: [PATCH 19/67] feat: build and verify reproducible v2 artifacts --- packages/artifact/src/index.ts | 5 + packages/artifact/src/manifest.ts | 270 ++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+) create mode 100644 packages/artifact/src/index.ts create mode 100644 packages/artifact/src/manifest.ts diff --git a/packages/artifact/src/index.ts b/packages/artifact/src/index.ts new file mode 100644 index 0000000..0167728 --- /dev/null +++ b/packages/artifact/src/index.ts @@ -0,0 +1,5 @@ +export * from "./assembler.js"; +export * from "./commitments.js"; +export * from "./manifest.js"; +export * from "./memory.js"; +export * from "./routes.js"; diff --git a/packages/artifact/src/manifest.ts b/packages/artifact/src/manifest.ts new file mode 100644 index 0000000..93a5510 --- /dev/null +++ b/packages/artifact/src/manifest.ts @@ -0,0 +1,270 @@ +import { createHash } from "node:crypto"; +import { ABI_VERSION, ISA_VERSION, PROTOCOL_VERSION, RAM_NIBBLES, ROM_WORDS, SCHEMA_VERSION, WORD_BITS } from "../../isa/src/constants.js"; +import { assembleV2, type V2AssemblyResult } from "./assembler.js"; +import { deriveRunId, limitsHash, programId, protocolVersions, staticCommitment, type Hash256, type LimitsV2 } from "./commitments.js"; +import { normalizeRam, normalizeRom, ramRoot, ramRootCell, romRoot, romRootCell } from "./memory.js"; +import { normalizeRoutes, routeRoot, type RouteDescriptor } from "./routes.js"; + +export interface CompilerInfo { + readonly name: string; + readonly version: string; + readonly gitCommit: string; + readonly node: string; +} + +export interface ArtifactManifestV2 { + readonly artifactVersion: 2; + readonly name: string; + readonly description: string; + readonly schemaVersion: 2; + readonly isaVersion: 2; + readonly abiVersion: 2; + readonly protocolVersion: 2; + readonly wordBits: 16; + readonly romWords: 1024; + readonly ramNibbles: 256; + readonly entryPc: number; + readonly coreId: number; + readonly runId: Hash256; + readonly runSalt: Hash256; + readonly romRoot: Hash256; + readonly initialRamRoot: Hash256; + readonly routeRoot: Hash256; + readonly programId: Hash256; + readonly limitsHash: Hash256; + readonly staticCommitment: Hash256; + readonly sourceHash: Hash256; + readonly compiler: CompilerInfo; +} + +export interface BuildArtifactOptions { + readonly name: string; + readonly description?: string; + readonly fileName?: string; + readonly coreId?: number; + readonly runId?: Hash256; + readonly runSalt?: Hash256; + readonly routes?: readonly RouteDescriptor[]; + readonly limits?: Partial; + readonly compiler?: Partial; +} + +export interface ArtifactBundle { + readonly manifest: ArtifactManifestV2; + readonly source: string; + readonly assembly: V2AssemblyResult; + readonly routes: readonly RouteDescriptor[]; + readonly limits: LimitsV2; + readonly files: Readonly>; +} + +export interface ArtifactVerificationReport { + readonly valid: boolean; + readonly errors: readonly string[]; + readonly computed: Readonly>; +} + +export const DEFAULT_LIMITS: LimitsV2 = { + maxStepsPerAdvance: 1, + maxInputRecords: 16, + maxOutputRecords: 16, + maxActionsPerAdvance: 32, + maxOutputsPerAdvance: 16, +}; + +export function buildArtifact(source: string, options: BuildArtifactOptions): ArtifactBundle { + const assembly = assembleV2(source, options.fileName ?? "program.tasm"); + const routes = normalizeRoutes(options.routes ?? []); + const limits: LimitsV2 = { + ...DEFAULT_LIMITS, + ...options.limits, + }; + validateLimits(limits); + const romRootValue = romRoot(assembly.words); + const initialRamRoot = ramRoot(assembly.ram); + const routeRootValue = routeRoot(routes); + const programIdValue = programId({ + artifactVersion: 2, + isaVersion: ISA_VERSION, + wordBits: WORD_BITS, + romWords: ROM_WORDS, + ramNibbles: RAM_NIBBLES, + entryPc: assembly.entryPc, + romRoot: romRootValue, + initialRamRoot, + }); + const runSalt = options.runSalt ?? zeroHash(); + const runId = options.runId ?? deriveRunId([programIdValue], routeRootValue, runSalt); + const limitsHashValue = limitsHash(limits); + const staticCommitmentValue = staticCommitment({ + schemaVersion: SCHEMA_VERSION, + isaVersion: ISA_VERSION, + abiVersion: ABI_VERSION, + protocolVersion: PROTOCOL_VERSION, + runId, + coreId: options.coreId ?? 0, + programId: programIdValue, + romRoot: romRootValue, + routeRoot: routeRootValue, + limitsHash: limitsHashValue, + }); + const compiler: CompilerInfo = { + name: options.compiler?.name ?? "tonolith-assembler", + version: options.compiler?.version ?? "0.1.0", + gitCommit: options.compiler?.gitCommit ?? "0".repeat(40), + node: options.compiler?.node ?? process.version, + }; + validateCompiler(compiler); + const manifest: ArtifactManifestV2 = { + artifactVersion: 2, + name: options.name, + description: options.description ?? "", + schemaVersion: 2, + isaVersion: 2, + abiVersion: 2, + protocolVersion: 2, + wordBits: 16, + romWords: 1024, + ramNibbles: 256, + entryPc: assembly.entryPc, + coreId: options.coreId ?? 0, + runId, + runSalt, + romRoot: romRootValue, + initialRamRoot, + routeRoot: routeRootValue, + programId: programIdValue, + limitsHash: limitsHashValue, + staticCommitment: staticCommitmentValue, + sourceHash: sha256(assembly.canonicalSource), + compiler, + }; + const files = makeFiles(manifest, source, assembly, routes); + return { manifest, source, assembly, routes, limits, files }; +} + +export function verifyArtifact(bundle: ArtifactBundle): ArtifactVerificationReport { + const errors: string[] = []; + let computedValues: Record = {}; + const manifest = bundle.manifest; + const versions = protocolVersions(); + if (manifest.artifactVersion !== 2) errors.push("unsupported artifactVersion"); + if (manifest.schemaVersion !== versions.schemaVersion) errors.push("unsupported schemaVersion"); + if (manifest.isaVersion !== versions.isaVersion) errors.push("unsupported isaVersion"); + if (manifest.abiVersion !== versions.abiVersion) errors.push("unsupported abiVersion"); + if (manifest.protocolVersion !== versions.protocolVersion) errors.push("unsupported protocolVersion"); + for (const field of ["runId", "runSalt", "romRoot", "initialRamRoot", "routeRoot", "programId", "limitsHash", "staticCommitment", "sourceHash"] as const) { + if (!/^[0-9a-f]{64}$/.test(manifest[field])) errors.push(`${field} is not canonical lowercase uint256 hex`); + } + try { + const computedRomRoot = romRoot(bundle.assembly.words); + const computedRamRoot = ramRoot(bundle.assembly.ram); + const computedRouteRoot = routeRoot(bundle.routes); + const computedProgramId = programId({ + artifactVersion: 2, + isaVersion: 2, + wordBits: WORD_BITS, + romWords: ROM_WORDS, + ramNibbles: RAM_NIBBLES, + entryPc: bundle.assembly.entryPc, + romRoot: computedRomRoot, + initialRamRoot: computedRamRoot, + }); + const computedLimitsHash = limitsHash(bundle.limits); + const computedStatic = staticCommitment({ + schemaVersion: 2, + isaVersion: 2, + abiVersion: 2, + protocolVersion: 2, + runId: manifest.runId, + coreId: manifest.coreId, + programId: computedProgramId, + romRoot: computedRomRoot, + routeRoot: computedRouteRoot, + limitsHash: computedLimitsHash, + }); + const computedRunId = deriveRunId([computedProgramId], computedRouteRoot, manifest.runSalt); + const computed = { + romRoot: computedRomRoot, + initialRamRoot: computedRamRoot, + routeRoot: computedRouteRoot, + programId: computedProgramId, + limitsHash: computedLimitsHash, + staticCommitment: computedStatic, + derivedRunId: computedRunId, + sourceHash: sha256(bundle.assembly.canonicalSource), + }; + computedValues = computed; + for (const [field, value] of Object.entries(computed)) { + const manifestField = field === "derivedRunId" ? "runId" : field; + if (manifest[manifestField as keyof ArtifactManifestV2] !== value) errors.push(`${manifestField} does not match recomputed commitment`); + } + if (manifest.entryPc !== bundle.assembly.entryPc) errors.push("entryPc does not match assembly"); + if (manifest.coreId < 0 || manifest.coreId > 0xffff || !Number.isInteger(manifest.coreId)) errors.push("coreId is out of range"); + validateCompiler(manifest.compiler); + validateLimits(bundle.limits); + const artifactJson = bundle.files["artifact.json"]; + if (artifactJson !== undefined && artifactJson !== canonicalJson(manifest)) errors.push("artifact.json is not canonical or does not match manifest"); + } catch (error) { + errors.push(error instanceof Error ? error.message : String(error)); + } + return { + valid: errors.length === 0, + errors, + computed: computedValues, + }; +} + +export function assertArtifact(bundle: ArtifactBundle): void { + const report = verifyArtifact(bundle); + if (!report.valid) throw new Error(`invalid v2 artifact: ${report.errors.join("; ")}`); +} + +export function canonicalJson(value: unknown): string { + return JSON.stringify(sortJson(value)); +} + +function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2AssemblyResult, routes: readonly RouteDescriptor[]): Readonly> { + const artifactJson = canonicalJson(manifest); + return { + "artifact.json": artifactJson, + "program.tasm": source, + "program.rom.boc": romRootCell(assembly.words).toBoc({ idx: false }).toString("base64"), + "initial-ram.boc": ramRootCell(assembly.ram).toBoc({ idx: false }).toString("base64"), + "symbols.json": canonicalJson({ labels: assembly.labels, exports: assembly.exports }), + "source-map.json": canonicalJson(assembly.sourceMap), + "routes.json": canonicalJson(routes), + "BUILDINFO.json": canonicalJson({ artifactSha256: sha256(artifactJson), compiler: manifest.compiler, sourceHash: manifest.sourceHash }), + }; +} + +function sha256(value: string): string { + return createHash("sha256").update(value, "utf8").digest("hex"); +} + +function zeroHash(): Hash256 { + return "0".repeat(64); +} + +function sortJson(value: unknown): unknown { + if (Array.isArray(value)) return value.map(sortJson); + if (value !== null && typeof value === "object") { + return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, sortJson(entry)])); + } + if (typeof value === "number" && !Number.isFinite(value)) throw new TypeError("canonical JSON cannot contain non-finite numbers"); + return value; +} + +function validateCompiler(compiler: CompilerInfo): void { + if (compiler.name.length === 0 || compiler.version.length === 0 || compiler.node.length === 0 || !/^[0-9a-f]{40}$/.test(compiler.gitCommit)) { + throw new RangeError("compiler metadata is not canonical"); + } +} + +function validateLimits(limits: LimitsV2): void { + if (!Number.isInteger(limits.maxStepsPerAdvance) || limits.maxStepsPerAdvance < 1 || limits.maxStepsPerAdvance > 0xffff) throw new RangeError("maxStepsPerAdvance is out of range"); + if (!Number.isInteger(limits.maxInputRecords) || limits.maxInputRecords < 1 || limits.maxInputRecords > 16) throw new RangeError("maxInputRecords is out of range"); + if (!Number.isInteger(limits.maxOutputRecords) || limits.maxOutputRecords < 1 || limits.maxOutputRecords > 16) throw new RangeError("maxOutputRecords is out of range"); + if (!Number.isInteger(limits.maxActionsPerAdvance) || limits.maxActionsPerAdvance < 1 || limits.maxActionsPerAdvance > 32) throw new RangeError("maxActionsPerAdvance is out of range"); + if (!Number.isInteger(limits.maxOutputsPerAdvance) || limits.maxOutputsPerAdvance < 1 || limits.maxOutputsPerAdvance > 16) throw new RangeError("maxOutputsPerAdvance is out of range"); +} From 58b8fd8dc7370635469cd00d64f20e48bbf3a675 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:14:47 +0100 Subject: [PATCH 20/67] test: verify reproducible v2 artifact manifests --- tests/v2/artifact.test.ts | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/v2/artifact.test.ts diff --git a/tests/v2/artifact.test.ts b/tests/v2/artifact.test.ts new file mode 100644 index 0000000..a34c6e0 --- /dev/null +++ b/tests/v2/artifact.test.ts @@ -0,0 +1,70 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { Cell } from "@ton/core"; +import { programId as emulatorProgramId, staticCommitmentCell as emulatorStaticCommitmentCell } from "../../packages/emulator/src/commitments.js"; +import { + assertArtifact, + buildArtifact, + canonicalJson, + verifyArtifact, +} from "../../packages/artifact/src/manifest.js"; + +const compiler = { + name: "tonolith-assembler", + version: "2.0.0-test", + gitCommit: "a".repeat(40), + node: "v26.7.0", +}; + +test("v2 artifact builder emits deterministic files and independently matching commitments", () => { + const source = `.entry start\n.ram 0x04 0xA\nstart:\nLDI 1\nOUT\nHALT\n`; + const first = buildArtifact(source, { name: "fixture", description: "deterministic fixture", compiler }); + const second = buildArtifact(source, { name: "fixture", description: "deterministic fixture", compiler }); + assert.deepEqual(first.manifest, second.manifest); + assert.deepEqual(first.files, second.files); + assert.equal(verifyArtifact(first).valid, true); + assert.doesNotThrow(() => assertArtifact(first)); + assert.equal(first.files["artifact.json"], canonicalJson(first.manifest)); + assert.equal(Cell.fromBoc(Buffer.from(first.files["program.rom.boc"]!, "base64"))[0]?.hash().toString("hex"), first.manifest.romRoot); + assert.equal(Cell.fromBoc(Buffer.from(first.files["initial-ram.boc"]!, "base64"))[0]?.hash().toString("hex"), first.manifest.initialRamRoot); + + const emulatorProgram = emulatorProgramId({ + artifactVersion: 2, + isaVersion: 2, + wordBits: 16, + romWords: 1024, + ramNibbles: 256, + entryPc: first.manifest.entryPc, + romRoot: first.manifest.romRoot, + initialRamRoot: first.manifest.initialRamRoot, + }); + assert.equal(emulatorProgram, first.manifest.programId); + assert.equal(emulatorStaticCommitmentCell({ + schemaVersion: 2, + isaVersion: 2, + abiVersion: 2, + protocolVersion: 2, + runId: first.manifest.runId, + coreId: first.manifest.coreId, + programId: first.manifest.programId, + romRoot: first.manifest.romRoot, + routeRoot: first.manifest.routeRoot, + limitsHash: first.manifest.limitsHash, + }).hash().toString("hex"), first.manifest.staticCommitment); +}); + +test("artifact verification detects commitment and version tampering", () => { + const bundle = buildArtifact(".entry 0\nNOP\n", { name: "tamper-fixture", compiler }); + const tamperedManifest = { ...bundle.manifest, programId: "f".repeat(64) } as typeof bundle.manifest; + const report = verifyArtifact({ ...bundle, manifest: tamperedManifest }); + assert.equal(report.valid, false); + assert.match(report.errors.join("\n"), /programId|staticCommitment/); + + const badVersion = { ...bundle.manifest, isaVersion: 1 } as unknown as typeof bundle.manifest; + assert.equal(verifyArtifact({ ...bundle, manifest: badVersion }).valid, false); +}); + +test("artifact build rejects invalid compiler metadata and noncanonical limits", () => { + assert.throws(() => buildArtifact("NOP\n", { name: "bad", compiler: { ...compiler, gitCommit: "not-a-commit" } }), /compiler metadata/); + assert.throws(() => buildArtifact("NOP\n", { name: "bad", compiler, limits: { maxOutputRecords: 17 } }), /maxOutputRecords/); +}); From 55bd4d124cbdb1a5377fd4c56ac8418bcd5e5ba4 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:20:33 +0100 Subject: [PATCH 21/67] feat: add strict versioned v2 message ABI --- packages/abi/src/index.ts | 1 + packages/abi/src/messages.ts | 520 +++++++++++++++++++++++++++++++++++ 2 files changed, 521 insertions(+) create mode 100644 packages/abi/src/index.ts create mode 100644 packages/abi/src/messages.ts diff --git a/packages/abi/src/index.ts b/packages/abi/src/index.ts new file mode 100644 index 0000000..b6d1c75 --- /dev/null +++ b/packages/abi/src/index.ts @@ -0,0 +1 @@ +export * from "./messages.js"; diff --git a/packages/abi/src/messages.ts b/packages/abi/src/messages.ts new file mode 100644 index 0000000..8731dab --- /dev/null +++ b/packages/abi/src/messages.ts @@ -0,0 +1,520 @@ +import { beginCell, Cell, type Slice } from "@ton/core"; +import { ABI_VERSION, EVENT_PREFIX, MESSAGE_PREFIX } from "../../isa/src/constants.js"; + +export type Hash256 = string; + +export interface CommonV2Message { + readonly queryId: bigint; + readonly runId: Hash256; +} + +export interface AdvanceV2Message extends CommonV2Message { + readonly kind: "advance"; + readonly expectedAdvanceCount: bigint; + readonly expectedStateHash: Hash256; + readonly maxInstructions: number; + readonly maxOutputs: number; +} + +export interface DispatchOutputV2Message extends CommonV2Message { + readonly kind: "dispatchOutput"; + readonly expectedStateHash: Hash256; + readonly outputId: Hash256; +} + +export interface DeliverInputV2Message extends CommonV2Message { + readonly kind: "deliverInput"; + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly sourceEpoch: bigint; + readonly destinationEpoch: bigint; + readonly sourcePort: number; + readonly destinationPort: number; + readonly sequence: number; + readonly value: number; + readonly sourceInstructionCount: bigint; + readonly sourceStateHash: Hash256; +} + +export interface InputAcceptedV2Message extends CommonV2Message { + readonly kind: "inputAccepted"; + readonly outputId: Hash256; + readonly destinationCoreId: number; + readonly destinationEpoch: bigint; + readonly destinationStateHash: Hash256; +} + +export interface TopUpV2Message extends CommonV2Message { + readonly kind: "topUp"; +} + +export type V2Message = AdvanceV2Message | DispatchOutputV2Message | DeliverInputV2Message | InputAcceptedV2Message | TopUpV2Message; + +export interface BatchAdvancedV2Event { + readonly kind: "batchAdvanced"; + readonly queryId: bigint; + readonly runId: Hash256; + readonly coreId: number; + readonly advanceCount: bigint; + readonly epoch: bigint; + readonly startInstructionCount: bigint; + readonly endInstructionCount: bigint; + readonly stepsExecuted: number; + readonly outputsProduced: number; + readonly stopReason: number; + readonly previousStateHash: Hash256; + readonly nextStateHash: Hash256; + readonly batchCommitment: Hash256; + readonly finalPc: number; + readonly finalStatus: number; +} + +export interface OutputCommittedV2Event { + readonly kind: "outputCommitted"; + readonly queryId: bigint; + readonly runId: Hash256; + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly epoch: bigint; + readonly sourcePort: number; + readonly destinationPort: number; + readonly sequence: number; + readonly value: number; + readonly outputCommitment: Hash256; +} + +export interface InputCommittedV2Event { + readonly kind: "inputCommitted"; + readonly queryId: bigint; + readonly runId: Hash256; + readonly outputId: Hash256; + readonly sourceCoreId: number; + readonly destinationCoreId: number; + readonly epoch: bigint; + readonly destinationPort: number; + readonly sequence: number; + readonly inputCommitment: Hash256; +} + +export interface OutputAcknowledgedV2Event { + readonly kind: "outputAcknowledged"; + readonly queryId: bigint; + readonly runId: Hash256; + readonly outputId: Hash256; + readonly destinationCoreId: number; + readonly destinationEpoch: bigint; + readonly outputCommitment: Hash256; +} + +export type V2Event = BatchAdvancedV2Event | OutputCommittedV2Event | InputCommittedV2Event | OutputAcknowledgedV2Event; + +export class V2AbiError extends Error { + public readonly code: "SHORT_BODY" | "UNKNOWN_PREFIX" | "INVALID_VERSION" | "TRAILING_DATA" | "INVALID_FIELD"; + + public constructor(code: V2AbiError["code"], message: string) { + super(message); + this.name = "V2AbiError"; + this.code = code; + } +} + +export function encodeAdvanceV2(message: Omit): Cell { + return commonCell(MESSAGE_PREFIX.advance, message) + .storeUint(message.expectedAdvanceCount, 64) + .storeUint(toBigInt(message.expectedStateHash), 256) + .storeUint(message.maxInstructions, 16) + .storeUint(message.maxOutputs, 8) + .endCell(); +} + +export function decodeAdvanceV2(input: Cell | Slice): AdvanceV2Message { + const slice = parseSlice(input, MESSAGE_PREFIX.advance); + const common = readCommon(slice, MESSAGE_PREFIX.advance); + const message: AdvanceV2Message = { + kind: "advance", + ...common, + expectedAdvanceCount: slice.loadUintBig(64), + expectedStateHash: loadHash(slice), + maxInstructions: slice.loadUint(16), + maxOutputs: slice.loadUint(8), + }; + assertUnsigned(message.maxInstructions, 1, 0xffff, "maxInstructions"); + assertUnsigned(message.maxOutputs, 1, 0xff, "maxOutputs"); + finish(slice); + return message; +} + +export function encodeDispatchOutputV2(message: Omit): Cell { + return commonCell(MESSAGE_PREFIX.dispatchOutput, message) + .storeUint(toBigInt(message.expectedStateHash), 256) + .storeUint(toBigInt(message.outputId), 256) + .endCell(); +} + +export function decodeDispatchOutputV2(input: Cell | Slice): DispatchOutputV2Message { + const slice = parseSlice(input, MESSAGE_PREFIX.dispatchOutput); + const common = readCommon(slice, MESSAGE_PREFIX.dispatchOutput); + const message: DispatchOutputV2Message = { + kind: "dispatchOutput", + ...common, + expectedStateHash: loadHash(slice), + outputId: loadHash(slice), + }; + finish(slice); + return message; +} + +export function encodeDeliverInputV2(message: Omit): Cell { + const sourceState = beginCell().storeUint(toBigInt(message.sourceStateHash), 256).endCell(); + return commonCell(MESSAGE_PREFIX.deliverInput, message) + .storeUint(toBigInt(message.outputId), 256) + .storeUint(message.sourceCoreId, 16) + .storeUint(message.destinationCoreId, 16) + .storeUint(message.sourceEpoch, 64) + .storeUint(message.destinationEpoch, 64) + .storeUint(message.sourcePort, 4) + .storeUint(message.destinationPort, 4) + .storeUint(message.sequence, 8) + .storeUint(message.value, 4) + .storeUint(message.sourceInstructionCount, 64) + .storeRef(sourceState) + .endCell(); +} + +export function decodeDeliverInputV2(input: Cell | Slice): DeliverInputV2Message { + const slice = parseSlice(input, MESSAGE_PREFIX.deliverInput); + const common = readCommon(slice, MESSAGE_PREFIX.deliverInput); + const outputId = loadHash(slice); + const sourceCoreId = slice.loadUint(16); + const destinationCoreId = slice.loadUint(16); + const sourceEpoch = slice.loadUintBig(64); + const destinationEpoch = slice.loadUintBig(64); + const sourcePort = slice.loadUint(4); + const destinationPort = slice.loadUint(4); + const sequence = slice.loadUint(8); + const value = slice.loadUint(4); + const sourceInstructionCount = slice.loadUintBig(64); + const sourceStateHash = loadHashRef(slice); + const message: DeliverInputV2Message = { + kind: "deliverInput", + ...common, + outputId, + sourceCoreId, + destinationCoreId, + sourceEpoch, + destinationEpoch, + sourcePort, + destinationPort, + sequence, + value, + sourceInstructionCount, + sourceStateHash, + }; + assertUnsigned(message.sourcePort, 0, 15, "sourcePort"); + assertUnsigned(message.destinationPort, 0, 15, "destinationPort"); + assertUnsigned(message.value, 0, 15, "value"); + finish(slice); + return message; +} + +export function encodeInputAcceptedV2(message: Omit): Cell { + return commonCell(MESSAGE_PREFIX.inputAccepted, message) + .storeUint(toBigInt(message.outputId), 256) + .storeUint(message.destinationCoreId, 16) + .storeUint(message.destinationEpoch, 64) + .storeUint(toBigInt(message.destinationStateHash), 256) + .endCell(); +} + +export function decodeInputAcceptedV2(input: Cell | Slice): InputAcceptedV2Message { + const slice = parseSlice(input, MESSAGE_PREFIX.inputAccepted); + const common = readCommon(slice, MESSAGE_PREFIX.inputAccepted); + const message: InputAcceptedV2Message = { + kind: "inputAccepted", + ...common, + outputId: loadHash(slice), + destinationCoreId: slice.loadUint(16), + destinationEpoch: slice.loadUintBig(64), + destinationStateHash: loadHash(slice), + }; + finish(slice); + return message; +} + +export function encodeTopUpV2(message: Omit): Cell { + return commonCell(MESSAGE_PREFIX.topUp, message).endCell(); +} + +export function decodeTopUpV2(input: Cell | Slice): TopUpV2Message { + const slice = parseSlice(input, MESSAGE_PREFIX.topUp); + const common = readCommon(slice, MESSAGE_PREFIX.topUp); + finish(slice); + return { kind: "topUp", ...common }; +} + +export function decodeV2Message(input: Cell | Slice): V2Message { + const slice = asSlice(input); + if (slice.remainingBits < 32) throw new V2AbiError("SHORT_BODY", "message body does not contain an operation prefix"); + switch (slice.preloadUint(32)) { + case MESSAGE_PREFIX.advance: return decodeAdvanceV2(slice); + case MESSAGE_PREFIX.dispatchOutput: return decodeDispatchOutputV2(slice); + case MESSAGE_PREFIX.deliverInput: return decodeDeliverInputV2(slice); + case MESSAGE_PREFIX.inputAccepted: return decodeInputAcceptedV2(slice); + case MESSAGE_PREFIX.topUp: return decodeTopUpV2(slice); + default: throw new V2AbiError("UNKNOWN_PREFIX", "unknown v2 message prefix"); + } +} + +export function encodeBatchAdvancedV2(event: Omit): Cell { + const detail = beginCell() + .storeUint(toBigInt(event.previousStateHash), 256) + .storeUint(toBigInt(event.nextStateHash), 256) + .storeUint(toBigInt(event.batchCommitment), 256) + .storeUint(event.finalPc, 10) + .storeUint(event.finalStatus, 3) + .endCell(); + return beginCell() + .storeUint(EVENT_PREFIX.batchAdvanced, 32) + .storeUint(ABI_VERSION, 16) + .storeUint(event.queryId, 64) + .storeUint(toBigInt(event.runId), 256) + .storeUint(event.coreId, 16) + .storeUint(event.advanceCount, 64) + .storeUint(event.epoch, 64) + .storeUint(event.startInstructionCount, 64) + .storeUint(event.endInstructionCount, 64) + .storeUint(event.stepsExecuted, 16) + .storeUint(event.outputsProduced, 8) + .storeUint(event.stopReason, 8) + .storeRef(detail) + .endCell(); +} + +export function decodeBatchAdvancedV2(input: Cell | Slice): BatchAdvancedV2Event { + const slice = parseSlice(input, EVENT_PREFIX.batchAdvanced); + const common = readEventCommon(slice, EVENT_PREFIX.batchAdvanced); + const coreId = slice.loadUint(16); + const advanceCount = slice.loadUintBig(64); + const epoch = slice.loadUintBig(64); + const startInstructionCount = slice.loadUintBig(64); + const endInstructionCount = slice.loadUintBig(64); + const stepsExecuted = slice.loadUint(16); + const outputsProduced = slice.loadUint(8); + const stopReason = slice.loadUint(8); + const detail = slice.loadRef().beginParse(); + const previousStateHash = loadHash(detail); + const nextStateHash = loadHash(detail); + const batchCommitment = loadHash(detail); + const finalPc = detail.loadUint(10); + const finalStatus = detail.loadUint(3); + finish(detail); + const event: BatchAdvancedV2Event = { + kind: "batchAdvanced", + ...common, + coreId, + advanceCount, + epoch, + startInstructionCount, + endInstructionCount, + stepsExecuted, + outputsProduced, + stopReason, + previousStateHash, + nextStateHash, + batchCommitment, + finalPc, + finalStatus, + }; + finish(slice); + return event; +} + +export function encodeOutputCommittedV2(event: Omit): Cell { + return beginCell() + .storeUint(EVENT_PREFIX.outputCommitted, 32) + .storeUint(ABI_VERSION, 16) + .storeUint(event.queryId, 64) + .storeUint(toBigInt(event.runId), 256) + .storeUint(toBigInt(event.outputId), 256) + .storeUint(event.sourceCoreId, 16) + .storeUint(event.destinationCoreId, 16) + .storeUint(event.epoch, 64) + .storeUint(event.sourcePort, 4) + .storeUint(event.destinationPort, 4) + .storeUint(event.sequence, 8) + .storeUint(event.value, 4) + .storeUint(toBigInt(event.outputCommitment), 256) + .endCell(); +} + +export function decodeOutputCommittedV2(input: Cell | Slice): OutputCommittedV2Event { + const slice = parseSlice(input, EVENT_PREFIX.outputCommitted); + const common = readEventCommon(slice, EVENT_PREFIX.outputCommitted); + const event: OutputCommittedV2Event = { + kind: "outputCommitted", + ...common, + outputId: loadHash(slice), + sourceCoreId: slice.loadUint(16), + destinationCoreId: slice.loadUint(16), + epoch: slice.loadUintBig(64), + sourcePort: slice.loadUint(4), + destinationPort: slice.loadUint(4), + sequence: slice.loadUint(8), + value: slice.loadUint(4), + outputCommitment: loadHash(slice), + }; + finish(slice); + return event; +} + +export function encodeInputCommittedV2(event: Omit): Cell { + return beginCell() + .storeUint(EVENT_PREFIX.inputCommitted, 32) + .storeUint(ABI_VERSION, 16) + .storeUint(event.queryId, 64) + .storeUint(toBigInt(event.runId), 256) + .storeUint(toBigInt(event.outputId), 256) + .storeUint(event.sourceCoreId, 16) + .storeUint(event.destinationCoreId, 16) + .storeUint(event.epoch, 64) + .storeUint(event.destinationPort, 4) + .storeUint(event.sequence, 8) + .storeUint(toBigInt(event.inputCommitment), 256) + .endCell(); +} + +export function decodeInputCommittedV2(input: Cell | Slice): InputCommittedV2Event { + const slice = parseSlice(input, EVENT_PREFIX.inputCommitted); + const common = readEventCommon(slice, EVENT_PREFIX.inputCommitted); + const event: InputCommittedV2Event = { + kind: "inputCommitted", + ...common, + outputId: loadHash(slice), + sourceCoreId: slice.loadUint(16), + destinationCoreId: slice.loadUint(16), + epoch: slice.loadUintBig(64), + destinationPort: slice.loadUint(4), + sequence: slice.loadUint(8), + inputCommitment: loadHash(slice), + }; + finish(slice); + return event; +} + +export function encodeOutputAcknowledgedV2(event: Omit): Cell { + return beginCell() + .storeUint(EVENT_PREFIX.outputAcknowledged, 32) + .storeUint(ABI_VERSION, 16) + .storeUint(event.queryId, 64) + .storeUint(toBigInt(event.runId), 256) + .storeUint(toBigInt(event.outputId), 256) + .storeUint(event.destinationCoreId, 16) + .storeUint(event.destinationEpoch, 64) + .storeUint(toBigInt(event.outputCommitment), 256) + .endCell(); +} + +export function decodeOutputAcknowledgedV2(input: Cell | Slice): OutputAcknowledgedV2Event { + const slice = parseSlice(input, EVENT_PREFIX.outputAcknowledged); + const common = readEventCommon(slice, EVENT_PREFIX.outputAcknowledged); + const event: OutputAcknowledgedV2Event = { + kind: "outputAcknowledged", + ...common, + outputId: loadHash(slice), + destinationCoreId: slice.loadUint(16), + destinationEpoch: slice.loadUintBig(64), + outputCommitment: loadHash(slice), + }; + finish(slice); + return event; +} + +export function decodeV2Event(input: Cell | Slice): V2Event { + const slice = asSlice(input); + if (slice.remainingBits < 32) throw new V2AbiError("SHORT_BODY", "event body does not contain a prefix"); + switch (slice.preloadUint(32)) { + case EVENT_PREFIX.batchAdvanced: return decodeBatchAdvancedV2(slice); + case EVENT_PREFIX.outputCommitted: return decodeOutputCommittedV2(slice); + case EVENT_PREFIX.inputCommitted: return decodeInputCommittedV2(slice); + case EVENT_PREFIX.outputAcknowledged: return decodeOutputAcknowledgedV2(slice); + default: throw new V2AbiError("UNKNOWN_PREFIX", "unknown v2 event prefix"); + } +} + +export function encodeV2Event(event: V2Event): Cell { + switch (event.kind) { + case "batchAdvanced": return encodeBatchAdvancedV2(event); + case "outputCommitted": return encodeOutputCommittedV2(event); + case "inputCommitted": return encodeInputCommittedV2(event); + case "outputAcknowledged": return encodeOutputAcknowledgedV2(event); + } +} + +function commonCell(op: number, message: CommonV2Message): ReturnType { + return beginCell() + .storeUint(op, 32) + .storeUint(ABI_VERSION, 16) + .storeUint(message.queryId, 64) + .storeUint(toBigInt(message.runId), 256); +} + +function readCommon(slice: Slice, expectedOp: number): CommonV2Message { + const op = loadUintSafe(slice, 32, "operation"); + if (op !== expectedOp) throw new V2AbiError("UNKNOWN_PREFIX", "unexpected operation prefix"); + const version = loadUintSafe(slice, 16, "abiVersion"); + if (version !== ABI_VERSION) throw new V2AbiError("INVALID_VERSION", `unsupported ABI version ${version}`); + return { queryId: slice.loadUintBig(64), runId: loadHash(slice) }; +} + +function readEventCommon(slice: Slice, expectedEvent: number): CommonV2Message { + const event = loadUintSafe(slice, 32, "event"); + if (event !== expectedEvent) throw new V2AbiError("UNKNOWN_PREFIX", "unexpected event prefix"); + const version = loadUintSafe(slice, 16, "abiVersion"); + if (version !== ABI_VERSION) throw new V2AbiError("INVALID_VERSION", `unsupported ABI version ${version}`); + return { queryId: slice.loadUintBig(64), runId: loadHash(slice) }; +} + +function parseSlice(input: Cell | Slice, expectedPrefix: number): Slice { + const slice = asSlice(input); + if (slice.remainingBits < 32) throw new V2AbiError("SHORT_BODY", "body is shorter than the operation prefix"); + if (slice.preloadUint(32) !== expectedPrefix) throw new V2AbiError("UNKNOWN_PREFIX", "unexpected operation prefix"); + return slice; +} + +function asSlice(input: Cell | Slice): Slice { + return input instanceof Cell ? input.beginParse() : input; +} + +function finish(slice: Slice): void { + if (slice.remainingBits !== 0 || slice.remainingRefs !== 0) throw new V2AbiError("TRAILING_DATA", "message contains trailing bits or references"); +} + +function loadHash(slice: Slice): Hash256 { + return slice.loadUintBig(256).toString(16).padStart(64, "0"); +} + +function loadHashRef(slice: Slice): Hash256 { + const ref = slice.loadRef().beginParse(); + const hash = loadHash(ref); + finish(ref); + return hash; +} + +function toBigInt(value: Hash256): bigint { + if (!/^[0-9a-f]{64}$/i.test(value)) throw new V2AbiError("INVALID_FIELD", "hash must be a 256-bit hexadecimal value"); + return BigInt(`0x${value}`); +} + +function loadUintSafe(slice: Slice, bits: number, field: string): number { + try { + return slice.loadUint(bits); + } catch (error) { + throw new V2AbiError("SHORT_BODY", `cannot read ${field}: ${error instanceof Error ? error.message : String(error)}`); + } +} + +function assertUnsigned(value: number, minimum: number, maximum: number, field: string): void { + if (!Number.isInteger(value) || value < minimum || value > maximum) throw new V2AbiError("INVALID_FIELD", `${field} must be in ${minimum}..${maximum}`); +} From 8861fd0e9b4fedf57dd243d14be5639956cd6b3d Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:20:54 +0100 Subject: [PATCH 22/67] test: cover v2 ABI and event round trips --- tests/v2/abi.test.ts | 141 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/v2/abi.test.ts diff --git a/tests/v2/abi.test.ts b/tests/v2/abi.test.ts new file mode 100644 index 0000000..29a4653 --- /dev/null +++ b/tests/v2/abi.test.ts @@ -0,0 +1,141 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { beginCell } from "@ton/core"; +import { + V2AbiError, + decodeAdvanceV2, + decodeBatchAdvancedV2, + decodeDeliverInputV2, + decodeDispatchOutputV2, + decodeInputAcceptedV2, + decodeInputCommittedV2, + decodeOutputAcknowledgedV2, + decodeOutputCommittedV2, + decodeV2Event, + decodeTopUpV2, + decodeV2Message, + encodeAdvanceV2, + encodeBatchAdvancedV2, + encodeDeliverInputV2, + encodeDispatchOutputV2, + encodeInputAcceptedV2, + encodeInputCommittedV2, + encodeOutputAcknowledgedV2, + encodeOutputCommittedV2, + encodeTopUpV2, +} from "../../packages/abi/src/messages.js"; + +const runId = "11".repeat(32); +const hash = "22".repeat(32); + +test("v2 message serializers round-trip every mutating message", () => { + const messages = [ + encodeAdvanceV2({ queryId: 1n, runId, expectedAdvanceCount: 3n, expectedStateHash: hash, maxInstructions: 4, maxOutputs: 2 }), + encodeDispatchOutputV2({ queryId: 2n, runId, expectedStateHash: hash, outputId: "33".repeat(32) }), + encodeDeliverInputV2({ + queryId: 3n, + runId, + outputId: "33".repeat(32), + sourceCoreId: 1, + destinationCoreId: 2, + sourceEpoch: 4n, + destinationEpoch: 5n, + sourcePort: 6, + destinationPort: 7, + sequence: 8, + value: 9, + sourceInstructionCount: 10n, + sourceStateHash: hash, + }), + encodeInputAcceptedV2({ queryId: 4n, runId, outputId: "33".repeat(32), destinationCoreId: 2, destinationEpoch: 5n, destinationStateHash: hash }), + encodeTopUpV2({ queryId: 5n, runId }), + ]; + assert.equal(decodeAdvanceV2(messages[0]!).maxInstructions, 4); + assert.equal(decodeDispatchOutputV2(messages[1]!).outputId, "33".repeat(32)); + assert.equal(decodeDeliverInputV2(messages[2]!).value, 9); + assert.equal(decodeInputAcceptedV2(messages[3]!).destinationEpoch, 5n); + assert.equal(decodeTopUpV2(messages[4]!).kind, "topUp"); + assert.deepEqual(messages.map((message) => decodeV2Message(message).kind), ["advance", "dispatchOutput", "deliverInput", "inputAccepted", "topUp"]); +}); + +test("BatchAdvancedV2 event uses the fixed aggregate layout", () => { + const event = { + queryId: 7n, + runId, + coreId: 2, + advanceCount: 8n, + epoch: 9n, + startInstructionCount: 10n, + endInstructionCount: 11n, + stepsExecuted: 12, + outputsProduced: 1, + stopReason: 5, + previousStateHash: hash, + nextStateHash: "33".repeat(32), + batchCommitment: "44".repeat(32), + finalPc: 13, + finalStatus: 0, + }; + assert.deepEqual(decodeBatchAdvancedV2(encodeBatchAdvancedV2(event)), { kind: "batchAdvanced", ...event }); +}); + +test("optional output, input, and acknowledgement events have strict compact codecs", () => { + const output = { + queryId: 1n, + runId, + outputId: "33".repeat(32), + sourceCoreId: 0, + destinationCoreId: 1, + epoch: 2n, + sourcePort: 3, + destinationPort: 4, + sequence: 5, + value: 6, + outputCommitment: hash, + }; + const input = { + queryId: 2n, + runId, + outputId: output.outputId, + sourceCoreId: 0, + destinationCoreId: 1, + epoch: 2n, + destinationPort: 4, + sequence: 5, + inputCommitment: "44".repeat(32), + }; + const acknowledgement = { + queryId: 3n, + runId, + outputId: output.outputId, + destinationCoreId: 1, + destinationEpoch: 2n, + outputCommitment: "55".repeat(32), + }; + assert.deepEqual(decodeOutputCommittedV2(encodeOutputCommittedV2(output)), { kind: "outputCommitted", ...output }); + assert.deepEqual(decodeInputCommittedV2(encodeInputCommittedV2(input)), { kind: "inputCommitted", ...input }); + assert.deepEqual(decodeOutputAcknowledgedV2(encodeOutputAcknowledgedV2(acknowledgement)), { kind: "outputAcknowledged", ...acknowledgement }); + assert.equal(decodeV2Event(encodeInputCommittedV2(input)).kind, "inputCommitted"); +}); + +test("v2 ABI rejects short, wrong-version, unknown-prefix, and trailing data", () => { + assert.throws(() => decodeV2Message(beginCell().storeUint(1, 32).endCell()), (error: unknown) => error instanceof V2AbiError && error.code === "UNKNOWN_PREFIX"); + assert.throws(() => decodeV2Message(beginCell().storeUint(0x544e4c21, 32).storeUint(1, 16).endCell()), (error: unknown) => error instanceof V2AbiError && error.code === "INVALID_VERSION"); + assert.throws(() => decodeV2Message(beginCell().storeUint(0x544e4c21, 32).endCell()), (error: unknown) => error instanceof V2AbiError); + const trailing = beginCell() + .storeUint(0x544e4c25, 32) + .storeUint(2, 16) + .storeUint(1, 64) + .storeUint(BigInt(`0x${runId}`), 256) + .storeBit(1) + .endCell(); + assert.throws(() => decodeTopUpV2(trailing), (error: unknown) => error instanceof V2AbiError && error.code === "TRAILING_DATA"); + const ref = beginCell() + .storeUint(0x544e4c25, 32) + .storeUint(2, 16) + .storeUint(1, 64) + .storeUint(BigInt(`0x${runId}`), 256) + .storeRef(beginCell().endCell()) + .endCell(); + assert.throws(() => decodeTopUpV2(ref), (error: unknown) => error instanceof V2AbiError && error.code === "TRAILING_DATA"); +}); From 04e3d3f10c946bc9d21e9ba0b7e009d3d9afdd98 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:26:20 +0100 Subject: [PATCH 23/67] feat: define versioned v2 Tolk storage and ABI types --- contracts/v2/types.tolk | 268 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 contracts/v2/types.tolk diff --git a/contracts/v2/types.tolk b/contracts/v2/types.tolk new file mode 100644 index 0000000..6d14e28 --- /dev/null +++ b/contracts/v2/types.tolk @@ -0,0 +1,268 @@ +enum ErrorsV2 { + UnknownMessage = 100 + InvalidAbi = 101 + InvalidVersion = 102 + InvalidBody = 103 + InvalidValue = 104 + StaleAdvance = 120 + StateHashMismatch = 121 + InvalidRun = 122 + InvalidStaticCommitment = 123 + InvalidStatus = 124 + InvalidInstruction = 140 + InvalidPc = 141 + ArchitecturalInvariant = 142 + UnauthorizedPeer = 160 + InvalidRoute = 161 + InvalidEpoch = 162 + InvalidSequence = 163 + ConflictingReplay = 164 + QueueFull = 165 + NotFound = 166 + InsufficientReserve = 180 + InsufficientValue = 181 + InvalidStorage = 190 + ExternalMessageRejected = 191 +} + +const TONOLITH_MAGIC_V2: uint32 = 0x544E4C32 +const SCHEMA_VERSION_V2: uint16 = 2 +const ISA_VERSION_V2: uint16 = 2 +const ABI_VERSION_V2: uint16 = 2 +const PROTOCOL_VERSION_V2: uint16 = 2 +const DOMAIN_STATE_V2: uint32 = 0x544C4332 +const DOMAIN_EPOCH_V2: uint32 = 0x544C4532 +const DOMAIN_OUTPUT_V2: uint32 = 0x544C4F32 +const DOMAIN_INPUT_V2: uint32 = 0x544C4932 +const DOMAIN_ACK_V2: uint32 = 0x544C4132 +const DOMAIN_BATCH_V2: uint32 = 0x544C4232 +const MESSAGE_ADVANCE_V2: uint32 = 0x544E4C21 +const MESSAGE_DISPATCH_OUTPUT_V2: uint32 = 0x544E4C22 +const MESSAGE_DELIVER_INPUT_V2: uint32 = 0x544E4C23 +const MESSAGE_INPUT_ACCEPTED_V2: uint32 = 0x544E4C24 +const MESSAGE_TOP_UP_V2: uint32 = 0x544E4C25 +const EVENT_BATCH_ADVANCED_V2: uint32 = 0x544E4CA1 +const EVENT_OUTPUT_COMMITTED_V2: uint32 = 0x544E4CA2 +const EVENT_INPUT_COMMITTED_V2: uint32 = 0x544E4CA3 +const EVENT_OUTPUT_ACKNOWLEDGED_V2: uint32 = 0x544E4CA4 + +const FLAG_ZERO_V2: uint2 = 1 +const FLAG_CARRY_V2: uint2 = 2 +const STATUS_RUNNING_V2: uint3 = 0 +const STATUS_WAITING_INPUT_V2: uint3 = 1 +const STATUS_BACKPRESSURED_V2: uint3 = 2 +const STATUS_HALTED_V2: uint3 = 3 +const STATUS_FAULTED_V2: uint3 = 4 +const STOP_REQUEST_LIMIT_V2: uint8 = 0 +const STOP_HALTED_V2: uint8 = 1 +const STOP_FAULTED_V2: uint8 = 2 +const STOP_INPUT_UNAVAILABLE_V2: uint8 = 3 +const STOP_OUTPUT_BACKPRESSURE_V2: uint8 = 4 +const STOP_YIELDED_V2: uint8 = 5 +const STOP_OUTPUT_EVENT_LIMIT_V2: uint8 = 6 +const MAX_STEPS_PER_ADVANCE_V2: uint16 = 1 +const MAX_INPUT_RECORDS_V2: uint8 = 16 +const MAX_OUTPUT_RECORDS_V2: uint8 = 16 + +struct V2Hash { + value: uint256 +} + +struct V2CoreCommitments { + outputCommitment: uint256 + inputCommitment: uint256 + epochHistoryCommitment: uint256 +} + +struct V2Registers { + packed: uint64 +} + +struct V2RamPage { + packed: uint256 +} + +struct V2RamRoot { + page0: Cell + page1: Cell + page2: Cell + page3: Cell +} + +struct V2QueueRoots { + inboxRoot: cell + outboxRoot: cell +} + +struct V2CoreState { + domain: uint32 + staticCommitment: uint256 + status: uint3 + faultCode: uint8 + pc: uint10 + accumulator: uint4 + flags: uint2 + epoch: uint64 + instructionCount: uint64 + advanceCount: uint64 + acceptedMessageCount: uint64 + outputCount: uint64 + inputCount: uint64 + nextOutputSequence: uint128 + commitments: Cell + memory: cell + queues: Cell +} + +struct V2StaticRoots { + romRoot: uint256 + routeRoot: uint256 + limitsHash: uint256 +} + +struct V2StaticData { + coreId: uint16 + runId: uint256 + staticCommitment: uint256 + programId: uint256 + roots: Cell +} + +struct V2Storage { + magic: uint32 + schemaVersion: uint16 + isaVersion: uint16 + abiVersion: uint16 + protocolVersion: uint16 + identity: Cell + core: Cell + rom: cell + routes: cell +} + +fun V2Storage.load(): V2Storage { + return V2Storage.fromCell(contract.getData()); +} + +fun V2Storage.save(self) { + contract.setData(self.toCell()); +} + +struct (0x544E4C21) V2Advance { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + expectedAdvanceCount: uint64 + expectedStateHash: uint256 + maxInstructions: uint16 + maxOutputs: uint8 +} + +struct (0x544E4C22) V2DispatchOutput { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + expectedStateHash: uint256 + outputId: uint256 +} + +struct (0x544E4C23) V2DeliverInput { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + outputId: uint256 + sourceCoreId: uint16 + destinationCoreId: uint16 + sourceEpoch: uint64 + destinationEpoch: uint64 + sourcePort: uint4 + destinationPort: uint4 + sequence: uint8 + value: uint4 + sourceInstructionCount: uint64 + sourceState: Cell +} + +struct (0x544E4C24) V2InputAccepted { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + outputId: uint256 + destinationCoreId: uint16 + destinationEpoch: uint64 + destinationStateHash: uint256 +} + +struct (0x544E4C25) V2TopUp { + abiVersion: uint16 + queryId: uint64 + runId: uint256 +} + +struct (0x544E4CA1) V2BatchAdvanced { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + coreId: uint16 + advanceCount: uint64 + epoch: uint64 + startInstructionCount: uint64 + endInstructionCount: uint64 + stepsExecuted: uint16 + outputsProduced: uint8 + stopReason: uint8 + detail: Cell +} + +struct V2BatchDetail { + previousStateHash: uint256 + nextStateHash: uint256 + batchCommitment: uint256 + finalPc: uint10 + finalStatus: uint3 +} + +struct (0x544E4CA2) V2OutputCommitted { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + outputId: uint256 + sourceCoreId: uint16 + destinationCoreId: uint16 + epoch: uint64 + sourcePort: uint4 + destinationPort: uint4 + sequence: uint8 + value: uint4 + outputCommitment: uint256 +} + +struct (0x544E4CA3) V2InputCommitted { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + outputId: uint256 + sourceCoreId: uint16 + destinationCoreId: uint16 + epoch: uint64 + destinationPort: uint4 + sequence: uint8 + inputCommitment: uint256 +} + +struct (0x544E4CA4) V2OutputAcknowledged { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + outputId: uint256 + destinationCoreId: uint16 + destinationEpoch: uint64 + outputCommitment: uint256 +} + +type V2AllowedMessage = + | V2Advance + | V2DispatchOutput + | V2DeliverInput + | V2InputAccepted + | V2TopUp From 7d4141ec8c62fa2579603130310622cd5b3b7404 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 19:26:31 +0100 Subject: [PATCH 24/67] feat: register the v2 core with Acton --- Acton.toml | 5 + contracts/TonolithCoreV2.tolk | 212 ++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 contracts/TonolithCoreV2.tolk diff --git a/Acton.toml b/Acton.toml index a9f35e1..0cd05fc 100644 --- a/Acton.toml +++ b/Acton.toml @@ -12,6 +12,11 @@ display-name = "TonolithCpu" src = "contracts/TonolithCpu.tolk" depends = [] +[contracts.TonolithCoreV2] +display-name = "TonolithCoreV2" +src = "contracts/TonolithCoreV2.tolk" +depends = [] + [test] [scripts] diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk new file mode 100644 index 0000000..e32f924 --- /dev/null +++ b/contracts/TonolithCoreV2.tolk @@ -0,0 +1,212 @@ +import "v2/types" + +const MIN_ADVANCE_VALUE_V2: coins = ton("0.01") +const STORAGE_RESERVE_V2: coins = ton("0.1") + +contract TonolithCoreV2 { + author: "ef-code" + version: "2.0.0" + description: "Tonolith v2 deterministic architectural processor core" + storage: V2Storage + incomingMessages: V2AllowedMessage + incomingExternal: slice + emittedEvents: V2BatchAdvanced | V2OutputCommitted | V2InputCommitted | V2OutputAcknowledged + thrownErrors: ErrorsV2 +} + +fun emitBatchV2(event: V2BatchAdvanced) { + val message = createExternalLogMessage({ + dest: createAddressNone(), + body: event, + }); + message.send(SEND_MODE_REGULAR); +} + +fun returnSafeExcessV2(sender: address) { + val refund = createMessage({ + bounce: BounceMode.NoBounce, + value: 0, + dest: sender, + }); + refund.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +} + +fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { + assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; + assert (msg.maxInstructions > 0 && msg.maxInstructions <= MAX_STEPS_PER_ADVANCE_V2) throw ErrorsV2.InvalidBody; + assert (msg.maxOutputs > 0 && msg.maxOutputs <= MAX_OUTPUT_RECORDS_V2) throw ErrorsV2.InvalidBody; + assert (value >= MIN_ADVANCE_VALUE_V2) throw ErrorsV2.InsufficientValue; + + var storage = V2Storage.load(); + assert (storage.magic == TONOLITH_MAGIC_V2) throw ErrorsV2.InvalidStorage; + assert (storage.schemaVersion == SCHEMA_VERSION_V2) throw ErrorsV2.InvalidStorage; + assert (storage.isaVersion == ISA_VERSION_V2) throw ErrorsV2.InvalidStorage; + assert (storage.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidStorage; + assert (storage.protocolVersion == PROTOCOL_VERSION_V2) throw ErrorsV2.InvalidStorage; + val identity = storage.identity.load(); + assert (msg.runId == identity.runId) throw ErrorsV2.InvalidRun; + + val previousStateHash = storage.core.hash(); + var core = storage.core.load(); + assert (core.domain == DOMAIN_STATE_V2) throw ErrorsV2.InvalidStorage; + assert (core.staticCommitment == identity.staticCommitment) throw ErrorsV2.InvalidStaticCommitment; + assert (core.status == STATUS_RUNNING_V2) throw ErrorsV2.InvalidStatus; + assert (msg.expectedAdvanceCount == core.advanceCount) throw ErrorsV2.StaleAdvance; + assert (msg.expectedStateHash == previousStateHash) throw ErrorsV2.StateHashMismatch; + + reserveToncoinsOnBalance( + STORAGE_RESERVE_V2, + RESERVE_MODE_AT_MOST | RESERVE_MODE_BOUNCE_ON_ACTION_FAIL, + ); + + val startInstructionCount = core.instructionCount; + core.advanceCount += 1; + core.acceptedMessageCount += 1; + storage.core = core.toCell(); + val nextStateHash = storage.core.hash(); + storage.save(); + + emitBatchV2(V2BatchAdvanced { + abiVersion: ABI_VERSION_V2, + queryId: msg.queryId, + runId: identity.runId, + coreId: identity.coreId, + advanceCount: core.advanceCount, + epoch: core.epoch, + startInstructionCount, + endInstructionCount: core.instructionCount, + stepsExecuted: 0, + outputsProduced: 0, + stopReason: STOP_REQUEST_LIMIT_V2, + detail: V2BatchDetail { + previousStateHash, + nextStateHash, + batchCommitment: 0, + finalPc: core.pc, + finalStatus: core.status, + }.toCell(), + }); + returnSafeExcessV2(sender); +} + +fun onInternalMessage(in: InMessage) { + if (in.body.isEmpty()) { + throw ErrorsV2.InvalidBody; + } + if (in.body.remainingBitsCount() < 32) { + throw ErrorsV2.InvalidBody; + } + val opcode = in.body.preloadUint(32); + if (opcode == MESSAGE_ADVANCE_V2) { + processAdvanceV2( + in.senderAddress, + in.valueCoins, + V2Advance.fromSlice(in.body, { assertEndAfterReading: true }), + ); + return; + } + if (opcode == MESSAGE_TOP_UP_V2) { + val msg = V2TopUp.fromSlice(in.body, { assertEndAfterReading: true }); + val storage = V2Storage.load(); + assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; + assert (msg.runId == storage.identity.load().runId) throw ErrorsV2.InvalidRun; + return; + } + throw ErrorsV2.UnknownMessage; +} + +fun onExternalMessage(_in: slice) { + throw ErrorsV2.ExternalMessageRejected; +} + +fun onBouncedMessage(_in: InMessageBounced) {} + +get fun getMagicV2(): uint32 { + val storage = lazy V2Storage.load(); + return storage.magic; +} + +get fun getSchemaVersionV2(): uint16 { + val storage = lazy V2Storage.load(); + return storage.schemaVersion; +} + +get fun getIsaVersionV2(): uint16 { + val storage = lazy V2Storage.load(); + return storage.isaVersion; +} + +get fun getAbiVersionV2(): uint16 { + val storage = lazy V2Storage.load(); + return storage.abiVersion; +} + +get fun getProtocolVersionV2(): uint16 { + val storage = lazy V2Storage.load(); + return storage.protocolVersion; +} + +get fun getCoreIdV2(): uint16 { + val storage = lazy V2Storage.load(); + return storage.identity.load().coreId; +} + +get fun getRunIdV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().runId; +} + +get fun getStaticCommitmentV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().staticCommitment; +} + +get fun getProgramIdV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().programId; +} + +get fun getRomRootV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().roots.load().romRoot; +} + +get fun getRouteRootV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().roots.load().routeRoot; +} + +get fun getCoreStateHashV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.hash(); +} + +get fun getAdvanceCountV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().advanceCount; +} + +get fun getInstructionCountV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().instructionCount; +} + +get fun getEpochV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().epoch; +} + +get fun getStatusV2(): uint3 { + val storage = lazy V2Storage.load(); + return storage.core.load().status; +} + +get fun getAccumulatorV2(): uint4 { + val storage = lazy V2Storage.load(); + return storage.core.load().accumulator; +} + +get fun getFlagsV2(): uint2 { + val storage = lazy V2Storage.load(); + return storage.core.load().flags; +} From 1d22ca4a42d850a3db3d8b66dd204fda76005b4a Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:00:50 +0100 Subject: [PATCH 25/67] feat: add executable v2 Tolk core foundation --- contracts/TonolithCoreV2.tolk | 84 ++++++- contracts/v2/commitments.tolk | 161 +++++++++++++ contracts/v2/execution.tolk | 372 +++++++++++++++++++++++++++++++ contracts/v2/memory.tolk | 133 +++++++++++ contracts/v2/queues.tolk | 199 +++++++++++++++++ contracts/v2/rom.tolk | 116 ++++++++++ contracts/v2/routes.tolk | 27 +++ contracts/v2/types.tolk | 80 ++++++- tests/tonolith_v2.test.tolk | 213 ++++++++++++++++++ wrappers/TonolithCoreV2.gen.tolk | 281 +++++++++++++++++++++++ 10 files changed, 1656 insertions(+), 10 deletions(-) create mode 100644 contracts/v2/commitments.tolk create mode 100644 contracts/v2/execution.tolk create mode 100644 contracts/v2/memory.tolk create mode 100644 contracts/v2/queues.tolk create mode 100644 contracts/v2/rom.tolk create mode 100644 contracts/v2/routes.tolk create mode 100644 tests/tonolith_v2.test.tolk create mode 100644 wrappers/TonolithCoreV2.gen.tolk diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index e32f924..4daf978 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -1,3 +1,6 @@ +import "v2/commitments" +import "v2/execution" +import "v2/rom" import "v2/types" const MIN_ADVANCE_VALUE_V2: coins = ton("0.01") @@ -31,10 +34,33 @@ fun returnSafeExcessV2(sender: address) { refund.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); } +fun v2StopReason(status: uint3, yielded: bool): uint8 { + if (status == STATUS_HALTED_V2) { + return STOP_HALTED_V2; + } + if (status == STATUS_FAULTED_V2) { + return STOP_FAULTED_V2; + } + if (status == STATUS_WAITING_INPUT_V2) { + return STOP_INPUT_UNAVAILABLE_V2; + } + if (status == STATUS_BACKPRESSURED_V2) { + return STOP_OUTPUT_BACKPRESSURE_V2; + } + if (yielded) { + return STOP_YIELDED_V2; + } + return STOP_REQUEST_LIMIT_V2; +} + fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; - assert (msg.maxInstructions > 0 && msg.maxInstructions <= MAX_STEPS_PER_ADVANCE_V2) throw ErrorsV2.InvalidBody; - assert (msg.maxOutputs > 0 && msg.maxOutputs <= MAX_OUTPUT_RECORDS_V2) throw ErrorsV2.InvalidBody; + assert ( + msg.maxInstructions > 0 && msg.maxInstructions <= MAX_STEPS_PER_ADVANCE_V2 + ) throw ErrorsV2.InvalidBody; + assert ( + msg.maxOutputs > 0 && msg.maxOutputs <= MAX_OUTPUT_RECORDS_V2 + ) throw ErrorsV2.InvalidBody; assert (value >= MIN_ADVANCE_VALUE_V2) throw ErrorsV2.InsufficientValue; var storage = V2Storage.load(); @@ -49,8 +75,12 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { val previousStateHash = storage.core.hash(); var core = storage.core.load(); assert (core.domain == DOMAIN_STATE_V2) throw ErrorsV2.InvalidStorage; - assert (core.staticCommitment == identity.staticCommitment) throw ErrorsV2.InvalidStaticCommitment; - assert (core.status == STATUS_RUNNING_V2) throw ErrorsV2.InvalidStatus; + assert ( + core.staticCommitment == identity.staticCommitment + ) throw ErrorsV2.InvalidStaticCommitment; + assert ( + core.status != STATUS_HALTED_V2 && core.status != STATUS_FAULTED_V2 + ) throw ErrorsV2.InvalidStatus; assert (msg.expectedAdvanceCount == core.advanceCount) throw ErrorsV2.StaleAdvance; assert (msg.expectedStateHash == previousStateHash) throw ErrorsV2.StateHashMismatch; @@ -60,6 +90,28 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { ); val startInstructionCount = core.instructionCount; + val startOutputCount = core.outputCount; + val startEpoch = core.epoch; + if (core.status == STATUS_WAITING_INPUT_V2 || core.status == STATUS_BACKPRESSURED_V2) { + core.status = STATUS_RUNNING_V2; + } + val word = v2RomWord(storage.rom, core.pc); + val _legacyOutput = v2ExecuteInstruction( + mutate core, + word, + identity.runId, + identity.coreId, + storage.routes, + previousStateHash, + ); + val executionStateHash = core.toCell().hash(); + val stepsExecuted = (core.instructionCount - startInstructionCount) as uint16; + val outputsProduced = (core.outputCount - startOutputCount) as uint8; + val yielded = core.epoch != startEpoch; + val stopReason = v2StopReason(core.status, yielded); + val trace = stepsExecuted == 0 + ? previousStateHash + : nextV2TraceCommitment(previousStateHash, word, executionStateHash); core.advanceCount += 1; core.acceptedMessageCount += 1; storage.core = core.toCell(); @@ -75,13 +127,22 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { epoch: core.epoch, startInstructionCount, endInstructionCount: core.instructionCount, - stepsExecuted: 0, - outputsProduced: 0, - stopReason: STOP_REQUEST_LIMIT_V2, + stepsExecuted, + outputsProduced, + stopReason, detail: V2BatchDetail { previousStateHash, nextStateHash, - batchCommitment: 0, + batchCommitment: nextV2BatchCommitment( + previousStateHash, + nextStateHash, + startInstructionCount, + core.instructionCount, + stepsExecuted, + outputsProduced, + stopReason, + trace, + ), finalPc: core.pc, finalStatus: core.status, }.toCell(), @@ -91,7 +152,7 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { fun onInternalMessage(in: InMessage) { if (in.body.isEmpty()) { - throw ErrorsV2.InvalidBody; + return; } if (in.body.remainingBitsCount() < 32) { throw ErrorsV2.InvalidBody; @@ -191,6 +252,11 @@ get fun getInstructionCountV2(): uint64 { return storage.core.load().instructionCount; } +get fun getPcV2(): uint10 { + val storage = lazy V2Storage.load(); + return storage.core.load().pc; +} + get fun getEpochV2(): uint64 { val storage = lazy V2Storage.load(); return storage.core.load().epoch; diff --git a/contracts/v2/commitments.tolk b/contracts/v2/commitments.tolk new file mode 100644 index 0000000..c73b9bd --- /dev/null +++ b/contracts/v2/commitments.tolk @@ -0,0 +1,161 @@ +import "types" + +struct V2LegacyOutput { + outputIndex: uint64 + instructionCount: uint64 + value: uint4 + outputCommitment: uint256 +} + +fun nextV2LegacyOutputCommitment( + previous: uint256, + outputIndex: uint64, + instructionCount: uint64, + value: uint4, +): uint256 { + return beginCell() + .storeUint(DOMAIN_OUTPUT_V2, 32) + .storeUint(previous, 256) + .storeUint(outputIndex, 64) + .storeUint(instructionCount, 64) + .storeUint(value, 4) + .endCell() + .hash(); +} + +fun nextV2AcknowledgementCommitment(previous: uint256, outputId: uint256): uint256 { + return beginCell() + .storeUint(DOMAIN_ACK_V2, 32) + .storeUint(previous, 256) + .storeUint(outputId, 256) + .endCell() + .hash(); +} + +fun v2OutputId( + runId: uint256, + sourceStateHash: uint256, + sourceCoreId: uint16, + destinationCoreId: uint16, + sourceEpoch: uint64, + destinationEpoch: uint64, + sourcePort: uint4, + destinationPort: uint4, + sequence: uint8, + value: uint4, + sourceInstructionCount: uint64, +): uint256 { + return beginCell() + .storeUint(DOMAIN_OUTPUT_V2, 32) + .storeUint(runId, 256) + .storeUint(sourceStateHash, 256) + .storeUint(sourceCoreId, 16) + .storeUint(destinationCoreId, 16) + .storeUint(sourceEpoch, 64) + .storeUint(destinationEpoch, 64) + .storeUint(sourcePort, 4) + .storeUint(destinationPort, 4) + .storeUint(sequence, 8) + .storeUint(value, 4) + .storeUint(sourceInstructionCount, 64) + .endCell() + .hash(); +} + +fun nextV2OutputCommitment( + previous: uint256, + outputId: uint256, + instructionCount: uint64, + value: uint4, +): uint256 { + return beginCell() + .storeUint(DOMAIN_OUTPUT_V2, 32) + .storeUint(previous, 256) + .storeUint(outputId, 256) + .storeUint(instructionCount, 64) + .storeUint(value, 4) + .endCell() + .hash(); +} + +fun nextV2InputCommitment( + previous: uint256, + outputId: uint256, + epoch: uint64, + sequence: uint8, + value: uint4, + consumed: uint1, +): uint256 { + return beginCell() + .storeUint(DOMAIN_INPUT_V2, 32) + .storeUint(previous, 256) + .storeUint(outputId, 256) + .storeUint(epoch, 64) + .storeUint(sequence, 8) + .storeUint(value, 4) + .storeUint(consumed, 1) + .endCell() + .hash(); +} + +fun nextV2EpochCommitment( + previous: uint256, + epoch: uint64, + stateHash: uint256, + outputCommitment: uint256, + inputCommitment: uint256, +): uint256 { + val payload = beginCell() + .storeUint(stateHash, 256) + .storeUint(outputCommitment, 256) + .storeUint(inputCommitment, 256) + .endCell(); + return beginCell() + .storeUint(DOMAIN_EPOCH_V2, 32) + .storeUint(previous, 256) + .storeUint(epoch, 64) + .storeRef(payload) + .endCell() + .hash(); +} + +fun nextV2TraceCommitment( + previous: uint256, + instructionWord: uint16, + nextStateHash: uint256, +): uint256 { + return beginCell() + .storeUint(DOMAIN_BATCH_V2, 32) + .storeUint(previous, 256) + .storeUint(instructionWord, 16) + .storeUint(nextStateHash, 256) + .endCell() + .hash(); +} + +fun nextV2BatchCommitment( + previousStateHash: uint256, + nextStateHash: uint256, + startInstructionCount: uint64, + endInstructionCount: uint64, + stepsExecuted: uint16, + outputsProduced: uint8, + stopReason: uint8, + traceCommitment: uint256, +): uint256 { + val detail = beginCell() + .storeUint(previousStateHash, 256) + .storeUint(nextStateHash, 256) + .storeUint(startInstructionCount, 64) + .storeUint(endInstructionCount, 64) + .storeUint(stepsExecuted, 16) + .storeUint(outputsProduced, 8) + .storeUint(stopReason, 8) + .storeUint(traceCommitment, 256) + .endCell(); + return beginCell().storeUint(DOMAIN_BATCH_V2, 32).storeRef(detail).endCell().hash(); +} + +fun v2StateHash(core: V2CoreState): uint256 { + return core.toCell().hash(); +} diff --git a/contracts/v2/execution.tolk b/contracts/v2/execution.tolk new file mode 100644 index 0000000..26429e1 --- /dev/null +++ b/contracts/v2/execution.tolk @@ -0,0 +1,372 @@ +import "commitments" +import "memory" +import "queues" +import "routes" +import "types" + +fun v2HasFlag(flags: uint2, flag: uint2): bool { + return (flags & flag) != 0; +} + +fun v2SetZeroFlag(flags: uint2, value: uint4): uint2 { + if (value == 0) { + return (flags | FLAG_ZERO_V2) as uint2; + } + return (flags & ~FLAG_ZERO_V2) as uint2; +} + +fun v2SetCarryFlag(flags: uint2, carry: bool): uint2 { + if (carry) { + return (flags | FLAG_CARRY_V2) as uint2; + } + return (flags & ~FLAG_CARRY_V2) as uint2; +} + +fun v2AddAccumulator(mutate core: V2CoreState, operand: uint4, includeCarry: bool) { + val incomingCarry = includeCarry && v2HasFlag(core.flags, FLAG_CARRY_V2) ? 1 : 0; + val sum = core.accumulator + operand + incomingCarry; + core.accumulator = (sum & 15) as uint4; + core.flags = v2SetCarryFlag(core.flags, sum >= 16); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); +} + +fun v2SubtractAccumulator(mutate core: V2CoreState, operand: uint4) { + val before = core.accumulator; + core.accumulator = ((before - operand) & 15) as uint4; + core.flags = v2SetCarryFlag(core.flags, before >= operand); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); +} + +fun v2LogicalAccumulator(mutate core: V2CoreState, value: uint4) { + core.accumulator = value; + core.flags = v2SetCarryFlag(core.flags, false); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); +} + +fun v2GetOutputSequence(packed: uint128, port: int): uint8 { + assert (port >= 0 && port < 16) throw ErrorsV2.InvalidRoute; + return ((packed >> (port * 8)) & 255) as uint8; +} + +fun v2SetOutputSequence(packed: uint128, port: int, sequence: uint8): uint128 { + assert (port >= 0 && port < 16) throw ErrorsV2.InvalidRoute; + val shift = port * 8; + val mask = 255 << shift; + return ((packed & ~mask) | (sequence << shift)) as uint128; +} + +fun v2ExecuteSys( + mutate core: V2CoreState, + operand: int, + runId: uint256, + coreId: uint16, + routes: cell, + beforeStateHash: uint256, +): V2LegacyOutput? { + val subop = operand & 15; + if (subop == 0) { + core.status = STATUS_HALTED_V2; + return null; + } + if (subop == 1) { + val outputIndex = core.outputCount; + val value = core.accumulator; + val commitment = nextV2LegacyOutputCommitment( + core.commitments.load().outputCommitment, + outputIndex, + core.instructionCount, + value, + ); + core.outputCount += 1; + var commitments = core.commitments.load(); + commitments.outputCommitment = commitment; + core.commitments = commitments.toCell(); + return V2LegacyOutput { + outputIndex, + instructionCount: core.instructionCount, + value, + outputCommitment: commitment, + }; + } + if (subop == 2) { + core.flags = v2SetCarryFlag(core.flags, false); + return null; + } + if (subop == 3) { + core.flags = v2SetCarryFlag(core.flags, true); + return null; + } + if (subop == 4) { + v2LogicalAccumulator(mutate core, ((~core.accumulator) & 15) as uint4); + return null; + } + if (subop == 5) { + val before = core.accumulator; + core.flags = v2SetCarryFlag(core.flags, (before & 8) != 0); + core.accumulator = ((before << 1) & 15) as uint4; + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (subop == 6) { + val before = core.accumulator; + core.flags = v2SetCarryFlag(core.flags, (before & 1) != 0); + core.accumulator = (before >> 1) as uint4; + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (subop == 7) { + val before = core.accumulator; + core.accumulator = ((before + 1) & 15) as uint4; + core.flags = v2SetCarryFlag(core.flags, before == 15); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (subop == 8) { + val before = core.accumulator; + core.accumulator = ((before - 1) & 15) as uint4; + core.flags = v2SetCarryFlag(core.flags, before != 0); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (subop == 9) { + val port = operand >> 4; + val queues = core.queues.load(); + val cursor = v2FindInput(queues.inboxRoot, core.epoch, port); + if (cursor.found == 0) { + core.pc = ((core.pc - 1) & 1023) as uint10; + core.instructionCount -= 1; + core.status = STATUS_WAITING_INPUT_V2; + return null; + } + val input = v2InputAt(queues.inboxRoot, cursor.index); + val consumed = V2InputRecord { + domain: input.domain, + outputId: input.outputId, + sourceCoreId: input.sourceCoreId, + destinationCoreId: input.destinationCoreId, + sourceEpoch: input.sourceEpoch, + destinationEpoch: input.destinationEpoch, + sourcePort: input.sourcePort, + destinationPort: input.destinationPort, + sequence: input.sequence, + value: input.value, + sourceInstructionCount: input.sourceInstructionCount, + sourceStateHash: input.sourceStateHash, + consumed: 1, + }.toCell(); + var newQueues = queues; + newQueues.inboxRoot = v2ReplaceRecordAt(queues.inboxRoot, cursor.index, consumed); + core.queues = newQueues.toCell(); + core.accumulator = input.value; + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + var commitments = core.commitments.load(); + commitments.inputCommitment = nextV2InputCommitment( + commitments.inputCommitment, + input.outputId, + input.destinationEpoch, + input.sequence, + input.value, + 1, + ); + core.commitments = commitments.toCell(); + return null; + } + if (subop == 10) { + val port = operand >> 4; + val queues = core.queues.load(); + assert (v2QueueCount(queues.outboxRoot) < MAX_OUTPUT_RECORDS_V2) throw ErrorsV2.QueueFull; + val route = v2FindRoute(routes, coreId, port); + assert (route != null) throw ErrorsV2.InvalidRoute; + val sequence = v2GetOutputSequence(core.nextOutputSequence, port); + assert (sequence < 255) throw ErrorsV2.InvalidSequence; + val destinationEpoch = core.epoch + route!.delay; + val outputId = v2OutputId( + runId, + beforeStateHash, + coreId, + route!.destinationCoreId, + core.epoch, + destinationEpoch, + port as uint4, + route!.destinationPort, + sequence, + core.accumulator, + core.instructionCount, + ); + val output = V2OutputRecord { + domain: DOMAIN_OUTPUT_V2, + outputId, + sourceCoreId: coreId, + destinationCoreId: route!.destinationCoreId, + sourceEpoch: core.epoch, + destinationEpoch, + sourcePort: port as uint4, + destinationPort: route!.destinationPort, + sequence, + value: core.accumulator, + sourceInstructionCount: core.instructionCount, + sourceStateHash: beforeStateHash, + }.toCell(); + var updatedQueues = queues; + updatedQueues.outboxRoot = v2AppendQueueRoot(queues.outboxRoot, output); + core.queues = updatedQueues.toCell(); + core.nextOutputSequence = v2SetOutputSequence(core.nextOutputSequence, port, sequence + 1); + core.outputCount += 1; + var outputCommitments = core.commitments.load(); + outputCommitments.outputCommitment = nextV2OutputCommitment( + outputCommitments.outputCommitment, + outputId, + core.instructionCount, + core.accumulator, + ); + core.commitments = outputCommitments.toCell(); + return null; + } + if (subop == 11) { + val commitments = core.commitments.load(); + val closedStateHash = core.toCell().hash(); + var nextCommitments = commitments; + nextCommitments.epochHistoryCommitment = nextV2EpochCommitment( + commitments.epochHistoryCommitment, + core.epoch, + closedStateHash, + commitments.outputCommitment, + commitments.inputCommitment, + ); + core.commitments = nextCommitments.toCell(); + core.epoch += 1; + core.status = STATUS_RUNNING_V2; + return null; + } + if (subop == 12) { + core.status = STATUS_FAULTED_V2; + core.faultCode = (operand >> 4) as uint8; + return null; + } + throw ErrorsV2.InvalidInstruction; +} + +fun v2ExecuteInstruction( + mutate core: V2CoreState, + word: uint16, + runId: uint256, + coreId: uint16, + routes: cell, + beforeStateHash: uint256, +): V2LegacyOutput? { + val opcode = word >> 12; + val operand = word & 0xFFF; + if (opcode == 0 && operand != 0) { + throw ErrorsV2.InvalidInstruction; + } + if (opcode == 1 && operand > 15) { + throw ErrorsV2.InvalidInstruction; + } + if (opcode >= 2 && opcode <= 9 && operand > 15) { + throw ErrorsV2.InvalidInstruction; + } + if ((opcode == 10 || opcode == 11) && operand > 255) { + throw ErrorsV2.InvalidInstruction; + } + if (opcode >= 12 && opcode <= 14 && operand > 1023) { + throw ErrorsV2.InvalidInstruction; + } + if (opcode == 15) { + val subop = operand & 15; + if (subop <= 8 || subop == 11) { + assert (operand == subop) throw ErrorsV2.InvalidInstruction; + } else if (subop == 9 || subop == 10) { + assert ((operand >> 4) < 16) throw ErrorsV2.InvalidInstruction; + } else if (subop == 12) { + // The upper eight bits are the architectural TRAP code. + } else { + throw ErrorsV2.InvalidInstruction; + } + } + core.pc = ((core.pc + 1) & 1023) as uint10; + core.instructionCount += 1; + + if (opcode == 0) { + return null; + } + if (opcode == 1) { + core.accumulator = operand as uint4; + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (opcode == 2) { + core.accumulator = readV2Register(core, operand); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (opcode == 3) { + writeV2Register(mutate core, operand, core.accumulator); + return null; + } + if (opcode == 4) { + v2AddAccumulator(mutate core, readV2Register(core, operand), false); + return null; + } + if (opcode == 5) { + v2AddAccumulator(mutate core, readV2Register(core, operand), true); + return null; + } + if (opcode == 6) { + v2SubtractAccumulator(mutate core, readV2Register(core, operand)); + return null; + } + if (opcode == 7) { + v2LogicalAccumulator( + mutate core, + (core.accumulator & readV2Register(core, operand)) as uint4, + ); + return null; + } + if (opcode == 8) { + v2LogicalAccumulator( + mutate core, + (core.accumulator | readV2Register(core, operand)) as uint4, + ); + return null; + } + if (opcode == 9) { + v2LogicalAccumulator( + mutate core, + (core.accumulator ^ readV2Register(core, operand)) as uint4, + ); + return null; + } + if (opcode == 10) { + core.accumulator = readV2Ram(core, operand); + core.flags = v2SetZeroFlag(core.flags, core.accumulator); + return null; + } + if (opcode == 11) { + writeV2Ram(mutate core, operand, core.accumulator); + return null; + } + if (opcode == 12) { + core.pc = operand as uint10; + return null; + } + if (opcode == 13) { + if (v2HasFlag(core.flags, FLAG_ZERO_V2)) { + core.pc = operand as uint10; + } + return null; + } + if (opcode == 14) { + if (v2HasFlag(core.flags, FLAG_CARRY_V2)) { + core.pc = operand as uint10; + } + return null; + } + return v2ExecuteSys( + mutate core, + operand, + runId, + coreId, + routes, + beforeStateHash, + ); +} diff --git a/contracts/v2/memory.tolk b/contracts/v2/memory.tolk new file mode 100644 index 0000000..e37c33b --- /dev/null +++ b/contracts/v2/memory.tolk @@ -0,0 +1,133 @@ +import "types" + +fun zeroV2RamPage(): Cell { + return V2RamPage { packed: 0 }.toCell(); +} + +fun zeroV2RamRoot(): Cell { + val page = zeroV2RamPage(); + return V2RamRoot { + page0: page, + page1: page, + page2: page, + page3: page, + }.toCell(); +} + +fun zeroV2Memory(): Cell { + return V2Memory { + registers: V2Registers { packed: 0 }.toCell(), + ram: zeroV2RamRoot(), + }.toCell(); +} + +fun emptyV2RecordRoot(): cell { + val page = beginCell().storeUint(0, 3).endCell(); + return beginCell() + .storeUint(0, 5) + .storeRef(page) + .storeRef(page) + .storeRef(page) + .storeRef(page) + .endCell(); +} + +fun emptyV2Queues(): Cell { + val empty = emptyV2RecordRoot(); + return V2QueueRoots { + inboxRoot: empty, + outboxRoot: empty, + }.toCell(); +} + +fun selectV2RamPage(root: V2RamRoot, index: int): Cell { + if (index == 0) { + return root.page0; + } + if (index == 1) { + return root.page1; + } + if (index == 2) { + return root.page2; + } + if (index == 3) { + return root.page3; + } + throw ErrorsV2.InvalidStorage; +} + +fun readV2Register(core: V2CoreState, index: int): uint4 { + assert (index >= 0 && index < 16) throw ErrorsV2.ArchitecturalInvariant; + val memory = core.memory.load(); + val registers = memory.registers.load(); + return ((registers.packed >> (index * 4)) & 15) as uint4; +} + +fun writeV2Register(mutate core: V2CoreState, index: int, value: uint4) { + assert (index >= 0 && index < 16) throw ErrorsV2.ArchitecturalInvariant; + val memory = core.memory.load(); + val registers = memory.registers.load(); + val shift = index * 4; + val mask = 15 << shift; + val packed = ((registers.packed & ~mask) | (value << shift)) as uint64; + val newRegisters = V2Registers { packed }.toCell(); + core.memory = V2Memory { + registers: newRegisters, + ram: memory.ram, + }.toCell(); +} + +fun readV2Ram(core: V2CoreState, address: int): uint4 { + assert (address >= 0 && address < 256) throw ErrorsV2.ArchitecturalInvariant; + val memory = core.memory.load(); + val page = selectV2RamPage(memory.ram.load(), address >> 6).load(); + val shift = (address & 63) * 4; + return ((page.packed >> shift) & 15) as uint4; +} + +fun writeV2Ram(mutate core: V2CoreState, address: int, value: uint4) { + assert (address >= 0 && address < 256) throw ErrorsV2.ArchitecturalInvariant; + val memory = core.memory.load(); + val root = memory.ram.load(); + val pageIndex = address >> 6; + val offset = address & 63; + val oldPage = selectV2RamPage(root, pageIndex).load(); + val shift = offset * 4; + val mask = 15 << shift; + val packed = ((oldPage.packed & ~mask) | (value << shift)) as uint256; + val newPage = V2RamPage { packed }.toCell(); + var newRoot: Cell; + if (pageIndex == 0) { + newRoot = V2RamRoot { + page0: newPage, + page1: root.page1, + page2: root.page2, + page3: root.page3, + }.toCell(); + } else if (pageIndex == 1) { + newRoot = V2RamRoot { + page0: root.page0, + page1: newPage, + page2: root.page2, + page3: root.page3, + }.toCell(); + } else if (pageIndex == 2) { + newRoot = V2RamRoot { + page0: root.page0, + page1: root.page1, + page2: newPage, + page3: root.page3, + }.toCell(); + } else { + newRoot = V2RamRoot { + page0: root.page0, + page1: root.page1, + page2: root.page2, + page3: newPage, + }.toCell(); + } + core.memory = V2Memory { + registers: memory.registers, + ram: newRoot, + }.toCell(); +} diff --git a/contracts/v2/queues.tolk b/contracts/v2/queues.tolk new file mode 100644 index 0000000..3412340 --- /dev/null +++ b/contracts/v2/queues.tolk @@ -0,0 +1,199 @@ +import "types" + +fun v2QueueCount(root: cell): int { + return root.beginParse().loadUint(5); +} + +fun v2QueuePageCount(page: cell): int { + return page.beginParse().loadUint(3); +} + +fun v2EmptyQueuePage(): cell { + return beginCell().storeUint(0, 3).endCell(); +} + +fun v2AppendQueuePage(page: cell, record: cell): cell { + var slice = page.beginParse(); + val count = slice.loadUint(3); + assert (count < 4) throw ErrorsV2.QueueFull; + var builder = beginCell().storeUint(count + 1, 3); + if (count > 0) { + builder.storeRef(slice.loadRef()); + } + if (count > 1) { + builder.storeRef(slice.loadRef()); + } + if (count > 2) { + builder.storeRef(slice.loadRef()); + } + builder.storeRef(record); + return builder.endCell(); +} + +fun v2BuildQueueRoot(count: int, page0: cell, page1: cell, page2: cell, page3: cell): cell { + return beginCell() + .storeUint(count, 5) + .storeRef(page0) + .storeRef(page1) + .storeRef(page2) + .storeRef(page3) + .endCell(); +} + +fun v2AppendQueueRoot(root: cell, record: cell): cell { + var slice = root.beginParse(); + val count = slice.loadUint(5); + assert (count < 16) throw ErrorsV2.QueueFull; + val page0 = slice.loadRef(); + val page1 = slice.loadRef(); + val page2 = slice.loadRef(); + val page3 = slice.loadRef(); + val pageIndex = count >> 2; + if (pageIndex == 0) { + return v2BuildQueueRoot(count + 1, v2AppendQueuePage(page0, record), page1, page2, page3); + } + if (pageIndex == 1) { + return v2BuildQueueRoot(count + 1, page0, v2AppendQueuePage(page1, record), page2, page3); + } + if (pageIndex == 2) { + return v2BuildQueueRoot(count + 1, page0, page1, v2AppendQueuePage(page2, record), page3); + } + return v2BuildQueueRoot(count + 1, page0, page1, page2, v2AppendQueuePage(page3, record)); +} + +fun v2RecordAt(root: cell, index: int): cell { + var rootSlice = root.beginParse(); + val count = rootSlice.loadUint(5); + assert (index >= 0 && index < count) throw ErrorsV2.NotFound; + val page0 = rootSlice.loadRef(); + val page1 = rootSlice.loadRef(); + val page2 = rootSlice.loadRef(); + val page3 = rootSlice.loadRef(); + val pageIndex = index >> 2; + val offset = index & 3; + var page = page0; + if (pageIndex == 1) { + page = page1; + } + if (pageIndex == 2) { + page = page2; + } + if (pageIndex == 3) { + page = page3; + } + var pageSlice = page.beginParse(); + val pageCount = pageSlice.loadUint(3); + assert (offset >= 0 && offset < pageCount) throw ErrorsV2.NotFound; + if (offset == 0) { + return pageSlice.loadRef(); + } + pageSlice.loadRef(); + if (offset == 1) { + return pageSlice.loadRef(); + } + pageSlice.loadRef(); + if (offset == 2) { + return pageSlice.loadRef(); + } + pageSlice.loadRef(); + return pageSlice.loadRef(); +} + +fun v2ReplaceQueuePageRecord(page: cell, offset: int, record: cell): cell { + var slice = page.beginParse(); + val count = slice.loadUint(3); + assert (offset >= 0 && offset < count) throw ErrorsV2.NotFound; + val records0 = offset == 0 ? record : slice.loadRef(); + val records1 = count > 1 ? (offset == 1 ? record : slice.loadRef()) : null; + val records2 = count > 2 ? (offset == 2 ? record : slice.loadRef()) : null; + val records3 = count > 3 ? (offset == 3 ? record : slice.loadRef()) : null; + var builder = beginCell().storeUint(count, 3).storeRef(records0); + if (records1 != null) { + builder.storeRef(records1!); + } + if (records2 != null) { + builder.storeRef(records2!); + } + if (records3 != null) { + builder.storeRef(records3!); + } + return builder.endCell(); +} + +fun v2ReplaceRecordAt(root: cell, index: int, record: cell): cell { + var rootSlice = root.beginParse(); + val count = rootSlice.loadUint(5); + assert (index >= 0 && index < count) throw ErrorsV2.NotFound; + val page0 = rootSlice.loadRef(); + val page1 = rootSlice.loadRef(); + val page2 = rootSlice.loadRef(); + val page3 = rootSlice.loadRef(); + val pageIndex = index >> 2; + val offset = index & 3; + if (pageIndex == 0) { + return v2BuildQueueRoot( + count, + v2ReplaceQueuePageRecord(page0, offset, record), + page1, + page2, + page3, + ); + } + if (pageIndex == 1) { + return v2BuildQueueRoot( + count, + page0, + v2ReplaceQueuePageRecord(page1, offset, record), + page2, + page3, + ); + } + if (pageIndex == 2) { + return v2BuildQueueRoot( + count, + page0, + page1, + v2ReplaceQueuePageRecord(page2, offset, record), + page3, + ); + } + return v2BuildQueueRoot( + count, + page0, + page1, + page2, + v2ReplaceQueuePageRecord(page3, offset, record), + ); +} + +fun v2OutputAt(root: cell, index: int): V2OutputRecord { + return (v2RecordAt(root, index) as Cell).load(); +} + +fun v2InputAt(root: cell, index: int): V2InputRecord { + return (v2RecordAt(root, index) as Cell).load(); +} + +fun v2FindInput(root: cell, epoch: uint64, port: int): V2InputCursor { + val count = v2QueueCount(root); + return v2FindInputFrom(root, epoch, port, 0, count); +} + +fun v2FindInputFrom(root: cell, epoch: uint64, port: int, index: int, count: int): V2InputCursor { + if (index >= count || index >= 16) { + return V2InputCursor { + found: 0, + index: 0, + value: 0, + }; + } + val input = v2InputAt(root, index); + if (input.destinationEpoch == epoch && input.destinationPort == port && input.consumed == 0) { + return V2InputCursor { + found: 1, + index: index as uint8, + value: input.value, + }; + } + return v2FindInputFrom(root, epoch, port, index + 1, count); +} diff --git a/contracts/v2/rom.tolk b/contracts/v2/rom.tolk new file mode 100644 index 0000000..7f36691 --- /dev/null +++ b/contracts/v2/rom.tolk @@ -0,0 +1,116 @@ +import "types" + +fun zeroV2RomLeaf(): Cell { + return V2RomLeaf { + firstHalf: 0, + secondHalf: 0, + }.toCell(); +} + +fun zeroV2RomLevel1(): Cell { + val leaf = zeroV2RomLeaf(); + return V2RomLevel1 { + leaf0: leaf, + leaf1: leaf, + leaf2: leaf, + leaf3: leaf, + }.toCell(); +} + +fun zeroV2RomLevel2(): Cell { + val level1 = zeroV2RomLevel1(); + return V2RomLevel2 { + level10: level1, + level11: level1, + level12: level1, + level13: level1, + }.toCell(); +} + +fun zeroV2RomRoot(): Cell { + val level2 = zeroV2RomLevel2(); + return V2RomRoot { + level20: level2, + level21: level2, + level22: level2, + level23: level2, + }.toCell(); +} + +fun v2RomWithFirstLeaf(firstLeaf: Cell): Cell { + val zeroLeaf = zeroV2RomLeaf(); + val firstLevel1 = V2RomLevel1 { + leaf0: firstLeaf, + leaf1: zeroLeaf, + leaf2: zeroLeaf, + leaf3: zeroLeaf, + }.toCell(); + val zeroLevel1 = zeroV2RomLevel1(); + val firstLevel2 = V2RomLevel2 { + level10: firstLevel1, + level11: zeroLevel1, + level12: zeroLevel1, + level13: zeroLevel1, + }.toCell(); + val zeroLevel2 = zeroV2RomLevel2(); + return V2RomRoot { + level20: firstLevel2, + level21: zeroLevel2, + level22: zeroLevel2, + level23: zeroLevel2, + }.toCell(); +} + +fun selectV2RomLevel1(level2: V2RomLevel2, index: int): V2RomLevel1 { + if (index == 0) { + return level2.level10.load(); + } + if (index == 1) { + return level2.level11.load(); + } + if (index == 2) { + return level2.level12.load(); + } + if (index == 3) { + return level2.level13.load(); + } + throw ErrorsV2.InvalidStorage; +} + +fun v2RomWord(romCell: cell, address: uint10): uint16 { + val root = (romCell as Cell).load(); + val leafIndex = address >> 5; + val wordIndex = address & 31; + val rootIndex = leafIndex >> 4; + var level2: V2RomLevel2; + if (rootIndex == 0) { + level2 = root.level20.load(); + } else if (rootIndex == 1) { + level2 = root.level21.load(); + } else if (rootIndex == 2) { + level2 = root.level22.load(); + } else if (rootIndex == 3) { + level2 = root.level23.load(); + } else { + throw ErrorsV2.InvalidPc; + } + val level1 = selectV2RomLevel1(level2, (leafIndex >> 2) & 3); + val leafIndexInLevel = leafIndex & 3; + var leafCell = level1.leaf0; + if (leafIndexInLevel == 1) { + leafCell = level1.leaf1; + } + if (leafIndexInLevel == 2) { + leafCell = level1.leaf2; + } + if (leafIndexInLevel == 3) { + leafCell = level1.leaf3; + } + val leaf = leafCell.load(); + if (wordIndex < 16) { + val shift = (15 - wordIndex) * 16; + return ((leaf.firstHalf >> shift) & 0xFFFF) as uint16; + } + val shift = (31 - wordIndex) * 16; + return ((leaf.secondHalf >> shift) & 0xFFFF) as uint16; +} diff --git a/contracts/v2/routes.tolk b/contracts/v2/routes.tolk new file mode 100644 index 0000000..b819f01 --- /dev/null +++ b/contracts/v2/routes.tolk @@ -0,0 +1,27 @@ +import "queues" +import "types" + +fun v2RouteAt(root: cell, index: int): V2Route { + return (v2RecordAt(root, index) as Cell).load(); +} + +fun v2FindRoute(routes: cell, sourceCoreId: uint16, sourcePort: int): V2Route? { + return v2FindRouteFrom(routes, sourceCoreId, sourcePort, 0, v2QueueCount(routes)); +} + +fun v2FindRouteFrom( + routes: cell, + sourceCoreId: uint16, + sourcePort: int, + index: int, + count: int, +): V2Route? { + if (index >= count || index >= 16) { + return null; + } + val route = v2RouteAt(routes, index); + if (route.sourceCoreId == sourceCoreId && route.sourcePort == sourcePort) { + return route; + } + return v2FindRouteFrom(routes, sourceCoreId, sourcePort, index + 1, count); +} diff --git a/contracts/v2/types.tolk b/contracts/v2/types.tolk index 6d14e28..0247381 100644 --- a/contracts/v2/types.tolk +++ b/contracts/v2/types.tolk @@ -89,11 +89,89 @@ struct V2RamRoot { page3: Cell } +struct V2RomLeaf { + firstHalf: uint256 + secondHalf: uint256 +} + +struct V2RomLevel1 { + leaf0: Cell + leaf1: Cell + leaf2: Cell + leaf3: Cell +} + +struct V2RomLevel2 { + level10: Cell + level11: Cell + level12: Cell + level13: Cell +} + +struct V2RomRoot { + level20: Cell + level21: Cell + level22: Cell + level23: Cell +} + +struct V2Memory { + registers: Cell + ram: Cell +} + struct V2QueueRoots { inboxRoot: cell outboxRoot: cell } +struct V2Route { + domain: uint32 + sourceCoreId: uint16 + sourcePort: uint4 + destinationCoreId: uint16 + destinationPort: uint4 + delay: uint8 + maxRecordsPerEpoch: uint8 +} + +struct V2OutputRecord { + domain: uint32 + outputId: uint256 + sourceCoreId: uint16 + destinationCoreId: uint16 + sourceEpoch: uint64 + destinationEpoch: uint64 + sourcePort: uint4 + destinationPort: uint4 + sequence: uint8 + value: uint4 + sourceInstructionCount: uint64 + sourceStateHash: uint256 +} + +struct V2InputRecord { + domain: uint32 + outputId: uint256 + sourceCoreId: uint16 + destinationCoreId: uint16 + sourceEpoch: uint64 + destinationEpoch: uint64 + sourcePort: uint4 + destinationPort: uint4 + sequence: uint8 + value: uint4 + sourceInstructionCount: uint64 + sourceStateHash: uint256 + consumed: uint1 +} + +struct V2InputCursor { + found: uint1 + index: uint8 + value: uint4 +} + struct V2CoreState { domain: uint32 staticCommitment: uint256 @@ -110,7 +188,7 @@ struct V2CoreState { inputCount: uint64 nextOutputSequence: uint128 commitments: Cell - memory: cell + memory: Cell queues: Cell } diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk new file mode 100644 index 0000000..e4cb219 --- /dev/null +++ b/tests/tonolith_v2.test.tolk @@ -0,0 +1,213 @@ +import "@acton/emulation/network" +import "@acton/emulation/testing" +import "@acton/testing/expect" + +import "@wrappers/TonolithCoreV2.gen" +import "@contracts/v2/memory" +import "@contracts/v2/rom" +import "@contracts/v2/types" + +fun makeV2StorageForTest(rom: Cell): V2Storage { + val staticRoots = V2StaticRoots { + romRoot: rom.hash(), + routeRoot: 0, + limitsHash: 0, + }.toCell(); + val identity = V2StaticData { + coreId: 0, + runId: 0, + staticCommitment: 0, + programId: 0, + roots: staticRoots, + }.toCell(); + val commitments = V2CoreCommitments { + outputCommitment: 0, + inputCommitment: 0, + epochHistoryCommitment: 0, + }.toCell(); + val core = V2CoreState { + domain: DOMAIN_STATE_V2, + staticCommitment: 0, + status: STATUS_RUNNING_V2, + faultCode: 0, + pc: 0, + accumulator: 0, + flags: 0, + epoch: 0, + instructionCount: 0, + advanceCount: 0, + acceptedMessageCount: 0, + outputCount: 0, + inputCount: 0, + nextOutputSequence: 0, + commitments, + memory: zeroV2Memory(), + queues: emptyV2Queues(), + }.toCell(); + return V2Storage { + magic: TONOLITH_MAGIC_V2, + schemaVersion: SCHEMA_VERSION_V2, + isaVersion: ISA_VERSION_V2, + abiVersion: ABI_VERSION_V2, + protocolVersion: PROTOCOL_VERSION_V2, + identity, + core, + rom, + routes: beginCell().endCell(), + }; +} + +fun romWithFirstWordV2(word: uint16): Cell { + return v2RomWithFirstLeaf( + V2RomLeaf { + firstHalf: (word as uint256) << 240, + secondHalf: 0, + }.toCell(), + ); +} + +fun sendAdvanceV2ForTest( + core: TonolithCoreV2, + from: address, + queryId: uint64, + runId: uint256, + expectedAdvanceCount: uint64, + expectedStateHash: uint256, + maxInstructions: uint16, + maxOutputs: uint8, + value: coins, +): SendResultList { + val advanceMessage = createMessage({ + bounce: false, + value, + dest: core.address, + body: V2Advance { + abiVersion: ABI_VERSION_V2, + queryId, + runId, + expectedAdvanceCount, + expectedStateHash, + maxInstructions, + maxOutputs, + }, + }); + return net.send(from, advanceMessage); +} + +get fun `test v2 deploy exposes immutable metadata and a canonical initial state`() { + val deployer = testing.treasury("v2-metadata-deployer"); + val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + + expect(core.getMagicV2()).toEqual(0x544E4C32); + expect(core.getSchemaVersionV2()).toEqual(2); + expect(core.getIsaVersionV2()).toEqual(2); + expect(core.getAbiVersionV2()).toEqual(2); + expect(core.getProtocolVersionV2()).toEqual(2); + expect(core.getCoreIdV2()).toEqual(0); + expect(core.getStatusV2()).toEqual(0); + expect(core.getEpochV2()).toEqual(0); + expect(core.getInstructionCountV2()).toEqual(0); + expect( + core.getCoreStateHashV2(), + ).toEqual(0x0782faed379592a6d47134282b8c495e6073033acb733ed5e629f8b24ddb14b9); +} + +get fun `test v2 advance executes a canonical instruction and rejects stale state`() { + val deployer = testing.treasury("v2-advance-deployer"); + val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x1001))); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + val initialHash = core.getCoreStateHashV2(); + val wire = V2Advance { + abiVersion: ABI_VERSION_V2, + queryId: 17, + runId: 0, + expectedAdvanceCount: 0, + expectedStateHash: initialHash, + maxInstructions: 1, + maxOutputs: 1, + }.toCell(); + expect( + V2Advance.fromSlice(wire.beginParse(), { assertEndAfterReading: true }).abiVersion, + ).toEqual(2); + val first = sendAdvanceV2ForTest( + core, + deployer.address, + 17, + 0, + 0, + initialHash, + 1, + 1, + ton("0.1"), + ); + expect(first).toHaveSuccessfulTx(); + expect(core.getInstructionCountV2()).toEqual(1); + expect(core.getAdvanceCountV2()).toEqual(1); + expect(core.getAccumulatorV2()).toEqual(1); + expect(core.getPcV2()).toEqual(1); + expect( + core.getCoreStateHashV2(), + ).toEqual(0xf05071c3bb072ab16ebe16a348fe1548ac806319b8cb95b284db1fce74e8694d); + val advancedHash = core.getCoreStateHashV2(); + expect(advancedHash).toNotEqual(initialHash); + + val stale = sendAdvanceV2ForTest( + core, + deployer.address, + 17, + 0, + 0, + initialHash, + 1, + 1, + ton("0.1"), + ); + expect(stale).toHaveFailedTx({ + from: deployer.address, + to: core.address, + exitCode: ErrorsV2.StaleAdvance, + }); + expect(core.getCoreStateHashV2()).toEqual(advancedHash); +} + +get fun `test v2 HALT is an architectural state and rejects subsequent advance`() { + val deployer = testing.treasury("v2-halt-deployer"); + val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF000))); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + val first = sendAdvanceV2ForTest( + core, + deployer.address, + 0, + 0, + 0, + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); + expect(first).toHaveSuccessfulTx(); + expect(core.getStatusV2()).toEqual(3); + val halted = sendAdvanceV2ForTest( + core, + deployer.address, + 1, + 0, + 1, + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); + expect(halted).toHaveFailedTx({ + from: deployer.address, + to: core.address, + exitCode: ErrorsV2.InvalidStatus, + }); +} diff --git a/wrappers/TonolithCoreV2.gen.tolk b/wrappers/TonolithCoreV2.gen.tolk new file mode 100644 index 0000000..2b8f52e --- /dev/null +++ b/wrappers/TonolithCoreV2.gen.tolk @@ -0,0 +1,281 @@ +// Auto-generated wrapper for contract 'TonolithCoreV2' +// +// This file is automatically generated by 'acton wrapper' +// Do not edit manually — changes will be overwritten + +import "@acton/build" +import "@acton/emulation/network" +import "@acton/testing/assert" +import "@contracts/v2/types" + +/// Storage `V2Storage` is defined in `@contracts/v2/types` +struct TonolithCoreV2 { + address: address + stateInit: ContractState? = null +} + +/// Creates a contract wrapper instance from the storage data +fun TonolithCoreV2.fromStorage( + storage: V2Storage, + toShard: AddressShardingOptions? = null, +): TonolithCoreV2 { + val stateInit = ContractState { + code: build("TonolithCoreV2"), + data: storage.toCell(), + }; + val address = AutoDeployAddress { stateInit, toShard }.calculateAddress(); + return TonolithCoreV2 { address, stateInit }; +} + +/// Creates a contract wrapper instance from the address +fun TonolithCoreV2.fromAddress(address: address): TonolithCoreV2 { + return TonolithCoreV2 { address }; +} + +/// Deploys the contract to the blockchain +fun TonolithCoreV2.deploy(self, from: address, config: SendParams = {}): SendResultList { + if (self.stateInit == null) { + Assert.fail("Cannot deploy a contract created with 'fromAddress' because it lacks state init for deployment"); + } + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: { stateInit: self.stateInit }, + }); + return net.send(from, genericMsg); +} + +fun TonolithCoreV2.sendV2Advance( + self, + from: address, + abiVersion: uint16, + queryId: uint64, + runId: uint256, + expectedAdvanceCount: uint64, + expectedStateHash: uint256, + maxInstructions: uint16, + maxOutputs: uint8, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body: V2Advance { + abiVersion, + queryId, + runId, + expectedAdvanceCount, + expectedStateHash, + maxInstructions, + maxOutputs, + }, + }); + return net.send(from, genericMsg); +} + +fun TonolithCoreV2.sendV2DispatchOutput( + self, + from: address, + abiVersion: uint16, + queryId: uint64, + runId: uint256, + expectedStateHash: uint256, + outputId: uint256, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body: V2DispatchOutput { abiVersion, queryId, runId, expectedStateHash, outputId }, + }); + return net.send(from, genericMsg); +} + +fun TonolithCoreV2.sendV2DeliverInput( + self, + from: address, + abiVersion: uint16, + queryId: uint64, + runId: uint256, + outputId: uint256, + sourceCoreId: uint16, + destinationCoreId: uint16, + sourceEpoch: uint64, + destinationEpoch: uint64, + sourcePort: uint4, + destinationPort: uint4, + sequence: uint8, + value: uint4, + sourceInstructionCount: uint64, + sourceState: V2Hash, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body: V2DeliverInput { + abiVersion, + queryId, + runId, + outputId, + sourceCoreId, + destinationCoreId, + sourceEpoch, + destinationEpoch, + sourcePort, + destinationPort, + sequence, + value, + sourceInstructionCount, + sourceState: sourceState.toCell(), + }, + }); + return net.send(from, genericMsg); +} + +fun TonolithCoreV2.sendV2InputAccepted( + self, + from: address, + abiVersion: uint16, + queryId: uint64, + runId: uint256, + outputId: uint256, + destinationCoreId: uint16, + destinationEpoch: uint64, + destinationStateHash: uint256, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body: V2InputAccepted { + abiVersion, + queryId, + runId, + outputId, + destinationCoreId, + destinationEpoch, + destinationStateHash, + }, + }); + return net.send(from, genericMsg); +} + +fun TonolithCoreV2.sendV2TopUp( + self, + from: address, + abiVersion: uint16, + queryId: uint64, + runId: uint256, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body: V2TopUp { abiVersion, queryId, runId }, + }); + return net.send(from, genericMsg); +} + +/// Send message to the contract with a custom body cell +fun TonolithCoreV2.sendAny( + self, + from: address, + body: cell, + config: SendParams = {}, +): SendResultList { + val genericMsg = createMessage({ + bounce: config.bounce, + value: config.value, + dest: self.address, + body, + }); + return net.send(from, genericMsg); +} + +/// Send an external-in message to the contract with a custom body cell +fun TonolithCoreV2.sendAnyExternal(self, body: cell): SendResultList? { + val msg = net.createExternalMessage(self.address, body); + return net.sendExternal(msg); +} + +fun TonolithCoreV2.getMagicV2(self): uint32 { + return net.runGetMethod(self.address, "getMagicV2"); +} + +fun TonolithCoreV2.getSchemaVersionV2(self): uint16 { + return net.runGetMethod(self.address, "getSchemaVersionV2"); +} + +fun TonolithCoreV2.getIsaVersionV2(self): uint16 { + return net.runGetMethod(self.address, "getIsaVersionV2"); +} + +fun TonolithCoreV2.getAbiVersionV2(self): uint16 { + return net.runGetMethod(self.address, "getAbiVersionV2"); +} + +fun TonolithCoreV2.getProtocolVersionV2(self): uint16 { + return net.runGetMethod(self.address, "getProtocolVersionV2"); +} + +fun TonolithCoreV2.getCoreIdV2(self): uint16 { + return net.runGetMethod(self.address, "getCoreIdV2"); +} + +fun TonolithCoreV2.getRunIdV2(self): uint256 { + return net.runGetMethod(self.address, "getRunIdV2"); +} + +fun TonolithCoreV2.getStaticCommitmentV2(self): uint256 { + return net.runGetMethod(self.address, "getStaticCommitmentV2"); +} + +fun TonolithCoreV2.getProgramIdV2(self): uint256 { + return net.runGetMethod(self.address, "getProgramIdV2"); +} + +fun TonolithCoreV2.getRomRootV2(self): uint256 { + return net.runGetMethod(self.address, "getRomRootV2"); +} + +fun TonolithCoreV2.getRouteRootV2(self): uint256 { + return net.runGetMethod(self.address, "getRouteRootV2"); +} + +fun TonolithCoreV2.getCoreStateHashV2(self): uint256 { + return net.runGetMethod(self.address, "getCoreStateHashV2"); +} + +fun TonolithCoreV2.getAdvanceCountV2(self): uint64 { + return net.runGetMethod(self.address, "getAdvanceCountV2"); +} + +fun TonolithCoreV2.getInstructionCountV2(self): uint64 { + return net.runGetMethod(self.address, "getInstructionCountV2"); +} + +fun TonolithCoreV2.getPcV2(self): uint10 { + return net.runGetMethod(self.address, "getPcV2"); +} + +fun TonolithCoreV2.getEpochV2(self): uint64 { + return net.runGetMethod(self.address, "getEpochV2"); +} + +fun TonolithCoreV2.getStatusV2(self): uint3 { + return net.runGetMethod(self.address, "getStatusV2"); +} + +fun TonolithCoreV2.getAccumulatorV2(self): uint4 { + return net.runGetMethod(self.address, "getAccumulatorV2"); +} + +fun TonolithCoreV2.getFlagsV2(self): uint2 { + return net.runGetMethod(self.address, "getFlagsV2"); +} From 251d0157f5a638d3975e710db82aa5475cff97ed Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:43:14 +0100 Subject: [PATCH 26/67] feat: add authenticated v2 direct routing --- contracts/TonolithCoreV2.tolk | 384 ++++++++++++++++++++++++++- contracts/v2/commitments.tolk | 33 ++- contracts/v2/execution.tolk | 1 - contracts/v2/peers.tolk | 23 ++ contracts/v2/queues.tolk | 113 ++++++++ contracts/v2/types.tolk | 35 ++- packages/abi/src/messages.ts | 15 +- packages/artifact/src/commitments.ts | 3 + packages/artifact/src/index.ts | 1 + packages/artifact/src/manifest.ts | 20 +- packages/artifact/src/peers.ts | 49 ++++ packages/emulator/src/commitments.ts | 4 +- packages/isa/src/constants.ts | 1 + spec/v2/protocol.json | 1 + tests/tonolith_v2.test.tolk | 353 +++++++++++++++++++++++- tests/v2/artifact.test.ts | 1 + wrappers/TonolithCoreV2.gen.tolk | 42 ++- 17 files changed, 1050 insertions(+), 29 deletions(-) create mode 100644 contracts/v2/peers.tolk create mode 100644 packages/artifact/src/peers.ts diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index 4daf978..285a4f9 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -1,9 +1,14 @@ import "v2/commitments" import "v2/execution" +import "v2/peers" +import "v2/queues" import "v2/rom" +import "v2/routes" import "v2/types" const MIN_ADVANCE_VALUE_V2: coins = ton("0.01") +const MIN_DISPATCH_VALUE_V2: coins = ton("0.02") +const MIN_DELIVERY_VALUE_V2: coins = ton("0.02") const STORAGE_RESERVE_V2: coins = ton("0.1") contract TonolithCoreV2 { @@ -25,15 +30,67 @@ fun emitBatchV2(event: V2BatchAdvanced) { message.send(SEND_MODE_REGULAR); } +fun emitOutputCommittedV2(event: V2OutputCommitted) { + val message = createExternalLogMessage({ + dest: createAddressNone(), + body: event, + }); + message.send(SEND_MODE_REGULAR); +} + +fun emitInputCommittedV2(event: V2InputCommitted) { + val message = createExternalLogMessage({ + dest: createAddressNone(), + body: event, + }); + message.send(SEND_MODE_REGULAR); +} + +fun emitOutputAcknowledgedV2(event: V2OutputAcknowledged) { + val message = createExternalLogMessage({ + dest: createAddressNone(), + body: event, + }); + message.send(SEND_MODE_REGULAR); +} + fun returnSafeExcessV2(sender: address) { val refund = createMessage({ bounce: BounceMode.NoBounce, - value: 0, + value: MIN_DELIVERY_VALUE_V2, dest: sender, }); refund.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); } +fun sendInputAcceptedV2( + destination: address, + queryId: uint64, + runId: uint256, + outputId: uint256, + destinationCoreId: uint16, + destinationEpoch: uint64, + destinationStateHash: uint256, +) { + val acknowledgement = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: 0, + dest: destination, + body: V2InputAccepted { + abiVersion: ABI_VERSION_V2, + queryId, + runId, + detail: V2AcknowledgementDetail { + outputId, + destinationCoreId, + destinationEpoch, + destinationStateHash, + }.toCell(), + }, + }); + acknowledgement.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); +} + fun v2StopReason(status: uint3, yielded: bool): uint8 { if (status == STATUS_HALTED_V2) { return STOP_HALTED_V2; @@ -53,6 +110,246 @@ fun v2StopReason(status: uint3, yielded: bool): uint8 { return STOP_REQUEST_LIMIT_V2; } +fun processDispatchOutputV2(sender: address, value: coins, msg: V2DispatchOutput) { + assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; + assert (value >= MIN_DISPATCH_VALUE_V2) throw ErrorsV2.InsufficientValue; + val storage = V2Storage.load(); + val identity = storage.identity.load(); + assert (msg.runId == identity.runId) throw ErrorsV2.InvalidRun; + val previousStateHash = storage.core.hash(); + assert (msg.expectedStateHash == previousStateHash) throw ErrorsV2.StateHashMismatch; + val core = storage.core.load(); + val queues = core.queues.load(); + val cursor = v2FindRecordById(queues.outboxRoot, msg.outputId); + assert (cursor.found != 0) throw ErrorsV2.NotFound; + val output = v2OutputAt(queues.outboxRoot, cursor.index); + assert (output.domain == DOMAIN_OUTPUT_V2) throw ErrorsV2.InvalidStorage; + assert (output.outputId == msg.outputId) throw ErrorsV2.ConflictingReplay; + val peer = v2FindPeer(storage.network.load().peers, output.destinationCoreId); + assert (peer != null) throw ErrorsV2.UnauthorizedPeer; + + reserveToncoinsOnBalance( + STORAGE_RESERVE_V2, + RESERVE_MODE_AT_MOST | RESERVE_MODE_BOUNCE_ON_ACTION_FAIL, + ); + val delivery = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: MIN_DELIVERY_VALUE_V2, + dest: peer!.peerAddress, + body: V2DeliverInput { + abiVersion: ABI_VERSION_V2, + queryId: msg.queryId, + runId: identity.runId, + outputId: output.outputId, + sourceCoreId: output.sourceCoreId, + destinationCoreId: output.destinationCoreId, + sourceEpoch: output.sourceEpoch, + destinationEpoch: output.destinationEpoch, + sourcePort: output.sourcePort, + destinationPort: output.destinationPort, + sequence: output.sequence, + value: output.value, + sourceInstructionCount: output.sourceInstructionCount, + sourceState: V2Hash { value: output.sourceStateHash }.toCell(), + }, + }); + delivery.send(SEND_MODE_REGULAR); + returnSafeExcessV2(sender); +} + +fun processDeliverInputV2(sender: address, value: coins, msg: V2DeliverInput) { + assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; + assert (value >= MIN_DELIVERY_VALUE_V2) throw ErrorsV2.InsufficientValue; + var storage = V2Storage.load(); + val identity = storage.identity.load(); + assert (msg.runId == identity.runId) throw ErrorsV2.InvalidRun; + assert (msg.destinationCoreId == identity.coreId) throw ErrorsV2.UnauthorizedPeer; + val network = storage.network.load(); + val sourcePeer = v2FindPeer(network.peers, msg.sourceCoreId); + assert (sourcePeer != null) throw ErrorsV2.UnauthorizedPeer; + assert (sender == sourcePeer!.peerAddress) throw ErrorsV2.UnauthorizedPeer; + val route = v2FindRoute(network.routes, msg.sourceCoreId, msg.sourcePort); + assert (route != null) throw ErrorsV2.InvalidRoute; + assert (route!.destinationCoreId == msg.destinationCoreId) throw ErrorsV2.InvalidRoute; + assert (route!.destinationPort == msg.destinationPort) throw ErrorsV2.InvalidRoute; + assert (msg.destinationEpoch == msg.sourceEpoch + route!.delay) throw ErrorsV2.InvalidEpoch; + assert (msg.sequence < route!.maxRecordsPerEpoch) throw ErrorsV2.InvalidSequence; + assert ( + msg.destinationEpoch >= storage.core.load().epoch && + msg.destinationEpoch <= storage.core.load().epoch + 1 + ) throw ErrorsV2.InvalidEpoch; + val expectedOutputId = v2OutputId( + identity.runId, + msg.sourceCoreId, + msg.destinationCoreId, + msg.sourceEpoch, + msg.destinationEpoch, + msg.sourcePort, + msg.destinationPort, + msg.sequence, + msg.value, + msg.sourceInstructionCount, + ); + assert (expectedOutputId == msg.outputId) throw ErrorsV2.InvalidRoute; + val sourceStateHash = msg.sourceState.load().value; + var core = storage.core.load(); + val queues = core.queues.load(); + val existingCursor = v2FindRecordById(queues.inboxRoot, msg.outputId); + if (existingCursor.found != 0) { + val existing = v2InputAt(queues.inboxRoot, existingCursor.index); + assert (existing.domain == DOMAIN_INPUT_V2) throw ErrorsV2.InvalidStorage; + assert ( + existing.sourceCoreId == msg.sourceCoreId && + existing.destinationCoreId == msg.destinationCoreId && + existing.sourceEpoch == msg.sourceEpoch && + existing.destinationEpoch == msg.destinationEpoch && + existing.sourcePort == msg.sourcePort && + existing.destinationPort == msg.destinationPort && + existing.sequence == msg.sequence && + existing.value == msg.value && + existing.sourceInstructionCount == msg.sourceInstructionCount && + existing.sourceStateHash == sourceStateHash + ) throw ErrorsV2.ConflictingReplay; + reserveToncoinsOnBalance( + STORAGE_RESERVE_V2, + RESERVE_MODE_AT_MOST | RESERVE_MODE_BOUNCE_ON_ACTION_FAIL, + ); + sendInputAcceptedV2( + sender, + msg.queryId, + identity.runId, + msg.outputId, + identity.coreId, + msg.destinationEpoch, + storage.core.hash(), + ); + returnSafeExcessV2(sender); + return; + } + val slotCursor = v2FindInputSlot( + queues.inboxRoot, + msg.destinationEpoch, + msg.destinationPort, + msg.sequence, + ); + assert (slotCursor.found == 0) throw ErrorsV2.ConflictingReplay; + assert (v2QueueCount(queues.inboxRoot) < MAX_INPUT_RECORDS_V2) throw ErrorsV2.QueueFull; + val input = V2InputRecord { + domain: DOMAIN_INPUT_V2, + outputId: msg.outputId, + sourceCoreId: msg.sourceCoreId, + destinationCoreId: msg.destinationCoreId, + sourceEpoch: msg.sourceEpoch, + destinationEpoch: msg.destinationEpoch, + sourcePort: msg.sourcePort, + destinationPort: msg.destinationPort, + sequence: msg.sequence, + value: msg.value, + sourceInstructionCount: msg.sourceInstructionCount, + sourceStateHash, + consumed: 0, + }.toCell(); + var updatedQueues = queues; + updatedQueues.inboxRoot = v2AppendQueueRoot(queues.inboxRoot, input); + core.queues = updatedQueues.toCell(); + core.inputCount += 1; + core.acceptedMessageCount += 1; + var commitments = core.commitments.load(); + commitments.inputCommitment = nextV2InputCommitment( + commitments.inputCommitment, + msg.outputId, + msg.destinationEpoch, + msg.sequence, + msg.value, + 0, + ); + core.commitments = commitments.toCell(); + if (core.status == STATUS_WAITING_INPUT_V2 && msg.destinationEpoch == core.epoch) { + core.status = STATUS_RUNNING_V2; + } + storage.core = core.toCell(); + val destinationStateHash = storage.core.hash(); + storage.save(); + emitInputCommittedV2(V2InputCommitted { + abiVersion: ABI_VERSION_V2, + queryId: msg.queryId, + runId: identity.runId, + outputId: msg.outputId, + sourceCoreId: msg.sourceCoreId, + destinationCoreId: msg.destinationCoreId, + epoch: msg.destinationEpoch, + destinationPort: msg.destinationPort, + sequence: msg.sequence, + inputCommitment: commitments.inputCommitment, + }); + reserveToncoinsOnBalance( + STORAGE_RESERVE_V2, + RESERVE_MODE_AT_MOST | RESERVE_MODE_BOUNCE_ON_ACTION_FAIL, + ); + sendInputAcceptedV2( + sender, + msg.queryId, + identity.runId, + msg.outputId, + identity.coreId, + msg.destinationEpoch, + destinationStateHash, + ); +} + +fun processInputAcceptedV2(sender: address, msg: V2InputAccepted) { + assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; + val detail = msg.detail.load(); + var storage = V2Storage.load(); + val identity = storage.identity.load(); + assert (msg.runId == identity.runId) throw ErrorsV2.InvalidRun; + val network = storage.network.load(); + val destinationPeer = v2FindPeer(network.peers, detail.destinationCoreId); + assert (destinationPeer != null) throw ErrorsV2.UnauthorizedPeer; + assert (sender == destinationPeer!.peerAddress) throw ErrorsV2.UnauthorizedPeer; + var core = storage.core.load(); + val queues = core.queues.load(); + val cursor = v2FindRecordById(queues.outboxRoot, detail.outputId); + if (cursor.found == 0) { + return; + } + val output = v2OutputAt(queues.outboxRoot, cursor.index); + assert (output.domain == DOMAIN_OUTPUT_V2) throw ErrorsV2.InvalidStorage; + assert (output.destinationCoreId == detail.destinationCoreId) throw ErrorsV2.UnauthorizedPeer; + assert (output.destinationEpoch == detail.destinationEpoch) throw ErrorsV2.InvalidEpoch; + var updatedQueues = queues; + updatedQueues.outboxRoot = v2RemoveRecordAt(queues.outboxRoot, cursor.index); + core.queues = updatedQueues.toCell(); + core.acceptedMessageCount += 1; + var commitments = core.commitments.load(); + commitments.outputCommitment = nextV2AcknowledgementCommitment( + commitments.outputCommitment, + detail.outputId, + ); + core.commitments = commitments.toCell(); + if ( + core.status == STATUS_BACKPRESSURED_V2 && v2QueueCount(updatedQueues.outboxRoot) < + MAX_OUTPUT_RECORDS_V2 + ) { + core.status = STATUS_RUNNING_V2; + } + storage.core = core.toCell(); + storage.save(); + reserveToncoinsOnBalance( + STORAGE_RESERVE_V2, + RESERVE_MODE_AT_MOST | RESERVE_MODE_BOUNCE_ON_ACTION_FAIL, + ); + emitOutputAcknowledgedV2(V2OutputAcknowledged { + abiVersion: ABI_VERSION_V2, + queryId: msg.queryId, + runId: identity.runId, + outputId: detail.outputId, + destinationCoreId: detail.destinationCoreId, + destinationEpoch: detail.destinationEpoch, + outputCommitment: commitments.outputCommitment, + }); +} + fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { assert (msg.abiVersion == ABI_VERSION_V2) throw ErrorsV2.InvalidVersion; assert ( @@ -91,6 +388,7 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { val startInstructionCount = core.instructionCount; val startOutputCount = core.outputCount; + val startOutboxCount = v2QueueCount(core.queues.load().outboxRoot); val startEpoch = core.epoch; if (core.status == STATUS_WAITING_INPUT_V2 || core.status == STATUS_BACKPRESSURED_V2) { core.status = STATUS_RUNNING_V2; @@ -101,7 +399,7 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { word, identity.runId, identity.coreId, - storage.routes, + storage.network.load().routes, previousStateHash, ); val executionStateHash = core.toCell().hash(); @@ -118,6 +416,25 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { val nextStateHash = storage.core.hash(); storage.save(); + val nextQueues = core.queues.load(); + if (v2QueueCount(nextQueues.outboxRoot) > startOutboxCount) { + val output = v2OutputAt(nextQueues.outboxRoot, startOutboxCount); + emitOutputCommittedV2(V2OutputCommitted { + abiVersion: ABI_VERSION_V2, + queryId: msg.queryId, + runId: identity.runId, + outputId: output.outputId, + sourceCoreId: output.sourceCoreId, + destinationCoreId: output.destinationCoreId, + epoch: output.sourceEpoch, + sourcePort: output.sourcePort, + destinationPort: output.destinationPort, + sequence: output.sequence, + value: output.value, + outputCommitment: core.commitments.load().outputCommitment, + }); + } + emitBatchV2(V2BatchAdvanced { abiVersion: ABI_VERSION_V2, queryId: msg.queryId, @@ -166,6 +483,29 @@ fun onInternalMessage(in: InMessage) { ); return; } + if (opcode == MESSAGE_DISPATCH_OUTPUT_V2) { + processDispatchOutputV2( + in.senderAddress, + in.valueCoins, + V2DispatchOutput.fromSlice(in.body, { assertEndAfterReading: true }), + ); + return; + } + if (opcode == MESSAGE_DELIVER_INPUT_V2) { + processDeliverInputV2( + in.senderAddress, + in.valueCoins, + V2DeliverInput.fromSlice(in.body, { assertEndAfterReading: true }), + ); + return; + } + if (opcode == MESSAGE_INPUT_ACCEPTED_V2) { + processInputAcceptedV2( + in.senderAddress, + V2InputAccepted.fromSlice(in.body, { assertEndAfterReading: true }), + ); + return; + } if (opcode == MESSAGE_TOP_UP_V2) { val msg = V2TopUp.fromSlice(in.body, { assertEndAfterReading: true }); val storage = V2Storage.load(); @@ -276,3 +616,43 @@ get fun getFlagsV2(): uint2 { val storage = lazy V2Storage.load(); return storage.core.load().flags; } + +get fun getAcceptedMessageCountV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().acceptedMessageCount; +} + +get fun getOutputCountV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().outputCount; +} + +get fun getInputCountV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().inputCount; +} + +get fun getInboxCountV2(): uint8 { + val storage = lazy V2Storage.load(); + return v2QueueCount(storage.core.load().queues.load().inboxRoot) as uint8; +} + +get fun getOutboxCountV2(): uint8 { + val storage = lazy V2Storage.load(); + return v2QueueCount(storage.core.load().queues.load().outboxRoot) as uint8; +} + +get fun getOutputCommitmentV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.load().commitments.load().outputCommitment; +} + +get fun getInputCommitmentV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.load().commitments.load().inputCommitment; +} + +get fun getEpochHistoryCommitmentV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.load().commitments.load().epochHistoryCommitment; +} diff --git a/contracts/v2/commitments.tolk b/contracts/v2/commitments.tolk index c73b9bd..11c6b11 100644 --- a/contracts/v2/commitments.tolk +++ b/contracts/v2/commitments.tolk @@ -7,6 +7,37 @@ struct V2LegacyOutput { outputCommitment: uint256 } +fun v2StaticCommitment( + runId: uint256, + coreId: uint16, + programId: uint256, + romRoot: uint256, + routeRoot: uint256, + peerRoot: uint256, + limitsHash: uint256, +): uint256 { + val programPayload = beginCell() + .storeUint(programId, 256) + .storeUint(romRoot, 256) + .storeUint(routeRoot, 256) + .endCell(); + val limitsPayload = beginCell().storeUint(limitsHash, 256).endCell(); + val peerPayload = beginCell().storeUint(peerRoot, 256).endCell(); + return beginCell() + .storeUint(DOMAIN_STATIC_V2, 32) + .storeUint(SCHEMA_VERSION_V2, 16) + .storeUint(ISA_VERSION_V2, 16) + .storeUint(ABI_VERSION_V2, 16) + .storeUint(PROTOCOL_VERSION_V2, 16) + .storeUint(runId, 256) + .storeUint(coreId, 16) + .storeRef(programPayload) + .storeRef(limitsPayload) + .storeRef(peerPayload) + .endCell() + .hash(); +} + fun nextV2LegacyOutputCommitment( previous: uint256, outputIndex: uint64, @@ -34,7 +65,6 @@ fun nextV2AcknowledgementCommitment(previous: uint256, outputId: uint256): uint2 fun v2OutputId( runId: uint256, - sourceStateHash: uint256, sourceCoreId: uint16, destinationCoreId: uint16, sourceEpoch: uint64, @@ -48,7 +78,6 @@ fun v2OutputId( return beginCell() .storeUint(DOMAIN_OUTPUT_V2, 32) .storeUint(runId, 256) - .storeUint(sourceStateHash, 256) .storeUint(sourceCoreId, 16) .storeUint(destinationCoreId, 16) .storeUint(sourceEpoch, 64) diff --git a/contracts/v2/execution.tolk b/contracts/v2/execution.tolk index 26429e1..f88d219 100644 --- a/contracts/v2/execution.tolk +++ b/contracts/v2/execution.tolk @@ -182,7 +182,6 @@ fun v2ExecuteSys( val destinationEpoch = core.epoch + route!.delay; val outputId = v2OutputId( runId, - beforeStateHash, coreId, route!.destinationCoreId, core.epoch, diff --git a/contracts/v2/peers.tolk b/contracts/v2/peers.tolk new file mode 100644 index 0000000..2b69a90 --- /dev/null +++ b/contracts/v2/peers.tolk @@ -0,0 +1,23 @@ +import "queues" +import "types" + +const DOMAIN_PEER_V2: uint32 = 0x544C5033 + +fun v2PeerAt(root: cell, index: int): V2Peer { + return (v2RecordAt(root, index) as Cell).load(); +} + +fun v2FindPeer(peers: cell, coreId: uint16): V2Peer? { + return v2FindPeerFrom(peers, coreId, 0, v2QueueCount(peers)); +} + +fun v2FindPeerFrom(peers: cell, coreId: uint16, index: int, count: int): V2Peer? { + if (index >= count || index >= 16) { + return null; + } + val peer = v2PeerAt(peers, index); + if (peer.domain == DOMAIN_PEER_V2 && peer.coreId == coreId) { + return peer; + } + return v2FindPeerFrom(peers, coreId, index + 1, count); +} diff --git a/contracts/v2/queues.tolk b/contracts/v2/queues.tolk index 3412340..34b5f79 100644 --- a/contracts/v2/queues.tolk +++ b/contracts/v2/queues.tolk @@ -197,3 +197,116 @@ fun v2FindInputFrom(root: cell, epoch: uint64, port: int, index: int, count: int } return v2FindInputFrom(root, epoch, port, index + 1, count); } + +fun v2FindRecordById(root: cell, outputId: uint256): V2RecordCursor { + return v2FindRecordByIdFrom(root, outputId, 0, v2QueueCount(root)); +} + +fun v2FindRecordByIdFrom(root: cell, outputId: uint256, index: int, count: int): V2RecordCursor { + if (index >= count || index >= 16) { + return V2RecordCursor { + found: 0, + index: 0, + }; + } + var record = v2RecordAt(root, index).beginParse(); + record.loadUint(32); + val candidate = record.loadUint(256); + if (candidate == outputId) { + return V2RecordCursor { + found: 1, + index: index as uint8, + }; + } + return v2FindRecordByIdFrom(root, outputId, index + 1, count); +} + +fun v2FindInputSlot(root: cell, epoch: uint64, port: int, sequence: uint8): V2RecordCursor { + return v2FindInputSlotFrom( + root, + epoch, + port, + sequence, + 0, + v2QueueCount(root), + ); +} + +fun v2FindInputSlotFrom( + root: cell, + epoch: uint64, + port: int, + sequence: uint8, + index: int, + count: int, +): V2RecordCursor { + if (index >= count || index >= 16) { + return V2RecordCursor { + found: 0, + index: 0, + }; + } + val input = v2InputAt(root, index); + if ( + input.destinationEpoch == epoch && input.destinationPort == port && + input.sequence == sequence + ) { + return V2RecordCursor { + found: 1, + index: index as uint8, + }; + } + return v2FindInputSlotFrom( + root, + epoch, + port, + sequence, + index + 1, + count, + ); +} + +fun v2EmptyQueueRootForMutation(): cell { + val page = beginCell().storeUint(0, 3).endCell(); + return beginCell() + .storeUint(0, 5) + .storeRef(page) + .storeRef(page) + .storeRef(page) + .storeRef(page) + .endCell(); +} + +fun v2RemoveRecordAt(root: cell, index: int): cell { + val count = v2QueueCount(root); + assert (index >= 0 && index < count) throw ErrorsV2.NotFound; + return v2RemoveRecordAtFrom( + root, + index, + 0, + count, + v2EmptyQueueRootForMutation(), + ); +} + +fun v2RemoveRecordAtFrom( + root: cell, + skippedIndex: int, + sourceIndex: int, + count: int, + result: cell, +): cell { + if (sourceIndex >= count) { + return result; + } + if (sourceIndex == skippedIndex) { + return v2RemoveRecordAtFrom(root, skippedIndex, sourceIndex + 1, count, result); + } + return v2RemoveRecordAtFrom( + root, + skippedIndex, + sourceIndex + 1, + count, + v2AppendQueueRoot(result, v2RecordAt(root, sourceIndex)), + ); +} diff --git a/contracts/v2/types.tolk b/contracts/v2/types.tolk index 0247381..138889e 100644 --- a/contracts/v2/types.tolk +++ b/contracts/v2/types.tolk @@ -31,9 +31,11 @@ const ISA_VERSION_V2: uint16 = 2 const ABI_VERSION_V2: uint16 = 2 const PROTOCOL_VERSION_V2: uint16 = 2 const DOMAIN_STATE_V2: uint32 = 0x544C4332 +const DOMAIN_STATIC_V2: uint32 = 0x544C5332 const DOMAIN_EPOCH_V2: uint32 = 0x544C4532 const DOMAIN_OUTPUT_V2: uint32 = 0x544C4F32 const DOMAIN_INPUT_V2: uint32 = 0x544C4932 +const DOMAIN_ROUTE_V2: uint32 = 0x544C5232 const DOMAIN_ACK_V2: uint32 = 0x544C4132 const DOMAIN_BATCH_V2: uint32 = 0x544C4232 const MESSAGE_ADVANCE_V2: uint32 = 0x544E4C21 @@ -135,6 +137,12 @@ struct V2Route { maxRecordsPerEpoch: uint8 } +struct V2Peer { + domain: uint32 + coreId: uint16 + peerAddress: address +} + struct V2OutputRecord { domain: uint32 outputId: uint256 @@ -172,6 +180,11 @@ struct V2InputCursor { value: uint4 } +struct V2RecordCursor { + found: uint1 + index: uint8 +} + struct V2CoreState { domain: uint32 staticCommitment: uint256 @@ -192,10 +205,15 @@ struct V2CoreState { queues: Cell } +struct V2PeerRoots { + peerRoot: uint256 +} + struct V2StaticRoots { romRoot: uint256 routeRoot: uint256 limitsHash: uint256 + peers: Cell } struct V2StaticData { @@ -215,7 +233,12 @@ struct V2Storage { identity: Cell core: Cell rom: cell + network: Cell +} + +struct V2NetworkData { routes: cell + peers: cell } fun V2Storage.load(): V2Storage { @@ -261,16 +284,20 @@ struct (0x544E4C23) V2DeliverInput { sourceState: Cell } -struct (0x544E4C24) V2InputAccepted { - abiVersion: uint16 - queryId: uint64 - runId: uint256 +struct V2AcknowledgementDetail { outputId: uint256 destinationCoreId: uint16 destinationEpoch: uint64 destinationStateHash: uint256 } +struct (0x544E4C24) V2InputAccepted { + abiVersion: uint16 + queryId: uint64 + runId: uint256 + detail: Cell +} + struct (0x544E4C25) V2TopUp { abiVersion: uint16 queryId: uint64 diff --git a/packages/abi/src/messages.ts b/packages/abi/src/messages.ts index 8731dab..a05af02 100644 --- a/packages/abi/src/messages.ts +++ b/packages/abi/src/messages.ts @@ -220,25 +220,30 @@ export function decodeDeliverInputV2(input: Cell | Slice): DeliverInputV2Message } export function encodeInputAcceptedV2(message: Omit): Cell { - return commonCell(MESSAGE_PREFIX.inputAccepted, message) + const detail = beginCell() .storeUint(toBigInt(message.outputId), 256) .storeUint(message.destinationCoreId, 16) .storeUint(message.destinationEpoch, 64) .storeUint(toBigInt(message.destinationStateHash), 256) .endCell(); + return commonCell(MESSAGE_PREFIX.inputAccepted, message) + .storeRef(detail) + .endCell(); } export function decodeInputAcceptedV2(input: Cell | Slice): InputAcceptedV2Message { const slice = parseSlice(input, MESSAGE_PREFIX.inputAccepted); const common = readCommon(slice, MESSAGE_PREFIX.inputAccepted); + const detail = slice.loadRef().beginParse(); const message: InputAcceptedV2Message = { kind: "inputAccepted", ...common, - outputId: loadHash(slice), - destinationCoreId: slice.loadUint(16), - destinationEpoch: slice.loadUintBig(64), - destinationStateHash: loadHash(slice), + outputId: loadHash(detail), + destinationCoreId: detail.loadUint(16), + destinationEpoch: detail.loadUintBig(64), + destinationStateHash: loadHash(detail), }; + finish(detail); finish(slice); return message; } diff --git a/packages/artifact/src/commitments.ts b/packages/artifact/src/commitments.ts index be55cb9..8ebaf01 100644 --- a/packages/artifact/src/commitments.ts +++ b/packages/artifact/src/commitments.ts @@ -66,6 +66,7 @@ export function staticCommitmentCell(fields: { readonly romRoot: Hash256; readonly routeRoot: Hash256; readonly limitsHash: Hash256; + readonly peerRoot?: Hash256; }): Cell { const programPayload = beginCell() .storeUint(toBigInt(fields.programId), 256) @@ -73,6 +74,7 @@ export function staticCommitmentCell(fields: { .storeUint(toBigInt(fields.routeRoot), 256) .endCell(); const limitsPayload = beginCell().storeUint(toBigInt(fields.limitsHash), 256).endCell(); + const peerPayload = beginCell().storeUint(toBigInt(fields.peerRoot ?? "0".repeat(64)), 256).endCell(); return beginCell() .storeUint(DOMAIN.static, 32) .storeUint(fields.schemaVersion, 16) @@ -83,6 +85,7 @@ export function staticCommitmentCell(fields: { .storeUint(fields.coreId, 16) .storeRef(programPayload) .storeRef(limitsPayload) + .storeRef(peerPayload) .endCell(); } diff --git a/packages/artifact/src/index.ts b/packages/artifact/src/index.ts index 0167728..2e2a3ed 100644 --- a/packages/artifact/src/index.ts +++ b/packages/artifact/src/index.ts @@ -3,3 +3,4 @@ export * from "./commitments.js"; export * from "./manifest.js"; export * from "./memory.js"; export * from "./routes.js"; +export * from "./peers.js"; diff --git a/packages/artifact/src/manifest.ts b/packages/artifact/src/manifest.ts index 93a5510..33798c9 100644 --- a/packages/artifact/src/manifest.ts +++ b/packages/artifact/src/manifest.ts @@ -3,6 +3,7 @@ import { ABI_VERSION, ISA_VERSION, PROTOCOL_VERSION, RAM_NIBBLES, ROM_WORDS, SCH import { assembleV2, type V2AssemblyResult } from "./assembler.js"; import { deriveRunId, limitsHash, programId, protocolVersions, staticCommitment, type Hash256, type LimitsV2 } from "./commitments.js"; import { normalizeRam, normalizeRom, ramRoot, ramRootCell, romRoot, romRootCell } from "./memory.js"; +import { normalizePeers, peerRoot, type PeerDescriptor } from "./peers.js"; import { normalizeRoutes, routeRoot, type RouteDescriptor } from "./routes.js"; export interface CompilerInfo { @@ -30,6 +31,7 @@ export interface ArtifactManifestV2 { readonly romRoot: Hash256; readonly initialRamRoot: Hash256; readonly routeRoot: Hash256; + readonly peerRoot: Hash256; readonly programId: Hash256; readonly limitsHash: Hash256; readonly staticCommitment: Hash256; @@ -45,6 +47,7 @@ export interface BuildArtifactOptions { readonly runId?: Hash256; readonly runSalt?: Hash256; readonly routes?: readonly RouteDescriptor[]; + readonly peers?: readonly PeerDescriptor[]; readonly limits?: Partial; readonly compiler?: Partial; } @@ -54,6 +57,7 @@ export interface ArtifactBundle { readonly source: string; readonly assembly: V2AssemblyResult; readonly routes: readonly RouteDescriptor[]; + readonly peers: readonly PeerDescriptor[]; readonly limits: LimitsV2; readonly files: Readonly>; } @@ -75,6 +79,7 @@ export const DEFAULT_LIMITS: LimitsV2 = { export function buildArtifact(source: string, options: BuildArtifactOptions): ArtifactBundle { const assembly = assembleV2(source, options.fileName ?? "program.tasm"); const routes = normalizeRoutes(options.routes ?? []); + const peers = normalizePeers(options.peers ?? []); const limits: LimitsV2 = { ...DEFAULT_LIMITS, ...options.limits, @@ -83,6 +88,7 @@ export function buildArtifact(source: string, options: BuildArtifactOptions): Ar const romRootValue = romRoot(assembly.words); const initialRamRoot = ramRoot(assembly.ram); const routeRootValue = routeRoot(routes); + const peerRootValue = peerRoot(peers); const programIdValue = programId({ artifactVersion: 2, isaVersion: ISA_VERSION, @@ -106,6 +112,7 @@ export function buildArtifact(source: string, options: BuildArtifactOptions): Ar programId: programIdValue, romRoot: romRootValue, routeRoot: routeRootValue, + peerRoot: peerRootValue, limitsHash: limitsHashValue, }); const compiler: CompilerInfo = { @@ -133,14 +140,15 @@ export function buildArtifact(source: string, options: BuildArtifactOptions): Ar romRoot: romRootValue, initialRamRoot, routeRoot: routeRootValue, + peerRoot: peerRootValue, programId: programIdValue, limitsHash: limitsHashValue, staticCommitment: staticCommitmentValue, sourceHash: sha256(assembly.canonicalSource), compiler, }; - const files = makeFiles(manifest, source, assembly, routes); - return { manifest, source, assembly, routes, limits, files }; + const files = makeFiles(manifest, source, assembly, routes, peers); + return { manifest, source, assembly, routes, peers, limits, files }; } export function verifyArtifact(bundle: ArtifactBundle): ArtifactVerificationReport { @@ -153,13 +161,14 @@ export function verifyArtifact(bundle: ArtifactBundle): ArtifactVerificationRepo if (manifest.isaVersion !== versions.isaVersion) errors.push("unsupported isaVersion"); if (manifest.abiVersion !== versions.abiVersion) errors.push("unsupported abiVersion"); if (manifest.protocolVersion !== versions.protocolVersion) errors.push("unsupported protocolVersion"); - for (const field of ["runId", "runSalt", "romRoot", "initialRamRoot", "routeRoot", "programId", "limitsHash", "staticCommitment", "sourceHash"] as const) { + for (const field of ["runId", "runSalt", "romRoot", "initialRamRoot", "routeRoot", "peerRoot", "programId", "limitsHash", "staticCommitment", "sourceHash"] as const) { if (!/^[0-9a-f]{64}$/.test(manifest[field])) errors.push(`${field} is not canonical lowercase uint256 hex`); } try { const computedRomRoot = romRoot(bundle.assembly.words); const computedRamRoot = ramRoot(bundle.assembly.ram); const computedRouteRoot = routeRoot(bundle.routes); + const computedPeerRoot = peerRoot(bundle.peers); const computedProgramId = programId({ artifactVersion: 2, isaVersion: 2, @@ -181,6 +190,7 @@ export function verifyArtifact(bundle: ArtifactBundle): ArtifactVerificationRepo programId: computedProgramId, romRoot: computedRomRoot, routeRoot: computedRouteRoot, + peerRoot: computedPeerRoot, limitsHash: computedLimitsHash, }); const computedRunId = deriveRunId([computedProgramId], computedRouteRoot, manifest.runSalt); @@ -188,6 +198,7 @@ export function verifyArtifact(bundle: ArtifactBundle): ArtifactVerificationRepo romRoot: computedRomRoot, initialRamRoot: computedRamRoot, routeRoot: computedRouteRoot, + peerRoot: computedPeerRoot, programId: computedProgramId, limitsHash: computedLimitsHash, staticCommitment: computedStatic, @@ -224,7 +235,7 @@ export function canonicalJson(value: unknown): string { return JSON.stringify(sortJson(value)); } -function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2AssemblyResult, routes: readonly RouteDescriptor[]): Readonly> { +function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2AssemblyResult, routes: readonly RouteDescriptor[], peers: readonly PeerDescriptor[]): Readonly> { const artifactJson = canonicalJson(manifest); return { "artifact.json": artifactJson, @@ -234,6 +245,7 @@ function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2Ass "symbols.json": canonicalJson({ labels: assembly.labels, exports: assembly.exports }), "source-map.json": canonicalJson(assembly.sourceMap), "routes.json": canonicalJson(routes), + "peers.json": canonicalJson(peers), "BUILDINFO.json": canonicalJson({ artifactSha256: sha256(artifactJson), compiler: manifest.compiler, sourceHash: manifest.sourceHash }), }; } diff --git a/packages/artifact/src/peers.ts b/packages/artifact/src/peers.ts new file mode 100644 index 0000000..ced2568 --- /dev/null +++ b/packages/artifact/src/peers.ts @@ -0,0 +1,49 @@ +import { Address, beginCell, type Cell } from "@ton/core"; +import { DOMAIN, MAX_INPUT_RECORDS } from "../../isa/src/constants.js"; + +export interface PeerDescriptor { + readonly coreId: number; + readonly address: string; +} + +export function normalizePeers(peers: readonly PeerDescriptor[]): PeerDescriptor[] { + if (peers.length > MAX_INPUT_RECORDS) throw new RangeError("v2 peer table supports at most 16 peers"); + const normalized = peers.map((peer) => ({ coreId: peer.coreId, address: Address.parse(peer.address).toRawString() })).sort((a, b) => a.coreId - b.coreId); + let previous: number | undefined; + for (const peer of normalized) { + if (!Number.isInteger(peer.coreId) || peer.coreId < 0 || peer.coreId > 0xffff) throw new RangeError("peer coreId must be 0..65535"); + if (previous === peer.coreId) throw new RangeError(`duplicate peer coreId ${peer.coreId}`); + previous = peer.coreId; + } + return normalized; +} + +export function peerCell(peer: PeerDescriptor): Cell { + const normalized = normalizePeers([peer])[0]!; + return beginCell() + .storeUint(DOMAIN.peer, 32) + .storeUint(normalized.coreId, 16) + .storeAddress(Address.parse(normalized.address)) + .endCell(); +} + +export function peerRootCell(peers: readonly PeerDescriptor[]): Cell { + const normalized = normalizePeers(peers); + const pages = Array.from({ length: 4 }, (_, pageIndex) => { + const page = normalized.slice(pageIndex * 4, pageIndex * 4 + 4); + const builder = beginCell().storeUint(page.length, 3); + page.forEach((peer) => builder.storeRef(peerCell(peer))); + return builder.endCell(); + }); + return beginCell() + .storeUint(normalized.length, 5) + .storeRef(pages[0]!) + .storeRef(pages[1]!) + .storeRef(pages[2]!) + .storeRef(pages[3]!) + .endCell(); +} + +export function peerRoot(peers: readonly PeerDescriptor[]): string { + return peerRootCell(peers).hash().toString("hex"); +} diff --git a/packages/emulator/src/commitments.ts b/packages/emulator/src/commitments.ts index 7b826b6..318911b 100644 --- a/packages/emulator/src/commitments.ts +++ b/packages/emulator/src/commitments.ts @@ -87,6 +87,7 @@ export function staticCommitmentCell(fields: { romRoot: Hash256; routeRoot: Hash256; limitsHash: Hash256; + peerRoot?: Hash256; }): Cell { const programPayload = beginCell() .storeUint(toBigInt(fields.programId), 256) @@ -94,6 +95,7 @@ export function staticCommitmentCell(fields: { .storeUint(toBigInt(fields.routeRoot), 256) .endCell(); const limitsPayload = beginCell().storeUint(toBigInt(fields.limitsHash), 256).endCell(); + const peerPayload = beginCell().storeUint(toBigInt(fields.peerRoot ?? zeroHash()), 256).endCell(); return beginCell() .storeUint(DOMAIN.static, 32) .storeUint(fields.schemaVersion, 16) @@ -104,6 +106,7 @@ export function staticCommitmentCell(fields: { .storeUint(fields.coreId, 16) .storeRef(programPayload) .storeRef(limitsPayload) + .storeRef(peerPayload) .endCell(); } @@ -272,7 +275,6 @@ export function outputId(runId: Hash256, record: Omit) beginCell() .storeUint(DOMAIN.output, 32) .storeUint(toBigInt(runId), 256) - .storeUint(toBigInt(record.sourceStateHash), 256) .storeUint(record.sourceCoreId, 16) .storeUint(record.destinationCoreId, 16) .storeUint(record.sourceEpoch, 64) diff --git a/packages/isa/src/constants.ts b/packages/isa/src/constants.ts index 8304347..f863724 100644 --- a/packages/isa/src/constants.ts +++ b/packages/isa/src/constants.ts @@ -29,6 +29,7 @@ export const DOMAIN = { output: 0x544c4f32, input: 0x544c4932, route: 0x544c5232, + peer: 0x544c5033, batch: 0x544c4232, acknowledgement: 0x544c4132, run: 0x544c4432, diff --git a/spec/v2/protocol.json b/spec/v2/protocol.json index a7aa582..50c8e82 100644 --- a/spec/v2/protocol.json +++ b/spec/v2/protocol.json @@ -25,6 +25,7 @@ "output": "0x544c4f32", "input": "0x544c4932", "route": "0x544c5232", + "peer": "0x544c5033", "batch": "0x544c4232", "acknowledgement": "0x544c4132", "run": "0x544c4432", diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index e4cb219..6ff1477 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -3,15 +3,23 @@ import "@acton/emulation/testing" import "@acton/testing/expect" import "@wrappers/TonolithCoreV2.gen" +import "@contracts/v2/commitments" import "@contracts/v2/memory" +import "@contracts/v2/peers" +import "@contracts/v2/queues" import "@contracts/v2/rom" import "@contracts/v2/types" fun makeV2StorageForTest(rom: Cell): V2Storage { + return makeV2StorageForNetwork(rom, emptyV2RecordRoot(), emptyV2RecordRoot()); +} + +fun makeV2StorageForNetwork(rom: Cell, routes: cell, peers: cell): V2Storage { val staticRoots = V2StaticRoots { romRoot: rom.hash(), - routeRoot: 0, + routeRoot: routes.hash(), limitsHash: 0, + peers: V2PeerRoots { peerRoot: peers.hash() }.toCell(), }.toCell(); val identity = V2StaticData { coreId: 0, @@ -53,10 +61,18 @@ fun makeV2StorageForTest(rom: Cell): V2Storage { identity, core, rom, - routes: beginCell().endCell(), + network: V2NetworkData { routes, peers }.toCell(), }; } +fun oneRouteV2(route: V2Route): cell { + return v2AppendQueueRoot(emptyV2RecordRoot(), route.toCell()); +} + +fun onePeerV2(peer: V2Peer): cell { + return v2AppendQueueRoot(emptyV2RecordRoot(), peer.toCell()); +} + fun romWithFirstWordV2(word: uint16): Cell { return v2RomWithFirstLeaf( V2RomLeaf { @@ -94,6 +110,76 @@ fun sendAdvanceV2ForTest( return net.send(from, advanceMessage); } +fun sendDeliverInputV2ForTest( + core: TonolithCoreV2, + from: address, + queryId: uint64, + runId: uint256, + outputId: uint256, + sourceCoreId: uint16, + destinationCoreId: uint16, + sourceEpoch: uint64, + destinationEpoch: uint64, + sourcePort: uint4, + destinationPort: uint4, + sequence: uint8, + value: uint4, + sourceInstructionCount: uint64, + sourceStateHash: uint256, +): SendResultList { + val deliverMessage = createMessage({ + bounce: false, + value: ton("0.1"), + dest: core.address, + body: V2DeliverInput { + abiVersion: ABI_VERSION_V2, + queryId, + runId, + outputId, + sourceCoreId, + destinationCoreId, + sourceEpoch, + destinationEpoch, + sourcePort, + destinationPort, + sequence, + value, + sourceInstructionCount, + sourceState: V2Hash { value: sourceStateHash }.toCell(), + }, + }); + return net.send(from, deliverMessage); +} + +fun sendInputAcceptedV2ForTest( + core: TonolithCoreV2, + from: address, + queryId: uint64, + runId: uint256, + outputId: uint256, + destinationCoreId: uint16, + destinationEpoch: uint64, + destinationStateHash: uint256, +): SendResultList { + val acknowledgement = createMessage({ + bounce: false, + value: ton("0.1"), + dest: core.address, + body: V2InputAccepted { + abiVersion: ABI_VERSION_V2, + queryId, + runId, + detail: V2AcknowledgementDetail { + outputId, + destinationCoreId, + destinationEpoch, + destinationStateHash, + }.toCell(), + }, + }); + return net.send(from, acknowledgement); +} + get fun `test v2 deploy exposes immutable metadata and a canonical initial state`() { val deployer = testing.treasury("v2-metadata-deployer"); val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); @@ -211,3 +297,266 @@ get fun `test v2 HALT is an architectural state and rejects subsequent advance`( exitCode: ErrorsV2.InvalidStatus, }); } + +get fun `test v2 OUTP appends a routed output and commits it`() { + val deployer = testing.treasury("v2-outp-deployer"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 3, + destinationCoreId: 1, + destinationPort: 4, + delay: 1, + maxRecordsPerEpoch: 4, + }; + val core = TonolithCoreV2.fromStorage( + makeV2StorageForNetwork( + romWithFirstWordV2(0xF03A), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ), + ); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + val result = sendAdvanceV2ForTest( + core, + deployer.address, + 3, + 0, + 0, + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); + expect(result).toHaveSuccessfulTx(); + expect(result).toEmitExternalMessage(); + expect(core.getOutputCountV2()).toEqual(1); + expect(core.getOutboxCountV2()).toEqual(1); + expect(core.getAccumulatorV2()).toEqual(0); + expect(core.getInstructionCountV2()).toEqual(1); + expect(core.getOutputCommitmentV2()).toEqual( + 0xbef05e20c0bfad82f96fadd6cd4d085e08d26804ae52ec28d063f48d48fe886b, + ); + expect(core.getCoreStateHashV2()).toEqual( + 0x0eed2e36924e740c884530155b6c8f079f1e342a2ff3ee0a1b7b672ba88c7c42, + ); +} + +get fun `test v2 IN waits, accepts authenticated input, deduplicates, and consumes`() { + val deployer = testing.treasury("v2-in-deployer"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 0, + destinationCoreId: 0, + destinationPort: 2, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var storage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var initialCore = storage.core.load(); + initialCore.epoch = 1; + storage.core = initialCore.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + val waiting = sendAdvanceV2ForTest( + core, + deployer.address, + 10, + 0, + 0, + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); + expect(waiting).toHaveSuccessfulTx(); + expect(core.getStatusV2()).toEqual(STATUS_WAITING_INPUT_V2); + expect(core.getInstructionCountV2()).toEqual(0); + val outputId = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + ); + val first = sendDeliverInputV2ForTest( + core, + deployer.address, + 11, + 0, + outputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ); + expect(first).toHaveSuccessfulTx(); + expect(first).toEmitExternalMessage(); + expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); + expect(core.getInboxCountV2()).toEqual(1); + expect(core.getInputCountV2()).toEqual(1); + val duplicate = sendDeliverInputV2ForTest( + core, + deployer.address, + 12, + 0, + outputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ); + expect(duplicate).toHaveSuccessfulTx(); + expect(core.getInputCountV2()).toEqual(1); + val consume = sendAdvanceV2ForTest( + core, + deployer.address, + 13, + 0, + 1, + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); + expect(consume).toHaveSuccessfulTx(); + expect(core.getAccumulatorV2()).toEqual(7); + expect(core.getInstructionCountV2()).toEqual(1); + val conflict = sendDeliverInputV2ForTest( + core, + deployer.address, + 14, + 0, + outputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 1, + ); + expect(conflict).toHaveFailedTx({ + from: deployer.address, + to: core.address, + exitCode: ErrorsV2.ConflictingReplay, + }); +} + +get fun `test v2 acknowledgement retires output and rejects wrong peer`() { + val deployer = testing.treasury("v2-ack-deployer"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 1, + destinationPort: 0, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var storage = makeV2StorageForNetwork( + romWithFirstWordV2(0), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var coreState = storage.core.load(); + val outputId = v2OutputId( + 0, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 3, + 1, + ); + val output = V2OutputRecord { + domain: DOMAIN_OUTPUT_V2, + outputId, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 3, + sourceInstructionCount: 1, + sourceStateHash: 0, + }.toCell(); + var queues = coreState.queues.load(); + queues.outboxRoot = v2AppendQueueRoot(queues.outboxRoot, output); + coreState.queues = queues.toCell(); + storage.core = coreState.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect( + core.deploy(deployer.address, SendParams { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + val wrong = sendInputAcceptedV2ForTest( + core, + testing.treasury("v2-wrong-ack").address, + 1, + 0, + outputId, + 1, + 1, + 0, + ); + expect(wrong).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); + val accepted = sendInputAcceptedV2ForTest( + core, + deployer.address, + 2, + 0, + outputId, + 1, + 1, + 0, + ); + expect(accepted).toHaveSuccessfulTx(); + expect(accepted).toEmitExternalMessage(); + expect(core.getOutboxCountV2()).toEqual(0); +} diff --git a/tests/v2/artifact.test.ts b/tests/v2/artifact.test.ts index a34c6e0..bf491a2 100644 --- a/tests/v2/artifact.test.ts +++ b/tests/v2/artifact.test.ts @@ -49,6 +49,7 @@ test("v2 artifact builder emits deterministic files and independently matching c programId: first.manifest.programId, romRoot: first.manifest.romRoot, routeRoot: first.manifest.routeRoot, + peerRoot: first.manifest.peerRoot, limitsHash: first.manifest.limitsHash, }).hash().toString("hex"), first.manifest.staticCommitment); }); diff --git a/wrappers/TonolithCoreV2.gen.tolk b/wrappers/TonolithCoreV2.gen.tolk index 2b8f52e..7c4e2f2 100644 --- a/wrappers/TonolithCoreV2.gen.tolk +++ b/wrappers/TonolithCoreV2.gen.tolk @@ -142,10 +142,7 @@ fun TonolithCoreV2.sendV2InputAccepted( abiVersion: uint16, queryId: uint64, runId: uint256, - outputId: uint256, - destinationCoreId: uint16, - destinationEpoch: uint64, - destinationStateHash: uint256, + detail: V2AcknowledgementDetail, config: SendParams = {}, ): SendResultList { val genericMsg = createMessage({ @@ -156,10 +153,7 @@ fun TonolithCoreV2.sendV2InputAccepted( abiVersion, queryId, runId, - outputId, - destinationCoreId, - destinationEpoch, - destinationStateHash, + detail: detail.toCell(), }, }); return net.send(from, genericMsg); @@ -279,3 +273,35 @@ fun TonolithCoreV2.getAccumulatorV2(self): uint4 { fun TonolithCoreV2.getFlagsV2(self): uint2 { return net.runGetMethod(self.address, "getFlagsV2"); } + +fun TonolithCoreV2.getAcceptedMessageCountV2(self): uint64 { + return net.runGetMethod(self.address, "getAcceptedMessageCountV2"); +} + +fun TonolithCoreV2.getOutputCountV2(self): uint64 { + return net.runGetMethod(self.address, "getOutputCountV2"); +} + +fun TonolithCoreV2.getInputCountV2(self): uint64 { + return net.runGetMethod(self.address, "getInputCountV2"); +} + +fun TonolithCoreV2.getInboxCountV2(self): uint8 { + return net.runGetMethod(self.address, "getInboxCountV2"); +} + +fun TonolithCoreV2.getOutboxCountV2(self): uint8 { + return net.runGetMethod(self.address, "getOutboxCountV2"); +} + +fun TonolithCoreV2.getOutputCommitmentV2(self): uint256 { + return net.runGetMethod(self.address, "getOutputCommitmentV2"); +} + +fun TonolithCoreV2.getInputCommitmentV2(self): uint256 { + return net.runGetMethod(self.address, "getInputCommitmentV2"); +} + +fun TonolithCoreV2.getEpochHistoryCommitmentV2(self): uint256 { + return net.runGetMethod(self.address, "getEpochHistoryCommitmentV2"); +} From 64265792a89d82fa948760d3107d74996bbcf3f4 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:56:07 +0100 Subject: [PATCH 27/67] feat: add deterministic v2 StateInit SDK --- package.json | 5 ++ packages/sdk/src/index.ts | 16 ++++ packages/sdk/src/stateinit.ts | 153 ++++++++++++++++++++++++++++++++++ tests/v2/stateinit.test.ts | 49 +++++++++++ 4 files changed, 223 insertions(+) create mode 100644 packages/sdk/src/index.ts create mode 100644 packages/sdk/src/stateinit.ts create mode 100644 tests/v2/stateinit.test.ts diff --git a/package.json b/package.json index 4160160..1642041 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,11 @@ "types": "./dist/tools/browser.d.ts", "import": "./dist/tools/browser.js", "default": "./dist/tools/browser.js" + }, + "./v2": { + "types": "./dist/packages/sdk/src/index.d.ts", + "import": "./dist/packages/sdk/src/index.js", + "default": "./dist/packages/sdk/src/index.js" } }, "files": [ diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts new file mode 100644 index 0000000..0084786 --- /dev/null +++ b/packages/sdk/src/index.ts @@ -0,0 +1,16 @@ +export * from "../../abi/src/messages.js"; +export { + deriveRunId, + limitsCell, + limitsHash, + programCommitmentCell, + programId, + protocolVersions, + staticCommitment, + staticCommitmentCell, +} from "../../artifact/src/commitments.js"; +export type { LimitsV2, ProgramFields } from "../../artifact/src/commitments.js"; +export * from "../../artifact/src/memory.js"; +export * from "../../artifact/src/peers.js"; +export * from "../../artifact/src/routes.js"; +export * from "./stateinit.js"; diff --git a/packages/sdk/src/stateinit.ts b/packages/sdk/src/stateinit.ts new file mode 100644 index 0000000..db4c514 --- /dev/null +++ b/packages/sdk/src/stateinit.ts @@ -0,0 +1,153 @@ +import { + Address, + beginCell, + contractAddress, + storeStateInit, + type Cell, + type StateInit, +} from "@ton/core"; +import { staticCommitmentCell } from "../../artifact/src/commitments.js"; +import { peerRoot, peerRootCell, type PeerDescriptor } from "../../artifact/src/peers.js"; +import { routeRootCell, type RouteDescriptor } from "../../artifact/src/routes.js"; +import { normalizeRam, ramRoot, romRootCell } from "../../artifact/src/memory.js"; +import { createInitialState, type CoreConfig } from "../../emulator/src/model.js"; +import { coreStateCell, stateHash } from "../../emulator/src/commitments.js"; +import type { ArtifactBundle, ArtifactManifestV2 } from "../../artifact/src/manifest.js"; + +export interface V2StateInitDescriptor { + readonly manifest: Pick; + readonly rom: Cell; + readonly routes: readonly RouteDescriptor[]; + readonly peers: readonly PeerDescriptor[]; + readonly initialRam?: readonly number[] | Uint8Array; + readonly workchain?: number; +} + +export interface V2StateInitResult { + readonly data: Cell; + readonly stateInit: StateInit; + readonly stateInitCell: Cell; + readonly stateInitHash: string; + readonly address: Address; + readonly codeHash: string; + readonly dataHash: string; + readonly staticCommitment: string; + readonly initialStateHash: string; + readonly initialState: ReturnType; +} + +export function buildV2StateInit(code: Cell, descriptor: V2StateInitDescriptor): V2StateInitResult { + const normalizedPeers = descriptor.peers.map((peer) => ({ ...peer })); + const normalizedRoutes = descriptor.routes.map((route) => ({ ...route })); + const computedPeerRoot = peerRoot(normalizedPeers); + if (computedPeerRoot !== descriptor.manifest.peerRoot) throw new Error("peer root does not match the deployment manifest"); + const computedRouteRoot = routeRootCell(normalizedRoutes).hash().toString("hex"); + if (computedRouteRoot !== descriptor.manifest.routeRoot) throw new Error("route root does not match the deployment manifest"); + + const staticCell = staticCommitmentCell({ + schemaVersion: descriptor.manifest.schemaVersion, + isaVersion: descriptor.manifest.isaVersion, + abiVersion: descriptor.manifest.abiVersion, + protocolVersion: descriptor.manifest.protocolVersion, + runId: descriptor.manifest.runId, + coreId: descriptor.manifest.coreId, + programId: descriptor.manifest.programId, + romRoot: descriptor.manifest.romRoot, + routeRoot: descriptor.manifest.routeRoot, + peerRoot: descriptor.manifest.peerRoot, + limitsHash: descriptor.manifest.limitsHash, + }); + const computedStatic = staticCell.hash().toString("hex"); + if (computedStatic !== descriptor.manifest.staticCommitment) throw new Error("static commitment does not match the deployment manifest"); + + const config: CoreConfig = { + runId: descriptor.manifest.runId, + coreId: descriptor.manifest.coreId, + programId: descriptor.manifest.programId, + romRoot: descriptor.manifest.romRoot, + initialRamRoot: descriptor.manifest.initialRamRoot, + routeRoot: descriptor.manifest.routeRoot, + staticCommitment: descriptor.manifest.staticCommitment, + routes: normalizedRoutes, + requiredInputs: [], + maxStepsPerAdvance: 1, + }; + const initialState = createInitialState(config); + initialState.ram = normalizeRam(descriptor.initialRam ?? []); + if (ramRoot(initialState.ram) !== descriptor.manifest.initialRamRoot) throw new Error("initial RAM root does not match the deployment manifest"); + const coreCell = coreStateCell(initialState); + const routesCell = routeRootCell(normalizedRoutes); + const peersCell = peerRootCell(normalizedPeers); + const identityCell = beginCell() + .storeUint(descriptor.manifest.coreId, 16) + .storeUint(toBigInt(descriptor.manifest.runId), 256) + .storeUint(toBigInt(descriptor.manifest.staticCommitment), 256) + .storeUint(toBigInt(descriptor.manifest.programId), 256) + .storeRef( + beginCell() + .storeUint(toBigInt(descriptor.manifest.romRoot), 256) + .storeUint(toBigInt(descriptor.manifest.routeRoot), 256) + .storeUint(toBigInt(descriptor.manifest.limitsHash), 256) + .storeRef(beginCell().storeUint(toBigInt(descriptor.manifest.peerRoot), 256).endCell()) + .endCell(), + ) + .endCell(); + const networkCell = beginCell().storeRef(routesCell).storeRef(peersCell).endCell(); + const data = beginCell() + .storeUint(0x544e4c32, 32) + .storeUint(descriptor.manifest.schemaVersion, 16) + .storeUint(descriptor.manifest.isaVersion, 16) + .storeUint(descriptor.manifest.abiVersion, 16) + .storeUint(descriptor.manifest.protocolVersion, 16) + .storeRef(identityCell) + .storeRef(coreCell) + .storeRef(descriptor.rom) + .storeRef(networkCell) + .endCell(); + const stateInit: StateInit = { code, data }; + const stateInitCell = beginCell().store(storeStateInit(stateInit)).endCell(); + const workchain = descriptor.workchain ?? 0; + return { + data, + stateInit, + stateInitCell, + stateInitHash: stateInitCell.hash().toString("hex"), + address: contractAddress(workchain, stateInit), + codeHash: code.hash().toString("hex"), + dataHash: data.hash().toString("hex"), + staticCommitment: computedStatic, + initialStateHash: stateHash(initialState), + initialState, + }; +} + +export function buildV2StateInitFromArtifact(code: Cell, artifact: ArtifactBundle, workchain = 0): V2StateInitResult { + const romCell = romRootCell(artifact.assembly.words); + return buildV2StateInit(code, { + manifest: artifact.manifest, + rom: romCell, + routes: artifact.routes, + peers: artifact.peers, + initialRam: artifact.assembly.ram, + workchain, + }); +} + +function toBigInt(hash: string): bigint { + if (!/^[0-9a-f]{64}$/i.test(hash)) throw new RangeError("expected a 256-bit hexadecimal commitment"); + return BigInt(`0x${hash}`); +} diff --git a/tests/v2/stateinit.test.ts b/tests/v2/stateinit.test.ts new file mode 100644 index 0000000..55197e8 --- /dev/null +++ b/tests/v2/stateinit.test.ts @@ -0,0 +1,49 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { beginCell, Cell, contractAddress } from "@ton/core"; +import { buildArtifact } from "../../packages/artifact/src/manifest.js"; +import { buildV2StateInitFromArtifact } from "../../packages/sdk/src/stateinit.js"; + +const compiler = { + name: "tonolith-assembler", + version: "2.0.0-test", + gitCommit: "b".repeat(40), + node: "v26.7.0", +}; + +test("v2 StateInit and address derivation are deterministic and content-bound", () => { + const artifact = buildArtifact(".entry start\nstart:\nLDI 1\nHALT\n", { + name: "stateinit-fixture", + compiler, + }); + const code = beginCell().storeUint(0xdead, 16).endCell(); + const first = buildV2StateInitFromArtifact(code, artifact); + const second = buildV2StateInitFromArtifact(code, artifact); + + assert.equal(first.stateInitHash, first.stateInitCell.hash().toString("hex")); + assert.equal(first.dataHash, first.data.hash().toString("hex")); + assert.equal(first.codeHash, code.hash().toString("hex")); + assert.equal(first.address.toRawString(), contractAddress(0, first.stateInit).toRawString()); + assert.equal(first.address.toRawString(), second.address.toRawString()); + assert.equal(first.stateInitHash, second.stateInitHash); + assert.equal(first.initialStateHash, second.initialStateHash); + assert.equal(first.staticCommitment, artifact.manifest.staticCommitment); + assert.equal(first.initialState.config.staticCommitment, artifact.manifest.staticCommitment); + assert.equal(Cell.fromBoc(first.stateInitCell.toBoc({ idx: false }))[0]?.hash().toString("hex"), first.stateInitHash); +}); + +test("peer addresses are bound to the static commitment and StateInit data", () => { + const artifact = buildArtifact("NOP\n", { + name: "peer-fixture", + compiler, + peers: [{ coreId: 1, address: `0:${"11".repeat(32)}` }], + }); + const code = beginCell().storeUint(1, 1).endCell(); + const result = buildV2StateInitFromArtifact(code, artifact); + assert.notEqual(result.staticCommitment, "0".repeat(64)); + assert.equal(result.data.beginParse().remainingRefs, 4); + assert.throws( + () => buildV2StateInitFromArtifact(code, { ...artifact, manifest: { ...artifact.manifest, peerRoot: "0".repeat(64) } }), + /peer root/, + ); +}); From 05cb7551f8ba9996b1e6d37a781db27acd5ec862 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:56:16 +0100 Subject: [PATCH 28/67] feat: add independent v2 chain verifier --- packages/verifier/src/index.ts | 2 + packages/verifier/src/types.ts | 94 ++++++++++++ packages/verifier/src/verify.ts | 251 ++++++++++++++++++++++++++++++++ tests/v2/verifier.test.ts | 113 ++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 packages/verifier/src/index.ts create mode 100644 packages/verifier/src/types.ts create mode 100644 packages/verifier/src/verify.ts create mode 100644 tests/v2/verifier.test.ts diff --git a/packages/verifier/src/index.ts b/packages/verifier/src/index.ts new file mode 100644 index 0000000..bccd68b --- /dev/null +++ b/packages/verifier/src/index.ts @@ -0,0 +1,2 @@ +export * from "./types.js"; +export * from "./verify.js"; diff --git a/packages/verifier/src/types.ts b/packages/verifier/src/types.ts new file mode 100644 index 0000000..8c23094 --- /dev/null +++ b/packages/verifier/src/types.ts @@ -0,0 +1,94 @@ +import type { Cell } from "@ton/core"; +import type { Hash256 } from "../../abi/src/messages.js"; + +export interface RawAccountSnapshot { + readonly address: string; + readonly balanceNano?: string; + readonly codeBoc?: string; + readonly dataBoc?: string; + readonly stateInitBoc?: string; + readonly lastTransaction?: { readonly hash: string; readonly lt: string }; +} + +export interface RawMessageSnapshot { + readonly direction: "in" | "out"; + readonly source?: string; + readonly destination?: string; + readonly valueNano?: string; + readonly bodyBoc?: string; + readonly bounced?: boolean; +} + +export interface RawTransactionSnapshot { + readonly hash: string; + readonly lt: string; + readonly prevTransactionHash?: string; + readonly prevTransactionLt?: string; + readonly success: boolean; + readonly exitCode?: number; + readonly inbound?: RawMessageSnapshot; + readonly outbound: readonly RawMessageSnapshot[]; + readonly stateDataBoc?: string; + readonly feesNano?: string; +} + +export interface RawChainSnapshot { + readonly source: string; + readonly network: string; + readonly account: RawAccountSnapshot; + readonly transactions: readonly RawTransactionSnapshot[]; +} + +export interface RawChainSource { + readonly id: string; + fetchSnapshot(address: string): Promise; +} + +export interface V2VerificationExpectations { + readonly address: string; + readonly network: string; + readonly codeHash: Hash256; + readonly dataHash: Hash256; + readonly stateInitHash: Hash256; + readonly programId: Hash256; + readonly runId: Hash256; + readonly romRoot: Hash256; + readonly routeRoot: Hash256; + readonly staticCommitment: Hash256; +} + +export interface ReplayEvidence { + readonly initialState: import("../../emulator/src/model.js").V2State; + readonly rom: readonly number[]; +} + +export interface VerificationReport { + readonly overall: "verified" | "failed" | "incomplete"; + readonly network: string; + readonly address: string; + readonly sources: readonly string[]; + readonly deployment?: { + readonly transactionHash?: string; + readonly transactionLt?: string; + readonly codeHash?: Hash256; + readonly dataHash?: Hash256; + readonly stateInitHash?: Hash256; + }; + readonly acceptedAdvances: number; + readonly executedInstructions: bigint; + readonly deliveredInputs: number; + readonly duplicateDeliveries: number; + readonly staleAttempts: number; + readonly finalStateHash?: Hash256; + readonly finalStatus?: number; + readonly finalPc?: number; + readonly outputCommitment?: Hash256; + readonly totalFeesNano?: string; + readonly errors: readonly string[]; + readonly unresolved: readonly string[]; +} + +export interface ParsedBody { + readonly kind: string; + readonly cell: Cell; +} diff --git a/packages/verifier/src/verify.ts b/packages/verifier/src/verify.ts new file mode 100644 index 0000000..7bf7dcf --- /dev/null +++ b/packages/verifier/src/verify.ts @@ -0,0 +1,251 @@ +import { Address, Cell, contractAddress, loadStateInit } from "@ton/core"; +import { decodeV2Message, type V2Message } from "../../abi/src/messages.js"; +import { advance, type V2AdvanceRequest } from "../../emulator/src/executor.js"; +import { stateHash } from "../../emulator/src/commitments.js"; +import type { Hash256 } from "../../abi/src/messages.js"; +import type { + RawAccountSnapshot, + RawChainSnapshot, + RawChainSource, + RawMessageSnapshot, + RawTransactionSnapshot, + ReplayEvidence, + V2VerificationExpectations, + VerificationReport, +} from "./types.js"; + +export async function verifyRunFromSources( + address: string, + expectations: V2VerificationExpectations, + sources: readonly RawChainSource[], + replay?: ReplayEvidence, +): Promise { + if (sources.length < 2) { + return incompleteReport(expectations, sources.map((source) => source.id), "at least two independent sources are required"); + } + const snapshots = await Promise.all(sources.map((source) => source.fetchSnapshot(address))); + const errors: string[] = []; + const unresolved: string[] = []; + const first = snapshots[0]; + if (first === undefined) return incompleteReport(expectations, sources.map((source) => source.id), "no source returned a snapshot"); + for (const snapshot of snapshots) { + errors.push(...verifySnapshotIdentity(snapshot, expectations)); + errors.push(...verifyHistoryContinuity(snapshot.transactions)); + } + const fingerprint = snapshotFingerprint(first); + for (const snapshot of snapshots.slice(1)) { + if (snapshotFingerprint(snapshot) !== fingerprint) errors.push(`provider disagreement: ${snapshot.source}`); + } + + let replayResult: ReplayResult | undefined; + if (replay !== undefined && errors.length === 0) { + replayResult = replayAcceptedAdvances(first.transactions, replay); + errors.push(...replayResult.errors); + } else if (replay === undefined) { + unresolved.push("independent emulator replay was not supplied"); + } + const counters = summarizeTransactions(first.transactions); + const final = replayResult?.finalState; + const deployment = deploymentSummary(first.account); + const totalFeesNano = sumFees(first.transactions); + const overall: VerificationReport["overall"] = errors.length > 0 ? "failed" : unresolved.length > 0 ? "incomplete" : "verified"; + const reportBase = { + overall, + network: expectations.network, + address: Address.parse(expectations.address).toRawString(), + sources: sources.map((source) => source.id), + acceptedAdvances: counters.acceptedAdvances, + executedInstructions: replayResult?.executedInstructions ?? 0n, + deliveredInputs: counters.deliveredInputs, + duplicateDeliveries: counters.duplicateDeliveries, + staleAttempts: counters.staleAttempts, + errors, + unresolved, + ...(final === undefined ? {} : { finalStateHash: stateHash(final), finalStatus: final.status, finalPc: final.pc, outputCommitment: final.outputCommitment }), + ...(totalFeesNano === undefined ? {} : { totalFeesNano }), + }; + return deployment === undefined ? reportBase : { ...reportBase, deployment }; +} + +export function verifySnapshotIdentity(snapshot: RawChainSnapshot, expectations: V2VerificationExpectations): string[] { + const errors: string[] = []; + errors.push(...verifyAccount(snapshot.account, expectations)); + if (snapshot.network !== expectations.network) errors.push(`network mismatch from ${snapshot.source}`); + return errors; +} + +export function verifyAccount(account: RawAccountSnapshot, expectations: V2VerificationExpectations): string[] { + const errors: string[] = []; + let expectedAddress: Address | undefined; + try { + expectedAddress = Address.parse(expectations.address); + if (Address.parse(account.address).toRawString() !== expectedAddress.toRawString()) errors.push("account address does not match expected address"); + } catch (error) { + errors.push(`invalid account address: ${error instanceof Error ? error.message : String(error)}`); + } + if (expectedAddress === undefined) return errors; + if (account.codeBoc === undefined || account.dataBoc === undefined) { + errors.push("account code/data BOC is missing"); + return errors; + } + try { + const code = firstCell(account.codeBoc); + const data = firstCell(account.dataBoc); + const codeHash = code.hash().toString("hex"); + const dataHash = data.hash().toString("hex"); + if (codeHash !== expectations.codeHash) errors.push("code hash mismatch"); + if (dataHash !== expectations.dataHash) errors.push("data hash mismatch"); + const initCell = account.stateInitBoc === undefined ? undefined : firstCell(account.stateInitBoc); + if (initCell !== undefined) { + const stateInit = loadStateInit(initCell.beginParse()); + if (stateInit.code === undefined || stateInit.data === undefined) errors.push("StateInit lacks code or data"); + else { + const derived = contractAddress(expectedAddress.workChain, stateInit).toRawString(); + if (derived !== expectedAddress.toRawString()) errors.push("StateInit-derived address mismatch"); + if (initCell.hash().toString("hex") !== expectations.stateInitHash) errors.push("StateInit hash mismatch"); + } + } else { + errors.push("deployment StateInit BOC is missing"); + } + } catch (error) { + errors.push(`invalid account BOC: ${error instanceof Error ? error.message : String(error)}`); + } + return errors; +} + +export function verifyHistoryContinuity(transactions: readonly RawTransactionSnapshot[]): string[] { + const errors: string[] = []; + let previous: RawTransactionSnapshot | undefined; + for (const transaction of transactions) { + if (!/^[0-9a-f]+$/i.test(transaction.hash)) errors.push("transaction hash is not hexadecimal"); + if (!/^\d+$/.test(transaction.lt)) errors.push(`transaction ${transaction.hash} has an invalid logical time`); + if (previous !== undefined) { + if (transaction.prevTransactionHash !== undefined && transaction.prevTransactionHash !== previous.hash) errors.push(`history link mismatch at ${transaction.hash}`); + if (transaction.prevTransactionLt !== undefined && transaction.prevTransactionLt !== previous.lt) errors.push(`history logical-time link mismatch at ${transaction.hash}`); + if (BigInt(transaction.lt) <= BigInt(previous.lt)) errors.push(`history is not strictly ordered at ${transaction.hash}`); + } + previous = transaction; + } + return errors; +} + +export function parseMessageBody(message: RawMessageSnapshot): { readonly message?: V2Message; readonly error?: string } { + if (message.bodyBoc === undefined) return { error: "message body BOC is missing" }; + try { + return { message: decodeV2Message(firstCell(message.bodyBoc)) }; + } catch (error) { + return { error: `invalid v2 message body: ${error instanceof Error ? error.message : String(error)}` }; + } +} + +interface ReplayResult { + readonly finalState: ReplayEvidence["initialState"]; + readonly executedInstructions: bigint; + readonly errors: readonly string[]; +} + +function replayAcceptedAdvances(transactions: readonly RawTransactionSnapshot[], evidence: ReplayEvidence): ReplayResult { + let current = evidence.initialState; + let executedInstructions = 0n; + const errors: string[] = []; + for (const transaction of transactions) { + if (!transaction.success || transaction.inbound === undefined) continue; + const parsed = parseMessageBody(transaction.inbound); + if (parsed.message?.kind !== "advance") continue; + const request: V2AdvanceRequest = { + queryId: parsed.message.queryId, + expectedAdvanceCount: parsed.message.expectedAdvanceCount, + expectedStateHash: parsed.message.expectedStateHash, + maxInstructions: parsed.message.maxInstructions, + maxOutputs: parsed.message.maxOutputs, + }; + try { + const result = advance(current, { rom: evidence.rom }, request); + current = result.state; + executedInstructions += BigInt(result.executed); + const observed = transaction.stateDataBoc === undefined ? undefined : stateHashFromBoc(transaction.stateDataBoc); + if (observed !== undefined && observed !== stateHash(current)) errors.push(`state hash mismatch after transaction ${transaction.hash}`); + } catch (error) { + errors.push(`replay failed at ${transaction.hash}: ${error instanceof Error ? error.message : String(error)}`); + } + } + return { finalState: current, executedInstructions, errors }; +} + +function stateHashFromBoc(boc: string): Hash256 { + return firstCell(boc).hash().toString("hex"); +} + +function firstCell(boc: string): Cell { + const cells = Cell.fromBoc(Buffer.from(boc, "base64")); + const cell = cells[0]; + if (cell === undefined) throw new Error("BOC did not contain a root cell"); + return cell; +} + +function deploymentSummary(account: RawAccountSnapshot): VerificationReport["deployment"] { + if (account.stateInitBoc === undefined) return undefined; + const init = firstCell(account.stateInitBoc); + const state = loadStateInit(init.beginParse()); + const stateInitHash = init.hash().toString("hex"); + const codeHash = state.code === null || state.code === undefined ? undefined : state.code.hash().toString("hex"); + const dataHash = state.data === null || state.data === undefined ? undefined : state.data.hash().toString("hex"); + return { + stateInitHash, + ...(codeHash === undefined ? {} : { codeHash }), + ...(dataHash === undefined ? {} : { dataHash }), + }; +} + +function summarizeTransactions(transactions: readonly RawTransactionSnapshot[]): { acceptedAdvances: number; deliveredInputs: number; duplicateDeliveries: number; staleAttempts: number } { + let acceptedAdvances = 0; + let deliveredInputs = 0; + let duplicateDeliveries = 0; + let staleAttempts = 0; + for (const transaction of transactions) { + const parsed = transaction.inbound === undefined ? undefined : parseMessageBody(transaction.inbound).message; + if (parsed?.kind === "advance") { + if (transaction.success) acceptedAdvances += 1; + else if (transaction.exitCode === 120) staleAttempts += 1; + } + if (parsed?.kind === "deliverInput" && transaction.success) deliveredInputs += 1; + if (parsed?.kind === "deliverInput" && transaction.success && transaction.outbound.some((message) => parseMessageBody(message).message?.kind === "inputAccepted")) { + duplicateDeliveries += transaction.outbound.filter((message) => parseMessageBody(message).message?.kind === "inputAccepted").length > 1 ? 1 : 0; + } + } + return { acceptedAdvances, deliveredInputs, duplicateDeliveries, staleAttempts }; +} + +function sumFees(transactions: readonly RawTransactionSnapshot[]): string | undefined { + let total = 0n; + let found = false; + for (const transaction of transactions) { + if (transaction.feesNano === undefined) continue; + total += BigInt(transaction.feesNano); + found = true; + } + return found ? total.toString() : undefined; +} + +function snapshotFingerprint(snapshot: RawChainSnapshot): string { + return JSON.stringify({ + account: snapshot.account, + transactions: snapshot.transactions, + }); +} + +function incompleteReport(expectations: V2VerificationExpectations, sources: readonly string[], reason: string): VerificationReport { + return { + overall: "incomplete", + network: expectations.network, + address: expectations.address, + sources, + acceptedAdvances: 0, + executedInstructions: 0n, + deliveredInputs: 0, + duplicateDeliveries: 0, + staleAttempts: 0, + errors: [], + unresolved: [reason], + }; +} diff --git a/tests/v2/verifier.test.ts b/tests/v2/verifier.test.ts new file mode 100644 index 0000000..00e4d38 --- /dev/null +++ b/tests/v2/verifier.test.ts @@ -0,0 +1,113 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { beginCell } from "@ton/core"; +import { encodeAdvanceV2 } from "../../packages/abi/src/messages.js"; +import { buildArtifact } from "../../packages/artifact/src/manifest.js"; +import { coreStateCell } from "../../packages/emulator/src/commitments.js"; +import { advance } from "../../packages/emulator/src/executor.js"; +import { buildV2StateInitFromArtifact } from "../../packages/sdk/src/stateinit.js"; +import { verifyRunFromSources, type RawChainSnapshot, type RawChainSource } from "../../packages/verifier/src/index.js"; + +const compiler = { + name: "tonolith-assembler", + version: "2.0.0-test", + gitCommit: "c".repeat(40), + node: "v26.7.0", +}; + +test("independent verifier checks StateInit, history, provider agreement, and emulator replay", async () => { + const artifact = buildArtifact(".entry start\nstart:\nLDI 1\nHALT\n", { name: "verifier-fixture", compiler }); + const deployment = buildV2StateInitFromArtifact(beginCell().storeUint(1, 1).endCell(), artifact); + const advanced = advance( + deployment.initialState, + { rom: artifact.assembly.words }, + { + expectedAdvanceCount: 0n, + expectedStateHash: deployment.initialStateHash, + maxInstructions: 1, + maxOutputs: 1, + }, + ); + const deploymentHash = "d".repeat(64); + const advanceHash = "e".repeat(64); + const snapshot: RawChainSnapshot = { + source: "fixture-a", + network: "testnet", + account: { + address: deployment.address.toRawString(), + codeBoc: deployment.stateInit.code!.toBoc({ idx: false }).toString("base64"), + dataBoc: deployment.data.toBoc({ idx: false }).toString("base64"), + stateInitBoc: deployment.stateInitCell.toBoc({ idx: false }).toString("base64"), + }, + transactions: [ + { hash: deploymentHash, lt: "1", success: true, outbound: [] }, + { + hash: advanceHash, + lt: "2", + prevTransactionHash: deploymentHash, + prevTransactionLt: "1", + success: true, + inbound: { + direction: "in", + destination: deployment.address.toRawString(), + bodyBoc: encodeAdvanceV2({ + queryId: 1n, + runId: artifact.manifest.runId, + expectedAdvanceCount: 0n, + expectedStateHash: deployment.initialStateHash, + maxInstructions: 1, + maxOutputs: 1, + }).toBoc({ idx: false }).toString("base64"), + }, + outbound: [], + stateDataBoc: coreStateCell(advanced.state).toBoc({ idx: false }).toString("base64"), + feesNano: "1000", + }, + ], + }; + const source = (id: string, value: RawChainSnapshot = snapshot): RawChainSource => ({ + id, + fetchSnapshot: async () => ({ ...value, source: id }), + }); + const report = await verifyRunFromSources( + deployment.address.toRawString(), + { + address: deployment.address.toRawString(), + network: "testnet", + codeHash: deployment.codeHash, + dataHash: deployment.dataHash, + stateInitHash: deployment.stateInitHash, + programId: artifact.manifest.programId, + runId: artifact.manifest.runId, + romRoot: artifact.manifest.romRoot, + routeRoot: artifact.manifest.routeRoot, + staticCommitment: artifact.manifest.staticCommitment, + }, + [source("fixture-a"), source("fixture-b")], + { initialState: deployment.initialState, rom: artifact.assembly.words }, + ); + assert.equal(report.overall, "verified"); + assert.equal(report.acceptedAdvances, 1); + assert.equal(report.executedInstructions, 1n); + assert.equal(report.finalStateHash, advanced.nextStateHash); + assert.equal(report.totalFeesNano, "1000"); + + const disagreement = await verifyRunFromSources( + deployment.address.toRawString(), + { + address: deployment.address.toRawString(), + network: "testnet", + codeHash: deployment.codeHash, + dataHash: deployment.dataHash, + stateInitHash: deployment.stateInitHash, + programId: artifact.manifest.programId, + runId: artifact.manifest.runId, + romRoot: artifact.manifest.romRoot, + routeRoot: artifact.manifest.routeRoot, + staticCommitment: artifact.manifest.staticCommitment, + }, + [source("fixture-a"), source("fixture-b", { ...snapshot, transactions: [{ ...snapshot.transactions[0]!, hash: "f".repeat(64) }, ...snapshot.transactions.slice(1)] })], + ); + assert.equal(disagreement.overall, "failed"); + assert.match(disagreement.errors.join("\n"), /provider disagreement/); +}); From 8bf90994c0e76b334085d0f8809e0ae954855b0b Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:00:28 +0100 Subject: [PATCH 29/67] feat: serialize v2 artifact build inputs --- packages/artifact/src/manifest.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/artifact/src/manifest.ts b/packages/artifact/src/manifest.ts index 33798c9..f5e334c 100644 --- a/packages/artifact/src/manifest.ts +++ b/packages/artifact/src/manifest.ts @@ -147,7 +147,7 @@ export function buildArtifact(source: string, options: BuildArtifactOptions): Ar sourceHash: sha256(assembly.canonicalSource), compiler, }; - const files = makeFiles(manifest, source, assembly, routes, peers); + const files = makeFiles(manifest, source, assembly, routes, peers, limits); return { manifest, source, assembly, routes, peers, limits, files }; } @@ -235,7 +235,7 @@ export function canonicalJson(value: unknown): string { return JSON.stringify(sortJson(value)); } -function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2AssemblyResult, routes: readonly RouteDescriptor[], peers: readonly PeerDescriptor[]): Readonly> { +function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2AssemblyResult, routes: readonly RouteDescriptor[], peers: readonly PeerDescriptor[], limits: LimitsV2): Readonly> { const artifactJson = canonicalJson(manifest); return { "artifact.json": artifactJson, @@ -244,6 +244,16 @@ function makeFiles(manifest: ArtifactManifestV2, source: string, assembly: V2Ass "initial-ram.boc": ramRootCell(assembly.ram).toBoc({ idx: false }).toString("base64"), "symbols.json": canonicalJson({ labels: assembly.labels, exports: assembly.exports }), "source-map.json": canonicalJson(assembly.sourceMap), + "assembly.json": canonicalJson({ + words: assembly.words, + ram: Array.from(assembly.ram), + entryPc: assembly.entryPc, + labels: assembly.labels, + exports: assembly.exports, + sourceMap: assembly.sourceMap, + canonicalSource: assembly.canonicalSource, + }), + "limits.json": canonicalJson(limits), "routes.json": canonicalJson(routes), "peers.json": canonicalJson(peers), "BUILDINFO.json": canonicalJson({ artifactSha256: sha256(artifactJson), compiler: manifest.compiler, sourceHash: manifest.sourceHash }), From b47013a3c4d0d17d76aba99ce97cd1e9ea38c25a Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:00:38 +0100 Subject: [PATCH 30/67] feat: add Node-safe v2 artifact loader --- package.json | 5 ++ packages/sdk/src/node.ts | 108 ++++++++++++++++++++++++++++++++++++++ tests/v2/sdk-node.test.ts | 23 ++++++++ 3 files changed, 136 insertions(+) create mode 100644 packages/sdk/src/node.ts create mode 100644 tests/v2/sdk-node.test.ts diff --git a/package.json b/package.json index 1642041..c8c7024 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,11 @@ "types": "./dist/packages/sdk/src/index.d.ts", "import": "./dist/packages/sdk/src/index.js", "default": "./dist/packages/sdk/src/index.js" + }, + "./v2/node": { + "types": "./dist/packages/sdk/src/node.d.ts", + "import": "./dist/packages/sdk/src/node.js", + "default": "./dist/packages/sdk/src/node.js" } }, "files": [ diff --git a/packages/sdk/src/node.ts b/packages/sdk/src/node.ts new file mode 100644 index 0000000..55a45b1 --- /dev/null +++ b/packages/sdk/src/node.ts @@ -0,0 +1,108 @@ +import { readFile, readdir } from "node:fs/promises"; +import { readFileSync, readdirSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { assertArtifact, type ArtifactBundle, type ArtifactManifestV2, type CompilerInfo } from "../../artifact/src/manifest.js"; +import type { V2AssemblyResult, V2SourceMapEntry } from "../../artifact/src/assembler.js"; +import type { LimitsV2 } from "../../artifact/src/commitments.js"; +import { normalizePeers, type PeerDescriptor } from "../../artifact/src/peers.js"; +import { normalizeRoutes, type RouteDescriptor } from "../../artifact/src/routes.js"; + +const REQUIRED_FILES = [ + "artifact.json", + "program.tasm", + "assembly.json", + "limits.json", + "routes.json", + "peers.json", +] as const; + +/** Load a canonical v2 artifact without relying on the checkout that built it. */ +export async function loadArtifactDirectory(directory: string): Promise { + const root = resolve(directory); + const names = new Set(await readdir(root)); + const files: Record = {}; + await Promise.all(REQUIRED_FILES.map(async (name) => { + if (!names.has(name)) throw new Error(`artifact is missing ${name}`); + files[name] = await readFile(join(root, name), "utf8"); + })); + for (const name of names) { + if (name === "artifact.json" || name === "program.tasm" || name === "assembly.json" || name === "limits.json" || name === "routes.json" || name === "peers.json") continue; + const path = join(root, name); + const content = await readFile(path, "utf8").catch(() => undefined); + if (content !== undefined) files[name] = content; + } + return finishBundle(files); +} + +/** Synchronous counterpart for CLIs and deterministic build scripts. */ +export function loadArtifactDirectorySync(directory: string): ArtifactBundle { + const root = resolve(directory); + const names = new Set(readdirSync(root)); + const files: Record = {}; + for (const name of REQUIRED_FILES) { + if (!names.has(name)) throw new Error(`artifact is missing ${name}`); + files[name] = readFileSync(join(root, name), "utf8"); + } + for (const name of names) { + if (REQUIRED_FILES.includes(name as typeof REQUIRED_FILES[number])) continue; + try { + files[name] = readFileSync(join(root, name), "utf8"); + } catch { + // Ignore directories and binary sidecars; canonical required files are text. + } + } + return finishBundle(files); +} + +function finishBundle(files: Record): ArtifactBundle { + const manifest = parseJson(files["artifact.json"], "artifact.json"); + const assemblyJson = parseJson(files["assembly.json"], "assembly.json"); + const limits = parseJson(files["limits.json"], "limits.json"); + const routes = normalizeRoutes(parseJson(files["routes.json"], "routes.json")); + const peers = normalizePeers(parseJson(files["peers.json"], "peers.json")); + const assembly: V2AssemblyResult = { + words: [...assemblyJson.words], + ram: Uint8Array.from(assemblyJson.ram), + entryPc: assemblyJson.entryPc, + labels: { ...assemblyJson.labels }, + exports: { ...assemblyJson.exports }, + sourceMap: assemblyJson.sourceMap.map((entry) => ({ ...entry })), + canonicalSource: assemblyJson.canonicalSource, + }; + const bundle: ArtifactBundle = { + manifest, + source: requiredFile(files, "program.tasm"), + assembly, + routes, + peers, + limits, + files, + }; + assertArtifact(bundle); + return bundle; +} + +function requiredFile(files: Readonly>, name: string): string { + const value = files[name]; + if (value === undefined) throw new Error(`artifact is missing ${name}`); + return value; +} + +interface SerializedAssembly { + readonly words: readonly number[]; + readonly ram: readonly number[]; + readonly entryPc: number; + readonly labels: Readonly>; + readonly exports: Readonly>; + readonly sourceMap: readonly V2SourceMapEntry[]; + readonly canonicalSource: string; +} + +function parseJson(value: string | undefined, file: string): T { + if (value === undefined) throw new Error(`artifact is missing ${file}`); + try { + return JSON.parse(value) as T; + } catch (error) { + throw new Error(`invalid ${file}: ${error instanceof Error ? error.message : String(error)}`); + } +} diff --git a/tests/v2/sdk-node.test.ts b/tests/v2/sdk-node.test.ts new file mode 100644 index 0000000..0f46902 --- /dev/null +++ b/tests/v2/sdk-node.test.ts @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { buildArtifact } from "../../packages/artifact/src/manifest.js"; +import { loadArtifactDirectory } from "../../packages/sdk/src/node.js"; + +const compiler = { name: "tonolith-assembler", version: "2.0.0-test", gitCommit: "c".repeat(40), node: "v26.7.0" }; + +test("Node SDK loads and independently verifies a canonical artifact directory", async () => { + const artifact = buildArtifact(".entry start\nstart:\nLDI 1\nHALT\n", { name: "node-loader", compiler }); + const directory = await mkdtemp(join(tmpdir(), "tonolith-v2-artifact-")); + try { + for (const [name, content] of Object.entries(artifact.files)) await writeFile(join(directory, name), content, "utf8"); + const loaded = await loadArtifactDirectory(directory); + assert.equal(loaded.manifest.staticCommitment, artifact.manifest.staticCommitment); + assert.deepEqual(loaded.assembly.words, artifact.assembly.words); + assert.deepEqual(Array.from(loaded.assembly.ram), Array.from(artifact.assembly.ram)); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); From bac46e2db895e50cfd66e1f8adc3a1fbc46285f5 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:06:18 +0100 Subject: [PATCH 31/67] feat: add fail-closed permissionless keeper planner --- packages/keeper/src/index.ts | 3 ++ packages/keeper/src/planner.ts | 67 ++++++++++++++++++++++++++++++++++ packages/keeper/src/runner.ts | 26 +++++++++++++ packages/keeper/src/types.ts | 67 ++++++++++++++++++++++++++++++++++ tests/v2/keeper.test.ts | 62 +++++++++++++++++++++++++++++++ 5 files changed, 225 insertions(+) create mode 100644 packages/keeper/src/index.ts create mode 100644 packages/keeper/src/planner.ts create mode 100644 packages/keeper/src/runner.ts create mode 100644 packages/keeper/src/types.ts create mode 100644 tests/v2/keeper.test.ts diff --git a/packages/keeper/src/index.ts b/packages/keeper/src/index.ts new file mode 100644 index 0000000..aeec99d --- /dev/null +++ b/packages/keeper/src/index.ts @@ -0,0 +1,3 @@ +export * from "./types.js"; +export * from "./planner.js"; +export * from "./runner.js"; diff --git a/packages/keeper/src/planner.ts b/packages/keeper/src/planner.ts new file mode 100644 index 0000000..e271787 --- /dev/null +++ b/packages/keeper/src/planner.ts @@ -0,0 +1,67 @@ +import { Address } from "@ton/core"; +import { encodeAdvanceV2, encodeDispatchOutputV2 } from "../../abi/src/messages.js"; +import { STATUS } from "../../isa/src/constants.js"; +import type { KeeperAction, KeeperCoreConfig, KeeperCoreObservation } from "./types.js"; + +export class KeeperSafetyError extends Error { + public constructor(message: string) { + super(message); + this.name = "KeeperSafetyError"; + } +} + +export function planNextAction(config: KeeperCoreConfig, observation: KeeperCoreObservation, queryId = 0n): KeeperAction | undefined { + assertObservation(config, observation); + if (observation.pendingOutputs.length > 0) { + const output = observation.pendingOutputs[0]; + if (output === undefined) return undefined; + return { + kind: "dispatchOutput", + address: observation.address, + runId: observation.runId, + expectedStateHash: observation.stateHash, + outputId: output.outputId, + bodyBoc: encodeDispatchOutputV2({ + queryId, + runId: observation.runId, + expectedStateHash: observation.stateHash, + outputId: output.outputId, + }).toBoc({ idx: false }).toString("base64"), + valueNano: config.dispatchValueNano, + reason: "delivery-priority", + }; + } + if (observation.status !== STATUS.running) return undefined; + return { + kind: "advance", + address: observation.address, + runId: observation.runId, + expectedStateHash: observation.stateHash, + expectedAdvanceCount: observation.advanceCount, + bodyBoc: encodeAdvanceV2({ + queryId, + runId: observation.runId, + expectedAdvanceCount: observation.advanceCount, + expectedStateHash: observation.stateHash, + maxInstructions: config.maxInstructions, + maxOutputs: config.maxOutputs, + }).toBoc({ idx: false }).toString("base64"), + valueNano: config.dispatchValueNano, + reason: "advance-runnable", + }; +} + +function assertObservation(config: KeeperCoreConfig, observation: KeeperCoreObservation): void { + try { + if (Address.parse(config.address).toRawString() !== Address.parse(observation.address).toRawString()) throw new KeeperSafetyError("core address mismatch"); + } catch (error) { + if (error instanceof KeeperSafetyError) throw error; + throw new KeeperSafetyError(`invalid core address: ${error instanceof Error ? error.message : String(error)}`); + } + if (observation.network !== "testnet" || config.network !== "testnet") throw new KeeperSafetyError("keeper is restricted to TON testnet"); + if (observation.codeHash !== config.codeHash) throw new KeeperSafetyError("core code hash mismatch"); + if (observation.runId !== config.runId) throw new KeeperSafetyError("core run ID mismatch"); + if (!Number.isInteger(config.maxInstructions) || config.maxInstructions < 1 || config.maxInstructions > 0xffff) throw new KeeperSafetyError("invalid instruction cap"); + if (!Number.isInteger(config.maxOutputs) || config.maxOutputs < 1 || config.maxOutputs > 0xff) throw new KeeperSafetyError("invalid output cap"); + if (config.dispatchValueNano < 0n) throw new KeeperSafetyError("negative keeper value cap"); +} diff --git a/packages/keeper/src/runner.ts b/packages/keeper/src/runner.ts new file mode 100644 index 0000000..087a1e4 --- /dev/null +++ b/packages/keeper/src/runner.ts @@ -0,0 +1,26 @@ +import type { KeeperCoreConfig, KeeperCoreObservation, KeeperProvider, KeeperRunResult, KeeperWallet } from "./types.js"; +import { KeeperSafetyError, planNextAction } from "./planner.js"; + +export async function runKeeperOnce( + config: KeeperCoreConfig, + provider: KeeperProvider, + wallet?: KeeperWallet, + queryId = 0n, +): Promise { + let observation: KeeperCoreObservation; + try { + observation = await provider.readCore(config.address); + const action = planNextAction(config, observation, queryId); + if (action === undefined) return { provider: provider.id, ...(wallet === undefined ? {} : { wallet: wallet.id }), errors: [] }; + if (wallet === undefined) return { provider: provider.id, action, errors: [] }; + const submission = await wallet.submitInternal(action.address, action.bodyBoc, action.valueNano); + if (!submission.acceptedByRpc || submission.transactionHash === undefined) { + return { provider: provider.id, wallet: wallet.id, action, submission, finalized: false, errors: ["RPC acceptance or transaction hash was unavailable; no retry was attempted"] }; + } + const finality = await wallet.waitForFinality(submission.transactionHash); + return { provider: provider.id, wallet: wallet.id, action, submission, finalized: finality.finalized, errors: finality.finalized ? [] : ["transaction was not masterchain-finalized"] }; + } catch (error) { + const message = error instanceof KeeperSafetyError ? error.message : error instanceof Error ? error.message : String(error); + return { provider: provider.id, ...(wallet === undefined ? {} : { wallet: wallet.id }), errors: [message] }; + } +} diff --git a/packages/keeper/src/types.ts b/packages/keeper/src/types.ts new file mode 100644 index 0000000..aeb70b0 --- /dev/null +++ b/packages/keeper/src/types.ts @@ -0,0 +1,67 @@ +import type { Hash256 } from "../../abi/src/messages.js"; +import type { CoreStatus } from "../../isa/src/constants.js"; + +export interface KeeperCoreConfig { + readonly address: string; + readonly runId: Hash256; + readonly codeHash: Hash256; + readonly network: "testnet"; + readonly maxInstructions: number; + readonly maxOutputs: number; + readonly dispatchValueNano: bigint; +} + +export interface PendingOutputObservation { + readonly outputId: Hash256; +} + +export interface KeeperCoreObservation { + readonly address: string; + readonly network: "testnet"; + readonly codeHash: Hash256; + readonly runId: Hash256; + readonly stateHash: Hash256; + readonly advanceCount: bigint; + readonly status: CoreStatus; + readonly pendingOutputs: readonly PendingOutputObservation[]; + readonly inputCount: bigint; +} + +export interface KeeperProvider { + readonly id: string; + readCore(address: string): Promise; +} + +export interface KeeperSubmission { + readonly transactionHash?: string; + readonly acceptedByRpc: boolean; +} + +export interface KeeperWallet { + readonly id: string; + submitInternal(address: string, bodyBoc: string, valueNano: bigint): Promise; + waitForFinality(transactionHash: string): Promise<{ readonly finalized: boolean; readonly stateHash?: Hash256 }>; +} + +export type KeeperActionKind = "dispatchOutput" | "advance"; + +export interface KeeperAction { + readonly kind: KeeperActionKind; + readonly address: string; + readonly runId: Hash256; + readonly expectedStateHash: Hash256; + readonly expectedAdvanceCount?: bigint; + readonly outputId?: Hash256; + readonly bodyBoc: string; + readonly valueNano: bigint; + readonly reason: "delivery-priority" | "advance-runnable"; +} + +export interface KeeperRunResult { + readonly provider: string; + readonly wallet?: string; + readonly action?: KeeperAction; + readonly submission?: KeeperSubmission; + readonly finalized?: boolean; + readonly errors: readonly string[]; +} diff --git a/tests/v2/keeper.test.ts b/tests/v2/keeper.test.ts new file mode 100644 index 0000000..38a0109 --- /dev/null +++ b/tests/v2/keeper.test.ts @@ -0,0 +1,62 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { buildArtifact } from "../../packages/artifact/src/manifest.js"; +import { runKeeperOnce, planNextAction } from "../../packages/keeper/src/index.js"; +import { STATUS } from "../../packages/isa/src/constants.js"; + +const artifact = buildArtifact("NOP\n", { name: "keeper-fixture", compiler: { name: "tonolith-assembler", version: "2.0.0-test", gitCommit: "d".repeat(40), node: "v26.7.0" } }); +const base = { + address: "0:" + "12".repeat(32), + runId: artifact.manifest.runId, + codeHash: "ab".repeat(32), + network: "testnet" as const, + maxInstructions: 1, + maxOutputs: 1, + dispatchValueNano: 20_000_000n, +}; + +function observation(overrides: Partial[1]> = {}) { + return { + address: base.address, + network: "testnet" as const, + codeHash: base.codeHash, + runId: base.runId, + stateHash: "ef".repeat(32), + advanceCount: 4n, + status: STATUS.running, + pendingOutputs: [], + inputCount: 0n, + ...overrides, + }; +} + +test("keeper prioritizes pending delivery and binds the observed state", () => { + const action = planNextAction(base, observation({ pendingOutputs: [{ outputId: "01".repeat(32) }] })); + assert.equal(action?.kind, "dispatchOutput"); + assert.equal(action?.reason, "delivery-priority"); + assert.equal(action?.expectedStateHash, "ef".repeat(32)); +}); + +test("keeper plans a single bounded advance only for a fresh runnable core", () => { + const action = planNextAction(base, observation()); + assert.equal(action?.kind, "advance"); + assert.equal(action?.expectedAdvanceCount, 4n); + assert.throws(() => planNextAction(base, observation({ codeHash: "cd".repeat(32) })), /code hash mismatch/); + assert.equal(planNextAction(base, observation({ status: STATUS.waitingInput })), undefined); +}); + +test("keeper runner is read-only without a wallet and never retries unknown submission state", async () => { + const provider = { id: "fixture-a", readCore: async () => observation() }; + const planned = await runKeeperOnce(base, provider); + assert.equal(planned.action?.kind, "advance"); + assert.equal(planned.wallet, undefined); + let submitted = 0; + const result = await runKeeperOnce(base, provider, { + id: "wallet-fixture", + submitInternal: async () => { submitted += 1; return { acceptedByRpc: false }; }, + waitForFinality: async () => ({ finalized: false }), + }); + assert.equal(submitted, 1); + assert.equal(result.finalized, false); + assert.match(result.errors[0] ?? "", /no retry/); +}); From c6e82daf07234be03498988ed747d1680bfa2899 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:06:32 +0100 Subject: [PATCH 32/67] feat: add Tonolith v2 operational CLI --- package.json | 3 + tools/tonolith-v2.ts | 297 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 tools/tonolith-v2.ts diff --git a/package.json b/package.json index c8c7024..daeca74 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "type": "module", "main": "./dist/tools/index.js", "types": "./dist/tools/index.d.ts", + "bin": { + "tonolith": "./dist/tools/tonolith-v2.js" + }, "exports": { ".": { "types": "./dist/tools/index.d.ts", diff --git a/tools/tonolith-v2.ts b/tools/tonolith-v2.ts new file mode 100644 index 0000000..45b190b --- /dev/null +++ b/tools/tonolith-v2.ts @@ -0,0 +1,297 @@ +import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises"; +import { basename, dirname, extname, join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; +import { Cell } from "@ton/core"; +import { buildArtifact, canonicalJson, verifyArtifact, type ArtifactBundle } from "../packages/artifact/src/manifest.js"; +import { encodeAdvanceV2 } from "../packages/abi/src/messages.js"; +import { advance } from "../packages/emulator/src/executor.js"; +import { stateHash } from "../packages/emulator/src/commitments.js"; +import type { InputRecord, OutputRecord, V2State } from "../packages/emulator/src/model.js"; +import { loadArtifactDirectory } from "../packages/sdk/src/node.js"; +import { buildV2StateInitFromArtifact } from "../packages/sdk/src/stateinit.js"; +import { planNextAction } from "../packages/keeper/src/planner.js"; +import type { KeeperCoreConfig, KeeperCoreObservation } from "../packages/keeper/src/types.js"; +import { verifyRunFromSources, type RawChainSnapshot, type ReplayEvidence, type V2VerificationExpectations } from "../packages/verifier/src/index.js"; + +export interface CliIo { + readonly stdout: (value: string) => void; + readonly stderr: (value: string) => void; +} + +const defaultIo: CliIo = { stdout: (value) => console.log(value), stderr: (value) => console.error(value) }; + +export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Promise { + try { + const [command, ...rest] = argv; + if (command === undefined) return usage(io, 2); + switch (command) { + case "assemble": return await assembleCommand(rest, io); + case "inspect": return await inspectCommand(rest, io); + case "verify-artifact": return await verifyArtifactCommand(rest, io); + case "derive-address": return await deriveAddressCommand(rest, io); + case "deploy-plan": return await deployPlanCommand(rest, io); + case "verify-run": return await verifyRunCommand(rest, io); + case "replay": return incompleteNetworkCommand("replay", io); + case "trace": return await traceCommand(rest, io); + case "keeper": return await keeperCommand(rest, io); + case "benchmark": return await benchmarkCommand(rest, io); + default: return usage(io, 2, `unknown command ${command}`); + } + } catch (error) { + io.stderr(error instanceof Error ? error.message : String(error)); + return 1; + } +} + +async function assembleCommand(args: readonly string[], io: CliIo): Promise { + const sourcePath = args[0]; + const out = option(args, "--out"); + if (sourcePath === undefined || out === undefined) return usage(io, 2, "assemble requires --out "); + const source = await readFile(resolve(sourcePath), "utf8"); + const artifact = buildArtifact(source, { + name: basename(sourcePath, extname(sourcePath)), + fileName: basename(sourcePath), + compiler: { name: "tonolith-assembler", version: "2.0.0", gitCommit: "0".repeat(40), node: process.version }, + }); + await writeArtifactDirectory(resolve(out), artifact); + io.stdout(json({ command: "assemble", artifactDir: resolve(out), manifest: artifact.manifest })); + return 0; +} + +async function inspectCommand(args: readonly string[], io: CliIo): Promise { + const target = args[0]; + if (target === undefined) return usage(io, 2, "inspect requires "); + const targetPath = resolve(target); + const targetStat = await stat(targetPath); + if (targetStat.isDirectory()) { + const artifact = await loadArtifactDirectory(targetPath); + io.stdout(json({ command: "inspect", kind: "artifact", manifest: artifact.manifest, files: Object.keys(artifact.files).sort() })); + return 0; + } + const cell = firstCell(await readFile(targetPath)); + io.stdout(json({ command: "inspect", kind: "boc", hash: cell.hash().toString("hex"), refs: cell.refs.length, bits: cell.bits.length })); + return 0; +} + +async function verifyArtifactCommand(args: readonly string[], io: CliIo): Promise { + const directory = args[0]; + if (directory === undefined) return usage(io, 2, "verify-artifact requires "); + try { + const artifact = await loadArtifactDirectory(directory); + const report = verifyArtifact(artifact); + io.stdout(json({ command: "verify-artifact", ...report })); + return report.valid ? 0 : 1; + } catch (error) { + io.stdout(json({ command: "verify-artifact", valid: false, errors: [error instanceof Error ? error.message : String(error)] })); + return 1; + } +} + +async function deriveAddressCommand(args: readonly string[], io: CliIo): Promise { + const directory = args[0]; + const network = option(args, "--network"); + const codePath = option(args, "--code-boc"); + if (directory === undefined || network === undefined || codePath === undefined) return usage(io, 2, "derive-address requires --network testnet --code-boc "); + assertTestnet(network); + const artifact = await loadArtifactDirectory(directory); + const code = firstCell(await readFile(resolve(codePath))); + const result = buildV2StateInitFromArtifact(code, artifact); + io.stdout(json({ command: "derive-address", network, address: result.address.toString(), codeHash: result.codeHash, dataHash: result.dataHash, stateInitHash: result.stateInitHash, staticCommitment: result.staticCommitment, initialStateHash: result.initialStateHash })); + return 0; +} + +async function deployPlanCommand(args: readonly string[], io: CliIo): Promise { + const runPath = args[0]; + const network = option(args, "--network"); + if (runPath === undefined || network === undefined) return usage(io, 2, "deploy-plan requires --network testnet"); + assertTestnet(network); + const run = await readJson(runPath); + const artifact = await loadArtifactDirectory(run.artifactDir); + const code = firstCell(await readFile(resolve(run.codeBoc))); + const stateInit = buildV2StateInitFromArtifact(code, artifact, run.workchain ?? 0); + io.stdout(json({ + command: "deploy-plan", + network, + address: stateInit.address.toString(), + codeHash: stateInit.codeHash, + dataHash: stateInit.dataHash, + stateInitHash: stateInit.stateInitHash, + initialStateHash: stateInit.initialStateHash, + requiredBalanceNano: run.requiredBalanceNano ?? "500000000", + actions: ["fund deployment wallet", "submit StateInit", "wait for inclusion", "wait for masterchain-referenced finality", "verify account commitments"], + broadcast: false, + })); + return 0; +} + +async function verifyRunCommand(args: readonly string[], io: CliIo): Promise { + const manifestPath = args[0]; + const network = option(args, "--network"); + if (manifestPath === undefined || network === undefined) return usage(io, 2, "verify-run requires --network testnet"); + assertTestnet(network); + const manifest = await readJson(manifestPath); + const sources = manifest.sources.map((source) => ({ id: source.id, fetchSnapshot: async () => readJson(source.snapshot) })); + const replay = manifest.replay === undefined ? undefined : deserializeReplay(manifest.replay); + const report = await verifyRunFromSources(manifest.address, manifest.expectations, sources, replay); + io.stdout(json(report)); + return report.overall === "verified" ? 0 : 1; +} + +async function traceCommand(args: readonly string[], io: CliIo): Promise { + const address = args[0]; + const format = option(args, "--format"); + const input = option(args, "--input"); + if (address === undefined || format === undefined || !["text", "json", "ndjson"].includes(format)) return usage(io, 2, "trace requires
--format text|json|ndjson --input "); + if (input === undefined) return incompleteNetworkCommand("trace", io, `no raw trace supplied for ${address}`); + const trace = await readJson(input); + if (format === "text") io.stdout(formatTraceText(trace)); + else if (format === "json") io.stdout(json(trace)); + else io.stdout(formatNdjson(trace)); + return 0; +} + +async function keeperCommand(args: readonly string[], io: CliIo): Promise { + if (args[0] !== "run" || args[1] === undefined) return usage(io, 2, "keeper run requires "); + const config = await readJson(args[1]); + assertTestnet(config.core.network); + const action = planNextAction(config.core, config.observation, BigInt(config.queryId ?? "0")); + io.stdout(json({ command: "keeper run", mode: "read-only", action, unresolved: ["wallet and provider adapters are external to this CLI invocation"] })); + return 0; +} + +async function benchmarkCommand(args: readonly string[], io: CliIo): Promise { + const mode = args[0]; + const artifactPath = args[1]; + if (mode !== "local" && mode !== "testnet") return usage(io, 2, "benchmark requires local|testnet "); + if (artifactPath === undefined) return usage(io, 2, "benchmark requires an artifact or deployment record"); + if (mode === "testnet") return incompleteNetworkCommand("benchmark testnet", io, "testnet deployment record and raw providers are required"); + const artifact = await loadArtifactDirectory(artifactPath); + const state = artifactToInitialState(artifact); + const result = advance(state, { rom: artifact.assembly.words }, { + expectedAdvanceCount: 0n, + expectedStateHash: stateHash(state), + maxInstructions: 1, + maxOutputs: 1, + }); + io.stdout(json({ command: "benchmark local", artifact: artifact.manifest.name, executed: result.executed, stopReason: result.stopReason, stateHash: stateHash(result.state) })); + return 0; +} + +function artifactToInitialState(artifact: ArtifactBundle) { + const { createInitialState } = requireEmulatorModel(); + const state = createInitialState({ + runId: artifact.manifest.runId, + coreId: artifact.manifest.coreId, + programId: artifact.manifest.programId, + romRoot: artifact.manifest.romRoot, + initialRamRoot: artifact.manifest.initialRamRoot, + routeRoot: artifact.manifest.routeRoot, + staticCommitment: artifact.manifest.staticCommitment, + routes: artifact.routes, + requiredInputs: [], + maxStepsPerAdvance: 1, + }); + state.ram = artifact.assembly.ram.slice(); + return state; +} + +function requireEmulatorModel(): typeof import("../packages/emulator/src/model.js") { + // Kept as a function so the browser-safe SDK never inherits this CLI's Node path. + return requireModel; +} + +import * as requireModel from "../packages/emulator/src/model.js"; + +function deserializeReplay(value: RunManifest["replay"]): ReplayEvidence | undefined { + if (value === undefined) return undefined; + const initial = value.initialState; + return { + rom: value.rom, + initialState: { + ...initial, + advanceCount: BigInt(initial.advanceCount), + acceptedMessageCount: BigInt(initial.acceptedMessageCount), + instructionCount: BigInt(initial.instructionCount), + outputCount: BigInt(initial.outputCount), + inputCount: BigInt(initial.inputCount), + epoch: BigInt(initial.epoch), + ram: Uint8Array.from(initial.ram), + registers: [...initial.registers], + nextOutputSequence: [...initial.nextOutputSequence], + inbox: initial.inbox.map((record) => ({ ...record, sourceEpoch: BigInt(record.sourceEpoch), destinationEpoch: BigInt(record.destinationEpoch), sourceInstructionCount: BigInt(record.sourceInstructionCount) })), + outbox: initial.outbox.map((record) => ({ ...record, sourceEpoch: BigInt(record.sourceEpoch), destinationEpoch: BigInt(record.destinationEpoch), sourceInstructionCount: BigInt(record.sourceInstructionCount) })), + }, + }; +} + +interface DeployRunConfig { readonly artifactDir: string; readonly codeBoc: string; readonly workchain?: number; readonly requiredBalanceNano?: string; } +interface RunManifest { readonly address: string; readonly expectations: V2VerificationExpectations; readonly sources: readonly { readonly id: string; readonly snapshot: string }[]; readonly replay?: { readonly rom: readonly number[]; readonly initialState: ReplayStateJson }; } +interface KeeperConfigFile { readonly core: KeeperCoreConfig; readonly observation: KeeperCoreObservation; readonly queryId?: string; } + +interface ReplayRecordJson extends Omit { + readonly sourceEpoch: string; + readonly destinationEpoch: string; + readonly sourceInstructionCount: string; +} + +interface ReplayStateJson extends Omit { + readonly advanceCount: string; + readonly acceptedMessageCount: string; + readonly instructionCount: string; + readonly outputCount: string; + readonly inputCount: string; + readonly epoch: string; + readonly ram: readonly number[]; + readonly inbox: readonly ReplayRecordJson[]; + readonly outbox: readonly ReplayRecordJson[]; +} + +function firstCell(value: Buffer): Cell { + const cell = Cell.fromBoc(value)[0]; + if (cell === undefined) throw new Error("BOC did not contain a root cell"); + return cell; +} + +async function writeArtifactDirectory(directory: string, artifact: ArtifactBundle): Promise { + await mkdir(directory, { recursive: true }); + for (const [name, content] of Object.entries(artifact.files)) await writeFile(join(directory, name), content, "utf8"); +} + +async function readJson(path: string): Promise { + return JSON.parse(await readFile(resolve(path), "utf8")) as T; +} + +function option(args: readonly string[], name: string): string | undefined { + const index = args.indexOf(name); + return index >= 0 ? args[index + 1] : undefined; +} + +function assertTestnet(network: string): void { + if (network !== "testnet") throw new Error("Tonolith v2 CLI is restricted to TON testnet during this release"); +} + +function incompleteNetworkCommand(command: string, io: CliIo, reason = "a configured raw-chain provider is required"): number { + io.stdout(json({ command, overall: "incomplete", network: "testnet", unresolved: [reason], broadcast: false })); + return 1; +} + +function formatTraceText(value: unknown): string { + if (!Array.isArray(value)) return JSON.stringify(value, null, 2); + return value.map((entry, index) => `${String(index).padStart(4, "0")} ${JSON.stringify(entry)}`).join("\n"); +} + +function formatNdjson(value: unknown): string { + return Array.isArray(value) ? value.map((entry) => JSON.stringify(entry)).join("\n") : JSON.stringify(value); +} + +function json(value: unknown): string { + return JSON.stringify(value, (_key, entry: unknown) => typeof entry === "bigint" ? entry.toString() : entry, 2); +} + +function usage(io: CliIo, code: number, message?: string): number { + if (message !== undefined) io.stderr(message); + io.stderr("usage: tonolith ..."); + return code; +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) process.exitCode = await runCli(process.argv.slice(2)); From 95be123901ed45b7f4327945374b489dfb17539a Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:19:39 +0100 Subject: [PATCH 33/67] feat: add v2 benchmark program suite --- benchmarks/v2/README.md | 25 +++++ benchmarks/v2/batch-selection.json | 23 ++++ benchmarks/v2/expected.json | 66 ++++++++++++ packages/benchmarks/src/index.ts | 1 + packages/benchmarks/src/runner.ts | 159 ++++++++++++++++++++++++++++ programs/v2/add8-high.tasm | 7 ++ programs/v2/add8-low.tasm | 16 +++ programs/v2/cellular-automaton.tasm | 55 ++++++++++ programs/v2/counter-sink.tasm | 7 ++ programs/v2/counter-source.tasm | 7 ++ programs/v2/crc4.tasm | 13 +++ programs/v2/fibonacci.tasm | 24 +++++ tests/v2/benchmarks.test.ts | 50 +++++++++ 13 files changed, 453 insertions(+) create mode 100644 benchmarks/v2/README.md create mode 100644 benchmarks/v2/batch-selection.json create mode 100644 benchmarks/v2/expected.json create mode 100644 packages/benchmarks/src/index.ts create mode 100644 packages/benchmarks/src/runner.ts create mode 100644 programs/v2/add8-high.tasm create mode 100644 programs/v2/add8-low.tasm create mode 100644 programs/v2/cellular-automaton.tasm create mode 100644 programs/v2/counter-sink.tasm create mode 100644 programs/v2/counter-source.tasm create mode 100644 programs/v2/crc4.tasm create mode 100644 programs/v2/fibonacci.tasm create mode 100644 tests/v2/benchmarks.test.ts diff --git a/benchmarks/v2/README.md b/benchmarks/v2/README.md new file mode 100644 index 0000000..97f6c2b --- /dev/null +++ b/benchmarks/v2/README.md @@ -0,0 +1,25 @@ +# Tonolith v2 benchmark suite + +The v2 suite is executable, deterministic, and deliberately split into two +evidence classes: + +- `local-emulator`: the independent TypeScript execution and message-routing + results checked by `tests/v2/benchmarks.test.ts`; +- `acton-sandbox`, `local-validator`, and `testnet`: fields reserved for + measured contract, validator, and public-network evidence. + +The checked-in expected vectors are in [`expected.json`](expected.json). They +include the source programs, output vectors, instruction counts, final state +hashes, and multi-core acknowledgement counts. Gas and real network fees stay +`null` until the corresponding environment measures them; they are not inferred +from emulator runtime. + +Run the suite with: + +```text +npm test -- --test-name-pattern='v2 benchmark suite|bounded checksum|two-core|two 4-bit' +``` + +The benchmark runner emits per-instruction frames compatible with the v2 trace +schema. A frame is an architectural transition; it is not a claim that a TON +transaction was included or finalized. diff --git a/benchmarks/v2/batch-selection.json b/benchmarks/v2/batch-selection.json new file mode 100644 index 0000000..5c7d9ce --- /dev/null +++ b/benchmarks/v2/batch-selection.json @@ -0,0 +1,23 @@ +{ + "schema": "tonolith-batch-selection-v2", + "network": "local-acton", + "measurementCommand": "acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42", + "hardCeilingGas": 400000, + "observedNetworkGasReference": 44381, + "safetyRule": "selected worst-case must remain below both 400000 gas and 40% of the observed network reference after protocol-path measurement", + "selectedMaxStepsPerAdvance": 1, + "selectionStatus": "conservative-single-step", + "candidates": [ + { "maxSteps": 1, "contractAccepted": true, "worstCaseGas": 44381, "networkGasMeasured": false, "headroomToHardCeiling": 355619, "decision": "selected" }, + { "maxSteps": 2, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, + { "maxSteps": 4, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, + { "maxSteps": 8, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, + { "maxSteps": 16, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, + { "maxSteps": 32, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, + { "maxSteps": 64, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" } + ], + "unresolved": [ + "Acton currently accepts only one v2 instruction per Advance; candidates 2 through 64 require a separately proven batched loop.", + "The 40% rule needs a confirmed testnet gas baseline, not a local emulator estimate." + ] +} diff --git a/benchmarks/v2/expected.json b/benchmarks/v2/expected.json new file mode 100644 index 0000000..5ac41b8 --- /dev/null +++ b/benchmarks/v2/expected.json @@ -0,0 +1,66 @@ +{ + "schema": "tonolith-benchmark-v2", + "evidence": "local-emulator", + "compiler": "tonolith-assembler@2.0.0", + "programs": [ + { + "name": "fibonacci", + "source": "programs/v2/fibonacci.tasm", + "outputs": [1, 1, 2, 3, 5, 8, 13], + "instructions": 97, + "batches": 97, + "finalStateHash": "73188259a45c2b3978ebae240c2c8254a049f8497ed53025c74b6535af9a8c54", + "gas": null, + "feesNano": null, + "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" + }, + { + "name": "crc4", + "source": "programs/v2/crc4.tasm", + "outputs": [5], + "instructions": 12, + "batches": 12, + "finalStateHash": "09d7dbb2d629f3afea9a1b28c46aa9db3eb5fe55b337dbbdbc6a81d211a26d10", + "gas": null, + "feesNano": null, + "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" + }, + { + "name": "cellular-automaton", + "source": "programs/v2/cellular-automaton.tasm", + "outputs": [0], + "instructions": 50, + "batches": 50, + "finalStateHash": "4525925f5f34b7a1eec086f6145a128d0884c265309b558144f24d79017e05d4", + "gas": null, + "feesNano": null, + "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" + }, + { + "name": "counter-pipeline", + "source": ["programs/v2/counter-source.tasm", "programs/v2/counter-sink.tasm"], + "outputs": [1, 2], + "sourceInstructions": 6, + "destinationInstructions": 6, + "acknowledgements": 2, + "sourceStateHash": "14f50ae5021d2dd7e3457e4123fdefeb52e17861d8f89b8745de8462e890a69b", + "destinationStateHash": "7395115f6a60279fd4fdaa60e46fbc2fb03dbbd63a408f3b834e2e61c9d5e464", + "gas": null, + "feesNano": null, + "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" + }, + { + "name": "add8-carry", + "source": ["programs/v2/add8-low.tasm", "programs/v2/add8-high.tasm"], + "outputs": [1], + "sourceInstructions": 10, + "destinationInstructions": 6, + "acknowledgements": 2, + "sourceStateHash": "b44f4a24acf1cf2201b95693ccfbd76a9e5a01a13f61f685c7a12ae03944a2bd", + "destinationStateHash": "5a8a315fb231acd00208d871d3a631df09847c2f82a2dc5b35671854b2e8fce4", + "gas": null, + "feesNano": null, + "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" + } + ] +} diff --git a/packages/benchmarks/src/index.ts b/packages/benchmarks/src/index.ts new file mode 100644 index 0000000..dc8a6ae --- /dev/null +++ b/packages/benchmarks/src/index.ts @@ -0,0 +1 @@ +export * from "./runner.js"; diff --git a/packages/benchmarks/src/runner.ts b/packages/benchmarks/src/runner.ts new file mode 100644 index 0000000..41573e4 --- /dev/null +++ b/packages/benchmarks/src/runner.ts @@ -0,0 +1,159 @@ +import { buildArtifact, type ArtifactBundle, type BuildArtifactOptions } from "../../artifact/src/manifest.js"; +import { acknowledgeOutput, deliverInput, dispatchOutput, step, type V2StepResult } from "../../emulator/src/executor.js"; +import { createInitialState, type V2State } from "../../emulator/src/model.js"; +import { stateHash } from "../../emulator/src/commitments.js"; +import { STATUS } from "../../isa/src/constants.js"; +import type { TraceFrame, TraceStateSnapshot } from "../../trace/src/schema.js"; + +export interface BenchmarkResult { + readonly name: string; + readonly evidence: "local-emulator"; + readonly outputs: readonly number[]; + readonly executedInstructions: number; + readonly finalStateHash: string; + readonly finalStatus: number; + readonly frames: readonly TraceFrame[]; +} + +export interface NetworkBenchmarkResult { + readonly name: string; + readonly evidence: "local-emulator"; + readonly deliveredValues: readonly number[]; + readonly sourceStateHash: string; + readonly destinationStateHash: string; + readonly sourceInstructions: number; + readonly destinationInstructions: number; + readonly acknowledgements: number; +} + +export function makeBenchmarkArtifact(source: string, name: string, options: Omit = {}): ArtifactBundle { + return buildArtifact(source, { + ...options, + name, + compiler: options.compiler ?? { name: "tonolith-assembler", version: "2.0.0", gitCommit: "e".repeat(40), node: process.version }, + }); +} + +export function runArtifact(artifact: ArtifactBundle, maxInstructions = 10_000): BenchmarkResult { + let state = initialState(artifact); + let executedInstructions = 0; + const outputs: number[] = []; + const frames: TraceFrame[] = []; + for (let index = 0; index < maxInstructions && state.status !== STATUS.halted && state.status !== STATUS.faulted; index += 1) { + const before = state; + const result = step(before, { rom: artifact.assembly.words }); + state = result.state; + if (!result.executed) break; + executedInstructions += 1; + if (result.legacyOutput !== undefined) outputs.push(result.legacyOutput.value); + if (result.output !== undefined) outputs.push(result.output.value); + frames.push(frame(frames.length, artifact.manifest.coreId, before, state, result)); + } + return { + name: artifact.manifest.name, + evidence: "local-emulator", + outputs, + executedInstructions, + finalStateHash: stateHash(state), + finalStatus: state.status, + frames, + }; +} + +export function runTwoCorePipeline(sourceArtifact: ArtifactBundle, destinationArtifact: ArtifactBundle, maxTicks = 2_000): NetworkBenchmarkResult { + let source = initialState(sourceArtifact); + let destination = initialState(destinationArtifact); + // The route delay is one logical epoch; start the receiving fixture in the + // first eligible epoch, matching the on-chain deployment/run configuration. + destination.epoch = 1n; + const deliveredValues: number[] = []; + let sourceInstructions = 0; + let destinationInstructions = 0; + let acknowledgements = 0; + for (let tick = 0; tick < maxTicks; tick += 1) { + if (source.status === STATUS.running) { + const result = step(source, { rom: sourceArtifact.assembly.words }); + source = result.state; + if (result.executed) { + sourceInstructions += 1; + const output = result.output; + if (output !== undefined) { + const dispatched = dispatchOutput(source, { expectedStateHash: stateHash(source), outputId: output.outputId }); + const delivered = deliverInput(destination, dispatched.message, source.config.coreId); + destination = delivered.state; + source = acknowledgeOutput(source, delivered.acknowledgement, destination.config.coreId).state; + acknowledgements += 1; + } + } + } + if (destination.status === STATUS.running) { + const result = step(destination, { rom: destinationArtifact.assembly.words }); + destination = result.state; + if (result.executed) { + destinationInstructions += 1; + if (result.legacyOutput !== undefined) deliveredValues.push(result.legacyOutput.value); + if (result.output !== undefined) deliveredValues.push(result.output.value); + } + } + if ((source.status === STATUS.halted || source.status === STATUS.faulted) && (destination.status === STATUS.halted || destination.status === STATUS.faulted)) break; + } + return { + name: `${sourceArtifact.manifest.name}->${destinationArtifact.manifest.name}`, + evidence: "local-emulator", + deliveredValues, + sourceStateHash: stateHash(source), + destinationStateHash: stateHash(destination), + sourceInstructions, + destinationInstructions, + acknowledgements, + }; +} + +function initialState(artifact: ArtifactBundle): V2State { + const state = createInitialState({ + runId: artifact.manifest.runId, + coreId: artifact.manifest.coreId, + programId: artifact.manifest.programId, + romRoot: artifact.manifest.romRoot, + initialRamRoot: artifact.manifest.initialRamRoot, + routeRoot: artifact.manifest.routeRoot, + staticCommitment: artifact.manifest.staticCommitment, + routes: artifact.routes, + requiredInputs: [], + maxStepsPerAdvance: artifact.limits.maxStepsPerAdvance, + maxInputRecords: artifact.limits.maxInputRecords, + maxOutputRecords: artifact.limits.maxOutputRecords, + }); + state.ram = artifact.assembly.ram.slice(); + return state; +} + +function frame(index: number, coreId: number, before: V2State, after: V2State, result: V2StepResult): TraceFrame { + return { + index, + coreId, + instructionWord: result.word, + opcode: result.instruction.opcode, + before: snapshot(before), + after: snapshot(after), + phase: result.output === undefined ? "none" : "committed", + ...(result.output === undefined ? {} : { outputId: result.output.outputId }), + }; +} + +function snapshot(state: V2State): TraceStateSnapshot { + return { + stateHash: stateHash(state), + pc: state.pc, + accumulator: state.accumulator, + flags: state.flags, + epoch: state.epoch.toString(), + instructionCount: state.instructionCount.toString(), + advanceCount: state.advanceCount.toString(), + status: state.status, + outputCommitment: state.outputCommitment, + inputCommitment: state.inputCommitment, + inboxCount: state.inbox.length, + outboxCount: state.outbox.length, + }; +} diff --git a/programs/v2/add8-high.tasm b/programs/v2/add8-high.tasm new file mode 100644 index 0000000..7d3276b --- /dev/null +++ b/programs/v2/add8-high.tasm @@ -0,0 +1,7 @@ +; High nibble receives the low result and carry as two authenticated inputs. +IN 0 +STR R0 +IN 1 +OUT +YIELD +HALT diff --git a/programs/v2/add8-low.tasm b/programs/v2/add8-low.tasm new file mode 100644 index 0000000..d6e3133 --- /dev/null +++ b/programs/v2/add8-low.tasm @@ -0,0 +1,16 @@ +; Low nibble: 0xF + 0x1 = 0 with carry on port 1. +LDI 15 +STR R0 +LDI 1 +ADD R0 +OUTP 0 +JC SEND_CARRY +LDI 0 +OUTP 1 +YIELD +HALT +SEND_CARRY: +LDI 1 +OUTP 1 +YIELD +HALT diff --git a/programs/v2/cellular-automaton.tasm b/programs/v2/cellular-automaton.tasm new file mode 100644 index 0000000..5469b53 --- /dev/null +++ b/programs/v2/cellular-automaton.tasm @@ -0,0 +1,55 @@ +; A bounded four-cell Rule-90-style update, repeated twice. +.nibble 0 1 +.nibble 1 0 +.nibble 2 1 +.nibble 3 0 +LDM 0 +STR R0 +LDM 1 +XOR R0 +STM 4 +LDM 1 +STR R0 +LDM 2 +XOR R0 +STM 5 +LDM 2 +STR R0 +LDM 3 +XOR R0 +STM 6 +LDM 4 +STR R0 +LDM 5 +XOR R0 +STM 7 +LDM 4 +STR R0 +LDM 5 +XOR R0 +STM 0 +LDM 5 +STR R0 +LDM 6 +XOR R0 +STM 1 +LDM 6 +STR R0 +LDM 7 +XOR R0 +STM 2 +LDM 7 +STR R0 +LDM 0 +XOR R0 +STM 3 +LDM 0 +XOR R0 +LDM 1 +XOR R0 +LDM 2 +XOR R0 +LDM 3 +XOR R0 +OUT +HALT diff --git a/programs/v2/counter-sink.tasm b/programs/v2/counter-sink.tasm new file mode 100644 index 0000000..c363337 --- /dev/null +++ b/programs/v2/counter-sink.tasm @@ -0,0 +1,7 @@ +; Destination half of the two-core counter pipeline. +IN 0 +OUT +IN 0 +OUT +YIELD +HALT diff --git a/programs/v2/counter-source.tasm b/programs/v2/counter-source.tasm new file mode 100644 index 0000000..54c477f --- /dev/null +++ b/programs/v2/counter-source.tasm @@ -0,0 +1,7 @@ +; Source half of the two-core counter pipeline. +LDI 1 +OUTP 0 +LDI 2 +OUTP 0 +YIELD +HALT diff --git a/programs/v2/crc4.tasm b/programs/v2/crc4.tasm new file mode 100644 index 0000000..c0602b5 --- /dev/null +++ b/programs/v2/crc4.tasm @@ -0,0 +1,13 @@ +; Bounded checksum over the fixed nibble vector A, 3, 5, 9. +LDI 0xA +STR R0 +LDI 3 +XOR R0 +STR R0 +LDI 5 +XOR R0 +STR R0 +LDI 9 +XOR R0 +OUT +HALT diff --git a/programs/v2/fibonacci.tasm b/programs/v2/fibonacci.tasm new file mode 100644 index 0000000..4870d27 --- /dev/null +++ b/programs/v2/fibonacci.tasm @@ -0,0 +1,24 @@ +; v2 regression: the canonical seven-value Fibonacci trace. +LDI 1 +STR R0 +LDI 1 +STR R1 +LDI 7 +STR R2 +LOOP: +LDR R0 +OUT +LDR R2 +DEC +STR R2 +JZ DONE +LDR R0 +ADD R1 +STR R3 +LDR R1 +STR R0 +LDR R3 +STR R1 +JMP LOOP +DONE: +HALT diff --git a/tests/v2/benchmarks.test.ts b/tests/v2/benchmarks.test.ts new file mode 100644 index 0000000..adbc9eb --- /dev/null +++ b/tests/v2/benchmarks.test.ts @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import { makeBenchmarkArtifact, runArtifact, runTwoCorePipeline } from "../../packages/benchmarks/src/index.js"; + +const compiler = { name: "tonolith-assembler", version: "2.0.0-test", gitCommit: "f".repeat(40), node: "v26.7.0" }; +const sameRunId = "aa".repeat(32); + +async function source(name: string): Promise { + return readFile(new URL(`../../programs/v2/${name}`, import.meta.url), "utf8"); +} + +test("v2 benchmark suite produces the canonical Fibonacci vector", async () => { + const artifact = makeBenchmarkArtifact(await source("fibonacci.tasm"), "fibonacci-v2", { compiler }); + const result = runArtifact(artifact); + assert.deepEqual(result.outputs, [1, 1, 2, 3, 5, 8, 13]); + assert.equal(result.finalStatus, 3); + assert.equal(result.frames.length, result.executedInstructions); +}); + +test("v2 bounded checksum and cellular automaton terminate with deterministic outputs", async () => { + const checksum = runArtifact(makeBenchmarkArtifact(await source("crc4.tasm"), "crc4", { compiler })); + const automaton = runArtifact(makeBenchmarkArtifact(await source("cellular-automaton.tasm"), "cellular", { compiler })); + assert.deepEqual(checksum.outputs, [5]); + assert.equal(automaton.outputs.length, 1); + assert.ok(automaton.executedInstructions > checksum.executedInstructions); +}); + +test("two-core counter pipeline authenticates delivery, deduplicates through ACK, and preserves outputs", async () => { + const routes = [{ sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 16 }]; + const peers = [{ coreId: 0, address: `0:${"10".repeat(32)}` }, { coreId: 1, address: `0:${"11".repeat(32)}` }]; + const sourceArtifact = makeBenchmarkArtifact(await source("counter-source.tasm"), "counter-source", { compiler, coreId: 0, runId: sameRunId, routes, peers }); + const destinationArtifact = makeBenchmarkArtifact(await source("counter-sink.tasm"), "counter-sink", { compiler, coreId: 1, runId: sameRunId, routes, peers }); + const result = runTwoCorePipeline(sourceArtifact, destinationArtifact); + assert.deepEqual(result.deliveredValues, [1, 2]); + assert.equal(result.acknowledgements, 2); +}); + +test("two 4-bit cores implement the carry half of an 8-bit addition", async () => { + const routes = [ + { sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 16 }, + { sourceCoreId: 0, sourcePort: 1, destinationCoreId: 1, destinationPort: 1, delay: 1, maxRecordsPerEpoch: 16 }, + ]; + const peers = [{ coreId: 0, address: `0:${"20".repeat(32)}` }, { coreId: 1, address: `0:${"21".repeat(32)}` }]; + const low = makeBenchmarkArtifact(await source("add8-low.tasm"), "add8-low", { compiler, coreId: 0, runId: sameRunId, routes, peers }); + const high = makeBenchmarkArtifact(await source("add8-high.tasm"), "add8-high", { compiler, coreId: 1, runId: sameRunId, routes, peers }); + const result = runTwoCorePipeline(low, high); + assert.deepEqual(result.deliveredValues, [1]); + assert.equal(result.acknowledgements, 2); +}); From ee995d673dd3fc8e2e75b9f11da21b466df4923e Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:19:56 +0100 Subject: [PATCH 34/67] feat: define verified v2 trace schema --- packages/trace/src/index.ts | 1 + packages/trace/src/schema.ts | 85 ++++++++++++++++++++++++++++++++++++ tests/v2/trace.test.ts | 25 +++++++++++ 3 files changed, 111 insertions(+) create mode 100644 packages/trace/src/index.ts create mode 100644 packages/trace/src/schema.ts create mode 100644 tests/v2/trace.test.ts diff --git a/packages/trace/src/index.ts b/packages/trace/src/index.ts new file mode 100644 index 0000000..20cdc26 --- /dev/null +++ b/packages/trace/src/index.ts @@ -0,0 +1 @@ +export * from "./schema.js"; diff --git a/packages/trace/src/schema.ts b/packages/trace/src/schema.ts new file mode 100644 index 0000000..52b7764 --- /dev/null +++ b/packages/trace/src/schema.ts @@ -0,0 +1,85 @@ +import type { Hash256 } from "../../abi/src/messages.js"; + +export const V2_TRACE_SCHEMA = "tonolith-trace-v2" as const; +export type TraceEvidence = "local-emulator" | "acton-sandbox" | "local-validator" | "testnet"; +export type MessagePhase = "none" | "committed" | "dispatched" | "destination-included" | "acknowledged" | "finalized"; + +export interface TraceStateSnapshot { + readonly stateHash: Hash256; + readonly pc: number; + readonly accumulator: number; + readonly flags: number; + readonly epoch: string; + readonly instructionCount: string; + readonly advanceCount: string; + readonly status: number; + readonly outputCommitment: Hash256; + readonly inputCommitment: Hash256; + readonly inboxCount: number; + readonly outboxCount: number; +} + +export interface TraceFrame { + readonly index: number; + readonly coreId: number; + readonly instructionWord?: number; + readonly opcode?: number; + readonly sourceLine?: number; + readonly before: TraceStateSnapshot; + readonly after: TraceStateSnapshot; + readonly phase: MessagePhase; + readonly outputId?: Hash256; + readonly transactionHash?: string; + readonly transactionFinalized?: boolean; + readonly note?: string; +} + +export interface TonolithTraceV2 { + readonly schema: typeof V2_TRACE_SCHEMA; + readonly evidence: TraceEvidence; + readonly network: "local" | "testnet"; + readonly runId: Hash256; + readonly coreIds: readonly number[]; + readonly programId: Hash256; + readonly romRoot: Hash256; + readonly routeRoot: Hash256; + readonly staticCommitment: Hash256; + readonly frames: readonly TraceFrame[]; + readonly unresolved: readonly string[]; +} + +export function encodeTrace(trace: TonolithTraceV2): string { + validateTrace(trace); + return JSON.stringify(trace, canonicalReplacer); +} + +export function decodeTrace(serialized: string): TonolithTraceV2 { + const value: unknown = JSON.parse(serialized); + validateTrace(value); + return value; +} + +export function validateTrace(value: unknown): asserts value is TonolithTraceV2 { + if (value === null || typeof value !== "object") throw new TypeError("trace must be an object"); + const trace = value as Partial; + if (trace.schema !== V2_TRACE_SCHEMA) throw new TypeError("unsupported trace schema"); + if (trace.evidence === undefined || !["local-emulator", "acton-sandbox", "local-validator", "testnet"].includes(trace.evidence)) throw new TypeError("invalid trace evidence"); + if (trace.network !== "local" && trace.network !== "testnet") throw new TypeError("invalid trace network"); + if (typeof trace.runId !== "string" || !/^[0-9a-f]{64}$/.test(trace.runId)) throw new TypeError("invalid trace run ID"); + if (!Array.isArray(trace.frames) || !Array.isArray(trace.coreIds)) throw new TypeError("trace frames and coreIds must be arrays"); + let previous = -1; + for (const frame of trace.frames) { + if (!Number.isInteger(frame.index) || frame.index <= previous) throw new TypeError("trace frame indexes must be strictly increasing"); + previous = frame.index; + validateState(frame.before); + validateState(frame.after); + } +} + +function validateState(value: unknown): asserts value is TraceStateSnapshot { + if (value === null || typeof value !== "object" || typeof (value as TraceStateSnapshot).stateHash !== "string") throw new TypeError("invalid trace state snapshot"); +} + +function canonicalReplacer(_key: string, value: unknown): unknown { + return typeof value === "bigint" ? value.toString() : value; +} diff --git a/tests/v2/trace.test.ts b/tests/v2/trace.test.ts new file mode 100644 index 0000000..af72994 --- /dev/null +++ b/tests/v2/trace.test.ts @@ -0,0 +1,25 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { decodeTrace, encodeTrace, type TonolithTraceV2 } from "../../packages/trace/src/index.js"; + +const state = { stateHash: "01".repeat(32), pc: 0, accumulator: 0, flags: 0, epoch: "0", instructionCount: "0", advanceCount: "0", status: 0, outputCommitment: "00".repeat(32), inputCommitment: "00".repeat(32), inboxCount: 0, outboxCount: 0 }; +const trace: TonolithTraceV2 = { + schema: "tonolith-trace-v2", + evidence: "local-emulator", + network: "local", + runId: "02".repeat(32), + coreIds: [0], + programId: "03".repeat(32), + romRoot: "04".repeat(32), + routeRoot: "05".repeat(32), + staticCommitment: "06".repeat(32), + frames: [{ index: 0, coreId: 0, before: state, after: { ...state, stateHash: "07".repeat(32), instructionCount: "1" }, phase: "none" }], + unresolved: [], +}; + +test("v2 trace schema is strict, deterministic, and round-trippable", () => { + const encoded = encodeTrace(trace); + assert.deepEqual(decodeTrace(encoded), trace); + assert.throws(() => decodeTrace(encoded.replace("tonolith-trace-v2", "trace-v1")), /unsupported trace schema/); + assert.throws(() => decodeTrace(JSON.stringify({ ...trace, frames: [{ ...trace.frames[0], index: 2 }, { ...trace.frames[0], index: 1 }] })), /strictly increasing/); +}); From 5f61384fb9ace21b91397ce139fc376f33c148f6 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:20:07 +0100 Subject: [PATCH 35/67] docs: add v2 threat model and trust boundaries --- docs/v2/THREAT_MODEL.md | 83 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/v2/THREAT_MODEL.md diff --git a/docs/v2/THREAT_MODEL.md b/docs/v2/THREAT_MODEL.md new file mode 100644 index 0000000..74a4343 --- /dev/null +++ b/docs/v2/THREAT_MODEL.md @@ -0,0 +1,83 @@ +# Tonolith v2 threat model + +This document describes the security boundary of the v2 architectural +processor, its asynchronous core-to-core protocol, and the off-chain tooling. +Tonolith is a deterministic processor state machine; it is not transistor-level +or gate-level execution and it does not claim Intel 4004 compatibility. + +## Assets + +- The deployed code and immutable StateInit identity. +- Program, ROM, RAM, route, peer, limits, and static commitments. +- The accepted state-hash chain, logical epochs, counters, and queue contents. +- Reliable output delivery, acknowledgements, deduplication, and bounded funds. +- The independence and reproducibility of the emulator, verifier, artifacts, and + visualizer traces. +- Keeper wallet funds and credentials held outside the repository. + +## Trust boundaries + +1. The immutable v2 contract is the consensus object. It does not trust a + keeper, emulator, assembler, RPC index, or visualizer. +2. A keeper is an untrusted liveness agent. Any keeper can submit a bounded + request, but it cannot authorize state mutation or change static data. +3. A peer address is authenticated by the immutable peer table and is not a + keeper allowlist. +4. The verifier treats raw BOCs and transaction history as evidence. Indexed + decoded fields are advisory and cannot replace body or StateInit checks. +5. The browser visualizer consumes verifier output and labels reconstructed + fields separately from chain facts. +6. Wallet signing and RPC credentials are external operational dependencies. + +## Adversaries + +- A stale or racing keeper submitting an old state hash or counter. +- A malicious sender impersonating a core, replaying an output, or mutating a + delivery field while retaining an output ID. +- A provider returning incomplete, reordered, divergent, or fabricated history. +- A poisoned artifact, source map, route table, or deployment manifest. +- A malformed cell designed to trigger parser ambiguity, excessive depth, a + dictionary failure, an action-phase failure, or a queue overflow. +- A compromised keeper host attempting to leak signing material or overspend. +- A visualizer or API incorrectly presenting a dispatch as destination receipt + or finality. + +## Primary controls + +| Threat | Control | Evidence target | +| --- | --- | --- | +| stale/racing advance | expected counter + exact state hash; no privileged keeper | Acton stale and two-keeper tests | +| forged delivery | immutable run/route/peer checks and authenticated sender | first-delivery, wrong-peer, and conflict tests | +| replay | output ID binding, destination slot uniqueness, exact duplicate ACK | duplicate and conflicting-replay tests | +| lost output | pending outbox remains until authenticated ACK; bounce does not erase it | retry/bounce tests | +| queue exhaustion | fixed queue bounds and explicit backpressure | full-queue tests | +| epoch confusion | route delay, epoch window, sequence and quota checks | stale-epoch tests | +| artifact substitution | canonical files and recomputed commitments | artifact tamper tests | +| StateInit substitution | code/data/StateInit/address recomputation | SDK and raw verifier tests | +| provider disagreement | two-source fingerprint and continuity comparison | verifier disagreement test | +| secret leakage | external wallet interface, read-only keeper mode, staged secret scan | release preflight | +| false visual claims | explicit message phases and evidence class in trace schema | trace validation and UI integration | + +## Invariants + +- No external message changes state. +- No keeper identity occurs in static commitments or authorization rules. +- A stale request cannot mutate architectural or protocol state. +- An output remains retryable until its matching authenticated acknowledgement. +- An exact duplicate delivery is idempotent; a conflicting duplicate fails. +- A route, peer, epoch, sequence, and output ID are jointly consistent. +- A bounded request cannot create an unbounded queue, action list, cell graph, or + message body. +- A verifier never returns `verified` when history, code, StateInit, BOCs, or + commitment comparisons are incomplete. +- Local emulator and Acton evidence are not described as testnet or mainnet + evidence. + +## Residual risks and release gates + +The design still requires local-validator evidence for action rollback, bounce +semantics, and final balance reconciliation; public testnet evidence for real +inclusion, finality, fees, and independent keepers; and external review before +any production or mainnet claim. A verifier/provider outage is an incomplete +result, not permission to skip history. A keeper wallet compromise can still +burn its own funds and must be contained by external spend limits. From 4be4f28027ba8fd51b15a5df7d3e53be9f49a769 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:20:28 +0100 Subject: [PATCH 36/67] test: record v2 gas and batch selection baseline --- benchmarks/gas-v2.json | 2817 ++++++++++++++++++++++++++++++ tests/v2/batch-selection.test.ts | 14 + 2 files changed, 2831 insertions(+) create mode 100644 benchmarks/gas-v2.json create mode 100644 tests/v2/batch-selection.test.ts diff --git a/benchmarks/gas-v2.json b/benchmarks/gas-v2.json new file mode 100644 index 0000000..86b6c6d --- /dev/null +++ b/benchmarks/gas-v2.json @@ -0,0 +1,2817 @@ +{ + "timestamp": 1786911290, + "opcodes": { + "V2Advance": { + "min_gas": 4578, + "max_gas": 44381, + "avg_gas": 24852, + "samples": 7, + "all_values": [ + 24434, + 4726, + 25921, + 4578, + 44381, + 28597, + 41329 + ] + }, + "TopUp": { + "min_gas": 1328, + "max_gas": 1373, + "avg_gas": 1351, + "samples": 2, + "all_values": [ + 1328, + 1373 + ] + }, + "Advance": { + "min_gas": 1588, + "max_gas": 19527, + "avg_gas": 15266, + "samples": 198, + "all_values": [ + 15477, + 15330, + 15477, + 16755, + 16923, + 16897, + 16915, + 16985, + 17091, + 17536, + 17456, + 18290, + 18512, + 18556, + 18636, + 18688, + 15477, + 15330, + 15767, + 16032, + 16393, + 14730, + 16831, + 15477, + 16923, + 16831, + 15477, + 19229, + 15477, + 18970, + 15477, + 19174, + 15477, + 19308, + 15477, + 19527, + 14730, + 3045, + 3251, + 1777, + 1588, + 1870, + 15477, + 1761, + 7970, + 7813, + 8256, + 8508, + 8846, + 9147, + 8560, + 8256, + 8846, + 12045, + 16831, + 2949, + 2265, + 2343, + 2421, + 3491, + 15477, + 15477, + 19527, + 15477, + 16367, + 17915, + 16341, + 17536, + 16419, + 16419, + 14730, + 16831, + 15477, + 18636, + 16831, + 15477, + 15330, + 17536, + 16755, + 15477, + 15330, + 17536, + 16923, + 16897, + 17456, + 16915, + 18512, + 18556, + 15477, + 18636, + 15477, + 15330, + 15477, + 15330, + 15477, + 15330, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16341, + 15767, + 16755, + 15330, + 15767, + 15330, + 15767, + 15330, + 16032, + 15767, + 19229, + 15767, + 18688, + 15330, + 16367, + 16831, + 15477, + 15330, + 15477, + 16755, + 15477, + 15330, + 15767, + 16897, + 16393, + 16831, + 16032 + ] + }, + "0x00000999": { + "min_gas": 1332, + "max_gas": 1332, + "avg_gas": 1332, + "samples": 1, + "all_values": [ + 1332 + ] + }, + "V2InputAccepted": { + "min_gas": 309, + "max_gas": 22983, + "avg_gas": 7699, + "samples": 4, + "all_values": [ + 7196, + 22983, + 309, + 309 + ] + }, + "V2DeliverInput": { + "min_gas": 21758, + "max_gas": 31321, + "avg_gas": 26243, + "samples": 3, + "all_values": [ + 31321, + 25651, + 21758 + ] + } + }, + "trace_chains": { + "test Fibonacci contract emits the canonical output trace::trace#74": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 74", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic and flag boundary outcomes::trace#10": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 17232, + "total_gas_fees": 1148802, + "total_forward_fees": 112536, + "total_fees": 1298424 + }, + "test Fibonacci contract emits the canonical output trace::trace#63": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 63", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test JC false path and maximum jump target::trace#2": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#98": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 98", + "tx_count": 2, + "total_gas_used": 16676, + "total_gas_fees": 1111735, + "total_forward_fees": 112536, + "total_fees": 1261357 + }, + "test JC false path and maximum jump target::trace#5": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#15": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 15", + "tx_count": 1, + "total_gas_used": 7813, + "total_gas_fees": 520868, + "total_forward_fees": 68090, + "total_fees": 520868 + }, + "test named failure paths and halted rejection::trace#31": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 31", + "tx_count": 1, + "total_gas_used": 12045, + "total_gas_fees": 803001, + "total_forward_fees": 68090, + "total_fees": 803001 + }, + "test external messages are rejected::trace#1": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93069, + "total_fees": 20601 + }, + "test ROM and RAM boundary pages remain addressable::trace#2": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test register load jump and ADC without carry::trace#11": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test named failure paths and halted rejection::trace#23": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 23", + "tx_count": 1, + "total_gas_used": 9147, + "total_gas_fees": 609801, + "total_forward_fees": 68090, + "total_fees": 609801 + }, + "test Fibonacci contract emits the canonical output trace::trace#48": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 48", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 advance executes a canonical instruction and rejects stale state::trace#2": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3625451, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#20": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 20", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test INC sets carry and zero on nibble wrap::trace#1": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94491, + "total_fees": 20601 + }, + "test OUT updates the commitment and emits a bounded event::trace#1": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#81": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 81", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 deploy exposes immutable metadata and a canonical initial state::trace#1": { + "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94135, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#18": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 18", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#42": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 42", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test JC false path and maximum jump target::trace#1": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test named failure paths and halted rejection::trace#28": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 28", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test RAM and branch opcodes::trace#10": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 16728, + "total_gas_fees": 1115202, + "total_forward_fees": 112536, + "total_fees": 1264824 + }, + "test Fibonacci contract emits the canonical output trace::trace#14": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 14", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test Fibonacci contract emits the canonical output trace::trace#91": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 91", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#97": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 97", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#2": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3675185, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#9": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test named failure paths and halted rejection::trace#2": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#10": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#75": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 75", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test INC sets carry and zero on nibble wrap::trace#3": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test stale and invalid advance requests do not mutate state::trace#1": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92002, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#1": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93424, + "total_fees": 20601 + }, + "test exact arithmetic carry and subtraction boundaries::trace#6": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test Fibonacci contract emits the canonical output trace::trace#96": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 96", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test Fibonacci contract emits the canonical output trace::trace#34": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 34", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic logical and SYS opcodes::trace#11": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 17400, + "total_gas_fees": 1160002, + "total_forward_fees": 112536, + "total_fees": 1309624 + }, + "test top up message preserves metadata::trace#1": { + "test_name": "test top up message preserves metadata", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 89869, + "total_fees": 20601 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#6": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 41638, + "total_gas_fees": 2775869, + "total_forward_fees": 124981, + "total_fees": 2974958 + }, + "test named failure paths and halted rejection::trace#22": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 22", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#29": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 29", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic and flag boundary outcomes::trace#16": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 16", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test OUT updates the commitment and emits a bounded event::trace#3": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test named failure paths and halted rejection::trace#27": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 27", + "tx_count": 1, + "total_gas_used": 8256, + "total_gas_fees": 550401, + "total_forward_fees": 68090, + "total_fees": 550401 + }, + "test Fibonacci contract emits the canonical output trace::trace#69": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 69", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#37": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 37", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#9": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 9", + "tx_count": 1, + "total_gas_used": 1870, + "total_gas_fees": 124668, + "total_forward_fees": 68090, + "total_fees": 124668 + }, + "test Fibonacci contract emits the canonical output trace::trace#5": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic and flag boundary outcomes::trace#9": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test Fibonacci contract emits the canonical output trace::trace#61": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 61", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#66": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 66", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test arithmetic and flag boundary outcomes::trace#1": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 96980, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#7": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test register load jump and ADC without carry::trace#7": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 16702, + "total_gas_fees": 1113469, + "total_forward_fees": 112536, + "total_fees": 1263091 + }, + "test Fibonacci contract emits the canonical output trace::trace#35": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 35", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#43": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 43", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#93": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 93", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#7": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 21758, + "total_gas_fees": 1450534, + "total_forward_fees": 103291, + "total_fees": 1450534 + }, + "test named failure paths and halted rejection::trace#42": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 42", + "tx_count": 1, + "total_gas_used": 3491, + "total_gas_fees": 232734, + "total_forward_fees": 68090, + "total_fees": 232734 + }, + "test stale and invalid advance requests do not mutate state::trace#3": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15039, + "total_gas_fees": 1002602, + "total_forward_fees": 112536, + "total_fees": 1152224 + }, + "test named failure paths and halted rejection::trace#25": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 25", + "tx_count": 1, + "total_gas_used": 8560, + "total_gas_fees": 570668, + "total_forward_fees": 68090, + "total_fees": 570668 + }, + "test exact arithmetic carry and subtraction boundaries::trace#4": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#1": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test v2 OUTP appends a routed output and commits it::trace#2": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3675185, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#25": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 25", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test register load jump and ADC without carry::trace#6": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test v2 advance executes a canonical instruction and rejects stale state::trace#1": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93780, + "total_fees": 20601 + }, + "test register load jump and ADC without carry::trace#9": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test ROM and RAM boundary pages remain addressable::trace#5": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test arithmetic logical and SYS opcodes::trace#2": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#77": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 77", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test top up message preserves metadata::trace#2": { + "test_name": "test top up message preserves metadata", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test exact arithmetic carry and subtraction boundaries::trace#2": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test OUT updates the commitment and emits a bounded event::trace#4": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test arithmetic and flag boundary outcomes::trace#13": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17224, + "total_gas_fees": 1148269, + "total_forward_fees": 112536, + "total_fees": 1297891 + }, + "test Fibonacci contract emits the canonical output trace::trace#11": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#30": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 30", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test RAM and branch opcodes::trace#8": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test ROM and RAM boundary pages remain addressable::trace#10": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#21": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 21", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test deploy exposes Tonolith metadata::trace#1": { + "test_name": "test deploy exposes Tonolith metadata", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 89869, + "total_fees": 20601 + }, + "test v2 advance executes a canonical instruction and rejects stale state::trace#3": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 24743, + "total_gas_fees": 1649535, + "total_forward_fees": 124981, + "total_fees": 1848624 + }, + "test ROM and RAM boundary pages remain addressable::trace#8": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#8": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test exact arithmetic carry and subtraction boundaries::trace#3": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#49": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 49", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#26": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 26", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test register load jump and ADC without carry::trace#2": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test external messages are rejected::trace#3": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 3", + "tx_count": 0, + "total_gas_used": 0, + "total_gas_fees": 0, + "total_forward_fees": 0, + "total_fees": 0 + }, + "test Fibonacci contract emits the canonical output trace::trace#92": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 92", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test Fibonacci contract emits the canonical output trace::trace#12": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test INC sets carry and zero on nibble wrap::trace#2": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#28": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 28", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test named failure paths and halted rejection::trace#36": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 36", + "tx_count": 1, + "total_gas_used": 2265, + "total_gas_fees": 151001, + "total_forward_fees": 68090, + "total_fees": 151001 + }, + "test Fibonacci contract emits the canonical output trace::trace#38": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 38", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test Fibonacci contract emits the canonical output trace::trace#24": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 24", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test external messages are rejected::trace#2": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#4": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 1118, + "total_gas_fees": 74534, + "total_forward_fees": 44446, + "total_fees": 74534 + }, + "test v2 OUTP appends a routed output and commits it::trace#3": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 44690, + "total_gas_fees": 2979335, + "total_forward_fees": 124981, + "total_fees": 3318158 + }, + "test exact arithmetic carry and subtraction boundaries::trace#11": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 17206, + "total_gas_fees": 1147069, + "total_forward_fees": 112536, + "total_fees": 1296691 + }, + "test named failure paths and halted rejection::trace#11": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 11", + "tx_count": 1, + "total_gas_used": 1761, + "total_gas_fees": 117401, + "total_forward_fees": 68090, + "total_fees": 117401 + }, + "test Fibonacci contract emits the canonical output trace::trace#32": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 32", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test ROM and RAM boundary pages remain addressable::trace#7": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 19279, + "total_gas_fees": 1285269, + "total_forward_fees": 112536, + "total_fees": 1434891 + }, + "test register load jump and ADC without carry::trace#1": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test named failure paths and halted rejection::trace#6": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 1588, + "total_gas_fees": 105868, + "total_forward_fees": 68135, + "total_fees": 105868 + }, + "test Fibonacci contract emits the canonical output trace::trace#23": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 23", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#40": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 40", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test named failure paths and halted rejection::trace#38": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 38", + "tx_count": 1, + "total_gas_used": 2343, + "total_gas_fees": 156201, + "total_forward_fees": 68090, + "total_fees": 156201 + }, + "test Fibonacci contract emits the canonical output trace::trace#84": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 84", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test Fibonacci contract emits the canonical output trace::trace#67": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 67", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test ROM and RAM boundary pages remain addressable::trace#9": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 19483, + "total_gas_fees": 1298869, + "total_forward_fees": 112536, + "total_fees": 1448491 + }, + "test OUT updates the commitment and emits a bounded event::trace#2": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test RAM and branch opcodes::trace#9": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test arithmetic logical and SYS opcodes::trace#5": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test stale and invalid advance requests do not mutate state::trace#5": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 3251, + "total_gas_fees": 216734, + "total_forward_fees": 68090, + "total_fees": 216734 + }, + "test Fibonacci contract emits the canonical output trace::trace#4": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#32": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 32", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test RAM and branch opcodes::trace#4": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 19836, + "total_gas_fees": 1322402, + "total_forward_fees": 112536, + "total_fees": 1472024 + }, + "test ROM and RAM boundary pages remain addressable::trace#3": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#80": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 80", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test Fibonacci contract emits the canonical output trace::trace#83": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 83", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test INC sets carry and zero on nibble wrap::trace#5": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test RAM and branch opcodes::trace#6": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 16676, + "total_gas_fees": 1111735, + "total_forward_fees": 112536, + "total_fees": 1261357 + }, + "test Fibonacci contract emits the canonical output trace::trace#58": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 58", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test arithmetic and flag boundary outcomes::trace#3": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic logical and SYS opcodes::trace#13": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17765, + "total_gas_fees": 1184335, + "total_forward_fees": 112536, + "total_fees": 1333957 + }, + "test exact arithmetic carry and subtraction boundaries::trace#1": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 99113, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#87": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 87", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#29": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 29", + "tx_count": 1, + "total_gas_used": 8846, + "total_gas_fees": 589734, + "total_forward_fees": 68090, + "total_fees": 589734 + }, + "test Fibonacci contract emits the canonical output trace::trace#52": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 52", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test arithmetic logical and SYS opcodes::trace#16": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 16", + "tx_count": 2, + "total_gas_used": 18865, + "total_gas_fees": 1257669, + "total_forward_fees": 112536, + "total_fees": 1407291 + }, + "test Fibonacci contract emits the canonical output trace::trace#94": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 94", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test Fibonacci contract emits the canonical output trace::trace#19": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 19", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#2": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test advance executes one instruction and emits commitments::trace#3": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic and flag boundary outcomes::trace#12": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17765, + "total_gas_fees": 1184335, + "total_forward_fees": 112536, + "total_fees": 1333957 + }, + "test exact arithmetic carry and subtraction boundaries::trace#7": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 deploy exposes immutable metadata and a canonical initial state::trace#2": { + "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3589362, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#64": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 64", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test RAM and branch opcodes::trace#1": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test exact arithmetic carry and subtraction boundaries::trace#10": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#33": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 33", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#53": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 53", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test named failure paths and halted rejection::trace#16": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 16", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test arithmetic logical and SYS opcodes::trace#9": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17224, + "total_gas_fees": 1148269, + "total_forward_fees": 112536, + "total_fees": 1297891 + }, + "test arithmetic and flag boundary outcomes::trace#8": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#4": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 4578, + "total_gas_fees": 305201, + "total_forward_fees": 80535, + "total_fees": 305201 + }, + "test Fibonacci contract emits the canonical output trace::trace#51": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 51", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#54": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 54", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test named failure paths and halted rejection::trace#35": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 35", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#15": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 15", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#37": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 37", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#3": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test RAM and branch opcodes::trace#7": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 18224, + "total_gas_fees": 1214935, + "total_forward_fees": 112536, + "total_fees": 1364557 + }, + "test Fibonacci contract emits the canonical output trace::trace#60": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 60", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic and flag boundary outcomes::trace#7": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test named failure paths and halted rejection::trace#12": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test RAM and branch opcodes::trace#11": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 16728, + "total_gas_fees": 1115202, + "total_forward_fees": 112536, + "total_fees": 1264824 + }, + "test named failure paths and halted rejection::trace#14": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#6": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#55": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 55", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test ROM and RAM boundary pages remain addressable::trace#6": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic logical and SYS opcodes::trace#7": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 17232, + "total_gas_fees": 1148802, + "total_forward_fees": 112536, + "total_fees": 1298424 + }, + "test named failure paths and halted rejection::trace#30": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 30", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#8": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test JC false path and maximum jump target::trace#3": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 16702, + "total_gas_fees": 1113469, + "total_forward_fees": 112536, + "total_fees": 1263091 + }, + "test register load jump and ADC without carry::trace#5": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic logical and SYS opcodes::trace#15": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 15", + "tx_count": 2, + "total_gas_used": 18821, + "total_gas_fees": 1254735, + "total_forward_fees": 112536, + "total_fees": 1404357 + }, + "test arithmetic logical and SYS opcodes::trace#10": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 17294, + "total_gas_fees": 1152935, + "total_forward_fees": 112536, + "total_fees": 1302557 + }, + "test register load jump and ADC without carry::trace#10": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test register load jump and ADC without carry::trace#13": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test Fibonacci contract emits the canonical output trace::trace#17": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#19": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 19", + "tx_count": 1, + "total_gas_used": 8508, + "total_gas_fees": 567201, + "total_forward_fees": 68090, + "total_fees": 567201 + }, + "test RAM and branch opcodes::trace#5": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#70": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 70", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test JC false path and maximum jump target::trace#6": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test Fibonacci contract emits the canonical output trace::trace#10": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 10", + "tx_count": 2, + "total_gas_used": 19538, + "total_gas_fees": 1302535, + "total_forward_fees": 112536, + "total_fees": 1553491 + }, + "test v2 OUTP appends a routed output and commits it::trace#1": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test INC sets carry and zero on nibble wrap::trace#4": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 18945, + "total_gas_fees": 1263002, + "total_forward_fees": 112536, + "total_fees": 1412624 + }, + "test Fibonacci contract emits the canonical output trace::trace#85": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 85", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic logical and SYS opcodes::trace#6": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test exact arithmetic carry and subtraction boundaries::trace#9": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#34": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 34", + "tx_count": 1, + "total_gas_used": 2949, + "total_gas_fees": 196601, + "total_forward_fees": 68090, + "total_fees": 196601 + }, + "test named failure paths and halted rejection::trace#41": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 41", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#41": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 41", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#89": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 89", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test top up message preserves metadata::trace#3": { + "test_name": "test top up message preserves metadata", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1328, + "total_gas_fees": 88534, + "total_forward_fees": 44446, + "total_fees": 88534 + }, + "test arithmetic and flag boundary outcomes::trace#17": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 18945, + "total_gas_fees": 1263002, + "total_forward_fees": 112536, + "total_fees": 1412624 + }, + "test Fibonacci contract emits the canonical output trace::trace#44": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 44", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test arithmetic and flag boundary outcomes::trace#4": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test register load jump and ADC without carry::trace#8": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15039, + "total_gas_fees": 1002602, + "total_forward_fees": 112536, + "total_fees": 1152224 + }, + "test Fibonacci contract emits the canonical output trace::trace#26": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 26", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test Fibonacci contract emits the canonical output trace::trace#16": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 16", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test named failure paths and halted rejection::trace#7": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 1373, + "total_gas_fees": 91534, + "total_forward_fees": 44446, + "total_fees": 91534 + }, + "test named failure paths and halted rejection::trace#17": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 17", + "tx_count": 1, + "total_gas_used": 8256, + "total_gas_fees": 550401, + "total_forward_fees": 68090, + "total_fees": 550401 + }, + "test Fibonacci contract emits the canonical output trace::trace#50": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 50", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test Fibonacci contract emits the canonical output trace::trace#88": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 88", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test named failure paths and halted rejection::trace#5": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 44446, + "total_fees": 63401 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#3": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 26230, + "total_gas_fees": 1748669, + "total_forward_fees": 124981, + "total_fees": 1947758 + }, + "test Fibonacci contract emits the canonical output trace::trace#62": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 62", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test RAM and branch opcodes::trace#3": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic and flag boundary outcomes::trace#2": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test arithmetic and flag boundary outcomes::trace#6": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test Fibonacci contract emits the canonical output trace::trace#22": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 22", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test Fibonacci contract emits the canonical output trace::trace#99": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 99", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test named failure paths and halted rejection::trace#13": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 13", + "tx_count": 1, + "total_gas_used": 7970, + "total_gas_fees": 531334, + "total_forward_fees": 68090, + "total_fees": 531334 + }, + "test ROM and RAM boundary pages remain addressable::trace#1": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93069, + "total_fees": 20601 + }, + "test named failure paths and halted rejection::trace#39": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 39", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#79": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 79", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#72": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 72", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test Fibonacci contract emits the canonical output trace::trace#76": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 76", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#71": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 71", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#78": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 78", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test JC false path and maximum jump target::trace#4": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test named failure paths and halted rejection::trace#33": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 33", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test Fibonacci contract emits the canonical output trace::trace#45": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 45", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#65": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 65", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic and flag boundary outcomes::trace#11": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 17206, + "total_gas_fees": 1147069, + "total_forward_fees": 112536, + "total_fees": 1296691 + }, + "test arithmetic logical and SYS opcodes::trace#12": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test stale and invalid advance requests do not mutate state::trace#2": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#95": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 95", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#5": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 5", + "tx_count": 3, + "total_gas_used": 26269, + "total_gas_fees": 1751270, + "total_forward_fees": 243739, + "total_fees": 1821490 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#3": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 91291, + "total_fees": 20601 + }, + "test ROM and RAM boundary pages remain addressable::trace#12": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic logical and SYS opcodes::trace#1": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test named failure paths and halted rejection::trace#24": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 24", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test register load jump and ADC without carry::trace#12": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17232, + "total_gas_fees": 1148802, + "total_forward_fees": 112536, + "total_fees": 1298424 + }, + "test v2 advance executes a canonical instruction and rejects stale state::trace#4": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 4726, + "total_gas_fees": 315068, + "total_forward_fees": 80535, + "total_fees": 315068 + }, + "test arithmetic logical and SYS opcodes::trace#8": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 17206, + "total_gas_fees": 1147069, + "total_forward_fees": 112536, + "total_fees": 1296691 + }, + "test arithmetic and flag boundary outcomes::trace#14": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 14", + "tx_count": 2, + "total_gas_used": 18821, + "total_gas_fees": 1254735, + "total_forward_fees": 112536, + "total_fees": 1404357 + }, + "test ROM and RAM boundary pages remain addressable::trace#13": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 19836, + "total_gas_fees": 1322402, + "total_forward_fees": 112536, + "total_fees": 1472024 + }, + "test exact arithmetic carry and subtraction boundaries::trace#5": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test arithmetic logical and SYS opcodes::trace#4": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#21": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 21", + "tx_count": 1, + "total_gas_used": 8846, + "total_gas_fees": 589734, + "total_forward_fees": 68090, + "total_fees": 589734 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#2": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3687807, + "total_fees": 70068 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#2": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 3625451, + "total_fees": 70068 + }, + "test named failure paths and halted rejection::trace#40": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 40", + "tx_count": 1, + "total_gas_used": 2421, + "total_gas_fees": 161401, + "total_forward_fees": 68090, + "total_fees": 161401 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#5": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 22983, + "total_gas_fees": 1532201, + "total_forward_fees": 96002, + "total_fees": 1669535 + }, + "test Fibonacci contract emits the canonical output trace::trace#56": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 56", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test register load jump and ADC without carry::trace#3": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#4": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 7196, + "total_gas_fees": 479734, + "total_forward_fees": 96002, + "total_fees": 479734 + }, + "test Fibonacci contract emits the canonical output trace::trace#47": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 47", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test RAM and branch opcodes::trace#13": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#1": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92002, + "total_fees": 20601 + }, + "test RAM and branch opcodes::trace#2": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#4": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 31630, + "total_gas_fees": 2108669, + "total_forward_fees": 199293, + "total_fees": 2295869 + }, + "test stale and invalid advance requests do not mutate state::trace#6": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 1777, + "total_gas_fees": 118468, + "total_forward_fees": 68090, + "total_fees": 118468 + }, + "test Fibonacci contract emits the canonical output trace::trace#59": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 59", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test deploy exposes Tonolith metadata::trace#2": { + "test_name": "test deploy exposes Tonolith metadata", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test arithmetic and flag boundary outcomes::trace#15": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 15", + "tx_count": 2, + "total_gas_used": 18865, + "total_gas_fees": 1257669, + "total_forward_fees": 112536, + "total_fees": 1407291 + }, + "test stale and invalid advance requests do not mutate state::trace#4": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 3045, + "total_gas_fees": 203001, + "total_forward_fees": 68090, + "total_fees": 203001 + }, + "test Fibonacci contract emits the canonical output trace::trace#57": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 57", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#82": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 82", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test named failure paths and halted rejection::trace#18": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 18", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test arithmetic logical and SYS opcodes::trace#14": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 14", + "tx_count": 2, + "total_gas_used": 18599, + "total_gas_fees": 1239935, + "total_forward_fees": 112536, + "total_fees": 1389557 + }, + "test Fibonacci contract emits the canonical output trace::trace#46": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 46", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test advance executes one instruction and emits commitments::trace#2": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test register load jump and ADC without carry::trace#4": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#1": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#86": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 86", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test named failure paths and halted rejection::trace#1": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test advance executes one instruction and emits commitments::trace#1": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test ROM and RAM boundary pages remain addressable::trace#11": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 19617, + "total_gas_fees": 1307802, + "total_forward_fees": 112536, + "total_fees": 1457424 + }, + "test named failure paths and halted rejection::trace#3": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1332, + "total_gas_fees": 88801, + "total_forward_fees": 44446, + "total_fees": 88801 + }, + "test Fibonacci contract emits the canonical output trace::trace#13": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#68": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 68", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test ROM and RAM boundary pages remain addressable::trace#4": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1408501, + "total_fees": 63401 + }, + "test arithmetic logical and SYS opcodes::trace#3": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test named failure paths and halted rejection::trace#20": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 20", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#73": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 73", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#31": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 31", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test arithmetic logical and SYS opcodes::trace#18": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 18", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test Fibonacci contract emits the canonical output trace::trace#36": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 36", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test Fibonacci contract emits the canonical output trace::trace#39": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 39", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic logical and SYS opcodes::trace#17": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 18945, + "total_gas_fees": 1263002, + "total_forward_fees": 112536, + "total_fees": 1412624 + }, + "test RAM and branch opcodes::trace#12": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 15039, + "total_gas_fees": 1002602, + "total_forward_fees": 112536, + "total_fees": 1152224 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#3": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 28906, + "total_gas_fees": 1927069, + "total_forward_fees": 124981, + "total_fees": 2126158 + }, + "test arithmetic and flag boundary outcomes::trace#5": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test exact arithmetic carry and subtraction boundaries::trace#8": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#27": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 27", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#90": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 90", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + } + } +} \ No newline at end of file diff --git a/tests/v2/batch-selection.test.ts b/tests/v2/batch-selection.test.ts new file mode 100644 index 0000000..5105f7c --- /dev/null +++ b/tests/v2/batch-selection.test.ts @@ -0,0 +1,14 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +test("v2 batch selection records every candidate and selects only measured single-step execution", async () => { + const matrix = JSON.parse(await readFile(new URL("../../benchmarks/v2/batch-selection.json", import.meta.url), "utf8")) as { + selectedMaxStepsPerAdvance: number; + candidates: readonly { maxSteps: number; decision: string; contractAccepted: boolean }[]; + }; + assert.deepEqual(matrix.candidates.map((candidate) => candidate.maxSteps), [1, 2, 4, 8, 16, 32, 64]); + assert.equal(matrix.selectedMaxStepsPerAdvance, 1); + assert.equal(matrix.candidates[0]?.contractAccepted, true); + assert.ok(matrix.candidates.slice(1).every((candidate) => !candidate.contractAccepted)); +}); From 92c8261d58d24e2813408dce35fccb468316cb73 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:31:54 +0100 Subject: [PATCH 37/67] fix: make v2 excess refunds balance-safe --- Acton.toml | 2 + contracts/TonolithCoreV2.tolk | 3 +- scripts/deploy-v2-fibonacci.tolk | 154 +++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 scripts/deploy-v2-fibonacci.tolk diff --git a/Acton.toml b/Acton.toml index 0cd05fc..c680cc3 100644 --- a/Acton.toml +++ b/Acton.toml @@ -24,6 +24,8 @@ deploy-emulation = "acton script scripts/deploy.tolk" deploy-testnet = "acton script scripts/deploy-fibonacci.tolk --net testnet" deploy-fibonacci-emulation = "acton script scripts/deploy-fibonacci.tolk" deploy-fibonacci-testnet = "acton script scripts/deploy-fibonacci.tolk --net testnet" +deploy-v2-fibonacci-emulation = "acton script scripts/deploy-v2-fibonacci.tolk" +deploy-v2-fibonacci-testnet = "acton script scripts/deploy-v2-fibonacci.tolk --net testnet" [fmt] width = 100 diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index 285a4f9..7db2eed 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -57,7 +57,7 @@ fun emitOutputAcknowledgedV2(event: V2OutputAcknowledged) { fun returnSafeExcessV2(sender: address) { val refund = createMessage({ bounce: BounceMode.NoBounce, - value: MIN_DELIVERY_VALUE_V2, + value: 0, dest: sender, }); refund.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE); @@ -223,7 +223,6 @@ fun processDeliverInputV2(sender: address, value: coins, msg: V2DeliverInput) { msg.destinationEpoch, storage.core.hash(), ); - returnSafeExcessV2(sender); return; } val slotCursor = v2FindInputSlot( diff --git a/scripts/deploy-v2-fibonacci.tolk b/scripts/deploy-v2-fibonacci.tolk new file mode 100644 index 0000000..c314a38 --- /dev/null +++ b/scripts/deploy-v2-fibonacci.tolk @@ -0,0 +1,154 @@ +import "@acton/io" +import "@acton/emulation/network" +import "@acton/emulation/scripts" +import "@wrappers/TonolithCoreV2.gen" +import "@contracts/v2/memory" +import "@contracts/v2/rom" +import "@contracts/v2/types" + +const V2_RUN_ID: uint256 = 0x9d74f5c6d97a5cca67a4f8811b67bc49e2ec29d0f04f09fc1c07a045518a1ca3 +const V2_PROGRAM_ID: uint256 = 0xe140cb991c86e81663dbe688da3e6a2b18d14b260b0aca0a0c7b1381707a7dda +const V2_ROM_ROOT: uint256 = 0x029ccf15774f680518705bb7c8be4b2ca92f34a4ceb9c963e55609225398c748 +const V2_RAM_ROOT: uint256 = 0xed807133df392920f6ed73d30ab7e67ecaee528d940e1be1ea8e44cd25031b8e +const V2_ROUTE_ROOT: uint256 = 0x9e548d24c3c980ff9fe617f92286289613d6deae0b0da0cbe88aa8a51e6d9bcb +const V2_LIMITS_HASH: uint256 = 0xf8d3ee4ee34e55e437aa1decfa2742d81aa6d623beec67c9394d736376beb1fb +const V2_STATIC_COMMITMENT: uint256 = 0x2c5978dc7ca91a1899cd345f5e9232500f50670ac7612fddb30f64eb879677f9 + +fun main() { + val deployer = scripts.wallet("tonolith-testnet"); + val core = TonolithCoreV2.fromStorage(fibonacciStorage()); + val deploy = core.deploy(deployer.address, { value: ton("1") }); + println(deploy.waitForTrace()); + val init = core.stateInit; + if (init != null) { + println("INIT|{}|{}|{}", core.address, init.code.hash(), init.data.hash()); + } + println("DEPLOY|{}|{}|{}", core.address, core.getProgramIdV2(), core.getRomRootV2()); + println( + "COMMIT|{}|{}|{}", + core.getRouteRootV2(), + core.getStaticCommitmentV2(), + core.getCoreStateHashV2(), + ); + + var ticks = 0; + while (core.getStatusV2() == STATUS_RUNNING_V2 && ticks < 200) { + val advanceCount = core.getAdvanceCountV2(); + val stateHash = core.getCoreStateHashV2(); + val tick = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + ticks, + V2_RUN_ID, + advanceCount, + stateHash, + 1, + 1, + { value: ton("0.01") }, + ); + println(tick.waitForTrace()); + println( + "STATE|{}|{}|{}|{}|{}", + ticks + 1, + core.getAdvanceCountV2(), + core.getInstructionCountV2(), + core.getPcV2(), + core.getAccumulatorV2(), + ); + println("STATE2|{}|{}", core.getOutputCountV2(), core.getCoreStateHashV2()); + ticks += 1; + } + println( + "FINAL|{}|{}|{}|{}", + core.getStatusV2(), + core.getOutputCountV2(), + core.getInstructionCountV2(), + core.getCoreStateHashV2(), + ); +} + +fun fibonacciStorage(): V2Storage { + val routes = emptyV2RecordRoot(); + val peers = emptyV2RecordRoot(); + val staticRoots = V2StaticRoots { + romRoot: V2_ROM_ROOT, + routeRoot: V2_ROUTE_ROOT, + limitsHash: V2_LIMITS_HASH, + peers: V2PeerRoots { peerRoot: peers.hash() }.toCell(), + }.toCell(); + val identity = V2StaticData { + coreId: 0, + runId: V2_RUN_ID, + staticCommitment: V2_STATIC_COMMITMENT, + programId: V2_PROGRAM_ID, + roots: staticRoots, + }.toCell(); + val core = V2CoreState { + domain: DOMAIN_STATE_V2, + staticCommitment: V2_STATIC_COMMITMENT, + status: STATUS_RUNNING_V2, + faultCode: 0, + pc: 0, + accumulator: 0, + flags: 0, + epoch: 0, + instructionCount: 0, + advanceCount: 0, + acceptedMessageCount: 0, + outputCount: 0, + inputCount: 0, + nextOutputSequence: 0, + commitments: V2CoreCommitments { + outputCommitment: 0, + inputCommitment: 0, + epochHistoryCommitment: 0, + }.toCell(), + memory: zeroV2Memory(), + queues: emptyV2Queues(), + }.toCell(); + return V2Storage { + magic: TONOLITH_MAGIC_V2, + schemaVersion: SCHEMA_VERSION_V2, + isaVersion: ISA_VERSION_V2, + abiVersion: ABI_VERSION_V2, + protocolVersion: PROTOCOL_VERSION_V2, + identity, + core, + rom: fibonacciRom(), + network: V2NetworkData { routes, peers }.toCell(), + }; +} + +fun fibonacciRom(): Cell { + val firstHalf = beginCell() + .storeUint(0x1001, 16) + .storeUint(0x3000, 16) + .storeUint(0x1001, 16) + .storeUint(0x3001, 16) + .storeUint(0x1007, 16) + .storeUint(0x3002, 16) + .storeUint(0x2000, 16) + .storeUint(0xF001, 16) + .storeUint(0x2002, 16) + .storeUint(0xF008, 16) + .storeUint(0x3002, 16) + .storeUint(0xD014, 16) + .storeUint(0x2000, 16) + .storeUint(0x4001, 16) + .storeUint(0x3003, 16) + .storeUint(0x2001, 16) + .toSlice() + .loadUint(256) as uint256; + val secondHalf = beginCell() + .storeUint(0x3000, 16) + .storeUint(0x2003, 16) + .storeUint(0x3001, 16) + .storeUint(0xC006, 16) + .storeUint(0xF000, 16) + .storeUint(0, 176) + .toSlice() + .loadUint(256) as uint256; + return v2RomWithFirstLeaf( + V2RomLeaf { firstHalf, secondHalf }.toCell(), + ); +} From bae4208b25ccefd1f86bea1cb42e174621cafe6b Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:32:06 +0100 Subject: [PATCH 38/67] test: bind benchmark hashes to accepted advances --- benchmarks/v2/expected.json | 6 +++--- packages/benchmarks/src/runner.ts | 5 +++++ tests/v2/benchmarks.test.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/benchmarks/v2/expected.json b/benchmarks/v2/expected.json index 5ac41b8..805e113 100644 --- a/benchmarks/v2/expected.json +++ b/benchmarks/v2/expected.json @@ -9,7 +9,7 @@ "outputs": [1, 1, 2, 3, 5, 8, 13], "instructions": 97, "batches": 97, - "finalStateHash": "73188259a45c2b3978ebae240c2c8254a049f8497ed53025c74b6535af9a8c54", + "finalStateHash": "8238d6bbcd507f4a3b9cba3b1ad48b2d88fd6ab11f7068b75340a3f0044a9f1f", "gas": null, "feesNano": null, "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" @@ -20,7 +20,7 @@ "outputs": [5], "instructions": 12, "batches": 12, - "finalStateHash": "09d7dbb2d629f3afea9a1b28c46aa9db3eb5fe55b337dbbdbc6a81d211a26d10", + "finalStateHash": "d4fa5c2c9d717aee879fcb1593b178bbe63ded666bb01f2dc0b2ee110232c925", "gas": null, "feesNano": null, "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" @@ -31,7 +31,7 @@ "outputs": [0], "instructions": 50, "batches": 50, - "finalStateHash": "4525925f5f34b7a1eec086f6145a128d0884c265309b558144f24d79017e05d4", + "finalStateHash": "9d8ae2f2680feb1ac415466c93a93708bed064639c4e2cd37a4054f7ae26d0c1", "gas": null, "feesNano": null, "visualizerTrace": "generated by packages/benchmarks/src/runner.ts" diff --git a/packages/benchmarks/src/runner.ts b/packages/benchmarks/src/runner.ts index 41573e4..cc29dfe 100644 --- a/packages/benchmarks/src/runner.ts +++ b/packages/benchmarks/src/runner.ts @@ -44,6 +44,11 @@ export function runArtifact(artifact: ArtifactBundle, maxInstructions = 10_000): const result = step(before, { rom: artifact.assembly.words }); state = result.state; if (!result.executed) break; + // Each benchmark frame represents one accepted bounded Advance, not only + // the architectural instruction. Keep protocol counters identical to the + // emulator's Advance transition and the contract state commitment. + state.advanceCount += 1n; + state.acceptedMessageCount += 1n; executedInstructions += 1; if (result.legacyOutput !== undefined) outputs.push(result.legacyOutput.value); if (result.output !== undefined) outputs.push(result.output.value); diff --git a/tests/v2/benchmarks.test.ts b/tests/v2/benchmarks.test.ts index adbc9eb..11d273e 100644 --- a/tests/v2/benchmarks.test.ts +++ b/tests/v2/benchmarks.test.ts @@ -16,6 +16,7 @@ test("v2 benchmark suite produces the canonical Fibonacci vector", async () => { assert.deepEqual(result.outputs, [1, 1, 2, 3, 5, 8, 13]); assert.equal(result.finalStatus, 3); assert.equal(result.frames.length, result.executedInstructions); + assert.equal(result.finalStateHash, "8238d6bbcd507f4a3b9cba3b1ad48b2d88fd6ab11f7068b75340a3f0044a9f1f"); }); test("v2 bounded checksum and cellular automaton terminate with deterministic outputs", async () => { From 3f357f9057c5bf64685d86c40e52e99ff61b479e Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:28:24 +0100 Subject: [PATCH 39/67] test: validate v2 delivery protocol boundaries --- contracts/TonolithCoreV2.tolk | 44 +- contracts/v2/execution.tolk | 10 +- contracts/v2/queues.tolk | 70 ++ contracts/v2/rom.tolk | 16 +- contracts/v2/routes.tolk | 5 +- tests/tonolith_v2.test.tolk | 1384 +++++++++++++++++++++++++++++++++ 6 files changed, 1499 insertions(+), 30 deletions(-) diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index 7db2eed..bfc8ac6 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -249,7 +249,7 @@ fun processDeliverInputV2(sender: address, value: coins, msg: V2DeliverInput) { consumed: 0, }.toCell(); var updatedQueues = queues; - updatedQueues.inboxRoot = v2AppendQueueRoot(queues.inboxRoot, input); + updatedQueues.inboxRoot = v2InsertInputRecord(queues.inboxRoot, input); core.queues = updatedQueues.toCell(); core.inputCount += 1; core.acceptedMessageCount += 1; @@ -392,23 +392,35 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { if (core.status == STATUS_WAITING_INPUT_V2 || core.status == STATUS_BACKPRESSURED_V2) { core.status = STATUS_RUNNING_V2; } - val word = v2RomWord(storage.rom, core.pc); - val _legacyOutput = v2ExecuteInstruction( - mutate core, - word, - identity.runId, - identity.coreId, - storage.network.load().routes, - previousStateHash, - ); - val executionStateHash = core.toCell().hash(); - val stepsExecuted = (core.instructionCount - startInstructionCount) as uint16; - val outputsProduced = (core.outputCount - startOutputCount) as uint8; + var stepsExecuted: uint16 = 0; + var outputsProduced: uint8 = 0; + var trace = previousStateHash; + var keepRunning = true; + while (keepRunning && stepsExecuted < msg.maxInstructions && core.status == STATUS_RUNNING_V2) { + val word = v2RomWord(storage.rom, core.pc); + val beforeInstructionCount = core.instructionCount; + val _legacyOutput = v2ExecuteInstruction( + mutate core, + word, + identity.runId, + identity.coreId, + storage.network.load().routes, + previousStateHash, + ); + if (core.instructionCount == beforeInstructionCount) { + keepRunning = false; + } + if (core.instructionCount != beforeInstructionCount) { + trace = nextV2TraceCommitment(trace, word, core.toCell().hash()); + stepsExecuted = (core.instructionCount - startInstructionCount) as uint16; + outputsProduced = (core.outputCount - startOutputCount) as uint8; + if (core.epoch != startEpoch || outputsProduced >= msg.maxOutputs) { + keepRunning = false; + } + } + } val yielded = core.epoch != startEpoch; val stopReason = v2StopReason(core.status, yielded); - val trace = stepsExecuted == 0 - ? previousStateHash - : nextV2TraceCommitment(previousStateHash, word, executionStateHash); core.advanceCount += 1; core.acceptedMessageCount += 1; storage.core = core.toCell(); diff --git a/contracts/v2/execution.tolk b/contracts/v2/execution.tolk index f88d219..f01821f 100644 --- a/contracts/v2/execution.tolk +++ b/contracts/v2/execution.tolk @@ -174,7 +174,15 @@ fun v2ExecuteSys( if (subop == 10) { val port = operand >> 4; val queues = core.queues.load(); - assert (v2QueueCount(queues.outboxRoot) < MAX_OUTPUT_RECORDS_V2) throw ErrorsV2.QueueFull; + if (v2QueueCount(queues.outboxRoot) >= MAX_OUTPUT_RECORDS_V2) { + // A blocked OUTP is not an executed instruction. Restore the + // fetch counters and leave the core in an observable stop state + // so a later acknowledgement can wake it safely. + core.pc = ((core.pc - 1) & 1023) as uint10; + core.instructionCount -= 1; + core.status = STATUS_BACKPRESSURED_V2; + return null; + } val route = v2FindRoute(routes, coreId, port); assert (route != null) throw ErrorsV2.InvalidRoute; val sequence = v2GetOutputSequence(core.nextOutputSequence, port); diff --git a/contracts/v2/queues.tolk b/contracts/v2/queues.tolk index 34b5f79..0c5567e 100644 --- a/contracts/v2/queues.tolk +++ b/contracts/v2/queues.tolk @@ -179,6 +179,76 @@ fun v2FindInput(root: cell, epoch: uint64, port: int): V2InputCursor { return v2FindInputFrom(root, epoch, port, 0, count); } +fun v2InputBefore(left: V2InputRecord, right: V2InputRecord): bool { + if (left.destinationEpoch != right.destinationEpoch) { + return left.destinationEpoch < right.destinationEpoch; + } + if (left.destinationPort != right.destinationPort) { + return left.destinationPort < right.destinationPort; + } + return left.sequence < right.sequence; +} + +fun v2InputInsertionIndex(root: cell, record: V2InputRecord, index: int, count: int): int { + if (index >= count || index >= 16) { + return count; + } + val current = v2InputAt(root, index); + if (v2InputBefore(record, current)) { + return index; + } + return v2InputInsertionIndex(root, record, index + 1, count); +} + +fun v2InsertInputRecordFrom( + root: cell, + record: cell, + insertionIndex: int, + sourceIndex: int, + count: int, + result: cell, +): cell { + if (sourceIndex == insertionIndex) { + val withInserted = v2AppendQueueRoot(result, record); + if (sourceIndex >= count) { + return withInserted; + } + return v2InsertInputRecordFrom( + root, + record, + insertionIndex, + sourceIndex + 1, + count, + v2AppendQueueRoot(withInserted, v2RecordAt(root, sourceIndex)), + ); + } + if (sourceIndex >= count) { + return result; + } + return v2InsertInputRecordFrom( + root, + record, + insertionIndex, + sourceIndex + 1, + count, + v2AppendQueueRoot(result, v2RecordAt(root, sourceIndex)), + ); +} + +fun v2InsertInputRecord(root: cell, record: cell): cell { + val count = v2QueueCount(root); + val typedRecord = (record as Cell).load(); + val insertionIndex = v2InputInsertionIndex(root, typedRecord, 0, count); + return v2InsertInputRecordFrom( + root, + record, + insertionIndex, + 0, + count, + v2EmptyQueueRootForMutation(), + ); +} + fun v2FindInputFrom(root: cell, epoch: uint64, port: int, index: int, count: int): V2InputCursor { if (index >= count || index >= 16) { return V2InputCursor { diff --git a/contracts/v2/rom.tolk b/contracts/v2/rom.tolk index 7f36691..ab4aba7 100644 --- a/contracts/v2/rom.tolk +++ b/contracts/v2/rom.tolk @@ -82,18 +82,10 @@ fun v2RomWord(romCell: cell, address: uint10): uint16 { val leafIndex = address >> 5; val wordIndex = address & 31; val rootIndex = leafIndex >> 4; - var level2: V2RomLevel2; - if (rootIndex == 0) { - level2 = root.level20.load(); - } else if (rootIndex == 1) { - level2 = root.level21.load(); - } else if (rootIndex == 2) { - level2 = root.level22.load(); - } else if (rootIndex == 3) { - level2 = root.level23.load(); - } else { - throw ErrorsV2.InvalidPc; - } + // A 10-bit program counter addresses 32 leaves. The four-way root + // therefore uses only its first two children, exactly like the v1 ROM + // layout; the remaining children stay canonical zero padding. + val level2 = rootIndex == 0 ? root.level20.load() : root.level21.load(); val level1 = selectV2RomLevel1(level2, (leafIndex >> 2) & 3); val leafIndexInLevel = leafIndex & 3; var leafCell = level1.leaf0; diff --git a/contracts/v2/routes.tolk b/contracts/v2/routes.tolk index b819f01..1ea5811 100644 --- a/contracts/v2/routes.tolk +++ b/contracts/v2/routes.tolk @@ -20,7 +20,10 @@ fun v2FindRouteFrom( return null; } val route = v2RouteAt(routes, index); - if (route.sourceCoreId == sourceCoreId && route.sourcePort == sourcePort) { + if ( + route.domain == DOMAIN_ROUTE_V2 && route.sourceCoreId == sourceCoreId && + route.sourcePort == sourcePort + ) { return route; } return v2FindRouteFrom(routes, sourceCoreId, sourcePort, index + 1, count); diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index 6ff1477..56977dc 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -2,12 +2,14 @@ import "@acton/emulation/network" import "@acton/emulation/testing" import "@acton/testing/expect" +import "@contracts/TonolithCoreV2" import "@wrappers/TonolithCoreV2.gen" import "@contracts/v2/commitments" import "@contracts/v2/memory" import "@contracts/v2/peers" import "@contracts/v2/queues" import "@contracts/v2/rom" +import "@contracts/v2/routes" import "@contracts/v2/types" fun makeV2StorageForTest(rom: Cell): V2Storage { @@ -151,6 +153,45 @@ fun sendDeliverInputV2ForTest( return net.send(from, deliverMessage); } +fun sendDeliverInputV2WithConfig( + core: TonolithCoreV2, + from: address, + abiVersion: uint16, + messageValue: coins, + queryId: uint64, + runId: uint256, + outputId: uint256, + sourceCoreId: uint16, + destinationCoreId: uint16, + sourceEpoch: uint64, + destinationEpoch: uint64, + sourcePort: uint4, + destinationPort: uint4, + sequence: uint8, + value: uint4, + sourceInstructionCount: uint64, + sourceStateHash: uint256, +): SendResultList { + return core.sendV2DeliverInput( + from, + abiVersion, + queryId, + runId, + outputId, + sourceCoreId, + destinationCoreId, + sourceEpoch, + destinationEpoch, + sourcePort, + destinationPort, + sequence, + value, + sourceInstructionCount, + V2Hash { value: sourceStateHash }, + { bounce: false, value: messageValue }, + ); +} + fun sendInputAcceptedV2ForTest( core: TonolithCoreV2, from: address, @@ -180,6 +221,20 @@ fun sendInputAcceptedV2ForTest( return net.send(from, acknowledgement); } +fun advanceV2OnceForTest(core: TonolithCoreV2, from: address): SendResultList { + return sendAdvanceV2ForTest( + core, + from, + 0, + core.getRunIdV2(), + core.getAdvanceCountV2(), + core.getCoreStateHashV2(), + 1, + 1, + ton("0.1"), + ); +} + get fun `test v2 deploy exposes immutable metadata and a canonical initial state`() { val deployer = testing.treasury("v2-metadata-deployer"); val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); @@ -346,6 +401,62 @@ get fun `test v2 OUTP appends a routed output and commits it`() { expect(core.getCoreStateHashV2()).toEqual( 0x0eed2e36924e740c884530155b6c8f079f1e342a2ff3ee0a1b7b672ba88c7c42, ); + val dispatch = core.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 4, + 0, + core.getCoreStateHashV2(), + 0x18f93e46534eae22589575988cee189065e808d771d9eb925991245077a826fe, + { bounce: false, value: ton("0.1") }, + ); + expect(dispatch).toHaveSuccessfulTx(); + expect(core.getOutboxCountV2()).toEqual(1); + val invalidDispatch = core.sendV2DispatchOutput( + deployer.address, + 1, + 5, + 0, + core.getCoreStateHashV2(), + 0, + { bounce: false, value: ton("0.1") }, + ); + expect(invalidDispatch).toHaveFailedTx({ exitCode: ErrorsV2.InvalidVersion }); + val missingDispatch = core.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 6, + 0, + core.getCoreStateHashV2(), + 1, + { bounce: false, value: ton("0.1") }, + ); + expect(missingDispatch).toHaveFailedTx({ exitCode: ErrorsV2.NotFound }); + val rawMissingDispatch = core.sendAny( + deployer.address, + V2DispatchOutput { + abiVersion: ABI_VERSION_V2, + queryId: 7, + runId: 0, + expectedStateHash: core.getCoreStateHashV2(), + outputId: 1, + }.toCell(), + { bounce: false, value: ton("0.1") }, + ); + expect(rawMissingDispatch).toHaveFailedTx({ exitCode: ErrorsV2.NotFound }); + val encodedMissingDispatch = core.sendAny( + deployer.address, + beginCell() + .storeUint(MESSAGE_DISPATCH_OUTPUT_V2, 32) + .storeUint(ABI_VERSION_V2, 16) + .storeUint(8, 64) + .storeUint(0, 256) + .storeUint(core.getCoreStateHashV2(), 256) + .storeUint(1, 256) + .endCell(), + { bounce: false, value: ton("0.1") }, + ); + expect(encodedMissingDispatch).toHaveFailedTx({ exitCode: ErrorsV2.NotFound }); } get fun `test v2 IN waits, accepts authenticated input, deduplicates, and consumes`() { @@ -559,4 +670,1277 @@ get fun `test v2 acknowledgement retires output and rejects wrong peer`() { expect(accepted).toHaveSuccessfulTx(); expect(accepted).toEmitExternalMessage(); expect(core.getOutboxCountV2()).toEqual(0); + val duplicateAccepted = sendInputAcceptedV2ForTest( + core, + deployer.address, + 3, + 0, + outputId, + 1, + 1, + 0, + ); + expect(duplicateAccepted).toHaveSuccessfulTx(); + expect(core.getOutboxCountV2()).toEqual(0); + + var pressuredStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var pressuredCore = pressuredStorage.core.load(); + pressuredCore.status = STATUS_BACKPRESSURED_V2; + var pressuredQueues = pressuredCore.queues.load(); + pressuredQueues.outboxRoot = v2AppendQueueRoot(pressuredQueues.outboxRoot, output); + pressuredCore.queues = pressuredQueues.toCell(); + pressuredStorage.core = pressuredCore.toCell(); + val pressured = TonolithCoreV2.fromStorage(pressuredStorage); + expect(pressured.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: pressured.address, + }); + expect( + sendInputAcceptedV2ForTest( + pressured, + deployer.address, + 4, + 0, + outputId, + 1, + 1, + 0, + ), + ).toHaveSuccessfulTx(); + expect(pressured.getStatusV2()).toEqual(STATUS_RUNNING_V2); +} + +get fun `test v2 inherited opcode families execute through the contract`() { + val deployer = testing.treasury("v2-opcode-deployer"); + val nop = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); + expect( + nop.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: nop.address }); + expect(advanceV2OnceForTest(nop, deployer.address)).toHaveSuccessfulTx(); + val ldi = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x100F))); + expect( + ldi.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: ldi.address }); + expect(advanceV2OnceForTest(ldi, deployer.address)).toHaveSuccessfulTx(); + val ldr = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x2000))); + expect( + ldr.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: ldr.address }); + expect(advanceV2OnceForTest(ldr, deployer.address)).toHaveSuccessfulTx(); + val str = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x3000))); + expect( + str.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: str.address }); + expect(advanceV2OnceForTest(str, deployer.address)).toHaveSuccessfulTx(); + val add = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x4000))); + expect( + add.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: add.address }); + expect(advanceV2OnceForTest(add, deployer.address)).toHaveSuccessfulTx(); + val adc = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x5000))); + expect( + adc.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: adc.address }); + expect(advanceV2OnceForTest(adc, deployer.address)).toHaveSuccessfulTx(); + val sub = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x6000))); + expect( + sub.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: sub.address }); + expect(advanceV2OnceForTest(sub, deployer.address)).toHaveSuccessfulTx(); + val andCpu = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x7000))); + expect( + andCpu.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: andCpu.address }); + expect(advanceV2OnceForTest(andCpu, deployer.address)).toHaveSuccessfulTx(); + val orCpu = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x8000))); + expect( + orCpu.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: orCpu.address }); + expect(advanceV2OnceForTest(orCpu, deployer.address)).toHaveSuccessfulTx(); + val xorCpu = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x9000))); + expect( + xorCpu.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: xorCpu.address }); + expect(advanceV2OnceForTest(xorCpu, deployer.address)).toHaveSuccessfulTx(); + val ldm = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xA000))); + expect( + ldm.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: ldm.address }); + expect(advanceV2OnceForTest(ldm, deployer.address)).toHaveSuccessfulTx(); + val stm = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xB000))); + expect( + stm.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: stm.address }); + expect(advanceV2OnceForTest(stm, deployer.address)).toHaveSuccessfulTx(); + val jmp = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xC000))); + expect( + jmp.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: jmp.address }); + expect(advanceV2OnceForTest(jmp, deployer.address)).toHaveSuccessfulTx(); + val jz = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xD000))); + expect( + jz.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: jz.address }); + expect(advanceV2OnceForTest(jz, deployer.address)).toHaveSuccessfulTx(); + val jc = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xE000))); + expect( + jc.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: jc.address }); + expect(advanceV2OnceForTest(jc, deployer.address)).toHaveSuccessfulTx(); +} + +get fun `test v2 SYS controls cover flags shifts arithmetic and architectural stops`() { + val deployer = testing.treasury("v2-sys-deployer"); + val clc = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF002))); + expect( + clc.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: clc.address }); + expect(advanceV2OnceForTest(clc, deployer.address)).toHaveSuccessfulTx(); + val stc = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF003))); + expect( + stc.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: stc.address }); + expect(advanceV2OnceForTest(stc, deployer.address)).toHaveSuccessfulTx(); + val notCpu = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF004))); + expect( + notCpu.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: notCpu.address }); + expect(advanceV2OnceForTest(notCpu, deployer.address)).toHaveSuccessfulTx(); + val shl = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF005))); + expect( + shl.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: shl.address }); + expect(advanceV2OnceForTest(shl, deployer.address)).toHaveSuccessfulTx(); + val shr = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF006))); + expect( + shr.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: shr.address }); + expect(advanceV2OnceForTest(shr, deployer.address)).toHaveSuccessfulTx(); + val inc = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF007))); + expect( + inc.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: inc.address }); + expect(advanceV2OnceForTest(inc, deployer.address)).toHaveSuccessfulTx(); + val dec = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF008))); + expect( + dec.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: dec.address }); + expect(advanceV2OnceForTest(dec, deployer.address)).toHaveSuccessfulTx(); + val out = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF001))); + expect( + out.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: out.address }); + val outResult = advanceV2OnceForTest(out, deployer.address); + expect(outResult).toHaveSuccessfulTx(); + expect(outResult).toEmitExternalMessage(); + val yield = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF00B))); + expect( + yield.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: yield.address }); + expect(advanceV2OnceForTest(yield, deployer.address)).toHaveSuccessfulTx(); + expect(yield.getEpochV2()).toEqual(1); + val trap = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF1AC))); + expect( + trap.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: trap.address }); + expect(advanceV2OnceForTest(trap, deployer.address)).toHaveSuccessfulTx(); + expect(trap.getStatusV2()).toEqual(STATUS_FAULTED_V2); +} + +get fun `test v2 bounded memory ROM queues routes peers and commitments`() { + val route0 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 1, + destinationPort: 0, + delay: 1, + maxRecordsPerEpoch: 4, + }.toCell(); + val route1 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 1, + destinationCoreId: 2, + destinationPort: 1, + delay: 2, + maxRecordsPerEpoch: 4, + }.toCell(); + val route2 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 2, + sourcePort: 2, + destinationCoreId: 3, + destinationPort: 2, + delay: 3, + maxRecordsPerEpoch: 4, + }.toCell(); + val route3 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 3, + sourcePort: 3, + destinationCoreId: 4, + destinationPort: 3, + delay: 4, + maxRecordsPerEpoch: 4, + }.toCell(); + var routes = emptyV2RecordRoot(); + routes = v2AppendQueueRoot(routes, route0); + routes = v2AppendQueueRoot(routes, route1); + routes = v2AppendQueueRoot(routes, route2); + routes = v2AppendQueueRoot(routes, route3); + expect(v2QueueCount(routes)).toEqual(4); + expect((v2RecordAt(routes, 0) as Cell).load().sourcePort).toEqual(0); + expect((v2RecordAt(routes, 3) as Cell).load().destinationCoreId).toEqual(4); + val replaced = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 9, + sourcePort: 9, + destinationCoreId: 10, + destinationPort: 9, + delay: 1, + maxRecordsPerEpoch: 4, + }.toCell(); + val replacedRoutes = v2ReplaceRecordAt(routes, 2, replaced); + expect((v2RecordAt(replacedRoutes, 2) as Cell).load().sourceCoreId).toEqual(9); + val removedRoutes = v2RemoveRecordAt(replacedRoutes, 1); + expect(v2QueueCount(removedRoutes)).toEqual(3); + var fullRoutes = emptyV2RecordRoot(); + repeat (16) { + fullRoutes = v2AppendQueueRoot(fullRoutes, route0); + } + expect(v2QueueCount(fullRoutes)).toEqual(16); + expect((v2RecordAt(fullRoutes, 4) as Cell).load().sourcePort).toEqual(0); + expect((v2RecordAt(fullRoutes, 8) as Cell).load().sourcePort).toEqual(0); + expect((v2RecordAt(fullRoutes, 12) as Cell).load().sourcePort).toEqual(0); + expect(v2QueueCount(v2RemoveRecordAt(fullRoutes, 4))).toEqual(15); + expect(v2QueueCount(v2RemoveRecordAt(fullRoutes, 8))).toEqual(15); + expect(v2QueueCount(v2RemoveRecordAt(fullRoutes, 12))).toEqual(15); + val peer0 = V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 7, + peerAddress: testing.treasury("v2-peer-address").address, + }.toCell(); + var peers = emptyV2RecordRoot(); + peers = v2AppendQueueRoot(peers, peer0); + expect(v2FindPeer(peers, 7)!.coreId).toEqual(7); + expect(v2FindPeer(peers, 8)).toEqual(null); + val input = V2InputRecord { + domain: DOMAIN_INPUT_V2, + outputId: 1, + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 2, + sequence: 0, + value: 9, + sourceInstructionCount: 1, + sourceStateHash: 2, + consumed: 0, + }.toCell(); + var inputs = emptyV2RecordRoot(); + inputs = v2AppendQueueRoot(inputs, input); + expect(v2FindInput(inputs, 1, 2)!.value).toEqual(9); + expect(v2FindInputSlot(inputs, 1, 2, 0).found).toEqual(1); + expect(v2FindRecordById(inputs, 1).found).toEqual(1); + val consumed = V2InputRecord { + domain: DOMAIN_INPUT_V2, + outputId: 1, + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 2, + sequence: 0, + value: 9, + sourceInstructionCount: 1, + sourceStateHash: 2, + consumed: 1, + }.toCell(); + expect(v2InputAt(v2ReplaceRecordAt(inputs, 0, consumed), 0).consumed).toEqual(1); + expect(v2FindInput(inputs, 9, 9).found).toEqual(0); + expect(v2FindInputSlot(inputs, 9, 9, 9).found).toEqual(0); + expect(v2FindRecordById(inputs, 99).found).toEqual(0); + val rom = romWithFirstWordV2(0xABCD); + var memCore = makeV2StorageForTest(rom).core.load(); + writeV2Ram(mutate memCore, 0, 1); + writeV2Ram(mutate memCore, 64, 2); + writeV2Ram(mutate memCore, 128, 3); + writeV2Ram(mutate memCore, 192, 4); + expect(readV2Ram(memCore, 0)).toEqual(1); + expect(readV2Ram(memCore, 64)).toEqual(2); + expect(readV2Ram(memCore, 128)).toEqual(3); + expect(readV2Ram(memCore, 192)).toEqual(4); + expect(v2RomWord(rom, 0)).toEqual(0xABCD); + expect(v2RomWord(rom, 16)).toEqual(0); + expect(v2RomWord(rom, 32)).toEqual(0); + expect(v2RomWord(rom, 128)).toEqual(0); + expect(v2RomWord(rom, 256)).toEqual(0); + expect(v2RomWord(rom, 512)).toEqual(0); + expect(v2RomWord(rom, 1023)).toEqual(0); + expect( + v2StaticCommitment( + 1, + 0, + 2, + rom.hash(), + routes.hash(), + peers.hash(), + 3, + ), + ).toNotEqual(0); + expect(nextV2LegacyOutputCommitment(0, 1, 2, 3)).toNotEqual(0); + expect(nextV2AcknowledgementCommitment(0, 1)).toNotEqual(0); + expect(nextV2OutputCommitment(0, 1, 2, 3)).toNotEqual(0); + expect( + nextV2InputCommitment( + 0, + 1, + 2, + 3, + 4, + 0, + ), + ).toNotEqual(0); + expect(nextV2EpochCommitment(0, 1, 2, 3, 4)).toNotEqual(0); + expect(nextV2TraceCommitment(0, 1, 2)).toNotEqual(0); + expect( + nextV2BatchCommitment( + 1, + 2, + 0, + 1, + 1, + 0, + STOP_REQUEST_LIMIT_V2, + 3, + ), + ).toNotEqual(0); +} + +get fun `test v2 bounded structures cover every fixed slot and selector`() { + expect(v2StopReason(STATUS_HALTED_V2, false)).toEqual(STOP_HALTED_V2); + expect(v2StopReason(STATUS_FAULTED_V2, false)).toEqual(STOP_FAULTED_V2); + expect(v2StopReason(STATUS_WAITING_INPUT_V2, false)).toEqual(STOP_INPUT_UNAVAILABLE_V2); + expect(v2StopReason(STATUS_BACKPRESSURED_V2, false)).toEqual(STOP_OUTPUT_BACKPRESSURE_V2); + expect(v2StopReason(STATUS_RUNNING_V2, true)).toEqual(STOP_YIELDED_V2); + expect(v2StopReason(STATUS_RUNNING_V2, false)).toEqual(STOP_REQUEST_LIMIT_V2); + + val route0 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 1, + destinationPort: 0, + delay: 1, + maxRecordsPerEpoch: 4, + }.toCell(); + val invalidRoute = V2Route { + domain: 0, + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 99, + destinationPort: 15, + delay: 1, + maxRecordsPerEpoch: 4, + }.toCell(); + var routes = emptyV2RecordRoot(); + routes = v2AppendQueueRoot(routes, invalidRoute); + routes = v2AppendQueueRoot(routes, route0); + expect(v2FindRoute(routes, 0, 0)!.destinationCoreId).toEqual(1); + expect(v2FindRoute(routes, 7, 7)).toEqual(null); + + val validPeer = V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 7, + peerAddress: testing.treasury("v2-selector-peer").address, + }.toCell(); + val invalidPeer = V2Peer { + domain: 0, + coreId: 7, + peerAddress: testing.treasury("v2-selector-invalid-peer").address, + }.toCell(); + var peers = emptyV2RecordRoot(); + peers = v2AppendQueueRoot(peers, invalidPeer); + peers = v2AppendQueueRoot(peers, validPeer); + expect(v2FindPeer(peers, 7)!.coreId).toEqual(7); + + var fullRoutes = emptyV2RecordRoot(); + repeat (16) { + fullRoutes = v2AppendQueueRoot(fullRoutes, route0); + } + expect(v2RecordAt(fullRoutes, 0).hash()).toEqual(v2RecordAt(fullRoutes, 0).hash()); + expect(v2RecordAt(fullRoutes, 1).hash()).toEqual(v2RecordAt(fullRoutes, 1).hash()); + expect(v2RecordAt(fullRoutes, 2).hash()).toEqual(v2RecordAt(fullRoutes, 2).hash()); + expect(v2RecordAt(fullRoutes, 3).hash()).toEqual(v2RecordAt(fullRoutes, 3).hash()); + expect(v2RecordAt(fullRoutes, 4).hash()).toEqual(v2RecordAt(fullRoutes, 4).hash()); + expect(v2RecordAt(fullRoutes, 5).hash()).toEqual(v2RecordAt(fullRoutes, 5).hash()); + expect(v2RecordAt(fullRoutes, 6).hash()).toEqual(v2RecordAt(fullRoutes, 6).hash()); + expect(v2RecordAt(fullRoutes, 7).hash()).toEqual(v2RecordAt(fullRoutes, 7).hash()); + expect(v2RecordAt(fullRoutes, 8).hash()).toEqual(v2RecordAt(fullRoutes, 8).hash()); + expect(v2RecordAt(fullRoutes, 9).hash()).toEqual(v2RecordAt(fullRoutes, 9).hash()); + expect(v2RecordAt(fullRoutes, 10).hash()).toEqual(v2RecordAt(fullRoutes, 10).hash()); + expect(v2RecordAt(fullRoutes, 11).hash()).toEqual(v2RecordAt(fullRoutes, 11).hash()); + expect(v2RecordAt(fullRoutes, 12).hash()).toEqual(v2RecordAt(fullRoutes, 12).hash()); + expect(v2RecordAt(fullRoutes, 13).hash()).toEqual(v2RecordAt(fullRoutes, 13).hash()); + expect(v2RecordAt(fullRoutes, 14).hash()).toEqual(v2RecordAt(fullRoutes, 14).hash()); + expect(v2RecordAt(fullRoutes, 15).hash()).toEqual(v2RecordAt(fullRoutes, 15).hash()); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 0, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 1, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 2, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 3, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 4, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 5, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 6, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 7, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 8, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 9, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 10, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 11, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 12, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 13, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 14, route0))).toEqual(16); + expect(v2QueueCount(v2ReplaceRecordAt(fullRoutes, 15, route0))).toEqual(16); + + var memCore = makeV2StorageForTest(zeroV2RomRoot()).core.load(); + writeV2Register(mutate memCore, 0, 1); + writeV2Register(mutate memCore, 15, 15); + expect(readV2Register(memCore, 0)).toEqual(1); + expect(readV2Register(memCore, 15)).toEqual(15); + writeV2Ram(mutate memCore, 63, 5); + writeV2Ram(mutate memCore, 127, 6); + writeV2Ram(mutate memCore, 191, 7); + writeV2Ram(mutate memCore, 255, 8); + expect(readV2Ram(memCore, 63)).toEqual(5); + expect(readV2Ram(memCore, 127)).toEqual(6); + expect(readV2Ram(memCore, 191)).toEqual(7); + expect(readV2Ram(memCore, 255)).toEqual(8); + + val rom = romWithFirstWordV2(0xABCD); + val romRoot = rom.load(); + val level2 = romRoot.level20.load(); + expect(selectV2RomLevel1(level2, 0).leaf0.load().firstHalf).toEqual(0xABCD as uint256 << 240); + expect(selectV2RomLevel1(level2, 1).leaf0.load().firstHalf).toEqual(0); + expect(selectV2RomLevel1(level2, 2).leaf0.load().firstHalf).toEqual(0); + expect(selectV2RomLevel1(level2, 3).leaf0.load().firstHalf).toEqual(0); + expect(v2RomWord(rom, 15)).toEqual(0); + expect(v2RomWord(rom, 16)).toEqual(0); + expect(v2RomWord(rom, 31)).toEqual(0); + expect(v2RomWord(rom, 64)).toEqual(0); + expect(v2RomWord(rom, 192)).toEqual(0); + expect(v2RomWord(rom, 320)).toEqual(0); + expect(v2RomWord(rom, 448)).toEqual(0); + expect(v2RomWord(rom, 640)).toEqual(0); + expect(v2RomWord(rom, 768)).toEqual(0); + expect(v2RomWord(rom, 896)).toEqual(0); + expect(v2RomWord(rom, 1023)).toEqual(0); +} + +get fun `test v2 advance validation and storage guards`() { + val deployer = testing.treasury("v2-advance-validation"); + val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0))); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + val initialHash = core.getCoreStateHashV2(); + + val invalidAbi = core.sendV2Advance( + deployer.address, + 1, + 0, + 0, + 0, + initialHash, + 1, + 1, + { value: ton("0.1") }, + ); + expect(invalidAbi).toHaveFailedTx({ exitCode: ErrorsV2.InvalidVersion }); + val zeroSteps = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 2, + 0, + 0, + initialHash, + 0, + 1, + { value: ton("0.1") }, + ); + expect(zeroSteps).toHaveFailedTx({ exitCode: ErrorsV2.InvalidBody }); + val tooManySteps = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 3, + 0, + 0, + initialHash, + 2, + 1, + { value: ton("0.1") }, + ); + expect(tooManySteps).toHaveFailedTx({ exitCode: ErrorsV2.InvalidBody }); + val zeroOutputs = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 4, + 0, + 0, + initialHash, + 1, + 0, + { value: ton("0.1") }, + ); + expect(zeroOutputs).toHaveFailedTx({ exitCode: ErrorsV2.InvalidBody }); + val tooManyOutputs = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 5, + 0, + 0, + initialHash, + 1, + 17, + { value: ton("0.1") }, + ); + expect(tooManyOutputs).toHaveFailedTx({ exitCode: ErrorsV2.InvalidBody }); + val insufficient = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 6, + 0, + 0, + initialHash, + 1, + 1, + { value: ton("0.001") }, + ); + expect(insufficient).toHaveFailedTx({ exitCode: ErrorsV2.InsufficientValue }); + val invalidRun = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 7, + 1, + 0, + initialHash, + 1, + 1, + { value: ton("0.1") }, + ); + expect(invalidRun).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRun }); + val stale = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 8, + 0, + 1, + initialHash, + 1, + 1, + { value: ton("0.1") }, + ); + expect(stale).toHaveFailedTx({ exitCode: ErrorsV2.StaleAdvance }); + val wrongHash = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 9, + 0, + 0, + 1, + 1, + 1, + { value: ton("0.1") }, + ); + expect(wrongHash).toHaveFailedTx({ exitCode: ErrorsV2.StateHashMismatch }); + + var invalidMagicStorage = makeV2StorageForTest(zeroV2RomRoot()); + invalidMagicStorage.magic = 0; + val invalidMagic = TonolithCoreV2.fromStorage(invalidMagicStorage); + expect(invalidMagic.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidMagic.address, + }); + expect( + invalidMagic.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 10, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidSchemaStorage = makeV2StorageForTest(zeroV2RomRoot()); + invalidSchemaStorage.schemaVersion = 1; + val invalidSchema = TonolithCoreV2.fromStorage(invalidSchemaStorage); + expect(invalidSchema.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidSchema.address, + }); + expect( + invalidSchema.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 11, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidIsaStorage = makeV2StorageForTest(zeroV2RomRoot()); + invalidIsaStorage.isaVersion = 1; + val invalidIsa = TonolithCoreV2.fromStorage(invalidIsaStorage); + expect(invalidIsa.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidIsa.address, + }); + expect( + invalidIsa.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 12, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidAbiStorage = makeV2StorageForTest(zeroV2RomRoot()); + invalidAbiStorage.abiVersion = 1; + val invalidAbiStorageCore = TonolithCoreV2.fromStorage(invalidAbiStorage); + expect( + invalidAbiStorageCore.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidAbiStorageCore.address }); + expect( + invalidAbiStorageCore.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 13, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidProtocolStorage = makeV2StorageForTest(zeroV2RomRoot()); + invalidProtocolStorage.protocolVersion = 1; + val invalidProtocol = TonolithCoreV2.fromStorage(invalidProtocolStorage); + expect(invalidProtocol.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidProtocol.address, + }); + expect( + invalidProtocol.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 14, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidDomainStorage = makeV2StorageForTest(zeroV2RomRoot()); + var invalidDomainCore = invalidDomainStorage.core.load(); + invalidDomainCore.domain = 0; + invalidDomainStorage.core = invalidDomainCore.toCell(); + val invalidDomain = TonolithCoreV2.fromStorage(invalidDomainStorage); + expect(invalidDomain.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidDomain.address, + }); + expect( + invalidDomain.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 15, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var invalidCommitmentStorage = makeV2StorageForTest(zeroV2RomRoot()); + var invalidCommitmentCore = invalidCommitmentStorage.core.load(); + invalidCommitmentCore.staticCommitment = 1; + invalidCommitmentStorage.core = invalidCommitmentCore.toCell(); + val invalidCommitment = TonolithCoreV2.fromStorage(invalidCommitmentStorage); + expect( + invalidCommitment.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidCommitment.address }); + expect( + invalidCommitment.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 16, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStaticCommitment }); + + var invalidStatusStorage = makeV2StorageForTest(zeroV2RomRoot()); + var invalidStatusCore = invalidStatusStorage.core.load(); + invalidStatusCore.status = STATUS_FAULTED_V2; + invalidStatusStorage.core = invalidStatusCore.toCell(); + val invalidStatus = TonolithCoreV2.fromStorage(invalidStatusStorage); + expect(invalidStatus.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidStatus.address, + }); + expect( + invalidStatus.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 17, + 0, + 0, + 0, + 1, + 1, + { value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStatus }); +} + +get fun `test v2 receiver selectors topup and external rejection`() { + val deployer = testing.treasury("v2-receiver-selectors"); + val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(zeroV2RomRoot())); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + expect( + core.sendAny(deployer.address, createEmptyCell(), { value: ton("0.1") }), + ).toHaveSuccessfulTx(); + val short = core.sendAny( + deployer.address, + beginCell().storeUint(1, 8).endCell(), + { value: ton("0.1") }, + ); + expect(short).toHaveFailedTx({ exitCode: ErrorsV2.InvalidBody }); + val unknown = core.sendAny( + deployer.address, + beginCell().storeUint(0x99999999, 32).endCell(), + { value: ton("0.1") }, + ); + expect(unknown).toHaveFailedTx({ exitCode: ErrorsV2.UnknownMessage }); + val topup = core.sendV2TopUp( + deployer.address, + ABI_VERSION_V2, + 1, + 0, + { value: ton("0.1") }, + ); + expect(topup).toHaveSuccessfulTx(); + val invalidTopupVersion = core.sendV2TopUp( + deployer.address, + 1, + 2, + 0, + { value: ton("0.1") }, + ); + expect(invalidTopupVersion).toHaveFailedTx({ exitCode: ErrorsV2.InvalidVersion }); + val invalidTopupRun = core.sendV2TopUp( + deployer.address, + ABI_VERSION_V2, + 3, + 1, + { value: ton("0.1") }, + ); + expect(invalidTopupRun).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRun }); + expect(core.sendAnyExternal(createEmptyCell())).toBeNull(); +} + +get fun `test v2 inbox insertion is canonical under out of order delivery`() { + val deployer = testing.treasury("v2-inbox-order"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 0, + destinationCoreId: 0, + destinationPort: 2, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var storage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var initialCore = storage.core.load(); + initialCore.epoch = 1; + initialCore.status = STATUS_WAITING_INPUT_V2; + storage.core = initialCore.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + val sequenceOne = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 1, + 8, + 1, + ); + val sequenceZero = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + ); + expect( + sendDeliverInputV2ForTest( + core, + deployer.address, + 1, + 0, + sequenceOne, + 1, + 0, + 0, + 1, + 0, + 2, + 1, + 8, + 1, + 0, + ), + ).toHaveSuccessfulTx(); + expect( + sendDeliverInputV2ForTest( + core, + deployer.address, + 2, + 0, + sequenceZero, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveSuccessfulTx(); + expect(core.getInboxCountV2()).toEqual(2); + expect( + advanceV2OnceForTest(core, deployer.address), + ).toHaveSuccessfulTx(); + expect(core.getAccumulatorV2()).toEqual(7); +} + +get fun `test v2 delivery validation rejects malformed routes epochs and replays`() { + val deployer = testing.treasury("v2-delivery-validation"); + val route0 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 0, + destinationCoreId: 0, + destinationPort: 2, + delay: 1, + maxRecordsPerEpoch: 4, + }; + val route1 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 1, + destinationCoreId: 0, + destinationPort: 3, + delay: 1, + maxRecordsPerEpoch: 4, + }; + val route2 = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 1, + sourcePort: 2, + destinationCoreId: 2, + destinationPort: 4, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var routes = emptyV2RecordRoot(); + routes = v2AppendQueueRoot(routes, route0.toCell()); + routes = v2AppendQueueRoot(routes, route1.toCell()); + routes = v2AppendQueueRoot(routes, route2.toCell()); + var storage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + routes, + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var initialCore = storage.core.load(); + initialCore.epoch = 1; + initialCore.status = STATUS_WAITING_INPUT_V2; + storage.core = initialCore.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + val validOutputId = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + ); + + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + 1, + ton("0.1"), + 1, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidVersion }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.001"), + 2, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InsufficientValue }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 3, + 1, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRun }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 4, + 0, + validOutputId, + 1, + 1, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 5, + 0, + validOutputId, + 2, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); + expect( + sendDeliverInputV2WithConfig( + core, + testing.treasury("v2-delivery-wrong-sender").address, + ABI_VERSION_V2, + ton("0.1"), + 6, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 7, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 3, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRoute }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 8, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 2, + 0, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRoute }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 9, + 0, + validOutputId, + 1, + 0, + 0, + 0, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidEpoch }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 10, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 4, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidSequence }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 11, + 0, + validOutputId, + 1, + 0, + 2, + 3, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidEpoch }); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 12, + 0, + 1, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRoute }); + val first = sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 13, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ); + expect(first).toHaveSuccessfulTx(); + val conflictingOutputId = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 8, + 1, + ); + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 14, + 0, + conflictingOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 8, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.ConflictingReplay }); } From 22d6e194dfbf637fdba1b12fa33cb88dc4bbc5b5 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:47:44 +0100 Subject: [PATCH 40/67] test: cover v2 execution edge paths --- tests/tonolith_v2.test.tolk | 214 ++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index 56977dc..6214cda 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -5,6 +5,7 @@ import "@acton/testing/expect" import "@contracts/TonolithCoreV2" import "@wrappers/TonolithCoreV2.gen" import "@contracts/v2/commitments" +import "@contracts/v2/execution" import "@contracts/v2/memory" import "@contracts/v2/peers" import "@contracts/v2/queues" @@ -84,6 +85,23 @@ fun romWithFirstWordV2(word: uint16): Cell { ); } +fun outputRecordV2ForTest(sequence: uint8): cell { + return V2OutputRecord { + domain: DOMAIN_OUTPUT_V2, + outputId: (sequence as uint256) + 100, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence, + value: 1, + sourceInstructionCount: 1, + sourceStateHash: 0, + }.toCell(); +} + fun sendAdvanceV2ForTest( core: TonolithCoreV2, from: address, @@ -1146,6 +1164,202 @@ get fun `test v2 bounded structures cover every fixed slot and selector`() { expect(v2RomWord(rom, 1023)).toEqual(0); } +get fun `test v2 execution edge paths preserve bounded progress`() { + val deployer = testing.treasury("v2-execution-edge-paths"); + + var waitingStorage = makeV2StorageForTest(romWithFirstWordV2(0)); + var waitingCore = waitingStorage.core.load(); + waitingCore.status = STATUS_WAITING_INPUT_V2; + waitingStorage.core = waitingCore.toCell(); + val waiting = TonolithCoreV2.fromStorage(waitingStorage); + expect(waiting.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: waiting.address, + }); + expect(advanceV2OnceForTest(waiting, deployer.address)).toHaveSuccessfulTx(); + expect(waiting.getStatusV2()).toEqual(STATUS_RUNNING_V2); + + var backpressureStorage = makeV2StorageForTest(romWithFirstWordV2(0)); + var backpressureCore = backpressureStorage.core.load(); + backpressureCore.status = STATUS_BACKPRESSURED_V2; + backpressureStorage.core = backpressureCore.toCell(); + val backpressure = TonolithCoreV2.fromStorage(backpressureStorage); + expect( + backpressure.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: backpressure.address }); + expect( + advanceV2OnceForTest(backpressure, deployer.address), + ).toHaveSuccessfulTx(); + expect(backpressure.getStatusV2()).toEqual(STATUS_RUNNING_V2); + + var jzStorage = makeV2StorageForTest(romWithFirstWordV2(0xD123)); + var jzCore = jzStorage.core.load(); + jzCore.flags = FLAG_ZERO_V2; + jzStorage.core = jzCore.toCell(); + val jzTakenWithFlag = TonolithCoreV2.fromStorage(jzStorage); + expect( + jzTakenWithFlag.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: jzTakenWithFlag.address }); + expect( + advanceV2OnceForTest(jzTakenWithFlag, deployer.address), + ).toHaveSuccessfulTx(); + expect(jzTakenWithFlag.getPcV2()).toEqual(0x123); + + val jzNotTaken = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xD123))); + expect(jzNotTaken.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: jzNotTaken.address, + }); + expect(advanceV2OnceForTest(jzNotTaken, deployer.address)).toHaveSuccessfulTx(); + expect(jzNotTaken.getPcV2()).toEqual(1); + + var jcStorage = makeV2StorageForTest(romWithFirstWordV2(0xE123)); + var jcCore = jcStorage.core.load(); + jcCore.flags = FLAG_CARRY_V2; + jcStorage.core = jcCore.toCell(); + val jcTaken = TonolithCoreV2.fromStorage(jcStorage); + expect(jcTaken.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: jcTaken.address, + }); + expect(advanceV2OnceForTest(jcTaken, deployer.address)).toHaveSuccessfulTx(); + expect(jcTaken.getPcV2()).toEqual(0x123); + + var adcStorage = makeV2StorageForTest(romWithFirstWordV2(0x5000)); + var adcCore = adcStorage.core.load(); + adcCore.flags = FLAG_CARRY_V2; + adcStorage.core = adcCore.toCell(); + val adc = TonolithCoreV2.fromStorage(adcStorage); + expect(adc.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: adc.address, + }); + expect(advanceV2OnceForTest(adc, deployer.address)).toHaveSuccessfulTx(); + expect(adc.getAccumulatorV2()).toEqual(1); + + val invalidNop = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0001))); + expect(invalidNop.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidNop.address, + }); + expect(advanceV2OnceForTest(invalidNop, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidLdi = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x10FF))); + expect(invalidLdi.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidLdi.address, + }); + expect(advanceV2OnceForTest(invalidLdi, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidRegister = TonolithCoreV2.fromStorage( + makeV2StorageForTest(romWithFirstWordV2(0x2FF)), + ); + expect(invalidRegister.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidRegister.address, + }); + expect(advanceV2OnceForTest(invalidRegister, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidRam = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xA100))); + expect(invalidRam.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidRam.address, + }); + expect(advanceV2OnceForTest(invalidRam, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidJump = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xC400))); + expect(invalidJump.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidJump.address, + }); + expect(advanceV2OnceForTest(invalidJump, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidSys = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0xF00D))); + expect(invalidSys.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidSys.address, + }); + expect(advanceV2OnceForTest(invalidSys, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + + val missingRoute = TonolithCoreV2.fromStorage( + makeV2StorageForNetwork( + romWithFirstWordV2(0xF03A), + emptyV2RecordRoot(), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ), + ); + expect(missingRoute.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: missingRoute.address, + }); + expect(advanceV2OnceForTest(missingRoute, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidRoute, + }); + + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 3, + destinationCoreId: 1, + destinationPort: 4, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var sequenceStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF03A), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var sequenceCore = sequenceStorage.core.load(); + sequenceCore.nextOutputSequence = (255 as uint128) << 24; + sequenceStorage.core = sequenceCore.toCell(); + val sequenceLimit = TonolithCoreV2.fromStorage(sequenceStorage); + expect(sequenceLimit.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: sequenceLimit.address, + }); + expect(advanceV2OnceForTest(sequenceLimit, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidSequence, + }); + + var fullOutboxStorage = makeV2StorageForTest(romWithFirstWordV2(0xF03A)); + var fullOutboxCore = fullOutboxStorage.core.load(); + var fullOutboxQueues = fullOutboxCore.queues.load(); + repeat (16) { + fullOutboxQueues.outboxRoot = v2AppendQueueRoot( + fullOutboxQueues.outboxRoot, + outputRecordV2ForTest(0), + ); + } + fullOutboxCore.queues = fullOutboxQueues.toCell(); + fullOutboxCore.pc = 0; + fullOutboxCore.instructionCount = 0; + fullOutboxStorage.core = fullOutboxCore.toCell(); + var directFullOutboxCore = fullOutboxCore; + v2ExecuteSys( + mutate directFullOutboxCore, + 0x3A, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + expect(directFullOutboxCore.status).toEqual(STATUS_BACKPRESSURED_V2); + val fullOutbox = TonolithCoreV2.fromStorage(fullOutboxStorage); + expect(fullOutbox.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: fullOutbox.address, + }); + expect(fullOutbox.getOutboxCountV2()).toEqual(16); + expect(advanceV2OnceForTest(fullOutbox, deployer.address)).toHaveSuccessfulTx(); + expect(fullOutbox.getStatusV2()).toEqual(STATUS_BACKPRESSURED_V2); + expect(fullOutbox.getPcV2()).toEqual(0); + expect(fullOutbox.getInstructionCountV2()).toEqual(0); + expect(fullOutbox.getOutboxCountV2()).toEqual(16); +} + get fun `test v2 advance validation and storage guards`() { val deployer = testing.treasury("v2-advance-validation"); val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0))); From 4bbed251b54cf07cafbc56a9637fa70bddf457ae Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 22:49:16 +0100 Subject: [PATCH 41/67] test: exercise canonical v2 queue ordering --- tests/tonolith_v2.test.tolk | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index 6214cda..d32beaf 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -102,6 +102,24 @@ fun outputRecordV2ForTest(sequence: uint8): cell { }.toCell(); } +fun inputRecordV2ForTest(epoch: uint64, port: uint4, sequence: uint8, outputId: uint256): cell { + return V2InputRecord { + domain: DOMAIN_INPUT_V2, + outputId, + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: epoch - 1, + destinationEpoch: epoch, + sourcePort: 0, + destinationPort: port, + sequence, + value: 1, + sourceInstructionCount: 1, + sourceStateHash: 2, + consumed: 0, + }.toCell(); +} + fun sendAdvanceV2ForTest( core: TonolithCoreV2, from: address, @@ -970,6 +988,41 @@ get fun `test v2 bounded memory ROM queues routes peers and commitments`() { expect(v2FindInput(inputs, 1, 2)!.value).toEqual(9); expect(v2FindInputSlot(inputs, 1, 2, 0).found).toEqual(1); expect(v2FindRecordById(inputs, 1).found).toEqual(1); + + var orderedInputs = emptyV2RecordRoot(); + orderedInputs = v2InsertInputRecord( + orderedInputs, + inputRecordV2ForTest(1, 2, 0, 10), + ); + orderedInputs = v2InsertInputRecord( + orderedInputs, + inputRecordV2ForTest(1, 2, 2, 12), + ); + orderedInputs = v2InsertInputRecord( + orderedInputs, + inputRecordV2ForTest(1, 2, 1, 11), + ); + orderedInputs = v2InsertInputRecord( + orderedInputs, + inputRecordV2ForTest(0, 9, 0, 9), + ); + orderedInputs = v2InsertInputRecord( + orderedInputs, + inputRecordV2ForTest(1, 1, 0, 8), + ); + expect(v2InputAt(orderedInputs, 0).outputId).toEqual(9); + expect(v2InputAt(orderedInputs, 1).outputId).toEqual(8); + expect(v2InputAt(orderedInputs, 2).outputId).toEqual(10); + expect(v2InputAt(orderedInputs, 3).outputId).toEqual(11); + expect(v2InputAt(orderedInputs, 4).outputId).toEqual(12); + + var onePage = emptyV2RecordRoot(); + onePage = v2AppendQueueRoot(onePage, route0); + expect(v2QueueCount(v2ReplaceRecordAt(onePage, 0, route0))).toEqual(1); + val twoPage = v2AppendQueueRoot(onePage, route0); + expect(v2QueueCount(v2ReplaceRecordAt(twoPage, 1, route0))).toEqual(2); + val threePage = v2AppendQueueRoot(twoPage, route0); + expect(v2QueueCount(v2ReplaceRecordAt(threePage, 2, route0))).toEqual(3); val consumed = V2InputRecord { domain: DOMAIN_INPUT_V2, outputId: 1, From 064a84732ba9c777f29fc21dfdfa5c421eae8fe9 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:01:05 +0100 Subject: [PATCH 42/67] test: add measured v2 batching execution paths --- contracts/TonolithCoreV2.tolk | 3 +- tests/v2_batch_contract.test.tolk | 238 ++++++++++++++++++++++++++++++ 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 tests/v2_batch_contract.test.tolk diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index bfc8ac6..c4b7f73 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -399,13 +399,14 @@ fun processAdvanceV2(sender: address, value: coins, msg: V2Advance) { while (keepRunning && stepsExecuted < msg.maxInstructions && core.status == STATUS_RUNNING_V2) { val word = v2RomWord(storage.rom, core.pc); val beforeInstructionCount = core.instructionCount; + val instructionStateHash = core.toCell().hash(); val _legacyOutput = v2ExecuteInstruction( mutate core, word, identity.runId, identity.coreId, storage.network.load().routes, - previousStateHash, + instructionStateHash, ); if (core.instructionCount == beforeInstructionCount) { keepRunning = false; diff --git a/tests/v2_batch_contract.test.tolk b/tests/v2_batch_contract.test.tolk new file mode 100644 index 0000000..aaf02ac --- /dev/null +++ b/tests/v2_batch_contract.test.tolk @@ -0,0 +1,238 @@ +import "@acton/emulation/network" +import "@acton/emulation/testing" +import "@acton/testing/expect" + +import "@wrappers/TonolithCoreV2.gen" +import "@contracts/v2/memory" +import "@contracts/v2/queues" +import "@contracts/v2/rom" +import "@contracts/v2/types" + +fun batchStorage(rom: Cell, routes: cell): V2Storage { + val staticRoots = V2StaticRoots { + romRoot: rom.hash(), + routeRoot: routes.hash(), + limitsHash: 0, + peers: V2PeerRoots { peerRoot: emptyV2RecordRoot().hash() }.toCell(), + }.toCell(); + val identity = V2StaticData { + coreId: 0, + runId: 0, + staticCommitment: 0, + programId: 0, + roots: staticRoots, + }.toCell(); + val commitments = V2CoreCommitments { + outputCommitment: 0, + inputCommitment: 0, + epochHistoryCommitment: 0, + }.toCell(); + val core = V2CoreState { + domain: DOMAIN_STATE_V2, + staticCommitment: 0, + status: STATUS_RUNNING_V2, + faultCode: 0, + pc: 0, + accumulator: 0, + flags: 0, + epoch: 0, + instructionCount: 0, + advanceCount: 0, + acceptedMessageCount: 0, + outputCount: 0, + inputCount: 0, + nextOutputSequence: 0, + commitments, + memory: zeroV2Memory(), + queues: emptyV2Queues(), + }.toCell(); + return V2Storage { + magic: TONOLITH_MAGIC_V2, + schemaVersion: SCHEMA_VERSION_V2, + isaVersion: ISA_VERSION_V2, + abiVersion: ABI_VERSION_V2, + protocolVersion: PROTOCOL_VERSION_V2, + identity, + core, + rom, + network: V2NetworkData { + routes, + peers: emptyV2RecordRoot(), + }.toCell(), + }; +} + +fun batchRom(word: uint16): Cell { + return v2RomWithFirstLeaf( + V2RomLeaf { + firstHalf: (word as uint256) << 240, + secondHalf: 0, + }.toCell(), + ); +} + +fun batchAdvance(core: TonolithCoreV2, from: address, maxOutputs: uint8 = MAX_OUTPUT_RECORDS_V2) { + val result = core.sendV2Advance( + from, + ABI_VERSION_V2, + 0, + core.getRunIdV2(), + core.getAdvanceCountV2(), + core.getCoreStateHashV2(), + MAX_STEPS_PER_ADVANCE_V2, + maxOutputs, + { bounce: false, value: ton("0.1") }, + ); + expect(result).toHaveSuccessfulTx(); +} + +fun batchOutput(sequence: uint8): cell { + return V2OutputRecord { + domain: DOMAIN_OUTPUT_V2, + outputId: (sequence as uint256) + 1, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence, + value: 0, + sourceInstructionCount: 0, + sourceStateHash: 0, + }.toCell(); +} + +get fun `test v2 bounded advance executes the full configured nop batch`() { + val deployer = testing.treasury("v2-batch-nop"); + val core = TonolithCoreV2.fromStorage(batchStorage(batchRom(0), emptyV2RecordRoot())); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + batchAdvance(core, deployer.address); + + expect(core.getInstructionCountV2()).toEqual(MAX_STEPS_PER_ADVANCE_V2); + expect(core.getAdvanceCountV2()).toEqual(1); + expect(core.getAcceptedMessageCountV2()).toEqual(1); + expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); +} + +get fun `test v2 bounded advance stops at a legacy output event`() { + val deployer = testing.treasury("v2-batch-legacy-output"); + val core = TonolithCoreV2.fromStorage(batchStorage(batchRom(0xF001), emptyV2RecordRoot())); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + val result = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 1, + 0, + 0, + core.getCoreStateHashV2(), + MAX_STEPS_PER_ADVANCE_V2, + 1, + { bounce: false, value: ton("0.1") }, + ); + expect(result).toHaveSuccessfulTx(); + expect(core.getInstructionCountV2()).toEqual(1); + expect(core.getOutputCountV2()).toEqual(1); +} + +get fun `test v2 bounded advance stops at a routed output event`() { + val deployer = testing.treasury("v2-batch-routed-output"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 3, + destinationCoreId: 1, + destinationPort: 4, + delay: 1, + maxRecordsPerEpoch: 4, + }; + val core = TonolithCoreV2.fromStorage( + batchStorage(batchRom(0xF03A), v2AppendQueueRoot(emptyV2RecordRoot(), route.toCell())), + ); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + val result = core.sendV2Advance( + deployer.address, + ABI_VERSION_V2, + 2, + 0, + 0, + core.getCoreStateHashV2(), + MAX_STEPS_PER_ADVANCE_V2, + 1, + { bounce: false, value: ton("0.1") }, + ); + expect(result).toHaveSuccessfulTx(); + expect(result).toEmitExternalMessage(); + expect(core.getInstructionCountV2()).toEqual(1); + expect(core.getOutboxCountV2()).toEqual(1); +} + +get fun `test v2 bounded advance stops at yield and trap`() { + val deployer = testing.treasury("v2-batch-stop-controls"); + val yielded = TonolithCoreV2.fromStorage(batchStorage(batchRom(0xF00B), emptyV2RecordRoot())); + expect(yielded.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: yielded.address, + }); + batchAdvance(yielded, deployer.address); + expect(yielded.getInstructionCountV2()).toEqual(1); + expect(yielded.getEpochV2()).toEqual(1); + expect(yielded.getStatusV2()).toEqual(STATUS_RUNNING_V2); + + val trapped = TonolithCoreV2.fromStorage(batchStorage(batchRom(0xF1AC), emptyV2RecordRoot())); + expect(trapped.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: trapped.address, + }); + batchAdvance(trapped, deployer.address); + expect(trapped.getInstructionCountV2()).toEqual(1); + expect(trapped.getStatusV2()).toEqual(STATUS_FAULTED_V2); +} + +get fun `test v2 bounded advance records input wait without executing`() { + val deployer = testing.treasury("v2-batch-input-wait"); + var storage = batchStorage(batchRom(0xF029), emptyV2RecordRoot()); + var initialCore = storage.core.load(); + initialCore.epoch = 1; + storage.core = initialCore.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + batchAdvance(core, deployer.address); + + expect(core.getInstructionCountV2()).toEqual(0); + expect(core.getAdvanceCountV2()).toEqual(1); + expect(core.getStatusV2()).toEqual(STATUS_WAITING_INPUT_V2); +} + +get fun `test v2 bounded advance records outbox backpressure without executing`() { + val deployer = testing.treasury("v2-batch-output-backpressure"); + var storage = batchStorage(batchRom(0xF03A), emptyV2RecordRoot()); + var initialCore = storage.core.load(); + var queues = initialCore.queues.load(); + repeat (16) { + queues.outboxRoot = v2AppendQueueRoot(queues.outboxRoot, batchOutput(0)); + } + initialCore.queues = queues.toCell(); + storage.core = initialCore.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + batchAdvance(core, deployer.address); + + expect(core.getInstructionCountV2()).toEqual(0); + expect(core.getAdvanceCountV2()).toEqual(1); + expect(core.getStatusV2()).toEqual(STATUS_BACKPRESSURED_V2); + expect(core.getOutboxCountV2()).toEqual(16); +} From fbb469f1a1b01b8fc6c0727699932c4e97755027 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:03:50 +0100 Subject: [PATCH 43/67] test: make zero epoch fixtures underflow safe --- tests/tonolith_v2.test.tolk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index d32beaf..d620404 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -108,7 +108,7 @@ fun inputRecordV2ForTest(epoch: uint64, port: uint4, sequence: uint8, outputId: outputId, sourceCoreId: 1, destinationCoreId: 0, - sourceEpoch: epoch - 1, + sourceEpoch: epoch == 0 ? 0 : epoch - 1, destinationEpoch: epoch, sourcePort: 0, destinationPort: port, @@ -1452,7 +1452,7 @@ get fun `test v2 advance validation and storage guards`() { 0, 0, initialHash, - 2, + MAX_STEPS_PER_ADVANCE_V2 + 1, 1, { value: ton("0.1") }, ); From 24ae139baa77f0542ecc4ec088702c41344937e8 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:07:04 +0100 Subject: [PATCH 44/67] test: cover v2 batched ISA families --- tests/v2_batch_contract.test.tolk | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/v2_batch_contract.test.tolk b/tests/v2_batch_contract.test.tolk index aaf02ac..595f897 100644 --- a/tests/v2_batch_contract.test.tolk +++ b/tests/v2_batch_contract.test.tolk @@ -71,6 +71,29 @@ fun batchRom(word: uint16): Cell { ); } +fun batchIsaRom(): Cell { + val firstHalf = ((0x0000 as uint256) << 240) | ((0x1001 as uint256) << 224) | + ((0x2000 as uint256) << 208) | + ((0x3000 as uint256) << 192) | + ((0x4000 as uint256) << 176) | + ((0x5000 as uint256) << 160) | + ((0x6000 as uint256) << 144) | + ((0x7000 as uint256) << 128) | + ((0x8000 as uint256) << 112) | + ((0x9000 as uint256) << 96) | + ((0xA000 as uint256) << 80) | + ((0xB000 as uint256) << 64) | + ((0x1001 as uint256) << 48) | + ((0xD00F as uint256) << 32) | + ((0xE00F as uint256) << 16); + return v2RomWithFirstLeaf( + V2RomLeaf { + firstHalf, + secondHalf: 0, + }.toCell(), + ); +} + fun batchAdvance(core: TonolithCoreV2, from: address, maxOutputs: uint8 = MAX_OUTPUT_RECORDS_V2) { val result = core.sendV2Advance( from, @@ -118,6 +141,21 @@ get fun `test v2 bounded advance executes the full configured nop batch`() { expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); } +get fun `test v2 bounded advance traverses every nonblocking ISA family`() { + val deployer = testing.treasury("v2-batch-isa-families"); + val core = TonolithCoreV2.fromStorage(batchStorage(batchIsaRom(), emptyV2RecordRoot())); + expect(core.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: core.address, + }); + + batchAdvance(core, deployer.address); + + val expectedSteps = MAX_STEPS_PER_ADVANCE_V2 < 16 ? MAX_STEPS_PER_ADVANCE_V2 : (16 as uint16); + expect(core.getInstructionCountV2()).toEqual(expectedSteps); + expect(core.getOutputCountV2()).toEqual(0); + expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); +} + get fun `test v2 bounded advance stops at a legacy output event`() { val deployer = testing.treasury("v2-batch-legacy-output"); val core = TonolithCoreV2.fromStorage(batchStorage(batchRom(0xF001), emptyV2RecordRoot())); From 88f5cecf7b2461aedf5b56a4e7e253a8bdbf8cd3 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:08:05 +0100 Subject: [PATCH 45/67] test: assert complete v2 batch progress --- tests/v2_batch_contract.test.tolk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/v2_batch_contract.test.tolk b/tests/v2_batch_contract.test.tolk index 595f897..dba68ec 100644 --- a/tests/v2_batch_contract.test.tolk +++ b/tests/v2_batch_contract.test.tolk @@ -150,8 +150,7 @@ get fun `test v2 bounded advance traverses every nonblocking ISA family`() { batchAdvance(core, deployer.address); - val expectedSteps = MAX_STEPS_PER_ADVANCE_V2 < 16 ? MAX_STEPS_PER_ADVANCE_V2 : (16 as uint16); - expect(core.getInstructionCountV2()).toEqual(expectedSteps); + expect(core.getInstructionCountV2()).toEqual(MAX_STEPS_PER_ADVANCE_V2); expect(core.getOutputCountV2()).toEqual(0); expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); } From 0b271e2d6d6ce0e0c864e46637bc48bd610d1be6 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:12:19 +0100 Subject: [PATCH 46/67] bench: record v2 batch candidate measurements --- benchmarks/v2/batch-selection.json | 117 +++++++++++++++++++++++++---- tests/v2/batch-selection.test.ts | 22 +++++- 2 files changed, 121 insertions(+), 18 deletions(-) diff --git a/benchmarks/v2/batch-selection.json b/benchmarks/v2/batch-selection.json index 5c7d9ce..ac9ab56 100644 --- a/benchmarks/v2/batch-selection.json +++ b/benchmarks/v2/batch-selection.json @@ -1,23 +1,112 @@ { "schema": "tonolith-batch-selection-v2", - "network": "local-acton", - "measurementCommand": "acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42", + "measurement": { + "command": "acton test tests/v2_batch_contract.test.tolk --snapshot gas.json --fuzz-seed 42", + "environment": "clean temporary Acton checkout", + "sourceCommit": "88f5cec", + "paths": [ + "full nonblocking ISA sequence", + "legacy OUT event", + "routed OUTP event", + "YIELD and TRAP stops", + "IN wait", + "full outbox backpressure" + ] + }, "hardCeilingGas": 400000, - "observedNetworkGasReference": 44381, - "safetyRule": "selected worst-case must remain below both 400000 gas and 40% of the observed network reference after protocol-path measurement", + "testnetGasReferenceGas": null, + "safetyRule": "A production batch must remain below 400000 gas and below 40% of a measured testnet worst-case reference. The second comparison is intentionally unresolved until testnet measurement.", "selectedMaxStepsPerAdvance": 1, - "selectionStatus": "conservative-single-step", + "selectionStatus": "conservative-single-step-until-testnet-gas-baseline", "candidates": [ - { "maxSteps": 1, "contractAccepted": true, "worstCaseGas": 44381, "networkGasMeasured": false, "headroomToHardCeiling": 355619, "decision": "selected" }, - { "maxSteps": 2, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, - { "maxSteps": 4, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, - { "maxSteps": 8, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, - { "maxSteps": 16, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, - { "maxSteps": 32, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" }, - { "maxSteps": 64, "contractAccepted": false, "worstCaseGas": null, "networkGasMeasured": false, "headroomToHardCeiling": null, "decision": "deferred-until-batched-core-is-implemented" } + { + "maxSteps": 1, + "contractAccepted": true, + "worstCaseGas": 47997, + "worstCaseTraceGas": 48306, + "averageGas": 32141, + "worstCaseLocalFeeNano": 3559225, + "worstCasePath": "routed OUTP event", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "selected-conservative" + }, + { + "maxSteps": 2, + "contractAccepted": true, + "worstCaseGas": 47997, + "worstCaseTraceGas": 48306, + "averageGas": 34672, + "worstCaseLocalFeeNano": 3559225, + "worstCasePath": "routed OUTP event", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "deferred-testnet-gas-reference" + }, + { + "maxSteps": 4, + "contractAccepted": true, + "worstCaseGas": 61924, + "worstCaseTraceGas": 62233, + "averageGas": 40163, + "worstCaseLocalFeeNano": 4347958, + "worstCasePath": "full nonblocking ISA sequence", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "deferred-testnet-gas-reference" + }, + { + "maxSteps": 8, + "contractAccepted": true, + "worstCaseGas": 113797, + "worstCaseTraceGas": 114106, + "averageGas": 51519, + "worstCaseLocalFeeNano": 7806158, + "worstCasePath": "full nonblocking ISA sequence", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "deferred-testnet-gas-reference" + }, + { + "maxSteps": 16, + "contractAccepted": true, + "worstCaseGas": 209075, + "worstCaseTraceGas": 209384, + "averageGas": 73172, + "worstCaseLocalFeeNano": 14158025, + "worstCasePath": "full nonblocking ISA sequence", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "deferred-testnet-gas-reference" + }, + { + "maxSteps": 32, + "contractAccepted": true, + "worstCaseGas": 364979, + "worstCaseTraceGas": 365288, + "averageGas": 112148, + "worstCaseLocalFeeNano": 24551626, + "worstCasePath": "full nonblocking ISA sequence", + "hardCeilingPass": true, + "testnet40PercentPass": null, + "decision": "deferred-testnet-gas-reference" + }, + { + "maxSteps": 64, + "contractAccepted": true, + "worstCaseGas": 677438, + "worstCaseTraceGas": 677747, + "averageGas": 190254, + "worstCaseLocalFeeNano": 45382228, + "worstCasePath": "full nonblocking ISA sequence", + "hardCeilingPass": false, + "testnet40PercentPass": null, + "decision": "rejected-hard-ceiling" + } ], "unresolved": [ - "Acton currently accepts only one v2 instruction per Advance; candidates 2 through 64 require a separately proven batched loop.", - "The 40% rule needs a confirmed testnet gas baseline, not a local emulator estimate." + "No candidate can pass the 40% comparison until a real testnet worst-case reference is measured.", + "Acton snapshots expose gas and fees but not a stable action/cell/message-size accounting table; those release fields remain external measurement gates.", + "The selected implementation remains one instruction per Advance until the testnet baseline and all fee, storage, action, and message-size gates are satisfied." ] } diff --git a/tests/v2/batch-selection.test.ts b/tests/v2/batch-selection.test.ts index 5105f7c..b44c741 100644 --- a/tests/v2/batch-selection.test.ts +++ b/tests/v2/batch-selection.test.ts @@ -2,13 +2,27 @@ import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import test from "node:test"; -test("v2 batch selection records every candidate and selects only measured single-step execution", async () => { +test("v2 batch selection records every measured candidate and keeps deployment conservative", async () => { const matrix = JSON.parse(await readFile(new URL("../../benchmarks/v2/batch-selection.json", import.meta.url), "utf8")) as { selectedMaxStepsPerAdvance: number; - candidates: readonly { maxSteps: number; decision: string; contractAccepted: boolean }[]; + selectionStatus: string; + candidates: readonly { + maxSteps: number; + decision: string; + contractAccepted: boolean; + worstCaseGas: number; + worstCaseTraceGas: number; + hardCeilingPass: boolean; + testnet40PercentPass: boolean | null; + }[]; }; assert.deepEqual(matrix.candidates.map((candidate) => candidate.maxSteps), [1, 2, 4, 8, 16, 32, 64]); assert.equal(matrix.selectedMaxStepsPerAdvance, 1); - assert.equal(matrix.candidates[0]?.contractAccepted, true); - assert.ok(matrix.candidates.slice(1).every((candidate) => !candidate.contractAccepted)); + assert.equal(matrix.selectionStatus, "conservative-single-step-until-testnet-gas-baseline"); + assert.ok(matrix.candidates.every((candidate) => candidate.contractAccepted)); + assert.deepEqual(matrix.candidates.map((candidate) => candidate.worstCaseGas), [47997, 47997, 61924, 113797, 209075, 364979, 677438]); + assert.deepEqual(matrix.candidates.map((candidate) => candidate.worstCaseTraceGas), [48306, 48306, 62233, 114106, 209384, 365288, 677747]); + assert.ok(matrix.candidates.slice(0, 6).every((candidate) => candidate.hardCeilingPass)); + assert.equal(matrix.candidates[6]?.hardCeilingPass, false); + assert.ok(matrix.candidates.every((candidate) => candidate.testnet40PercentPass === null)); }); From a6a583339f4de302184f63c5b448bea658e7f796 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:13:58 +0100 Subject: [PATCH 47/67] chore: bind fibonacci deployment to v2 batch limits --- scripts/deploy-v2-fibonacci.tolk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/deploy-v2-fibonacci.tolk b/scripts/deploy-v2-fibonacci.tolk index c314a38..e3e6813 100644 --- a/scripts/deploy-v2-fibonacci.tolk +++ b/scripts/deploy-v2-fibonacci.tolk @@ -42,8 +42,8 @@ fun main() { V2_RUN_ID, advanceCount, stateHash, - 1, - 1, + MAX_STEPS_PER_ADVANCE_V2, + MAX_OUTPUT_RECORDS_V2, { value: ton("0.01") }, ); println(tick.waitForTrace()); From 66fe34154ba8f2db8a6f83059ab6473fd3e94347 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:26:18 +0100 Subject: [PATCH 48/67] test: preserve v2 outbox across bounced delivery --- contracts/TonolithCoreV2.tolk | 23 +++++++- tests/tonolith_v2.test.tolk | 99 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index c4b7f73..7cdb447 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -532,7 +532,28 @@ fun onExternalMessage(_in: slice) { throw ErrorsV2.ExternalMessageRejected; } -fun onBouncedMessage(_in: InMessageBounced) {} +/// A bounced dispatch or acknowledgement is deliberately non-destructive. +/// +/// The outbox is the retry journal: an output is removed only after the +/// destination has accepted it and returned `MESSAGE_INPUT_ACCEPTED_V2`. +/// Therefore a bounce must not advance a sequence, clear an output, or write +/// a speculative retry record. A keeper can safely dispatch the same output +/// again with a new query id. We still parse the bounded bounce prefix so a +/// malformed bounce is ignored without touching persistent state. +fun onBouncedMessage(in: InMessageBounced) { + if (in.bouncedBody.remainingBitsCount() < 32) { + return; + } + var body = in.bouncedBody; + body.skipBouncedPrefix(); + if (body.remainingBitsCount() < 32) { + return; + } + val opcode = body.preloadUint(32); + if (opcode == MESSAGE_DELIVER_INPUT_V2 || opcode == MESSAGE_INPUT_ACCEPTED_V2) { + return; + } +} get fun getMagicV2(): uint32 { val storage = lazy V2Storage.load(); diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index d620404..de8b7d4 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -753,6 +753,105 @@ get fun `test v2 acknowledgement retires output and rejects wrong peer`() { expect(pressured.getStatusV2()).toEqual(STATUS_RUNNING_V2); } +get fun `test v2 bounced delivery keeps the outbox retryable`() { + val deployer = testing.treasury("v2-bounce-deployer"); + val route = V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: 0, + destinationCoreId: 1, + destinationPort: 0, + delay: 1, + maxRecordsPerEpoch: 4, + }; + var storage = makeV2StorageForNetwork( + romWithFirstWordV2(0), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var coreState = storage.core.load(); + val outputId = v2OutputId( + 0, + 0, + 1, + 0, + 1, + 0, + 0, + 0, + 3, + 1, + ); + val output = V2OutputRecord { + domain: DOMAIN_OUTPUT_V2, + outputId, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 3, + sourceInstructionCount: 1, + sourceStateHash: 0, + }.toCell(); + var queues = coreState.queues.load(); + queues.outboxRoot = v2AppendQueueRoot(queues.outboxRoot, output); + coreState.queues = queues.toCell(); + storage.core = coreState.toCell(); + val core = TonolithCoreV2.fromStorage(storage); + expect( + core.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: core.address }); + + val originalDelivery = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: ton("0.02"), + dest: core.address, + body: V2DeliverInput { + abiVersion: ABI_VERSION_V2, + queryId: 91, + runId: 0, + outputId, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 3, + sourceInstructionCount: 1, + sourceState: V2Hash { value: 0 }.toCell(), + }, + }); + val bouncedDelivery = originalDelivery.bounced(); + val bounceResult = testing.processSingleTraceStep( + deployer.address, + bouncedDelivery, + ); + expect(bounceResult.gasUsed > 0).toEqual(true); + expect(core.getOutboxCountV2()).toEqual(1); + + val malformedBounce = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: ton("0.02"), + dest: core.address, + body: beginCell().storeUint(0xFFFFFFFF, 32).endCell(), + }).bounced(); + val malformedResult = testing.processSingleTraceStep( + deployer.address, + malformedBounce, + ); + expect(malformedResult.gasUsed > 0).toEqual(true); + expect(core.getOutboxCountV2()).toEqual(1); +} + get fun `test v2 inherited opcode families execute through the contract`() { val deployer = testing.treasury("v2-opcode-deployer"); val nop = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); From 7a90ab08d52ad3aa4e8a79f876b46716ade18e3c Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:30:21 +0100 Subject: [PATCH 49/67] feat: expose versioned v2 inspection getters --- contracts/TonolithCoreV2.tolk | 110 +++++++++++++++++++++++++++++++ tests/tonolith_v2.test.tolk | 36 +++++++++- wrappers/TonolithCoreV2.gen.tolk | 56 ++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index 7cdb447..7d1fb0b 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -1,5 +1,6 @@ import "v2/commitments" import "v2/execution" +import "v2/memory" import "v2/peers" import "v2/queues" import "v2/rom" @@ -595,6 +596,12 @@ get fun getStaticCommitmentV2(): uint256 { return storage.identity.load().staticCommitment; } +/// Returns the immutable identity cell used by the off-chain verifier. +get fun getStaticIdentityV2(): cell { + val storage = lazy V2Storage.load(); + return storage.identity; +} + get fun getProgramIdV2(): uint256 { val storage = lazy V2Storage.load(); return storage.identity.load().programId; @@ -610,11 +617,21 @@ get fun getRouteRootV2(): uint256 { return storage.identity.load().roots.load().routeRoot; } +get fun getPeerRootV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.identity.load().roots.load().peers.load().peerRoot; +} + get fun getCoreStateHashV2(): uint256 { val storage = lazy V2Storage.load(); return storage.core.hash(); } +get fun getStateHashV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.hash(); +} + get fun getAdvanceCountV2(): uint64 { val storage = lazy V2Storage.load(); return storage.core.load().advanceCount; @@ -640,6 +657,11 @@ get fun getStatusV2(): uint3 { return storage.core.load().status; } +get fun getFaultCodeV2(): uint8 { + val storage = lazy V2Storage.load(); + return storage.core.load().faultCode; +} + get fun getAccumulatorV2(): uint4 { val storage = lazy V2Storage.load(); return storage.core.load().accumulator; @@ -650,6 +672,36 @@ get fun getFlagsV2(): uint2 { return storage.core.load().flags; } +get fun getRegistersV2(): uint64 { + val storage = lazy V2Storage.load(); + return storage.core.load().memory.load().registers.load().packed; +} + +get fun getRamRootV2(): uint256 { + val storage = lazy V2Storage.load(); + return storage.core.load().memory.load().ram.hash(); +} + +get fun getMemoryPageV2(pageIndex: uint8): uint256 { + val storage = lazy V2Storage.load(); + val page = selectV2RamPage(storage.core.load().memory.load().ram.load(), pageIndex); + return page.load().packed; +} + +/// Returns a compact epoch view cell: domain, epoch, status, instruction count, +/// and the cumulative epoch-history commitment. +get fun getEpochStateV2(): cell { + val storage = lazy V2Storage.load(); + val core = storage.core.load(); + return beginCell() + .storeUint(DOMAIN_EPOCH_V2, 32) + .storeUint(core.epoch, 64) + .storeUint(core.status, 3) + .storeUint(core.instructionCount, 64) + .storeUint(core.commitments.load().epochHistoryCommitment, 256) + .endCell(); +} + get fun getAcceptedMessageCountV2(): uint64 { val storage = lazy V2Storage.load(); return storage.core.load().acceptedMessageCount; @@ -675,6 +727,64 @@ get fun getOutboxCountV2(): uint8 { return v2QueueCount(storage.core.load().queues.load().outboxRoot) as uint8; } +/// High byte is the fixed capacity; low byte is the current count. +get fun getInboxSummaryV2(): uint16 { + val storage = lazy V2Storage.load(); + return (v2QueueCount(storage.core.load().queues.load().inboxRoot) | + (MAX_INPUT_RECORDS_V2 << 8)) as uint16; +} + +get fun getOutboxSummaryV2(): uint16 { + val storage = lazy V2Storage.load(); + return (v2QueueCount(storage.core.load().queues.load().outboxRoot) | + (MAX_OUTPUT_RECORDS_V2 << 8)) as uint16; +} + +/// Returns found in bit 8 and the four-bit input value in bits 0..3. +get fun getInboxSlotV2(epoch: uint64, port: uint4, sequence: uint8): uint16 { + val storage = lazy V2Storage.load(); + val cursor = v2FindInputSlot( + storage.core.load().queues.load().inboxRoot, + epoch, + port, + sequence, + ); + if (cursor.found == 0) { + return 0; + } + val input = v2InputAt(storage.core.load().queues.load().inboxRoot, cursor.index); + return (0x100 | input.value) as uint16; +} + +get fun getOutputV2(outputId: uint256): cell { + val storage = lazy V2Storage.load(); + val cursor = v2FindRecordById(storage.core.load().queues.load().outboxRoot, outputId); + assert (cursor.found != 0) throw ErrorsV2.NotFound; + return v2RecordAt(storage.core.load().queues.load().outboxRoot, cursor.index); +} + +get fun getRouteV2(sourceCoreId: uint16, sourcePort: uint4): cell { + val storage = lazy V2Storage.load(); + val route = v2FindRoute(storage.network.load().routes, sourceCoreId, sourcePort); + assert (route != null) throw ErrorsV2.NotFound; + return route!.toCell(); +} + +/// Returns the minimum attached value for the public message class: +/// 0 = AdvanceV2, 1 = DispatchOutputV2, 2 = DeliverInputV2. +get fun estimateRequiredValueV2(messageClass: uint8): uint64 { + if (messageClass == 0) { + return MIN_ADVANCE_VALUE_V2 as uint64; + } + if (messageClass == 1) { + return MIN_DISPATCH_VALUE_V2 as uint64; + } + if (messageClass == 2) { + return MIN_DELIVERY_VALUE_V2 as uint64; + } + throw ErrorsV2.InvalidBody; +} + get fun getOutputCommitmentV2(): uint256 { val storage = lazy V2Storage.load(); return storage.core.load().commitments.load().outputCommitment; diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index de8b7d4..905e1cc 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -273,7 +273,8 @@ fun advanceV2OnceForTest(core: TonolithCoreV2, from: address): SendResultList { get fun `test v2 deploy exposes immutable metadata and a canonical initial state`() { val deployer = testing.treasury("v2-metadata-deployer"); - val core = TonolithCoreV2.fromStorage(makeV2StorageForTest(romWithFirstWordV2(0x0000))); + val storage = makeV2StorageForTest(romWithFirstWordV2(0x0000)); + val core = TonolithCoreV2.fromStorage(storage); expect( core.deploy(deployer.address, SendParams { value: ton("1") }), ).toHaveSuccessfulDeploy({ to: core.address }); @@ -287,6 +288,27 @@ get fun `test v2 deploy exposes immutable metadata and a canonical initial state expect(core.getStatusV2()).toEqual(0); expect(core.getEpochV2()).toEqual(0); expect(core.getInstructionCountV2()).toEqual(0); + expect(core.getStateHashV2()).toEqual(core.getCoreStateHashV2()); + expect(core.getStaticIdentityV2().hash()).toEqual(storage.identity.hash()); + expect(core.getRegistersV2()).toEqual(0); + expect(core.getRamRootV2()).toEqual(storage.core.load().memory.load().ram.hash()); + expect(core.getMemoryPageV2(0)).toEqual(0); + expect(core.getFaultCodeV2()).toEqual(0); + expect(core.getInboxSummaryV2()).toEqual(4096); + expect(core.getOutboxSummaryV2()).toEqual(4096); + expect(core.getEpochStateV2().hash()).toEqual( + beginCell() + .storeUint(DOMAIN_EPOCH_V2, 32) + .storeUint(0, 64) + .storeUint(STATUS_RUNNING_V2, 3) + .storeUint(0, 64) + .storeUint(0, 256) + .endCell() + .hash(), + ); + expect(core.estimateRequiredValueV2(0)).toEqual(MIN_ADVANCE_VALUE_V2 as uint64); + expect(core.estimateRequiredValueV2(1)).toEqual(MIN_DISPATCH_VALUE_V2 as uint64); + expect(core.estimateRequiredValueV2(2)).toEqual(MIN_DELIVERY_VALUE_V2 as uint64); expect( core.getCoreStateHashV2(), ).toEqual(0x0782faed379592a6d47134282b8c495e6073033acb733ed5e629f8b24ddb14b9); @@ -434,6 +456,17 @@ get fun `test v2 OUTP appends a routed output and commits it`() { expect(core.getOutputCommitmentV2()).toEqual( 0xbef05e20c0bfad82f96fadd6cd4d085e08d26804ae52ec28d063f48d48fe886b, ); + expect(core.getPeerRootV2()).toEqual( + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }).hash(), + ); + expect(core.getRouteV2(0, 3).hash()).toEqual(route.toCell().hash()); + expect( + core.getOutputV2(0x18f93e46534eae22589575988cee189065e808d771d9eb925991245077a826fe).hash(), + ).toNotEqual(0); expect(core.getCoreStateHashV2()).toEqual( 0x0eed2e36924e740c884530155b6c8f079f1e342a2ff3ee0a1b7b672ba88c7c42, ); @@ -570,6 +603,7 @@ get fun `test v2 IN waits, accepts authenticated input, deduplicates, and consum expect(core.getStatusV2()).toEqual(STATUS_RUNNING_V2); expect(core.getInboxCountV2()).toEqual(1); expect(core.getInputCountV2()).toEqual(1); + expect(core.getInboxSlotV2(1, 2, 0)).toEqual(0x107); val duplicate = sendDeliverInputV2ForTest( core, deployer.address, diff --git a/wrappers/TonolithCoreV2.gen.tolk b/wrappers/TonolithCoreV2.gen.tolk index 7c4e2f2..55e571b 100644 --- a/wrappers/TonolithCoreV2.gen.tolk +++ b/wrappers/TonolithCoreV2.gen.tolk @@ -230,6 +230,10 @@ fun TonolithCoreV2.getStaticCommitmentV2(self): uint256 { return net.runGetMethod(self.address, "getStaticCommitmentV2"); } +fun TonolithCoreV2.getStaticIdentityV2(self): cell { + return net.runGetMethod(self.address, "getStaticIdentityV2"); +} + fun TonolithCoreV2.getProgramIdV2(self): uint256 { return net.runGetMethod(self.address, "getProgramIdV2"); } @@ -242,10 +246,18 @@ fun TonolithCoreV2.getRouteRootV2(self): uint256 { return net.runGetMethod(self.address, "getRouteRootV2"); } +fun TonolithCoreV2.getPeerRootV2(self): uint256 { + return net.runGetMethod(self.address, "getPeerRootV2"); +} + fun TonolithCoreV2.getCoreStateHashV2(self): uint256 { return net.runGetMethod(self.address, "getCoreStateHashV2"); } +fun TonolithCoreV2.getStateHashV2(self): uint256 { + return net.runGetMethod(self.address, "getStateHashV2"); +} + fun TonolithCoreV2.getAdvanceCountV2(self): uint64 { return net.runGetMethod(self.address, "getAdvanceCountV2"); } @@ -266,6 +278,10 @@ fun TonolithCoreV2.getStatusV2(self): uint3 { return net.runGetMethod(self.address, "getStatusV2"); } +fun TonolithCoreV2.getFaultCodeV2(self): uint8 { + return net.runGetMethod(self.address, "getFaultCodeV2"); +} + fun TonolithCoreV2.getAccumulatorV2(self): uint4 { return net.runGetMethod(self.address, "getAccumulatorV2"); } @@ -274,6 +290,22 @@ fun TonolithCoreV2.getFlagsV2(self): uint2 { return net.runGetMethod(self.address, "getFlagsV2"); } +fun TonolithCoreV2.getRegistersV2(self): uint64 { + return net.runGetMethod(self.address, "getRegistersV2"); +} + +fun TonolithCoreV2.getRamRootV2(self): uint256 { + return net.runGetMethod(self.address, "getRamRootV2"); +} + +fun TonolithCoreV2.getMemoryPageV2(self, pageIndex: uint8): uint256 { + return net.runGetMethod(self.address, "getMemoryPageV2", [pageIndex]); +} + +fun TonolithCoreV2.getEpochStateV2(self): cell { + return net.runGetMethod(self.address, "getEpochStateV2"); +} + fun TonolithCoreV2.getAcceptedMessageCountV2(self): uint64 { return net.runGetMethod(self.address, "getAcceptedMessageCountV2"); } @@ -294,6 +326,30 @@ fun TonolithCoreV2.getOutboxCountV2(self): uint8 { return net.runGetMethod(self.address, "getOutboxCountV2"); } +fun TonolithCoreV2.getInboxSummaryV2(self): uint16 { + return net.runGetMethod(self.address, "getInboxSummaryV2"); +} + +fun TonolithCoreV2.getOutboxSummaryV2(self): uint16 { + return net.runGetMethod(self.address, "getOutboxSummaryV2"); +} + +fun TonolithCoreV2.getInboxSlotV2(self, epoch: uint64, port: uint4, sequence: uint8): uint16 { + return net.runGetMethod(self.address, "getInboxSlotV2", [epoch, port, sequence]); +} + +fun TonolithCoreV2.getOutputV2(self, outputId: uint256): cell { + return net.runGetMethod(self.address, "getOutputV2", [outputId]); +} + +fun TonolithCoreV2.getRouteV2(self, sourceCoreId: uint16, sourcePort: uint4): cell { + return net.runGetMethod(self.address, "getRouteV2", [sourceCoreId, sourcePort]); +} + +fun TonolithCoreV2.estimateRequiredValueV2(self, messageClass: uint8): uint64 { + return net.runGetMethod(self.address, "estimateRequiredValueV2", [messageClass]); +} + fun TonolithCoreV2.getOutputCommitmentV2(self): uint256 { return net.runGetMethod(self.address, "getOutputCommitmentV2"); } From fd29df6e536a78d12d24192780fc976ccf031bd2 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:38:26 +0100 Subject: [PATCH 50/67] feat: make v2 verifier replay protocol messages --- packages/verifier/src/types.ts | 14 ++ packages/verifier/src/verify.ts | 259 ++++++++++++++++++++++++++------ tests/v2/verifier.test.ts | 147 +++++++++++++++++- 3 files changed, 371 insertions(+), 49 deletions(-) diff --git a/packages/verifier/src/types.ts b/packages/verifier/src/types.ts index 8c23094..e8f63b6 100644 --- a/packages/verifier/src/types.ts +++ b/packages/verifier/src/types.ts @@ -55,11 +55,15 @@ export interface V2VerificationExpectations { readonly romRoot: Hash256; readonly routeRoot: Hash256; readonly staticCommitment: Hash256; + readonly coreId?: number; + readonly maxStepsPerAdvance?: number; } export interface ReplayEvidence { readonly initialState: import("../../emulator/src/model.js").V2State; readonly rom: readonly number[]; + /** Optional address-to-core bindings used to prove authenticated deliveries. */ + readonly peerIdsByAddress?: Readonly>; } export interface VerificationReport { @@ -67,6 +71,14 @@ export interface VerificationReport { readonly network: string; readonly address: string; readonly sources: readonly string[]; + readonly commitments: { + readonly programId: Hash256; + readonly runId: Hash256; + readonly romRoot: Hash256; + readonly routeRoot: Hash256; + readonly staticCommitment: Hash256; + }; + readonly providerAgreement: boolean; readonly deployment?: { readonly transactionHash?: string; readonly transactionLt?: string; @@ -84,6 +96,8 @@ export interface VerificationReport { readonly finalPc?: number; readonly outputCommitment?: Hash256; readonly totalFeesNano?: string; + readonly stateSnapshotsChecked: number; + readonly replayedMessageKinds: readonly string[]; readonly errors: readonly string[]; readonly unresolved: readonly string[]; } diff --git a/packages/verifier/src/verify.ts b/packages/verifier/src/verify.ts index 7bf7dcf..be2e13d 100644 --- a/packages/verifier/src/verify.ts +++ b/packages/verifier/src/verify.ts @@ -1,6 +1,13 @@ import { Address, Cell, contractAddress, loadStateInit } from "@ton/core"; import { decodeV2Message, type V2Message } from "../../abi/src/messages.js"; -import { advance, type V2AdvanceRequest } from "../../emulator/src/executor.js"; +import { + acknowledgeOutput, + advance, + deliverInput, + dispatchOutput, + type DeliverInputMessage, + type V2AdvanceRequest, +} from "../../emulator/src/executor.js"; import { stateHash } from "../../emulator/src/commitments.js"; import type { Hash256 } from "../../abi/src/messages.js"; import type { @@ -23,24 +30,34 @@ export async function verifyRunFromSources( if (sources.length < 2) { return incompleteReport(expectations, sources.map((source) => source.id), "at least two independent sources are required"); } + const sourceIds = sources.map((source) => source.id); + if (new Set(sourceIds).size !== sourceIds.length) { + return incompleteReport(expectations, sourceIds, "independent source identifiers must be distinct"); + } const snapshots = await Promise.all(sources.map((source) => source.fetchSnapshot(address))); const errors: string[] = []; const unresolved: string[] = []; const first = snapshots[0]; - if (first === undefined) return incompleteReport(expectations, sources.map((source) => source.id), "no source returned a snapshot"); + if (first === undefined) return incompleteReport(expectations, sourceIds, "no source returned a snapshot"); for (const snapshot of snapshots) { errors.push(...verifySnapshotIdentity(snapshot, expectations)); errors.push(...verifyHistoryContinuity(snapshot.transactions)); } const fingerprint = snapshotFingerprint(first); + let providerAgreement = true; for (const snapshot of snapshots.slice(1)) { - if (snapshotFingerprint(snapshot) !== fingerprint) errors.push(`provider disagreement: ${snapshot.source}`); + if (snapshotFingerprint(snapshot) !== fingerprint) { + providerAgreement = false; + errors.push(`provider disagreement: ${snapshot.source}`); + } } let replayResult: ReplayResult | undefined; if (replay !== undefined && errors.length === 0) { - replayResult = replayAcceptedAdvances(first.transactions, replay); + errors.push(...verifyReplayConfiguration(replay, expectations)); + replayResult = replayTransactions(first.transactions, replay, expectations); errors.push(...replayResult.errors); + unresolved.push(...replayResult.unresolved); } else if (replay === undefined) { unresolved.push("independent emulator replay was not supplied"); } @@ -53,12 +70,22 @@ export async function verifyRunFromSources( overall, network: expectations.network, address: Address.parse(expectations.address).toRawString(), - sources: sources.map((source) => source.id), + sources: sourceIds, + commitments: { + programId: expectations.programId, + runId: expectations.runId, + romRoot: expectations.romRoot, + routeRoot: expectations.routeRoot, + staticCommitment: expectations.staticCommitment, + }, + providerAgreement, acceptedAdvances: counters.acceptedAdvances, executedInstructions: replayResult?.executedInstructions ?? 0n, - deliveredInputs: counters.deliveredInputs, - duplicateDeliveries: counters.duplicateDeliveries, + deliveredInputs: replayResult?.deliveredInputs ?? counters.deliveredInputs, + duplicateDeliveries: replayResult?.duplicateDeliveries ?? counters.duplicateDeliveries, staleAttempts: counters.staleAttempts, + stateSnapshotsChecked: replayResult?.stateSnapshotsChecked ?? 0, + replayedMessageKinds: replayResult?.messageKinds ?? [], errors, unresolved, ...(final === undefined ? {} : { finalStateHash: stateHash(final), finalStatus: final.status, finalPc: final.pc, outputCommitment: final.outputCommitment }), @@ -71,6 +98,7 @@ export function verifySnapshotIdentity(snapshot: RawChainSnapshot, expectations: const errors: string[] = []; errors.push(...verifyAccount(snapshot.account, expectations)); if (snapshot.network !== expectations.network) errors.push(`network mismatch from ${snapshot.source}`); + if (snapshot.transactions.length === 0) errors.push(`transaction history is empty from ${snapshot.source}`); return errors; } @@ -86,29 +114,38 @@ export function verifyAccount(account: RawAccountSnapshot, expectations: V2Verif if (expectedAddress === undefined) return errors; if (account.codeBoc === undefined || account.dataBoc === undefined) { errors.push("account code/data BOC is missing"); - return errors; } - try { - const code = firstCell(account.codeBoc); - const data = firstCell(account.dataBoc); - const codeHash = code.hash().toString("hex"); - const dataHash = data.hash().toString("hex"); - if (codeHash !== expectations.codeHash) errors.push("code hash mismatch"); - if (dataHash !== expectations.dataHash) errors.push("data hash mismatch"); - const initCell = account.stateInitBoc === undefined ? undefined : firstCell(account.stateInitBoc); - if (initCell !== undefined) { - const stateInit = loadStateInit(initCell.beginParse()); - if (stateInit.code === undefined || stateInit.data === undefined) errors.push("StateInit lacks code or data"); - else { - const derived = contractAddress(expectedAddress.workChain, stateInit).toRawString(); - if (derived !== expectedAddress.toRawString()) errors.push("StateInit-derived address mismatch"); - if (initCell.hash().toString("hex") !== expectations.stateInitHash) errors.push("StateInit hash mismatch"); + if (account.codeBoc !== undefined && account.dataBoc !== undefined) { + try { + const code = firstCell(account.codeBoc); + const data = firstCell(account.dataBoc); + const codeHash = code.hash().toString("hex"); + const dataHash = data.hash().toString("hex"); + if (!sameHash(codeHash, expectations.codeHash)) errors.push("code hash mismatch"); + if (!sameHash(dataHash, expectations.dataHash)) errors.push("data hash mismatch"); + + if (account.stateInitBoc === undefined) { + errors.push("deployment StateInit BOC is missing"); + } else { + const initCell = firstCell(account.stateInitBoc); + if (!sameHash(initCell.hash().toString("hex"), expectations.stateInitHash)) errors.push("StateInit hash mismatch"); + const stateInit = loadStateInit(initCell.beginParse()); + if (stateInit.code == null || stateInit.data == null) { + errors.push("StateInit lacks code or data"); + } else { + const stateInitCodeHash = stateInit.code.hash().toString("hex"); + const stateInitDataHash = stateInit.data.hash().toString("hex"); + if (!sameHash(stateInitCodeHash, codeHash)) errors.push("StateInit code does not match account code"); + if (!sameHash(stateInitDataHash, dataHash)) errors.push("StateInit data does not match account data"); + if (!sameHash(stateInitCodeHash, expectations.codeHash)) errors.push("StateInit code hash mismatch"); + if (!sameHash(stateInitDataHash, expectations.dataHash)) errors.push("StateInit data hash mismatch"); + const derived = contractAddress(expectedAddress.workChain, stateInit).toRawString(); + if (derived !== expectedAddress.toRawString()) errors.push("StateInit-derived address mismatch"); + } } - } else { - errors.push("deployment StateInit BOC is missing"); + } catch (error) { + errors.push(`invalid account BOC: ${error instanceof Error ? error.message : String(error)}`); } - } catch (error) { - errors.push(`invalid account BOC: ${error instanceof Error ? error.message : String(error)}`); } return errors; } @@ -116,12 +153,17 @@ export function verifyAccount(account: RawAccountSnapshot, expectations: V2Verif export function verifyHistoryContinuity(transactions: readonly RawTransactionSnapshot[]): string[] { const errors: string[] = []; let previous: RawTransactionSnapshot | undefined; + const seenHashes = new Set(); for (const transaction of transactions) { - if (!/^[0-9a-f]+$/i.test(transaction.hash)) errors.push("transaction hash is not hexadecimal"); + if (!/^[0-9a-f]{64}$/i.test(transaction.hash)) errors.push("transaction hash is not a 256-bit hexadecimal value"); + if (seenHashes.has(transaction.hash.toLowerCase())) errors.push(`transaction ${transaction.hash} appears more than once`); + seenHashes.add(transaction.hash.toLowerCase()); if (!/^\d+$/.test(transaction.lt)) errors.push(`transaction ${transaction.hash} has an invalid logical time`); if (previous !== undefined) { - if (transaction.prevTransactionHash !== undefined && transaction.prevTransactionHash !== previous.hash) errors.push(`history link mismatch at ${transaction.hash}`); - if (transaction.prevTransactionLt !== undefined && transaction.prevTransactionLt !== previous.lt) errors.push(`history logical-time link mismatch at ${transaction.hash}`); + if (transaction.prevTransactionHash === undefined) errors.push(`history predecessor hash is missing at ${transaction.hash}`); + else if (transaction.prevTransactionHash.toLowerCase() !== previous.hash.toLowerCase()) errors.push(`history link mismatch at ${transaction.hash}`); + if (transaction.prevTransactionLt === undefined) errors.push(`history predecessor logical time is missing at ${transaction.hash}`); + else if (transaction.prevTransactionLt !== previous.lt) errors.push(`history logical-time link mismatch at ${transaction.hash}`); if (BigInt(transaction.lt) <= BigInt(previous.lt)) errors.push(`history is not strictly ordered at ${transaction.hash}`); } previous = transaction; @@ -142,40 +184,154 @@ interface ReplayResult { readonly finalState: ReplayEvidence["initialState"]; readonly executedInstructions: bigint; readonly errors: readonly string[]; + readonly unresolved: readonly string[]; + readonly deliveredInputs: number; + readonly duplicateDeliveries: number; + readonly stateSnapshotsChecked: number; + readonly messageKinds: readonly string[]; } -function replayAcceptedAdvances(transactions: readonly RawTransactionSnapshot[], evidence: ReplayEvidence): ReplayResult { +function replayTransactions( + transactions: readonly RawTransactionSnapshot[], + evidence: ReplayEvidence, + expectations: V2VerificationExpectations, +): ReplayResult { let current = evidence.initialState; let executedInstructions = 0n; const errors: string[] = []; + const unresolved: string[] = []; + const messageKinds = new Set(); + let deliveredInputs = 0; + let duplicateDeliveries = 0; + let stateSnapshotsChecked = 0; for (const transaction of transactions) { - if (!transaction.success || transaction.inbound === undefined) continue; + if (transaction.inbound === undefined) continue; const parsed = parseMessageBody(transaction.inbound); - if (parsed.message?.kind !== "advance") continue; - const request: V2AdvanceRequest = { - queryId: parsed.message.queryId, - expectedAdvanceCount: parsed.message.expectedAdvanceCount, - expectedStateHash: parsed.message.expectedStateHash, - maxInstructions: parsed.message.maxInstructions, - maxOutputs: parsed.message.maxOutputs, - }; + if (parsed.message === undefined) { + if (transaction.success) errors.push(`successful transaction ${transaction.hash} has an undecodable inbound message`); + continue; + } + const message = parsed.message; + messageKinds.add(message.kind); + if (message.runId.toLowerCase() !== expectations.runId.toLowerCase()) { + errors.push(`run ID mismatch in transaction ${transaction.hash}`); + } + if (!transaction.success) continue; try { - const result = advance(current, { rom: evidence.rom }, request); - current = result.state; - executedInstructions += BigInt(result.executed); - const observed = transaction.stateDataBoc === undefined ? undefined : stateHashFromBoc(transaction.stateDataBoc); - if (observed !== undefined && observed !== stateHash(current)) errors.push(`state hash mismatch after transaction ${transaction.hash}`); + let stateChanged = false; + switch (message.kind) { + case "advance": { + const request: V2AdvanceRequest = { + queryId: message.queryId, + expectedAdvanceCount: message.expectedAdvanceCount, + expectedStateHash: message.expectedStateHash, + maxInstructions: message.maxInstructions, + maxOutputs: message.maxOutputs, + }; + const result = advance(current, { rom: evidence.rom }, request); + current = result.state; + executedInstructions += BigInt(result.executed); + stateChanged = true; + break; + } + case "dispatchOutput": + dispatchOutput(current, { + queryId: message.queryId, + expectedStateHash: message.expectedStateHash, + outputId: message.outputId, + }); + break; + case "deliverInput": { + const authenticatedPeer = authenticatedPeerId(transaction.inbound.source, evidence); + if (authenticatedPeer === undefined) { + unresolved.push(`authenticated source-core binding is missing at ${transaction.hash}`); + } + const delivery: DeliverInputMessage = { ...message }; + const result = deliverInput(current, delivery, authenticatedPeer ?? message.sourceCoreId); + current = result.state; + deliveredInputs += 1; + if (result.duplicate) duplicateDeliveries += 1; + stateChanged = !result.duplicate; + const acknowledgement = transaction.outbound + .map((outbound) => parseMessageBody(outbound).message) + .find((outbound) => outbound?.kind === "inputAccepted" && outbound.outputId === message.outputId); + if (acknowledgement === undefined) errors.push(`delivery ${transaction.hash} did not emit its acknowledgement`); + break; + } + case "inputAccepted": { + const authenticatedPeer = authenticatedPeerId(transaction.inbound.source, evidence); + if (authenticatedPeer === undefined) { + unresolved.push(`authenticated destination-core binding is missing at ${transaction.hash}`); + } + const result = acknowledgeOutput(current, { ...message }, authenticatedPeer ?? message.destinationCoreId); + current = result.state; + stateChanged = !result.duplicate; + break; + } + case "topUp": + break; + } + if (stateChanged) { + stateSnapshotsChecked += 1; + if (transaction.stateDataBoc === undefined) { + unresolved.push(`post-state BOC is missing at ${transaction.hash}`); + } else { + const observed = stateHashFromBoc(transaction.stateDataBoc); + if (!sameHash(observed, stateHash(current))) errors.push(`state hash mismatch after transaction ${transaction.hash}`); + } + } } catch (error) { errors.push(`replay failed at ${transaction.hash}: ${error instanceof Error ? error.message : String(error)}`); } } - return { finalState: current, executedInstructions, errors }; + return { + finalState: current, + executedInstructions, + errors, + unresolved, + deliveredInputs, + duplicateDeliveries, + stateSnapshotsChecked, + messageKinds: [...messageKinds].sort(), + }; +} + +function verifyReplayConfiguration(replay: ReplayEvidence, expectations: V2VerificationExpectations): string[] { + const errors: string[] = []; + const config = replay.initialState.config; + if (!sameHash(config.runId, expectations.runId)) errors.push("replay run ID does not match expectations"); + if (!sameHash(config.programId, expectations.programId)) errors.push("replay program ID does not match expectations"); + if (!sameHash(config.romRoot, expectations.romRoot)) errors.push("replay ROM root does not match expectations"); + if (!sameHash(config.routeRoot, expectations.routeRoot)) errors.push("replay route root does not match expectations"); + if (!sameHash(config.staticCommitment, expectations.staticCommitment)) errors.push("replay static commitment does not match expectations"); + if (expectations.coreId !== undefined && config.coreId !== expectations.coreId) errors.push("replay core ID does not match expectations"); + if (expectations.maxStepsPerAdvance !== undefined && config.maxStepsPerAdvance !== expectations.maxStepsPerAdvance) { + errors.push("replay max-steps limit does not match expectations"); + } + if (replay.rom.length === 0) errors.push("replay ROM is empty"); + return errors; +} + +function authenticatedPeerId(source: string | undefined, evidence: ReplayEvidence): number | undefined { + if (source === undefined || evidence.peerIdsByAddress === undefined) return undefined; + const direct = evidence.peerIdsByAddress[source]; + if (direct !== undefined) return direct; + try { + const normalized = Address.parse(source).toRawString(); + return evidence.peerIdsByAddress[normalized]; + } catch { + return undefined; + } } function stateHashFromBoc(boc: string): Hash256 { return firstCell(boc).hash().toString("hex"); } +function sameHash(left: string, right: string): boolean { + return left.toLowerCase() === right.toLowerCase(); +} + function firstCell(boc: string): Cell { const cells = Cell.fromBoc(Buffer.from(boc, "base64")); const cell = cells[0]; @@ -229,6 +385,7 @@ function sumFees(transactions: readonly RawTransactionSnapshot[]): string | unde function snapshotFingerprint(snapshot: RawChainSnapshot): string { return JSON.stringify({ + network: snapshot.network, account: snapshot.account, transactions: snapshot.transactions, }); @@ -240,11 +397,21 @@ function incompleteReport(expectations: V2VerificationExpectations, sources: rea network: expectations.network, address: expectations.address, sources, + commitments: { + programId: expectations.programId, + runId: expectations.runId, + romRoot: expectations.romRoot, + routeRoot: expectations.routeRoot, + staticCommitment: expectations.staticCommitment, + }, + providerAgreement: false, acceptedAdvances: 0, executedInstructions: 0n, deliveredInputs: 0, duplicateDeliveries: 0, staleAttempts: 0, + stateSnapshotsChecked: 0, + replayedMessageKinds: [], errors: [], unresolved: [reason], }; diff --git a/tests/v2/verifier.test.ts b/tests/v2/verifier.test.ts index 00e4d38..0f48f35 100644 --- a/tests/v2/verifier.test.ts +++ b/tests/v2/verifier.test.ts @@ -2,11 +2,13 @@ import assert from "node:assert/strict"; import test from "node:test"; import { beginCell } from "@ton/core"; import { encodeAdvanceV2 } from "../../packages/abi/src/messages.js"; +import { encodeDeliverInputV2, encodeInputAcceptedV2 } from "../../packages/abi/src/messages.js"; import { buildArtifact } from "../../packages/artifact/src/manifest.js"; -import { coreStateCell } from "../../packages/emulator/src/commitments.js"; -import { advance } from "../../packages/emulator/src/executor.js"; +import { coreStateCell, stateHash } from "../../packages/emulator/src/commitments.js"; +import { acknowledgeOutput, advance, deliverInput } from "../../packages/emulator/src/executor.js"; import { buildV2StateInitFromArtifact } from "../../packages/sdk/src/stateinit.js"; -import { verifyRunFromSources, type RawChainSnapshot, type RawChainSource } from "../../packages/verifier/src/index.js"; +import { verifyAccount, verifyHistoryContinuity, verifyRunFromSources, type RawChainSnapshot, type RawChainSource } from "../../packages/verifier/src/index.js"; +import { outputId } from "../../packages/emulator/src/commitments.js"; const compiler = { name: "tonolith-assembler", @@ -111,3 +113,142 @@ test("independent verifier checks StateInit, history, provider agreement, and em assert.equal(disagreement.overall, "failed"); assert.match(disagreement.errors.join("\n"), /provider disagreement/); }); + +test("verifier rejects StateInit/data drift and incomplete predecessor links", async () => { + const artifact = buildArtifact("NOP\n", { name: "verifier-integrity-fixture", compiler }); + const deployment = buildV2StateInitFromArtifact(beginCell().storeUint(1, 1).endCell(), artifact); + const expectations = { + address: deployment.address.toRawString(), + network: "testnet", + codeHash: deployment.codeHash, + dataHash: deployment.dataHash, + stateInitHash: deployment.stateInitHash, + programId: artifact.manifest.programId, + runId: artifact.manifest.runId, + romRoot: artifact.manifest.romRoot, + routeRoot: artifact.manifest.routeRoot, + staticCommitment: artifact.manifest.staticCommitment, + } as const; + const account = { + address: deployment.address.toRawString(), + codeBoc: deployment.stateInit.code!.toBoc({ idx: false }).toString("base64"), + dataBoc: beginCell().storeUint(0, 1).endCell().toBoc({ idx: false }).toString("base64"), + stateInitBoc: deployment.stateInitCell.toBoc({ idx: false }).toString("base64"), + }; + assert.match(verifyAccount(account, expectations).join("\n"), /data hash mismatch/); + assert.match( + verifyHistoryContinuity([ + { hash: "a".repeat(64), lt: "1", success: true, outbound: [] }, + { hash: "b".repeat(64), lt: "2", success: true, outbound: [] }, + ]).join("\n"), + /predecessor hash is missing/, + ); +}); + +test("verifier replays authenticated delivery and acknowledgement state transitions", async () => { + const peerAddress = `0:${"ab".repeat(32)}`; + const artifact = buildArtifact("LDI 3\nOUTP 0\n", { + name: "verifier-protocol-fixture", + routes: [ + { sourceCoreId: 0, sourcePort: 0, destinationCoreId: 1, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 4 }, + { sourceCoreId: 1, sourcePort: 0, destinationCoreId: 0, destinationPort: 0, delay: 1, maxRecordsPerEpoch: 4 }, + ], + peers: [{ coreId: 1, address: peerAddress }], + compiler, + }); + const deployment = buildV2StateInitFromArtifact(beginCell().storeUint(1, 1).endCell(), artifact); + const firstAdvance = advance(deployment.initialState, { rom: artifact.assembly.words }, { + queryId: 1n, + expectedAdvanceCount: 0n, + expectedStateHash: deployment.initialStateHash, + maxInstructions: 1, + maxOutputs: 1, + }); + const secondAdvance = advance(firstAdvance.state, { rom: artifact.assembly.words }, { + queryId: 2n, + expectedAdvanceCount: 1n, + expectedStateHash: firstAdvance.nextStateHash, + maxInstructions: 1, + maxOutputs: 1, + }); + const pendingOutput = secondAdvance.outputs[0]!; + const acknowledgement = { + queryId: 3n, + runId: artifact.manifest.runId, + outputId: pendingOutput.outputId, + destinationCoreId: 1, + destinationEpoch: pendingOutput.destinationEpoch, + destinationStateHash: secondAdvance.nextStateHash, + } as const; + const acknowledged = acknowledgeOutput(secondAdvance.state, acknowledgement, 1); + const inputBase = { + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: 0n, + destinationEpoch: 1n, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 7, + sourceInstructionCount: 1n, + sourceStateHash: "00".repeat(32), + } as const; + const inputMessage = { + queryId: 4n, + runId: artifact.manifest.runId, + outputId: outputId(artifact.manifest.runId, inputBase), + ...inputBase, + } as const; + const delivered = deliverInput(acknowledged.state, inputMessage, 1); + const encodeMessage = (message: Parameters[0] | Parameters[0]) => { + if ("destinationStateHash" in message) return encodeInputAcceptedV2(message).toBoc({ idx: false }).toString("base64"); + return encodeDeliverInputV2(message).toBoc({ idx: false }).toString("base64"); + }; + const snapshot: RawChainSnapshot = { + source: "protocol-fixture-a", + network: "testnet", + account: { + address: deployment.address.toRawString(), + codeBoc: deployment.stateInit.code!.toBoc({ idx: false }).toString("base64"), + dataBoc: deployment.data.toBoc({ idx: false }).toString("base64"), + stateInitBoc: deployment.stateInitCell.toBoc({ idx: false }).toString("base64"), + }, + transactions: [ + { hash: "1".repeat(64), lt: "1", success: true, outbound: [] }, + { + hash: "2".repeat(64), lt: "2", prevTransactionHash: "1".repeat(64), prevTransactionLt: "1", success: true, + inbound: { direction: "in", bodyBoc: encodeAdvanceV2({ queryId: 1n, runId: artifact.manifest.runId, expectedAdvanceCount: 0n, expectedStateHash: deployment.initialStateHash, maxInstructions: 1, maxOutputs: 1 }).toBoc({ idx: false }).toString("base64") }, + outbound: [], stateDataBoc: coreStateCell(firstAdvance.state).toBoc({ idx: false }).toString("base64"), + }, + { + hash: "3".repeat(64), lt: "3", prevTransactionHash: "2".repeat(64), prevTransactionLt: "2", success: true, + inbound: { direction: "in", bodyBoc: encodeAdvanceV2({ queryId: 2n, runId: artifact.manifest.runId, expectedAdvanceCount: 1n, expectedStateHash: firstAdvance.nextStateHash, maxInstructions: 1, maxOutputs: 1 }).toBoc({ idx: false }).toString("base64") }, + outbound: [], stateDataBoc: coreStateCell(secondAdvance.state).toBoc({ idx: false }).toString("base64"), + }, + { + hash: "4".repeat(64), lt: "4", prevTransactionHash: "3".repeat(64), prevTransactionLt: "3", success: true, + inbound: { direction: "in", source: peerAddress, bodyBoc: encodeInputAcceptedV2(acknowledgement).toBoc({ idx: false }).toString("base64") }, + outbound: [], stateDataBoc: coreStateCell(acknowledged.state).toBoc({ idx: false }).toString("base64"), + }, + { + hash: "5".repeat(64), lt: "5", prevTransactionHash: "4".repeat(64), prevTransactionLt: "4", success: true, + inbound: { direction: "in", source: peerAddress, bodyBoc: encodeDeliverInputV2(inputMessage).toBoc({ idx: false }).toString("base64") }, + outbound: [{ direction: "out", bodyBoc: encodeMessage({ ...acknowledgement, outputId: inputMessage.outputId, destinationEpoch: inputMessage.destinationEpoch, destinationStateHash: stateHash(delivered.state) }) }], + stateDataBoc: coreStateCell(delivered.state).toBoc({ idx: false }).toString("base64"), + }, + ], + }; + const source = (id: string): RawChainSource => ({ id, fetchSnapshot: async () => ({ ...snapshot, source: id }) }); + const report = await verifyRunFromSources( + deployment.address.toRawString(), + { address: deployment.address.toRawString(), network: "testnet", codeHash: deployment.codeHash, dataHash: deployment.dataHash, stateInitHash: deployment.stateInitHash, programId: artifact.manifest.programId, runId: artifact.manifest.runId, romRoot: artifact.manifest.romRoot, routeRoot: artifact.manifest.routeRoot, staticCommitment: artifact.manifest.staticCommitment }, + [source("protocol-fixture-a"), source("protocol-fixture-b")], + { initialState: deployment.initialState, rom: artifact.assembly.words, peerIdsByAddress: { [peerAddress]: 1 } }, + ); + assert.equal(report.overall, "verified"); + assert.equal(report.deliveredInputs, 1); + assert.equal(report.duplicateDeliveries, 0); + assert.deepEqual(report.replayedMessageKinds, ["advance", "deliverInput", "inputAccepted"]); + assert.equal(report.stateSnapshotsChecked, 4); + assert.equal(report.providerAgreement, true); +}); From a1374c455b8ebfaae518f986146bb9f7295b062f Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:41:38 +0100 Subject: [PATCH 51/67] hardening: bind keeper actions to artifact limits --- packages/keeper/src/planner.ts | 34 ++++++++++++++++++++++++++++++---- packages/keeper/src/types.ts | 4 ++++ tests/v2/keeper.test.ts | 4 ++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/packages/keeper/src/planner.ts b/packages/keeper/src/planner.ts index e271787..2ba1569 100644 --- a/packages/keeper/src/planner.ts +++ b/packages/keeper/src/planner.ts @@ -12,6 +12,7 @@ export class KeeperSafetyError extends Error { export function planNextAction(config: KeeperCoreConfig, observation: KeeperCoreObservation, queryId = 0n): KeeperAction | undefined { assertObservation(config, observation); + if (queryId < 0n) throw new KeeperSafetyError("query id must be non-negative"); if (observation.pendingOutputs.length > 0) { const output = observation.pendingOutputs[0]; if (output === undefined) return undefined; @@ -46,7 +47,7 @@ export function planNextAction(config: KeeperCoreConfig, observation: KeeperCore maxInstructions: config.maxInstructions, maxOutputs: config.maxOutputs, }).toBoc({ idx: false }).toString("base64"), - valueNano: config.dispatchValueNano, + valueNano: config.advanceValueNano, reason: "advance-runnable", }; } @@ -59,9 +60,34 @@ function assertObservation(config: KeeperCoreConfig, observation: KeeperCoreObse throw new KeeperSafetyError(`invalid core address: ${error instanceof Error ? error.message : String(error)}`); } if (observation.network !== "testnet" || config.network !== "testnet") throw new KeeperSafetyError("keeper is restricted to TON testnet"); - if (observation.codeHash !== config.codeHash) throw new KeeperSafetyError("core code hash mismatch"); - if (observation.runId !== config.runId) throw new KeeperSafetyError("core run ID mismatch"); + if (observation.codeHash.toLowerCase() !== config.codeHash.toLowerCase()) throw new KeeperSafetyError("core code hash mismatch"); + if (observation.runId.toLowerCase() !== config.runId.toLowerCase()) throw new KeeperSafetyError("core run ID mismatch"); + assertHash(config.runId, "run ID"); + assertHash(config.codeHash, "code hash"); + assertHash(observation.runId, "observed run ID"); + assertHash(observation.codeHash, "observed code hash"); + assertHash(observation.stateHash, "observed state hash"); + if (!Number.isInteger(config.artifactMaxInstructions) || config.artifactMaxInstructions < 1 || config.artifactMaxInstructions > 0xffff) { + throw new KeeperSafetyError("invalid artifact instruction cap"); + } + if (!Number.isInteger(config.artifactMaxOutputs) || config.artifactMaxOutputs < 1 || config.artifactMaxOutputs > 0xff) { + throw new KeeperSafetyError("invalid artifact output cap"); + } if (!Number.isInteger(config.maxInstructions) || config.maxInstructions < 1 || config.maxInstructions > 0xffff) throw new KeeperSafetyError("invalid instruction cap"); if (!Number.isInteger(config.maxOutputs) || config.maxOutputs < 1 || config.maxOutputs > 0xff) throw new KeeperSafetyError("invalid output cap"); - if (config.dispatchValueNano < 0n) throw new KeeperSafetyError("negative keeper value cap"); + if (config.maxInstructions > config.artifactMaxInstructions) throw new KeeperSafetyError("requested instruction cap exceeds artifact limit"); + if (config.maxOutputs > config.artifactMaxOutputs) throw new KeeperSafetyError("requested output cap exceeds artifact limit"); + if (config.advanceValueNano <= 0n || config.dispatchValueNano <= 0n) throw new KeeperSafetyError("keeper message values must be positive"); + if (observation.advanceCount < 0n) throw new KeeperSafetyError("invalid observed advance count"); + const outputIds = new Set(); + for (const output of observation.pendingOutputs) { + assertHash(output.outputId, "pending output ID"); + const normalized = output.outputId.toLowerCase(); + if (outputIds.has(normalized)) throw new KeeperSafetyError("duplicate pending output ID"); + outputIds.add(normalized); + } +} + +function assertHash(value: string, field: string): void { + if (!/^[0-9a-f]{64}$/i.test(value)) throw new KeeperSafetyError(`${field} must be a 256-bit hexadecimal value`); } diff --git a/packages/keeper/src/types.ts b/packages/keeper/src/types.ts index aeb70b0..f1afde6 100644 --- a/packages/keeper/src/types.ts +++ b/packages/keeper/src/types.ts @@ -6,8 +6,12 @@ export interface KeeperCoreConfig { readonly runId: Hash256; readonly codeHash: Hash256; readonly network: "testnet"; + /** The selected artifact limit; the keeper may never request more. */ + readonly artifactMaxInstructions: number; + readonly artifactMaxOutputs: number; readonly maxInstructions: number; readonly maxOutputs: number; + readonly advanceValueNano: bigint; readonly dispatchValueNano: bigint; } diff --git a/tests/v2/keeper.test.ts b/tests/v2/keeper.test.ts index 38a0109..4ce628a 100644 --- a/tests/v2/keeper.test.ts +++ b/tests/v2/keeper.test.ts @@ -10,8 +10,11 @@ const base = { runId: artifact.manifest.runId, codeHash: "ab".repeat(32), network: "testnet" as const, + artifactMaxInstructions: 1, + artifactMaxOutputs: 16, maxInstructions: 1, maxOutputs: 1, + advanceValueNano: 10_000_000n, dispatchValueNano: 20_000_000n, }; @@ -42,6 +45,7 @@ test("keeper plans a single bounded advance only for a fresh runnable core", () assert.equal(action?.kind, "advance"); assert.equal(action?.expectedAdvanceCount, 4n); assert.throws(() => planNextAction(base, observation({ codeHash: "cd".repeat(32) })), /code hash mismatch/); + assert.throws(() => planNextAction({ ...base, maxInstructions: 2 }, observation()), /exceeds artifact limit/); assert.equal(planNextAction(base, observation({ status: STATUS.waitingInput })), undefined); }); From 34971e179d8011e0d20be2253a7acfd8f3896336 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:44:17 +0100 Subject: [PATCH 52/67] test: drive benchmark runner through bounded advances --- packages/benchmarks/src/runner.ts | 45 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/packages/benchmarks/src/runner.ts b/packages/benchmarks/src/runner.ts index cc29dfe..024950a 100644 --- a/packages/benchmarks/src/runner.ts +++ b/packages/benchmarks/src/runner.ts @@ -1,5 +1,5 @@ import { buildArtifact, type ArtifactBundle, type BuildArtifactOptions } from "../../artifact/src/manifest.js"; -import { acknowledgeOutput, deliverInput, dispatchOutput, step, type V2StepResult } from "../../emulator/src/executor.js"; +import { acknowledgeOutput, advance, deliverInput, dispatchOutput, step, type V2StepResult } from "../../emulator/src/executor.js"; import { createInitialState, type V2State } from "../../emulator/src/model.js"; import { stateHash } from "../../emulator/src/commitments.js"; import { STATUS } from "../../isa/src/constants.js"; @@ -39,20 +39,35 @@ export function runArtifact(artifact: ArtifactBundle, maxInstructions = 10_000): let executedInstructions = 0; const outputs: number[] = []; const frames: TraceFrame[] = []; - for (let index = 0; index < maxInstructions && state.status !== STATUS.halted && state.status !== STATUS.faulted; index += 1) { - const before = state; - const result = step(before, { rom: artifact.assembly.words }); - state = result.state; - if (!result.executed) break; - // Each benchmark frame represents one accepted bounded Advance, not only - // the architectural instruction. Keep protocol counters identical to the - // emulator's Advance transition and the contract state commitment. - state.advanceCount += 1n; - state.acceptedMessageCount += 1n; - executedInstructions += 1; - if (result.legacyOutput !== undefined) outputs.push(result.legacyOutput.value); - if (result.output !== undefined) outputs.push(result.output.value); - frames.push(frame(frames.length, artifact.manifest.coreId, before, state, result)); + while (executedInstructions < maxInstructions && state.status !== STATUS.halted && state.status !== STATUS.faulted) { + const beforeBatch = state; + const requestedSteps = Math.min( + artifact.limits.maxStepsPerAdvance, + maxInstructions - executedInstructions, + ); + const batch = advance(state, { rom: artifact.assembly.words }, { + expectedAdvanceCount: state.advanceCount, + expectedStateHash: stateHash(state), + maxInstructions: requestedSteps, + maxOutputs: artifact.limits.maxOutputsPerAdvance, + }); + if (batch.executed === 0) break; + + // Reconstruct instruction-level frames independently for the visualizer, + // while taking the accepted state and protocol counters from Advance. + let simulation = beforeBatch; + for (let offset = 0; offset < batch.executed; offset += 1) { + const before = simulation; + const result = step(before, { rom: artifact.assembly.words }); + if (!result.executed) break; + simulation = result.state; + const after = offset === batch.executed - 1 ? batch.state : simulation; + if (result.legacyOutput !== undefined) outputs.push(result.legacyOutput.value); + if (result.output !== undefined) outputs.push(result.output.value); + frames.push(frame(frames.length, artifact.manifest.coreId, before, after, result)); + } + state = batch.state; + executedInstructions += batch.executed; } return { name: artifact.manifest.name, From bb087b5236107b4c2e2c8db72b8f2324a953964a Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:53:06 +0100 Subject: [PATCH 53/67] hardening: validate v2 traces and evidence boundaries --- packages/trace/src/schema.ts | 87 +++++++++++++++++++++++++++++++++--- tests/v2/trace.test.ts | 4 ++ 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/packages/trace/src/schema.ts b/packages/trace/src/schema.ts index 52b7764..f58fdfa 100644 --- a/packages/trace/src/schema.ts +++ b/packages/trace/src/schema.ts @@ -65,19 +65,94 @@ export function validateTrace(value: unknown): asserts value is TonolithTraceV2 if (trace.schema !== V2_TRACE_SCHEMA) throw new TypeError("unsupported trace schema"); if (trace.evidence === undefined || !["local-emulator", "acton-sandbox", "local-validator", "testnet"].includes(trace.evidence)) throw new TypeError("invalid trace evidence"); if (trace.network !== "local" && trace.network !== "testnet") throw new TypeError("invalid trace network"); - if (typeof trace.runId !== "string" || !/^[0-9a-f]{64}$/.test(trace.runId)) throw new TypeError("invalid trace run ID"); + if (trace.evidence === "testnet" && trace.network !== "testnet") throw new TypeError("testnet evidence requires a testnet trace"); + if (trace.evidence !== "testnet" && trace.network !== "local") throw new TypeError("local evidence requires a local trace"); + assertHash(trace.runId, "trace run ID"); + assertHash(trace.programId, "trace program ID"); + assertHash(trace.romRoot, "trace ROM root"); + assertHash(trace.routeRoot, "trace route root"); + assertHash(trace.staticCommitment, "trace static commitment"); if (!Array.isArray(trace.frames) || !Array.isArray(trace.coreIds)) throw new TypeError("trace frames and coreIds must be arrays"); + const coreIds = trace.coreIds as readonly unknown[]; + const knownCoreIds = new Set(); + for (const coreId of coreIds) { + assertInteger(coreId, 0, 0xffff, "trace core ID"); + if (knownCoreIds.has(coreId)) throw new TypeError("trace core IDs must be unique"); + knownCoreIds.add(coreId); + } + if (!Array.isArray(trace.unresolved) || trace.unresolved.some((entry) => typeof entry !== "string")) { + throw new TypeError("trace unresolved must be an array of strings"); + } let previous = -1; - for (const frame of trace.frames) { - if (!Number.isInteger(frame.index) || frame.index <= previous) throw new TypeError("trace frame indexes must be strictly increasing"); + const lastAfterByCore = new Map(); + for (const candidate of trace.frames) { + const frame = validateFrame(candidate, knownCoreIds); + if (!Number.isInteger(frame.index) || frame.index < 0 || frame.index <= previous) throw new TypeError("trace frame indexes must be strictly increasing"); previous = frame.index; - validateState(frame.before); - validateState(frame.after); + const prior = lastAfterByCore.get(frame.coreId); + if (prior !== undefined && prior.stateHash !== frame.before.stateHash) { + throw new TypeError(`trace core ${frame.coreId} does not continue from the previous state`); + } + if (BigInt(frame.after.instructionCount) < BigInt(frame.before.instructionCount)) { + throw new TypeError("trace instruction count cannot move backwards"); + } + if (BigInt(frame.after.advanceCount) < BigInt(frame.before.advanceCount)) { + throw new TypeError("trace advance count cannot move backwards"); + } + lastAfterByCore.set(frame.coreId, frame.after); } } function validateState(value: unknown): asserts value is TraceStateSnapshot { - if (value === null || typeof value !== "object" || typeof (value as TraceStateSnapshot).stateHash !== "string") throw new TypeError("invalid trace state snapshot"); + if (value === null || typeof value !== "object") throw new TypeError("invalid trace state snapshot"); + const state = value as Partial; + assertHash(state.stateHash, "trace state hash"); + assertInteger(state.pc, 0, 1023, "trace PC"); + assertInteger(state.accumulator, 0, 15, "trace accumulator"); + assertInteger(state.flags, 0, 3, "trace flags"); + assertDecimal(state.epoch, "trace epoch"); + assertDecimal(state.instructionCount, "trace instruction count"); + assertDecimal(state.advanceCount, "trace advance count"); + assertInteger(state.status, 0, 4, "trace status"); + assertHash(state.outputCommitment, "trace output commitment"); + assertHash(state.inputCommitment, "trace input commitment"); + assertInteger(state.inboxCount, 0, 16, "trace inbox count"); + assertInteger(state.outboxCount, 0, 16, "trace outbox count"); +} + +function validateFrame(value: unknown, coreIds: ReadonlySet): TraceFrame { + if (value === null || typeof value !== "object") throw new TypeError("invalid trace frame"); + const frame = value as Partial; + assertInteger(frame.index, 0, Number.MAX_SAFE_INTEGER, "trace frame index"); + assertInteger(frame.coreId, 0, 0xffff, "trace frame core ID"); + if (!coreIds.has(frame.coreId)) throw new TypeError("trace frame core ID is not declared"); + if (frame.instructionWord !== undefined) assertInteger(frame.instructionWord, 0, 0xffff, "trace instruction word"); + if (frame.opcode !== undefined) assertInteger(frame.opcode, 0, 0x0f, "trace opcode"); + if (frame.sourceLine !== undefined) assertInteger(frame.sourceLine, 0, Number.MAX_SAFE_INTEGER, "trace source line"); + if (frame.phase === undefined || !["none", "committed", "dispatched", "destination-included", "acknowledged", "finalized"].includes(frame.phase)) { + throw new TypeError("invalid trace message phase"); + } + if (frame.outputId !== undefined) assertHash(frame.outputId, "trace output ID"); + if (frame.transactionHash !== undefined && !/^[0-9a-f]{64}$/i.test(frame.transactionHash)) throw new TypeError("invalid trace transaction hash"); + if (frame.transactionFinalized !== undefined && typeof frame.transactionFinalized !== "boolean") throw new TypeError("invalid trace finality flag"); + if (frame.note !== undefined && typeof frame.note !== "string") throw new TypeError("invalid trace note"); + validateState(frame.before); + validateState(frame.after); + return frame as TraceFrame; +} + +function assertHash(value: unknown, field: string): asserts value is Hash256 { + if (typeof value !== "string" || !/^[0-9a-f]{64}$/.test(value)) throw new TypeError(`invalid ${field}`); +} + +function assertDecimal(value: unknown, field: string): asserts value is string { + if (typeof value !== "string" || !/^\d+$/.test(value)) throw new TypeError(`invalid ${field}`); +} + +function assertInteger(value: unknown, minimum: number, maximum: number, field: string): asserts value is number { + if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum || value > maximum) { + throw new TypeError(`invalid ${field}`); + } } function canonicalReplacer(_key: string, value: unknown): unknown { diff --git a/tests/v2/trace.test.ts b/tests/v2/trace.test.ts index af72994..3bade91 100644 --- a/tests/v2/trace.test.ts +++ b/tests/v2/trace.test.ts @@ -22,4 +22,8 @@ test("v2 trace schema is strict, deterministic, and round-trippable", () => { assert.deepEqual(decodeTrace(encoded), trace); assert.throws(() => decodeTrace(encoded.replace("tonolith-trace-v2", "trace-v1")), /unsupported trace schema/); assert.throws(() => decodeTrace(JSON.stringify({ ...trace, frames: [{ ...trace.frames[0], index: 2 }, { ...trace.frames[0], index: 1 }] })), /strictly increasing/); + assert.throws(() => decodeTrace(JSON.stringify({ ...trace, evidence: "testnet" })), /testnet evidence/); + assert.throws(() => decodeTrace(JSON.stringify({ ...trace, frames: [{ ...trace.frames[0], before: { ...state, pc: 1024 } }] })), /trace PC/); + assert.throws(() => decodeTrace(JSON.stringify({ ...trace, coreIds: [0, 0] })), /core IDs must be unique/); + assert.throws(() => decodeTrace(JSON.stringify({ ...trace, frames: [{ ...trace.frames[0], outputId: "00".repeat(31) }] })), /trace output ID/); }); From 736c62ff148c3cb20630b2d4ac514424316560b8 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:54:14 +0100 Subject: [PATCH 54/67] test: verify clean-directory v2 reproducibility --- tests/v2/reproducibility.test.ts | 49 ++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 tests/v2/reproducibility.test.ts diff --git a/tests/v2/reproducibility.test.ts b/tests/v2/reproducibility.test.ts new file mode 100644 index 0000000..51a649c --- /dev/null +++ b/tests/v2/reproducibility.test.ts @@ -0,0 +1,49 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { buildArtifact, verifyArtifact } from "../../packages/artifact/src/manifest.js"; +import { loadArtifactDirectorySync } from "../../packages/sdk/src/node.js"; + +const compiler = { + name: "tonolith-assembler", + version: "2.0.0-test", + gitCommit: "b".repeat(40), + node: "v26.7.0", +}; + +test("v2 artifacts reproduce byte-for-byte in two clean directories", async () => { + const artifact = buildArtifact(".entry start\nstart:\nLDI 1\nOUT\nHALT\n", { + name: "clean-repro-fixture", + fileName: "repro.tasm", + compiler, + }); + assert.equal(verifyArtifact(artifact).valid, true); + const root = await mkdtemp(join(tmpdir(), "tonolith-v2-repro-")); + try { + const directories = [join(root, "clean-a"), join(root, "clean-b")]; + for (const directory of directories) { + await mkdir(directory, { recursive: true }); + await Promise.all(Object.entries(artifact.files).map(async ([name, contents]) => { + await writeFile(join(directory, name), contents, "utf8"); + })); + } + const first = loadArtifactDirectorySync(directories[0]!); + const second = loadArtifactDirectorySync(directories[1]!); + assert.deepEqual(first.manifest, second.manifest); + assert.deepEqual(Object.keys(first.files).sort(), Object.keys(second.files).sort()); + for (const name of Object.keys(first.files).sort()) { + const firstBytes = await readFile(join(directories[0]!, name)); + const secondBytes = await readFile(join(directories[1]!, name)); + assert.equal(sha256(firstBytes), sha256(secondBytes), `generated file differs: ${name}`); + } + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +function sha256(value: Uint8Array): string { + return createHash("sha256").update(value).digest("hex"); +} From c24595a2fa3f7adca85bbb552a9910d5a359d841 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:58:49 +0100 Subject: [PATCH 55/67] test: add recorded-seed v2 property corpus --- spec/v2/fuzz-seeds.json | 12 +++ tests/v2/property.test.ts | 149 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 spec/v2/fuzz-seeds.json create mode 100644 tests/v2/property.test.ts diff --git a/spec/v2/fuzz-seeds.json b/spec/v2/fuzz-seeds.json new file mode 100644 index 0000000..a7d2384 --- /dev/null +++ b/spec/v2/fuzz-seeds.json @@ -0,0 +1,12 @@ +{ + "schema": "tonolith-fuzz-seeds-v2", + "property": { + "seed": "0x7a11ce55", + "iterations": 10000 + }, + "statefulBatch": { + "seed": "0x4b41544f", + "iterations": 2048, + "batchSize": 4 + } +} diff --git a/tests/v2/property.test.ts b/tests/v2/property.test.ts new file mode 100644 index 0000000..0eaf1f7 --- /dev/null +++ b/tests/v2/property.test.ts @@ -0,0 +1,149 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; +import { encodeInstruction, encodePortInstruction, encodeTrap } from "../../packages/isa/src/codec.js"; +import { Opcode } from "../../packages/isa/src/types.js"; +import { SYS } from "../../packages/isa/src/constants.js"; +import { advance, step } from "../../packages/emulator/src/executor.js"; +import { stateHash } from "../../packages/emulator/src/commitments.js"; +import { assertStateBounds, cloneState, createInitialState, type CoreConfig, type V2State } from "../../packages/emulator/src/model.js"; + +interface FuzzSeeds { + readonly property: { readonly seed: string; readonly iterations: number }; + readonly statefulBatch: { readonly seed: string; readonly iterations: number; readonly batchSize: number }; +} + +test("v2 recorded-seed property traces preserve step invariants", async () => { + const seeds = JSON.parse(await readFile(new URL("../../spec/v2/fuzz-seeds.json", import.meta.url), "utf8")) as FuzzSeeds; + let random = seedFrom(seeds.property.seed); + for (let iteration = 0; iteration < seeds.property.iterations; iteration += 1) { + const state = randomState(random); + random = state.random; + const word = randomWord(() => { + const next = nextRandom(random); + random = next.random; + return next.value; + }); + const before = cloneState(state.state); + const result = step(state.state, { rom: [word, ...new Array(1023).fill(0)] }); + assert.deepEqual(state.state, before, `input mutated at iteration ${iteration}`); + assertStateBounds(result.state); + const replay = step(before, { rom: [word, ...new Array(1023).fill(0)] }); + assert.equal(stateHash(result.state), stateHash(replay.state), `non-deterministic result at iteration ${iteration}`); + if (result.executed) { + assert.equal(result.state.instructionCount, before.instructionCount + 1n); + assert.equal(result.state.pc >= 0 && result.state.pc < 1024, true); + } else { + assert.equal(result.state.instructionCount, before.instructionCount); + assert.equal(result.state.pc, before.pc); + } + } +}); + +test("v2 recorded-seed batches agree with repeated single-step execution", async () => { + const seeds = JSON.parse(await readFile(new URL("../../spec/v2/fuzz-seeds.json", import.meta.url), "utf8")) as FuzzSeeds; + let random = seedFrom(seeds.statefulBatch.seed); + for (let iteration = 0; iteration < seeds.statefulBatch.iterations; iteration += 1) { + const words: number[] = []; + for (let index = 0; index < seeds.statefulBatch.batchSize; index += 1) { + const next = nextRandom(random); + random = next.random; + words.push(randomNonStoppingWord(() => { + const value = nextRandom(random); + random = value.random; + return value.value; + })); + } + const generatedState = randomState(random); + const initial = generatedState.state; + random = generatedState.random; + const environment = { rom: [...words, ...new Array(1024 - words.length).fill(0)] }; + const batched = advance(initial, environment, { + expectedAdvanceCount: initial.advanceCount, + expectedStateHash: stateHash(initial), + maxInstructions: seeds.statefulBatch.batchSize, + maxOutputs: 16, + }); + let repeated = initial; + for (let index = 0; index < seeds.statefulBatch.batchSize; index += 1) { + repeated = step(repeated, environment).state; + } + repeated.advanceCount += 1n; + repeated.acceptedMessageCount += 1n; + assert.equal(batched.executed, seeds.statefulBatch.batchSize, `batch stopped at iteration ${iteration}`); + assert.deepEqual(stripProtocolCounters(batched.state), stripProtocolCounters(repeated)); + assert.equal(batched.state.advanceCount, repeated.advanceCount); + assert.equal(batched.state.acceptedMessageCount, repeated.acceptedMessageCount); + } +}); + +function randomState(random: number): { readonly random: number; readonly state: V2State } { + const state = createInitialState(config()); + const values: number[] = []; + for (let index = 0; index < 2 + 16 + 256; index += 1) { + const next = nextRandom(random); + random = next.random; + values.push(next.value & 0x0f); + } + state.accumulator = values[0]!; + state.flags = values[1]! & 3; + state.registers = values.slice(2, 18); + state.ram = Uint8Array.from(values.slice(18)); + state.pc = 0; + return { random, state }; +} + +function randomWord(random: () => number): number { + const opcode = random() % 16; + if (opcode === Opcode.SYS) { + const subop = random() % 13; + if (subop === SYS.IN || subop === SYS.OUTP) return encodePortInstruction(subop, random() % 16); + if (subop === SYS.TRAP) return encodeTrap(random() & 0xff); + return encodeInstruction(Opcode.SYS, subop); + } + if (opcode === Opcode.LDI || opcode >= Opcode.LDR && opcode <= Opcode.XOR) return encodeInstruction(opcode, random() % 16); + if (opcode === Opcode.LDM || opcode === Opcode.STM) return encodeInstruction(opcode, random() % 256); + if (opcode >= Opcode.JMP && opcode <= Opcode.JC) return encodeInstruction(opcode, random() % 1024); + return encodeInstruction(opcode); +} + +function randomNonStoppingWord(random: () => number): number { + const opcodes = [Opcode.NOP, Opcode.LDI, Opcode.LDR, Opcode.STR, Opcode.ADD, Opcode.ADC, Opcode.SUB, Opcode.AND, Opcode.OR, Opcode.XOR, Opcode.LDM, Opcode.STM, Opcode.SYS]; + const opcode = opcodes[random() % opcodes.length]!; + if (opcode === Opcode.SYS) { + const controls = [SYS.CLC, SYS.STC, SYS.NOT, SYS.SHL, SYS.SHR, SYS.INC, SYS.DEC]; + return encodeInstruction(Opcode.SYS, controls[random() % controls.length]!); + } + if (opcode === Opcode.LDI || opcode >= Opcode.LDR && opcode <= Opcode.XOR) return encodeInstruction(opcode, random() % 16); + if (opcode === Opcode.LDM || opcode === Opcode.STM) return encodeInstruction(opcode, random() % 256); + return encodeInstruction(opcode); +} + +function config(): CoreConfig { + return { + runId: "11".repeat(32), + coreId: 0, + programId: "22".repeat(32), + romRoot: "33".repeat(32), + initialRamRoot: "44".repeat(32), + routeRoot: "55".repeat(32), + staticCommitment: "66".repeat(32), + routes: Array.from({ length: 16 }, (_, sourcePort) => ({ sourceCoreId: 0, sourcePort, destinationCoreId: 1, destinationPort: sourcePort, delay: 1, maxRecordsPerEpoch: 16 })), + requiredInputs: [], + maxStepsPerAdvance: 4, + }; +} + +function stripProtocolCounters(state: V2State): unknown { + const { advanceCount: _advanceCount, acceptedMessageCount: _acceptedMessageCount, ...architectural } = state; + return architectural; +} + +function seedFrom(value: string): number { + return Number.parseInt(value.replace(/^0x/, ""), 16) >>> 0; +} + +function nextRandom(random: number): { readonly random: number; readonly value: number } { + const next = (Math.imul(random, 1_664_525) + 1_013_904_223) >>> 0; + return { random: next, value: next }; +} From c06cb54e940874b54bc63e62eb7670edc9ee8eb6 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Sun, 16 Aug 2026 23:59:47 +0100 Subject: [PATCH 56/67] hardening: bind v2 StateInit to selected batch limit --- packages/sdk/src/stateinit.ts | 9 ++++++++- tests/v2/stateinit.test.ts | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/stateinit.ts b/packages/sdk/src/stateinit.ts index db4c514..778d901 100644 --- a/packages/sdk/src/stateinit.ts +++ b/packages/sdk/src/stateinit.ts @@ -10,6 +10,7 @@ import { staticCommitmentCell } from "../../artifact/src/commitments.js"; import { peerRoot, peerRootCell, type PeerDescriptor } from "../../artifact/src/peers.js"; import { routeRootCell, type RouteDescriptor } from "../../artifact/src/routes.js"; import { normalizeRam, ramRoot, romRootCell } from "../../artifact/src/memory.js"; +import { INITIAL_MAX_STEPS_PER_ADVANCE } from "../../isa/src/constants.js"; import { createInitialState, type CoreConfig } from "../../emulator/src/model.js"; import { coreStateCell, stateHash } from "../../emulator/src/commitments.js"; import type { ArtifactBundle, ArtifactManifestV2 } from "../../artifact/src/manifest.js"; @@ -34,6 +35,7 @@ export interface V2StateInitDescriptor { readonly routes: readonly RouteDescriptor[]; readonly peers: readonly PeerDescriptor[]; readonly initialRam?: readonly number[] | Uint8Array; + readonly maxStepsPerAdvance?: number; readonly workchain?: number; } @@ -73,6 +75,10 @@ export function buildV2StateInit(code: Cell, descriptor: V2StateInitDescriptor): }); const computedStatic = staticCell.hash().toString("hex"); if (computedStatic !== descriptor.manifest.staticCommitment) throw new Error("static commitment does not match the deployment manifest"); + const maxStepsPerAdvance = descriptor.maxStepsPerAdvance ?? INITIAL_MAX_STEPS_PER_ADVANCE; + if (maxStepsPerAdvance !== INITIAL_MAX_STEPS_PER_ADVANCE) { + throw new Error(`v2 StateInit only accepts the selected single-step limit (${INITIAL_MAX_STEPS_PER_ADVANCE})`); + } const config: CoreConfig = { runId: descriptor.manifest.runId, @@ -84,7 +90,7 @@ export function buildV2StateInit(code: Cell, descriptor: V2StateInitDescriptor): staticCommitment: descriptor.manifest.staticCommitment, routes: normalizedRoutes, requiredInputs: [], - maxStepsPerAdvance: 1, + maxStepsPerAdvance, }; const initialState = createInitialState(config); initialState.ram = normalizeRam(descriptor.initialRam ?? []); @@ -143,6 +149,7 @@ export function buildV2StateInitFromArtifact(code: Cell, artifact: ArtifactBundl routes: artifact.routes, peers: artifact.peers, initialRam: artifact.assembly.ram, + maxStepsPerAdvance: artifact.limits.maxStepsPerAdvance, workchain, }); } diff --git a/tests/v2/stateinit.test.ts b/tests/v2/stateinit.test.ts index 55197e8..1701125 100644 --- a/tests/v2/stateinit.test.ts +++ b/tests/v2/stateinit.test.ts @@ -29,9 +29,23 @@ test("v2 StateInit and address derivation are deterministic and content-bound", assert.equal(first.initialStateHash, second.initialStateHash); assert.equal(first.staticCommitment, artifact.manifest.staticCommitment); assert.equal(first.initialState.config.staticCommitment, artifact.manifest.staticCommitment); + assert.equal(first.initialState.config.maxStepsPerAdvance, artifact.limits.maxStepsPerAdvance); assert.equal(Cell.fromBoc(first.stateInitCell.toBoc({ idx: false }))[0]?.hash().toString("hex"), first.stateInitHash); }); +test("v2 StateInit rejects artifacts outside the selected execution limit", () => { + const artifact = buildArtifact("NOP\n", { + name: "batch-limit-fixture", + compiler, + limits: { maxStepsPerAdvance: 2 }, + }); + const code = beginCell().storeUint(1, 1).endCell(); + assert.throws( + () => buildV2StateInitFromArtifact(code, artifact), + /selected single-step limit/, + ); +}); + test("peer addresses are bound to the static commitment and StateInit data", () => { const artifact = buildArtifact("NOP\n", { name: "peer-fixture", From f01cfae427b6ec6fa16caaf3468a0ded0ea5956d Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:19:04 +0100 Subject: [PATCH 57/67] bench: refresh v2 gas and fee snapshot --- benchmarks/gas-v2.json | 4957 +++++++++++++++++++++++++++------------- 1 file changed, 3349 insertions(+), 1608 deletions(-) diff --git a/benchmarks/gas-v2.json b/benchmarks/gas-v2.json index 86b6c6d..905ca4d 100644 --- a/benchmarks/gas-v2.json +++ b/benchmarks/gas-v2.json @@ -1,29 +1,164 @@ { - "timestamp": 1786911290, + "timestamp": 1786922309, "opcodes": { + "V2DispatchOutput": { + "min_gas": 1877, + "max_gas": 17899, + "avg_gas": 8728, + "samples": 5, + "all_values": [ + 17899, + 1877, + 7954, + 7954, + 7954 + ] + }, + "V2TopUp": { + "min_gas": 2546, + "max_gas": 2946, + "avg_gas": 2798, + "samples": 3, + "all_values": [ + 2901, + 2546, + 2946 + ] + }, + "0x99999999": { + "min_gas": 1823, + "max_gas": 1823, + "avg_gas": 1823, + "samples": 1, + "all_values": [ + 1823 + ] + }, + "0x00000999": { + "min_gas": 1332, + "max_gas": 1332, + "avg_gas": 1332, + "samples": 1, + "all_values": [ + 1332 + ] + }, + "V2DeliverInput": { + "min_gas": 309, + "max_gas": 49066, + "avg_gas": 16819, + "samples": 20, + "all_values": [ + 35038, + 49066, + 2719, + 2812, + 3610, + 3706, + 7940, + 7442, + 17628, + 17050, + 11398, + 11468, + 13412, + 14228, + 35038, + 22045, + 35038, + 24501, + 21927, + 309 + ] + }, "V2Advance": { - "min_gas": 4578, - "max_gas": 44381, - "avg_gas": 24852, - "samples": 7, + "min_gas": 1844, + "max_gas": 47997, + "avg_gas": 22332, + "samples": 73, "all_values": [ - 24434, - 4726, - 25921, - 4578, - 44381, - 28597, - 41329 + 30832, + 33805, + 30896, + 44756, + 27040, + 27092, + 28508, + 28482, + 28706, + 30864, + 13195, + 13352, + 13195, + 13890, + 14228, + 14942, + 20106, + 23081, + 29537, + 27740, + 4744, + 27043, + 27043, + 1844, + 1999, + 2051, + 2206, + 2258, + 2351, + 3620, + 4744, + 4848, + 2808, + 2886, + 2964, + 3042, + 3230, + 4337, + 4441, + 4648, + 29974, + 29152, + 4596, + 30135, + 30215, + 31029, + 31141, + 31185, + 31265, + 31435, + 30832, + 33805, + 30896, + 30049, + 44635, + 47997, + 27043, + 27740, + 29894, + 29444, + 30686, + 30864, + 30838, + 30856, + 30926, + 31032, + 30249, + 31993, + 28255, + 28482, + 28680, + 47997, + 29537 ] }, - "TopUp": { - "min_gas": 1328, - "max_gas": 1373, - "avg_gas": 1351, - "samples": 2, + "0x00020000": { + "min_gas": 1938, + "max_gas": 1938, + "avg_gas": 1938, + "samples": 1, "all_values": [ - 1328, - 1373 + 1938 ] }, "Advance": { @@ -32,22 +167,6 @@ "avg_gas": 15266, "samples": 198, "all_values": [ - 15477, - 15330, - 15477, - 16755, - 16923, - 16897, - 16915, - 16985, - 17091, - 17536, - 17456, - 18290, - 18512, - 18556, - 18636, - 18688, 15477, 15330, 15767, @@ -229,61 +348,63 @@ 16897, 16393, 16831, - 16032 - ] - }, - "0x00000999": { - "min_gas": 1332, - "max_gas": 1332, - "avg_gas": 1332, - "samples": 1, - "all_values": [ - 1332 + 16032, + 15477, + 15330, + 15477, + 16755, + 16923, + 16897, + 16915, + 16985, + 17091, + 17536, + 17456, + 18290, + 18512, + 18556, + 18636, + 18688 ] }, "V2InputAccepted": { "min_gas": 309, - "max_gas": 22983, - "avg_gas": 7699, - "samples": 4, + "max_gas": 23418, + "avg_gas": 7219, + "samples": 9, "all_values": [ - 7196, - 22983, + 309, + 309, + 309, + 7396, + 23183, + 9430, + 23418, 309, 309 ] }, - "V2DeliverInput": { - "min_gas": 21758, - "max_gas": 31321, - "avg_gas": 26243, - "samples": 3, + "TopUp": { + "min_gas": 1328, + "max_gas": 1373, + "avg_gas": 1351, + "samples": 2, "all_values": [ - 31321, - 25651, - 21758 + 1373, + 1328 ] } }, "trace_chains": { - "test Fibonacci contract emits the canonical output trace::trace#74": { + "test Fibonacci contract emits the canonical output trace::trace#23": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 74", + "trace_name": "Trace 23", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test arithmetic and flag boundary outcomes::trace#10": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 10", - "tx_count": 2, - "total_gas_used": 17232, - "total_gas_fees": 1148802, - "total_forward_fees": 112536, - "total_fees": 1298424 - }, "test Fibonacci contract emits the canonical output trace::trace#63": { "test_name": "test Fibonacci contract emits the canonical output trace", "trace_name": "Trace 63", @@ -293,62 +414,71 @@ "total_forward_fees": 112536, "total_fees": 1192224 }, - "test JC false path and maximum jump target::trace#2": { - "test_name": "test JC false path and maximum jump target", - "trace_name": "Trace 2", + "test exact arithmetic carry and subtraction boundaries::trace#5": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test stale and invalid advance requests do not mutate state::trace#6": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 6", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 1777, + "total_gas_fees": 118468, + "total_forward_fees": 68090, + "total_fees": 118468 }, - "test Fibonacci contract emits the canonical output trace::trace#98": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 98", + "test OUT updates the commitment and emits a bounded event::trace#1": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test ROM and RAM boundary pages remain addressable::trace#10": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 16676, - "total_gas_fees": 1111735, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1261357 + "total_fees": 1202024 }, - "test JC false path and maximum jump target::trace#5": { - "test_name": "test JC false path and maximum jump target", - "trace_name": "Trace 5", + "test named failure paths and halted rejection::trace#12": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 12", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test named failure paths and halted rejection::trace#15": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 15", - "tx_count": 1, - "total_gas_used": 7813, - "total_gas_fees": 520868, - "total_forward_fees": 68090, - "total_fees": 520868 - }, - "test named failure paths and halted rejection::trace#31": { + "test named failure paths and halted rejection::trace#33": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 31", - "tx_count": 1, - "total_gas_used": 12045, - "total_gas_fees": 803001, - "total_forward_fees": 68090, - "total_fees": 803001 + "trace_name": "Trace 33", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 }, - "test external messages are rejected::trace#1": { - "test_name": "test external messages are rejected", - "trace_name": "Trace 1", + "test v2 advance validation and storage guards::trace#24": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 24", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 93069, - "total_fees": 20601 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 }, - "test ROM and RAM boundary pages remain addressable::trace#2": { - "test_name": "test ROM and RAM boundary pages remain addressable", + "test deploy exposes Tonolith metadata::trace#2": { + "test_name": "test deploy exposes Tonolith metadata", "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, @@ -356,32 +486,32 @@ "total_forward_fees": 1195877, "total_fees": 63401 }, - "test register load jump and ADC without carry::trace#11": { + "test register load jump and ADC without carry::trace#9": { "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test arithmetic and flag boundary outcomes::trace#11": { + "test_name": "test arithmetic and flag boundary outcomes", "trace_name": "Trace 11", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 17206, + "total_gas_fees": 1147069, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1296691 }, - "test named failure paths and halted rejection::trace#23": { + "test named failure paths and halted rejection::trace#37": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 23", + "trace_name": "Trace 37", "tx_count": 1, - "total_gas_used": 9147, - "total_gas_fees": 609801, - "total_forward_fees": 68090, - "total_fees": 609801 - }, - "test Fibonacci contract emits the canonical output trace::trace#48": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 48", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 }, "test v2 advance executes a canonical instruction and rejects stale state::trace#2": { "test_name": "test v2 advance executes a canonical instruction and rejects stale state", @@ -389,20 +519,20 @@ "tx_count": 1, "total_gas_used": 1051, "total_gas_fees": 70068, - "total_forward_fees": 3625451, + "total_forward_fees": 4318034, "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#20": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 20", + "test v2 inbox insertion is canonical under out of order delivery::trace#5": { + "test_name": "test v2 inbox insertion is canonical under out of order delivery", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "total_gas_used": 45065, + "total_gas_fees": 3004335, + "total_forward_fees": 124981, + "total_fees": 3203424 }, - "test INC sets carry and zero on nibble wrap::trace#1": { - "test_name": "test INC sets carry and zero on nibble wrap", + "test v2 bounded advance traverses every nonblocking ISA family::trace#1": { + "test_name": "test v2 bounded advance traverses every nonblocking ISA family", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, @@ -410,131 +540,104 @@ "total_forward_fees": 94491, "total_fees": 20601 }, - "test OUT updates the commitment and emits a bounded event::trace#1": { - "test_name": "test OUT updates the commitment and emits a bounded event", - "trace_name": "Trace 1", - "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92357, - "total_fees": 20601 - }, - "test Fibonacci contract emits the canonical output trace::trace#81": { + "test Fibonacci contract emits the canonical output trace::trace#66": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 81", + "trace_name": "Trace 66", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1553491 }, - "test v2 deploy exposes immutable metadata and a canonical initial state::trace#1": { - "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", - "trace_name": "Trace 1", + "test v2 execution edge paths preserve bounded progress::trace#4": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 94135, - "total_fees": 20601 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#18": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 18", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "test v2 OUTP appends a routed output and commits it::trace#5": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 1877, + "total_gas_fees": 125134, + "total_forward_fees": 88002, + "total_fees": 125134 }, - "test Fibonacci contract emits the canonical output trace::trace#42": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 42", + "test v2 inherited opcode families execute through the contract::trace#7": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, - "total_forward_fees": 112536, - "total_fees": 1259624 + "total_gas_used": 30203, + "total_gas_fees": 2013535, + "total_forward_fees": 124981, + "total_fees": 2212624 }, - "test JC false path and maximum jump target::trace#1": { - "test_name": "test JC false path and maximum jump target", - "trace_name": "Trace 1", + "test v2 acknowledgement retires output and rejects wrong peer::trace#5": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 5", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 94847, - "total_fees": 20601 + "total_gas_used": 23183, + "total_gas_fees": 1545534, + "total_forward_fees": 96002, + "total_fees": 1682868 }, - "test named failure paths and halted rejection::trace#28": { + "test v2 bounced delivery keeps the outbox retryable::trace#3": { + "test_name": "test v2 bounced delivery keeps the outbox retryable", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1938, + "total_gas_fees": 129201, + "total_forward_fees": 103291, + "total_fees": 129201 + }, + "test v2 inherited opcode families execute through the contract::trace#13": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 31173, + "total_gas_fees": 2078202, + "total_forward_fees": 124981, + "total_fees": 2277291 + }, + "test named failure paths and halted rejection::trace#20": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 28", + "trace_name": "Trace 20", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test RAM and branch opcodes::trace#10": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 10", - "tx_count": 2, - "total_gas_used": 16728, - "total_gas_fees": 1115202, - "total_forward_fees": 112536, - "total_fees": 1264824 - }, - "test Fibonacci contract emits the canonical output trace::trace#14": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 14", - "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, - "total_forward_fees": 112536, - "total_fees": 1259624 - }, - "test Fibonacci contract emits the canonical output trace::trace#91": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 91", - "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 - }, - "test Fibonacci contract emits the canonical output trace::trace#97": { + "test Fibonacci contract emits the canonical output trace::trace#62": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 97", + "trace_name": "Trace 62", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#2": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 2", + "test v2 inherited opcode families execute through the contract::trace#28": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 28", "tx_count": 1, "total_gas_used": 1051, "total_gas_fees": 70068, - "total_forward_fees": 3675185, + "total_forward_fees": 4318034, "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#9": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 9", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 - }, - "test named failure paths and halted rejection::trace#2": { + "test named failure paths and halted rejection::trace#23": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 2", + "trace_name": "Trace 23", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1195877, - "total_fees": 63401 + "total_gas_used": 9147, + "total_gas_fees": 609801, + "total_forward_fees": 68090, + "total_fees": 609801 }, "test named failure paths and halted rejection::trace#10": { "test_name": "test named failure paths and halted rejection", @@ -545,296 +648,359 @@ "total_forward_fees": 112536, "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#75": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 75", + "test external messages are rejected::trace#1": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93069, + "total_fees": 20601 + }, + "test v2 OUTP appends a routed output and commits it::trace#6": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 7954, + "total_gas_fees": 530268, + "total_forward_fees": 88002, + "total_fees": 530268 + }, + "test arithmetic and flag boundary outcomes::trace#4": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 4", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test INC sets carry and zero on nibble wrap::trace#3": { - "test_name": "test INC sets carry and zero on nibble wrap", + "test named failure paths and halted rejection::trace#32": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 32", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test JC false path and maximum jump target::trace#3": { + "test_name": "test JC false path and maximum jump target", "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 16702, + "total_gas_fees": 1113469, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1263091 }, - "test stale and invalid advance requests do not mutate state::trace#1": { - "test_name": "test stale and invalid advance requests do not mutate state", + "test Fibonacci contract emits the canonical output trace::trace#76": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 76", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test arithmetic and flag boundary outcomes::trace#1": { + "test_name": "test arithmetic and flag boundary outcomes", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 92002, + "total_forward_fees": 96980, "total_fees": 20601 }, - "test Fibonacci contract emits the canonical output trace::trace#1": { - "test_name": "test Fibonacci contract emits the canonical output trace", + "test exact arithmetic carry and subtraction boundaries::trace#1": { + "test_name": "test exact arithmetic carry and subtraction boundaries", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 93424, + "total_forward_fees": 99113, "total_fees": 20601 }, - "test exact arithmetic carry and subtraction boundaries::trace#6": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 6", - "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, - "total_forward_fees": 112536, - "total_fees": 1287224 - }, - "test Fibonacci contract emits the canonical output trace::trace#96": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 96", - "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, - "total_forward_fees": 112536, - "total_fees": 1416091 + "test v2 delivery validation rejects malformed routes epochs and replays::trace#12": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 11398, + "total_gas_fees": 759868, + "total_forward_fees": 103291, + "total_fees": 759868 }, - "test Fibonacci contract emits the canonical output trace::trace#34": { + "test Fibonacci contract emits the canonical output trace::trace#85": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 34", + "trace_name": "Trace 85", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test arithmetic logical and SYS opcodes::trace#11": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 11", + "test JC false path and maximum jump target::trace#1": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test register load jump and ADC without carry::trace#6": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 6", "tx_count": 2, - "total_gas_used": 17400, - "total_gas_fees": 1160002, + "total_gas_used": 16341, + "total_gas_fees": 1089402, "total_forward_fees": 112536, - "total_fees": 1309624 + "total_fees": 1239024 }, - "test top up message preserves metadata::trace#1": { - "test_name": "test top up message preserves metadata", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#17": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 31141, + "total_gas_fees": 2076069, + "total_forward_fees": 124981, + "total_fees": 2275158 + }, + "test v2 bounded advance records outbox backpressure without executing::trace#1": { + "test_name": "test v2 bounded advance records outbox backpressure without executing", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 89869, + "total_forward_fees": 96980, "total_fees": 20601 }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#6": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 6", + "test v2 bounded structures cover every fixed slot and selector::trace#1": { + "test_name": "test v2 bounded structures cover every fixed slot and selector", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test v2 inherited opcode families execute through the contract::trace#15": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 15", "tx_count": 2, - "total_gas_used": 41638, - "total_gas_fees": 2775869, + "total_gas_used": 31147, + "total_gas_fees": 2076469, "total_forward_fees": 124981, - "total_fees": 2974958 + "total_fees": 2275558 }, - "test named failure paths and halted rejection::trace#22": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 22", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "test v2 inherited opcode families execute through the contract::trace#19": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 19", + "tx_count": 2, + "total_gas_used": 31235, + "total_gas_fees": 2082335, + "total_forward_fees": 124981, + "total_fees": 2281424 }, - "test Fibonacci contract emits the canonical output trace::trace#29": { - "test_name": "test Fibonacci contract emits the canonical output trace", + "test v2 inherited opcode families execute through the contract::trace#29": { + "test_name": "test v2 inherited opcode families execute through the contract", "trace_name": "Trace 29", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "total_gas_used": 28791, + "total_gas_fees": 1919402, + "total_forward_fees": 124981, + "total_fees": 2118491 }, - "test arithmetic and flag boundary outcomes::trace#16": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 16", - "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 - }, - "test OUT updates the commitment and emits a bounded event::trace#3": { - "test_name": "test OUT updates the commitment and emits a bounded event", - "trace_name": "Trace 3", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#13": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 13", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 - }, - "test named failure paths and halted rejection::trace#27": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 27", - "tx_count": 1, - "total_gas_used": 8256, - "total_gas_fees": 550401, - "total_forward_fees": 68090, - "total_fees": 550401 + "total_gas_used": 31574, + "total_gas_fees": 2104935, + "total_forward_fees": 124981, + "total_fees": 2304024 }, - "test Fibonacci contract emits the canonical output trace::trace#69": { + "test Fibonacci contract emits the canonical output trace::trace#21": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 69", + "trace_name": "Trace 21", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test named failure paths and halted rejection::trace#37": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 37", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1195877, - "total_fees": 63401 - }, - "test named failure paths and halted rejection::trace#9": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 9", - "tx_count": 1, - "total_gas_used": 1870, - "total_gas_fees": 124668, - "total_forward_fees": 68090, - "total_fees": 124668 - }, - "test Fibonacci contract emits the canonical output trace::trace#5": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 5", + "test register load jump and ADC without carry::trace#11": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 11", "tx_count": 2, "total_gas_used": 15786, "total_gas_fees": 1052402, "total_forward_fees": 112536, "total_fees": 1202024 }, - "test arithmetic and flag boundary outcomes::trace#9": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 9", - "tx_count": 2, - "total_gas_used": 17845, - "total_gas_fees": 1189669, - "total_forward_fees": 112536, - "total_fees": 1339291 + "test v2 receiver selectors topup and external rejection::trace#7": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 2546, + "total_gas_fees": 169734, + "total_forward_fees": 65246, + "total_fees": 169734 }, - "test Fibonacci contract emits the canonical output trace::trace#61": { + "test Fibonacci contract emits the canonical output trace::trace#34": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 61", + "trace_name": "Trace 34", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#66": { + "test Fibonacci contract emits the canonical output trace::trace#94": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 66", + "trace_name": "Trace 94", "tx_count": 2, "total_gas_used": 19538, "total_gas_fees": 1302535, "total_forward_fees": 112536, "total_fees": 1553491 }, - "test arithmetic and flag boundary outcomes::trace#1": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 1", + "test v2 execution edge paths preserve bounded progress::trace#14": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 14", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 96980, - "total_fees": 20601 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#7": { + "test Fibonacci contract emits the canonical output trace::trace#77": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 7", - "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 - }, - "test register load jump and ADC without carry::trace#7": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 7", + "trace_name": "Trace 77", "tx_count": 2, - "total_gas_used": 16702, - "total_gas_fees": 1113469, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1263091 + "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#35": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 35", + "test arithmetic logical and SYS opcodes::trace#4": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 4", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#43": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 43", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "test v2 delivery validation rejects malformed routes epochs and replays::trace#4": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 2812, + "total_gas_fees": 187468, + "total_forward_fees": 103291, + "total_fees": 187468 }, - "test Fibonacci contract emits the canonical output trace::trace#93": { + "test v2 execution edge paths preserve bounded progress::trace#26": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 26", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4350168, + "total_fees": 70068 + }, + "test v2 advance validation and storage guards::trace#12": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test v2 advance validation and storage guards::trace#19": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 19", + "tx_count": 1, + "total_gas_used": 3042, + "total_gas_fees": 202801, + "total_forward_fees": 80535, + "total_fees": 202801 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#20": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 20", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#14": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 13412, + "total_gas_fees": 894134, + "total_forward_fees": 103291, + "total_fees": 894134 + }, + "test Fibonacci contract emits the canonical output trace::trace#32": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 93", + "trace_name": "Trace 32", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#7": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 7", + "test named failure paths and halted rejection::trace#39": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 39", "tx_count": 1, - "total_gas_used": 21758, - "total_gas_fees": 1450534, - "total_forward_fees": 103291, - "total_fees": 1450534 + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 }, - "test named failure paths and halted rejection::trace#42": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 42", + "test v2 receiver selectors topup and external rejection::trace#4": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 3491, - "total_gas_fees": 232734, - "total_forward_fees": 68090, - "total_fees": 232734 + "total_gas_used": 1218, + "total_gas_fees": 81201, + "total_forward_fees": 44446, + "total_fees": 81201 }, - "test stale and invalid advance requests do not mutate state::trace#3": { - "test_name": "test stale and invalid advance requests do not mutate state", - "trace_name": "Trace 3", + "test Fibonacci contract emits the canonical output trace::trace#64": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 64", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test register load jump and ADC without carry::trace#8": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 8", "tx_count": 2, "total_gas_used": 15039, "total_gas_fees": 1002602, "total_forward_fees": 112536, "total_fees": 1152224 }, - "test named failure paths and halted rejection::trace#25": { + "test named failure paths and halted rejection::trace#7": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 25", + "trace_name": "Trace 7", "tx_count": 1, - "total_gas_used": 8560, - "total_gas_fees": 570668, - "total_forward_fees": 68090, - "total_fees": 570668 + "total_gas_used": 1373, + "total_gas_fees": 91534, + "total_forward_fees": 44446, + "total_fees": 91534 }, - "test exact arithmetic carry and subtraction boundaries::trace#4": { - "test_name": "test exact arithmetic carry and subtraction boundaries", + "test Fibonacci contract emits the canonical output trace::trace#4": { + "test_name": "test Fibonacci contract emits the canonical output trace", "trace_name": "Trace 4", "tx_count": 2, "total_gas_used": 15639, @@ -842,233 +1008,323 @@ "total_forward_fees": 112536, "total_fees": 1192224 }, - "test v2 acknowledgement retires output and rejects wrong peer::trace#1": { - "test_name": "test v2 acknowledgement retires output and rejects wrong peer", - "trace_name": "Trace 1", + "test v2 advance validation and storage guards::trace#11": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 11", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92357, - "total_fees": 20601 + "total_gas_used": 4848, + "total_gas_fees": 323201, + "total_forward_fees": 80535, + "total_fees": 323201 }, - "test v2 OUTP appends a routed output and commits it::trace#2": { - "test_name": "test v2 OUTP appends a routed output and commits it", - "trace_name": "Trace 2", + "test v2 receiver selectors topup and external rejection::trace#6": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 6", "tx_count": 1, - "total_gas_used": 1051, - "total_gas_fees": 70068, - "total_forward_fees": 3675185, - "total_fees": 70068 + "total_gas_used": 2901, + "total_gas_fees": 193401, + "total_forward_fees": 65246, + "total_fees": 193401 }, - "test Fibonacci contract emits the canonical output trace::trace#25": { + "test Fibonacci contract emits the canonical output trace::trace#26": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 25", + "trace_name": "Trace 26", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 18997, + "total_gas_fees": 1266469, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1416091 }, - "test register load jump and ADC without carry::trace#6": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 6", + "test v2 execution edge paths preserve bounded progress::trace#5": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, - "total_forward_fees": 112536, - "total_fees": 1239024 + "total_gas_used": 27401, + "total_gas_fees": 1826735, + "total_forward_fees": 124981, + "total_fees": 2025824 }, - "test v2 advance executes a canonical instruction and rejects stale state::trace#1": { - "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#12": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 inherited opcode families execute through the contract::trace#2": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test v2 OUTP appends a routed output and commits it::trace#1": { + "test_name": "test v2 OUTP appends a routed output and commits it", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 93780, + "total_forward_fees": 92713, "total_fees": 20601 }, - "test register load jump and ADC without carry::trace#9": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 9", + "test v2 delivery validation rejects malformed routes epochs and replays::trace#8": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 95557, + "total_fees": 20601 + }, + "test arithmetic logical and SYS opcodes::trace#8": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 8", "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, + "total_gas_used": 17206, + "total_gas_fees": 1147069, "total_forward_fees": 112536, - "total_fees": 1292291 + "total_fees": 1296691 }, - "test ROM and RAM boundary pages remain addressable::trace#5": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 5", + "test v2 inherited opcode families execute through the contract::trace#26": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 26", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#75": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 75", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#21": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 21", + "tx_count": 1, + "total_gas_used": 8846, + "total_gas_fees": 589734, + "total_forward_fees": 68090, + "total_fees": 589734 + }, + "test arithmetic and flag boundary outcomes::trace#14": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 14", + "tx_count": 2, + "total_gas_used": 18821, + "total_gas_fees": 1254735, + "total_forward_fees": 112536, + "total_fees": 1404357 + }, + "test OUT updates the commitment and emits a bounded event::trace#2": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test arithmetic logical and SYS opcodes::trace#2": { + "test arithmetic logical and SYS opcodes::trace#18": { "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 18", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test stale and invalid advance requests do not mutate state::trace#2": { + "test_name": "test stale and invalid advance requests do not mutate state", "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1231966, + "total_forward_fees": 1195877, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#77": { + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#19": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 19", + "tx_count": 2, + "total_gas_used": 34114, + "total_gas_fees": 2274269, + "total_forward_fees": 124981, + "total_fees": 2473358 + }, + "test Fibonacci contract emits the canonical output trace::trace#13": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 77", + "trace_name": "Trace 13", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test top up message preserves metadata::trace#2": { - "test_name": "test top up message preserves metadata", - "trace_name": "Trace 2", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1195877, - "total_fees": 63401 - }, - "test exact arithmetic carry and subtraction boundaries::trace#2": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 2", + "test ROM and RAM boundary pages remain addressable::trace#5": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 5", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test OUT updates the commitment and emits a bounded event::trace#4": { - "test_name": "test OUT updates the commitment and emits a bounded event", - "trace_name": "Trace 4", + "test Fibonacci contract emits the canonical output trace::trace#22": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 22", "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, + "total_gas_used": 16341, + "total_gas_fees": 1089402, "total_forward_fees": 112536, - "total_fees": 1553491 + "total_fees": 1239024 }, - "test arithmetic and flag boundary outcomes::trace#13": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 13", + "test RAM and branch opcodes::trace#4": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 4", "tx_count": 2, - "total_gas_used": 17224, - "total_gas_fees": 1148269, + "total_gas_used": 19836, + "total_gas_fees": 1322402, "total_forward_fees": 112536, - "total_fees": 1297891 + "total_fees": 1472024 }, - "test Fibonacci contract emits the canonical output trace::trace#11": { + "test Fibonacci contract emits the canonical output trace::trace#39": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 11", + "trace_name": "Trace 39", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#30": { + "test Fibonacci contract emits the canonical output trace::trace#80": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 30", - "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, - "total_forward_fees": 112536, - "total_fees": 1287224 - }, - "test RAM and branch opcodes::trace#8": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 8", + "trace_name": "Trace 80", "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1259624 + "total_fees": 1553491 }, - "test ROM and RAM boundary pages remain addressable::trace#10": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 10", + "test arithmetic and flag boundary outcomes::trace#16": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 16", "tx_count": 2, "total_gas_used": 15786, "total_gas_fees": 1052402, "total_forward_fees": 112536, "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#21": { + "test Fibonacci contract emits the canonical output trace::trace#1": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 21", - "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 - }, - "test deploy exposes Tonolith metadata::trace#1": { - "test_name": "test deploy exposes Tonolith metadata", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 89869, + "total_forward_fees": 93424, "total_fees": 20601 }, - "test v2 advance executes a canonical instruction and rejects stale state::trace#3": { - "test_name": "test v2 advance executes a canonical instruction and rejects stale state", - "trace_name": "Trace 3", - "tx_count": 2, - "total_gas_used": 24743, - "total_gas_fees": 1649535, - "total_forward_fees": 124981, - "total_fees": 1848624 - }, - "test ROM and RAM boundary pages remain addressable::trace#8": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 8", + "test Fibonacci contract emits the canonical output trace::trace#29": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 29", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#8": { + "test v2 receiver selectors topup and external rejection::trace#9": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 9", + "tx_count": 0, + "total_gas_used": 0, + "total_gas_fees": 0, + "total_forward_fees": 0, + "total_fees": 0 + }, + "test Fibonacci contract emits the canonical output trace::trace#45": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 8", + "trace_name": "Trace 45", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test exact arithmetic carry and subtraction boundaries::trace#3": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 3", - "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 + "test v2 inherited opcode families execute through the contract::trace#8": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#49": { + "test named failure paths and halted rejection::trace#25": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 25", + "tx_count": 1, + "total_gas_used": 8560, + "total_gas_fees": 570668, + "total_forward_fees": 68090, + "total_fees": 570668 + }, + "test named failure paths and halted rejection::trace#15": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 15", + "tx_count": 1, + "total_gas_used": 7813, + "total_gas_fees": 520868, + "total_forward_fees": 68090, + "total_fees": 520868 + }, + "test Fibonacci contract emits the canonical output trace::trace#44": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 49", + "trace_name": "Trace 44", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 17064, + "total_gas_fees": 1137602, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1287224 }, - "test named failure paths and halted rejection::trace#26": { + "test external messages are rejected::trace#3": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 3", + "tx_count": 0, + "total_gas_used": 0, + "total_gas_fees": 0, + "total_forward_fees": 0, + "total_fees": 0 + }, + "test named failure paths and halted rejection::trace#8": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 26", + "trace_name": "Trace 8", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test register load jump and ADC without carry::trace#2": { + "test register load jump and ADC without carry::trace#5": { "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test RAM and branch opcodes::trace#2": { + "test_name": "test RAM and branch opcodes", "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, @@ -1076,59 +1332,95 @@ "total_forward_fees": 1231966, "total_fees": 63401 }, - "test external messages are rejected::trace#3": { - "test_name": "test external messages are rejected", - "trace_name": "Trace 3", - "tx_count": 0, - "total_gas_used": 0, - "total_gas_fees": 0, - "total_forward_fees": 0, - "total_fees": 0 + "test named failure paths and halted rejection::trace#4": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 1118, + "total_gas_fees": 74534, + "total_forward_fees": 44446, + "total_fees": 74534 }, - "test Fibonacci contract emits the canonical output trace::trace#92": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 92", - "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, - "total_forward_fees": 112536, - "total_fees": 1239024 + "test v2 delivery validation rejects malformed routes epochs and replays::trace#15": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 15", + "tx_count": 1, + "total_gas_used": 14228, + "total_gas_fees": 948534, + "total_forward_fees": 103291, + "total_fees": 948534 }, - "test Fibonacci contract emits the canonical output trace::trace#12": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 12", - "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, - "total_forward_fees": 112536, - "total_fees": 1416091 + "test v2 delivery validation rejects malformed routes epochs and replays::trace#17": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 17", + "tx_count": 1, + "total_gas_used": 22045, + "total_gas_fees": 1469668, + "total_forward_fees": 103291, + "total_fees": 1469668 }, - "test INC sets carry and zero on nibble wrap::trace#2": { - "test_name": "test INC sets carry and zero on nibble wrap", + "test v2 deploy exposes immutable metadata and a canonical initial state::trace#2": { + "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", "trace_name": "Trace 2", "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test named failure paths and halted rejection::trace#16": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 16", + "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#28": { + "test named failure paths and halted rejection::trace#3": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1332, + "total_gas_fees": 88801, + "total_forward_fees": 44446, + "total_fees": 88801 + }, + "test OUT updates the commitment and emits a bounded event::trace#3": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test Fibonacci contract emits the canonical output trace::trace#88": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 28", + "trace_name": "Trace 88", "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1259624 + "total_fees": 1221357 }, - "test named failure paths and halted rejection::trace#36": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 36", - "tx_count": 1, - "total_gas_used": 2265, - "total_gas_fees": 151001, - "total_forward_fees": 68090, - "total_fees": 151001 + "test Fibonacci contract emits the canonical output trace::trace#78": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 78", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test ROM and RAM boundary pages remain addressable::trace#8": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 }, "test Fibonacci contract emits the canonical output trace::trace#38": { "test_name": "test Fibonacci contract emits the canonical output trace", @@ -1139,404 +1431,521 @@ "total_forward_fees": 112536, "total_fees": 1553491 }, - "test Fibonacci contract emits the canonical output trace::trace#24": { + "test Fibonacci contract emits the canonical output trace::trace#46": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 24", + "trace_name": "Trace 46", "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1553491 + "total_fees": 1221357 }, - "test external messages are rejected::trace#2": { - "test_name": "test external messages are rejected", + "test v2 bounded advance traverses every nonblocking ISA family::trace#2": { + "test_name": "test v2 bounded advance traverses every nonblocking ISA family", "trace_name": "Trace 2", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test named failure paths and halted rejection::trace#4": { + "test v2 inherited opcode families execute through the contract::trace#25": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 25", + "tx_count": 2, + "total_gas_used": 32302, + "total_gas_fees": 2153469, + "total_forward_fees": 124981, + "total_fees": 2352558 + }, + "test Fibonacci contract emits the canonical output trace::trace#36": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 36", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test v2 bounded advance stops at yield and trap::trace#3": { + "test_name": "test v2 bounded advance stops at yield and trap", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 34114, + "total_gas_fees": 2274269, + "total_forward_fees": 124981, + "total_fees": 2473358 + }, + "test named failure paths and halted rejection::trace#34": { "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 34", + "tx_count": 1, + "total_gas_used": 2949, + "total_gas_fees": 196601, + "total_forward_fees": 68090, + "total_fees": 196601 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#4": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 1118, - "total_gas_fees": 74534, - "total_forward_fees": 44446, - "total_fees": 74534 + "total_gas_used": 7396, + "total_gas_fees": 493068, + "total_forward_fees": 96002, + "total_fees": 493068 }, - "test v2 OUTP appends a routed output and commits it::trace#3": { - "test_name": "test v2 OUTP appends a routed output and commits it", - "trace_name": "Trace 3", + "test v2 execution edge paths preserve bounded progress::trace#11": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 11", "tx_count": 2, - "total_gas_used": 44690, - "total_gas_fees": 2979335, + "total_gas_used": 29015, + "total_gas_fees": 1934335, "total_forward_fees": 124981, - "total_fees": 3318158 + "total_fees": 2133424 }, - "test exact arithmetic carry and subtraction boundaries::trace#11": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 11", + "test v2 receiver selectors topup and external rejection::trace#2": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test arithmetic logical and SYS opcodes::trace#9": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 9", "tx_count": 2, - "total_gas_used": 17206, - "total_gas_fees": 1147069, + "total_gas_used": 17224, + "total_gas_fees": 1148269, "total_forward_fees": 112536, - "total_fees": 1296691 + "total_fees": 1297891 }, - "test named failure paths and halted rejection::trace#11": { + "test named failure paths and halted rejection::trace#42": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 11", + "trace_name": "Trace 42", "tx_count": 1, - "total_gas_used": 1761, - "total_gas_fees": 117401, + "total_gas_used": 3491, + "total_gas_fees": 232734, "total_forward_fees": 68090, - "total_fees": 117401 + "total_fees": 232734 }, - "test Fibonacci contract emits the canonical output trace::trace#32": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 32", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#10": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test ROM and RAM boundary pages remain addressable::trace#7": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 7", + "test top up message preserves metadata::trace#3": { + "test_name": "test top up message preserves metadata", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1328, + "total_gas_fees": 88534, + "total_forward_fees": 44446, + "total_fees": 88534 + }, + "test arithmetic and flag boundary outcomes::trace#3": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 19279, - "total_gas_fees": 1285269, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1434891 + "total_fees": 1202024 }, - "test register load jump and ADC without carry::trace#1": { - "test_name": "test register load jump and ADC without carry", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#16": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 16", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 bounded advance executes the full configured nop batch::trace#1": { + "test_name": "test v2 bounded advance executes the full configured nop batch", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 94847, + "total_forward_fees": 91291, "total_fees": 20601 }, - "test named failure paths and halted rejection::trace#6": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 6", - "tx_count": 1, - "total_gas_used": 1588, - "total_gas_fees": 105868, - "total_forward_fees": 68135, - "total_fees": 105868 - }, - "test Fibonacci contract emits the canonical output trace::trace#23": { + "test Fibonacci contract emits the canonical output trace::trace#9": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 23", + "trace_name": "Trace 9", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#40": { + "test Fibonacci contract emits the canonical output trace::trace#35": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 40", + "trace_name": "Trace 35", "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1416091 + "total_fees": 1192224 }, - "test named failure paths and halted rejection::trace#38": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 38", - "tx_count": 1, - "total_gas_used": 2343, - "total_gas_fees": 156201, - "total_forward_fees": 68090, - "total_fees": 156201 + "test JC false path and maximum jump target::trace#5": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#84": { + "test v2 acknowledgement retires output and rejects wrong peer::trace#6": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 9430, + "total_gas_fees": 628668, + "total_forward_fees": 96002, + "total_fees": 628668 + }, + "test v2 inherited opcode families execute through the contract::trace#4": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 advance executes a canonical instruction and rejects stale state::trace#1": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93780, + "total_fees": 20601 + }, + "test v2 bounded advance records input wait without executing::trace#2": { + "test_name": "test v2 bounded advance records input wait without executing", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#28": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 84", + "trace_name": "Trace 28", "tx_count": 2, "total_gas_used": 16650, "total_gas_fees": 1110002, "total_forward_fees": 112536, "total_fees": 1259624 }, - "test Fibonacci contract emits the canonical output trace::trace#67": { + "test v2 inherited opcode families execute through the contract::trace#14": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#20": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 67", + "trace_name": "Trace 20", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test ROM and RAM boundary pages remain addressable::trace#9": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 9", - "tx_count": 2, - "total_gas_used": 19483, - "total_gas_fees": 1298869, - "total_forward_fees": 112536, - "total_fees": 1448491 + "test v2 delivery validation rejects malformed routes epochs and replays::trace#1": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 }, - "test OUT updates the commitment and emits a bounded event::trace#2": { - "test_name": "test OUT updates the commitment and emits a bounded event", - "trace_name": "Trace 2", + "test v2 advance validation and storage guards::trace#26": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 26", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 }, - "test RAM and branch opcodes::trace#9": { + "test INC sets carry and zero on nibble wrap::trace#1": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94491, + "total_fees": 20601 + }, + "test RAM and branch opcodes::trace#10": { "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 9", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 17845, - "total_gas_fees": 1189669, + "total_gas_used": 16728, + "total_gas_fees": 1115202, "total_forward_fees": 112536, - "total_fees": 1339291 + "total_fees": 1264824 }, - "test arithmetic logical and SYS opcodes::trace#5": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 5", + "test v2 advance executes a canonical instruction and rejects stale state::trace#3": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 + "total_gas_used": 28049, + "total_gas_fees": 1869935, + "total_forward_fees": 124981, + "total_fees": 2069024 }, - "test stale and invalid advance requests do not mutate state::trace#5": { - "test_name": "test stale and invalid advance requests do not mutate state", - "trace_name": "Trace 5", + "test v2 HALT is an architectural state and rejects subsequent advance::trace#4": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 3251, - "total_gas_fees": 216734, - "total_forward_fees": 68090, - "total_fees": 216734 + "total_gas_used": 4596, + "total_gas_fees": 306401, + "total_forward_fees": 80535, + "total_fees": 306401 }, - "test Fibonacci contract emits the canonical output trace::trace#4": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 4", + "test v2 execution edge paths preserve bounded progress::trace#21": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 21", + "tx_count": 1, + "total_gas_used": 13890, + "total_gas_fees": 926001, + "total_forward_fees": 80535, + "total_fees": 926001 + }, + "test arithmetic and flag boundary outcomes::trace#8": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 8", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test named failure paths and halted rejection::trace#32": { + "test named failure paths and halted rejection::trace#30": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 32", + "trace_name": "Trace 30", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test RAM and branch opcodes::trace#4": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 4", + "test stale and invalid advance requests do not mutate state::trace#3": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 19836, - "total_gas_fees": 1322402, + "total_gas_used": 15039, + "total_gas_fees": 1002602, "total_forward_fees": 112536, - "total_fees": 1472024 + "total_fees": 1152224 }, - "test ROM and RAM boundary pages remain addressable::trace#3": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 3", + "test external messages are rejected::trace#2": { + "test_name": "test external messages are rejected", + "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#80": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 80", - "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, - "total_forward_fees": 112536, - "total_fees": 1553491 - }, - "test Fibonacci contract emits the canonical output trace::trace#83": { + "test Fibonacci contract emits the canonical output trace::trace#79": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 83", + "trace_name": "Trace 79", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test INC sets carry and zero on nibble wrap::trace#5": { - "test_name": "test INC sets carry and zero on nibble wrap", + "test arithmetic and flag boundary outcomes::trace#5": { + "test_name": "test arithmetic and flag boundary outcomes", "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, + "total_gas_used": 17845, + "total_gas_fees": 1189669, "total_forward_fees": 112536, - "total_fees": 1292291 + "total_fees": 1339293 }, - "test RAM and branch opcodes::trace#6": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 6", + "test v2 inbox insertion is canonical under out of order delivery::trace#3": { + "test_name": "test v2 inbox insertion is canonical under out of order delivery", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 16676, - "total_gas_fees": 1111735, - "total_forward_fees": 112536, - "total_fees": 1261357 + "total_gas_used": 35347, + "total_gas_fees": 2356469, + "total_forward_fees": 199293, + "total_fees": 2543669 }, - "test Fibonacci contract emits the canonical output trace::trace#58": { + "test v2 execution edge paths preserve bounded progress::trace#8": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#48": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 58", + "trace_name": "Trace 48", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1221357 }, - "test arithmetic and flag boundary outcomes::trace#3": { - "test_name": "test arithmetic and flag boundary outcomes", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#3": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 + "total_gas_used": 30444, + "total_gas_fees": 2029602, + "total_forward_fees": 124981, + "total_fees": 2228691 }, - "test arithmetic logical and SYS opcodes::trace#13": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 13", + "test Fibonacci contract emits the canonical output trace::trace#24": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 24", "tx_count": 2, - "total_gas_used": 17765, - "total_gas_fees": 1184335, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1333957 - }, - "test exact arithmetic carry and subtraction boundaries::trace#1": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 1", - "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 99113, - "total_fees": 20601 + "total_fees": 1553491 }, - "test Fibonacci contract emits the canonical output trace::trace#87": { + "test Fibonacci contract emits the canonical output trace::trace#67": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 87", + "trace_name": "Trace 67", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test named failure paths and halted rejection::trace#29": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 29", + "test v2 receiver selectors topup and external rejection::trace#3": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 3", "tx_count": 1, - "total_gas_used": 8846, - "total_gas_fees": 589734, - "total_forward_fees": 68090, - "total_fees": 589734 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 44446, + "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#52": { + "test Fibonacci contract emits the canonical output trace::trace#43": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 52", - "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, - "total_forward_fees": 112536, - "total_fees": 1553491 - }, - "test arithmetic logical and SYS opcodes::trace#16": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 16", - "tx_count": 2, - "total_gas_used": 18865, - "total_gas_fees": 1257669, - "total_forward_fees": 112536, - "total_fees": 1407291 - }, - "test Fibonacci contract emits the canonical output trace::trace#94": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 94", + "trace_name": "Trace 43", "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1553491 + "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#19": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 19", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#15": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 15", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 + "total_gas_used": 31744, + "total_gas_fees": 2116269, + "total_forward_fees": 124981, + "total_fees": 2315358 }, - "test Fibonacci contract emits the canonical output trace::trace#2": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 2", + "test named failure paths and halted rejection::trace#18": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 18", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test advance executes one instruction and emits commitments::trace#3": { - "test_name": "test advance executes one instruction and emits commitments", + "test ROM and RAM boundary pages remain addressable::trace#1": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93069, + "total_fees": 20601 + }, + "test v2 bounded advance stops at a legacy output event::trace#3": { + "test_name": "test v2 bounded advance stops at a legacy output event", "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 + "total_gas_used": 31141, + "total_gas_fees": 2076069, + "total_forward_fees": 124981, + "total_fees": 2275158 }, - "test arithmetic and flag boundary outcomes::trace#12": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 12", + "test v2 bounded advance records input wait without executing::trace#3": { + "test_name": "test v2 bounded advance records input wait without executing", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 17765, - "total_gas_fees": 1184335, + "total_gas_used": 30283, + "total_gas_fees": 2018869, + "total_forward_fees": 124981, + "total_fees": 2217958 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#2": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4380391, + "total_fees": 70068 + }, + "test exact arithmetic carry and subtraction boundaries::trace#4": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1333957 + "total_fees": 1192224 }, - "test exact arithmetic carry and subtraction boundaries::trace#7": { + "test exact arithmetic carry and subtraction boundaries::trace#8": { "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 7", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 }, - "test v2 deploy exposes immutable metadata and a canonical initial state::trace#2": { - "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", - "trace_name": "Trace 2", + "test v2 bounded advance stops at yield and trap::trace#4": { + "test_name": "test v2 bounded advance stops at yield and trap", + "trace_name": "Trace 4", "tx_count": 1, "total_gas_used": 1051, "total_gas_fees": 70068, - "total_forward_fees": 3589362, + "total_forward_fees": 4318034, "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#64": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 64", + "test JC false path and maximum jump target::trace#6": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 6", "tx_count": 2, "total_gas_used": 16341, "total_gas_fees": 1089402, "total_forward_fees": 112536, "total_fees": 1239024 }, - "test RAM and branch opcodes::trace#1": { - "test_name": "test RAM and branch opcodes", + "test v2 acknowledgement retires output and rejects wrong peer::trace#1": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, @@ -1544,96 +1953,141 @@ "total_forward_fees": 92357, "total_fees": 20601 }, - "test exact arithmetic carry and subtraction boundaries::trace#10": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 10", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 - }, - "test Fibonacci contract emits the canonical output trace::trace#33": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 33", - "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 - }, - "test Fibonacci contract emits the canonical output trace::trace#53": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 53", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 - }, - "test named failure paths and halted rejection::trace#16": { + "test named failure paths and halted rejection::trace#22": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 16", + "trace_name": "Trace 22", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test arithmetic logical and SYS opcodes::trace#9": { - "test_name": "test arithmetic logical and SYS opcodes", + "test named failure paths and halted rejection::trace#9": { + "test_name": "test named failure paths and halted rejection", "trace_name": "Trace 9", + "tx_count": 1, + "total_gas_used": 1870, + "total_gas_fees": 124668, + "total_forward_fees": 68090, + "total_fees": 124668 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#1": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test v2 inherited opcode families execute through the contract::trace#11": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 11", "tx_count": 2, - "total_gas_used": 17224, - "total_gas_fees": 1148269, - "total_forward_fees": 112536, - "total_fees": 1297891 + "total_gas_used": 30995, + "total_gas_fees": 2066335, + "total_forward_fees": 124981, + "total_fees": 2265424 }, - "test arithmetic and flag boundary outcomes::trace#8": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 8", + "test Fibonacci contract emits the canonical output trace::trace#47": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 47", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test v2 HALT is an architectural state and rejects subsequent advance::trace#4": { - "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", - "trace_name": "Trace 4", + "test v2 advance validation and storage guards::trace#27": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 27", "tx_count": 1, - "total_gas_used": 4578, - "total_gas_fees": 305201, + "total_gas_used": 4648, + "total_gas_fees": 309868, "total_forward_fees": 80535, - "total_fees": 305201 + "total_fees": 309868 }, - "test Fibonacci contract emits the canonical output trace::trace#51": { + "test v2 bounded advance stops at a legacy output event::trace#2": { + "test_name": "test v2 bounded advance stops at a legacy output event", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#90": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 51", + "trace_name": "Trace 90", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#54": { + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#2": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4367768, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#6": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 54", + "trace_name": "Trace 6", "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1416091 + "total_fees": 1192224 }, - "test named failure paths and halted rejection::trace#35": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 35", + "test arithmetic and flag boundary outcomes::trace#9": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test v2 execution edge paths preserve bounded progress::trace#29": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 29", + "tx_count": 1, + "total_gas_used": 23081, + "total_gas_fees": 1538734, + "total_forward_fees": 80535, + "total_fees": 1538734 + }, + "test v2 inherited opcode families execute through the contract::trace#16": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 16", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test arithmetic logical and SYS opcodes::trace#2": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1195877, + "total_forward_fees": 1231966, "total_fees": 63401 }, + "test v2 inbox insertion is canonical under out of order delivery::trace#2": { + "test_name": "test v2 inbox insertion is canonical under out of order delivery", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4367768, + "total_fees": 70068 + }, "test Fibonacci contract emits the canonical output trace::trace#15": { "test_name": "test Fibonacci contract emits the canonical output trace", "trace_name": "Trace 15", @@ -1643,17 +2097,8 @@ "total_forward_fees": 112536, "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#37": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 37", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 - }, - "test Fibonacci contract emits the canonical output trace::trace#3": { - "test_name": "test Fibonacci contract emits the canonical output trace", + "test register load jump and ADC without carry::trace#3": { + "test_name": "test register load jump and ADC without carry", "trace_name": "Trace 3", "tx_count": 2, "total_gas_used": 15786, @@ -1661,41 +2106,32 @@ "total_forward_fees": 112536, "total_fees": 1202024 }, - "test RAM and branch opcodes::trace#7": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 7", + "test Fibonacci contract emits the canonical output trace::trace#16": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 16", "tx_count": 2, - "total_gas_used": 18224, - "total_gas_fees": 1214935, + "total_gas_used": 17064, + "total_gas_fees": 1137602, "total_forward_fees": 112536, - "total_fees": 1364557 + "total_fees": 1287226 }, - "test Fibonacci contract emits the canonical output trace::trace#60": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 60", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "test v2 HALT is an architectural state and rejects subsequent advance::trace#2": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test arithmetic and flag boundary outcomes::trace#7": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 7", + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#6": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 6", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, - "total_forward_fees": 112536, - "total_fees": 1202024 - }, - "test named failure paths and halted rejection::trace#12": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 12", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 44944, + "total_gas_fees": 2996269, + "total_forward_fees": 124981, + "total_fees": 3195358 }, "test RAM and branch opcodes::trace#11": { "test_name": "test RAM and branch opcodes", @@ -1706,87 +2142,177 @@ "total_forward_fees": 112536, "total_fees": 1264824 }, - "test named failure paths and halted rejection::trace#14": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 14", + "test Fibonacci contract emits the canonical output trace::trace#18": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 18", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 execution edge paths preserve bounded progress::trace#10": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 10", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test Fibonacci contract emits the canonical output trace::trace#6": { + "test Fibonacci contract emits the canonical output trace::trace#8": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 6", + "trace_name": "Trace 8", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#55": { + "test INC sets carry and zero on nibble wrap::trace#5": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test JC false path and maximum jump target::trace#4": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 17140, + "total_gas_fees": 1142669, + "total_forward_fees": 112536, + "total_fees": 1292291 + }, + "test v2 advance validation and storage guards::trace#5": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 2051, + "total_gas_fees": 136734, + "total_forward_fees": 80535, + "total_fees": 136734 + }, + "test v2 bounced delivery keeps the outbox retryable::trace#1": { + "test_name": "test v2 bounced delivery keeps the outbox retryable", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93424, + "total_fees": 20601 + }, + "test v2 bounded structures cover every fixed slot and selector::trace#2": { + "test_name": "test v2 bounded structures cover every fixed slot and selector", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 95557, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#59": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 55", + "trace_name": "Trace 59", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test ROM and RAM boundary pages remain addressable::trace#6": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 6", + "test Fibonacci contract emits the canonical output trace::trace#83": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 83", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1192224 }, - "test arithmetic logical and SYS opcodes::trace#7": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 7", + "test v2 receiver selectors topup and external rejection::trace#5": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 1823, + "total_gas_fees": 121534, + "total_forward_fees": 44446, + "total_fees": 121534 + }, + "test v2 execution edge paths preserve bounded progress::trace#19": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 19", + "tx_count": 1, + "total_gas_used": 13195, + "total_gas_fees": 879668, + "total_forward_fees": 80535, + "total_fees": 879668 + }, + "test INC sets carry and zero on nibble wrap::trace#4": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 4", "tx_count": 2, - "total_gas_used": 17232, - "total_gas_fees": 1148802, + "total_gas_used": 18945, + "total_gas_fees": 1263002, "total_forward_fees": 112536, - "total_fees": 1298424 + "total_fees": 1412624 }, - "test named failure paths and halted rejection::trace#30": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 30", + "test v2 advance validation and storage guards::trace#6": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 6", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 2206, + "total_gas_fees": 147068, + "total_forward_fees": 80535, + "total_fees": 147068 }, - "test named failure paths and halted rejection::trace#8": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 8", + "test v2 inherited opcode families execute through the contract::trace#22": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 22", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 }, - "test JC false path and maximum jump target::trace#3": { - "test_name": "test JC false path and maximum jump target", + "test exact arithmetic carry and subtraction boundaries::trace#3": { + "test_name": "test exact arithmetic carry and subtraction boundaries", "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 16702, - "total_gas_fees": 1113469, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1263091 + "total_fees": 1202024 }, - "test register load jump and ADC without carry::trace#5": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 5", + "test Fibonacci contract emits the canonical output trace::trace#72": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 72", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test Fibonacci contract emits the canonical output trace::trace#71": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 71", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, + "test arithmetic logical and SYS opcodes::trace#11": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 17400, + "total_gas_fees": 1160002, + "total_forward_fees": 112536, + "total_fees": 1309624 + }, "test arithmetic logical and SYS opcodes::trace#15": { "test_name": "test arithmetic logical and SYS opcodes", "trace_name": "Trace 15", @@ -1796,491 +2322,1616 @@ "total_forward_fees": 112536, "total_fees": 1404357 }, - "test arithmetic logical and SYS opcodes::trace#10": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 10", + "test RAM and branch opcodes::trace#6": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 6", "tx_count": 2, - "total_gas_used": 17294, - "total_gas_fees": 1152935, + "total_gas_used": 16676, + "total_gas_fees": 1111735, "total_forward_fees": 112536, - "total_fees": 1302557 + "total_fees": 1261357 }, - "test register load jump and ADC without carry::trace#10": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 10", + "test exact arithmetic carry and subtraction boundaries::trace#7": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 7", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1231966, "total_fees": 63401 }, - "test register load jump and ADC without carry::trace#13": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 13", - "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, - "total_forward_fees": 112536, - "total_fees": 1292291 - }, - "test Fibonacci contract emits the canonical output trace::trace#17": { - "test_name": "test Fibonacci contract emits the canonical output trace", + "test named failure paths and halted rejection::trace#17": { + "test_name": "test named failure paths and halted rejection", "trace_name": "Trace 17", + "tx_count": 1, + "total_gas_used": 8256, + "total_gas_fees": 550401, + "total_forward_fees": 68090, + "total_fees": 550401 + }, + "test v2 bounded advance executes the full configured nop batch::trace#3": { + "test_name": "test v2 bounded advance executes the full configured nop batch", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 + "total_gas_used": 27352, + "total_gas_fees": 1823469, + "total_forward_fees": 124981, + "total_fees": 2022558 }, - "test named failure paths and halted rejection::trace#19": { + "test named failure paths and halted rejection::trace#6": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 19", + "trace_name": "Trace 6", "tx_count": 1, - "total_gas_used": 8508, - "total_gas_fees": 567201, - "total_forward_fees": 68090, - "total_fees": 567201 + "total_gas_used": 1588, + "total_gas_fees": 105868, + "total_forward_fees": 68135, + "total_fees": 105868 }, - "test RAM and branch opcodes::trace#5": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 5", + "test Fibonacci contract emits the canonical output trace::trace#3": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 3", "tx_count": 2, "total_gas_used": 15786, "total_gas_fees": 1052402, "total_forward_fees": 112536, "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#70": { + "test Fibonacci contract emits the canonical output trace::trace#2": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 70", - "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, - "total_forward_fees": 112536, - "total_fees": 1259624 + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test ROM and RAM boundary pages remain addressable::trace#11": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 19617, + "total_gas_fees": 1307802, + "total_forward_fees": 112536, + "total_fees": 1457424 + }, + "test v2 advance validation and storage guards::trace#8": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 2351, + "total_gas_fees": 156734, + "total_forward_fees": 80535, + "total_fees": 156734 + }, + "test v2 advance validation and storage guards::trace#2": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#7": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 7940, + "total_gas_fees": 529334, + "total_forward_fees": 103291, + "total_fees": 529334 + }, + "test v2 execution edge paths preserve bounded progress::trace#15": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 15", + "tx_count": 1, + "total_gas_used": 13195, + "total_gas_fees": 879668, + "total_forward_fees": 80535, + "total_fees": 879668 + }, + "test v2 advance validation and storage guards::trace#23": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 23", + "tx_count": 1, + "total_gas_used": 4337, + "total_gas_fees": 289134, + "total_forward_fees": 80535, + "total_fees": 289134 + }, + "test v2 inherited opcode families execute through the contract::trace#18": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 18", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 advance validation and storage guards::trace#16": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 16", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test v2 execution edge paths preserve bounded progress::trace#28": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 28", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4367768, + "total_fees": 70068 + }, + "test ROM and RAM boundary pages remain addressable::trace#9": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 19483, + "total_gas_fees": 1298869, + "total_forward_fees": 112536, + "total_fees": 1448491 + }, + "test Fibonacci contract emits the canonical output trace::trace#91": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 91", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test Fibonacci contract emits the canonical output trace::trace#70": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 70", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test Fibonacci contract emits the canonical output trace::trace#82": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 82", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test advance executes one instruction and emits commitments::trace#1": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#21": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 21", + "tx_count": 2, + "total_gas_used": 31205, + "total_gas_fees": 2080335, + "total_forward_fees": 124981, + "total_fees": 2279424 + }, + "test v2 advance validation and storage guards::trace#20": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 20", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test v2 execution edge paths preserve bounded progress::trace#3": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 27349, + "total_gas_fees": 1823269, + "total_forward_fees": 124981, + "total_fees": 2022358 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#5": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 3610, + "total_gas_fees": 240668, + "total_forward_fees": 103291, + "total_fees": 240668 + }, + "test Fibonacci contract emits the canonical output trace::trace#89": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 89", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test RAM and branch opcodes::trace#1": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test arithmetic logical and SYS opcodes::trace#3": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test named failure paths and halted rejection::trace#24": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 24", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test arithmetic and flag boundary outcomes::trace#12": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17765, + "total_gas_fees": 1184335, + "total_forward_fees": 112536, + "total_fees": 1333957 + }, + "test register load jump and ADC without carry::trace#12": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17232, + "total_gas_fees": 1148802, + "total_forward_fees": 112536, + "total_fees": 1298424 + }, + "test v2 execution edge paths preserve bounded progress::trace#27": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 27", + "tx_count": 1, + "total_gas_used": 20106, + "total_gas_fees": 1340401, + "total_forward_fees": 80535, + "total_fees": 1340401 + }, + "test v2 advance validation and storage guards::trace#3": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 1844, + "total_gas_fees": 122934, + "total_forward_fees": 80535, + "total_fees": 122934 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#11": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 11", + "tx_count": 2, + "total_gas_used": 31494, + "total_gas_fees": 2099602, + "total_forward_fees": 124981, + "total_fees": 2298691 + }, + "test v2 advance validation and storage guards::trace#10": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 4744, + "total_gas_fees": 316268, + "total_forward_fees": 80535, + "total_fees": 316268 + }, + "test ROM and RAM boundary pages remain addressable::trace#6": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#18": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 18", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#69": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 69", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test arithmetic and flag boundary outcomes::trace#17": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 18945, + "total_gas_fees": 1263002, + "total_forward_fees": 112536, + "total_fees": 1412624 + }, + "test Fibonacci contract emits the canonical output trace::trace#41": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 41", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test stale and invalid advance requests do not mutate state::trace#4": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 3045, + "total_gas_fees": 203001, + "total_forward_fees": 68090, + "total_fees": 203001 + }, + "test register load jump and ADC without carry::trace#10": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test named failure paths and halted rejection::trace#28": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 28", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 bounded advance stops at yield and trap::trace#1": { + "test_name": "test v2 bounded advance stops at yield and trap", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#58": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 58", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test ROM and RAM boundary pages remain addressable::trace#2": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test Fibonacci contract emits the canonical output trace::trace#60": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 60", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 acknowledgement retires output and rejects wrong peer::trace#7": { + "test_name": "test v2 acknowledgement retires output and rejects wrong peer", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4380391, + "total_fees": 70068 + }, + "test v2 advance validation and storage guards::trace#1": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94491, + "total_fees": 20601 + }, + "test v2 execution edge paths preserve bounded progress::trace#31": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 31", + "tx_count": 2, + "total_gas_used": 29846, + "total_gas_fees": 1989735, + "total_forward_fees": 124981, + "total_fees": 2188824 + }, + "test Fibonacci contract emits the canonical output trace::trace#98": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 98", + "tx_count": 2, + "total_gas_used": 16676, + "total_gas_fees": 1111735, + "total_forward_fees": 112536, + "total_fees": 1261357 + }, + "test Fibonacci contract emits the canonical output trace::trace#68": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 68", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test Fibonacci contract emits the canonical output trace::trace#51": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 51", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test Fibonacci contract emits the canonical output trace::trace#84": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 84", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test v2 bounded advance stops at a routed output event::trace#2": { + "test_name": "test v2 bounded advance stops at a routed output event", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4340079, + "total_fees": 70068 + }, + "test arithmetic logical and SYS opcodes::trace#6": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test v2 bounded advance records outbox backpressure without executing::trace#3": { + "test_name": "test v2 bounded advance records outbox backpressure without executing", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 29846, + "total_gas_fees": 1989735, + "total_forward_fees": 124981, + "total_fees": 2188824 + }, + "test arithmetic logical and SYS opcodes::trace#12": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 12", + "tx_count": 2, + "total_gas_used": 17845, + "total_gas_fees": 1189669, + "total_forward_fees": 112536, + "total_fees": 1339291 + }, + "test named failure paths and halted rejection::trace#14": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 HALT is an architectural state and rejects subsequent advance::trace#3": { + "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 29461, + "total_gas_fees": 1964069, + "total_forward_fees": 124981, + "total_fees": 2163158 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#8": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test register load jump and ADC without carry::trace#4": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test named failure paths and halted rejection::trace#5": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 44446, + "total_fees": 63401 }, - "test JC false path and maximum jump target::trace#6": { - "test_name": "test JC false path and maximum jump target", - "trace_name": "Trace 6", + "test v2 bounded memory ROM queues routes peers and commitments::trace#1": { + "test_name": "test v2 bounded memory ROM queues routes peers and commitments", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test v2 inbox insertion is canonical under out of order delivery::trace#4": { + "test_name": "test v2 inbox insertion is canonical under out of order delivery", + "trace_name": "Trace 4", "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, - "total_forward_fees": 112536, - "total_fees": 1239024 + "total_gas_used": 49375, + "total_gas_fees": 3291669, + "total_forward_fees": 199293, + "total_fees": 3478869 }, - "test Fibonacci contract emits the canonical output trace::trace#10": { + "test v2 advance validation and storage guards::trace#18": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 18", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#95": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 10", + "trace_name": "Trace 95", "tx_count": 2, - "total_gas_used": 19538, - "total_gas_fees": 1302535, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1553491 + "total_fees": 1221357 }, - "test v2 OUTP appends a routed output and commits it::trace#1": { + "test ROM and RAM boundary pages remain addressable::trace#13": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 19836, + "total_gas_fees": 1322402, + "total_forward_fees": 112536, + "total_fees": 1472024 + }, + "test v2 execution edge paths preserve bounded progress::trace#9": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 28791, + "total_gas_fees": 1919402, + "total_forward_fees": 124981, + "total_fees": 2118491 + }, + "test v2 deploy exposes immutable metadata and a canonical initial state::trace#1": { + "test_name": "test v2 deploy exposes immutable metadata and a canonical initial state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94135, + "total_fees": 20601 + }, + "test v2 receiver selectors topup and external rejection::trace#1": { + "test_name": "test v2 receiver selectors topup and external rejection", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94491, + "total_fees": 20601 + }, + "test v2 OUTP appends a routed output and commits it::trace#8": { "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 7954, + "total_gas_fees": 530268, + "total_forward_fees": 88002, + "total_fees": 530268 + }, + "test v2 advance validation and storage guards::trace#14": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#14": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 14", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test v2 inherited opcode families execute through the contract::trace#1": { + "test_name": "test v2 inherited opcode families execute through the contract", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 92713, + "total_forward_fees": 93424, "total_fees": 20601 }, - "test INC sets carry and zero on nibble wrap::trace#4": { - "test_name": "test INC sets carry and zero on nibble wrap", - "trace_name": "Trace 4", + "test v2 advance validation and storage guards::trace#13": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 13", + "tx_count": 1, + "total_gas_used": 2808, + "total_gas_fees": 187201, + "total_forward_fees": 80535, + "total_fees": 187201 + }, + "test v2 bounded advance records input wait without executing::trace#1": { + "test_name": "test v2 bounded advance records input wait without executing", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 93780, + "total_fees": 20601 + }, + "test v2 execution edge paths preserve bounded progress::trace#16": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 16", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 advance validation and storage guards::trace#22": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 22", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#19": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 19", "tx_count": 2, - "total_gas_used": 18945, - "total_gas_fees": 1263002, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1412624 + "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#85": { + "test RAM and branch opcodes::trace#8": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 8", + "tx_count": 2, + "total_gas_used": 16650, + "total_gas_fees": 1110002, + "total_forward_fees": 112536, + "total_fees": 1259624 + }, + "test v2 inherited opcode families execute through the contract::trace#27": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 27", + "tx_count": 2, + "total_gas_used": 28564, + "total_gas_fees": 1904269, + "total_forward_fees": 124981, + "total_fees": 2103358 + }, + "test Fibonacci contract emits the canonical output trace::trace#57": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 85", + "trace_name": "Trace 57", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test arithmetic logical and SYS opcodes::trace#6": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 6", + "test Fibonacci contract emits the canonical output trace::trace#27": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 27", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1192224 + }, + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#7": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 21927, + "total_gas_fees": 1461801, + "total_forward_fees": 103291, + "total_fees": 1461801 + }, + "test arithmetic and flag boundary outcomes::trace#2": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 execution edge paths preserve bounded progress::trace#12": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#50": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 50", + "tx_count": 2, + "total_gas_used": 16341, + "total_gas_fees": 1089402, + "total_forward_fees": 112536, + "total_fees": 1239024 + }, + "test v2 bounded advance stops at yield and trap::trace#5": { + "test_name": "test v2 bounded advance stops at yield and trap", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 31205, + "total_gas_fees": 2080335, + "total_forward_fees": 124981, + "total_fees": 2279424 + }, + "test Fibonacci contract emits the canonical output trace::trace#25": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 25", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#3": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 2719, + "total_gas_fees": 181268, + "total_forward_fees": 103291, + "total_fees": 181268 + }, + "test v2 bounded advance stops at a legacy output event::trace#1": { + "test_name": "test v2 bounded advance stops at a legacy output event", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test v2 inherited opcode families execute through the contract::trace#30": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 30", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#40": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 40", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, + "total_forward_fees": 112536, + "total_fees": 1416091 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#6": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#14": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 14", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test named failure paths and halted rejection::trace#41": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 41", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1195877, + "total_fees": 63401 + }, + "test v2 inherited opcode families execute through the contract::trace#17": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 31165, + "total_gas_fees": 2077669, + "total_forward_fees": 124981, + "total_fees": 2276758 + }, + "test arithmetic logical and SYS opcodes::trace#1": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test v2 bounded advance stops at a routed output event::trace#3": { + "test_name": "test v2 bounded advance stops at a routed output event", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 48306, + "total_gas_fees": 3220402, + "total_forward_fees": 124981, + "total_fees": 3559225 + }, + "test v2 execution edge paths preserve bounded progress::trace#17": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 17", + "tx_count": 1, + "total_gas_used": 13352, + "total_gas_fees": 890134, + "total_forward_fees": 80535, + "total_fees": 890134 }, - "test exact arithmetic carry and subtraction boundaries::trace#9": { + "test exact arithmetic carry and subtraction boundaries::trace#10": { "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 9", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test named failure paths and halted rejection::trace#34": { + "test named failure paths and halted rejection::trace#36": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 34", + "trace_name": "Trace 36", "tx_count": 1, - "total_gas_used": 2949, - "total_gas_fees": 196601, + "total_gas_used": 2265, + "total_gas_fees": 151001, "total_forward_fees": 68090, - "total_fees": 196601 + "total_fees": 151001 }, - "test named failure paths and halted rejection::trace#41": { + "test v2 bounded advance executes the full configured nop batch::trace#2": { + "test_name": "test v2 bounded advance executes the full configured nop batch", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test named failure paths and halted rejection::trace#38": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 41", + "trace_name": "Trace 38", + "tx_count": 1, + "total_gas_used": 2343, + "total_gas_fees": 156201, + "total_forward_fees": 68090, + "total_fees": 156201 + }, + "test ROM and RAM boundary pages remain addressable::trace#4": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 4", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1195877, + "total_forward_fees": 1408501, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#41": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 41", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#9": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 9", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 + "total_gas_used": 31450, + "total_gas_fees": 2096669, + "total_forward_fees": 124981, + "total_fees": 2295758 }, - "test Fibonacci contract emits the canonical output trace::trace#89": { + "test v2 delivery validation rejects malformed routes epochs and replays::trace#6": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 3706, + "total_gas_fees": 247068, + "total_forward_fees": 103291, + "total_fees": 247068 + }, + "test Fibonacci contract emits the canonical output trace::trace#61": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 89", + "trace_name": "Trace 61", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test top up message preserves metadata::trace#3": { - "test_name": "test top up message preserves metadata", - "trace_name": "Trace 3", + "test v2 inbox insertion is canonical under out of order delivery::trace#1": { + "test_name": "test v2 inbox insertion is canonical under out of order delivery", + "trace_name": "Trace 1", "tx_count": 1, - "total_gas_used": 1328, - "total_gas_fees": 88534, - "total_forward_fees": 44446, - "total_fees": 88534 + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92002, + "total_fees": 20601 }, - "test arithmetic and flag boundary outcomes::trace#17": { + "test arithmetic and flag boundary outcomes::trace#10": { "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 17", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 18945, - "total_gas_fees": 1263002, + "total_gas_used": 17232, + "total_gas_fees": 1148802, "total_forward_fees": 112536, - "total_fees": 1412624 + "total_fees": 1298424 }, - "test Fibonacci contract emits the canonical output trace::trace#44": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 44", + "test arithmetic logical and SYS opcodes::trace#16": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 16", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 18865, + "total_gas_fees": 1257669, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1407291 }, - "test arithmetic and flag boundary outcomes::trace#4": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 4", + "test Fibonacci contract emits the canonical output trace::trace#99": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 99", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 17140, + "total_gas_fees": 1142669, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1292291 }, - "test register load jump and ADC without carry::trace#8": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 8", + "test v2 OUTP appends a routed output and commits it::trace#7": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 7", + "tx_count": 1, + "total_gas_used": 7954, + "total_gas_fees": 530268, + "total_forward_fees": 88002, + "total_fees": 530268 + }, + "test deploy exposes Tonolith metadata::trace#1": { + "test_name": "test deploy exposes Tonolith metadata", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 89869, + "total_fees": 20601 + }, + "test v2 advance validation and storage guards::trace#15": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 15", + "tx_count": 1, + "total_gas_used": 2886, + "total_gas_fees": 192401, + "total_forward_fees": 80535, + "total_fees": 192401 + }, + "test v2 bounded advance stops at yield and trap::trace#2": { + "test_name": "test v2 bounded advance stops at yield and trap", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 inherited opcode families execute through the contract::trace#20": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 20", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test JC false path and maximum jump target::trace#2": { + "test_name": "test JC false path and maximum jump target", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 inherited opcode families execute through the contract::trace#31": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 31", "tx_count": 2, - "total_gas_used": 15039, - "total_gas_fees": 1002602, - "total_forward_fees": 112536, - "total_fees": 1152224 + "total_gas_used": 28989, + "total_gas_fees": 1932602, + "total_forward_fees": 124981, + "total_fees": 2131691 }, - "test Fibonacci contract emits the canonical output trace::trace#26": { + "test Fibonacci contract emits the canonical output trace::trace#5": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 26", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1416091 + "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#16": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 16", + "test register load jump and ADC without carry::trace#7": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 16702, + "total_gas_fees": 1113469, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1263091 }, - "test named failure paths and halted rejection::trace#7": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 7", + "test INC sets carry and zero on nibble wrap::trace#2": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 2", "tx_count": 1, - "total_gas_used": 1373, - "total_gas_fees": 91534, - "total_forward_fees": 44446, - "total_fees": 91534 + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 }, - "test named failure paths and halted rejection::trace#17": { + "test named failure paths and halted rejection::trace#40": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 17", + "trace_name": "Trace 40", "tx_count": 1, - "total_gas_used": 8256, - "total_gas_fees": 550401, + "total_gas_used": 2421, + "total_gas_fees": 161401, "total_forward_fees": 68090, - "total_fees": 550401 + "total_fees": 161401 }, - "test Fibonacci contract emits the canonical output trace::trace#50": { + "test v2 OUTP appends a routed output and commits it::trace#3": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 48306, + "total_gas_fees": 3220402, + "total_forward_fees": 124981, + "total_fees": 3559225 + }, + "test Fibonacci contract emits the canonical output trace::trace#30": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 50", + "trace_name": "Trace 30", "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, + "total_gas_used": 17064, + "total_gas_fees": 1137602, "total_forward_fees": 112536, - "total_fees": 1239024 + "total_fees": 1287224 }, - "test Fibonacci contract emits the canonical output trace::trace#88": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 88", + "test v2 execution edge paths preserve bounded progress::trace#20": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 20", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#13": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 13", + "tx_count": 1, + "total_gas_used": 11468, + "total_gas_fees": 764534, + "total_forward_fees": 103291, + "total_fees": 764534 + }, + "test arithmetic logical and SYS opcodes::trace#7": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 17232, + "total_gas_fees": 1148802, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1298424 }, - "test named failure paths and halted rejection::trace#5": { + "test named failure paths and halted rejection::trace#26": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 5", + "trace_name": "Trace 26", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 44446, + "total_forward_fees": 1231966, "total_fees": 63401 }, - "test v2 HALT is an architectural state and rejects subsequent advance::trace#3": { - "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", + "test ROM and RAM boundary pages remain addressable::trace#3": { + "test_name": "test ROM and RAM boundary pages remain addressable", "trace_name": "Trace 3", + "tx_count": 1, + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 + }, + "test v2 inherited opcode families execute through the contract::trace#5": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 26230, - "total_gas_fees": 1748669, + "total_gas_used": 28049, + "total_gas_fees": 1869935, "total_forward_fees": 124981, - "total_fees": 1947758 + "total_fees": 2069024 }, - "test Fibonacci contract emits the canonical output trace::trace#62": { + "test Fibonacci contract emits the canonical output trace::trace#74": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 62", + "trace_name": "Trace 74", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test RAM and branch opcodes::trace#3": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 3", + "test arithmetic logical and SYS opcodes::trace#5": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 5", "tx_count": 2, "total_gas_used": 15786, "total_gas_fees": 1052402, "total_forward_fees": 112536, "total_fees": 1202024 }, - "test arithmetic and flag boundary outcomes::trace#2": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 2", + "test named failure paths and halted rejection::trace#35": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 35", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1231966, + "total_forward_fees": 1195877, "total_fees": 63401 }, - "test arithmetic and flag boundary outcomes::trace#6": { - "test_name": "test arithmetic and flag boundary outcomes", + "test v2 bounded advance records outbox backpressure without executing::trace#2": { + "test_name": "test v2 bounded advance records outbox backpressure without executing", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4371191, + "total_fees": 70068 + }, + "test v2 inherited opcode families execute through the contract::trace#6": { + "test_name": "test v2 inherited opcode families execute through the contract", "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test OUT updates the commitment and emits a bounded event::trace#4": { + "test_name": "test OUT updates the commitment and emits a bounded event", + "trace_name": "Trace 4", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1553491 }, - "test Fibonacci contract emits the canonical output trace::trace#22": { + "test Fibonacci contract emits the canonical output trace::trace#52": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 22", + "trace_name": "Trace 52", "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1239024 + "total_fees": 1553491 }, - "test Fibonacci contract emits the canonical output trace::trace#99": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 99", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#7": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, + "total_gas_used": 31338, + "total_gas_fees": 2089202, + "total_forward_fees": 124981, + "total_fees": 2288291 + }, + "test exact arithmetic carry and subtraction boundaries::trace#9": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 9", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1292291 + "total_fees": 1192224 }, - "test named failure paths and halted rejection::trace#13": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 13", + "test advance executes one instruction and emits commitments::trace#2": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 2", "tx_count": 1, - "total_gas_used": 7970, - "total_gas_fees": 531334, - "total_forward_fees": 68090, - "total_fees": 531334 + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 }, - "test ROM and RAM boundary pages remain addressable::trace#1": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 1", + "test Fibonacci contract emits the canonical output trace::trace#17": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 17", + "tx_count": 2, + "total_gas_used": 15639, + "total_gas_fees": 1042602, + "total_forward_fees": 112536, + "total_fees": 1192224 + }, + "test v2 advance validation and storage guards::trace#7": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 7", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 93069, - "total_fees": 20601 + "total_gas_used": 2258, + "total_gas_fees": 150534, + "total_forward_fees": 80535, + "total_fees": 150534 }, - "test named failure paths and halted rejection::trace#39": { + "test named failure paths and halted rejection::trace#2": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 39", + "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, "total_forward_fees": 1195877, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#79": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 79", + "test v2 delivery validation rejects malformed routes epochs and replays::trace#10": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 17628, + "total_gas_fees": 1175201, + "total_forward_fees": 103291, + "total_fees": 1175201 + }, + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#2": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 execution edge paths preserve bounded progress::trace#23": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 23", + "tx_count": 1, + "total_gas_used": 14228, + "total_gas_fees": 948534, + "total_forward_fees": 80535, + "total_fees": 948534 + }, + "test v2 OUTP appends a routed output and commits it::trace#4": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 4", + "tx_count": 3, + "total_gas_used": 18517, + "total_gas_fees": 1234470, + "total_forward_fees": 235739, + "total_fees": 1308334 + }, + "test v2 advance validation and storage guards::trace#25": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 25", + "tx_count": 1, + "total_gas_used": 4441, + "total_gas_fees": 296068, + "total_forward_fees": 80535, + "total_fees": 296068 + }, + "test arithmetic logical and SYS opcodes::trace#10": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 17294, + "total_gas_fees": 1152935, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1302557 }, - "test Fibonacci contract emits the canonical output trace::trace#72": { + "test Fibonacci contract emits the canonical output trace::trace#56": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 72", + "trace_name": "Trace 56", "tx_count": 2, - "total_gas_used": 17064, - "total_gas_fees": 1137602, + "total_gas_used": 16650, + "total_gas_fees": 1110002, "total_forward_fees": 112536, - "total_fees": 1287224 + "total_fees": 1259624 }, - "test Fibonacci contract emits the canonical output trace::trace#76": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 76", + "test RAM and branch opcodes::trace#5": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#71": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 71", + "test ROM and RAM boundary pages remain addressable::trace#12": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 12", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1202024 }, - "test Fibonacci contract emits the canonical output trace::trace#78": { + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#4": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 4", + "tx_count": 2, + "total_gas_used": 35347, + "total_gas_fees": 2356469, + "total_forward_fees": 199293, + "total_fees": 2543669 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#11": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 11", + "tx_count": 1, + "total_gas_used": 17050, + "total_gas_fees": 1136668, + "total_forward_fees": 103291, + "total_fees": 1136668 + }, + "test v2 inherited opcode families execute through the contract::trace#24": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 24", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#87": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 78", + "trace_name": "Trace 87", "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1239024 + "total_fees": 1192224 }, - "test JC false path and maximum jump target::trace#4": { - "test_name": "test JC false path and maximum jump target", - "trace_name": "Trace 4", + "test stale and invalid advance requests do not mutate state::trace#1": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92002, + "total_fees": 20601 + }, + "test v2 execution edge paths preserve bounded progress::trace#24": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 24", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test register load jump and ADC without carry::trace#1": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test v2 execution edge paths preserve bounded progress::trace#6": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 6", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test named failure paths and halted rejection::trace#19": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 19", + "tx_count": 1, + "total_gas_used": 8508, + "total_gas_fees": 567201, + "total_forward_fees": 68090, + "total_fees": 567201 + }, + "test Fibonacci contract emits the canonical output trace::trace#10": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 10", "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, + "total_gas_used": 19538, + "total_gas_fees": 1302535, "total_forward_fees": 112536, - "total_fees": 1292291 + "total_fees": 1553491 }, - "test named failure paths and halted rejection::trace#33": { + "test named failure paths and halted rejection::trace#11": { "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 33", + "trace_name": "Trace 11", + "tx_count": 1, + "total_gas_used": 1761, + "total_gas_fees": 117401, + "total_forward_fees": 68090, + "total_fees": 117401 + }, + "test RAM and branch opcodes::trace#13": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 13", "tx_count": 2, "total_gas_used": 17140, "total_gas_fees": 1142669, "total_forward_fees": 112536, "total_fees": 1292291 }, - "test Fibonacci contract emits the canonical output trace::trace#45": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 45", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#1": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92357, + "total_fees": 20601 + }, + "test v2 inherited opcode families execute through the contract::trace#23": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 23", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 + "total_gas_used": 30558, + "total_gas_fees": 2037202, + "total_forward_fees": 124981, + "total_fees": 2236291 }, - "test Fibonacci contract emits the canonical output trace::trace#65": { + "test Fibonacci contract emits the canonical output trace::trace#11": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 65", + "trace_name": "Trace 11", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test arithmetic and flag boundary outcomes::trace#11": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 11", + "test Fibonacci contract emits the canonical output trace::trace#97": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 97", "tx_count": 2, - "total_gas_used": 17206, - "total_gas_fees": 1147069, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1296691 + "total_fees": 1192224 }, - "test arithmetic logical and SYS opcodes::trace#12": { - "test_name": "test arithmetic logical and SYS opcodes", + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#1": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92002, + "total_fees": 20601 + }, + "test named failure paths and halted rejection::trace#1": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 92713, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#12": { + "test_name": "test Fibonacci contract emits the canonical output trace", "trace_name": "Trace 12", "tx_count": 2, - "total_gas_used": 17845, - "total_gas_fees": 1189669, + "total_gas_used": 18997, + "total_gas_fees": 1266469, "total_forward_fees": 112536, - "total_fees": 1339291 + "total_fees": 1416091 }, - "test stale and invalid advance requests do not mutate state::trace#2": { - "test_name": "test stale and invalid advance requests do not mutate state", + "test v2 OUTP appends a routed output and commits it::trace#2": { + "test_name": "test v2 OUTP appends a routed output and commits it", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4367768, + "total_fees": 70068 + }, + "test exact arithmetic carry and subtraction boundaries::trace#2": { + "test_name": "test exact arithmetic carry and subtraction boundaries", "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1195877, + "total_forward_fees": 1231966, "total_fees": 63401 }, - "test Fibonacci contract emits the canonical output trace::trace#95": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 95", + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#3": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "total_gas_used": 30358, + "total_gas_fees": 2023869, + "total_forward_fees": 124981, + "total_fees": 2222958 }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#5": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 5", - "tx_count": 3, - "total_gas_used": 26269, - "total_gas_fees": 1751270, - "total_forward_fees": 243739, - "total_fees": 1821490 + "test v2 bounced delivery keeps the outbox retryable::trace#2": { + "test_name": "test v2 bounced delivery keeps the outbox retryable", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4380391, + "total_fees": 70068 + }, + "test v2 execution edge paths preserve bounded progress::trace#22": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 22", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test ROM and RAM boundary pages remain addressable::trace#7": { + "test_name": "test ROM and RAM boundary pages remain addressable", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 19279, + "total_gas_fees": 1285269, + "total_forward_fees": 112536, + "total_fees": 1434891 }, "test v2 acknowledgement retires output and rejects wrong peer::trace#3": { "test_name": "test v2 acknowledgement retires output and rejects wrong peer", @@ -2291,473 +3942,545 @@ "total_forward_fees": 91291, "total_fees": 20601 }, - "test ROM and RAM boundary pages remain addressable::trace#12": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 12", + "test v2 bounded advance stops at a routed output event::trace#1": { + "test_name": "test v2 bounded advance stops at a routed output event", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 94847, + "total_fees": 20601 + }, + "test v2 execution edge paths preserve bounded progress::trace#2": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 2", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4281945, + "total_fees": 70068 + }, + "test stale and invalid advance requests do not mutate state::trace#5": { + "test_name": "test stale and invalid advance requests do not mutate state", + "trace_name": "Trace 5", + "tx_count": 1, + "total_gas_used": 3251, + "total_gas_fees": 216734, + "total_forward_fees": 68090, + "total_fees": 216734 + }, + "test v2 advance validation and storage guards::trace#21": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 21", + "tx_count": 1, + "total_gas_used": 3230, + "total_gas_fees": 215334, + "total_forward_fees": 80535, + "total_fees": 215334 + }, + "test Fibonacci contract emits the canonical output trace::trace#73": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 73", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1192224 }, - "test arithmetic logical and SYS opcodes::trace#1": { - "test_name": "test arithmetic logical and SYS opcodes", + "test top up message preserves metadata::trace#1": { + "test_name": "test top up message preserves metadata", "trace_name": "Trace 1", "tx_count": 1, "total_gas_used": 309, "total_gas_fees": 20601, - "total_forward_fees": 92357, + "total_forward_fees": 89869, "total_fees": 20601 }, - "test named failure paths and halted rejection::trace#24": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 24", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "test Fibonacci contract emits the canonical output trace::trace#53": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 53", + "tx_count": 2, + "total_gas_used": 16076, + "total_gas_fees": 1071735, + "total_forward_fees": 112536, + "total_fees": 1221357 }, - "test register load jump and ADC without carry::trace#12": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 12", + "test Fibonacci contract emits the canonical output trace::trace#37": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 37", "tx_count": 2, - "total_gas_used": 17232, - "total_gas_fees": 1148802, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1298424 + "total_fees": 1221357 }, - "test v2 advance executes a canonical instruction and rejects stale state::trace#4": { - "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "test v2 bounced delivery keeps the outbox retryable::trace#4": { + "test_name": "test v2 bounced delivery keeps the outbox retryable", "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 4726, - "total_gas_fees": 315068, - "total_forward_fees": 80535, - "total_fees": 315068 + "total_gas_used": 1719, + "total_gas_fees": 114601, + "total_forward_fees": 44446, + "total_fees": 114601 }, - "test arithmetic logical and SYS opcodes::trace#8": { - "test_name": "test arithmetic logical and SYS opcodes", + "test exact arithmetic carry and subtraction boundaries::trace#6": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 6", + "tx_count": 2, + "total_gas_used": 17064, + "total_gas_fees": 1137602, + "total_forward_fees": 112536, + "total_fees": 1287224 + }, + "test v2 receiver selectors topup and external rejection::trace#8": { + "test_name": "test v2 receiver selectors topup and external rejection", "trace_name": "Trace 8", + "tx_count": 1, + "total_gas_used": 2946, + "total_gas_fees": 196401, + "total_forward_fees": 65246, + "total_fees": 196401 + }, + "test v2 execution edge paths preserve bounded progress::trace#30": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 30", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4371191, + "total_fees": 70068 + }, + "test Fibonacci contract emits the canonical output trace::trace#86": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 86", "tx_count": 2, - "total_gas_used": 17206, - "total_gas_fees": 1147069, + "total_gas_used": 17064, + "total_gas_fees": 1137602, "total_forward_fees": 112536, - "total_fees": 1296691 + "total_fees": 1287224 }, - "test arithmetic and flag boundary outcomes::trace#14": { - "test_name": "test arithmetic and flag boundary outcomes", + "test v2 inherited opcode families execute through the contract::trace#12": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 12", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test v2 inherited opcode families execute through the contract::trace#21": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 21", + "tx_count": 2, + "total_gas_used": 31341, + "total_gas_fees": 2089402, + "total_forward_fees": 124981, + "total_fees": 2288492 + }, + "test arithmetic logical and SYS opcodes::trace#14": { + "test_name": "test arithmetic logical and SYS opcodes", "trace_name": "Trace 14", "tx_count": 2, - "total_gas_used": 18821, - "total_gas_fees": 1254735, + "total_gas_used": 18599, + "total_gas_fees": 1239935, + "total_forward_fees": 112536, + "total_fees": 1389557 + }, + "test advance executes one instruction and emits commitments::trace#3": { + "test_name": "test advance executes one instruction and emits commitments", + "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test v2 inherited opcode families execute through the contract::trace#10": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 10", + "tx_count": 1, + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4318034, + "total_fees": 70068 + }, + "test arithmetic and flag boundary outcomes::trace#13": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17224, + "total_gas_fees": 1148269, + "total_forward_fees": 112536, + "total_fees": 1297891 + }, + "test Fibonacci contract emits the canonical output trace::trace#96": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 96", + "tx_count": 2, + "total_gas_used": 18997, + "total_gas_fees": 1266469, "total_forward_fees": 112536, - "total_fees": 1404357 + "total_fees": 1416091 }, - "test ROM and RAM boundary pages remain addressable::trace#13": { - "test_name": "test ROM and RAM boundary pages remain addressable", + "test v2 execution edge paths preserve bounded progress::trace#13": { + "test_name": "test v2 execution edge paths preserve bounded progress", "trace_name": "Trace 13", "tx_count": 2, - "total_gas_used": 19836, - "total_gas_fees": 1322402, - "total_forward_fees": 112536, - "total_fees": 1472024 + "total_gas_used": 31173, + "total_gas_fees": 2078202, + "total_forward_fees": 124981, + "total_fees": 2277291 }, - "test exact arithmetic carry and subtraction boundaries::trace#5": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 5", + "test INC sets carry and zero on nibble wrap::trace#3": { + "test_name": "test INC sets carry and zero on nibble wrap", + "trace_name": "Trace 3", "tx_count": 2, "total_gas_used": 15786, "total_gas_fees": 1052402, "total_forward_fees": 112536, "total_fees": 1202024 }, - "test arithmetic logical and SYS opcodes::trace#4": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 4", + "test v2 execution edge paths preserve bounded progress::trace#1": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 1", + "tx_count": 1, + "total_gas_used": 309, + "total_gas_fees": 20601, + "total_forward_fees": 95202, + "total_fees": 20601 + }, + "test Fibonacci contract emits the canonical output trace::trace#65": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 65", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1221357 }, - "test named failure paths and halted rejection::trace#21": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 21", - "tx_count": 1, - "total_gas_used": 8846, - "total_gas_fees": 589734, - "total_forward_fees": 68090, - "total_fees": 589734 + "test arithmetic logical and SYS opcodes::trace#13": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 13", + "tx_count": 2, + "total_gas_used": 17765, + "total_gas_fees": 1184335, + "total_forward_fees": 112536, + "total_fees": 1333957 }, - "test v2 acknowledgement retires output and rejects wrong peer::trace#2": { - "test_name": "test v2 acknowledgement retires output and rejects wrong peer", - "trace_name": "Trace 2", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#4": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 4", "tx_count": 1, "total_gas_used": 1051, "total_gas_fees": 70068, - "total_forward_fees": 3687807, + "total_forward_fees": 4318034, "total_fees": 70068 }, - "test v2 HALT is an architectural state and rejects subsequent advance::trace#2": { - "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", - "trace_name": "Trace 2", + "test v2 execution edge paths preserve bounded progress::trace#18": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 18", "tx_count": 1, "total_gas_used": 1051, "total_gas_fees": 70068, - "total_forward_fees": 3625451, + "total_forward_fees": 4318034, "total_fees": 70068 }, - "test named failure paths and halted rejection::trace#40": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 40", - "tx_count": 1, - "total_gas_used": 2421, - "total_gas_fees": 161401, - "total_forward_fees": 68090, - "total_fees": 161401 - }, - "test v2 acknowledgement retires output and rejects wrong peer::trace#5": { + "test v2 acknowledgement retires output and rejects wrong peer::trace#8": { "test_name": "test v2 acknowledgement retires output and rejects wrong peer", - "trace_name": "Trace 5", + "trace_name": "Trace 8", "tx_count": 1, - "total_gas_used": 22983, - "total_gas_fees": 1532201, + "total_gas_used": 23418, + "total_gas_fees": 1561201, "total_forward_fees": 96002, - "total_fees": 1669535 + "total_fees": 1698535 }, - "test Fibonacci contract emits the canonical output trace::trace#56": { + "test Fibonacci contract emits the canonical output trace::trace#93": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 56", - "tx_count": 2, - "total_gas_used": 16650, - "total_gas_fees": 1110002, - "total_forward_fees": 112536, - "total_fees": 1259624 - }, - "test register load jump and ADC without carry::trace#3": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 3", + "trace_name": "Trace 93", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 16076, + "total_gas_fees": 1071735, "total_forward_fees": 112536, - "total_fees": 1202024 - }, - "test v2 acknowledgement retires output and rejects wrong peer::trace#4": { - "test_name": "test v2 acknowledgement retires output and rejects wrong peer", - "trace_name": "Trace 4", - "tx_count": 1, - "total_gas_used": 7196, - "total_gas_fees": 479734, - "total_forward_fees": 96002, - "total_fees": 479734 + "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#47": { + "test Fibonacci contract emits the canonical output trace::trace#55": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 47", + "trace_name": "Trace 55", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test RAM and branch opcodes::trace#13": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 13", - "tx_count": 2, - "total_gas_used": 17140, - "total_gas_fees": 1142669, - "total_forward_fees": 112536, - "total_fees": 1292291 - }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#1": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 1", - "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92002, - "total_fees": 20601 - }, - "test RAM and branch opcodes::trace#2": { - "test_name": "test RAM and branch opcodes", - "trace_name": "Trace 2", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 - }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#4": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 4", + "test v2 bounded advance traverses every nonblocking ISA family::trace#3": { + "test_name": "test v2 bounded advance traverses every nonblocking ISA family", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 31630, - "total_gas_fees": 2108669, - "total_forward_fees": 199293, - "total_fees": 2295869 + "total_gas_used": 27352, + "total_gas_fees": 1823469, + "total_forward_fees": 124981, + "total_fees": 2022558 }, - "test stale and invalid advance requests do not mutate state::trace#6": { - "test_name": "test stale and invalid advance requests do not mutate state", - "trace_name": "Trace 6", + "test named failure paths and halted rejection::trace#13": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 13", "tx_count": 1, - "total_gas_used": 1777, - "total_gas_fees": 118468, + "total_gas_used": 7970, + "total_gas_fees": 531334, "total_forward_fees": 68090, - "total_fees": 118468 + "total_fees": 531334 }, - "test Fibonacci contract emits the canonical output trace::trace#59": { + "test Fibonacci contract emits the canonical output trace::trace#92": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 59", + "trace_name": "Trace 92", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 16341, + "total_gas_fees": 1089402, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1239024 }, - "test deploy exposes Tonolith metadata::trace#2": { - "test_name": "test deploy exposes Tonolith metadata", + "test v2 delivery validation rejects malformed routes epochs and replays::trace#2": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", "trace_name": "Trace 2", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1195877, - "total_fees": 63401 + "total_gas_used": 1051, + "total_gas_fees": 70068, + "total_forward_fees": 4384479, + "total_fees": 70068 }, - "test arithmetic and flag boundary outcomes::trace#15": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 15", + "test Fibonacci contract emits the canonical output trace::trace#31": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 31", "tx_count": 2, - "total_gas_used": 18865, - "total_gas_fees": 1257669, + "total_gas_used": 15639, + "total_gas_fees": 1042602, "total_forward_fees": 112536, - "total_fees": 1407291 - }, - "test stale and invalid advance requests do not mutate state::trace#4": { - "test_name": "test stale and invalid advance requests do not mutate state", - "trace_name": "Trace 4", - "tx_count": 1, - "total_gas_used": 3045, - "total_gas_fees": 203001, - "total_forward_fees": 68090, - "total_fees": 203001 + "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#57": { + "test Fibonacci contract emits the canonical output trace::trace#81": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 57", + "trace_name": "Trace 81", "tx_count": 2, "total_gas_used": 16076, "total_gas_fees": 1071735, "total_forward_fees": 112536, "total_fees": 1221357 }, - "test Fibonacci contract emits the canonical output trace::trace#82": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 82", + "test register load jump and ADC without carry::trace#13": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 13", "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, + "total_gas_used": 17140, + "total_gas_fees": 1142669, "total_forward_fees": 112536, - "total_fees": 1416091 + "total_fees": 1292291 }, - "test named failure paths and halted rejection::trace#18": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 18", + "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#5": { + "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", + "trace_name": "Trace 5", + "tx_count": 2, + "total_gas_used": 24810, + "total_gas_fees": 1654002, + "total_forward_fees": 199293, + "total_fees": 1702001 + }, + "test v2 delivery validation rejects malformed routes epochs and replays::trace#9": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 9", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 7442, + "total_gas_fees": 496134, + "total_forward_fees": 103291, + "total_fees": 496134 }, - "test arithmetic logical and SYS opcodes::trace#14": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 14", + "test v2 inherited opcode families execute through the contract::trace#9": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 9", "tx_count": 2, - "total_gas_used": 18599, - "total_gas_fees": 1239935, - "total_forward_fees": 112536, - "total_fees": 1389557 + "total_gas_used": 29753, + "total_gas_fees": 1983535, + "total_forward_fees": 124981, + "total_fees": 2182624 }, - "test Fibonacci contract emits the canonical output trace::trace#46": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 46", + "test RAM and branch opcodes::trace#9": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 9", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 17845, + "total_gas_fees": 1189669, "total_forward_fees": 112536, - "total_fees": 1221357 - }, - "test advance executes one instruction and emits commitments::trace#2": { - "test_name": "test advance executes one instruction and emits commitments", - "trace_name": "Trace 2", - "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_fees": 1339291 }, - "test register load jump and ADC without carry::trace#4": { - "test_name": "test register load jump and ADC without carry", - "trace_name": "Trace 4", + "test exact arithmetic carry and subtraction boundaries::trace#11": { + "test_name": "test exact arithmetic carry and subtraction boundaries", + "trace_name": "Trace 11", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 17206, + "total_gas_fees": 1147069, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1296691 }, - "test v2 HALT is an architectural state and rejects subsequent advance::trace#1": { - "test_name": "test v2 HALT is an architectural state and rejects subsequent advance", - "trace_name": "Trace 1", + "test v2 advance validation and storage guards::trace#4": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 4", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92713, - "total_fees": 20601 + "total_gas_used": 1999, + "total_gas_fees": 133268, + "total_forward_fees": 80535, + "total_fees": 133268 }, - "test Fibonacci contract emits the canonical output trace::trace#86": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 86", + "test arithmetic and flag boundary outcomes::trace#6": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 6", "tx_count": 2, "total_gas_used": 17064, "total_gas_fees": 1137602, "total_forward_fees": 112536, "total_fees": 1287224 }, - "test named failure paths and halted rejection::trace#1": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 1", + "test v2 execution edge paths preserve bounded progress::trace#7": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 7", + "tx_count": 2, + "total_gas_used": 28817, + "total_gas_fees": 1921135, + "total_forward_fees": 124981, + "total_fees": 2120224 + }, + "test v2 execution edge paths preserve bounded progress::trace#25": { + "test_name": "test v2 execution edge paths preserve bounded progress", + "trace_name": "Trace 25", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92713, - "total_fees": 20601 + "total_gas_used": 14942, + "total_gas_fees": 996134, + "total_forward_fees": 80535, + "total_fees": 996134 }, - "test advance executes one instruction and emits commitments::trace#1": { - "test_name": "test advance executes one instruction and emits commitments", - "trace_name": "Trace 1", + "test register load jump and ADC without carry::trace#2": { + "test_name": "test register load jump and ADC without carry", + "trace_name": "Trace 2", "tx_count": 1, - "total_gas_used": 309, - "total_gas_fees": 20601, - "total_forward_fees": 92713, - "total_fees": 20601 + "total_gas_used": 951, + "total_gas_fees": 63401, + "total_forward_fees": 1231966, + "total_fees": 63401 }, - "test ROM and RAM boundary pages remain addressable::trace#11": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 11", + "test v2 inherited opcode families execute through the contract::trace#3": { + "test_name": "test v2 inherited opcode families execute through the contract", + "trace_name": "Trace 3", "tx_count": 2, - "total_gas_used": 19617, - "total_gas_fees": 1307802, - "total_forward_fees": 112536, - "total_fees": 1457424 + "total_gas_used": 27352, + "total_gas_fees": 1823469, + "total_forward_fees": 124981, + "total_fees": 2022558 }, - "test named failure paths and halted rejection::trace#3": { - "test_name": "test named failure paths and halted rejection", + "test RAM and branch opcodes::trace#3": { + "test_name": "test RAM and branch opcodes", "trace_name": "Trace 3", + "tx_count": 2, + "total_gas_used": 15786, + "total_gas_fees": 1052402, + "total_forward_fees": 112536, + "total_fees": 1202024 + }, + "test v2 advance validation and storage guards::trace#17": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 17", "tx_count": 1, - "total_gas_used": 1332, - "total_gas_fees": 88801, - "total_forward_fees": 44446, - "total_fees": 88801 + "total_gas_used": 2964, + "total_gas_fees": 197601, + "total_forward_fees": 80535, + "total_fees": 197601 }, - "test Fibonacci contract emits the canonical output trace::trace#13": { + "test Fibonacci contract emits the canonical output trace::trace#33": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 13", + "trace_name": "Trace 33", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#68": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 68", - "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, - "total_forward_fees": 112536, - "total_fees": 1416091 - }, - "test ROM and RAM boundary pages remain addressable::trace#4": { - "test_name": "test ROM and RAM boundary pages remain addressable", - "trace_name": "Trace 4", + "test top up message preserves metadata::trace#2": { + "test_name": "test top up message preserves metadata", + "trace_name": "Trace 2", "tx_count": 1, "total_gas_used": 951, "total_gas_fees": 63401, - "total_forward_fees": 1408501, + "total_forward_fees": 1195877, "total_fees": 63401 }, - "test arithmetic logical and SYS opcodes::trace#3": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 3", + "test Fibonacci contract emits the canonical output trace::trace#54": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 54", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 18997, + "total_gas_fees": 1266469, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1416091 }, - "test named failure paths and halted rejection::trace#20": { - "test_name": "test named failure paths and halted rejection", - "trace_name": "Trace 20", + "test v2 advance validation and storage guards::trace#9": { + "test_name": "test v2 advance validation and storage guards", + "trace_name": "Trace 9", "tx_count": 1, - "total_gas_used": 951, - "total_gas_fees": 63401, - "total_forward_fees": 1231966, - "total_fees": 63401 + "total_gas_used": 3620, + "total_gas_fees": 241334, + "total_forward_fees": 80535, + "total_fees": 241334 }, - "test Fibonacci contract emits the canonical output trace::trace#73": { + "test Fibonacci contract emits the canonical output trace::trace#49": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 73", + "trace_name": "Trace 49", "tx_count": 2, "total_gas_used": 15639, "total_gas_fees": 1042602, "total_forward_fees": 112536, "total_fees": 1192224 }, - "test Fibonacci contract emits the canonical output trace::trace#31": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 31", + "test v2 SYS controls cover flags shifts arithmetic and architectural stops::trace#5": { + "test_name": "test v2 SYS controls cover flags shifts arithmetic and architectural stops", + "trace_name": "Trace 5", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, - "total_forward_fees": 112536, - "total_fees": 1192224 + "total_gas_used": 30524, + "total_gas_fees": 2034935, + "total_forward_fees": 124981, + "total_fees": 2234024 }, - "test arithmetic logical and SYS opcodes::trace#18": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 18", + "test v2 delivery validation rejects malformed routes epochs and replays::trace#16": { + "test_name": "test v2 delivery validation rejects malformed routes epochs and replays", + "trace_name": "Trace 16", "tx_count": 2, - "total_gas_used": 18997, - "total_gas_fees": 1266469, - "total_forward_fees": 112536, - "total_fees": 1416091 + "total_gas_used": 35347, + "total_gas_fees": 2356469, + "total_forward_fees": 199293, + "total_fees": 2543669 }, - "test Fibonacci contract emits the canonical output trace::trace#36": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 36", - "tx_count": 2, - "total_gas_used": 16341, - "total_gas_fees": 1089402, - "total_forward_fees": 112536, - "total_fees": 1239024 + "test v2 advance executes a canonical instruction and rejects stale state::trace#4": { + "test_name": "test v2 advance executes a canonical instruction and rejects stale state", + "trace_name": "Trace 4", + "tx_count": 1, + "total_gas_used": 4744, + "total_gas_fees": 316268, + "total_forward_fees": 80535, + "total_fees": 316268 }, - "test Fibonacci contract emits the canonical output trace::trace#39": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 39", - "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, - "total_forward_fees": 112536, - "total_fees": 1221357 + "test named failure paths and halted rejection::trace#27": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 27", + "tx_count": 1, + "total_gas_used": 8256, + "total_gas_fees": 550401, + "total_forward_fees": 68090, + "total_fees": 550401 }, - "test arithmetic logical and SYS opcodes::trace#17": { - "test_name": "test arithmetic logical and SYS opcodes", - "trace_name": "Trace 17", + "test arithmetic and flag boundary outcomes::trace#7": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 18945, - "total_gas_fees": 1263002, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1412624 + "total_fees": 1202024 }, "test RAM and branch opcodes::trace#12": { "test_name": "test RAM and branch opcodes", @@ -2768,50 +4491,68 @@ "total_forward_fees": 112536, "total_fees": 1152224 }, - "test v2 IN waits, accepts authenticated input, deduplicates, and consumes::trace#3": { - "test_name": "test v2 IN waits, accepts authenticated input, deduplicates, and consumes", - "trace_name": "Trace 3", + "test RAM and branch opcodes::trace#7": { + "test_name": "test RAM and branch opcodes", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 28906, - "total_gas_fees": 1927069, - "total_forward_fees": 124981, - "total_fees": 2126158 + "total_gas_used": 18224, + "total_gas_fees": 1214935, + "total_forward_fees": 112536, + "total_fees": 1364557 }, - "test arithmetic and flag boundary outcomes::trace#5": { - "test_name": "test arithmetic and flag boundary outcomes", - "trace_name": "Trace 5", + "test named failure paths and halted rejection::trace#29": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 29", + "tx_count": 1, + "total_gas_used": 8846, + "total_gas_fees": 589734, + "total_forward_fees": 68090, + "total_fees": 589734 + }, + "test Fibonacci contract emits the canonical output trace::trace#42": { + "test_name": "test Fibonacci contract emits the canonical output trace", + "trace_name": "Trace 42", "tx_count": 2, - "total_gas_used": 17845, - "total_gas_fees": 1189669, + "total_gas_used": 16650, + "total_gas_fees": 1110002, "total_forward_fees": 112536, - "total_fees": 1339291 + "total_fees": 1259624 }, - "test exact arithmetic carry and subtraction boundaries::trace#8": { - "test_name": "test exact arithmetic carry and subtraction boundaries", - "trace_name": "Trace 8", + "test named failure paths and halted rejection::trace#31": { + "test_name": "test named failure paths and halted rejection", + "trace_name": "Trace 31", + "tx_count": 1, + "total_gas_used": 12045, + "total_gas_fees": 803001, + "total_forward_fees": 68090, + "total_fees": 803001 + }, + "test arithmetic and flag boundary outcomes::trace#15": { + "test_name": "test arithmetic and flag boundary outcomes", + "trace_name": "Trace 15", "tx_count": 2, - "total_gas_used": 15786, - "total_gas_fees": 1052402, + "total_gas_used": 18865, + "total_gas_fees": 1257669, "total_forward_fees": 112536, - "total_fees": 1202024 + "total_fees": 1407291 }, - "test Fibonacci contract emits the canonical output trace::trace#27": { - "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 27", + "test arithmetic logical and SYS opcodes::trace#17": { + "test_name": "test arithmetic logical and SYS opcodes", + "trace_name": "Trace 17", "tx_count": 2, - "total_gas_used": 15639, - "total_gas_fees": 1042602, + "total_gas_used": 18945, + "total_gas_fees": 1263002, "total_forward_fees": 112536, - "total_fees": 1192224 + "total_fees": 1412624 }, - "test Fibonacci contract emits the canonical output trace::trace#90": { + "test Fibonacci contract emits the canonical output trace::trace#7": { "test_name": "test Fibonacci contract emits the canonical output trace", - "trace_name": "Trace 90", + "trace_name": "Trace 7", "tx_count": 2, - "total_gas_used": 16076, - "total_gas_fees": 1071735, + "total_gas_used": 15786, + "total_gas_fees": 1052402, "total_forward_fees": 112536, - "total_fees": 1221357 + "total_fees": 1202024 } } } \ No newline at end of file From 0bbc0ef5efb37a3b27ec53beaeffbb581a56bb04 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:22:46 +0100 Subject: [PATCH 58/67] docs: record v2 local release and testnet boundary --- benchmarks/v2/batch-selection.json | 2 +- docs/v2/LOCAL_RELEASE.md | 91 ++++++++++++++++++++++ docs/v2/TESTNET_VALIDATION.md | 118 +++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 docs/v2/LOCAL_RELEASE.md create mode 100644 docs/v2/TESTNET_VALIDATION.md diff --git a/benchmarks/v2/batch-selection.json b/benchmarks/v2/batch-selection.json index ac9ab56..490c76f 100644 --- a/benchmarks/v2/batch-selection.json +++ b/benchmarks/v2/batch-selection.json @@ -3,7 +3,7 @@ "measurement": { "command": "acton test tests/v2_batch_contract.test.tolk --snapshot gas.json --fuzz-seed 42", "environment": "clean temporary Acton checkout", - "sourceCommit": "88f5cec", + "sourceCommit": "f01cfae", "paths": [ "full nonblocking ISA sequence", "legacy OUT event", diff --git a/docs/v2/LOCAL_RELEASE.md b/docs/v2/LOCAL_RELEASE.md new file mode 100644 index 0000000..40850b8 --- /dev/null +++ b/docs/v2/LOCAL_RELEASE.md @@ -0,0 +1,91 @@ +# Tonolith v2 local release record + +This record separates reproducible local evidence from public-network evidence. +It is not a deployment approval. The v2 testnet gate remains closed while the +coverage and mutation requirements below are open. + +## Toolchain observed + +- Acton `1.0.0`, git revision `3a4f0dc`; +- Tolk `1.4.0` through the Acton installation; +- Node.js `v26.7.0`; +- npm `12.0.2`; +- TypeScript cell dependencies from the checked-in lockfile; +- effective Git identity `ef-code` / + `111162892+EF-Code@users.noreply.github.com`. + +## Commands and results + +| Gate | Command | Result | +| --- | --- | --- | +| Acton formatting | `acton fmt --check` | PASS | +| Acton static checking | `acton check` | PASS | +| Acton compilation | `acton build` | PASS | +| Acton functional suite | `acton test --fuzz-seed 42` | PASS: 41 tests | +| TypeScript typecheck | `npm run typecheck` | PASS | +| TypeScript build | `npm run build` | PASS | +| TypeScript suite | `npm test` | PASS: 71 tests | +| Clean-directory reproducibility | `tests/v2/reproducibility.test.ts` | PASS | +| Acton gas snapshot | `acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42` | PASS | +| Acton coverage gate | `acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42` | OPEN: 98.3% lines, 74.1% branches, 86.7% blended | +| TypeScript coverage | `npm run test:coverage` | OPEN: 93.03% lines, 74.25% branches | +| v2 critical/major mutation run | `acton test --mutate --mutate-contract TonolithCoreV2 --mutation-levels critical,major --mutation-workers 2 --fuzz-seed 42` | OPEN: 401 killed, 254 survived, 34 compile-invalid, 61.2% score | + +The open gates are recorded failures, not waived requirements. The Acton +minimum-score command exits non-zero at the current 86.7% blended score. The +mutation run also fails the required critical-mutant and major-mutant bars. +Survivors need targeted tests or an explicit reviewed reachability disposition +before this record can become a release approval. + +The repository does not currently contain a configured local TON validator, +and no `lite-client`, `validator-engine-console`, `toncli`, or validator Docker +image was available during this check. Acton emulation therefore does not prove +validator action rollback, bounce delivery, external-message rejection at the +validator boundary, or final balance reconciliation. + +## Gas and batching evidence + +The refreshed Acton snapshot in [`../../benchmarks/gas-v2.json`](../../benchmarks/gas-v2.json) +measures these v2 message classes: + +- `V2Advance`: 1,844–47,997 gas, average 22,332 across 73 samples; +- `V2DeliverInput`: 309–49,066 gas, average 16,819 across 20 samples; +- `V2DispatchOutput`: 1,877–17,899 gas, average 8,728 across 5 samples; +- `V2InputAccepted`: 309–23,418 gas, average 7,219 across 5 samples; +- `V2TopUp`: 2,546–2,946 gas, average 2,798 across 3 samples. + +The complete candidate matrix is [`../../benchmarks/v2/batch-selection.json`](../../benchmarks/v2/batch-selection.json). +The selected value is `MAX_STEPS_PER_ADVANCE = 1`. Its measured worst-case +local contract gas is `47,997`, and its routed-output trace is `48,306` gas. +Candidates 2, 4, 8, 16, and 32 remain below the local 400,000-gas project +ceiling, while 64 reaches `677,438` gas and is rejected. No candidate can pass +the guide's 40%-of-testnet-reference rule until a real testnet reference exists. +Action count, storage-cell growth, message-size, and testnet fee gates remain +open because Acton does not provide a stable release table for those fields. + +## Deterministic benchmark vectors + +The checked-in vectors in [`../../benchmarks/v2/expected.json`](../../benchmarks/v2/expected.json) +are local-emulator evidence: + +- Fibonacci: `1, 1, 2, 3, 5, 8, 13`; 97 instructions and 97 one-step batches; + final state hash `8238d6bbcd507f4a3b9cba3b1ad48b2d88fd6ab11f7068b75340a3f0044a9f1f`; +- CRC-4: output `5`, 12 instructions; +- cellular automaton: output `0`, 50 instructions; +- counter pipeline: outputs `1, 2`, two acknowledgements; +- two-core 8-bit carry path: carry output `1`, two acknowledgements. + +Those hashes prove agreement between the checked-in emulator and its own +canonical inputs. They are not TON transaction or finality evidence. + +## Testnet boundary + +Two global Acton testnet wallet entries are configured for the project, but no +v2 deployment was attempted from this worktree because the local release gates +are open. No v2 address, transaction, fee, storage balance, keeper race, or +finality claim should be inferred from the wallet configuration. + +When the local gates pass, use the procedure in +[`TESTNET_VALIDATION.md`](TESTNET_VALIDATION.md). Record every value from the +chain and explorer output there; never replace a missing value with an emulator +estimate. diff --git a/docs/v2/TESTNET_VALIDATION.md b/docs/v2/TESTNET_VALIDATION.md new file mode 100644 index 0000000..3c576c2 --- /dev/null +++ b/docs/v2/TESTNET_VALIDATION.md @@ -0,0 +1,118 @@ +# Tonolith v2 testnet validation + +This is a testnet-only runbook. It is intentionally a blank evidence record +until a release candidate has passed all local gates. The existing v1 run in +[`../TESTNET.md`](../TESTNET.md) is separate evidence and must not be reused as +v2 proof. + +## Release boundary + +Do not run the deployment script while any local release gate is open. The v2 +candidate must first pass formatting, checking, compilation, all Acton and +TypeScript tests, reproducibility, coverage, mutation, and the gas/action/ +storage/message-size checks. Mainnet is out of scope. + +The current status is **not deployed**. The local Acton coverage and mutation +gates are open, and this record contains no fabricated address or transaction. + +## Preflight + +```sh +acton wallet list +acton doctor +acton fmt --check +acton check +acton build +acton test --fuzz-seed 42 +npm run typecheck +npm test +``` + +The deployment wallet is selected by the script as +`tonolith-testnet`. A second independently funded keeper should be available +as `tonolith-keeper-b`. Wallet files, mnemonics, signed messages, provider +credentials, and API keys stay outside the repository and are never printed. + +## Single-core Fibonacci deployment + +After the local release record is green, run exactly the testnet selector: + +```sh +acton script scripts/deploy-v2-fibonacci.tolk --net testnet --explorer tonviewer +``` + +Capture the script output and explorer data without committing secrets. The +deployment record must contain: + +```text +network: testnet +address: +deployment transaction: +deployment lt / masterchain reference: +code hash: +initial data hash: +StateInit hash: +program id: e140cb991c86e81663dbe688da3e6a2b18d14b260b0aca0a0c7b1381707a7dda +ROM root: 029ccf15774f680518705bb7c8be4b2ca92f34a4ceb9c963e55609225398c748 +RAM root: ed807133df392920f6ed73d30ab7e67ecaee528d940e1be1ea8e44cd25031b8e +route root: 9e548d24c3c980ff9fe617f92286289613d6deae0b0da0cbe88aa8a51e6d9bcb +limits hash: f8d3ee4ee34e55e437aa1decfa2742d81aa6d623beec67c9394d736376beb1fb +static commitment: 2c5978dc7ca91a1899cd345f5e9232500f50670ac7612fddb30f64eb879677f9 +initial state hash: +final state hash: +``` + +The immutable commitments above are the canonical Fibonacci artifact values; +the address and StateInit hashes still depend on the compiled contract code +and the complete network cell. Verify them from the actual StateInit BOC. + +## Transaction and keeper checks + +For the deployment and each accepted one-step `Advance`: + +1. confirm RPC submission, inclusion, and masterchain-referenced finality as + separate observations; +2. record gas used, computation/action/forward/storage fees, balance, and + reserve behavior; +3. verify `advanceCount`, `instructionCount`, PC, output event, previous-state + hash, next-state hash, and the next expected message; +4. submit one tick from `tonolith-keeper-b` rather than the deployer; +5. deliberately submit a stale count/hash from the other keeper and verify + rejection with no architectural state change; +6. retry with the fresh count/hash and verify exactly one accepted mutation; +7. replay the accepted history with the independent verifier using two + independent chain data sources; +8. export a trace accepted by the visualizer and label it `testnet`, never + `local-emulator`. + +The expected output sequence is `1, 1, 2, 3, 5, 8, 13`. A testnet claim is +incomplete unless the output events, final state hash, actual fees, and finality +are all recorded. + +## Multi-core boundary + +The TypeScript emulator proves authenticated routing, duplicate delivery, +acknowledgement, retry, backpressure, counter-pipeline, and two-core carry +vectors locally. A production multi-core testnet deployment additionally needs +a non-circular address/StateInit manifest for every peer. The current immutable +peer table binds exact peer addresses into each core's static data, so two cores +that derive each other's addresses cannot be deployed by independently guessing +those addresses. Until a reviewed address-manifest scheme or factory is added, +multi-core deployment remains an unresolved engineering gate and must not be +described as testnet-proven. + +## Evidence ledger + +| Evidence | Status | Record | +| --- | --- | --- | +| v2 code/data/StateInit commitments | pending | — | +| real deployment inclusion/finality | pending | — | +| single-step Fibonacci outputs | pending | — | +| actual gas and fees | pending | — | +| second keeper accepted tick | pending | — | +| stale/racing keeper rejection | pending | — | +| duplicate delivery and acknowledgement | pending | — | +| bounce/retry and backpressure recovery | pending | — | +| independent verifier replay | pending | — | +| visualizer-compatible verified trace | pending | — | +| two-core pipeline deployment | blocked by address manifest | — | From f8a1c3dd2e8a32b36fadd6ae787e0912853e8030 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:25:15 +0100 Subject: [PATCH 59/67] feat: make v2 CLI replay and trace validation executable --- tests/v2/cli.test.ts | 112 +++++++++++++++++++++++++++++++++++++++++++ tools/tonolith-v2.ts | 26 +++++++--- 2 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 tests/v2/cli.test.ts diff --git a/tests/v2/cli.test.ts b/tests/v2/cli.test.ts new file mode 100644 index 0000000..8778904 --- /dev/null +++ b/tests/v2/cli.test.ts @@ -0,0 +1,112 @@ +import assert from "node:assert/strict"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { runCli, type CliIo } from "../../tools/tonolith-v2.js"; + +test("v2 CLI assembles, verifies, and benchmarks a portable artifact", async () => { + const root = await mkdtemp(join(tmpdir(), "tonolith-v2-cli-")); + try { + const source = join(root, "program.tasm"); + const artifact = join(root, "artifact"); + await writeFile(source, ".entry start\nstart:\nNOP\nHALT\n", "utf8"); + const assembled = await capture((io) => runCli(["assemble", source, "--out", artifact], io)); + assert.equal(assembled.code, 0); + const assembledJson = JSON.parse(assembled.stdout) as { readonly artifactDir: string }; + assert.equal(assembledJson.artifactDir, artifact); + + const verified = await capture((io) => runCli(["verify-artifact", artifact], io)); + assert.equal(verified.code, 0); + assert.equal((JSON.parse(verified.stdout) as { readonly valid: boolean }).valid, true); + + const benchmark = await capture((io) => runCli(["benchmark", "local", artifact], io)); + assert.equal(benchmark.code, 0); + const benchmarkJson = JSON.parse(benchmark.stdout) as { + readonly requestedMaxInstructions: number; + readonly requestedMaxOutputs: number; + readonly executed: number; + }; + assert.equal(benchmarkJson.requestedMaxInstructions, 1); + assert.equal(benchmarkJson.requestedMaxOutputs, 16); + assert.equal(benchmarkJson.executed, 1); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +test("v2 CLI validates trace input and keeps network replay fail-closed", async () => { + const root = await mkdtemp(join(tmpdir(), "tonolith-v2-trace-")); + try { + const hash = "0".repeat(64); + const state = { + stateHash: hash, + pc: 0, + accumulator: 0, + flags: 0, + epoch: "0", + instructionCount: "0", + advanceCount: "0", + status: 0, + outputCommitment: hash, + inputCommitment: hash, + inboxCount: 0, + outboxCount: 0, + }; + const tracePath = join(root, "trace.json"); + await writeFile(tracePath, JSON.stringify({ + schema: "tonolith-trace-v2", + evidence: "local-emulator", + network: "local", + runId: hash, + coreIds: [0], + programId: hash, + romRoot: hash, + routeRoot: hash, + staticCommitment: hash, + frames: [{ index: 0, coreId: 0, before: state, after: state, phase: "none" }], + unresolved: [], + }), "utf8"); + const rendered = await capture((io) => runCli(["trace", "local-core", "--format", "json", "--input", tracePath], io)); + assert.equal(rendered.code, 0); + assert.equal((JSON.parse(rendered.stdout) as { readonly schema: string }).schema, "tonolith-trace-v2"); + + const invalidPath = join(root, "invalid-trace.json"); + await writeFile(invalidPath, JSON.stringify({ schema: "wrong" }), "utf8"); + const invalid = await capture((io) => runCli(["trace", "local-core", "--format", "json", "--input", invalidPath], io)); + assert.equal(invalid.code, 1); + assert.match(invalid.stderr, /unsupported trace schema/); + + const replayManifest = join(root, "run.json"); + const address = `0:${"11".repeat(32)}`; + await writeFile(replayManifest, JSON.stringify({ + address, + expectations: { + address, + network: "testnet", + codeHash: hash, + dataHash: hash, + stateInitHash: hash, + programId: hash, + runId: hash, + romRoot: hash, + routeRoot: hash, + staticCommitment: hash, + }, + sources: [], + }), "utf8"); + const replay = await capture((io) => runCli(["replay", replayManifest, "--network", "testnet"], io)); + assert.equal(replay.code, 1); + assert.match(replay.stdout, /independent replay evidence/); + } finally { + await rm(root, { recursive: true, force: true }); + } +}); + +async function capture(operation: (io: CliIo) => Promise): Promise<{ readonly code: number; readonly stdout: string; readonly stderr: string }> { + const stdout: string[] = []; + const stderr: string[] = []; + const io: CliIo = { stdout: (value) => stdout.push(value), stderr: (value) => stderr.push(value) }; + const code = await operation(io); + return { code, stdout: stdout.join(""), stderr: stderr.join("") }; +} diff --git a/tools/tonolith-v2.ts b/tools/tonolith-v2.ts index 45b190b..87ee83b 100644 --- a/tools/tonolith-v2.ts +++ b/tools/tonolith-v2.ts @@ -11,6 +11,7 @@ import { loadArtifactDirectory } from "../packages/sdk/src/node.js"; import { buildV2StateInitFromArtifact } from "../packages/sdk/src/stateinit.js"; import { planNextAction } from "../packages/keeper/src/planner.js"; import type { KeeperCoreConfig, KeeperCoreObservation } from "../packages/keeper/src/types.js"; +import { decodeTrace } from "../packages/trace/src/index.js"; import { verifyRunFromSources, type RawChainSnapshot, type ReplayEvidence, type V2VerificationExpectations } from "../packages/verifier/src/index.js"; export interface CliIo { @@ -31,7 +32,7 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr case "derive-address": return await deriveAddressCommand(rest, io); case "deploy-plan": return await deployPlanCommand(rest, io); case "verify-run": return await verifyRunCommand(rest, io); - case "replay": return incompleteNetworkCommand("replay", io); + case "replay": return await replayCommand(rest, io); case "trace": return await traceCommand(rest, io); case "keeper": return await keeperCommand(rest, io); case "benchmark": return await benchmarkCommand(rest, io); @@ -137,13 +138,26 @@ async function verifyRunCommand(args: readonly string[], io: CliIo): Promise { + const manifestPath = args[0]; + const network = option(args, "--network"); + if (manifestPath === undefined || network === undefined) return usage(io, 2, "replay requires --network testnet"); + assertTestnet(network); + const manifest = await readJson(manifestPath); + if (manifest.replay === undefined) return incompleteNetworkCommand("replay", io, "run manifest does not contain independent replay evidence"); + const sources = manifest.sources.map((source) => ({ id: source.id, fetchSnapshot: async () => readJson(source.snapshot) })); + const report = await verifyRunFromSources(manifest.address, manifest.expectations, sources, deserializeReplay(manifest.replay)); + io.stdout(json({ command: "replay", ...report })); + return report.overall === "verified" ? 0 : 1; +} + async function traceCommand(args: readonly string[], io: CliIo): Promise { const address = args[0]; const format = option(args, "--format"); const input = option(args, "--input"); if (address === undefined || format === undefined || !["text", "json", "ndjson"].includes(format)) return usage(io, 2, "trace requires
--format text|json|ndjson --input "); if (input === undefined) return incompleteNetworkCommand("trace", io, `no raw trace supplied for ${address}`); - const trace = await readJson(input); + const trace = decodeTrace(await readFile(resolve(input), "utf8")); if (format === "text") io.stdout(formatTraceText(trace)); else if (format === "json") io.stdout(json(trace)); else io.stdout(formatNdjson(trace)); @@ -170,10 +184,10 @@ async function benchmarkCommand(args: readonly string[], io: CliIo): Promise Date: Mon, 17 Aug 2026 00:28:30 +0100 Subject: [PATCH 60/67] test: cover v2 ROM and queue selector boundaries --- tests/tonolith_v2.test.tolk | 204 ++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index 905e1cc..0b2b6d6 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -85,6 +85,129 @@ fun romWithFirstWordV2(word: uint16): Cell { ); } +fun routeForCoverageV2(port: uint4): cell { + return V2Route { + domain: DOMAIN_ROUTE_V2, + sourceCoreId: 0, + sourcePort: port, + destinationCoreId: 1, + destinationPort: port, + delay: 1, + maxRecordsPerEpoch: 16, + }.toCell(); +} + +fun romWithDistinctSelectorsV2(): Cell { + val level10 = V2RomLevel1 { + leaf0: V2RomLeaf { + firstHalf: (1 as uint256) << 240, + secondHalf: ((11 as uint256) << 240) | 12, + }.toCell(), + leaf1: V2RomLeaf { + firstHalf: (2 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf2: V2RomLeaf { + firstHalf: (3 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf3: V2RomLeaf { + firstHalf: (4 as uint256) << 240, + secondHalf: 0, + }.toCell(), + }.toCell(); + val level11 = V2RomLevel1 { + leaf0: V2RomLeaf { + firstHalf: (5 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf1: V2RomLeaf { + firstHalf: (6 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf2: V2RomLeaf { + firstHalf: (7 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf3: V2RomLeaf { + firstHalf: (8 as uint256) << 240, + secondHalf: 0, + }.toCell(), + }.toCell(); + val level12 = V2RomLevel1 { + leaf0: V2RomLeaf { + firstHalf: (9 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf1: V2RomLeaf { + firstHalf: (10 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf2: V2RomLeaf { + firstHalf: (11 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf3: V2RomLeaf { + firstHalf: (12 as uint256) << 240, + secondHalf: 0, + }.toCell(), + }.toCell(); + val level13 = V2RomLevel1 { + leaf0: V2RomLeaf { + firstHalf: (13 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf1: V2RomLeaf { + firstHalf: (14 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf2: V2RomLeaf { + firstHalf: (15 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf3: V2RomLeaf { + firstHalf: (16 as uint256) << 240, + secondHalf: 0, + }.toCell(), + }.toCell(); + val level20 = V2RomLevel2 { + level10: level10, + level11: level11, + level12: level12, + level13: level13, + }.toCell(); + val level21 = V2RomLevel2 { + level10: V2RomLevel1 { + leaf0: V2RomLeaf { + firstHalf: (17 as uint256) << 240, + secondHalf: 0, + }.toCell(), + leaf1: V2RomLeaf { + firstHalf: 0, + secondHalf: 0, + }.toCell(), + leaf2: V2RomLeaf { + firstHalf: 0, + secondHalf: 0, + }.toCell(), + leaf3: V2RomLeaf { + firstHalf: 0, + secondHalf: 0, + }.toCell(), + }.toCell(), + level11: zeroV2RomLevel1(), + level12: zeroV2RomLevel1(), + level13: zeroV2RomLevel1(), + }.toCell(); + val zeroLevel2 = zeroV2RomLevel2(); + return V2RomRoot { + level20, + level21, + level22: zeroLevel2, + level23: zeroLevel2, + }.toCell(); +} + fun outputRecordV2ForTest(sequence: uint8): cell { return V2OutputRecord { domain: DOMAIN_OUTPUT_V2, @@ -2344,3 +2467,84 @@ get fun `test v2 delivery validation rejects malformed routes epochs and replays ), ).toHaveFailedTx({ exitCode: ErrorsV2.ConflictingReplay }); } + +get fun `test v2 ROM and queue selectors preserve distinct boundary records`() { + val rom = romWithDistinctSelectorsV2(); + expect(v2RomWord(rom, 0)).toEqual(1); + expect(v2RomWord(rom, 16)).toEqual(11); + expect(v2RomWord(rom, 31)).toEqual(12); + expect(v2RomWord(rom, 32)).toEqual(2); + expect(v2RomWord(rom, 64)).toEqual(3); + expect(v2RomWord(rom, 96)).toEqual(4); + expect(v2RomWord(rom, 128)).toEqual(5); + expect(v2RomWord(rom, 160)).toEqual(6); + expect(v2RomWord(rom, 192)).toEqual(7); + expect(v2RomWord(rom, 224)).toEqual(8); + expect(v2RomWord(rom, 256)).toEqual(9); + expect(v2RomWord(rom, 480)).toEqual(16); + expect(v2RomWord(rom, 512)).toEqual(17); + + var routes = emptyV2RecordRoot(); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(0)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(1)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(2)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(3)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(4)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(5)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(6)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(7)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(8)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(9)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(10)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(11)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(12)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(13)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(14)); + routes = v2AppendQueueRoot(routes, routeForCoverageV2(15)); + expect(v2QueueCount(routes)).toEqual(16); + expect((v2RecordAt(routes, 0) as Cell).load().sourcePort).toEqual(0); + expect((v2RecordAt(routes, 1) as Cell).load().sourcePort).toEqual(1); + expect((v2RecordAt(routes, 2) as Cell).load().sourcePort).toEqual(2); + expect((v2RecordAt(routes, 3) as Cell).load().sourcePort).toEqual(3); + expect((v2RecordAt(routes, 4) as Cell).load().sourcePort).toEqual(4); + expect((v2RecordAt(routes, 8) as Cell).load().sourcePort).toEqual(8); + expect((v2RecordAt(routes, 12) as Cell).load().sourcePort).toEqual(12); + expect((v2RecordAt(routes, 15) as Cell).load().sourcePort).toEqual(15); + expect( + (v2RecordAt(v2ReplaceRecordAt(routes, 0, routeForCoverageV2(15)), 0) as Cell) + .load() + .sourcePort, + ).toEqual(15); + expect( + (v2RecordAt(v2ReplaceRecordAt(routes, 3, routeForCoverageV2(14)), 3) as Cell) + .load() + .sourcePort, + ).toEqual(14); + expect( + (v2RecordAt(v2ReplaceRecordAt(routes, 4, routeForCoverageV2(13)), 4) as Cell) + .load() + .sourcePort, + ).toEqual(13); + expect( + (v2RecordAt(v2ReplaceRecordAt(routes, 8, routeForCoverageV2(12)), 8) as Cell) + .load() + .sourcePort, + ).toEqual(12); + expect( + (v2RecordAt(v2ReplaceRecordAt(routes, 12, routeForCoverageV2(11)), 12) as Cell) + .load() + .sourcePort, + ).toEqual(11); + expect( + (v2RecordAt(v2RemoveRecordAt(routes, 0), 0) as Cell).load().sourcePort, + ).toEqual(1); + expect( + (v2RecordAt(v2RemoveRecordAt(routes, 4), 4) as Cell).load().sourcePort, + ).toEqual(5); + expect( + (v2RecordAt(v2RemoveRecordAt(routes, 8), 8) as Cell).load().sourcePort, + ).toEqual(9); + expect( + (v2RecordAt(v2RemoveRecordAt(routes, 12), 12) as Cell).load().sourcePort, + ).toEqual(13); +} From 6ab0a5e9b06be062a55b0ba87e0a92891c5b12af Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 00:30:50 +0100 Subject: [PATCH 61/67] docs: expose v2 architecture and evidence status --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 5fcb26d..6250926 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,30 @@ Acton wallet named `tonolith-testnet`; no wallet or secret is created by this repository. See [docs/CLAIMS.md](docs/CLAIMS.md) for the evidence record and release context. +## Tonolith v2 + +Tonolith v2 is the versioned multi-core architecture under active engineering. +It adds an immutable `TonolithCoreV2`, versioned ABI and commitments, bounded +inboxes/outboxes, logical epochs, authenticated input delivery, +acknowledgements, retry-aware output dispatch, a permissionless bounded +`AdvanceV2`, a browser-safe SDK, an independent verifier, a testnet-only keeper, +and trace files that the [Tonolith Visualizer](https://github.com/EF-Code/tonolith-visualizer) +can consume. + +The current local evidence includes **42/42** Acton tests, **73/73** TypeScript +tests, deterministic Fibonacci/CRC-4/cellular-automaton vectors, a two-core +counter pipeline, and a two-core 8-bit carry path. The v2 Fibonacci emulator +produces `1, 1, 2, 3, 5, 8, 13` in **97 one-step batches** with final state hash +`8238d6bbcd507f4a3b9cba3b1ad48b2d88fd6ab11f7068b75340a3f0044a9f1f`. + +The measured local `V2Advance` worst case is **47,997 gas**. The selected +`MAX_STEPS_PER_ADVANCE` remains **1** until a real testnet gas reference and +the remaining action, storage, message-size, coverage, and mutation gates are +complete. The full [v2 local release record](docs/v2/LOCAL_RELEASE.md), +[batch-selection matrix](benchmarks/v2/batch-selection.json), and +[testnet validation ledger](docs/v2/TESTNET_VALIDATION.md) keep those evidence +levels separate. No v2 testnet deployment is claimed by the current branch. + Additional design references: - [docs/ISA.md](docs/ISA.md) — canonical instruction encoding and state model; From de19a76184492b719103ab450a507f5f5c582395 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:00:40 +0100 Subject: [PATCH 62/67] test: remove Acton v2 fixture lint warnings --- tests/tonolith_v2.test.tolk | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index 0b2b6d6..e700f71 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -170,12 +170,7 @@ fun romWithDistinctSelectorsV2(): Cell { secondHalf: 0, }.toCell(), }.toCell(); - val level20 = V2RomLevel2 { - level10: level10, - level11: level11, - level12: level12, - level13: level13, - }.toCell(); + val level20 = V2RomLevel2 { level10, level11, level12, level13 }.toCell(); val level21 = V2RomLevel2 { level10: V2RomLevel1 { leaf0: V2RomLeaf { From cad9ab2b09522d53e9e964a0a3fa7a34bbe53a3e Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:00:58 +0100 Subject: [PATCH 63/67] hardening: keep v2 verifier total on malformed input --- packages/verifier/src/verify.ts | 38 ++++++++++++++++++++---------- tests/v2/verifier.test.ts | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/packages/verifier/src/verify.ts b/packages/verifier/src/verify.ts index be2e13d..b9daa2b 100644 --- a/packages/verifier/src/verify.ts +++ b/packages/verifier/src/verify.ts @@ -69,7 +69,7 @@ export async function verifyRunFromSources( const reportBase = { overall, network: expectations.network, - address: Address.parse(expectations.address).toRawString(), + address: reportAddress(expectations.address), sources: sourceIds, commitments: { programId: expectations.programId, @@ -164,7 +164,9 @@ export function verifyHistoryContinuity(transactions: readonly RawTransactionSna else if (transaction.prevTransactionHash.toLowerCase() !== previous.hash.toLowerCase()) errors.push(`history link mismatch at ${transaction.hash}`); if (transaction.prevTransactionLt === undefined) errors.push(`history predecessor logical time is missing at ${transaction.hash}`); else if (transaction.prevTransactionLt !== previous.lt) errors.push(`history logical-time link mismatch at ${transaction.hash}`); - if (BigInt(transaction.lt) <= BigInt(previous.lt)) errors.push(`history is not strictly ordered at ${transaction.hash}`); + if (/^\d+$/.test(transaction.lt) && /^\d+$/.test(previous.lt) && BigInt(transaction.lt) <= BigInt(previous.lt)) { + errors.push(`history is not strictly ordered at ${transaction.hash}`); + } } previous = transaction; } @@ -341,16 +343,28 @@ function firstCell(boc: string): Cell { function deploymentSummary(account: RawAccountSnapshot): VerificationReport["deployment"] { if (account.stateInitBoc === undefined) return undefined; - const init = firstCell(account.stateInitBoc); - const state = loadStateInit(init.beginParse()); - const stateInitHash = init.hash().toString("hex"); - const codeHash = state.code === null || state.code === undefined ? undefined : state.code.hash().toString("hex"); - const dataHash = state.data === null || state.data === undefined ? undefined : state.data.hash().toString("hex"); - return { - stateInitHash, - ...(codeHash === undefined ? {} : { codeHash }), - ...(dataHash === undefined ? {} : { dataHash }), - }; + try { + const init = firstCell(account.stateInitBoc); + const state = loadStateInit(init.beginParse()); + const stateInitHash = init.hash().toString("hex"); + const codeHash = state.code === null || state.code === undefined ? undefined : state.code.hash().toString("hex"); + const dataHash = state.data === null || state.data === undefined ? undefined : state.data.hash().toString("hex"); + return { + stateInitHash, + ...(codeHash === undefined ? {} : { codeHash }), + ...(dataHash === undefined ? {} : { dataHash }), + }; + } catch { + return undefined; + } +} + +function reportAddress(value: string): string { + try { + return Address.parse(value).toRawString(); + } catch { + return value; + } } function summarizeTransactions(transactions: readonly RawTransactionSnapshot[]): { acceptedAdvances: number; deliveredInputs: number; duplicateDeliveries: number; staleAttempts: number } { diff --git a/tests/v2/verifier.test.ts b/tests/v2/verifier.test.ts index 0f48f35..e37370f 100644 --- a/tests/v2/verifier.test.ts +++ b/tests/v2/verifier.test.ts @@ -145,6 +145,47 @@ test("verifier rejects StateInit/data drift and incomplete predecessor links", a ); }); +test("verifier stays total on malformed history, address, and StateInit input", async () => { + const snapshot: RawChainSnapshot = { + source: "malformed-a", + network: "testnet", + account: { + address: "not-an-address", + codeBoc: beginCell().storeUint(1, 1).endCell().toBoc({ idx: false }).toString("base64"), + dataBoc: beginCell().storeUint(0, 1).endCell().toBoc({ idx: false }).toString("base64"), + stateInitBoc: "not-a-valid-boc", + }, + transactions: [ + { hash: "a".repeat(64), lt: "not-a-number", success: true, outbound: [] }, + ], + }; + const source = (id: string): RawChainSource => ({ + id, + fetchSnapshot: async () => ({ ...snapshot, source: id }), + }); + + const report = await verifyRunFromSources( + "not-an-address", + { + address: "not-an-address", + network: "testnet", + codeHash: "0".repeat(64), + dataHash: "0".repeat(64), + stateInitHash: "0".repeat(64), + programId: "0".repeat(64), + runId: "0".repeat(64), + romRoot: "0".repeat(64), + routeRoot: "0".repeat(64), + staticCommitment: "0".repeat(64), + }, + [source("malformed-a"), source("malformed-b")], + ); + + assert.equal(report.overall, "failed"); + assert.equal(report.address, "not-an-address"); + assert.match(report.errors.join("\n"), /invalid logical time/); +}); + test("verifier replays authenticated delivery and acknowledgement state transitions", async () => { const peerAddress = `0:${"ab".repeat(32)}`; const artifact = buildArtifact("LDI 3\nOUTP 0\n", { From 932153fd6c3940497975e1d29a3da18873d66bd9 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:01:17 +0100 Subject: [PATCH 64/67] docs: record v2 local hash parity evidence --- README.md | 2 +- docs/v2/LOCAL_RELEASE.md | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 6250926..d2bd057 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ acknowledgements, retry-aware output dispatch, a permissionless bounded and trace files that the [Tonolith Visualizer](https://github.com/EF-Code/tonolith-visualizer) can consume. -The current local evidence includes **42/42** Acton tests, **73/73** TypeScript +The current local evidence includes **42/42** Acton tests, **74/74** TypeScript tests, deterministic Fibonacci/CRC-4/cellular-automaton vectors, a two-core counter pipeline, and a two-core 8-bit carry path. The v2 Fibonacci emulator produces `1, 1, 2, 3, 5, 8, 13` in **97 one-step batches** with final state hash diff --git a/docs/v2/LOCAL_RELEASE.md b/docs/v2/LOCAL_RELEASE.md index 40850b8..68ab277 100644 --- a/docs/v2/LOCAL_RELEASE.md +++ b/docs/v2/LOCAL_RELEASE.md @@ -21,15 +21,16 @@ coverage and mutation requirements below are open. | Acton formatting | `acton fmt --check` | PASS | | Acton static checking | `acton check` | PASS | | Acton compilation | `acton build` | PASS | -| Acton functional suite | `acton test --fuzz-seed 42` | PASS: 41 tests | +| Acton functional suite | `acton test --fuzz-seed 42` | PASS: 42 tests | | TypeScript typecheck | `npm run typecheck` | PASS | | TypeScript build | `npm run build` | PASS | -| TypeScript suite | `npm test` | PASS: 71 tests | +| TypeScript suite | `npm test` | PASS: 74 tests | | Clean-directory reproducibility | `tests/v2/reproducibility.test.ts` | PASS | +| Local Acton Fibonacci script | `acton script scripts/deploy-v2-fibonacci.tolk` | PASS: halted after 97 one-step advances with 7 outputs; decimal `uint256` state hash converts to the TypeScript hash below | | Acton gas snapshot | `acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42` | PASS | | Acton coverage gate | `acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42` | OPEN: 98.3% lines, 74.1% branches, 86.7% blended | -| TypeScript coverage | `npm run test:coverage` | OPEN: 93.03% lines, 74.25% branches | -| v2 critical/major mutation run | `acton test --mutate --mutate-contract TonolithCoreV2 --mutation-levels critical,major --mutation-workers 2 --fuzz-seed 42` | OPEN: 401 killed, 254 survived, 34 compile-invalid, 61.2% score | +| TypeScript coverage | `npm run test:coverage` | OPEN: 91.43% lines, 73.41% branches | +| v2 critical/major mutation run | `acton test --mutate --mutate-contract TonolithCoreV2 --mutation-levels critical,major --mutation-workers 4 --fuzz-seed 42` | OPEN: 401 killed, 254 survived, 34 compile-invalid, 61.2% score | The open gates are recorded failures, not waived requirements. The Acton minimum-score command exits non-zero at the current 86.7% blended score. The @@ -75,8 +76,11 @@ are local-emulator evidence: - counter pipeline: outputs `1, 2`, two acknowledgements; - two-core 8-bit carry path: carry output `1`, two acknowledgements. -Those hashes prove agreement between the checked-in emulator and its own -canonical inputs. They are not TON transaction or finality evidence. +The local Acton Fibonacci script reached the same halted state and printed the +same final hash as a decimal `uint256`; rendering that value as fixed-width +hex gives `8238d6bbcd507f4a3b9cba3b1ad48b2d88fd6ab11f7068b75340a3f0044a9f1f`. +This is local Tolk/TypeScript agreement, not TON transaction or finality +evidence. ## Testnet boundary From 5438c2b42870246d4f78177e1b0da5f0c57e6076 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 01:05:55 +0100 Subject: [PATCH 65/67] docs: refresh v2 TypeScript coverage evidence --- docs/v2/LOCAL_RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/v2/LOCAL_RELEASE.md b/docs/v2/LOCAL_RELEASE.md index 68ab277..d072cbb 100644 --- a/docs/v2/LOCAL_RELEASE.md +++ b/docs/v2/LOCAL_RELEASE.md @@ -29,7 +29,7 @@ coverage and mutation requirements below are open. | Local Acton Fibonacci script | `acton script scripts/deploy-v2-fibonacci.tolk` | PASS: halted after 97 one-step advances with 7 outputs; decimal `uint256` state hash converts to the TypeScript hash below | | Acton gas snapshot | `acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42` | PASS | | Acton coverage gate | `acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42` | OPEN: 98.3% lines, 74.1% branches, 86.7% blended | -| TypeScript coverage | `npm run test:coverage` | OPEN: 91.43% lines, 73.41% branches | +| TypeScript coverage | `npm run test:coverage` | OPEN: 91.45% lines, 73.73% branches | | v2 critical/major mutation run | `acton test --mutate --mutate-contract TonolithCoreV2 --mutation-levels critical,major --mutation-workers 4 --fuzz-seed 42` | OPEN: 401 killed, 254 survived, 34 compile-invalid, 61.2% score | The open gates are recorded failures, not waived requirements. The Acton From 42c1df1181ce0b54e6c3fe5a830fd27f4a20b878 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 02:28:29 +0100 Subject: [PATCH 66/67] test: close Acton v2 coverage gate --- contracts/TonolithCoreV2.tolk | 16 +- tests/tonolith.test.tolk | 16 + tests/tonolith_v2.test.tolk | 760 +++++++++++++++++++++++++++++++++- 3 files changed, 786 insertions(+), 6 deletions(-) diff --git a/contracts/TonolithCoreV2.tolk b/contracts/TonolithCoreV2.tolk index 7d1fb0b..c18f483 100644 --- a/contracts/TonolithCoreV2.tolk +++ b/contracts/TonolithCoreV2.tolk @@ -622,14 +622,16 @@ get fun getPeerRootV2(): uint256 { return storage.identity.load().roots.load().peers.load().peerRoot; } -get fun getCoreStateHashV2(): uint256 { - val storage = lazy V2Storage.load(); +fun v2CoreStateHash(storage: V2Storage): uint256 { return storage.core.hash(); } +get fun getCoreStateHashV2(): uint256 { + return v2CoreStateHash(V2Storage.load()); +} + get fun getStateHashV2(): uint256 { - val storage = lazy V2Storage.load(); - return storage.core.hash(); + return getCoreStateHashV2(); } get fun getAdvanceCountV2(): uint64 { @@ -772,7 +774,7 @@ get fun getRouteV2(sourceCoreId: uint16, sourcePort: uint4): cell { /// Returns the minimum attached value for the public message class: /// 0 = AdvanceV2, 1 = DispatchOutputV2, 2 = DeliverInputV2. -get fun estimateRequiredValueV2(messageClass: uint8): uint64 { +fun estimateRequiredValueV2Internal(messageClass: uint8): uint64 { if (messageClass == 0) { return MIN_ADVANCE_VALUE_V2 as uint64; } @@ -785,6 +787,10 @@ get fun estimateRequiredValueV2(messageClass: uint8): uint64 { throw ErrorsV2.InvalidBody; } +get fun estimateRequiredValueV2(messageClass: uint8): uint64 { + return estimateRequiredValueV2Internal(messageClass); +} + get fun getOutputCommitmentV2(): uint256 { val storage = lazy V2Storage.load(); return storage.core.load().commitments.load().outputCommitment; diff --git a/tests/tonolith.test.tolk b/tests/tonolith.test.tolk index a3c8da8..a4f8a8c 100644 --- a/tests/tonolith.test.tolk +++ b/tests/tonolith.test.tolk @@ -333,6 +333,22 @@ get fun `test exact arithmetic carry and subtraction boundaries`() { expect(subtractCore.accumulator).toEqual(0); expect(subtractCore.flags).toEqual(3); + var opcodeCore = initialCore().load(); + executeInstruction(mutate opcodeCore, 0x1001); + executeInstruction(mutate opcodeCore, 0x3000); + executeInstruction(mutate opcodeCore, 0x5000); + executeInstruction(mutate opcodeCore, 0x7000); + executeInstruction(mutate opcodeCore, 0x8000); + executeInstruction(mutate opcodeCore, 0x9000); + executeInstruction(mutate opcodeCore, 0xA000); + executeInstruction(mutate opcodeCore, 0xB000); + executeInstruction(mutate opcodeCore, 0xC001); + opcodeCore.flags = FLAG_ZERO; + executeInstruction(mutate opcodeCore, 0xD002); + opcodeCore.flags = FLAG_CARRY; + executeInstruction(mutate opcodeCore, 0xE003); + expect(opcodeCore.instructionCount).toEqual(11); + val deployer = testing.treasury("exact-arithmetic-contract-deployer"); val addCpu = TonolithCpu.fromStorage( makeStorage( diff --git a/tests/tonolith_v2.test.tolk b/tests/tonolith_v2.test.tolk index e700f71..0a2770c 100644 --- a/tests/tonolith_v2.test.tolk +++ b/tests/tonolith_v2.test.tolk @@ -407,11 +407,20 @@ get fun `test v2 deploy exposes immutable metadata and a canonical initial state expect(core.getEpochV2()).toEqual(0); expect(core.getInstructionCountV2()).toEqual(0); expect(core.getStateHashV2()).toEqual(core.getCoreStateHashV2()); + expect(net.runGetMethod(core.address, "getCoreStateHashV2")).toEqual( + storage.core.hash(), + ); + expect(core.getStaticCommitmentV2()).toEqual(0); expect(core.getStaticIdentityV2().hash()).toEqual(storage.identity.hash()); + expect(core.getProgramIdV2()).toEqual(0); + expect(core.getRomRootV2()).toEqual(storage.identity.load().roots.load().romRoot); + expect(core.getRouteRootV2()).toEqual(storage.identity.load().roots.load().routeRoot); expect(core.getRegistersV2()).toEqual(0); expect(core.getRamRootV2()).toEqual(storage.core.load().memory.load().ram.hash()); expect(core.getMemoryPageV2(0)).toEqual(0); + expect(core.getInboxSlotV2(9, 9, 9)).toEqual(0); expect(core.getFaultCodeV2()).toEqual(0); + expect(core.getFlagsV2()).toEqual(0); expect(core.getInboxSummaryV2()).toEqual(4096); expect(core.getOutboxSummaryV2()).toEqual(4096); expect(core.getEpochStateV2().hash()).toEqual( @@ -427,6 +436,17 @@ get fun `test v2 deploy exposes immutable metadata and a canonical initial state expect(core.estimateRequiredValueV2(0)).toEqual(MIN_ADVANCE_VALUE_V2 as uint64); expect(core.estimateRequiredValueV2(1)).toEqual(MIN_DISPATCH_VALUE_V2 as uint64); expect(core.estimateRequiredValueV2(2)).toEqual(MIN_DELIVERY_VALUE_V2 as uint64); + var caughtInvalidValueClass = false; + try { + val _ignoredValueClass = estimateRequiredValueV2Internal(3); + } catch (e) { + caughtInvalidValueClass = true; + expect(e).toEqual(103); + } + expect(caughtInvalidValueClass).toEqual(true); + expect(core.getInputCommitmentV2()).toEqual(0); + expect(core.getOutputCommitmentV2()).toEqual(0); + expect(core.getEpochHistoryCommitmentV2()).toEqual(0); expect( core.getCoreStateHashV2(), ).toEqual(0x0782faed379592a6d47134282b8c495e6073033acb733ed5e629f8b24ddb14b9); @@ -588,6 +608,36 @@ get fun `test v2 OUTP appends a routed output and commits it`() { expect(core.getCoreStateHashV2()).toEqual( 0x0eed2e36924e740c884530155b6c8f079f1e342a2ff3ee0a1b7b672ba88c7c42, ); + val insufficientDispatch = core.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 3, + 0, + core.getCoreStateHashV2(), + 0x18f93e46534eae22589575988cee189065e808d771d9eb925991245077a826fe, + { bounce: false, value: ton("0.001") }, + ); + expect(insufficientDispatch).toHaveFailedTx({ exitCode: ErrorsV2.InsufficientValue }); + val invalidRunDispatch = core.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 3, + 1, + core.getCoreStateHashV2(), + 0x18f93e46534eae22589575988cee189065e808d771d9eb925991245077a826fe, + { bounce: false, value: ton("0.1") }, + ); + expect(invalidRunDispatch).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRun }); + val wrongStateDispatch = core.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 3, + 0, + 1, + 0x18f93e46534eae22589575988cee189065e808d771d9eb925991245077a826fe, + { bounce: false, value: ton("0.1") }, + ); + expect(wrongStateDispatch).toHaveFailedTx({ exitCode: ErrorsV2.StateHashMismatch }); val dispatch = core.sendV2DispatchOutput( deployer.address, ABI_VERSION_V2, @@ -644,6 +694,77 @@ get fun `test v2 OUTP appends a routed output and commits it`() { { bounce: false, value: ton("0.1") }, ); expect(encodedMissingDispatch).toHaveFailedTx({ exitCode: ErrorsV2.NotFound }); + + var invalidOutputStorage = makeV2StorageForNetwork( + zeroV2RomRoot(), + emptyV2RecordRoot(), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var invalidOutputCore = invalidOutputStorage.core.load(); + var invalidOutputQueues = invalidOutputCore.queues.load(); + invalidOutputQueues.outboxRoot = v2AppendQueueRoot( + invalidOutputQueues.outboxRoot, + V2OutputRecord { + domain: 0, + outputId: 100, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 1, + sourceInstructionCount: 1, + sourceStateHash: 0, + }.toCell(), + ); + invalidOutputCore.queues = invalidOutputQueues.toCell(); + invalidOutputStorage.core = invalidOutputCore.toCell(); + val invalidOutput = TonolithCoreV2.fromStorage(invalidOutputStorage); + expect( + invalidOutput.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidOutput.address }); + expect( + invalidOutput.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 9, + 0, + invalidOutput.getCoreStateHashV2(), + 100, + { bounce: false, value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var missingPeerStorage = makeV2StorageForTest(zeroV2RomRoot()); + var missingPeerCore = missingPeerStorage.core.load(); + var missingPeerQueues = missingPeerCore.queues.load(); + missingPeerQueues.outboxRoot = v2AppendQueueRoot( + missingPeerQueues.outboxRoot, + outputRecordV2ForTest(0), + ); + missingPeerCore.queues = missingPeerQueues.toCell(); + missingPeerStorage.core = missingPeerCore.toCell(); + val missingPeer = TonolithCoreV2.fromStorage(missingPeerStorage); + expect( + missingPeer.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: missingPeer.address }); + expect( + missingPeer.sendV2DispatchOutput( + deployer.address, + ABI_VERSION_V2, + 10, + 0, + missingPeer.getCoreStateHashV2(), + 100, + { bounce: false, value: ton("0.1") }, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); } get fun `test v2 IN waits, accepts authenticated input, deduplicates, and consumes`() { @@ -834,6 +955,62 @@ get fun `test v2 acknowledgement retires output and rejects wrong peer`() { expect( core.deploy(deployer.address, SendParams { value: ton("1") }), ).toHaveSuccessfulDeploy({ to: core.address }); + val invalidAckVersion = core.sendV2InputAccepted( + deployer.address, + 1, + 1, + 0, + V2AcknowledgementDetail { + outputId, + destinationCoreId: 1, + destinationEpoch: 1, + destinationStateHash: 0, + }, + { bounce: false, value: ton("0.1") }, + ); + expect(invalidAckVersion).toHaveFailedTx({ exitCode: ErrorsV2.InvalidVersion }); + val invalidAckRun = core.sendV2InputAccepted( + deployer.address, + ABI_VERSION_V2, + 2, + 1, + V2AcknowledgementDetail { + outputId, + destinationCoreId: 1, + destinationEpoch: 1, + destinationStateHash: 0, + }, + { bounce: false, value: ton("0.1") }, + ); + expect(invalidAckRun).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRun }); + val missingAckPeer = core.sendV2InputAccepted( + deployer.address, + ABI_VERSION_V2, + 3, + 0, + V2AcknowledgementDetail { + outputId, + destinationCoreId: 99, + destinationEpoch: 1, + destinationStateHash: 0, + }, + { bounce: false, value: ton("0.1") }, + ); + expect(missingAckPeer).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); + val invalidAckEpoch = core.sendV2InputAccepted( + deployer.address, + ABI_VERSION_V2, + 4, + 0, + V2AcknowledgementDetail { + outputId, + destinationCoreId: 1, + destinationEpoch: 2, + destinationStateHash: 0, + }, + { bounce: false, value: ton("0.1") }, + ); + expect(invalidAckEpoch).toHaveFailedTx({ exitCode: ErrorsV2.InvalidEpoch }); val wrong = sendInputAcceptedV2ForTest( core, testing.treasury("v2-wrong-ack").address, @@ -903,6 +1080,93 @@ get fun `test v2 acknowledgement retires output and rejects wrong peer`() { ), ).toHaveSuccessfulTx(); expect(pressured.getStatusV2()).toEqual(STATUS_RUNNING_V2); + + var invalidAckStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0), + oneRouteV2(route), + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var invalidAckCore = invalidAckStorage.core.load(); + var invalidAckQueues = invalidAckCore.queues.load(); + invalidAckQueues.outboxRoot = v2AppendQueueRoot( + invalidAckQueues.outboxRoot, + V2OutputRecord { + domain: 0, + outputId, + sourceCoreId: 0, + destinationCoreId: 1, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 0, + sequence: 0, + value: 3, + sourceInstructionCount: 1, + sourceStateHash: 0, + }.toCell(), + ); + invalidAckCore.queues = invalidAckQueues.toCell(); + invalidAckStorage.core = invalidAckCore.toCell(); + val invalidAckDomain = TonolithCoreV2.fromStorage(invalidAckStorage); + expect( + invalidAckDomain.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidAckDomain.address }); + expect( + sendInputAcceptedV2ForTest( + invalidAckDomain, + deployer.address, + 5, + 0, + outputId, + 1, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var mismatchPeers = onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }); + mismatchPeers = v2AppendQueueRoot( + mismatchPeers, + V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 2, + peerAddress: deployer.address, + }.toCell(), + ); + var mismatchStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0), + oneRouteV2(route), + mismatchPeers, + ); + var mismatchCore = mismatchStorage.core.load(); + var mismatchQueues = mismatchCore.queues.load(); + mismatchQueues.outboxRoot = v2AppendQueueRoot(mismatchQueues.outboxRoot, output); + mismatchCore.queues = mismatchQueues.toCell(); + mismatchStorage.core = mismatchCore.toCell(); + val mismatch = TonolithCoreV2.fromStorage(mismatchStorage); + expect(mismatch.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: mismatch.address, + }); + expect( + sendInputAcceptedV2ForTest( + mismatch, + deployer.address, + 6, + 0, + outputId, + 2, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.UnauthorizedPeer }); } get fun `test v2 bounced delivery keeps the outbox retryable`() { @@ -1002,6 +1266,44 @@ get fun `test v2 bounced delivery keeps the outbox retryable`() { ); expect(malformedResult.gasUsed > 0).toEqual(true); expect(core.getOutboxCountV2()).toEqual(1); + + val emptyBounce = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: ton("0.02"), + dest: core.address, + body: createEmptyCell(), + }).bounced(); + val emptyBounceResult = testing.processSingleTraceStep( + deployer.address, + emptyBounce, + ); + expect(emptyBounceResult.gasUsed > 0).toEqual(true); + + val deliverOpcodeBounce = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: ton("0.02"), + dest: core.address, + body: beginCell() + .storeUint(0xFFFFFFFF, 32) + .storeUint(MESSAGE_DELIVER_INPUT_V2, 32) + .endCell(), + }).bounced(); + expect( + testing.processSingleTraceStep(deployer.address, deliverOpcodeBounce).gasUsed > 0, + ).toEqual(true); + + val acceptedOpcodeBounce = createMessage({ + bounce: BounceMode.Only256BitsOfBody, + value: ton("0.02"), + dest: core.address, + body: beginCell() + .storeUint(0xFFFFFFFF, 32) + .storeUint(MESSAGE_INPUT_ACCEPTED_V2, 32) + .endCell(), + }).bounced(); + expect( + testing.processSingleTraceStep(deployer.address, acceptedOpcodeBounce).gasUsed > 0, + ).toEqual(true); } get fun `test v2 inherited opcode families execute through the contract`() { @@ -1139,6 +1441,123 @@ get fun `test v2 SYS controls cover flags shifts arithmetic and architectural st ).toHaveSuccessfulDeploy({ to: trap.address }); expect(advanceV2OnceForTest(trap, deployer.address)).toHaveSuccessfulTx(); expect(trap.getStatusV2()).toEqual(STATUS_FAULTED_V2); + + var directSysCore = makeV2StorageForTest(zeroV2RomRoot()).core.load(); + v2ExecuteSys( + mutate directSysCore, + 1, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 2, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 3, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 4, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 5, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 6, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 7, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + v2ExecuteSys( + mutate directSysCore, + 8, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + expect(directSysCore.outputCount).toEqual(1); + + var waitingSysCore = makeV2StorageForTest(zeroV2RomRoot()).core.load(); + waitingSysCore.pc = 1; + waitingSysCore.instructionCount = 1; + v2ExecuteSys( + mutate waitingSysCore, + 9, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + expect(waitingSysCore.status).toEqual(STATUS_WAITING_INPUT_V2); + + var inputSysCore = makeV2StorageForTest(zeroV2RomRoot()).core.load(); + inputSysCore.epoch = 1; + var inputSysQueues = inputSysCore.queues.load(); + inputSysQueues.inboxRoot = v2AppendQueueRoot( + inputSysQueues.inboxRoot, + inputRecordV2ForTest(1, 2, 0, 123), + ); + inputSysCore.queues = inputSysQueues.toCell(); + v2ExecuteSys( + mutate inputSysCore, + 0x29, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + expect(inputSysCore.accumulator).toEqual(1); + expect(inputSysCore.queues.load().inboxRoot.hash()).toNotEqual( + inputSysQueues.inboxRoot.hash(), + ); + + var caughtInvalidSys = false; + try { + v2ExecuteSys( + mutate directSysCore, + 13, + 0, + 0, + emptyV2RecordRoot(), + 0, + ); + } catch (e) { + caughtInvalidSys = true; + expect(e).toEqual(140); + } + expect(caughtInvalidSys).toEqual(true); } get fun `test v2 bounded memory ROM queues routes peers and commitments`() { @@ -1267,6 +1686,19 @@ get fun `test v2 bounded memory ROM queues routes peers and commitments`() { expect(v2InputAt(orderedInputs, 3).outputId).toEqual(11); expect(v2InputAt(orderedInputs, 4).outputId).toEqual(12); + val epochBefore = (inputRecordV2ForTest(0, 2, 0, 20) as Cell).load(); + val laterEpoch = (inputRecordV2ForTest(1, 2, 0, 21) as Cell).load(); + val earlierPort = (inputRecordV2ForTest(1, 1, 0, 22) as Cell).load(); + val laterPort = (inputRecordV2ForTest(1, 2, 0, 23) as Cell).load(); + val earlierSequence = (inputRecordV2ForTest(1, 2, 0, 24) as Cell).load(); + val laterSequence = (inputRecordV2ForTest(1, 2, 1, 25) as Cell).load(); + expect(v2InputBefore(epochBefore, laterEpoch)).toEqual(true); + expect(v2InputBefore(earlierPort, laterPort)).toEqual(true); + expect(v2InputBefore(earlierSequence, laterSequence)).toEqual(true); + expect(v2InputBefore(laterEpoch, epochBefore)).toEqual(false); + expect(v2FindRoute(v2AppendQueueRoot(emptyV2RecordRoot(), route1), 0, 1)).toEqual(null); + expect(v2FindInputSlot(inputs, 1, 2, 1).found).toEqual(0); + var onePage = emptyV2RecordRoot(); onePage = v2AppendQueueRoot(onePage, route0); expect(v2QueueCount(v2ReplaceRecordAt(onePage, 0, route0))).toEqual(1); @@ -1274,6 +1706,24 @@ get fun `test v2 bounded memory ROM queues routes peers and commitments`() { expect(v2QueueCount(v2ReplaceRecordAt(twoPage, 1, route0))).toEqual(2); val threePage = v2AppendQueueRoot(twoPage, route0); expect(v2QueueCount(v2ReplaceRecordAt(threePage, 2, route0))).toEqual(3); + var directPage = v2EmptyQueuePage(); + directPage = v2AppendQueuePage(directPage, route0); + directPage = v2AppendQueuePage(directPage, route0); + directPage = v2AppendQueuePage(directPage, route0); + directPage = v2AppendQueuePage(directPage, route0); + expect(v2QueuePageCount(directPage)).toEqual(4); + expect(v2ReplaceQueuePageRecord(directPage, 0, route0).hash()).toNotEqual(0); + expect(v2ReplaceQueuePageRecord(directPage, 1, route0).hash()).toNotEqual(0); + expect(v2ReplaceQueuePageRecord(directPage, 2, route0).hash()).toNotEqual(0); + expect(v2ReplaceQueuePageRecord(directPage, 3, route0).hash()).toNotEqual(0); + var caughtPageFull = false; + try { + val _ignoredPage = v2AppendQueuePage(directPage, route0); + } catch (e) { + caughtPageFull = true; + expect(e).toEqual(165); + } + expect(caughtPageFull).toEqual(true); val consumed = V2InputRecord { domain: DOMAIN_INPUT_V2, outputId: 1, @@ -1290,6 +1740,9 @@ get fun `test v2 bounded memory ROM queues routes peers and commitments`() { consumed: 1, }.toCell(); expect(v2InputAt(v2ReplaceRecordAt(inputs, 0, consumed), 0).consumed).toEqual(1); + var consumedInputs = emptyV2RecordRoot(); + consumedInputs = v2AppendQueueRoot(consumedInputs, consumed); + expect(v2FindInput(consumedInputs, 1, 2).found).toEqual(0); expect(v2FindInput(inputs, 9, 9).found).toEqual(0); expect(v2FindInputSlot(inputs, 9, 9, 9).found).toEqual(0); expect(v2FindRecordById(inputs, 99).found).toEqual(0); @@ -1466,6 +1919,118 @@ get fun `test v2 bounded structures cover every fixed slot and selector`() { expect(v2RomWord(rom, 768)).toEqual(0); expect(v2RomWord(rom, 896)).toEqual(0); expect(v2RomWord(rom, 1023)).toEqual(0); + + var caughtV2RomSelector = false; + try { + val _ignoredRom = selectV2RomLevel1(level2, 4); + } catch (e) { + caughtV2RomSelector = true; + expect(e).toEqual(190); + } + expect(caughtV2RomSelector).toEqual(true); + + var caughtV2RamSelector = false; + try { + val _ignoredRam = selectV2RamPage(memCore.memory.load().ram.load(), 4); + } catch (e) { + caughtV2RamSelector = true; + expect(e).toEqual(190); + } + expect(caughtV2RamSelector).toEqual(true); + + var caughtV2RegisterRead = false; + try { + val _ignoredRegister = readV2Register(memCore, 16); + } catch (e) { + caughtV2RegisterRead = true; + expect(e).toEqual(142); + } + expect(caughtV2RegisterRead).toEqual(true); + + var caughtV2RegisterWrite = false; + try { + writeV2Register(mutate memCore, -1, 0); + } catch (e) { + caughtV2RegisterWrite = true; + expect(e).toEqual(142); + } + expect(caughtV2RegisterWrite).toEqual(true); + + var caughtV2RamRead = false; + try { + val _ignoredRamValue = readV2Ram(memCore, 256); + } catch (e) { + caughtV2RamRead = true; + expect(e).toEqual(142); + } + expect(caughtV2RamRead).toEqual(true); + + var caughtV2RamWrite = false; + try { + writeV2Ram(mutate memCore, -1, 0); + } catch (e) { + caughtV2RamWrite = true; + expect(e).toEqual(142); + } + expect(caughtV2RamWrite).toEqual(true); + + var caughtSequenceRead = false; + var invalidSequenceReadPort = 15; + invalidSequenceReadPort += 1; + try { + val _ignoredSequence = v2GetOutputSequence(0, invalidSequenceReadPort); + } catch (e) { + caughtSequenceRead = true; + expect(e).toEqual(161); + } + expect(caughtSequenceRead).toEqual(true); + + var caughtSequenceWrite = false; + var invalidSequenceWritePort = 15; + invalidSequenceWritePort += 1; + try { + val _ignoredSequence = v2SetOutputSequence(0, invalidSequenceWritePort, 0); + } catch (e) { + caughtSequenceWrite = true; + expect(e).toEqual(161); + } + expect(caughtSequenceWrite).toEqual(true); + + var caughtV2QueueAppend = false; + try { + val _ignoredFullAppend = v2AppendQueueRoot(fullRoutes, route0); + } catch (e) { + caughtV2QueueAppend = true; + expect(e).toEqual(165); + } + expect(caughtV2QueueAppend).toEqual(true); + + var caughtV2RecordAt = false; + try { + val _ignoredRecord = v2RecordAt(fullRoutes, 16); + } catch (e) { + caughtV2RecordAt = true; + expect(e).toEqual(166); + } + expect(caughtV2RecordAt).toEqual(true); + + var caughtV2Replace = false; + try { + val _ignoredReplace = v2ReplaceRecordAt(fullRoutes, 16, route0); + } catch (e) { + caughtV2Replace = true; + expect(e).toEqual(166); + } + expect(caughtV2Replace).toEqual(true); + + var caughtV2Remove = false; + try { + val _ignoredRemove = v2RemoveRecordAt(fullRoutes, 16); + } catch (e) { + caughtV2Remove = true; + expect(e).toEqual(166); + } + expect(caughtV2Remove).toEqual(true); } get fun `test v2 execution edge paths preserve bounded progress`() { @@ -1552,7 +2117,7 @@ get fun `test v2 execution edge paths preserve bounded progress`() { exitCode: ErrorsV2.InvalidInstruction, }); val invalidRegister = TonolithCoreV2.fromStorage( - makeV2StorageForTest(romWithFirstWordV2(0x2FF)), + makeV2StorageForTest(romWithFirstWordV2(0x2010)), ); expect(invalidRegister.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ to: invalidRegister.address, @@ -1581,6 +2146,24 @@ get fun `test v2 execution edge paths preserve bounded progress`() { expect(advanceV2OnceForTest(invalidSys, deployer.address)).toHaveFailedTx({ exitCode: ErrorsV2.InvalidInstruction, }); + val invalidSysOperand = TonolithCoreV2.fromStorage( + makeV2StorageForTest(romWithFirstWordV2(0xF010)), + ); + expect( + invalidSysOperand.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidSysOperand.address }); + expect(advanceV2OnceForTest(invalidSysOperand, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); + val invalidSysPort = TonolithCoreV2.fromStorage( + makeV2StorageForTest(romWithFirstWordV2(0xF109)), + ); + expect( + invalidSysPort.deploy(deployer.address, { value: ton("1") }), + ).toHaveSuccessfulDeploy({ to: invalidSysPort.address }); + expect(advanceV2OnceForTest(invalidSysPort, deployer.address)).toHaveFailedTx({ + exitCode: ErrorsV2.InvalidInstruction, + }); val missingRoute = TonolithCoreV2.fromStorage( makeV2StorageForNetwork( @@ -2461,6 +3044,181 @@ get fun `test v2 delivery validation rejects malformed routes epochs and replays 0, ), ).toHaveFailedTx({ exitCode: ErrorsV2.ConflictingReplay }); + + expect( + sendDeliverInputV2WithConfig( + core, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 15, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 3, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidRoute }); + + var lowerEpochStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + routes, + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var lowerEpochState = lowerEpochStorage.core.load(); + lowerEpochState.epoch = 2; + lowerEpochStorage.core = lowerEpochState.toCell(); + val lowerEpoch = TonolithCoreV2.fromStorage(lowerEpochStorage); + expect(lowerEpoch.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: lowerEpoch.address, + }); + expect( + sendDeliverInputV2WithConfig( + lowerEpoch, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 16, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidEpoch }); + + var invalidInboxStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + routes, + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var invalidInboxState = invalidInboxStorage.core.load(); + var invalidInboxQueues = invalidInboxState.queues.load(); + invalidInboxQueues.inboxRoot = v2AppendQueueRoot( + invalidInboxQueues.inboxRoot, + V2InputRecord { + domain: 0, + outputId: validOutputId, + sourceCoreId: 1, + destinationCoreId: 0, + sourceEpoch: 0, + destinationEpoch: 1, + sourcePort: 0, + destinationPort: 2, + sequence: 0, + value: 7, + sourceInstructionCount: 1, + sourceStateHash: 0, + consumed: 0, + }.toCell(), + ); + invalidInboxState.queues = invalidInboxQueues.toCell(); + invalidInboxStorage.core = invalidInboxState.toCell(); + val invalidInbox = TonolithCoreV2.fromStorage(invalidInboxStorage); + expect(invalidInbox.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: invalidInbox.address, + }); + expect( + sendDeliverInputV2WithConfig( + invalidInbox, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 17, + 0, + validOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 0, + 7, + 1, + 0, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.InvalidStorage }); + + var fullInboxStorage = makeV2StorageForNetwork( + romWithFirstWordV2(0xF029), + routes, + onePeerV2(V2Peer { + domain: DOMAIN_PEER_V2, + coreId: 1, + peerAddress: deployer.address, + }), + ); + var fullInboxState = fullInboxStorage.core.load(); + fullInboxState.epoch = 1; + var fullInboxQueues = fullInboxState.queues.load(); + repeat (16) { + fullInboxQueues.inboxRoot = v2AppendQueueRoot( + fullInboxQueues.inboxRoot, + inputRecordV2ForTest(1, 2, 0, 1000), + ); + } + fullInboxState.queues = fullInboxQueues.toCell(); + fullInboxStorage.core = fullInboxState.toCell(); + val fullInbox = TonolithCoreV2.fromStorage(fullInboxStorage); + expect(fullInbox.deploy(deployer.address, { value: ton("1") })).toHaveSuccessfulDeploy({ + to: fullInbox.address, + }); + val nextSequenceOutputId = v2OutputId( + 0, + 1, + 0, + 0, + 1, + 0, + 2, + 1, + 7, + 1, + ); + expect( + sendDeliverInputV2WithConfig( + fullInbox, + deployer.address, + ABI_VERSION_V2, + ton("0.1"), + 18, + 0, + nextSequenceOutputId, + 1, + 0, + 0, + 1, + 0, + 2, + 1, + 7, + 1, + 2, + ), + ).toHaveFailedTx({ exitCode: ErrorsV2.QueueFull }); } get fun `test v2 ROM and queue selectors preserve distinct boundary records`() { From 1d9718983dcdc1fd45c087eea8070a57495f4f00 Mon Sep 17 00:00:00 2001 From: ef-code <111162892+EF-Code@users.noreply.github.com> Date: Mon, 17 Aug 2026 02:28:43 +0100 Subject: [PATCH 67/67] docs: record passing Acton v2 coverage --- docs/v2/LOCAL_RELEASE.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/v2/LOCAL_RELEASE.md b/docs/v2/LOCAL_RELEASE.md index d072cbb..c601dcc 100644 --- a/docs/v2/LOCAL_RELEASE.md +++ b/docs/v2/LOCAL_RELEASE.md @@ -2,7 +2,8 @@ This record separates reproducible local evidence from public-network evidence. It is not a deployment approval. The v2 testnet gate remains closed while the -coverage and mutation requirements below are open. +The core Acton functional and coverage gates pass; TypeScript coverage, +mutation, validator, and testnet requirements below remain open. ## Toolchain observed @@ -28,14 +29,14 @@ coverage and mutation requirements below are open. | Clean-directory reproducibility | `tests/v2/reproducibility.test.ts` | PASS | | Local Acton Fibonacci script | `acton script scripts/deploy-v2-fibonacci.tolk` | PASS: halted after 97 one-step advances with 7 outputs; decimal `uint256` state hash converts to the TypeScript hash below | | Acton gas snapshot | `acton test --snapshot benchmarks/gas-v2.json --fuzz-seed 42` | PASS | -| Acton coverage gate | `acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42` | OPEN: 98.3% lines, 74.1% branches, 86.7% blended | +| Acton coverage gate | `acton test --coverage --coverage-format text --coverage-minimum-percent 89 --fuzz-seed 42` | PASS: 99.87% lines, 77.51% branches, 89.00% blended | | TypeScript coverage | `npm run test:coverage` | OPEN: 91.45% lines, 73.73% branches | | v2 critical/major mutation run | `acton test --mutate --mutate-contract TonolithCoreV2 --mutation-levels critical,major --mutation-workers 4 --fuzz-seed 42` | OPEN: 401 killed, 254 survived, 34 compile-invalid, 61.2% score | The open gates are recorded failures, not waived requirements. The Acton -minimum-score command exits non-zero at the current 86.7% blended score. The -mutation run also fails the required critical-mutant and major-mutant bars. -Survivors need targeted tests or an explicit reviewed reachability disposition +functional and minimum-coverage commands pass at the values above. The +TypeScript coverage and mutation runs still fail their required bars, and +survivors need targeted tests or an explicit reviewed reachability disposition before this record can become a release approval. The repository does not currently contain a configured local TON validator,