policy_test.gno
3.31 Kb · 95 lines
1package grc20wrap
2
3import (
4 "math"
5 "testing"
6
7 "gno.land/p/nt/uassert/v0"
8)
9
10// Every policy here is stateless, so it prices correctly against a nil vault.
11// Pool is the one that reads the vault, and it is covered end to end in
12// TestPoolSharesAppreciate.
13func TestStatelessRates(t *testing.T) {
14 cases := []struct {
15 name string
16 pol Policy
17 wrapIn int64
18 wrapOut int64
19 unwrapIn int64
20 unwrapOut int64
21 }{
22 {"one to one", OneToOne{}, 7, 7, 7, 7},
23 {"kilo", Ratio{Num: 1_000, Den: 1}, 7, 7_000, 7_000, 7},
24 {"kilo truncates on the way out", Ratio{Num: 1_000, Den: 1}, 1, 1_000, 2_500, 2},
25 {"milli", Ratio{Num: 1, Den: 1_000}, 7_000, 7, 7, 7_000},
26 {"two for three", Ratio{Num: 2, Den: 3}, 9, 6, 6, 9},
27 {"soulbound keeps its base rate", Soulbound{Base: Ratio{Num: 2, Den: 1}}, 5, 10, 10, 5},
28 {"soulbound with no base is 1:1", Soulbound{}, 5, 5, 5, 5},
29 {"one percent both ways", Fee{WrapBPS: 100, UnwrapBPS: 100}, 1_000, 990, 1_000, 990},
30 {"a haircut under one unit rounds to nothing", Fee{WrapBPS: 100}, 55, 55, 55, 55},
31 }
32
33 for _, tc := range cases {
34 got, err := tc.pol.WrapRate(nil, tc.wrapIn)
35 uassert.NoError(t, err, tc.name)
36 uassert.Equal(t, tc.wrapOut, got, tc.name+" (wrap)")
37
38 got, err = tc.pol.UnwrapRate(nil, tc.unwrapIn)
39 uassert.NoError(t, err, tc.name)
40 uassert.Equal(t, tc.unwrapOut, got, tc.name+" (unwrap)")
41 }
42}
43
44func TestRateErrors(t *testing.T) {
45 cases := []struct {
46 name string
47 pol Policy
48 in int64
49 wrapErr error
50 unwrapErr error
51 }{
52 {"zero numerator", Ratio{Num: 0, Den: 1}, 5, ErrBadRatio, ErrBadRatio},
53 {"negative denominator", Ratio{Num: 2, Den: -1}, 5, ErrBadRatio, ErrBadRatio},
54 // Only the multiplying direction can overflow; the dividing one
55 // just returns a very small number.
56 {"overflowing numerator", Ratio{Num: math.MaxInt64, Den: 1}, 3, ErrOverflow, nil},
57 {"basis points above 100%", Fee{WrapBPS: 10_001, UnwrapBPS: 10_001}, 100, ErrBadBPS, ErrBadBPS},
58 {"negative basis points", Fee{WrapBPS: -1, UnwrapBPS: -1}, 100, ErrBadBPS, ErrBadBPS},
59 }
60
61 for _, tc := range cases {
62 _, err := tc.pol.WrapRate(nil, tc.in)
63 if tc.wrapErr == nil {
64 uassert.NoError(t, err, tc.name+" (wrap)")
65 } else {
66 uassert.ErrorIs(t, err, tc.wrapErr, tc.name+" (wrap)")
67 }
68
69 _, err = tc.pol.UnwrapRate(nil, tc.in)
70 if tc.unwrapErr == nil {
71 uassert.NoError(t, err, tc.name+" (unwrap)")
72 } else {
73 uassert.ErrorIs(t, err, tc.unwrapErr, tc.name+" (unwrap)")
74 }
75 }
76}
77
78func TestPolicyNames(t *testing.T) {
79 uassert.Equal(t, "1:1", OneToOne{}.Name())
80 uassert.Equal(t, "1000:1", Ratio{Num: 1_000, Den: 1}.Name())
81 uassert.Equal(t, "pool", Pool{}.Name())
82 uassert.Equal(t, "soulbound/1:1", Soulbound{}.Name())
83 uassert.Equal(t, "soulbound/pool", Soulbound{Base: Pool{}}.Name())
84 uassert.Equal(t, "fee(0/100bps)/pool", Fee{Base: Pool{}, UnwrapBPS: 100}.Name())
85}
86
87func TestOnlySoulboundVetoes(t *testing.T) {
88 uassert.NoError(t, OneToOne{}.CanMove(nil, alice, bob, 1))
89 uassert.NoError(t, Ratio{Num: 1, Den: 1}.CanMove(nil, alice, bob, 1))
90 uassert.NoError(t, Pool{}.CanMove(nil, alice, bob, 1))
91 uassert.NoError(t, Fee{Base: Pool{}}.CanMove(nil, alice, bob, 1))
92 uassert.ErrorIs(t, Soulbound{}.CanMove(nil, alice, bob, 1), ErrSoulbound)
93 // A fee over a soulbound base inherits the veto.
94 uassert.ErrorIs(t, Fee{Base: Soulbound{}}.CanMove(nil, alice, bob, 1), ErrSoulbound)
95}