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.

wbubble.gno

9.04 Kb · 212 lines
  1// Package wbubble is wrapped BUBBLE: the Bubble Rumble token as a registered GRC20, backed one to one by BUBBLE
  2// held in this realm. It exists for gnoswap, which pools only tokens registered in gno.land/r/nt/grc20reg/v0 —
  3// and the registry takes only a *grc20.Token made by grc20.NewToken inside the registering realm. BUBBLE
  4// (gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubble) keeps its ledger in kourt's grc20votes, not a
  5// grc20.Token, and a realm cannot be redeployed; so this realm holds BUBBLE and issues a registered token for
  6// it, the way gno.land/r/gnoland/wugnot wraps ugnot (ref/ holds the mainnet sources this mirrors).
  7//
  8// Wrap pulls BUBBLE from the caller — an allowance the caller granted THIS realm on the bubble realm, spent by
  9// this realm as itself — and mints the same amount of wrapped BUBBLE to the caller. Unwrap burns the caller's
 10// wrapped BUBBLE and sends the same amount of BUBBLE back. Nothing else mints or burns, and nothing here is a
 11// setting: no admin, no pause, no minter. The reserve (this realm's BUBBLE balance) covers the wrapped supply
 12// after every call, and a call that would leave it short aborts.
 13//
 14// Votes. BUBBLE carries a vote in its own realm; wrapped BUBBLE carries none. The BUBBLE held here is
 15// self-delegated, inside the bubble ledger, to this realm's own address — the ledger's default for every holder —
 16// and this realm never delegates it or votes with it, so that weight is never cast. Unwrap to vote.
 17package wbubble
 18
 19import (
 20	"chain"
 21	"strconv"
 22	"strings"
 23
 24	"gno.land/p/nt/grc20/v0"
 25	"gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/bubble"
 26	"gno.land/r/nt/grc20reg/v0"
 27)
 28
 29// Token is the registered token, exported as wugnot exports its own: a *grc20.Token carries no spend
 30// authority (its tellers act only for the realm that builds them, and only with an allowance), and the registry
 31// hands the same pointer to anyone. ledger is the mint and burn, and it never leaves this package.
 32var (
 33	Token  *grc20.Token
 34	ledger *grc20.PrivateLedger
 35)
 36
 37const (
 38	tokenName     = "Bubble Rumble BUBBLE (wrapped)"
 39	tokenSymbol   = "BUBBLE"
 40	tokenDecimals = 6
 41	pkgPath       = "gno.land/r/g1leu8d2vsplhehcfkjg50mwgdpxdkt8tztu95wr/wbubble" // must match gnomod.toml
 42	// RegistryKey is what gno.land/r/nt/grc20reg/v0 knows this token as: the realm path and the symbol.
 43	RegistryKey = pkgPath + "." + tokenSymbol
 44
 45	one = int64(1_000_000) // one BUBBLE in base units
 46)
 47
 48func init(cur realm) {
 49	// wbubble only ever creates this one token, so id 0 can't collide.
 50	Token, ledger = grc20.NewToken(tokenName, tokenSymbol, tokenDecimals, 0, cur)
 51	if key := grc20reg.Register(cross(cur), Token, ""); key != RegistryKey {
 52		panic("wbubble: registered as " + key + ", not " + RegistryKey + ": the package path is not the one in the source")
 53	}
 54}
 55
 56// Address is this realm's own address: the spender to approve on the bubble realm before Wrap, and the holder of
 57// the reserve.
 58func Address() address { return chain.PackageAddress(pkgPath) }
 59
 60// Reserve is the BUBBLE this realm holds. It covers TotalSupply after every call (checkReserve).
 61func Reserve() int64 { return bubble.BalanceOf(Address()) }
 62
 63// Wrap takes amount BUBBLE from the caller and mints as much wrapped BUBBLE to them. First, on the bubble realm,
 64// the caller approves THIS realm for the amount (bubble.Approve with spender Address()): bubble reads its own
 65// caller as the spender, and its caller here is this realm, not the user. A failure inside bubble panics across
 66// the realm boundary and aborts the whole transaction, so nothing is minted for BUBBLE that did not arrive.
 67func Wrap(cur realm, amount int64) {
 68	require(amount > 0, "wbubble: the amount must be positive")
 69	// The security guide's rule (gno-security-guide.md §5.6, §8): read the caller from cur only after IsCurrent.
 70	// Under interrealm v2 (gno-interrealm-v2.md §5.2) a crossing function's own cur is always current, so this
 71	// refuses nobody; it is kept so the realm meets both documents.
 72	if !cur.IsCurrent() {
 73		panic("wbubble: spoofed realm")
 74	}
 75	caller := cur.Previous().Address()
 76	self := cur.Address()
 77	before := bubble.BalanceOf(self)
 78	bubble.TransferFrom(cross(cur), caller, self, amount)
 79	checkErr(ledger.Mint(caller, amount))
 80	checkReserve(self, before+amount)
 81}
 82
 83// Unwrap burns amount of the caller's wrapped BUBBLE and sends them as much BUBBLE. The burn goes first: a caller
 84// without the wrapped tokens is refused before anything moves.
 85func Unwrap(cur realm, amount int64) {
 86	require(amount > 0, "wbubble: the amount must be positive")
 87	if !cur.IsCurrent() { // see Wrap
 88		panic("wbubble: spoofed realm")
 89	}
 90	caller := cur.Previous().Address()
 91	self := cur.Address()
 92	before := bubble.BalanceOf(self)
 93	checkErr(ledger.Burn(caller, amount))
 94	bubble.Transfer(cross(cur), caller, amount)
 95	checkReserve(self, before-amount)
 96}
 97
 98// checkReserve is the invariant after every move: the reserve moved by exactly the amount, and it covers the
 99// supply. Not `reserve == supply`: anyone can send BUBBLE straight to this realm's address, and a wrapper that
