read.gno
8.65 Kb · 246 lines
1package nsdata
2
3import (
4 "strings"
5 "time"
6
7 "gno.land/r/nym-mnsrehearsal001/nsnft/v2"
8)
9
10// Plain reads — no `cur realm` parameter, no authentication, since there
11// is no caller identity to check on a read. Everything here returns raw
12// stored values; formatting them for display is the view realm's job,
13// not the vault's. (Status strings like "VALID (363 days left)" used to
14// live here. They were display logic in a realm that can never be
15// redeployed, so they moved to nsview.)
16
17func ListDomains() string {
18 var b strings.Builder
19 first := true
20 domains.Iterate("", "", func(key string, v any) bool {
21 if !first {
22 b.WriteString(",")
23 }
24 b.WriteString(key)
25 first = false
26 return false
27 })
28 return b.String()
29}
30
31// ListNames and CountNames scan only the key span belonging to
32// `domainLabel` (see domainRange in auth.gno), costing O(names in that
33// domain) rather than O(every name in the registry).
34func ListNames(domainLabel string) string {
35 var b strings.Builder
36 first := true
37 start, end := domainRange(domainLabel)
38 // The label is the part of the key after "domain*". Records no
39 // longer carry their own label — the key already held it, and
40 // storing it twice cost storage forever to say the same thing.
41 cut := len(domainLabel) + 1
42 names.Iterate(start, end, func(key string, v any) bool {
43 if !first {
44 b.WriteString(",")
45 }
46 b.WriteString(key[cut:])
47 first = false
48 return false
49 })
50 return b.String()
51}
52
53func CountNames(domainLabel string) int64 {
54 var count int64
55 start, end := domainRange(domainLabel)
56 names.Iterate(start, end, func(key string, v any) bool {
57 count++
58 return false
59 })
60 return count
61}
62
63func CountDomains() int64 { return int64(domains.Size()) }
64
65func DomainExists(label string) bool { _, ok := getDomain(label); return ok }
66func NameExists(label, domainLabel string) bool { _, ok := getName(label, domainLabel); return ok }
67
68func GetDomainOwner(label string) address { return mustGetDomain(label).owner }
69
70// GetNameOwner asks the ledger. It still panics for an unknown NAME, as it
71// always did, so callers that relied on that are unchanged.
72func GetNameOwner(label, domainLabel string) address {
73 mustGetName(label, domainLabel)
74 return nsnft.OwnerOf(nameTokenID(label, domainLabel))
75}
76
77// GetDomainInfo / GetNameInfo return a whole record in one call, so a
78// view realm can render a page without six round trips.
79// Timestamps come back as unix SECONDS. They used to be time.Time,
80// which vm/qeval rendered using gno's internal epoch, so every consumer
81// had to know to subtract 62135596800 — our own frontend did exactly
82// that. Returning unix seconds stops leaking that offset into every
83// integration, and it is what the records now hold anyway.
84//
85// `streak` is computed from the registration date rather than read from
86// a stored field. See streakOf.
87func GetDomainInfo(label string) (owner address, registered, expires, streak int64, frozen bool) {
88 d := mustGetDomain(label)
89 return d.owner, d.registered, d.expires, streakOf(d.registered, time.Now().Unix()), d.frozen
90}
91
92/*
93GetNameInfo keeps its six-value shape so every existing reader — nslogic,
94nsview, nsskin, nsvote, the site, both snapshot tools — goes on parsing it
95unchanged. Two of the six are now answered differently:
96
97 owner is read from nsnft, never stored here. The snapshot tools record
98 whatever this returns, so they now record the real owner — the
99 restore-hands-sold-names-back bug the first attempt had cannot
100 happen, because there is no stale copy for them to read.
101
102 paid replaces isNFT and means exactly one thing: the name is NOT
103 held as an un-upgraded free-tier record. It is deliberately not
104 "transferable" — a frozen paid name is locked too, and reading
105 the lock would have made a frozen paid name look free, which is
106 precisely the proxy bug that let free renewals renew paid names.
107*/
108func GetNameInfo(label, domainLabel string) (owner address, registered, expires, streak int64, frozen, paid bool) {
109 n := mustGetName(label, domainLabel)
110 tid := nameTokenID(label, domainLabel)
111 return nsnft.OwnerOf(tid), n.registered, n.expires,
112 streakOf(n.registered, time.Now().Unix()), n.frozen,
113 nsnft.LockBits(tid)&(1<<lockBitFree) == 0
114}
115
116// GetNameTTL / GetDomainTTL report how long a resolver may cache an
117// answer about this record, in seconds, where 0 MEANS NEVER CACHE — the
118// ENS registry's rule, deliberately, so integrators need not read
119// anything of ours to handle it correctly.
120// A TTL written for a previous holder is not this holder's cache policy.
121// A stale one reads as 0 — never cache — which is the safe answer during
122// exactly the window after a sale when a cached answer pays the seller.
123func GetNameTTL(label, domainLabel string) int64 {
124 n := mustGetName(label, domainLabel)
125 if !isFresh(n, nameTokenID(label, domainLabel)) {
126 return 0
127 }
128 return n.ttl
129}
130func GetDomainTTL(label string) int64 { return mustGetDomain(label).ttl }
131
132// GetNameHeldSince is when the current holder's holding began, in unix
133// seconds — or 0 when the ledger has moved on since this registry last
134// saw the record (a sale it has not yet observed). It can understate a
135// holding, never overstate one. See Name.since.
136func GetNameHeldSince(label, domainLabel string) int64 {
137 n := mustGetName(label, domainLabel)
138 if !isFresh(n, nameTokenID(label, domainLabel)) {
139 return 0
140 }
141 return n.since
142}
143
144// RestoredFor reports whom a restore wrote this record for, or empty.
145// See Name.restoredFor.
146func RestoredFor(label, domainLabel string) address {
147 return mustGetName(label, domainLabel).restoredFor
148}
149
150// IsNamePaid reports that a name is not held as an un-upgraded free-tier
151// record. See GetNameInfo on why this is not "transferable".
152func IsNamePaid(label, domainLabel string) bool {
153 mustGetName(label, domainLabel)
154 return nsnft.LockBits(nameTokenID(label, domainLabel))&(1<<lockBitFree) == 0
155}
156
157func IsDomainValid(label string) bool {
158 d, ok := getDomain(label)
159 return ok && isValidNow(d.expires, time.Now().Unix())
160}
161
162func IsNameValid(label, domainLabel string) bool {
163 n, ok := getName(label, domainLabel)
164 return ok && isValidNow(n.expires, time.Now().Unix())
165}
166
167// -- extension slots --
168
169func GetDomainExtra(label, key string) string {
170 v := mustGetDomain(label).extra.Get(key)
171 if v == nil {
172 return ""
173 }
174 return v.(string)
175}
176
177/*
178GetNameExtra answers EMPTY for data written in a previous ownership epoch.
179
180This is the read half of the lazy wipe. A sale that settles on nsnft —
181through our marketplace or anybody else's — never calls this realm, so
182nothing here can clear the seller's payment addresses at the moment it
183happens. What this CAN do is refuse to repeat them: the instant the
184ledger's epoch moves, every extra on the name stops answering, for every
185reader, before anybody has written anything.
186
187The write half, which destroys the stale data rather than hiding it, is
188freshen in write.gno. Both halves are needed: hiding alone is what the
189first attempt shipped, and the buyer's first edit brought the seller's
190Bitcoin address back as the buyer's.
191*/
192func GetNameExtra(label, domainLabel, key string) string {
193 n := mustGetName(label, domainLabel)
194 if !isFresh(n, nameTokenID(label, domainLabel)) {
195 return ""
196 }
197 v := n.extra.Get(key)
198 if v == nil {
199 return ""
200 }
201 return v.(string)
202}
203
204func GetGlobal(key string) string {
205 v := globals.Get(key)
206 if v == nil {
207 return ""
208 }
209 return v.(string)
210}
211
212// GetAccount reads one field of an address's own record, or "" when it
213// has none. See `accounts` in types.gno.
214func GetAccount(owner address, key string) string {
215 v := accounts.Get(accountKey(owner, key))
216 if v == nil {
217 return ""
218 }
219 return v.(string)
220}
221
222// GetPrimary is a raw pointer lookup. It reports what an address last
223// SELECTED, not what it still owns — callers presenting this as reverse
224// resolution must re-check ownership (see nslogic.ResolveAddress).
225func GetPrimary(owner address) string {
226 v := primary.Get(owner.String())
227 if v == nil {
228 return ""
229 }
230 return v.(string)
231}
232
233// -- config reads --
234
235func GetTreasury() address { return treasury }
236func GetTermLength() int64 { return termLength }
237func GetGracePeriod() int64 { return gracePeriod }
238func GetMaxDeletePerCall() int { return maxDeletePerCall }
239func GetLabelPattern() string { return labelPattern }
240func GetLabelLength() (min, max int) { return minLabelLen, maxLabelLen }
241func GetTokenURIBase() string { return tokenURIBase }
242func GetExtraLimits() (k, v, entries int) { return maxExtraKeyLen, maxExtraValueLen, maxExtraEntries }
243func GetGlobalEntryLimit() int { return maxGlobalEntries }
244func GetAccountLimits() (k, v, entries int) {
245 return maxAccountKeyLen, maxAccountValueLen, maxAccountEntries
246}