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

cliffvestingdemo.gno

3.44 Kb · 87 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/v0)
 3// library: the cliff step, the linear ramp, and integer rounding.
 4//
 5// It contains no vesting logic of its own and holds no coins. Stateless, so
 6// Render is deterministic — which is precisely what the library is for.
 7package cliffvestingdemo
 8
 9import (
10	"strconv"
11	"strings"
12
13	"gno.land/p/moul/x/daily/cliffvesting/v0"
14)
15
16// Render renders the demo for gnoweb.
17func Render(path string) string {
18	var b strings.Builder
19	b.WriteString("# Cliff Vesting\n\n")
20	b.WriteString("A pure vesting calculator, demoing the ")
21	b.WriteString("[`p/moul/x/daily/cliffvesting`](/p/moul/x/daily/cliffvesting/v0) library.\n\n")
22
23	// 1200 tokens over 12 months, 3-month cliff.
24	s, err := cliffvesting.New(1200, 0, 3, 12)
25	if err != nil {
26		return "error: " + err.Error()
27	}
28
29	b.WriteString("## A 12-month grant with a 3-month cliff\n\n")
30	b.WriteString("`1200` tokens, `Start = 0`, `Cliff = 3`, `End = 12`.\n\n")
31	b.WriteString("| month | vested | unvested | % |\n|---|---|---|---|\n")
32	for m := int64(0); m <= 12; m++ {
33		note := ""
34		switch m {
35		case 2:
36			note = " ← nothing yet"
37		case 3:
38			note = " ← **cliff**"
39		case 12:
40			note = " ← fully vested"
41		}
42		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
43			strconv.FormatInt(s.Vested(m), 10) + " | " +
44			strconv.FormatInt(s.Unvested(m), 10) + " | " +
45			strconv.FormatInt(s.PercentVested(m), 10) + "%" + note + " |\n")
46	}
47
48	b.WriteString("\nThe cliff is a **step, not a ramp**: at month 3 a quarter of the term ")
49	b.WriteString("has elapsed, so `")
50	b.WriteString(strconv.FormatInt(s.CliffAmount(), 10))
51	b.WriteString("` tokens unlock at once. After that it accrues linearly.\n\n")
52
53	b.WriteString("## Claiming\n\n")
54	b.WriteString("The library tracks no balances — the caller supplies what has already ")
55	b.WriteString("been claimed:\n\n")
56	b.WriteString("| at month | already claimed | claimable |\n|---|---|---|\n")
57	for _, c := range [][2]int64{{6, 0}, {6, 300}, {6, 600}, {12, 600}} {
58		b.WriteString("| " + strconv.FormatInt(c[0], 10) + " | " +
59			strconv.FormatInt(c[1], 10) + " | " +
60			strconv.FormatInt(s.Claimable(c[0], c[1]), 10) + " |\n")
61	}
62
63	b.WriteString("\n## Integer rounding\n\n")
64	b.WriteString("All arithmetic is integer — no float ever reaches consensus state. ")
65	b.WriteString("Rounding is **down**, so nobody is ever paid more than they earned, ")
66	b.WriteString("and the final instalment collects the remainder.\n\n")
67	odd, _ := cliffvesting.NewLinear(1000, 0, 3)
68	b.WriteString("`1000` over `3` periods:\n\n")
69	b.WriteString("| t | vested |\n|---|---|\n")
70	for m := int64(0); m <= 3; m++ {
71		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
72			strconv.FormatInt(odd.Vested(m), 10) + " |\n")
73	}
74	b.WriteString("\n`333 + 333 + 334` — the end is exactly `1000`, never `999`.\n\n")
75
76	b.WriteString("## The bug this avoids\n\n")
77	tiny, _ := cliffvesting.NewLinear(7, 0, 1000)
78	b.WriteString("When the total is smaller than the duration, computing a per-tick rate ")
79	b.WriteString("first truncates it to zero and **nothing ever vests**. Multiplying ")
80	b.WriteString("before dividing keeps it honest — `7` tokens over `1000` ticks:\n\n")
81	b.WriteString("| t | vested |\n|---|---|\n")
82	for _, m := range []int64{100, 150, 500, 1000} {
83		b.WriteString("| " + strconv.FormatInt(m, 10) + " | " +
84			strconv.FormatInt(tiny.Vested(m), 10) + " |\n")
85	}
86	return b.String()
87}