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

vesting_test.gno

7.46 Kb · 213 lines
  1package vesting
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9// The real mainnet §132 term: 2026-09-12T15:00:00Z to 2028-09-12T15:00:00Z.
 10const (
 11	mainnetStart int64 = 1789225200
 12	mainnetEnd   int64 = 1852383600
 13	mainnetTerm        = mainnetEnd - mainnetStart // 63,158,400 seconds
 14)
 15
 16func TestNewValidates(t *testing.T) {
 17	for _, tc := range []struct {
 18		name                    string
 19		original, start, end    int64
 20		typ                     Type
 21		wantErr                 error
 22	}{
 23		{"a real mainnet schedule", 106560000000, mainnetStart, mainnetEnd, Continuous, nil},
 24		{"no schedule at all", 0, 0, 0, Continuous, nil},
 25		{"zero original ignores the rest", 0, 99, 1, Continuous, nil},
 26		{"a cliff needs no start", 100, 0, mainnetEnd, Delayed, nil},
 27		{"negative original", -1, 0, 10, Continuous, ErrNegativeOriginal},
 28		{"end not positive", 100, 0, 0, Continuous, ErrEndNotPositive},
 29		{"negative start", 100, -1, 10, Continuous, ErrNegativeStart},
 30		{"start equal to end", 100, 10, 10, Continuous, ErrStartAfterEnd},
 31		{"start after end", 100, 11, 10, Continuous, ErrStartAfterEnd},
 32	} {
 33		_, err := New(tc.original, tc.start, tc.end, tc.typ)
 34		if tc.wantErr == nil {
 35			uassert.NoError(t, err, tc.name)
 36			continue
 37		}
 38		uassert.ErrorIs(t, err, tc.wantErr, tc.name)
 39	}
 40}
 41
 42// TestVestedCurve pins the continuous curve at its boundaries and midpoint.
 43func TestVestedCurve(t *testing.T) {
 44	s, err := New(1000, 100, 200, Continuous)
 45	uassert.NoError(t, err)
 46
 47	for _, tc := range []struct {
 48		now, want int64
 49	}{
 50		{0, 0},      // long before
 51		{100, 0},    // at the start
 52		{125, 250},  // a quarter in
 53		{150, 500},  // halfway
 54		{199, 990},  // one tick short
 55		{200, 1000}, // exactly at the end
 56		{1 << 40, 1000},
 57	} {
 58		uassert.Equal(t, tc.want, s.Vested(tc.now))
 59		uassert.Equal(t, 1000-tc.want, s.Locked(tc.now))
 60	}
 61}
 62
 63// TestDelayedIsACliff: Start is ignored and nothing vests until End.
 64func TestDelayedIsACliff(t *testing.T) {
 65	s, err := New(1000, 0, 200, Delayed)
 66	uassert.NoError(t, err)
 67
 68	uassert.Equal(t, int64(0), s.Vested(0))
 69	uassert.Equal(t, int64(0), s.Vested(199))
 70	uassert.Equal(t, int64(1000), s.Vested(200))
 71	uassert.Equal(t, int64(1000), s.Vested(1000))
 72	uassert.Equal(t, int64(1000), s.Locked(199))
 73	uassert.Equal(t, int64(0), s.Locked(200))
 74}
 75
 76func TestZeroScheduleLocksNothing(t *testing.T) {
 77	var s Schedule
 78	uassert.True(t, s.IsZero())
 79	uassert.Equal(t, int64(0), s.Vested(12345))
 80	uassert.Equal(t, int64(0), s.Locked(12345))
 81	uassert.Equal(t, int64(999), s.Spendable(999, 12345))
 82	uassert.Equal(t, int64(1000), s.PermilleVested(12345))
 83}
 84
 85// TestRoundsDown is the direction that matters: reporting one ugnot more than
 86// the chain will move turns a page into a lie a user acts on.
 87func TestRoundsDown(t *testing.T) {
 88	s, err := New(10, 0, 3, Continuous) // 10/3 per second, never exact
 89	uassert.NoError(t, err)
 90
 91	uassert.Equal(t, int64(3), s.Vested(1)) // 3.33 -> 3
 92	uassert.Equal(t, int64(6), s.Vested(2)) // 6.66 -> 6
 93	uassert.Equal(t, int64(10), s.Vested(3))
 94}
 95
 96// TestNoOverflowAtChainScale is the whole reason this package exists rather
 97// than reusing p/moul/x/daily/cliffvesting, which computes total*elapsed in
 98// plain int64. Over the mainnet term that wraps above 146,036 GNOT: the
 99// largest genesis grant is 318,720,000 GNOT, and the naive form returns a
