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.

nativereg.gno

6.41 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/nym-moultest123/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}