100// demanded equality would then refuse every Wrap and Unwrap for good. Such a gift only puts the reserve over the
101// supply; it belongs to nobody and stays.
102func checkReserve(self address, want int64) {
103	have := bubble.BalanceOf(self)
104	if have != want {
105		panic("wbubble: the reserve moved by the wrong amount")
106	}
107	if have < Token.TotalSupply() {
108		panic("wbubble: the reserve is short of the wrapped supply")
109	}
110}
111
112// ------------------------------------------------------------ the GRC20 --
113//
114// wugnot's surface, line for line: the reads on the Token, the writes through the ledger's caller teller, which
115// acts for whoever crossed into this realm and works nowhere but here.
116
117func TotalSupply() int64 {
118	return Token.TotalSupply()
119}
120
121func BalanceOf(owner address) int64 {
122	return Token.BalanceOf(owner)
123}
124
125func Allowance(owner, spender address) int64 {
126	return Token.Allowance(owner, spender)
127}
128
129func Transfer(cur realm, to address, amount int64) {
130	userTeller := ledger.CallerTeller()
131	checkErr(userTeller.Transfer(0, cur, to, amount))
132}
133
134func Approve(cur realm, spender address, amount int64) {
135	userTeller := ledger.CallerTeller()
136	checkErr(userTeller.Approve(0, cur, spender, amount))
137}
138
139func TransferFrom(cur realm, from, to address, amount int64) {
140	userTeller := ledger.CallerTeller()
141	checkErr(userTeller.TransferFrom(0, cur, from, to, amount))
142}
143
144// ----------------------------------------------------------------- pages --
145
146// Render: "" the token's page; "balance/<addr>" one wrapped balance as a number (wugnot's path); "json" the
147// figures for the box.
148func Render(path string) string {
149	parts := strings.Split(path, "/")
150	switch {
151	case path == "":
152		return home()
153	case path == "json":
154		return json()
155	case len(parts) == 2 && parts[0] == "balance":
156		return strconv.FormatInt(Token.BalanceOf(address(parts[1])), 10)
157	default:
158		return "404"
159	}
160}
161
162func home() string {
163	self := Address()
164	supply, reserve := Token.TotalSupply(), bubble.BalanceOf(self)
165	var b strings.Builder
166	b.WriteString("Wrapped BUBBLE, symbol " + tokenSymbol + ", " + strconv.Itoa(tokenDecimals) + " decimals: the Bubble Rumble token as a registered GRC20, for gnoswap and every realm that reads the token registry.\n\n")
167	b.WriteString("Wrapped in circulation: " + amount(supply) + " BUBBLE. Reserve held by this realm at " + self.String() + ": " + amount(reserve) + " BUBBLE")
168	switch {
169	case reserve == supply:
170		b.WriteString(", exactly the supply.\n\n")
171	case reserve > supply:
172		b.WriteString(", " + amount(reserve-supply) + " over the supply (BUBBLE sent here directly; it belongs to nobody).\n\n")
173	default:
174		b.WriteString(" — SHORT of the supply by " + amount(supply-reserve) + "; this must never print.\n\n")
175	}
176	b.WriteString("Registry key: " + RegistryKey + " (gno.land/r/nt/grc20reg/v0).\n\n")
177	b.WriteString("To wrap: on the bubble realm call Approve with spender " + self.String() + " and the amount in base units, then call Wrap here with the same amount.\n")
178	b.WriteString("To unwrap: call Unwrap here with the amount; the BUBBLE comes back to your address.\n\n")
179	b.WriteString("Votes: BUBBLE carries a vote in its own realm and wrapped BUBBLE carries none. What this realm holds is self-delegated to the realm's own address inside the bubble ledger and is never cast. Unwrap to vote.\n")
180	return b.String()
181}
182
183func json() string {
184	return `{"name":"` + tokenName + `","symbol":"` + tokenSymbol + `","decimals":` + strconv.Itoa(tokenDecimals) +
185		`,"supply":` + strconv.FormatInt(Token.TotalSupply(), 10) + `,"reserve":` + strconv.FormatInt(Reserve(), 10) +
186		`,"address":"` + Address().String() + `","key":"` + RegistryKey + `"}`
187}
188
189// amount prints base units as BUBBLE with up to six decimals, trailing zeros dropped.
190func amount(n int64) string {
191	whole, frac := n/one, n%one
192	if frac == 0 {
193		return strconv.FormatInt(whole, 10)
194	}
195	f := strconv.FormatInt(frac, 10)
196	for len(f) < tokenDecimals {
197		f = "0" + f
198	}
199	return strconv.FormatInt(whole, 10) + "." + strings.TrimRight(f, "0")
200}
201
202func require(condition bool, msg string) {
203	if !condition {
204		panic(msg)
205	}
206}
207
208func checkErr(err error) {
209	if err != nil {
210		panic("wbubble: " + err.Error())
211	}
212}