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.

auth.gno

7.51 Kb · 199 lines
  1package nsdata
  2
  3import (
  4	"chain"
  5
  6	"gno.land/r/nym-mnsrehearsal001/nsnft/v2"
  7)
  8
  9// isFresh reports whether a name's profile data belongs to the token's
 10// CURRENT ownership epoch. See Name.epoch and freshen.
 11func isFresh(n *Name, tid string) bool { return freshAt(n, nsnft.Epoch(tid)) }
 12
 13// freshAt is isFresh with the ledger's answer passed in. See freshenTo.
 14func freshAt(n *Name, epoch int64) bool { return n.epoch == epoch }
 15
 16// ownsNow reports whether `addr` holds the token right now, per the ledger.
 17func ownsNow(addr address, tid string) bool {
 18	return addr != "" && nsnft.OwnerOf(tid) == addr
 19}
 20
 21// isValidNow / isReclaimable are pure integer math over `termLength` and
 22// `gracePeriod`, both of which are settable — so the lifecycle rules
 23// themselves can be retuned after deployment. All times are unix seconds.
 24func isValidNow(expires, now int64) bool {
 25	return now < expires
 26}
 27
 28// isReclaimable compares the elapsed time with the grace period instead of
 29// adding the grace period to the expiry. The sum wrapped: an expiry of
 30// math.MaxInt64 — the natural "never expires" value — read as valid AND
 31// reclaimable at once, and a stranger could take the name (audit L-2). A
 32// name that has not expired is never reclaimable, whatever grace says.
 33func isReclaimable(expires, now int64) bool {
 34	if now < expires {
 35		return false
 36	}
 37	return now-expires >= gracePeriod
 38}
 39
 40// streakOf derives how many whole terms a holder has actually lived
 41// through, replacing the counter that used to be stored on the record.
 42//
 43// The stored version counted RENEWAL CALLS, so it measured transactions
 44// rather than time: prepaying five years bumped it five times on a name
 45// registered that morning, and an earlier version could be run up
 46// arbitrarily by calling renew in a loop within one block. Deriving it
 47// from the registration date makes it unbuyable by construction — the
 48// only way to increase it is for time to pass — and it resets on its own
 49// when a lapsed name is reclaimed, because a reclaim writes a fresh
 50// record with a new `registered`.
 51//
 52// termLength is settable and could legitimately be zero, which would
 53// otherwise divide by zero in a realm that cannot be redeployed.
 54func streakOf(registered, now int64) int64 {
 55	if termLength <= 0 || now <= registered {
 56		return 0
 57	}
 58	return (now - registered) / termLength
 59}
 60
 61// assertIsTrustedLogic is the gate on every mutation function below.
 62// cur.Previous().Address() resolves to the CALLING realm's own address
 63// when invoked via cross(cur) from another realm — same idiom already
 64// used for assertIsAdmin, just checking a different address. A stray
 65// call from anywhere else (a different realm, a direct user tx trying
 66// to call these directly) is rejected.
 67func assertIsTrustedLogic(cur realm) {
 68	// The pause check lives here rather than in each mutation so it can
 69	// never be forgotten on a new one: every write in the realm passes
 70	// through this gate.
 71	if paused {
 72		panic("nsdata: registry is paused")
 73	}
 74	// A WALLET IS NEVER LOGIC, even if somebody trusted one (audit L-4).
 75	// The trust set takes any address, and a wallet granted by mistake —
 76	// or "temporarily", for a migration — could call every write here
 77	// directly, policy-free. A run script carries its signer's address, so
 78	// it is refused too.
 79	if prev := cur.Previous(); prev.IsUser() || prev.IsEphemeral() {
 80		panic("nsdata: trusted logic is a deployed realm, never a wallet or a run script")
 81	}
 82	if trustedLogicSet.Get(cur.Previous().Address().String()) == nil {
 83		panic("nsdata: restricted to a trusted logic realm")
 84	}
 85}
 86
 87// -- key encodings --
 88//
 89// Two DIFFERENT orderings, deliberately, and it matters which is which:
 90//
 91//   storage key : domain + "*" + label   (nameTreeKey)
 92//   token ID    : label  + "*" + domain  (nameTokenID)
 93//
 94// The storage key is DOMAIN-first so that every per-domain query is a
 95// RANGED scan over a contiguous key span instead of a full-tree scan
 96// with a filter. Before this, ListNames/CountNames walked every name in
 97// the registry to answer a question about one domain, which made the
 98// gnoweb home page O(domains × names) — a permanent, unfixable DoS in an
 99// immutable realm once the registry grew. Since this realm is deployed
