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.

pairreg.gno

6.10 Kb · 191 lines
  1// Package pairreg is the registry every AMM pair instance announces itself to,
  2// and the unified frontend over all of them.
  3//
  4// It is the third artifact of the instance-per-realm pattern: one shared
  5// p/moul/x/pair/v0 holding the logic, N tiny realms holding one pair each, and
  6// this realm knowing all of them.
  7//
  8// # Why registration cannot be faked, and code cannot be trusted
  9//
 10// Register keys an instance by cur.Previous().PkgPath(), which the runtime
 11// supplies, so an instance cannot claim a path that is not its own. That is
 12// the whole on-chain guarantee. It is NOT a guarantee about the code at that
 13// path: gno has no way to read another package's source from inside a realm,
 14// so nothing here can check that an instance really runs the shared template.
 15// The mirror image of Ethereum's CREATE2, which proves the code and says
 16// nothing legible about who deployed it.
 17//
 18// Consequences a reader of the index must keep in mind:
 19//
 20//   - Reserves shown here are CLAIMED. They are read live from the instance's
 21//     own struct, which is honest for an instance running the template and
 22//     arbitrary for one that does not.
 23//   - Several instances may exist for the same token couple. That is allowed
 24//     on purpose; there is no canonical pair and no factory to enforce one.
 25//   - Approving tokens to an instance risks exactly what was approved. Read
 26//     the instance's source (vm/qfile on its path) before funding it.
 27//
 28// # Why the registry can read live state at all
 29//
 30// Instances register a *pair.Pair pointer, not an address, the way grc20reg
 31// registers a *grc20.Token. One vm/qrender here therefore renders the real
 32// state of every instance, with no indexer and no multicall. It is safe
 33// because pair.Pair holds only concrete types: an interface or a func field
 34// would let a hostile instance hand foreign code to this realm's frame.
 35package pairreg
 36
 37import (
 38	"chain"
 39	"chain/runtime"
 40	"strings"
 41
 42	"gno.land/p/nym-moultest123/x/pair/v0"
 43	"gno.land/p/nt/avl/v0"
 44	"gno.land/p/nt/ufmt/v0"
 45)
 46
 47// Entry is one registered instance.
 48type Entry struct {
 49	Path   string     // the instance realm's package path, supplied by the runtime
 50	Pair   *pair.Pair // live pointer into the instance's own storage
 51	Height int64      // chain height at registration
 52}
 53
 54// entries maps instance path -> *Entry. couples maps a pair ID
 55// ("keyA~keyB") -> int, how many instances claim that couple.
 56var (
 57	entries avl.Tree
 58	couples avl.Tree
 59)
 60
 61// Register records the calling realm as an instance. Call it from the
 62// instance's init, so that deploying and listing are one transaction:
 63//
 64//	func init(cur realm) {
 65//		p = pair.New(keyA, keyB, grc20reg.MustGet(keyA), grc20reg.MustGet(keyB))
 66//		pairreg.Register(cross(cur), p)
 67//	}
 68//
 69// Permissionless by design: anyone may deploy an instance under their own
 70// address namespace and land here. Spam is self funded, since the registering
 71// transaction pays for the storage it adds.
 72func Register(cur realm, p *pair.Pair) {
 73	caller := cur.Previous()
 74	path := caller.PkgPath()
 75	if path == "" {
 76		panic("pairreg: only a realm can register")
 77	}
 78	if p == nil {
 79		panic("pairreg: nil pair")
 80	}
 81	if entries.Has(path) {
 82		panic("pairreg: already registered: " + path)
 83	}
 84
 85	entries.Set(path, &Entry{Path: path, Pair: p, Height: runtime.ChainHeight()})
 86	couples.Set(p.ID(), countFor(p.ID())+1)
 87
 88	chain.Emit("RegisterPair",
 89		"path", path,
 90		"pair", p.ID(),
 91	)
 92}
 93
 94// Get returns the entry for an instance path, or nil.
 95func Get(path string) *Entry {
 96	v := entries.Get(path)
 97	if v == nil {
 98		return nil
 99	}
100	return v.(*Entry)
101}
102
103// Size returns how many instances are registered.
104func Size() int { return entries.Size() }
105
106// Couples returns how many distinct token couples are represented.
107func Couples() int { return couples.Size() }
108
109// InstancesFor returns the instance paths claiming a given couple id.
110func InstancesFor(id string) []string {
111	out := []string{}
112	entries.Iterate("", "", func(_ string, value any) bool {
113		e := value.(*Entry)
114		if e.Pair.ID() == id {
115			out = append(out, e.Path)
116		}
117		return false
118	})
119	return out
120}
121
122// Render is the unified frontend: the index of every instance with its live
123// state, or one instance's own page when path is an instance path.
124func Render(path string) string {
125	if path != "" {
126		e := Get(path)
127		if e == nil {
128			return "# 404\n\nNo instance registered at `" + path + "`.\n"
129		}
130		out := e.Pair.Render("")
131		out += "\n---\n\n"
132		out += ufmt.Sprintf("Instance: [`%s`](%s) · registered at height %d · %d instance(s) claim this couple.\n",
133			e.Path, webPath(e.Path), e.Height, countFor(e.Pair.ID()))
134		return out
135	}
136
137	out := "# AMM pairs\n\n"
138	out += "One realm per token couple, all running [p/moul/x/pair/v0](/p/nym-moultest123/x/pair/v0). "
139	out += "Anyone can deploy one under their own namespace and it lands here.\n\n"
140
141	if entries.Size() == 0 {
142		out += "_No instance yet._\n"
143		return out
144	}
145
146	out += ufmt.Sprintf("%d instance(s), %d couple(s).\n\n", entries.Size(), couples.Size())
147	out += "**Reserves below are claimed by each instance, not verified.** "
148	out += "Nothing on chain can check that an instance runs the shared template; read its source before funding it.\n\n"
149	out += "| pair | reserves | LP shares | providers | instance |\n"
150	out += "|---|---|---|---|---|\n"
151	entries.Iterate("", "", func(key string, value any) bool {
152		e := value.(*Entry)
153		symA, symB := e.Pair.Symbols()
154		resA, resB := e.Pair.Reserves()
155		out += ufmt.Sprintf("| [%s/%s](/r/nym-moultest123/x/pairreg/v0:%s) | %d %s / %d %s | %d | %d | [`%s`](%s) |\n",
156			symA, symB, key,
157			resA, symA, resB, symB,
158			e.Pair.TotalShares(), e.Pair.Providers(),
159			shortPath(e.Path), webPath(e.Path))
160		return false
161	})
162	return out
163}
164
165//
166// Internals.
167//
168
169func countFor(id string) int {
170	v := couples.Get(id)
171	if v == nil {
172		return 0
173	}
174	return v.(int)
175}
176
177// webPath turns a package path into a gnoweb link.
178func webPath(pkgPath string) string {
179	if i := strings.Index(pkgPath, "/"); i >= 0 {
180		return pkgPath[i:]
181	}
182	return "/" + pkgPath
183}
184
185// shortPath drops the chain domain, which is the same on every row.
186func shortPath(pkgPath string) string {
187	if i := strings.Index(pkgPath, "/"); i >= 0 {
188		return pkgPath[i+1:]
189	}
190	return pkgPath
191}