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.

ops.gno

6.17 Kb · 147 lines
  1// Package ops is the house's till.
  2//
  3// WHAT IT IS FOR. The three arcade realms each take a cut -- 5% of a draw ticket,
  4// 3% of a duel stake -- and each pushes it to ONE compile-time address,
  5// opsVaultAddr, which is the address derived from THIS package's path. Until this
  6// realm existed, that address had no code at it: the money left the games and
  7// landed somewhere nothing could ever spend from. That is
  8// AUDIT-2026-10-07.md RED-2 and the arcade's own source names it, in terms, as
  9// "still blocking for deployment ... the owner's address is the missing input".
 10//
 11// This realm is that input, and nothing else. It holds wrapped GNOT and lets one
 12// address take it out.
 13//
 14// WHAT IT DELIBERATELY DOES NOT DO, because every one of these is a way for a
 15// till to become a liability:
 16//
 17//   - NO ACCESS TO ANY PRIZE POT. It cannot reach one. The draws pay winners from
 18//     their own balances and this realm has no call into them. The owner key
 19//     cannot touch a player's money because there is no code here that could.
 20//   - NO OWNERSHIP TRANSFER. owner is a compile-time constant with no setter, so
 21//     there is no function an attacker can reach to become the owner, and no
 22//     function the owner can be tricked into calling. The cost is stated below.
 23//   - NO PAUSE, NO UPGRADE, NO ADMIN. Nothing here can be turned off or changed
 24//     after deployment, by anyone.
 25//   - NO NATIVE COIN. chain/banker is not imported. This realm can move wrapped
 26//     GNOT and nothing else, which is what the games actually send.
 27//
 28// THE COST OF A FIXED OWNER, SAID PLAINLY RATHER THAN DISCOVERED LATER: if that
 29// key is lost, every coin in this realm is lost with it. There is no recovery
 30// path and cannot be one without adding exactly the kind of privileged function
 31// the list above refuses. That trade is the owner's and it was made knowingly.
 32package ops
 33
 34import (
 35	"chain"
 36	"chain/runtime/unsafe"
 37	"strconv"
 38
 39	"gno.land/r/gnoland/wugnot"
 40)
 41
 42// owner is the only address that may withdraw. Compile-time, no setter.
 43//
 44// IT IS THE OWNER'S PERSONAL WALLET AND NOT A REALM, which is the opposite of
 45// how the games address this one. That is deliberate: a till wants a person at
 46// the end of it, and putting another contract there would just move the same
 47// question one step along.
 48const owner address = "g1srj6dxvyd378vm7787r45drryzvalcu32rdkfu"
 49
 50// Owner reports who may withdraw. Exported so the claim is checkable from
 51// outside rather than taken from this comment.
 52func Owner() address { return owner }
 53
 54// Balance is the wrapped GNOT this realm holds, read from wugnot rather than
 55// tracked in a counter of our own. A realm that keeps its own idea of its
 56// balance is a realm that can disagree with the truth.
 57func Balance(cur realm) int64 { return wugnot.BalanceOf(cur.Address()) }
 58
 59// rejectAttachedCoin panics if the caller attached native coin to a call that
 60// has no use for it. Identical in shape and reasoning to the guard the three
 61// arcade realms gained on 2026-10-07: every money path here is wrapped GNOT, so
 62// attached ugnot would sit in this realm for ever with nothing able to reach it.
 63//
 64// Inert on a non-user call, for the reason r/splash/exchange's Buy comment
 65// gives: unsafe.OriginSend() reads the envelope attached at the chain root,
 66// shared by every realm in the call chain, so reading it is only sound when the
 67// immediate caller is an EOA.
 68func rejectAttachedCoin(cur realm, what string) {
 69	if !cur.Previous().IsUserCall() {
 70		return
 71	}
 72	if sent := unsafe.OriginSend(); len(sent) != 0 {
 73		panic("ops: " + what + " does not take native coin")
 74	}
 75}
 76
 77// requireOwner is the only authorisation in this realm.
 78//
 79// cur.Previous().Address() AND NOT runtime.OriginCaller(). The origin caller is
 80// whoever signed the transaction, which is the same thing right up until the
 81// owner is persuaded to call some other realm that calls this one -- at which
 82// point an origin check would authorise a withdrawal the owner never asked for.
 83// The immediate caller is the one that cannot be borrowed.
 84func requireOwner(cur realm) {
 85	if cur.Previous().Address() != owner {
 86		panic("ops: only the owner may withdraw")
 87	}
 88}
 89
 90// Withdraw sends `amount` of wrapped GNOT to the owner.
 91//
 92// THE DESTINATION IS NOT AN ARGUMENT. It is always the owner's own address, so
 93// there is no call shape in which this realm pays somebody else -- not by
 94// mistake, not by a malformed argument, not by a caller who has somehow got past
 95// requireOwner. A withdrawal function that takes a recipient is one typo away
 96// from being a drain.
 97func Withdraw(cur realm, amount int64) int64 {
 98	rejectAttachedCoin(cur, "Withdraw")
 99	requireOwner(cur)
100	if amount <= 0 {
101		panic("ops: amount must be positive")
102	}
103	have := wugnot.BalanceOf(cur.Address())
104	if amount > have {
105		panic("ops: asked for " + strconv.FormatInt(amount, 10) +
106			" and hold " + strconv.FormatInt(have, 10))
107	}
108	wugnot.Transfer(cross(cur), owner, amount)
109	chain.Emit("OpsWithdraw",
110		"to", string(owner),
111		"amount", strconv.FormatInt(amount, 10),
112	)
113	return amount
114}
115
116// WithdrawAll empties the till to the owner. Refuses on an empty one rather than
117// succeeding with a zero transfer: "it worked" and "there was nothing there"
118// are different answers and a caller deserves to be told which.
119func WithdrawAll(cur realm) int64 {
120	rejectAttachedCoin(cur, "WithdrawAll")
121	requireOwner(cur)
122	have := wugnot.BalanceOf(cur.Address())
123	if have == 0 {
124		panic("ops: nothing to withdraw")
125	}
126	wugnot.Transfer(cross(cur), owner, have)
127	chain.Emit("OpsWithdraw",
128		"to", string(owner),
129		"amount", strconv.FormatInt(have, 10),
130	)
131	return have
132}
133
134func Render(path string) string {
135	return "# SPLASH ops\n\n" +
136		"The house's till. It holds the cut the three arcade games take " +
137		"(5% of a draw ticket, 3% of a duel stake) and lets exactly one address " +
138		"withdraw it.\n\n" +
139		"- **Owner:** `" + string(owner) + "`\n" +
140		"- **No ownership transfer, no pause, no upgrade, no admin.** None of " +
141		"those functions exist here.\n" +
142		"- **It cannot reach a prize pot.** There is no call from this realm " +
143		"into any game.\n\n" +
144		"If the owner's key is lost, the balance is lost with it. There is no " +
145		"recovery path and adding one would mean adding the privileged function " +
146		"this realm refuses to have.\n"
147}