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

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}