aaabbb_test.gno
4.79 Kb · 127 lines
1package aaabbb
2
3import (
4 "chain"
5 "strings"
6 "testing"
7
8 "gno.land/p/nt/testutils/v0"
9 "gno.land/p/nt/uassert/v0"
10 "gno.land/r/moul/x/pairreg/v0"
11 "gno.land/r/moul/x/pairs/aaa/v0"
12 "gno.land/r/moul/x/pairs/bbb/v0"
13)
14
15const selfPath = "gno.land/r/moul/x/pairs/aaabbb/v0"
16
17func self() address { return chain.PackageAddress(selfPath) }
18
19// fund gives addr both tokens and approves this instance to spend them.
20func fund(cur realm, addr address) {
21 testing.SetRealm(testing.NewUserRealm(addr))
22 aaa.Faucet(cross(cur))
23 bbb.Faucet(cross(cur))
24 aaa.Approve(cross(cur), self(), 1_000_000_000)
25 bbb.Approve(cross(cur), self(), 1_000_000_000)
26}
27
28// Deploying the instance is the whole "factory call": its init builds the pair
29// and announces it, so one addpkg both creates and lists it.
30func TestSelfRegisteredAtInit(t *testing.T) {
31 e := pairreg.Get(selfPath)
32 uassert.True(t, e != nil, "instance must register itself in init")
33 uassert.Equal(t, selfPath, e.Path)
34 uassert.Equal(t, 1, pairreg.Size())
35
36 keyA, keyB := Keys()
37 uassert.Equal(t, "gno.land/r/moul/x/pairs/aaa/v0.AAA", keyA)
38 uassert.Equal(t, "gno.land/r/moul/x/pairs/bbb/v0.BBB", keyB)
39}
40
41// The registry holds a live pointer into this realm's storage, so its own
42// Render shows state it never copied. This is the property that lets one
43// qrender describe a thousand instances.
44func TestRegistrySeesLiveState(cur realm, t *testing.T) {
45 alice := testutils.TestAddress("alice-live")
46 fund(cur, alice)
47 AddLiquidity(cross(cur), 1_000_000, 4_000_000)
48
49 e := pairreg.Get(selfPath)
50 resA, resB := e.Pair.Reserves()
51 uassert.Equal(t, int64(1_000_000), resA)
52 uassert.Equal(t, int64(4_000_000), resB)
53
54 index := pairreg.Render("")
55 uassert.True(t, strings.Contains(index, "AAA/BBB"), "index lists the couple")
56 uassert.True(t, strings.Contains(index, "1000000 AAA"), "index shows live reserves")
57 uassert.True(t, strings.Contains(index, "moul/x/pairs/aaabbb/v0"), "index links the instance")
58
59 // Wind the pair back down so the next test starts from an empty pool.
60 RemoveLiquidity(cross(cur), TotalShares())
61}
62
63// Full lifecycle through the instance's own entry points, which is also the
64// proof that funds move to and from THIS realm's address while every line of
65// logic runs in the shared pure package.
66func TestLifecycle(cur realm, t *testing.T) {
67 alice := testutils.TestAddress("alice-cycle")
68 fund(cur, alice)
69
70 minted := AddLiquidity(cross(cur), 1_000_000, 4_000_000)
71 uassert.Equal(t, int64(1_000_000), minted, "first deposit mints amountA, no sqrt")
72 uassert.Equal(t, int64(1_000_000), aaa.BalanceOf(self()), "tokens land on the instance")
73 uassert.Equal(t, int64(4_000_000), bbb.BalanceOf(self()))
74 uassert.Equal(t, minted, SharesOf(alice))
75
76 quoted := Quote("gno.land/r/moul/x/pairs/aaa/v0.AAA", 100_000)
77 out := Swap(cross(cur), "gno.land/r/moul/x/pairs/aaa/v0.AAA", 100_000, 362_000)
78 uassert.Equal(t, int64(362_644), out, "same pricing as the single-realm design")
79 uassert.Equal(t, quoted, out, "Quote matches what Swap pays")
80
81 resA, resB := Reserves()
82 uassert.Equal(t, int64(1_100_000), resA)
83 uassert.Equal(t, int64(3_637_356), resB)
84
85 gotA, gotB := RemoveLiquidity(cross(cur), 500_000)
86 uassert.Equal(t, int64(550_000), gotA)
87 uassert.Equal(t, int64(1_818_678), gotB)
88
89 // The last provider out is paid the whole reserve, so nothing is stranded.
90 gotA, gotB = RemoveLiquidity(cross(cur), TotalShares())
91 uassert.Equal(t, int64(550_000), gotA)
92 uassert.Equal(t, int64(1_818_678), gotB)
93 uassert.Equal(t, int64(0), TotalShares())
94 uassert.Equal(t, int64(0), aaa.BalanceOf(self()), "instance holds nothing afterwards")
95 uassert.Equal(t, int64(0), bbb.BalanceOf(self()))
96}
97
98// A token this pair does not hold must be refused by name, not silently
99// treated as one of the two sides. Asserted through Quote, which reaches the
100// same guard without a crossing call: a panic raised inside a crossing call
101// unwinds the whole transaction and is not recoverable by the caller.
102func TestRejectsForeignToken(cur realm, t *testing.T) {
103 alice := testutils.TestAddress("alice-foreign")
104 fund(cur, alice)
105 AddLiquidity(cross(cur), 1_000_000, 4_000_000)
106
107 uassert.PanicsWithMessage(t, cur, "pair: gno.land/r/nope/v0.NOPE is not in this pair", func() {
108 Quote("gno.land/r/nope/v0.NOPE", 1_000)
109 })
110 RemoveLiquidity(cross(cur), TotalShares())
111}
112
113func TestRender(cur realm, t *testing.T) {
114 alice := testutils.TestAddress("alice-render")
115 fund(cur, alice)
116
117 empty := Render("")
118 uassert.True(t, strings.Contains(empty, "# AAA / BBB"))
119 uassert.True(t, strings.Contains(empty, "Empty"), "empty pair says so")
120
121 AddLiquidity(cross(cur), 1_000_000, 4_000_000)
122 full := Render("")
123 uassert.True(t, strings.Contains(full, "| AAA | 1000000 |"), "reserves row")
124 uassert.True(t, strings.Contains(full, "LP shares: **1000000**"))
125 uassert.True(t, strings.Contains(full, "1 AAA buys ~4 BBB"), "spot line")
126 RemoveLiquidity(cross(cur), TotalShares())
127}