Build1 publisher3 min readPublished
A pure settlement function turns a 1.01% payout bug into a 1,287-call sweep
Replay's top ticket pays exactly the host contract's cap, so one wei of disagreement between three call sites reverts the round. Four hundred simulated rounds never drew that ticket. Enumerating every rank took seconds.
The Engineer · Build desk

What happened
- Replay declares its profit reserve in quoteCaps and its ceiling in quoteRiskParams, then returns the owed payout at settlement, and one wei of independent re-derivation at any of those three sites reverts the transaction.
- The revert lands on the 96.03x top ticket, which wins on 13 of the 1,287 orderings of an 8-5 game, so the failure can only appear on 1.0101% of rounds.
- Four hundred rounds against the platform's local simulator, running its own chain and a real ECVRF node through the full contract lifecycle, never produced that outcome.
- A script calling the pure settlement function with hand-written randomness swept all 1,287 ranks and returned 13 wins and 1,274 losses, matching the closed form C(13,1) = 13.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- capability Once the randomness is addressable, the paying outcome is something you call on demand rather than wait for, and the win count becomes a number checkable against combinatorics.
- decision A game that resolves through storage writes forfeits this test path entirely and is left validating its most valuable outcomes by sampling, so purity of settlement is now a testability decision, not a style one.
- constraint The sweep's authority stops at the arithmetic the game returns; the facet's cap check sits outside the loop, so enforcement is compared against a ceiling the script holds itself.
- exposure Any player who hits the top ticket is the one who cannot be paid, so a defect of this shape surfaces to the loudest possible counterparty rather than in aggregate metrics.
The addressability is a side effect of a fairness fix. `_drawRank` runs rejection sampling over the sixteen 16-bit windows of the randomness word, because plain `byte % n` is biased, and a biased draw is not a rounding error when the draw is the odds [10]. The first line of that function sets `limit = (65536 / total) * total`, which for a 1,287-rank board is 64,350 [11]. Every rank is below 1,287 and therefore far below the limit, so a word whose leading two bytes are R is accepted on the first window and returns rank R [11]. The preimage of any outcome is two bytes you can type.
Purity does the rest. Settlement is declared `external pure`, randomness arrives as an argument, the result comes back as a return value, and storage is neither read nor written [9]. The sweep is therefore 1,287 read calls against the deployed contract, with no VRF in the loop and nothing to reset between iterations [9], which is why it finishes in seconds instead of 400 rounds [15].
The run output also lets you back out the stake. The facet cap prints as 96.03e18 and the quoted reserved profit as 95.03e18 [13], so under the platform's cap formula the escrowed stake is 1e18, the wager itself [1]. The top ticket consumes the escrow and the entire reserve, which is what leaves it with no room above [13].
Randomized play was not the cheap option here. The harness ran the platform's own chain with a real ECVRF node rather than a mock, through the full contract lifecycle [6]. At 1.0101% a round, 400 rounds carry 4.04 expected hits [2]; the run recorded zero, which the author's own arithmetic puts at roughly 1.7% [7]. Coverage is the harder number: 400 rounds can address at most 400 of 1,287 ranks, 31% of the board, and only if no rank repeats [3]. Had the tail landed and paid correctly, the result would have been one confirmed rank out of 1,287 [8].
The sweep asserts in both directions. A winning rank must return the quoted maxPayout, stay within the cap, and carry a maxDeficit of at least 4; a losing rank must carry a maxDeficit below 4 [12]. It also re-checks its own trick on every iteration with `if (pathId !== r) throw` [12], so a change to the window read fails the sweep loudly rather than quietly sweeping the same rank 1,287 times.
Three properties have to hold before enumeration replaces sampling in someone else's contract: settlement pure over caller-supplied entropy [9], an outcome index recoverable from entropy you can construct [11], and a space you can afford to walk at roughly 1,287 calls a board [15]. The first is the one worth designing for, because it is what makes the other two usable at all [9].
What to watch
- Whether the same sweep is run for other score lines and wagers; the published output covers only 8-5 at a 1e18 wager.
- Whether a live settlement at the 96.03x ticket is recorded, showing the facet accepts the payout the game returns.
- Whether other games on the platform expose a pure settlement entry point that can be enumerated the same way.