Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

v4 state

Back to all declarations

rakeBps

untyped bigint

Value

(300 <untyped> bigint)

bpsDenom

untyped bigint

Value

(10000 <untyped> bigint)

rakePotShareBps

untyped bigint

Value

(6000 <untyped> bigint)

rakeOpsShareBps

untyped bigint

Value

(4000 <untyped> bigint)

rung0

untyped bigint

// 10 GNOT

Value

(10000000 <untyped> bigint)

rung1

untyped bigint

// 50 GNOT

Value

(50000000 <untyped> bigint)

rung2

untyped bigint

// 200 GNOT

Value

(200000000 <untyped> bigint)

rung3

untyped bigint

// 1,000 GNOT

Value

(1000000000 <untyped> bigint)

maxStake

untyped bigint

maxStake is the largest stake the ladder can ever name — rung3 — used to bound poolOf's and rakeOf's inputs before any arithmetic touches them. Copied verbatim from r/splash/arcade/duel/duel.gno.

Value

(1000000000 <untyped> bigint)

opsVaultAddr

.uverse.address

Value

<gnolang.StringValue>

raffleRealmAddr

.uverse.address

Value

<gnolang.StringValue>

mulDiv

func(a uint64, b uint64, d uint64) uint64

mulDiv returns floor(a\*b/d) via a 128-bit intermediate product: raw a\*b/d on token-scale int64s wraps silently on the GnoVM (tools/muldiv\_probe.gno).

Open
OID
0db16f…1d1b:7
mulDiv details

Inspect func

poolOf

func(stake int64) int64

poolOf is 2 x stake, the whole pot of a duel, with the input bounded first.

Open
OID
0db16f…1d1b:8
poolOf details

Inspect func

rakeOf

func(pool int64) int64

rakeOf is the rake on a pool: floor(pool x rakeBps / bpsDenom).

Open
OID
0db16f…1d1b:9
rakeOf details

Inspect func

splitRake

func(rake int64) (potShare int64, opsShare int64)

splitRake divides a rake 60/40 into the raffle pot share and the ops share. Ops is floored; the pot takes the remainder and all the dust, so potShare + opsShare == rake exactly, always.

Open
OID
0db16f…1d1b:10
splitRake details

Inspect func

rakeEntryUnits

func(rake int64) int64

