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.

cliffvestingdemo.gno

4.65 Kb · 112 lines
  1// Package cliffvestingdemo is a small gnoweb demo of the vesting calculator
  2// provided by the [p/moul/x/daily/cliffvesting](/p/nym-moultest123/x/daily/cliffvesting/v1)
  3// library: the cliff step, the linear ramp, integer rounding, and the two
  4// arithmetic bugs the library exists to avoid.
  5//
  6// It contains no vesting logic of its own and holds no coins. Stateless, so
  7// Render is deterministic, which is precisely what the library is for.
  8package cliffvestingdemo
  9
 10import (
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/nym-moultest123/x/daily/cliffvesting/v1"
 15)
 16
 17// Render renders the demo for gnoweb.
 18func Render(path string) string {
 19	var b strings.Builder
 20	b.WriteString("# Cliff Vesting\n\n")
 21	b.WriteString("A pure vesting calculator, demoing the ")
 22	b.WriteString("[`p/moul/x/daily/cliffvesting`](/p/nym-moultest123/x/daily/cliffvesting/v1) library.\n\n")
 23
 24	// 1200 tokens over 12 months, 3-month cliff.
 25	s, err := cliffvesting.New(1200, 0, 3, 12)
 26	if err != nil {
 27		return "error: " + err.Error()
 28	}
 29
 30	b.WriteString("## A 12-month grant with a 3-month cliff\n\n")
 31	b.WriteString("`1200` tokens, `Start = 0`, `Cliff = 3`, `End = 12`.\n\n")
 32	b.WriteString("| month | vested | unvested | % |\n|---|---|---|---|\n")
 33	for m := int64(0); m <= 12; m++ {
 34		note := ""
 35		switch m {
 36		case 2:
 37			note = " ← nothing yet"
 38		case 3:
 39			note = " ← **cliff**"
 40		case 12:
 41			note = " ← fully vested"
 42		}
 43		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
 44			strconv.FormatInt(s.Vested(m), 10) + " | " +
 45			strconv.FormatInt(s.Unvested(m), 10) + " | " +
 46			strconv.FormatInt(s.PercentVested(m), 10) + "%" + note + " |\n")
 47	}
 48
 49	b.WriteString("\nThe cliff is a **step, not a ramp**: at month 3 a quarter of the term ")
 50	b.WriteString("has elapsed, so `")
 51	b.WriteString(strconv.FormatInt(s.CliffAmount(), 10))
 52	b.WriteString("` tokens unlock at once. After that it accrues linearly.\n\n")
 53
 54	b.WriteString("## Claiming\n\n")
 55	b.WriteString("The library tracks no balances, so the caller supplies what has already ")
 56	b.WriteString("been claimed:\n\n")
 57	b.WriteString("| at month | already claimed | claimable |\n|---|---|---|\n")
 58	for _, c := range [][2]int64{{6, 0}, {6, 300}, {6, 600}, {12, 600}} {
 59		b.WriteString("| " + strconv.FormatInt(c[0], 10) + " | " +
 60			strconv.FormatInt(c[1], 10) + " | " +
 61			strconv.FormatInt(s.Claimable(c[0], c[1]), 10) + " |\n")
 62	}
 63
 64	b.WriteString("\n## Integer rounding\n\n")
 65	b.WriteString("All arithmetic is integer, no float ever reaches consensus state. ")
 66	b.WriteString("Rounding is **down**, so nobody is ever paid more than they earned, ")
 67	b.WriteString("and the final instalment collects the remainder.\n\n")
 68	odd, _ := cliffvesting.NewLinear(1000, 0, 3)
 69	b.WriteString("`1000` over `3` periods:\n\n")
 70	b.WriteString("| t | vested |\n|---|---|\n")
 71	for m := int64(0); m <= 3; m++ {
 72		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
 73			strconv.FormatInt(odd.Vested(m), 10) + " |\n")
 74	}
 75	b.WriteString("\n`333 + 333 + 334`, and the end is exactly `1000`, never `999`.\n\n")
 76
 77	b.WriteString("## Bug 1: a rate truncated to zero\n\n")
 78	tiny, _ := cliffvesting.NewLinear(7, 0, 1000)
 79	b.WriteString("When the total is smaller than the duration, computing a per-tick rate ")
 80	b.WriteString("first truncates it to zero and **nothing ever vests**. Multiplying ")
 81	b.WriteString("before dividing keeps it honest. `7` tokens over `1000` ticks:\n\n")
 82	b.WriteString("| t | vested |\n|---|---|\n")
 83	for _, m := range []int64{100, 150, 500, 1000} {
 84		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
 85			strconv.FormatInt(tiny.Vested(m), 10) + " |\n")
 86	}
 87
 88	b.WriteString("\n## Bug 2: a product that leaves int64\n\n")
 89	b.WriteString("Multiplying first is only safe if the product fits. Over a term ")
 90	b.WriteString("measured in **seconds**, `total * elapsed` passes 2^63 for any grant ")
 91	b.WriteString("above about `146,036` whole coins, and a wrapped int64 is still a ")
 92	b.WriteString("valid int64: `v0` of this library returned a **negative** vested ")
 93	b.WriteString("amount and nothing signalled it. `v1` routes the product through a ")
 94	b.WriteString("128-bit intermediate.\n\n")
 95	b.WriteString("A real two-year schedule, `1789225200` to `1852383600`, at the ")
 96	b.WriteString("halfway mark:\n\n")
 97	b.WriteString("| grant | vested at halfway | expected |\n|---|---|---|\n")
 98	const (
 99		vStart int64 = 1789225200
100		vEnd   int64 = 1852383600
101	)
102	for _, total := range []int64{100000000000, 146037000000, 318720000000000} {
103		big, err := cliffvesting.NewLinear(total, vStart, vEnd)
104		if err != nil {
105			continue
106		}
107		b.WriteString("| " + strconv.FormatInt(total, 10) + " | " +
108			strconv.FormatInt(big.Vested(vStart+(vEnd-vStart)/2), 10) + " | " +
109			strconv.FormatInt(total/2, 10) + " |\n")
110	}
111	return b.String()
112}