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

cliffvestingdemo.gno

4.62 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/moul/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/moul/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/moul/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}