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.

wrapdemo_test.gno

11.24 Kb · 288 lines
  1package grc20wrapdemo
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/testutils/v0"
  8	"gno.land/p/nt/uassert/v0"
  9	faucet "gno.land/r/nym-moultest123/x/grc20faucet/v0"
 10)
 11
 12// Realm state carries over between test functions, but the block height does
 13// not: every test starts from the same height. The faucet's per-account
 14// cooldown would therefore fire on the second test to reuse an account, so each
 15// test claims with its own. TestFaucetCooldown covers the cooldown itself.
 16func fund(cur realm, t *testing.T, name string) address {
 17	t.Helper()
 18	who := testutils.TestAddress(name)
 19	testing.SetRealm(testing.NewUserRealm(who))
 20	faucet.Claim(cross(cur))
 21	faucet.Approve(cross(cur), "RED", Home(), faucet.ClaimAmount)
 22	faucet.Approve(cross(cur), "BLUE", Home(), faucet.ClaimAmount)
 23	return who
 24}
 25
 26// testing.SetRealm only governs crossing calls made from the frame that called
 27// it: put it in a helper with no crossing call of its own and it is silently
 28// ignored, and the caller stays whoever it was. fund() gets away with it
 29// because it crosses into the faucet right after; switching accounts mid-test
 30// has to be written inline, which is why there is no act() helper here.
 31
 32// The plain mode is the baseline: it proves custody actually crosses a realm
 33// boundary. The RED never belonged to this realm, and it can only ever take
 34// what the faucet's Approve handed over.
 35func TestPlainWrapperRoundTrip(cur realm, t *testing.T) {
 36	alice := fund(cur, t, "w-alice")
 37	before := faucet.BalanceOf("RED", alice)
 38
 39	key := NewWrapper(cross(cur), faucet.RedKey, "plain", "wRED")
 40	uassert.True(t, strings.HasSuffix(key, ".wRED"), "registered under its own symbol: "+key)
 41
 42	minted := Deposit(cross(cur), "wRED", 400_000)
 43	uassert.Equal(t, int64(400_000), minted)
 44	uassert.Equal(t, int64(400_000), BalanceOf("wRED", alice))
 45	uassert.Equal(t, int64(400_000), Escrow("wRED"))
 46	uassert.Equal(t, before-400_000, faucet.BalanceOf("RED", alice))
 47	// The escrow really sits at this realm's address, in the faucet's ledger.
 48	uassert.Equal(t, int64(400_000), faucet.BalanceOf("RED", Home()))
 49
 50	back := Withdraw(cross(cur), "wRED", 150_000)
 51	uassert.Equal(t, int64(150_000), back)
 52	uassert.Equal(t, int64(250_000), TotalSupply("wRED"))
 53	uassert.Equal(t, before-250_000, faucet.BalanceOf("RED", alice))
 54}
 55
 56// Depositing without an allowance is the default state of the world, and it
 57// fails. This realm holds no standing authority over anybody's balance.
 58func TestDepositNeedsAnAllowance(cur realm, t *testing.T) {
 59	fund(cur, t, "noallow")
 60	NewWrapper(cross(cur), faucet.RedKey, "plain", "noallow")
 61
 62	// Revoke what fund() granted.
 63	faucet.Approve(cross(cur), "RED", Home(), 0)
 64	uassert.AbortsContains(t, cur, "insufficient allowance", func() {
 65		Deposit(cross(cur), "noallow", 1)
 66	})
 67	uassert.Equal(t, int64(0), TotalSupply("noallow"))
 68}
 69
 70// kilo re-denominates a token whose own realm will never redeploy for it.
 71func TestKiloWrapperAddsThreeDecimals(cur realm, t *testing.T) {
 72	fund(cur, t, "kilo")
 73	NewWrapper(cross(cur), faucet.RedKey, "kilo", "kRED")
 74
 75	minted := Deposit(cross(cur), "kRED", 1_000)
 76	uassert.Equal(t, int64(1_000_000), minted)
 77	uassert.Equal(t, int64(1_000), Escrow("kRED"))
 78
 79	back := Withdraw(cross(cur), "kRED", 999_000)
 80	uassert.Equal(t, int64(999), back)
 81	// 999 wrapped units cannot buy back a whole underlying one, so the
 82	// redemption is refused rather than burning them for nothing.
 83	uassert.AbortsContains(t, cur, "rounds to zero", func() {
 84		Withdraw(cross(cur), "kRED", 999)
 85	})
 86}
 87
 88func TestSoulboundWrapperRefusesTransfer(cur realm, t *testing.T) {
 89	fund(cur, t, "soul")
 90	bob := testutils.TestAddress("soul-bob")
 91	NewWrapper(cross(cur), faucet.RedKey, "soulbound", "sRED")
 92	Deposit(cross(cur), "sRED", 300_000)
 93
 94	uassert.AbortsContains(t, cur, "soulbound", func() {
 95		Transfer(cross(cur), "sRED", bob, 1)
 96	})
 97	uassert.Equal(t, int64(0), BalanceOf("sRED", bob))
 98
 99	// The exit stays open: unwrap, and the RED moves as freely as ever.
100	back := Withdraw(cross(cur), "sRED", 300_000)
101	uassert.Equal(t, int64(300_000), back)
102	uassert.Equal(t, int64(0), TotalSupply("sRED"))
103}
104
105// Pool: a donation is shared by whoever is holding, and the donor can never
106// take it back out.
107func TestPoolWrapperSharesDonations(cur realm, t *testing.T) {
108	alice := fund(cur, t, "pool-a")
109	NewWrapper(cross(cur), faucet.RedKey, "pool", "pRED")
110	uassert.Equal(t, int64(100_000), Deposit(cross(cur), "pRED", 100_000))
111
112	bob := fund(cur, t, "pool-b")
113	Donate(cross(cur), "pRED", 100_000)
114	uassert.Equal(t, int64(200_000), Escrow("pRED"))
115	uassert.Equal(t, int64(100_000), TotalSupply("pRED"))
116	uassert.Equal(t, int64(0), BalanceOf("pRED", bob)) // a donation mints nothing
117
118	// Buying in afterwards costs twice as much per share.
119	uassert.Equal(t, int64(50_000), Deposit(cross(cur), "pRED", 100_000))
120
121	// alice walks away with double what she put in.
122	testing.SetRealm(testing.NewUserRealm(alice))
123	redBefore := faucet.BalanceOf("RED", alice)
124	uassert.Equal(t, int64(200_000), Withdraw(cross(cur), "pRED", 100_000))
125	uassert.Equal(t, redBefore+200_000, faucet.BalanceOf("RED", alice))
126
127	// And bob gets back exactly the 100_000 he deposited: his own donation
128	// was to the holders, and by then that was alice.
129	testing.SetRealm(testing.NewUserRealm(bob))
130	uassert.Equal(t, int64(100_000), Withdraw(cross(cur), "pRED", 50_000))
131	uassert.Equal(t, int64(0), Escrow("pRED"))
132}
133
134// Sticky: the 1% exit fee is collected by nobody, it is left in the pool, so
135// whoever stays longest ends up with it.
136func TestStickyWrapperChargesOnTheWayOut(cur realm, t *testing.T) {
137	alice := fund(cur, t, "stick-a")
138	NewWrapper(cross(cur), faucet.RedKey, "sticky", "fRED")
139	Deposit(cross(cur), "fRED", 500_000)
140
141	bob := fund(cur, t, "stick-b")
142	Deposit(cross(cur), "fRED", 500_000)
143	uassert.Equal(t, int64(1_000_000), TotalSupply("fRED"))
144
145	testing.SetRealm(testing.NewUserRealm(alice))
146	uassert.Equal(t, int64(495_000), Withdraw(cross(cur), "fRED", 500_000))
147
148	// The 5_000 alice paid on the way out now backs bob's shares.
149	uassert.Equal(t, int64(505_000), Escrow("fRED"))
150	testing.SetRealm(testing.NewUserRealm(bob))
151	uassert.Equal(t, int64(499_950), Withdraw(cross(cur), "fRED", 500_000))
152}
153
154// Fusion: one token backed by a fixed bundle of two, and nothing else.
155func TestFusionOfRedAndBlue(cur realm, t *testing.T) {
156	alice := fund(cur, t, "fuse-a")
157	bob := testutils.TestAddress("fuse-b")
158
159	key := NewFusion(cross(cur), faucet.RedKey, 1, faucet.BlueKey, 2, "PURPLE")
160	uassert.True(t, strings.HasSuffix(key, ".PURPLE"), key)
161	uassert.Equal(t, 2, Legs("PURPLE"))
162
163	redBefore := faucet.BalanceOf("RED", alice)
164	blueBefore := faucet.BalanceOf("BLUE", alice)
165	Fuse(cross(cur), "PURPLE", 100_000)
166
167	uassert.Equal(t, int64(100_000), BalanceOf("PURPLE", alice))
168	uassert.Equal(t, redBefore-100_000, faucet.BalanceOf("RED", alice))
169	uassert.Equal(t, blueBefore-200_000, faucet.BalanceOf("BLUE", alice))
170
171	sym, per, held := Leg("PURPLE", 1)
172	uassert.Equal(t, "BLUE", sym)
173	uassert.Equal(t, int64(2), per)
174	uassert.Equal(t, int64(200_000), held)
175
176	// PURPLE moves as one object, and whoever ends up with it can split it
177	// back into both legs without ever having touched the faucet.
178	Transfer(cross(cur), "PURPLE", bob, 40_000)
179	testing.SetRealm(testing.NewUserRealm(bob))
180	Defuse(cross(cur), "PURPLE", 40_000)
181	uassert.Equal(t, int64(40_000), faucet.BalanceOf("RED", bob))
182	uassert.Equal(t, int64(80_000), faucet.BalanceOf("BLUE", bob))
183	uassert.Equal(t, int64(60_000), TotalSupply("PURPLE"))
184}
185
186// Every token issued here is registered too, so it can be wrapped again.
187func TestWrappingAWrapper(cur realm, t *testing.T) {
188	fund(cur, t, "nest")
189	NewWrapper(cross(cur), faucet.RedKey, "plain", "nRED")
190	Deposit(cross(cur), "nRED", 800_000)
191
192	// nRED lives in THIS realm, so the allowance is granted through this
193	// realm's own Approve. Same shape, one level up.
194	NewWrapper(cross(cur), Key("nRED"), "pool", "nnRED")
195	Approve(cross(cur), "nRED", Home(), 800_000)
196	uassert.Equal(t, int64(600_000), Deposit(cross(cur), "nnRED", 600_000))
197
198	// The outer wrapper escrows the inner token, not the RED.
199	uassert.Equal(t, int64(600_000), Escrow("nnRED"))
200	uassert.Equal(t, int64(800_000), Escrow("nRED"))
201	uassert.Equal(t, int64(600_000), BalanceOf("nRED", Home()))
202
203	// And it unwinds the whole way back down.
204	uassert.Equal(t, int64(600_000), Withdraw(cross(cur), "nnRED", 600_000))
205	uassert.Equal(t, int64(800_000), Withdraw(cross(cur), "nRED", 800_000))
206	uassert.Equal(t, int64(0), TotalSupply("nRED"))
207}
208
209func TestAllowanceOnAWrappedToken(cur realm, t *testing.T) {
210	alice := fund(cur, t, "allow-a")
211	bob := testutils.TestAddress("allow-b")
212
213	NewWrapper(cross(cur), faucet.RedKey, "plain", "aRED")
214	Deposit(cross(cur), "aRED", 500_000)
215	Approve(cross(cur), "aRED", bob, 120_000)
216	uassert.Equal(t, int64(120_000), Allowance("aRED", alice, bob))
217
218	testing.SetRealm(testing.NewUserRealm(bob))
219	TransferFrom(cross(cur), "aRED", alice, bob, 120_000)
220	uassert.Equal(t, int64(120_000), BalanceOf("aRED", bob))
221	uassert.AbortsContains(t, cur, "insufficient allowance", func() {
222		TransferFrom(cross(cur), "aRED", alice, bob, 1)
223	})
224}
225
226func TestFaucetCooldown(cur realm, t *testing.T) {
227	who := fund(cur, t, "cooldown")
228	uassert.AbortsContains(t, cur, "too soon", func() {
229		faucet.Claim(cross(cur))
230	})
231
232	next, ok := faucet.NextClaim(who)
233	uassert.True(t, ok, "the account has claimed once")
234	testing.SkipHeights(faucet.ClaimEvery + 1)
235	faucet.Claim(cross(cur))
236	uassert.Equal(t, 2*faucet.ClaimAmount, faucet.BalanceOf("RED", who))
237	uassert.True(t, next > 0, "a cooldown was recorded")
238
239	_, ok = faucet.NextClaim(testutils.TestAddress("never"))
240	uassert.False(t, ok, "an account that never claimed has no cooldown")
241}
242
243func TestRejectsBadInput(cur realm, t *testing.T) {
244	fund(cur, t, "bad")
245	NewWrapper(cross(cur), faucet.RedKey, "plain", "xRED")
246	NewFusion(cross(cur), faucet.RedKey, 1, faucet.BlueKey, 1, "xMIX")
247
248	uassert.AbortsContains(t, cur, "unknown mode", func() {
249		NewWrapper(cross(cur), faucet.RedKey, "moon", "xMOON")
250	})
251	uassert.AbortsContains(t, cur, "already issued by this realm", func() {
252		NewWrapper(cross(cur), faucet.RedKey, "plain", "xRED")
253	})
254	uassert.AbortsContains(t, cur, "unknown token", func() {
255		NewWrapper(cross(cur), "gno.land/r/nope/nope.NOPE", "plain", "xNOPE")
256	})
257	uassert.AbortsContains(t, cur, "is a fusion", func() {
258		Deposit(cross(cur), "xMIX", 1)
259	})
260	uassert.AbortsContains(t, cur, "is a wrapper", func() {
261		Fuse(cross(cur), "xRED", 1)
262	})
263	uassert.AbortsContains(t, cur, "has not issued", func() {
264		Deposit(cross(cur), "GHOST", 1)
265	})
266	uassert.AbortsContains(t, cur, "unknown symbol", func() {
267		faucet.Transfer(cross(cur), "GREEN", Home(), 1)
268	})
269}
270
271func TestRender(cur realm, t *testing.T) {
272	fund(cur, t, "render")
273	NewWrapper(cross(cur), faucet.RedKey, "pool", "rRED")
274	Deposit(cross(cur), "rRED", 250_000)
275
276	index := Render("")
277	uassert.True(t, strings.Contains(index, "# GRC20 wrapper factory"), "index heading")
278	uassert.True(t, strings.Contains(index, Home().String()), "index shows the escrow address")
279	uassert.True(t, strings.Contains(index, "| [rRED](/r/nym-moultest123/x/grc20wrapdemo/v0:rRED) | pool | RED |"),
280		"index lists the wrapper:\n"+index)
281
282	detail := Render("rRED")
283	uassert.True(t, strings.Contains(detail, "escrowed underlying: 250000"), detail)
284	uassert.True(t, strings.Contains(detail, "- mode: `pool`"), detail)
285	uassert.True(t, strings.Contains(detail, "solvent: yes"), detail)
286
287	uassert.True(t, strings.Contains(Render("NOPE"), "404"), "unknown symbol renders a 404")
288}