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

wrapdemo_test.gno

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