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/vesting/v0

Package
Open in gnoweb ↗

Overview

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

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • vesting.gnogno
  • vesting_test.gnogno
vesting.gnogno
1// Package vesting computes a gno.land account's vesting curve, as a PURE2// calculator: no state, no balances, no transfers.3//4// It is not a vesting scheme of its own. It is a faithful reimplementation of5// the one the CHAIN enforces, tm2's std.VestingSchedule, so that a realm can6// answer "how much of this balance can actually move right now" with the same7// arithmetic the ante handler uses. Divergence here is worse than useless, so8// every rule below is copied from tm2/pkg/std/vesting.go rather than designed:9//10//   - Times are unix seconds, and nothing compares them against the chain's11//     clock. A schedule already over when the chain starts is valid and vests12//     everything at once.13//   - [Continuous] vests linearly between Start and End. [Delayed] is a cliff:14//     nothing before End, everything at or after it, and Start is ignored.15//   - Rounding is DOWN, always, so the account never counts as spendable a16//     ugnot the chain would refuse to move.17//   - A zero Original is "no schedule", which locks nothing.18//19// # Why this is not p/moul/x/daily/cliffvesting20//21// That package is the employee-grant shape (start, cliff, end) and multiplies22// in plain int64, which silently wraps for the amounts a chain actually holds:23// over the 63,158,400 second mainnet term, any grant above 146,036 GNOT24// overflows, and a 318,720,000 GNOT grant reports a NEGATIVE vested amount.25// tm2 reaches for math/big at exactly this point. gno has no math/big, so26// [Schedule.Vested] goes through a 128-bit intermediate instead.27//28// # What this package cannot do29//30// Find out an address's schedule. Realm code cannot read one: the VM's whole31// view of an account is banker.GetCoins, which returns the TOTAL balance with32// the locked part included, and no native exposes std.VestingSchedule. The33// schedule has to come from the caller. See gno.land/r/moul/vesting for what34// that means for a page that wants to show real numbers.35package vesting3637import (38	"errors"39	"math/bits"40)4142// Type selects the curve.43type Type uint84445const (46	// Continuous vests linearly from Start to End. The tm2 default.47	Continuous Type = iota48	// Delayed is a cliff: nothing vests until End, then all of it.49	Delayed50)5152func (t Type) String() string {53	if t == Delayed {54		return "delayed"55	}56	return "continuous"57}5859var (60	ErrNegativeOriginal = errors.New("vesting: original amount cannot be negative")61	ErrEndNotPositive   = errors.New("vesting: end time must be positive")62	ErrNegativeStart    = errors.New("vesting: start time cannot be negative")63	ErrStartAfterEnd    = errors.New("vesting: start time must be before end time")64)6566// Schedule is one account's vesting plan, as the chain stores it.67type Schedule struct {68	Original int64 // the granted amount, in the smallest unit69	Start    int64 // unix seconds; ignored by Delayed70	End      int64 // unix seconds71	Type     Type72}7374// New validates a schedule, applying tm2's own rules in tm2's own order.75//76// The Start >= 0 check is not cosmetic and is the reason Vested can stay in77// int64 for its subtractions: with 0 <= Start < End, neither End-Start nor78// now-Start can overflow. tm2 rejects a negative start for exactly this.79func New(original, start, end int64, typ Type) (Schedule, error) {80	s := Schedule{Original: original, Start: start, End: end, Type: typ}81	if original < 0 {82		return Schedule{}, ErrNegativeOriginal83	}84	if s.IsZero() {85		return s, nil // no schedule; the other fields do not matter86	}87	if end <= 0 {88		return Schedule{}, ErrEndNotPositive89	}90	if typ != Delayed {91		if start < 0 {92			return Schedule{}, ErrNegativeStart93		}94		if start >= end {95			return Schedule{}, ErrStartAfterEnd96		}97	}98	return s, nil99}100101// IsZero reports whether there is no schedule at all, which locks nothing.102func (s Schedule) IsZero() bool { return s.Original == 0 }103104// Vested returns how much of Original has vested at unix time now.105func (s Schedule) Vested(now int64) int64 {106	if s.IsZero() {107		return 0108	}109	if now >= s.End {110		return s.Original111	}112	if s.Type == Delayed {113		return 0 // a cliff vests nothing until End114	}115	if now <= s.Start {116		return 0117	}118	return mulDiv(s.Original, now-s.Start, s.End-s.Start)119}120121// Locked returns the part of Original that has not vested at unix time now.122// This is what the chain refuses to let leave the account.123func (s Schedule) Locked(now int64) int64 { return s.Original - s.Vested(now) }124125// Spendable returns how much of balance can actually move at unix time now.126//127// balance is the account's TOTAL, which is what banker.GetCoins reports. The128// locked part is capped at the balance: an account that has already spent down129// to less than it still owes to the schedule has nothing spendable, not a130// negative amount. tm2 reaches the same answer by subtracting locked coins131// from the balance and refusing the transfer if the result does not cover it.132func (s Schedule) Spendable(balance, now int64) int64 {133	if balance <= 0 {134		return 0135	}136	locked := s.Locked(now)137	if locked >= balance {138		return 0139	}140	return balance - locked141}142143// PermilleVested returns the vested share at now in tenths of a percent,144// rounded down, so a page can show one decimal without a float. Integer only:145// no float ever reaches consensus state.146func (s Schedule) PermilleVested(now int64) int64 {147	if s.IsZero() {148		return 1000 // nothing to vest is fully vested149	}150	return mulDiv(s.Vested(now), 1000, s.Original)151}152153// RemainingSeconds returns how long until the schedule completes, zero once it154// has. Reported rather than formatted: the caller owns how a duration reads.155func (s Schedule) RemainingSeconds(now int64) int64 {156	if s.IsZero() || now >= s.End {157		return 0158	}159	return s.End - now160}161162// mulDiv computes floor(a*b/den) through a 128-bit intermediate, so a product163// that leaves int64 does not wrap.164//165// Every caller here passes 0 <= b <= den with den > 0, which is what makes the166// bits.Div64 precondition hold: the quotient is then at most a, so it fits in167// 64 bits, which is exactly the hi < den that Div64 requires and panics168// without. Keep that invariant at the call site, not by checking it here.169func mulDiv(a, b, den int64) int64 {170	if a == 0 || b == 0 {171		return 0172	}173	hi, lo := bits.Mul64(uint64(a), uint64(b))174	q, _ := bits.Div64(hi, lo, uint64(den))175	return int64(q)176}177

Functions

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

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