PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/x/daily/cliffvesting/v1

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v1
Namespace
moul / x / daily / cliffvesting
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/daily/cliffvesting/v1
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • cliffvesting.gnogno
  • cliffvesting_test.gnogno
cliffvesting_test.gnogno
1package cliffvesting23import (4	"testing"56	"gno.land/p/nt/uassert/v0"7)89func mustNew(t *testing.T, total, start, cliff, end int64) Schedule {10	s, err := New(total, start, cliff, end)11	uassert.NoError(t, err)12	return s13}1415func TestNewValidation(t *testing.T) {16	_, err := New(0, 0, 0, 100)17	uassert.ErrorIs(t, err, ErrBadTotal)18	_, err = New(-5, 0, 0, 100)19	uassert.ErrorIs(t, err, ErrBadTotal)2021	_, err = New(100, 50, 50, 50)22	uassert.ErrorIs(t, err, ErrBadDuration, "end must be after start")23	_, err = New(100, 50, 50, 10)24	uassert.ErrorIs(t, err, ErrBadDuration)2526	_, err = New(100, 0, 200, 100)27	uassert.ErrorIs(t, err, ErrCliffAfter)2829	_, err = New(100, 50, 10, 100)30	uassert.ErrorIs(t, err, ErrCliffBefore)3132	_, err = New(100, 0, 100, 100)33	uassert.NoError(t, err, "a cliff exactly at the end is allowed")34}3536func TestNewLinearHasNoCliff(t *testing.T) {37	s, err := NewLinear(1000, 0, 100)38	uassert.NoError(t, err)39	uassert.False(t, s.HasCliff())40	uassert.Equal(t, int64(0), s.CliffAmount())41	uassert.Equal(t, int64(500), s.Vested(50), "linear from the start")42}4344// TestNothingBeforeCliff is the defining behaviour.45func TestNothingBeforeCliff(t *testing.T) {46	s := mustNew(t, 1200, 0, 300, 1200)4748	uassert.Equal(t, int64(0), s.Vested(0))49	uassert.Equal(t, int64(0), s.Vested(299), "one tick before the cliff, still nothing")50	uassert.Equal(t, int64(1200), s.Unvested(299))51}5253// TestCliffReleasesElapsedPortionAtOnce pins the step, not a ramp.54func TestCliffReleasesElapsedPortionAtOnce(t *testing.T) {55	s := mustNew(t, 1200, 0, 300, 1200)5657	uassert.Equal(t, int64(300), s.Vested(300), "25% of the term elapsed, released in one step")58	uassert.Equal(t, int64(300), s.CliffAmount())59	uassert.Equal(t, int64(25), s.PercentVested(300))60}6162func TestLinearAfterCliff(t *testing.T) {63	s := mustNew(t, 1200, 0, 300, 1200)6465	uassert.Equal(t, int64(600), s.Vested(600))66	uassert.Equal(t, int64(900), s.Vested(900))67	uassert.Equal(t, int64(1199), s.Vested(1199))68}6970// TestExactlyTotalAtEnd is what rounding down must never break.71func TestExactlyTotalAtEnd(t *testing.T) {72	s := mustNew(t, 1000, 0, 0, 3) // 1000/3 does not divide evenly7374	uassert.Equal(t, int64(333), s.Vested(1))75	uassert.Equal(t, int64(666), s.Vested(2))76	uassert.Equal(t, int64(1000), s.Vested(3), "the last instalment collects the remainder")77	uassert.Equal(t, int64(0), s.Unvested(3))78	uassert.Equal(t, int64(1000), s.Vested(99999), "still exactly total long after the end")79}8081// TestSmallTotalLongDuration is the rounding bug this package exists to avoid:82// with total < duration, dividing before multiplying truncates the rate to 083// and nothing ever vests.84func TestSmallTotalLongDuration(t *testing.T) {85	s := mustNew(t, 7, 0, 0, 1000)8687	uassert.Equal(t, int64(0), s.Vested(100), "7*100/1000 = 0.7, floors to 0")88	uassert.Equal(t, int64(1), s.Vested(150), "7*150/1000 = 1.05, floors to 1")89	uassert.Equal(t, int64(3), s.Vested(500), "half the term, half of 7, floored")90	uassert.Equal(t, int64(7), s.Vested(1000), "and still exactly total at the end")91}9293func TestVestedIsMonotonic(t *testing.T) {94	s := mustNew(t, 997, 10, 40, 310)9596	prev := int64(-1)97	for tick := int64(0); tick <= 320; tick++ {98		v := s.Vested(tick)99		uassert.True(t, v >= prev, "vested must never decrease")100		uassert.True(t, v <= s.Total, "vested must never exceed total")101		prev = v102	}103}104105func TestUnvestedComplementsVested(t *testing.T) {106	s := mustNew(t, 500, 0, 100, 400)107	for _, tick := range []int64{0, 99, 100, 250, 399, 400, 500} {108		uassert.Equal(t, s.Total, s.Vested(tick)+s.Unvested(tick),109			"vested + unvested must always equal total")110	}111}112113func TestClaimable(t *testing.T) {114	s := mustNew(t, 1000, 0, 0, 100)115116	uassert.Equal(t, int64(500), s.Claimable(50, 0))117	uassert.Equal(t, int64(300), s.Claimable(50, 200), "already-claimed is deducted")118	uassert.Equal(t, int64(0), s.Claimable(50, 500), "nothing left right now")119	uassert.Equal(t, int64(500), s.Claimable(100, 500), "the rest at the end")120}121122// TestClaimableNeverNegative guards the case where bookkeeping says more was123// claimed than has vested — the caller gets 0, not a negative payout.124func TestClaimableNeverNegative(t *testing.T) {125	s := mustNew(t, 1000, 0, 0, 100)126	uassert.Equal(t, int64(0), s.Claimable(10, 999))127}128129func TestPercentVested(t *testing.T) {130	s := mustNew(t, 1000, 0, 0, 100)131	uassert.Equal(t, int64(0), s.PercentVested(0))132	uassert.Equal(t, int64(50), s.PercentVested(50))133	uassert.Equal(t, int64(99), s.PercentVested(99))134	uassert.Equal(t, int64(100), s.PercentVested(100))135	uassert.Equal(t, int64(100), s.PercentVested(1000))136}137138// TestNonZeroStart checks the schedule is relative to Start, not to zero.139func TestNonZeroStart(t *testing.T) {140	s := mustNew(t, 400, 1000, 1100, 1400)141142	uassert.Equal(t, int64(0), s.Vested(999), "before the start")143	uassert.Equal(t, int64(0), s.Vested(1099), "before the cliff")144	uassert.Equal(t, int64(100), s.Vested(1100), "cliff: a quarter elapsed")145	uassert.Equal(t, int64(200), s.Vested(1200))146	uassert.Equal(t, int64(400), s.Vested(1400))147}148149func TestDurationAndFlags(t *testing.T) {150	s := mustNew(t, 100, 10, 20, 110)151	uassert.Equal(t, int64(100), s.Duration())152	uassert.True(t, s.HasCliff())153	uassert.False(t, s.IsFullyVested(109))154	uassert.True(t, s.IsFullyVested(110))155	uassert.True(t, s.IsFullyVested(999))156}157158// TestCliffAtEndIsAllOrNothing covers the degenerate schedule.159func TestCliffAtEndIsAllOrNothing(t *testing.T) {160	s := mustNew(t, 100, 0, 100, 100)161	uassert.Equal(t, int64(0), s.Vested(99))162	uassert.Equal(t, int64(100), s.Vested(100))163	uassert.Equal(t, int64(100), s.CliffAmount())164}165166// TestVestedDoesNotOverflow pins the defect that forced v1.167//168// v0 computed total*elapsed in plain int64. Over a term measured in seconds,169// the product passes 2^63 for any grant above roughly 146,036 whole coins, and170// the wrap is silent: the figures below came back NEGATIVE, which every caller171// then treated as a real vested amount.172func TestVestedDoesNotOverflow(t *testing.T) {173	// The real gno.land mainnet vesting term, 2026-09-12 to 2028-09-12.174	const (175		start int64 = 1789225200176		end   int64 = 1852383600177		term        = end - start // 63,158,400 seconds178	)179180	for _, tc := range []struct {181		name  string182		total int64183	}{184		{"just under the v0 ceiling", 146035000000},185		{"just over the v0 ceiling", 146037000000},186		{"the largest genesis grant", 318720000000000},187		{"the whole genesis allocation", 1332999998328067},188	} {189		s, err := NewLinear(tc.total, start, end)190		uassert.NoError(t, err, tc.name)191192		// Halfway through the term is exactly half the grant, and the naive193		// int64 product has long since wrapped by this size.194		uassert.Equal(t, tc.total/2, s.Vested(start+term/2), tc.name)195196		// The curve stays inside its bounds and never goes backwards.197		prev := int64(0)198		for i := int64(0); i <= 8; i++ {199			got := s.Vested(start + term*i/8)200			uassert.True(t, got >= prev, tc.name+": vested went backwards")201			uassert.True(t, got >= 0, tc.name+": vested went negative")202			uassert.True(t, got <= tc.total, tc.name+": vested exceeded the total")203			prev = got204		}205		uassert.Equal(t, tc.total, prev, tc.name)206207		// PercentVested multiplies by 100 on top, so it has its own ceiling.208		// It is 49 and not 50 at halfway for an ODD total: Vested floors to209		// (total-1)/2 and the percentage floors again, so two roundings in the210		// same direction land just under. That is the documented behaviour,211		// not the overflow, so the assertion allows both.212		half := s.PercentVested(start + term/2)213		uassert.True(t, half == 49 || half == 50, tc.name+": halfway percent left 49..50")214		uassert.Equal(t, int64(100), s.PercentVested(end), tc.name)215	}216}217

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.