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

nativereg.gno

6.40 Kb · 196 lines
  1// Package nativereg is the metadata a native coin does not have.
  2//
  3// A realm-issued coin is a string and a balance. `/gno.land/r/moul/x/moultest/v0:moultest`
  4// is the entire object: no name, no symbol, no decimals, no icon, no link to
  5// whoever issues it. The bank knows the number and nothing else, so a wallet or
  6// an explorer showing you a balance has only that path to print.
  7//
  8// This realm is where a denom says what it is. It is the mirror image of
  9// `r/nt/grc20reg`, and the two solve opposite problems: a GRC20 is an object
 10// that carries its own name and is hard to FIND, so the registry maps a key to
 11// the object; a native denom is trivial to find and carries no name, so the
 12// registry maps the denom to what it means.
 13//
 14// # Registration is proved, not claimed
 15//
 16// A denom embeds the package path of the realm allowed to issue it, verbatim:
 17// `"/" + pkgPath + ":" + baseName` (`chain.CoinDenom`). So the issuer is
 18// readable from the denom, and [Register] simply requires the caller to be it.
 19// A realm registers its own coins and nobody else's, with no allowlist, no
 20// owner and no signature scheme.
 21//
 22// A user account cannot register anything, not even a denom it holds all of:
 23// there is no package path in a user call to compare against. That is the
 24// intended shape, since the thing being described is the issuance, not the
 25// holding.
 26//
 27// # What is a hint and what is a fact
 28//
 29// `Denom` and `Issuer` are facts: the chain enforces the relation between them.
 30// Everything else is whatever the issuing realm chose to say, and `Decimals` in
 31// particular is a DISPLAY hint with nothing behind it. The bank has no notion of
 32// divisibility; a coin wrapped out of a 6-decimal GRC20 is still counted in
 33// whole units by every balance query. Rendering 1000000 as 1.000000 is a
 34// convention between this realm and whoever reads it.
 35//
 36// So trusting an entry means trusting the realm that wrote it, exactly as much
 37// as holding its coin already means trusting it not to call `RemoveCoin` on you.
 38package nativereg
 39
 40import (
 41	"chain/runtime"
 42	"strings"
 43
 44	"gno.land/p/nt/avl/v0"
 45	"gno.land/p/nt/ufmt/v0"
 46)
 47
 48// Entry is what one denom says about itself.
 49type Entry struct {
 50	// Denom is the full chain denom, the key of this registry.
 51	Denom string
 52	// Issuer is the package path embedded in Denom: the only realm that can
 53	// issue, remove or re-describe this coin.
 54	Issuer string
 55	// Name, Symbol and Decimals are display metadata, chosen by the issuer.
 56	// Decimals is a hint; see the package doc.
 57	Name     string
 58	Symbol   string
 59	Decimals int
 60	// Doc is one line about what the coin is for.
 61	Doc string
 62	// Height is when the entry was last written.
 63	Height int64
 64	// Revisions counts how many times the issuer has rewritten it.
 65	Revisions int
 66}
 67
 68const (
 69	// MaxName, MaxSymbol and MaxDoc bound what an issuer can store here. The
 70	// registry pays the storage for every byte a caller hands it, so the
 71	// limits are the realm's own cost control, not a style rule.
 72	MaxName   = 40
 73	MaxSymbol = 12
 74	MaxDoc    = 200
 75	// MaxDecimals matches the GRC20 ceiling, which is where a wrapped coin's
 76	// hint comes from in practice.
 77	MaxDecimals = 18
 78)
 79
 80var (
 81	byDenom = avl.NewTree() // denom -> *Entry
 82	denoms  []string        // registration order, for a stable Render
 83)
 84
 85// Register records or rewrites what a denom means. The caller must be the realm
 86// the denom names, which is the entire authorization.
 87//
 88// Calling it again overwrites the entry and bumps [Entry.Revisions]. An issuer
 89// can always re-describe its own coin, and can never describe anybody else's.
 90func Register(cur realm, denom, name, symbol string, decimals int, doc string) {
 91	issuer := issuerOf(denom)
 92	prev := cur.Previous()
 93	if prev.PkgPath() == "" {
 94		panic("only the issuing realm can register a denom; a user account has no package path to prove")
 95	}
 96	if prev.PkgPath() != issuer {
 97		panic(ufmt.Sprintf("%s is issued by %s, not by %s", denom, issuer, prev.PkgPath()))
 98	}
 99	assertLen("name", name, MaxName)
100	assertLen("symbol", symbol, MaxSymbol)
101	assertLen("doc", doc, MaxDoc)
102	if decimals < 0 || decimals > MaxDecimals {
103		panic(ufmt.Sprintf("decimals is %d, must be 0 to %d", decimals, MaxDecimals))
104	}
105
106	e := &Entry{
107		Denom:    denom,
108		Issuer:   issuer,
109		Name:     name,
110		Symbol:   symbol,
111		Decimals: decimals,
112		Doc:      doc,
113		Height:   runtime.ChainHeight(),
114	}
115	if old := byDenom.Get(denom); old != nil {
116		e.Revisions = old.(*Entry).Revisions + 1
117	} else {
118		denoms = append(denoms, denom)
119	}
120	byDenom.Set(denom, e)
121}
122
123// Get returns the entry for a denom, and whether it has one.
124func Get(denom string) (Entry, bool) {
125	v := byDenom.Get(denom)
126	if v == nil {
127		return Entry{}, false
128	}
129	return *v.(*Entry), true
130}
131
132// MustGet is [Get] for a caller that treats an unregistered denom as a bug.
133func MustGet(denom string) Entry {
134	e, ok := Get(denom)
135	if !ok {
136		panic("no entry for " + denom)
137	}
138	return e
139}
140
141// IsRegistered reports whether a denom has said anything about itself.
142func IsRegistered(denom string) bool {
143	return byDenom.Get(denom) != nil
144}
145
146// List returns every entry, in registration order.
147func List() []Entry {
148	out := make([]Entry, 0, len(denoms))
149	for _, d := range denoms {
150		out = append(out, MustGet(d))
151	}
152	return out
153}
154
155// ByIssuer returns every entry issued by one realm, in registration order.
156func ByIssuer(pkgPath string) []Entry {
157	out := []Entry{}
158	for _, d := range denoms {
159		if e := MustGet(d); e.Issuer == pkgPath {
160			out = append(out, e)
161		}
162	}
163	return out
164}
165
166// Count returns how many denoms are registered.
167func Count() int { return byDenom.Size() }
168
169// IssuerOf returns the package path embedded in a denom, without consulting the
170// registry: the relation is in the string itself. It aborts on anything that is
171// not a realm denom, which includes `ugnot`.
172//
173// It is exported because it is useful to anybody handling a denom, registered or
174// not: given a balance, this is how you find the code that can take it away.
175func IssuerOf(denom string) string { return issuerOf(denom) }
176
177func issuerOf(denom string) string {
178	if !strings.HasPrefix(denom, "/") {
179		panic("not a realm denom (it must start with /): " + denom)
180	}
181	i := strings.LastIndex(denom, ":")
182	if i < 0 {
183		panic("not a realm denom (it must contain :): " + denom)
184	}
185	issuer := denom[1:i]
186	if issuer == "" || denom[i+1:] == "" {
187		panic("malformed denom: " + denom)
188	}
189	return issuer
190}
191
192func assertLen(what, s string, max int) {
193	if len(s) > max {
194		panic(ufmt.Sprintf("%s is %d bytes, max %d", what, len(s), max))
195	}
196}