100// exactly once, the key layout is a forever decision; it is made here.
101//
102// The token ID stays label-first because that is the user-facing
103// identity (`alice*meme`), it is what the NFT exposes to every external
104// tool, and it is what `primary` stores for reverse resolution.
105//
106// Neither a label nor a domain may contain "*" (enforced by
107// validateLabel in the logic realm), so both encodings split
108// unambiguously on the first "*".
109
110func nameTreeKey(label, domain string) string { return domain + "*" + label }
111
112func nameTokenID(label, domain string) string { return label + "*" + domain }
113
114// There is no domainTokenID any more: domains are records, not tokens.
115// See MIGRATION.md, second attempt, rule 7.
116
117// domainRange returns the [start, end) key bounds covering exactly the
118// names stored under `domain`. '+' (0x2B) is the next byte after '*'
119// (0x2A), so `domain+"+"` sorts immediately past every `domain+"*"+...`
120// key while still sorting before any longer domain (e.g. "meme+" falls
121// between "meme*zzz" and "memes*a").
122func domainRange(domain string) (start, end string) {
123	return domain + "*", domain + "+"
124}
125
126// accountKey is the per-address store's key: the address, a colon, the
127// field. A bech32 address never contains ":" and a field may not (see
128// assertAccountKey), so the split is unambiguous, and every field of one
129// address sorts into one span.
130func accountKey(owner address, field string) string { return owner.String() + ":" + field }
131
132// accountRange bounds exactly one address's fields. ';' (0x3B) is the byte
133// after ':' (0x3A), the same trick domainRange plays with '*' and '+'.
134func accountRange(owner address) (start, end string) {
135	s := owner.String()
136	return s + ":", s + ";"
137}
138
139// splitTokenID splits a `label*domain` TOKEN ID. Do not use it on a
140// storage key — see the ordering note above.
141func splitTokenID(tid string) (label, domain string) {
142	for i := 0; i < len(tid); i++ {
143		if tid[i] == '*' {
144			return tid[:i], tid[i+1:]
145		}
146	}
147	return tid, ""
148}
149
150// -- lookups --
151
152func mustGetDomain(label string) *Domain {
153	v := domains.Get(label)
154	if v == nil {
155		panic("nsdata: unknown domain: " + label)
156	}
157	return v.(*Domain)
158}
159
160func getDomain(label string) (*Domain, bool) {
161	v := domains.Get(label)
162	if v == nil {
163		return nil, false
164	}
165	return v.(*Domain), true
166}
167
168func mustGetName(label, domain string) *Name {
169	v := names.Get(nameTreeKey(label, domain))
170	if v == nil {
171		panic("nsdata: unknown name: " + nameTokenID(label, domain))
172	}
173	return v.(*Name)
174}
175
176func getName(label, domain string) (*Name, bool) {
177	v := names.Get(nameTreeKey(label, domain))
178	if v == nil {
179		return nil, false
180	}
181	return v.(*Name), true
182}
183
184// clearPrimaryIfPointingAt drops `owner`'s reverse-resolution pointer
185// when (and only when) it still points at `tid`. Called on every
186// ownership change — transfer and reclaim.
187//
188// Without this, `primary` was a write-only tree: it had a Set path and
189// no Remove path anywhere in the realm, so selling or losing a name left
190// the previous holder permanently reverse-resolving to a name somebody
191// else now owns. Reverse resolution asserting ownership that is no
192// longer true is the one thing a name service must never do.
193func clearPrimaryIfPointingAt(owner address, tid string) {
194	k := owner.String()
195	if v := primary.Get(k); v != nil && v.(string) == tid {
196		primary.Remove(k)
197		chain.Emit("PrimaryCleared", "owner", k, "tid", tid, "auto", "true")
198	}
199}