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.18 Kb · 192 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/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/md/v0"
 43	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/pair/v0"
 44	"gno.land/p/nt/avl/v0"
 45	"gno.land/p/nt/ufmt/v0"
 46)
 47
 48// Entry is one registered instance.
 49type Entry struct {
 50	Path   string     // the instance realm's package path, supplied by the runtime
 51	Pair   *pair.Pair // live pointer into the instance's own storage
 52	Height int64      // chain height at registration
 53}
 54
 55// entries maps instance path -> *Entry. couples maps a pair ID
 56// ("keyA~keyB") -> int, how many instances claim that couple.
 57var (
 58	entries avl.Tree
 59	couples avl.Tree
 60)
 61
 62// Register records the calling realm as an instance. Call it from the
 63// instance's init, so that deploying and listing are one transaction:
 64//
 65//	func init(cur realm) {
 66//		p = pair.New(keyA, keyB, grc20reg.MustGet(keyA), grc20reg.MustGet(keyB))
 67//		pairreg.Register(cross(cur), p)
 68//	}
 69//
 70// Permissionless by design: anyone may deploy an instance under their own
 71// address namespace and land here. Spam is self funded, since the registering
 72// transaction pays for the storage it adds.
 73func Register(cur realm, p *pair.Pair) {
 74	caller := cur.Previous()
 75	path := caller.PkgPath()
 76	if path == "" {
 77		panic("pairreg: only a realm can register")
 78	}
 79	if p == nil {
 80		panic("pairreg: nil pair")
 81	}
 82	if entries.Has(path) {
 83		panic("pairreg: already registered: " + path)
 84	}
 85
 86	entries.Set(path, &Entry{Path: path, Pair: p, Height: runtime.ChainHeight()})
 87	couples.Set(p.ID(), countFor(p.ID())+1)
 88
 89	chain.Emit("RegisterPair",
 90		"path", path,
 91		"pair", p.ID(),
 92	)
 93}
 94
 95// Get returns the entry for an instance path, or nil.
 96func Get(path string) *Entry {
 97	v := entries.Get(path)
 98	if v == nil {
 99		return nil
100	}
101	return v.(*Entry)
102}
103
104// Size returns how many instances are registered.
105func Size() int { return entries.Size() }
106
107// Couples returns how many distinct token couples are represented.
108func Couples() int { return couples.Size() }
109
110// InstancesFor returns the instance paths claiming a given couple id.
111func InstancesFor(id string) []string {
112	out := []string{}
113	entries.Iterate("", "", func(_ string, value any) bool {
114		e := value.(*Entry)
115		if e.Pair.ID() == id {
116			out = append(out, e.Path)
117		}
118		return false
119	})
120	return out
121}
122
123// Render is the unified frontend: the index of every instance with its live
124// state, or one instance's own page when path is an instance path.
125func Render(path string) string {
126	if path != "" {
127		e := Get(path)
128		if e == nil {
129			return "# 404\n\nNo instance registered at " + md.InlineCode(path) + ".\n"
130		}
131		out := e.Pair.Render("")
132		out += "\n---\n\n"
133		out += ufmt.Sprintf("Instance: [`%s`](%s) · registered at height %d · %d instance(s) claim this couple.\n",
134			e.Path, webPath(e.Path), e.Height, countFor(e.Pair.ID()))
135		return out
136	}
137
138	out := "# AMM pairs\n\n"
139	out += "One realm per token couple, all running [p/moul/x/pair/v0](/p/moul/x/pair/v0). "
140	out += "Anyone can deploy one under their own namespace and it lands here.\n\n"
141
142	if entries.Size() == 0 {
143		out += "_No instance yet._\n"
144		return out
145	}
146
147	out += ufmt.Sprintf("%d instance(s), %d couple(s).\n\n", entries.Size(), couples.Size())
148	out += "**Reserves below are claimed by each instance, not verified.** "
149	out += "Nothing on chain can check that an instance runs the shared template; read its source before funding it.\n\n"
150	out += "| pair | reserves | LP shares | providers | instance |\n"
151	out += "|---|---|---|---|---|\n"
152	entries.Iterate("", "", func(key string, value any) bool {
153		e := value.(*Entry)
154		symA, symB := e.Pair.Symbols()
155		resA, resB := e.Pair.Reserves()
156		out += ufmt.Sprintf("| [%s/%s](/r/moul/x/pairreg/v0:%s) | %d %s / %d %s | %d | %d | [`%s`](%s) |\n",
157			symA, symB, key,
158			resA, symA, resB, symB,
159			e.Pair.TotalShares(), e.Pair.Providers(),
160			shortPath(e.Path), webPath(e.Path))
161		return false
162	})
163	return out
164}
165
166//
167// Internals.
168//
169
170func countFor(id string) int {
171	v := couples.Get(id)
172	if v == nil {
173		return 0
174	}
175	return v.(int)
176}
177
178// webPath turns a package path into a gnoweb link.
179func webPath(pkgPath string) string {
180	if i := strings.Index(pkgPath, "/"); i >= 0 {
181		return pkgPath[i:]
182	}
183	return "/" + pkgPath
184}
185
186// shortPath drops the chain domain, which is the same on every row.
187func shortPath(pkgPath string) string {
188	if i := strings.Index(pkgPath, "/"); i >= 0 {
189		return pkgPath[i+1:]
190	}
191	return pkgPath
192}