NOTES.md
11.07 Kb · 162 lines
1# Notes against the audit — realms/garden
2
3Things the audit surfaced that are product decisions, not engineering ones. Written here
4per the owner's own rule (NOTES.md at repo root): checked facts and open trade-offs that
5the pack/audit doesn't resolve go in a NOTES.md, not silently into code.
6
7## Open question: a realm can permanently own a gnome on behalf of no one (2026-10-09)
8
9**Status: unresolved. Needs the owner's decision before this realm goes near a chain.**
10
11`Adopt` derives the caller as `cur.Previous().Address()` (`garden.gno:230`, doc at
12`garden.gno:188-195`) — "the account that crossed into this realm", matching
13`examples/gno.land/r/demo/foo721`'s own idiom for caller identity. That address is not
14restricted to an externally-owned account (a human holding a key). **Any realm** can call
15`Adopt` the same way a human would, and `cur.Previous().Address()` then resolves to that
16calling realm's own address, not to any human behind it.
17
18Combine that with two things this realm does deliberately, by design, per the brief's
19"what it needs, and nothing more":
20
21- **No transfer.** `soulboundExtension.OnTransfer` panics unconditionally (rule 6, "every
22 transfer fails"). This is load-bearing and correct — it's the whole soulbound guarantee
23 the brief asks for.
24- **No burn, no admin, no rename.** There is no path, for anyone, to undo an `Adopt` once
25 it lands.
26
27Put together: if a realm (not a human) ever calls `Adopt` — whether on purpose, by a bug
28in that realm, or because some future UI flow proxies a user's call through an
29intermediate realm without meaning to — **that realm's own address becomes the permanent,
30unrecoverable owner of a gnome.** No human can ever claim it. No key held by any person
31unlocks it. It is not stuck in a recoverable sense (the audit found no reentrancy, no
32capability leak, no exposed transfer that would make it *worse*); it is correctly,
33permanently inaccessible to the person who might have expected to own it. The audit
34flagged this as **unfixable within this realm's current design** — there is no check this
35code can add that distinguishes "a human's wallet called this" from "a realm called this
36and the human never sees the result" without also restricting legitimate future
37composition (another realm adopting *on behalf of* a verified human, a batching/sponsor
38flow, etc.).
39
40### The trade-off, stated plainly
41
42- **Restrict `Adopt` to human (EOA) callers only** — e.g. require some check along the
43 lines of "this frame is a plain `maketx call`, not a realm-to-realm crossing call"
44 before minting. `cur.Previous().IsUserCall()` is the name used for this in
45 `pack/01-build-brief/gnogotchi-build-brief.md` and `pack/08-research/01-gno-land-platform.md`,
46 but this engineering pass has not independently verified, against this repo's actual
47 Gno toolchain, that a method of that name exists on `realm`/`cur.Previous()` — it is
48 recorded here as the pack's own suggested name, not as a confirmed API this realm could
49 call today. Verify it against the real SDK/runtime before relying on it. This closes the
50 custody trap
51 completely: a gnome can only ever be minted directly into a human wallet. The cost is
52 that it forecloses, permanently and by construction, any future flow where a realm
53 calls `Adopt` on a human's behalf — a sponsor/gift flow, a batching contract, a future
54 wrapper realm the owner might want to build in a later phase. Re-opening that door later
55 would need a new, deliberately-designed path (e.g. an explicit "adopt for" argument with
56 its own trust model), not just deleting the restriction, because by then real gnomes may
57 already exist that were minted the old way.
58- **Leave it open, as today.** Preserves maximum flexibility for whatever Phase 2+ wants
59 to build on top of `Adopt`. The cost is that the custody trap stays live for as long as
60 this code ships as-is: the first realm that calls `Adopt` — malicious, buggy, or just
61 built by someone who didn't read this note — mints a gnome nobody can ever claim, and
62 there is no admin, burn or migration path in this realm to undo it after the fact.
63
64This is not a question the engineering brief answers, and it is not safe to guess: closing
65it is a one-way, backward-compatible-looking change that quietly forecloses a product
66surface; leaving it open is a one-way, invisible-until-it-happens risk. **The owner should
67decide before this realm is deployed anywhere real funds or real users can reach it.**
68
69### Same open question, a second angle: a sponsor realm and the `OriginSend` guard (2026-10-09)
70
71`Adopt`'s `unsafe.OriginSend().IsZero()` check (`garden.gno`, inside `Adopt`) refuses the
72call if the transaction's *origin* attached any coins at all. Re-checked this round: that
73check is origin-scoped, not frame-scoped, so it inspects the whole transaction's send
74regardless of how many realms it crossed through to reach `Adopt`. A "restrict to human
75callers" fix for the custody question above does not make this interaction go away, and
76may make it worse in one specific shape worth recording here rather than only in code:
77
78A future **sponsor realm** — one that takes a small fee for itself and then crosses into
79`Adopt` on the human's behalf, remainder or nothing attached — would still be rejected by
80today's `OriginSend` check, because that check sees the *origin's* original send, not what
81actually reaches `Adopt`'s own frame. Nothing would be stranded in that flow (the sponsor
82realm's own fee logic handles its own coins; `Adopt` itself receives nothing), but the
83guard cannot tell "nothing reaches me" from "nothing reaches me because the origin sent
84zero in the first place" apart from "something was sent upstream and I can't prove it
85didn't end up here." It would reject a perfectly safe call.
86
87This is the same unresolved shape as the custody question above, from the other
88direction: both are consequences of `Adopt` being reachable by something other than a
89direct human `maketx call`, and both need the same owner decision — should `Adopt` ever be
90callable by, or through, another realm at all, and if so, under what trust model — before
91either the custody restriction or a sponsor-aware coin check is designed. Do not special-
92case the `OriginSend` guard for "known sponsor realms" ad hoc; it would be solving half of
93the same problem the custody note above already asks the owner to resolve in full.
94
95## Open question: the name allowlist excludes every combining mark, so whole writing systems are unnameable (2026-10-09, third audit round, finding Y7)
96
97**Status: this is a defensible, already-shipped trade-off, not a bug. Recorded here because
98which writing systems the game supports is a product decision, not an engineering one —
99same rule as the custody question above.**
100
101`validNameRune` (`garden.gno`) admits only individual Unicode letters (`unicode.IsLetter`),
102digits (`unicode.IsDigit`), space, hyphen and apostrophe — each check applied one rune at a
103time, with no notion of a rune combining with the one before or after it. Unicode's
104combining-mark categories (chiefly Mn, "nonspacing mark") are entirely outside that
105allowlist: no combining mark of any kind can ever pass `validNameRune`, today or after any
106future Unicode version, since the exclusion is by general category, not a specific list
107that could go stale.
108
109**What this actually forecloses**, stated plainly because "a name allowlist" undersells it:
110a large number of ordinary, everyday spellings are only representable with at least one
111base letter plus one or more combining marks, not as a sequence of precomposed
112letter-only code points. Concretely, and non-exhaustively:
113
114- **NFD-normalised Latin.** Some input methods and some Apple keyboards produce "é" as
115 the two-code-point sequence `e` + U+0301 COMBINING ACUTE ACCENT (NFD) rather than the
116 single precomposed U+00E9 (NFC). A name typed or pasted in NFD form — "José" being the
117 canonical example — is rejected today even though the visually-identical NFC spelling of
118 the same name is accepted.
119- **Devanagari, as ordinarily written.** Vowel signs (mātrā) are combining marks; ordinary
120 Devanagari text is not just base consonants.
121- **Thai, as ordinarily written.** Vowel and tone marks are combining marks positioned
122 above/below the preceding consonant.
123- **Hebrew with niqqud** (vowel points) and **Arabic with harakat** (vowel diacritics) —
124 both are combining marks layered on consonant letters; the unvocalised consonant-only
125 spelling of a name may pass where the fully-vocalised, more traditional spelling would
126 not.
127
128None of this is specific to "foreign" scripts as a category — `TestAdoptAcceptsCrossScriptHomoglyphs`
129already proves single-code-point non-Latin letters (Cyrillic, Greek, fullwidth, etc.) are
130accepted just fine. The exclusion is specifically and only about *combining* marks, which
131happen to be load-bearing for several scripts' ordinary orthography and merely decorative
132for Latin's.
133
134**The benefit this trade buys, stated equally plainly:** combining marks are exactly the
135mechanism "zalgo text" (stacking dozens of combining marks on a single base letter to
136produce the glitchy, vertically-exploding look) depends on. Excluding every combining mark
137makes zalgo-stacked names structurally impossible in this realm, not merely discouraged —
138there is no finite deny-list to maintain and no risk of a renderer-breaking name getting
139through because some zalgo-capable combining mark wasn't on it. This is a real, concrete
140security/quality property, not a hypothetical one: Render already embeds the name directly
141into a markdown heading with no further escaping (see `validNameRune`'s own comment), and a
142sufficiently mark-stacked name is exactly the kind of input that has broken renderers and
143UI layouts elsewhere on the web.
144
145**Why shipping strict is the safe order, not just the easy one:** relaxing this later
146(admitting combining marks, most plausibly gated to "must immediately follow a base
147letter/digit" so a name still can't *start* with a stray mark or stack unboundedly) is
148backward-compatible — every name accepted today stays accepted, and the newly-expanded set
149of acceptable names doesn't retroactively break anything already minted. Tightening a
150currently-open rule later is not backward-compatible in the same way: with no rename, no
151burn and no admin anywhere in this realm, any name already minted under a looser rule is on
152chain forever regardless of what a later, stricter rule would say about it — so a mistake
153in the direction of "too permissive" cannot be undone, while a mistake in the direction of
154"too strict" (this realm's current choice) costs only a future relaxation, not a migration.
155
156**The decision this note asks the owner to make:** whether Phase 1+ should relax
157`validNameRune` to admit combining marks (and if so, under what shape of gate) so players
158whose names are ordinarily written with them can spell them as intended, accepting the
159reintroduced zalgo-stacking risk and designing a mitigation for it (e.g. a cap on
160consecutive combining marks per base character) — or whether excluding them permanently,
161as today, is the intended answer for this game. This is not a question the engineering
162brief or the audit resolves, and it is not safe to guess which way the owner wants it.