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

pairreg.gno

6.07 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/moul/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/moul/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/moul/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}