basket_test.gno
6.52 Kb · 191 lines
1package grc20wrap
2
3import (
4 "testing"
5
6 "gno.land/p/nt/grc20/v0"
7 "gno.land/p/nt/uassert/v0"
8 "gno.land/p/nt/urequire/v0"
9)
10
11const (
12 pairPath = "gno.land/r/test/pair"
13 basketPath = "gno.land/r/test/basket"
14)
15
16// mintPair issues two independent tokens from one foreign realm and funds the
17// holders with both.
18func mintPair(cur realm, t *testing.T, red, blue int64, funded ...address) (*grc20.Token, *grc20.PrivateLedger, *grc20.Token, *grc20.PrivateLedger) {
19 t.Helper()
20 testing.SetRealm(testing.NewCodeRealm(pairPath))
21 rt, rl := grc20.NewToken("Red", "RED", 4, 0, cur)
22 bt, bl := grc20.NewToken("Blue", "BLUE", 4, 1, cur)
23 for _, a := range funded {
24 urequire.NoError(t, rl.Mint(a, red))
25 urequire.NoError(t, bl.Mint(a, blue))
26 }
27 return rt, rl, bt, bl
28}
29
30func TestFuseAndDefuse(cur realm, t *testing.T) {
31 red, redLed, blue, blueLed := mintPair(cur, t, 1_000, 1_000, alice)
32
33 testing.SetRealm(testing.NewCodeRealm(basketPath))
34 b := NewBasket(
35 []*grc20.Token{red, blue},
36 []int64{1, 2},
37 "Purple", "PURPLE", 4, 0, cur,
38 )
39 approve(cur, t, redLed, alice, b.Home(), 1_000)
40 approve(cur, t, blueLed, alice, b.Home(), 1_000)
41
42 urequire.NoError(t, b.Fuse(0, cur, alice, 10))
43 uassert.Equal(t, int64(10), b.Token().BalanceOf(alice))
44 uassert.Equal(t, int64(990), red.BalanceOf(alice))
45 uassert.Equal(t, int64(980), blue.BalanceOf(alice))
46
47 _, per, held := b.Leg(0)
48 uassert.Equal(t, int64(1), per)
49 uassert.Equal(t, int64(10), held)
50 _, per, held = b.Leg(1)
51 uassert.Equal(t, int64(2), per)
52 uassert.Equal(t, int64(20), held)
53 uassert.True(t, b.Solvent())
54
55 urequire.NoError(t, b.Defuse(0, cur, alice, 4))
56 uassert.Equal(t, int64(6), b.Token().TotalSupply())
57 uassert.Equal(t, int64(994), red.BalanceOf(alice))
58 uassert.Equal(t, int64(988), blue.BalanceOf(alice))
59
60 // Out entirely: the basket closes empty on both legs.
61 urequire.NoError(t, b.Defuse(0, cur, alice, 6))
62 uassert.Equal(t, int64(0), b.Token().TotalSupply())
63 _, _, held = b.Leg(0)
64 uassert.Equal(t, int64(0), held)
65 _, _, held = b.Leg(1)
66 uassert.Equal(t, int64(0), held)
67 uassert.Equal(t, int64(1_000), red.BalanceOf(alice))
68 uassert.Equal(t, int64(1_000), blue.BalanceOf(alice))
69}
70
71// The whole basket is priced before the first transfer, so being short on the
72// SECOND leg leaves the first one untouched. Without the pre-pass this is where
73// a partially-applied mint would come from.
74func TestFuseChecksEveryLegBeforeMovingAny(cur realm, t *testing.T) {
75 red, redLed, blue, blueLed := mintPair(cur, t, 1_000, 10, alice)
76
77 testing.SetRealm(testing.NewCodeRealm(basketPath))
78 b := NewBasket(
79 []*grc20.Token{red, blue},
80 []int64{1, 2},
81 "Purple", "PURPLE", 4, 0, cur,
82 )
83 approve(cur, t, redLed, alice, b.Home(), 1_000)
84 approve(cur, t, blueLed, alice, b.Home(), 1_000)
85
86 // 100 units needs 100 RED (fine) and 200 BLUE (alice has 10).
87 uassert.ErrorIs(t, b.Fuse(0, cur, alice, 100), ErrShortBalance)
88 uassert.Equal(t, int64(1_000), red.BalanceOf(alice))
89 uassert.Equal(t, int64(0), b.Token().TotalSupply())
90
91 // Same for a missing allowance on the second leg only.
92 approve(cur, t, blueLed, alice, b.Home(), 0)
93 urequire.NoError(t, blueLed.Mint(alice, 1_000))
94 uassert.ErrorIs(t, b.Fuse(0, cur, alice, 5), ErrShortAllowance)
95 uassert.Equal(t, int64(1_000), red.BalanceOf(alice))
96 uassert.Equal(t, int64(0), b.Token().TotalSupply())
97}
98
99func TestDefuseStopsAtTheBalance(cur realm, t *testing.T) {
100 red, redLed, blue, blueLed := mintPair(cur, t, 1_000, 1_000, alice, bob)
101
102 testing.SetRealm(testing.NewCodeRealm(basketPath))
103 b := NewBasket(
104 []*grc20.Token{red, blue},
105 []int64{1, 1},
106 "Purple", "PURPLE", 4, 0, cur,
107 )
108 for _, a := range []address{alice, bob} {
109 approve(cur, t, redLed, a, b.Home(), 1_000)
110 approve(cur, t, blueLed, a, b.Home(), 1_000)
111 }
112
113 urequire.NoError(t, b.Fuse(0, cur, alice, 50))
114 uassert.ErrorContains(t, b.Defuse(0, cur, bob, 1), "insufficient balance")
115 uassert.ErrorIs(t, b.Defuse(0, cur, alice, 0), ErrInvalidAmount)
116 _, _, held := b.Leg(0)
117 uassert.Equal(t, int64(50), held)
118}
119
120func TestNewBasketRejectsBadLegs(cur realm, t *testing.T) {
121 red, _, blue, _ := mintPair(cur, t, 1_000, 1_000)
122
123 testing.SetRealm(testing.NewCodeRealm(basketPath))
124
125 // A one-leg basket is a Vault; say so instead of pretending.
126 uassert.PanicsContains(t, cur, "at least two legs", func() {
127 NewBasket([]*grc20.Token{red}, []int64{1}, "X", "X", 4, 0, cur)
128 })
129 // Mismatched slices.
130 uassert.PanicsContains(t, cur, "at least two legs", func() {
131 NewBasket([]*grc20.Token{red, blue}, []int64{1}, "X", "X", 4, 0, cur)
132 })
133 // A zero part would let a leg be minted for free.
134 uassert.PanicsContains(t, cur, "at least two legs", func() {
135 NewBasket([]*grc20.Token{red, blue}, []int64{1, 0}, "X", "X", 4, 0, cur)
136 })
137 // The same token twice is two accountings of one balance.
138 uassert.PanicsContains(t, cur, "at least two legs", func() {
139 NewBasket([]*grc20.Token{red, red}, []int64{1, 1}, "X", "X", 4, 0, cur)
140 })
141 uassert.PanicsContains(t, cur, "nil underlying token", func() {
142 NewBasket([]*grc20.Token{red, nil}, []int64{1, 1}, "X", "X", 4, 0, cur)
143 })
144}
145
146func TestBasketMoveAndAllowance(cur realm, t *testing.T) {
147 red, redLed, blue, blueLed := mintPair(cur, t, 1_000, 1_000, alice)
148
149 testing.SetRealm(testing.NewCodeRealm(basketPath))
150 b := NewBasket(
151 []*grc20.Token{red, blue},
152 []int64{1, 1},
153 "Purple", "PURPLE", 4, 0, cur,
154 )
155 approve(cur, t, redLed, alice, b.Home(), 1_000)
156 approve(cur, t, blueLed, alice, b.Home(), 1_000)
157 urequire.NoError(t, b.Fuse(0, cur, alice, 100))
158
159 urequire.NoError(t, b.Move(alice, bob, 40))
160 uassert.Equal(t, int64(40), b.Token().BalanceOf(bob))
161 urequire.NoError(t, b.Allow(alice, bob, 30))
162 urequire.NoError(t, b.MoveFrom(bob, alice, carl, 30))
163 uassert.Equal(t, int64(30), b.Token().BalanceOf(carl))
164 uassert.ErrorIs(t, b.MoveFrom(bob, alice, carl, 0), ErrInvalidAmount)
165
166 // bob holds meta units he never minted, and can still split them.
167 urequire.NoError(t, b.Defuse(0, cur, bob, 40))
168 uassert.Equal(t, int64(40), red.BalanceOf(bob))
169 uassert.Equal(t, int64(40), blue.BalanceOf(bob))
170}
171
172func TestBasketSummary(cur realm, t *testing.T) {
173 red, redLed, blue, blueLed := mintPair(cur, t, 1_000, 1_000, alice)
174
175 testing.SetRealm(testing.NewCodeRealm(basketPath))
176 b := NewBasket(
177 []*grc20.Token{red, blue},
178 []int64{1, 2},
179 "Purple", "PURPLE", 4, 0, cur,
180 )
181 approve(cur, t, redLed, alice, b.Home(), 1_000)
182 approve(cur, t, blueLed, alice, b.Home(), 1_000)
183 urequire.NoError(t, b.Fuse(0, cur, alice, 7))
184
185 want := "**Purple** (PURPLE) - meta-token over 2 legs\n\n" +
186 "- meta supply: 7\n" +
187 "- leg RED: 1 per unit, 7 escrowed\n" +
188 "- leg BLUE: 2 per unit, 14 escrowed\n" +
189 "- solvent: yes\n"
190 uassert.Equal(t, want, b.Summary())
191}