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

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}