rakeEntryUnits is how many raffle entry units a rake of \`rake\` ugnot earns: exactly what that money would have bought at raffle.EntryUnitPrice. raffle.EntryUnitPrice is read as a compile-time constant of another realm, never a call, so this adds no runtime dependency on the raffle.

Open
OID
0db16f…1d1b:11
rakeEntryUnits details

Inspect func

splitEntries

func(total int64) (winnerUnits int64, loserUnits int64)

splitEntries divides a duel's earned entries between its winner and its loser: each player is guaranteed one entry whenever the total can pay for two (total >= 2), and the remainder is weighted two thirds to the loser.

Open
OID
0db16f…1d1b:12
splitEntries details

Inspect func

maxEntryUnitsFor

func(potShare int64) int64

maxEntryUnitsFor is the most booked entry units a booked pot share of \`potShare\` ugnot may ever carry — the same ceiling r/splash/arcade/raffle applies at its door (raffle.ArcadeEntryUnitsBound, raffle.gno:3286-3291), computed here from this realm's own constants so checkInvariant can assert it without calling another realm. Copied verbatim from r/splash/arcade/duel/duel.gno. A THIRD COPY, same situation as rakePotShareBps above — read THAT constant's comment for what happens if this one ever disagrees with the raffle's own (raffle.gno:3449-3455/FlushPot/SweepStuckRake) — and PINNED the same way: TestMaxEntryUnitsForMatchesRafflesArcadeEntryUnitsBound in duel\_test.gno asserts this function equals raffle.ArcadeEntryUnitsBound, to the entry, exactly as the old duel's TestFix15\_TheRafflesEntryBoundIsExactlyThisRealmsRakeEquivalent (duel\_test.gno:3338,3341,3742,3747) pins the old duel's identical copy.

Open
OID
0db16f…1d1b:13
maxEntryUnitsFor details

Inspect func

pinOffsetRounds

untyped bigint

Value

(100 <untyped> bigint)

bucketRounds

untyped bigint

Value

(100 <untyped> bigint)

minPinOffsetRounds

untyped bigint

Value

(100 <untyped> bigint)

pinRound

func() int64

pinRound computes the round Accept binds a duel to. It is the ONLY place this arithmetic exists (SPEC-09 §8: "one expression used twice, never two copies" — the old duel's R1/N1/T1 findings were all one deadline written as several expressions that drifted apart). raw = beacon.CurrentRound() + pinOffsetRounds Round = ((raw / bucketRounds) + 1) * bucketRounds current IS READ EXACTLY ONCE, into a local, and reused for both the derivation above and the assertion below — never a second call to beacon.CurrentRound() — so the invariant is checked against the very value the pin was derived from, not a possibly-different later read. ASSERTS SPEC-09 INVARIANT 2 IN THE CONTRACT, NOT JUST IN A TEST — and this time correctly: the panic below checks the MARGIN (round must be at least current + pinOffsetRounds), not merely the SIGN (round > current). The sign alone is NOT a useful guard: bucketRounds's own round-up already makes round > current by at least 1, for ANY pinOffsetRounds including 0 — so a sign-only check (this file's previous version) passes even when pinOffsetRounds has collapsed to 0 and the pin has collapsed with it to 1-100 rounds, well inside ordinary clock skew. The margin check here is STILL NOT what defends pinOffsetRounds from being set too low, though: raw embeds pinOffsetRounds directly, so round's own derivation already guarantees round >= current + pinOffsetRounds + 1 for WHATEVER pinOffsetRounds compiles in — the check below is therefore a defence against a future bug in the ROUNDING step (e.g. bucketRounds's arithmetic silently losing the "+1" that makes the round-up strict), not a defence against pinOffsetRounds itself being shrunk. THAT defence is the minPinOffsetRounds floor above, a BUILD-TIME failure — because pinOffsetRounds is a compile-time constant, and nothing checked at runtime against a value derived from that same constant can ever catch the constant itself being wrong.

Open
OID
0db16f…1d1b:14
pinRound details

Inspect func

pinnedRoundFor

func(current int64) int64

pinnedRoundFor is the pin arithmetic, and the ONLY copy of it. It was inlined in pinRound until RoundForAcceptAt needed the same answer for the front end. Two copies of this expression would be the same defect as a JavaScript copy, just closer together and harder to spot: a front end promising "you have 4 minutes to join this flip" against arithmetic that no longer matches what Accept does would be lying to a player about money. One function, both callers.

Open
OID
0db16f…1d1b:15
pinnedRoundFor details

Inspect func

maxLabelLen

untyped bigint

maxLabelLen bounds an offer's label, in bytes. Copied from r/splash/arcade/duel/duel.gno.

Value

(64 <untyped> bigint)

Offer

type

Offer is a standing stake in the lobby (SPEC-09 §4).

Value

v4.Offer

Duel

type

Duel is a matched pair with a round pinned to settle it (SPEC-09 §4).

Value

v4.Duel

nextOfferID

int64

Value

4

nextDuelID

int64

Value

4

escrowed

int64

Value

0

pendingPot

int64

Value

3600000

pendingEntryUnits

int64

Value

24

pendingSince

int64

Value

264215

maxPendingEntryHolders

untyped bigint

maxPendingEntryHolders bounds the undelivered entry batch, exactly as r/splash/arcade/duel/duel.gno's identical constant: an unbounded batch could grow past what a block can iterate. Copied verbatim.

Value

(128 <untyped> bigint)

blocksPerDay

untyped bigint

blocksPerDay and pendingGraceBlocks: SAME VALUES, SAME REASONING as r/splash/arcade/duel/duel.gno's identical constants. 21 days of blocks is the same window r/splash/arcade/raffle gives a committed week before its refunds open; pendingGraceBlocks must stay several raffle weeks long, and the raffle's week is blocks, so the two move together whatever the block time turns out to be. Copied verbatim.

Value

(26182 <untyped> bigint)

pendingGraceBlocks

untyped bigint

Value

(549822 <untyped> bigint)

realmSelf

.uverse.address

realmSelf is this realm's own address, resolved once so every escrow call names a constant rather than re-deriving it.

Value

<gnolang.StringValue>

idKeyWidth

untyped bigint

idKeyWidth/idZeroPad/idKey: enough decimal digits for any non-negative int64, so avl's lexicographic key order is numeric ID order. Copied from r/splash/arcade/duel/duel.gno.

Value

(19 <untyped> bigint)

idZeroPad

untyped string

Value

"0000000000000000000"

getOffer

func(id int64) *v4.Offer

getOffer returns the offer, or nil. avl's Get returns a single value, so existence is a separate Has.

Open
OID
0db16f…1d1b:34
getOffer details

Inspect func

stakeOf

func(rung int64) int64

stakeOf is the only place the ladder is read. Panics on any index outside it, including negative ones.

Open
OID
0db16f…1d1b:36
stakeOf details

Inspect func

checkInvariant

func()

checkInvariant is the O(1) solvency check run at the end of every state-changing function: the realm must hold at least what it owes in escrow. ">=" and not "==", deliberately — wugnot.Transfer is permissionless, so anyone can push wugnot at this address, and an exact check would let a one-ugnot donation brick every open offer and live duel.

Open
OID
0db16f…1d1b:38
checkInvariant details

Inspect func

entryUnitsOf

func(k string) int64

entryUnitsOf reads the booked entry units for a tree key (a holder address string), zero if absent. Copied from r/splash/arcade/duel/duel.gno.

Open
OID
0db16f…1d1b:39
entryUnitsOf details

Inspect func

bookEntries

func(holder .uverse.address, units int64, isUser bool) int64

bookEntries credits \`holder\` with \`units\` raffle entry units, booked in this realm for a future delivery, and returns what it actually booked — zero when the holder is not eligible or the batch is full. IT NEVER PANICS AND NEVER REFUSES ITS CALLER: a winner's Resolve must not be able to fail over a raffle entry, so every reason not to credit somebody is a zero and an event, not an abort. Copied verbatim (including both eligibility reasons and their reasoning) from r/splash/arcade/duel/duel.gno's identical function.

Open
OID
0db16f…1d1b:40
bookEntries details

Inspect func

requireUserCall

func(what string)

requireUserCall refuses a Post or Accept that did not come directly from a user. A realm party that wins a duel would have no way to call a winner-only exit, which strands the pool (SPEC-09 §8, finding D-1). Here Resolve (slice 2) is permissionless and pays directly, which removes the settlement hazard at its root — but §8 keeps the flag on Post and Accept anyway, for the entries half of finding 8 that a later slice still needs.

Open
OID
0db16f…1d1b:41
requireUserCall details

Inspect func

rejectAttachedCoin

func(what string)

rejectAttachedCoin panics if the caller attached native coin to a call that has no use for it. Every money-in path in this realm is the wugnot GRC20 allowance (Post, Accept), never the transaction's send envelope, so any attached ugnot would simply sit at this address forever with no code path that ever looks at it (SPEC-09 §8, ../speed/AUDIT-2026-10-07.md Y1). Call this as the FIRST STATEMENT of every crossing entrypoint. unsafe.OriginSend() reads the envelope attached at the chain root, shared by every realm in the call chain, so reading it is only sound when the immediate caller is an EOA — cur.Previous().IsUserCall() is that check. When the call did not come directly from a user, this is silently a no-op: requireUserCall is what refuses that case on Post and Accept, and Cancel has no reason to refuse a realm caller that is the offer's own poster.

Open
OID
0db16f…1d1b:42
rejectAttachedCoin details

Inspect func

Post

func(rung int64, opponent .uverse.address, label string) int64

Post pulls rungs\[rung] from the caller through the wugnot allowance and leaves it standing in the lobby. Returns the new offer's ID. opponent, when non-zero, addresses the offer to one player: only that address may later Accept it. Zero ("") leaves it open to anyone. label is free text for a future render, at most maxLabelLen bytes ("" is no label). Neither argument touches settlement: the pinned round and the beacon's randomness decide that, not this call.

Open
OID
0db16f…1d1b:43
Post details

Inspect func

Cancel

func(offerID int64)

Cancel returns an unaccepted offer's stake to its poster, exactly and with no fee, and deletes the record. Poster only. An accepted offer no longer exists — Accept deletes it in the same transaction that creates the duel — so "Cancel of an accepted offer" and "Cancel of an unknown offer" are the same refusal: whichever of Cancel or Accept lands first in block order removes the offer, and the other finds nothing. THE DESTINATION IS READ FROM THE RECORD, never a caller-supplied argument (SPEC-09 §8): a canceller can only ever recover the poster's own stake to the poster's own address.

Open
OID
0db16f…1d1b:44
Cancel details

Inspect func

Accept

func(offerID int64) int64

Accept takes a standing offer: pulls a stake equal to THE OFFER'S rung — the caller chooses nothing about the money — deletes the offer, pins the settlement round and creates the duel. Returns the new duel's ID. THE TAKER CHOOSES THE MOMENT OF Accept AND THEREFORE THE BUCKET, AND THAT IS SAFE — STATED RATHER THAN ASSUMED (SPEC-09 §3.1, invariant 3): every bucket reachable from "now" lies in the future, so no choice among them reveals anything about the randomness it will eventually be settled with. The poster has no influence over the round at all: it is pinned HERE, at Accept, and the poster's own call happened earlier, before this duel or its bucket existed to choose.

Open
OID
0db16f…1d1b:45
Accept details

Inspect func

outcomeTag

untyped string

outcomeTag is copied BYTE FOR BYTE from r/splash/arcade/duel/duel.gno's identical constant. THE OLD REALM'S commitTag AND takerCommitTag ARE GONE WITH THE COMMIT-REVEAL DESIGN THEY EXISTED TO SEAL (SPEC-09 §1, §4: no Secret, no Nonce, no TakerCommit here at all) — there is only ONE domain tag in this file, so the prefix-freeness machinery the old file needed to keep three tags from colliding (checkTagsPrefixFree and its init, guarding commitTag/outcomeTag/takerCommitTag pairwise) has nothing left to guard. Its absence here is deliberate, not an oversight: domain separation across a single tag is not a thing that needs checking.

Value

"splash/duel/v1/result:"

outcome

func(duelID int64, randomness string) posterWins bool

outcome computes a published duel's winner (SPEC-09 §3.2): sum = sha256(outcomeTag ‖ randomness ‖ duelID) posterWins = sum[len(sum)-1] % 2 == 0 duelID, AS ITS DECIMAL STRING (strconv.FormatInt, exactly as the old realm's posterWins suffixed its own duelID), IS MIXED IN, AND THAT IS LOAD-BEARING. bucketRounds (SPEC-09 §3.1) means MANY DUELS SHARE ONE Round and therefore one randomness — pinRound's whole point is that one beacon.Publish serves a whole bucket. Without duelID in this preimage, every duel in a bucket would hash to the identical sum and therefore resolve IDENTICALLY: exploitable (the first duel to resolve leaks every other duel in its bucket's outcome before they are even looked at) and absurd to watch (a communal settling moment where everyone flips the same side of the same coin). Mixing in duelID gives each duel its own independent coin despite sharing randomness with its whole bucket.

Open
OID
0db16f…1d1b:46
outcome details

Inspect func

Resolve

func(duelID int64)

Resolve computes a duel's winner from the published beacon round, pays and deletes the record — ALL IN ONE CALL. PERMISSIONLESS: there is no Collect in this design (SPEC-09 §3), so a winner never has to come back, and anybody — a party, a bystander, a keeper — may call this once the pinned round is on chain. The caller supplies nothing but duelID: the winner, the payout and the entries are all non-discretionary functions of stored state and the beacon's own randomness (invariant 4). ORDER OF OPERATIONS, SPEC-09'S OWN: reject attached coin first, as every entrypoint; look up the duel, refusing an unknown one; refuse if the pinned round is not yet published; THEN compute the outcome, pay, book entries, delete, emit. THE PAYOUT IS PORTED FROM r/splash/arcade/duel/duel.gno's settle, NOT REDESIGNED (SPEC-09 §8, this task's own instruction): same pool/rake arithmetic, same 60/40 split with ops floored and dust to the pot, same splitEntries/rakeEntryUnits weighting, same conservation assertion. THE ONE DEPARTURE, AND IT IS DELIBERATE RATHER THAN AN OMISSION, IS WHERE THE POT SHARE AND ITS ENTRIES LAND. The old settle's own comment says it plainly — "BOOKED, NOT SENT, AND NOT ONE LINE OF RAFFLE CODE RUNS IN THIS TRANSACTION" — and books into pendingPot/pendingEntries for a SEPARATE, later FlushPot to deliver, specifically so a raffle-side refusal can never take a winner's payout down with it. This file ports exactly that half of settle (see pendingPot's own doc comment above) rather than a direct, same-transaction call to raffle.FundPotWithEntries, because such a call would not merely risk the winner's payout here — IT WOULD PANIC EVERY SINGLE TIME, UNCONDITIONALLY: raffle.gno's FundPot and FundPotWithEntries both gate on cur.Previous().PkgPath() == the literal string "gno.land/r/g1r4aw43kagaaquhegxw6xr346h9f2qu2ezf0jar/arcade/duel/v3" — the OLD realm's own package path — and this realm's module is ".../arcade/duel/v2" (SPEC-09 §8b, set in slice 3's gnomod.toml), a different string, so that check fails on every call for as long as the raffle's compile-time constant names the old path. A direct call here would make Resolve permanently unable to pay any duel whose rake is positive (every rung's is), which is every duel — the opposite of SPEC-09 invariant 7 ("a duel can never be stuck by inaction"). ACTUALLY DELIVERING THE BOOKED POT IS FlushPot, BELOW (THIS SLICE, SPEC-09 §8a.4) — permissionless, separate from this call, and provably safe for a winner's payout even while the raffle refuses it. Nothing is lost in the meantime: the booked wugnot sits in this realm's own balance, accounted for by pendingPot in checkInvariant, exactly as it would between settle and FlushPot in the old realm today.

Open
OID
0db16f…1d1b:47
Resolve details

Inspect func

FlushPot

func() int64

FlushPot delivers the whole booked rake — pendingPot and the entries earned alongside it — to r/splash/arcade/raffle's CURRENT week, through raffle.FundPotWithEntries (or raffle.FundPot when nobody earned an entry), and returns the amount actually delivered. PERMISSIONLESS: anyone may pay the gas to do it, and the destination is not theirs to choose. Ported from r/splash/arcade/duel/duel.gno's FlushPot, same shape and same reasoning: a resolved duel's payout must never depend on another realm's bookkeeping (see pendingPot's own doc comment above), so delivery is a SEPARATE, later, permissionless call whose only failure mode costs the CALLER OF THIS FUNCTION a reverted transaction — never a duel's winner, who has already been paid and whose record is already gone. A NO-OP, NOT A PANIC, WHEN NOTHING IS BOOKED — the FIRST of two deliberate departures from the old realm's identical function, which panics in this case. A permissionless call anybody (in practice: a keeper, on a schedule) may make at any time is better served by a harmless zero than by forcing every caller to read PendingPot first just to avoid an abort. THE SECOND DEPARTURE: pendingPot AND THE REST OF THE BOOKING ARE ZEROED ONLY AFTER raffle.FundPot/FundPotWithEntries HAS RETURNED WITHOUT PANICKING — not before, the old realm's own ordering. ON A REAL CHAIN THIS ORDERING CHOICE DOES NOT MATTER: a panic reverts the WHOLE transaction, so whether the zeroing happened before or after the cross-realm call, it is discarded along with everything else the call did, and the old realm's "before" ordering is exactly as revert-safe as this file's "after" ordering. RE-ENTRANCY THROUGH THE CROSS-REALM CALL ITSELF IS SEPARATELY UNREACHABLE, ARCHITECTURALLY, NOT BY CONVENTION: this file imports raffle, so raffle cannot import this file back without a compile-time import cycle, and nothing else in raffle's own code calls into this realm — there is no path by which raffle.FundPot/ FundPotWithEntries could call back into FlushPot mid-execution regardless of ordering. A mutation check (reorder this file to zero first, the old realm's way) DOES make TestFlushPotRefusedDeliveryLeavesBookingIntactAndRetryable fail — but that result is a property of gno test's HARNESS, not of the chain: uassert.AbortsContains recovers the panic inside the same test process without re-running the whole message atomically, so a package-level write made before the panic (the zeroing) survives the recover when a real chain's revert would have discarded it. The harness's recover is not a state rollback, and the old realm's comment overstated what the mutation test proves by citing it as if it were one. The ordering below is kept anyway — zeroing after costs nothing and makes the retry property visible in THIS function's own control flow rather than resting on a chain-level guarantee this suite cannot exercise — but the reason to keep it is suite-observability, not a chain safety property the old ordering lacked. THE RULE THIS FILE ACTUALLY FOLLOWS, SO THE TWO ORDERINGS IN THIS SECTION DO NOT READ AS A CONTRADICTION: every OTHER state change in this file (Cancel's escrowed -=, Accept's offers.Remove, Resolve's duels.Remove) mutates bookkeeping BEFORE the paying wugnot.Transfer, on the general principle that a retry or a re-entrant call should find nothing rather than something stale. SweepStuckRake, below, follows that same general rule: its wugnot.Transfer is a plain, ungated call that this function's own prior checks (and checkInvariant's balance assertion) already make as close to unconditional as this realm can establish, so there is nothing to protect by delaying the zeroing. FlushPot is the ONE deliberate exception to the general rule, and the reason is specific to it: its cross-realm call is, TODAY, KNOWN to panic unconditionally (the raffle's package-path gate, documented below), so zeroing after is what keeps the retry property visible inside this function's own control flow for the one call in this file whose failure is not hypothetical. END-TO-END DELIVERY IS UNPROVEN BY THIS SUITE, AND THAT IS STATED HERE RATHER THAN DISCOVERED LATER (SPEC-09 §8a — found during slice 2 and a certainty, not a risk). raffle.gno's FundPot and FundPotWithEntries both gate on cur.Previous().PkgPath() being EXACTLY "gno.land/r/g1r4aw43kagaaquhegxw6xr346h9f2qu2ezf0jar/arcade/duel/v3" — the OLD realm's own path, never this realm's ".../arcade/duel/v2" — so EVERY CALL THIS FUNCTION MAKES TO THE RAFFLE TODAY PANICS, UNCONDITIONALLY, and that panic reverts this whole call with pendingPot intact (the retry property above, independently of the zero-after ordering, since nothing here has executed the zeroing yet when the panic happens). This is §8a's forced deploy order working as designed, not a defect to chase here: the raffle must be redeployed FIRST, naming this realm's path, before this function's happy path can be exercised for real — and it will be, for the first time, at that deploy, not in this suite. What this suite proves instead, and all it honestly can: the booking is correct, a refused delivery loses nothing and remains retryable, the books balance, this function is permissionless, and it refuses attached coin.

Open
OID
0db16f…1d1b:48
FlushPot details

Inspect func

SweepStuckRake

func() int64

SweepStuckRake is the LAST RESORT for a booked rake the raffle will not take, so that pendingPot is never a place money can enter and never leave. It transfers the WHOLE booked amount to the raffle's address by a PLAIN wugnot.Transfer — no raffle code runs, so nothing in the raffle can refuse it — and returns the amount moved. PERMISSIONLESS, and only once the batch's YOUNGEST CLAIM (pendingSince) has stood for pendingGraceBlocks. Ported from r/splash/arcade/duel/duel.gno's SweepStuckRake — same mechanism, same grace window, same dropped-entries cost, same reasoning; read that function's own doc comment for the full argument, including the gas figures for a full 128-holder batch. This comment restates only what is specific to deciding to port it here at all. WHY PORT A GRACE-WINDOW LAST RESORT WHEN §8a's REFUSAL IS A CERTAINTY, NOT THE OCCASIONAL TROUBLE THE OLD REALM'S VERSION IS A LAST RESORT FOR — this is the one judgment call this slice makes and states plainly rather than assuming. The invariant SweepStuckRake protects ("a place money can enter and never leave") does not care WHY FlushPot's door is shut, only THAT it might stay shut indefinitely. Today it is shut by construction — the raffle's duelRealmPkgPath names the old realm, not this one — and it reopens only when the raffle is redeployed naming this realm's path (SPEC-09 §8b). Resolve keeps booking pendingPot regardless of whether that redeploy ever happens, is delayed, or the swap this directory is scaffolding for is abandoned; Resolve must not, and does not, know or care. Without this function that booking would have exactly one exit (FlushPot), and that exit is the one this entire slice's own doc comment says is unproven. A grace-windowed, gate-bypassing last resort is cheap insurance against that exit never opening, and the mechanism — a plain transfer that runs no raffle code — does not depend on knowing why the gate is shut, only on giving the booking a second way out that the gate cannot touch. THE BOOKED ENTRIES ARE DROPPED ON THIS PATH, exactly as in the old realm's SweepStuckRake and for the identical reason: the money lands in the raffle's ops pot via sweepDonations, not a prize pot, so there is no draw left for an entry to be an entry IN — and an entry with no rake behind it is exactly what checkInvariant refuses. The 60/40 split is NOT preserved on this path (it all becomes the house's ops pot); that is the honest cost of a window existing at all, and it is preventable by anyone, at any time, by simply calling FlushPot instead. Panics if there is nothing booked, or if the window has not elapsed.

Open
OID
0db16f…1d1b:49
SweepStuckRake details

Inspect func

pageLimit

untyped bigint

pageLimit caps every list this realm returns. UNBOUNDED READS ARE A DENIAL SURFACE, not merely untidy: offers and duels grow without limit, and a caller asking for all of them on a busy arcade gets a response no node wants to build. Callers page.

Value

(100 <untyped> bigint)

PinOffsetRounds

func() int64

PinOffsetRounds and BucketRounds expose the pin arithmetic, so a front end can work out WHICH FLIP a new acceptance would land in -- "4 minutes left to get into this one" -- without keeping a second copy of either number. THAT SECOND COPY IS THE WHOLE REASON THESE EXIST. The constants are a security budget (read pinOffsetRounds' comment), and a JavaScript duplicate of a security budget is one that drifts silently the moment anyone retunes it. The front end asks the realm instead. Same reasoning and same shape as arcade/speed's PinOffsetRounds. They are reads of compile-time constants: no state, no caller, nothing to guard.

Open
OID
0db16f…1d1b:50
PinOffsetRounds details

Inspect func

RoundForAcceptAt

func(currentRound int64) int64

RoundForAcceptAt is THE question a front end actually has, answered by the realm that owns the answer: if a duel were accepted while the beacon reads \`currentRound\`, which round would it be pinned to? It is the exact expression Accept uses, factored out so there is ONE copy of it and both callers share it -- see pinnedRoundFor below. A front end can therefore show a truthful join deadline: the answer changes at a bucket boundary, and the moment it changes is the moment the current flip closes to newcomers.

Open
OID
0db16f…1d1b:52
RoundForAcceptAt details

Inspect func

RungCount

func() int64

RungCount and StakeOfRung expose the ladder so a front end need not hardcode it. Same names and shapes as the superseded realm, deliberately, so the existing front end's reads.js keeps working.

Open
OID
0db16f…1d1b:53
RungCount details

Inspect func

StakeOfRung

func(rung int64) int64
Open
OID
0db16f…1d1b:54
StakeOfRung details

Inspect func

Escrowed

func() int64

Escrowed is the players' money this realm is holding: every standing offer's stake plus both stakes of every live duel. It does NOT include pendingPot, which is the raffle's share already earned and awaiting delivery -- read PendingPot for that. Two separate numbers because they are two separate claims, and summing them would hide which is which.

Open
OID
0db16f…1d1b:55
Escrowed details

Inspect func

OfferIDs

func(offset int64, limit int64) []int64

OfferIDs and DuelIDs page through the boards in key order. offset and limit are CLAMPED rather than validated: a caller asking for a million gets pageLimit, and one asking past the end gets an empty slice. A reader that panics on a bad page is a reader a front end has to guard against.

Open
OID
0db16f…1d1b:58
OfferIDs details

Inspect func

DuelIDs

func(offset int64, limit int64) []int64
Open
OID
0db16f…1d1b:59
DuelIDs details

Inspect func

idsOf

func(t *v0.Tree, offset int64, limit int64) []int64
Open
OID
0db16f…1d1b:60
idsOf details

Inspect func

OfferOf

func(id int64) (.uverse.address, int64, int64)

OfferOf reports a standing offer: poster, rung, posted-at height. All zero for an id that is not on the table -- the same "zero means absent" shape the superseded realm used, so a front end can tell absent from present without a second call.

Open
OID
0db16f…1d1b:61
OfferOf details

Inspect func

OfferChallenge

func(id int64) (.uverse.address, string)
Open
OID
0db16f…1d1b:62
OfferChallenge details

Inspect func

DuelOf

func(id int64) (.uverse.address, .uverse.address, int64, int64, int64)

DuelOf reports a live duel: poster, taker, rung, THE PINNED ROUND, and the height it was accepted at. THE ROUND IS THE IMPORTANT ONE AND IS WHY THIS SHAPE DIFFERS FROM THE SUPERSEDED REALM'S. That realm returned a reveal deadline, a resolved flag and a winner, because its duels sat in state until somebody opened them. Here Resolve pays and DELETES in the same call, so a duel in this tree is by definition unsettled and there is no winner to report. What a caller wants instead is the round -- pair it with beacon.TimeOfRound(round) and you have the countdown.

Open
OID
0db16f…1d1b:63
DuelOf details

Inspect func

DuelLabel

func(id int64) string
Open
OID
0db16f…1d1b:64
DuelLabel details

Inspect func

DuelRound

func(id int64) int64

DuelRound is DuelOf's fourth value on its own, because it is the one a countdown polls and a caller should not have to decode a five-tuple to get it. 0 for an unknown or already-settled duel.

Open
OID
0db16f…1d1b:65
DuelRound details

Inspect func

PendingPot

func() int64

PendingPot is the raffle's share of settled duels' rake, earned and booked but not yet delivered. FlushPot's own doc comment told callers to read this and IT DID NOT EXIST -- a comment pointing at a function nobody wrote, which is the shape this project has met repeatedly today.

Open
OID
0db16f…1d1b:66
PendingPot details

Inspect func

PendingEntryUnits

func() int64

PendingEntryUnits and PendingEntryHolders are what FlushPot will deliver alongside the pot. A keeper reads all three to decide whether calling is worth the gas.

Open
OID
0db16f…1d1b:67
PendingEntryUnits details

Inspect func

PendingEntryHolders

func() int64
Open
OID
0db16f…1d1b:68
PendingEntryHolders details

Inspect func

PendingSince

func() int64

PendingSince is the height the current booking started accruing, or 0 when nothing is booked. It is SweepStuckRake's grace clock, exposed so the wait is visible rather than inferred.

Open
OID
0db16f…1d1b:69
PendingSince details

Inspect func

resultRingSize

untyped bigint

\--- recent results, a bounded ring ---------------------------------------- WHY THIS EXISTS. Resolve pays and DELETES the duel in the same call, so a settled duel leaves no state whatsoever. That is right for storage and wrong for the product: this design's whole point is that MANY DUELS SHARE A ROUND AND SETTLE TOGETHER, and a front end that showed nothing a second later would throw away the one moment the design was built to create. WHY IT IS BOUNDED, AND WHAT THAT COSTS. A realm cannot be pruned and nothing here can delete on a caller's behalf, so an unbounded history is a storage leak that grows for ever with no exit -- the exact shape this project forbids for money and forbids here for the same reason. So the ring keeps the last resultRingSize settlements and overwrites the oldest. WHAT IS LOST: everything older falls off and is recoverable ONLY from the chain's event log, which DuelResolve writes in full. That is a real loss and is stated rather than buried -- a reader must not mistake this for the complete history.

Value

(32 <untyped> bigint)

Result

type

Value

v4.Result

results

[]v4.Result (len=3)

// len is capped at resultRingSize

Open
OID
0db16f…1d1b:130
results details (len=3)

+3 more Inspect slice

resultNext

int

// write cursor; wraps

Value

3

resultSeen

int64

// total ever recorded, including those overwritten

Value

3

recordResult

func(duelID int64, round int64, winner .uverse.address, loser .uverse.address, rung int64)

recordResult is called by Resolve immediately before it emits, with the values it has already computed. It never panics and never allocates beyond the ring: once full it overwrites in place.

Open
OID
0db16f…1d1b:73
recordResult details

Inspect func

ResultCount

func() int64

ResultCount is how many settlements the ring currently holds, which is at most resultRingSize. Read ResultsSeen for how many there have ever been.

Open
OID
0db16f…1d1b:74
ResultCount details

Inspect func

ResultsSeen

func() int64

ResultsSeen is every settlement this realm has ever made, including those the ring has overwritten. The gap between this and ResultCount is exactly what a caller must go to the event log for.

Open
OID
0db16f…1d1b:75
ResultsSeen details

Inspect func

ResultRingSize

func() int64

ResultRingSize is the bound, exported so a front end can say "last 32" without hardcoding a number that might change.

Open
OID
0db16f…1d1b:76
ResultRingSize details

Inspect func

RecentResults

func(limit int64) []v4.Result

RecentResults returns up to \`limit\` settlements, MOST RECENT FIRST. limit is clamped to the ring's size.

Open
OID
0db16f…1d1b:77
RecentResults details

Inspect func

cell

func(s string) string

\--- Render ---------------------------------------------------------------- THE PAGE gnoweb SHOWS. The first v2 deployment had none, so anybody who went looking at this realm directly saw nothing at all. IT MUST NEVER PANIC, WHATEVER path IS. A panicking Render does not fail quietly on one request -- it takes the realm's whole gnoweb page down, for everyone, permanently, with no way to fix it because a realm cannot be upgraded. So this function ignores \`path\` entirely, reads only, and every loop below is over a bounded collection. EVERY PIECE OF CALLER TEXT GOES THROUGH cell(). A Label is free text from a poster, and this is a markdown document: an unescaped label can forge table rows, inject links, or impersonate the house. sanitize.InlineText handles the markdown; the pipe escaping is ours, because a bare | ends a table cell.

Open
OID
0db16f…1d1b:78
cell details

Inspect func

short

func(a .uverse.address) string
Open
OID
0db16f…1d1b:79
short details

Inspect func
rakeBps : untyped bigint =(300 <untyped> bigint)
bpsDenom : untyped bigint =(10000 <untyped> bigint)
rakePotShareBps : untyped bigint =(6000 <untyped> bigint)
rakeOpsShareBps : untyped bigint =(4000 <untyped> bigint)
rung0 : untyped bigint =(10000000 <untyped> bigint)
rung1 : untyped bigint =(50000000 <untyped> bigint)
rung2 : untyped bigint =(200000000 <untyped> bigint)
rung3 : untyped bigint =(1000000000 <untyped> bigint)
rungs : [4]int64 Inspect
maxStake : untyped bigint =(1000000000 <untyped> bigint)
init.10 : func() Inspect
opsVaultAddr : .uverse.address =<gnolang.StringValue>
raffleRealmAddr : .uverse.address =<gnolang.StringValue>
mulDiv : func(a uint64, b uint64, d uint64) uint64 Inspect
poolOf : func(stake int64) int64 Inspect
rakeOf : func(pool int64) int64 Inspect
splitRake : func(rake int64) (potShare int64, opsShare int64) Inspect
rakeEntryUnits : func(rake int64) int64 Inspect
splitEntries : func(total int64) (winnerUnits int64, loserUnits int64) Inspect
maxEntryUnitsFor : func(potShare int64) int64 Inspect
pinOffsetRounds : untyped bigint =(100 <untyped> bigint)
bucketRounds : untyped bigint =(100 <untyped> bigint)
minPinOffsetRounds : untyped bigint =(100 <untyped> bigint)
pinRound : func() int64 Inspect
pinnedRoundFor : func(current int64) int64 Inspect
maxLabelLen : untyped bigint =(64 <untyped> bigint)
Offer : type =v4.Offer
Duel : type =v4.Duel
offers : *v0.Tree Inspect
duels : *v0.Tree Inspect
nextOfferID : int64 =4
nextDuelID : int64 =4
escrowed : int64 =0
pendingPot : int64 =3600000
pendingEntries : *v0.Tree Inspect
pendingEntryUnits : int64 =24
pendingSince : int64 =264215
maxPendingEntryHolders : untyped bigint =(128 <untyped> bigint)
blocksPerDay : untyped bigint =(26182 <untyped> bigint)
pendingGraceBlocks : untyped bigint =(549822 <untyped> bigint)
realmSelf : .uverse.address =<gnolang.StringValue>
selfAddr : func() .uverse.address Inspect
idKeyWidth : untyped bigint =(19 <untyped> bigint)
idZeroPad : untyped string ="0000000000000000000"
idKey : func(id int64) string Inspect
getOffer : func(id int64) *v4.Offer Inspect
getDuel : func(id int64) *v4.Duel Inspect
stakeOf : func(rung int64) int64 Inspect
checkLabel : func(label string) Inspect
checkInvariant : func() Inspect
entryUnitsOf : func(k string) int64 Inspect
bookEntries : func(holder .uverse.address, units int64, isUser bool) int64 Inspect
requireUserCall : func(what string) Inspect
rejectAttachedCoin : func(what string) Inspect
Post : func(rung int64, opponent .uverse.address, label string) int64 Inspect
Cancel : func(offerID int64) Inspect
Accept : func(offerID int64) int64 Inspect
outcomeTag : untyped string ="splash/duel/v1/result:"
outcome : func(duelID int64, randomness string) posterWins bool Inspect
Resolve : func(duelID int64) Inspect
FlushPot : func() int64 Inspect
SweepStuckRake : func() int64 Inspect
pageLimit : untyped bigint =(100 <untyped> bigint)
PinOffsetRounds : func() int64 Inspect
BucketRounds : func() int64 Inspect
RoundForAcceptAt : func(currentRound int64) int64 Inspect
RungCount : func() int64 Inspect
StakeOfRung : func(rung int64) int64 Inspect
Escrowed : func() int64 Inspect
OfferCount : func() int64 Inspect
DuelCount : func() int64 Inspect
OfferIDs : func(offset int64, limit int64) []int64 Inspect
DuelIDs : func(offset int64, limit int64) []int64 Inspect
idsOf : func(t *v0.Tree, offset int64, limit int64) []int64 Inspect
OfferOf : func(id int64) (.uverse.address, int64, int64) Inspect
OfferChallenge : func(id int64) (.uverse.address, string) Inspect
DuelOf : func(id int64) (.uverse.address, .uverse.address, int64, int64, int64) Inspect
DuelLabel : func(id int64) string Inspect
DuelRound : func(id int64) int64 Inspect
PendingPot : func() int64 Inspect
PendingEntryUnits : func() int64 Inspect
PendingEntryHolders : func() int64 Inspect
PendingSince : func() int64 Inspect
resultRingSize : untyped bigint =(32 <untyped> bigint)
Result : type =v4.Result
results : []v4.Result (len=3) Inspect
resultNext : int =3
resultSeen : int64 =3
recordResult : func(duelID int64, round int64, winner .uverse.address, loser .uverse.address, rung int64) Inspect
ResultCount : func() int64 Inspect
ResultsSeen : func() int64 Inspect
ResultRingSize : func() int64 Inspect
RecentResults : func(limit int64) []v4.Result Inspect
cell : func(s string) string Inspect
short : func(a .uverse.address) string Inspect
Render : func(path string) string Inspect