This document describes how the Foundry dispute-game tests divide
responsibility and how new tests should be designed. It covers
prt/contracts; test-harness.md separately owns the Lua
and real-node end-to-end harness.
Exact campaign counts, hashes, mutation experiments, and release results are
historical evidence in the
TEST-REPORT.md archive.
Run from the repository root:
just prt-contracts::test-disputes
just test-prt-gas
just prt-contracts::coverage
just rollups-contracts::testThe state-transition suites require the machine/step submodule, FFI, and
the yield machine image (just programs::build-programs):
just prt-contracts::test-stf
just prt-contracts::test-stf-fuzzyjust prt-contracts::test-all combines the dispute and state-transition gates.
just test-prt-gas runs both the Tournament-only witnesses and the serialized
full-stack FFI leaf-proof matrix, and CI runs it in the prt-contracts job.
The witnesses are one-sided: a pass means
every allocation still covers its witnesses. Moving a constant has stricter
toolchain and clean-tree requirements; follow
prt-refund-gas-calibration.md. Ordinary Rollups contract tests and coverage
exclude the *FfiTest contracts deliberately.
| Location | Responsibility |
|---|---|
test/*.t.sol |
Focused library tests and public Tournament composition |
test/accounting/ |
Reserve algebra, exact refund formula, and callback behavior |
test/characterization/ |
Frozen historical three-level behavior |
test/config/ |
Canonical and generic parameter-table validation |
test/devnet/ |
The devnet-only deployment of the three-level test table CI and local tests run (the default DEVNET_GEOMETRY) and its test |
test/fixtures/ |
Injected geometry, small trees, independent models, and test-only transitions |
test/gas/ |
Retained production refund witnesses |
test/properties/ |
Match parity, lifecycle, recursion, population, and delay properties |
test/step/ and FFI suites |
Solidity-step and external state-transition oracles |
The full-stack leaf-proof gas fixture lives under
cartesi-rollups/contracts/test/gas/, where it can compose the production
InputBox, DaveConsensus provider, Cartesi state transition, and PRT Tournament.
Its height-one, position-one commitments are coordinate coherent and inject
only the geometry needed by the measured entry point.
Characterization is a historical behavior fossil, not the preferred semantic oracle. New behavior tests should use the smallest injected geometry that exposes the property under review.
Each layer has one job:
| Layer | What it establishes | What it does not establish |
|---|---|---|
| Deterministic regressions | Exact boundaries, selectors, rollback, precedence, and lifecycle traces | Exhaustive arrival schedules |
| Fuzz properties | Arithmetic partitions, symmetry, geometry, caps, and bounded input domains | Completeness outside the generator |
| Stateful invariants | Legal and rejected operation sequences against an independent ghost model | Recursive adversarial scheduling unless modeled explicitly |
| Finite exhaustive models | Every state in a stated small domain | An unbounded theorem |
| Characterization | Historical observable behavior | The desired semantics of new code |
| Compatibility and impact checks | Wire ABI and clone encoding; storage and bytecode drift | Correctness, or a promise that internal layout and deployment identity remain unchanged |
| Gas witnesses | Production refund-seam work for a declared execution envelope | Receipt gas or unbounded proof classes |
| Downstream integration | Rollups factory and staged-consensus composition | Real validator or Lua conformance |
| Coverage mapping | Production locations that deserve investigation | Semantic assurance or branch completeness |
Do not use a higher test count as a substitute for independent oracles and failure-sensitive assertions.
The tests mirror the production abstraction:
Clockowns one-clock representation, arithmetic, and storage transitions.MatchClocksowns policy involving two clocks.Matchowns commitment existence and structural phase.Tournamentproves that those local responsibilities compose correctly.
One-clock properties should cover elapsed time, remaining time, overdue time, strict deadlines, charging, replacement, and zero-sentinel rejection at an explicit observation instant.
Pair-clock tests should cover every supported local shape: uninitialized, two
paused, exactly one running, and two running. Each transition needs both
orientations and a rejection matrix. The shared timeout classifier must remain
exhaustive and disjoint, with equality assigned explicitly. Its independent
oracle must distinguish a paused winner's deferred overdue charge from a running
winner's zero deferred charge. Winner tests must show that survival is decided
on the full cost while the stored clock forgives at most one response budget
of it (min(cost, G)). Tournament tests own the strict verb partition:
a leaf proof is valid only under NONE, single-winner statuses select timeout
victory, and ELIMINATE_BOTH selects elimination. Response-budget tests must
prove that a successful response discounts elapsed time once without increasing
the prior balance; pairing must not grant time, and same-tournament survivor
re-entry must not raise the stored balance or forgive more than one response
budget. Recursive integration separately owns the shared pair envelope:
a child return may exceed the selected side's snapshotted remainder, but not
max(r1, r2) or the post-discount live pair mass, and it owns the refill
property: a commitment joining each child within T + G and propagating
within G returns with its pre-seal clock, delegation after delegation, from
either side of the pair envelope. Before every successful child return, the
recursive suites also assert that the carried allowance already lies within
the envelope: the return clamps to it, which would otherwise mask a child
that added time.
Tournament integration owns the structural distinctions that pair clocks alone cannot infer, including ready-to-bisect versus sealed-inner states when both clocks are paused.
The living clock evidence map is:
| Concern | Primary evidence |
|---|---|
| One-clock arithmetic, strict deadlines, and zero sentinel | test/Clock.t.sol |
| Legal pair shapes, timeout partition, equality, and orientation symmetry | test/MatchClocks.t.sol |
| Public proof, timeout, pairing, and rollback composition | test/Tournament.t.sol |
| Independent one-level topology and clock ghost state | test/properties/TournamentLifecycleInvariant.t.sol |
| Reachable lower bounds and exhaustive small one-level schedules | test/properties/LeafPopulationDelay.t.sol and test/properties/BoundedOneLevelDelay.t.sol |
| Shared child envelopes, propagation, concurrent children, and generic depth | test/properties/RecursiveTournamentLifecycle.t.sol, ConcurrentRecursivePopulation.t.sol, and FourLevelRecursiveLifecycle.t.sol |
| Ethereum and local-devnet block conversion for response and allowance budgets | test/Deployment.t.sol |
Within RecursiveTournamentLifecycle.t.sol,
testChildWinnerSelectsParentSideByFinalStateNotByRoot pins final-state
classification, while testChildReturnUsesSharedParentPairEnvelope pins the
asymmetric clock return. Together they establish the mechanics behind the
simplification; they do not identify an optimal attacker schedule or prove a
general recursive delay bound.
The one-level invariant establishes the exact parity-aware relation
runningClocks >= floor(K / 2) for leaf tournaments. It is not a recursive
clock theorem: sealed inner parents pause both clocks and delegate the
obligation to a linked child. No current model combines an identified correct
commitment, an eager correct strategy, an adversarial scheduler, and one shared
censorship ledger C; that general recursive result remains open.
Match tests deliberately triangulate four independent concerns:
- raw characterization records the historical tuple and sealed encoding while pinning the externally observable events and errors;
- an independent sparse-Merkle model owns divergence, parity, agree-proof ownership, and fixed-side winner attribution;
- focused validation tests own malformed children, proof failures, and phase guards; and
- Tournament integration owns clocks, deletion, re-pairing, recursion, and terminal outcomes.
The sparse-tree oracle must derive turn ownership independently. Copying the production parity table into the test would create correlated evidence rather than an oracle. Exercise both commitment orders, valid zero-valued payloads, leftmost divergence, every small-tree position, and the supported large-height boundaries.
Behavioral tests inject the geometry they require. Imports of production
ArbitrationConstants belong in canonical conformance tests, not in generic
Clock, Match, Tournament, or accounting properties.
The suite uses one-, two-, and four-level fixtures to exercise generic
structure. Historical Top/Middle/Bottom suites use a frozen test-owned provider
under test/characterization/. None of these fixtures proves that an off-chain
client uses a selected deployment table; that requires coordinated node and Lua
conformance.
Prefer evidence that does not replay production storage or branch tables:
- the Match oracle constructs its own sparse trees and tracks the revealer by alternation;
- the lifecycle ghost model tracks population, topology, coordinates, clocks, claimers, and counters independently;
- finite schedulers use their own packed state and enumerate a declared domain; and
- refund and reserve properties derive the algebra from joins, matches, caps, and balances instead of comparing two production getters.
Settlement stubs select a claimed leaf outcome. They are not oracles for the real machine transition. Coordinate-coherent fixtures prove that tournament spans and contested states line up, not that either computation is correct.
The state-transition suites separate cheap routing evidence from semantic
oracles. Solidity-only tests exercise input and ordinary-step dispatch across
the full 24-bit input, 48-bit mcycle, and 20-bit ucycle fields. Structured FFI
fuzzing adds canonical closing proofs and all four transition shapes, generated
with the v0.21 emulator and replayed through CartesiStateTransition.
The deterministic FFI matrix pins boundary-adjacent counters at the first and last uarch spans, input-window transitions, and the epoch tail. A tiny RV64 guest reaches zero and nonzero halt, TX exception, unexpected manual yield, and mcycle overflow. Opening vectors cover both halt values and every terminal class; representative closing vectors establish reset without rejected-input substitution. Separate vectors own rejected-input restoration and uarch-cycle overflow. Pending-yield vectors, logged from a bare emulator so client terminal classification cannot shape them, own a manual yield that outranks halt and the input budget: an RX_ACCEPTED opening on the budget's last cycle (also at the maximum mcycle) or on a preset halted machine takes the input, unlike the idle opening, and an RX_REJECTED closing on the budget's last cycle still restores the revert root. Combined witness mutations cover exact DA header and payload boundaries, the CMIO-step and step-reset seams, before-root and provider-root binding, one representative byte in each composed primitive, and replay across adjacent transition shapes.
Those vectors come from the Lua client and bare emulators. The Rust node's
own witness bytes are pinned on its side by node_witness_vectors_hold
(cartesi-rollups/node/tests/engine_machine.rs): window openings with and
without an input, a second input's opening, a maximum-size input's opening, a
plain ustep, a closing slot, and a rejected-input closing slot.
seam_witness_vectors_hold (engine/machine_stf.rs) adds the node's bytes at
the input budget's seams, where the v0.21 CLI departs from the step: accepted
openings on the budget's last cycle and on a halted machine, and a rejected
closing on the budget's last cycle. NodeWitnessesTest (cartesi-rollups/contracts)
replays each through CartesiStateTransition, with inputs rooted the way
DaveConsensus roots them, and requires the node's post-state. Likewise
node_proof_vectors_hold pins the node's commitment proofs (root joins under
both tables, and a leaf agree-state opening and a leaf join at each table's
leaf height) and an epoch's settlement validity proof; NodeProofsTest opens
the former with the tournament's Commitment library and validates the latter
as DaveConsensus stages it, requiring the reference CLI's outputs Merkle
root.
A nonempty DA payload paired with the provider's zero out-of-range root is
rejected (StateTransition.t.sol): a position with no input carries no input
bytes, which fixes the proof's length for the leaf refund.
One PRT-side composition test carries a canonical input-opening vector through
the real leaf Tournament and CartesiStateTransition with a standalone
IDataProvider. This pins the generic dispute seam without depending on
DaveConsensus or creating another expected-root implementation.
This is bounded cross-implementation evidence for the v0.21 adapter. It is not
an exhaustive enumeration of every RV64 instruction, access-log shape, proof
byte, or gas envelope; those lower-level instruction and log semantics remain
owned by machine/step and the emulator.
The ordinary Foundry fuzz budget is pinned in foundry.toml. A deeper campaign
must record its run count, depth where applicable, and seed. Do not describe a
fuzz campaign as exhaustive unless its generator enumerates the entire stated
domain.
Stateful handlers must account for reverts and discarded inputs. A green invariant with most operations rejected is weak evidence; retain counters or companion traces that prove the intended operations executed.
Coverage instrumentation changes gas and cannot run the retained gas witnesses or exact refund-formula measurements faithfully. FFI and state-transition tests have separate execution requirements, and the slow stateful executors are owned by the ordinary dispute gate. Deterministic companion traces map their principal production paths.
The coverage recipe uses IR-minimum mappings as a stack-depth workaround. Reported branch totals are directional and can disagree with demonstrably executing tests. Use uncovered lines and functions as investigation leads, not as a correctness score.
Within one deployment generation, the supported wire surface includes the Tournament function, event, and error ABI plus the immutable clone-argument encoding. An intentional wire-format break starts a new generation: deploy a fresh Tournament implementation, factory, and dependent Dave bundle; ship matching bindings, clients, and artifacts; and do not carry a live dispute or persisted event stream across the boundary.
Storage layout and raw Match and Clock encoding are internal in the current
non-upgradeable design. There is no supported state migration or raw-storage
client. Solidity tests reach raw Match, Clock, and topology state through the
white-box probe in test/fixtures/TournamentInspector.sol. Its slot
constants must follow intentional layout changes; they do not turn the
current layout into a compatibility promise. Clone-argument decoding and the
independently derived closure and finish predicates remain useful oracles
rather than echoes of the observer views.
Run:
just prt-contracts::compatibility-hashesThe report separates wire compatibility from implementation and deployment
impact. An ABI difference changes the supported wire surface. A storage-layout
difference means white-box probes must be reviewed. A bytecode difference
changes deployment identity and may change CREATE2-derived addresses. Inspect
every unexpected difference rather than updating a recorded value
mechanically, but do not require internal hashes to remain equal. Metadata-free
hashes also do not enforce the EIP-170 runtime-size ceiling; every
release-facing contract change must still pass the full forge build --sizes
gate.
Add a test when it:
- protects a production change;
- reproduces a concrete finding;
- validates a new supported geometry, proof, or fee envelope; or
- attacks an explicitly scoped open question.
Do not add fixed lifecycle traces or fuzz volume solely to improve headline counts. Current non-claims include a general recursive adversarial-arrival proof, node and Lua agreement with a future geometry, exhaustive state-transition instruction and access-log coverage, a universal leaf-proof gas ceiling, and semantic meaning for IR branch percentages.