100// NEGATIVE vested amount for it.
101func TestNoOverflowAtChainScale(t *testing.T) {
102	const largestGrant int64 = 318720000000000 // 318,720,000 GNOT in ugnot
103
104	s, err := New(largestGrant, mainnetStart, mainnetEnd, Continuous)
105	uassert.NoError(t, err)
106
107	// Exactly halfway through the term: the answer is half the grant, and the
108	// naive int64 product would have wrapped long before here.
109	half := s.Vested(mainnetStart + mainnetTerm/2)
110	uassert.Equal(t, largestGrant/2, half)
111	uassert.True(t, half > 0, "a wrapped product reports a negative amount")
112
113	// Every point on the curve stays inside [0, Original], monotonically.
114	prev := int64(0)
115	for i := int64(0); i <= 16; i++ {
116		got := s.Vested(mainnetStart + mainnetTerm*i/16)
117		uassert.True(t, got >= prev, "vested went backwards")
118		uassert.True(t, got >= 0 && got <= largestGrant, "vested left its bounds")
119		prev = got
120	}
121	uassert.Equal(t, largestGrant, prev)
122}
123
124// TestTotalSupplyGrantDoesNotOverflow takes the ceiling to the whole supply,
125// which is the largest schedule the chain could ever hold.
126func TestTotalSupplyGrantDoesNotOverflow(t *testing.T) {
127	const supply int64 = 1332999998328067 // total genesis allocation, ugnot
128
129	s, err := New(supply, mainnetStart, mainnetEnd, Continuous)
130	uassert.NoError(t, err)
131	uassert.Equal(t, supply/2, s.Vested(mainnetStart+mainnetTerm/2))
132	uassert.Equal(t, supply, s.Vested(mainnetEnd))
133}
134
135// TestSpendable is the number the page exists to show.
136func TestSpendable(t *testing.T) {
137	s, err := New(1000, 100, 200, Continuous)
138	uassert.NoError(t, err)
139
140	// Balance above the grant: the surplus is spendable from the start.
141	uassert.Equal(t, int64(200), s.Spendable(1200, 100)) // 1200 - 1000 locked
142	uassert.Equal(t, int64(700), s.Spendable(1200, 150)) // 1200 - 500 locked
143	uassert.Equal(t, int64(1200), s.Spendable(1200, 200))
144
145	// Spent down below what is still locked: nothing moves, and the answer is
146	// zero rather than a negative number.
147	uassert.Equal(t, int64(0), s.Spendable(400, 150))
148	uassert.Equal(t, int64(0), s.Spendable(500, 150)) // exactly locked
149	uassert.Equal(t, int64(1), s.Spendable(501, 150))
150
151	uassert.Equal(t, int64(0), s.Spendable(0, 150))
152	uassert.Equal(t, int64(0), s.Spendable(-5, 150))
153}
154
155// TestMoulsOwnAccount walks the real genesis row through the real schedule,
156// as a regression on the arithmetic the page will print.
157//
158// Genesis row, from the allocation sheet that built mainnet:
159//
160//	g1manfred47kzduec920z88wfr64ylksmdcedlf5=111000000000ugnot;
161//	  vesting=106560000000ugnot,1789225200,1852383600
162func TestMoulsOwnAccount(t *testing.T) {
163	const (
164		genesisBalance int64 = 111000000000
165		grant          int64 = 106560000000
166	)
167	s, err := New(grant, mainnetStart, mainnetEnd, Continuous)
168	uassert.NoError(t, err)
169
170	// At genesis: the 4% that never vested is immediately spendable.
171	uassert.Equal(t, int64(0), s.Vested(mainnetStart))
172	uassert.Equal(t, genesisBalance-grant, s.Spendable(genesisBalance, mainnetStart))
173	uassert.Equal(t, int64(4440000000), s.Spendable(genesisBalance, mainnetStart))
174
175	// Halfway: half the grant has vested.
176	uassert.Equal(t, grant/2, s.Vested(mainnetStart+mainnetTerm/2))
177
178	// At the end: everything moves, whatever the balance is by then.
179	uassert.Equal(t, int64(0), s.Locked(mainnetEnd))
180	uassert.Equal(t, genesisBalance, s.Spendable(genesisBalance, mainnetEnd))
181}
182
183func TestPermilleVested(t *testing.T) {
184	s, err := New(1000, 100, 200, Continuous)
185	uassert.NoError(t, err)
186
187	uassert.Equal(t, int64(0), s.PermilleVested(100))
188	uassert.Equal(t, int64(250), s.PermilleVested(125))
189	uassert.Equal(t, int64(1000), s.PermilleVested(200))
190
191	// Rounds down, like everything else here.
192	s2, err := New(3, 0, 300, Continuous)
193	uassert.NoError(t, err)
194	uassert.Equal(t, int64(333), s2.PermilleVested(100)) // 1/3 of 3 = 1 -> 333
195}
196
197func TestRemainingSeconds(t *testing.T) {
198	s, err := New(1000, 100, 200, Continuous)
199	uassert.NoError(t, err)
200
201	uassert.Equal(t, int64(100), s.RemainingSeconds(100))
202	uassert.Equal(t, int64(1), s.RemainingSeconds(199))
203	uassert.Equal(t, int64(0), s.RemainingSeconds(200))
204	uassert.Equal(t, int64(0), s.RemainingSeconds(10000))
205
206	var zero Schedule
207	uassert.Equal(t, int64(0), zero.RemainingSeconds(0))
208}
209
210func TestTypeString(t *testing.T) {
211	uassert.Equal(t, "continuous", Continuous.String())
212	uassert.Equal(t, "delayed", Delayed.String())
213}