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}