Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}