render.gno
5.62 Kb · 152 lines
1package merkledrop
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/moul/x/merkle/v0"
8 "gno.land/p/nt/avl/v0"
9)
10
11// demoAllocations is the example drop seeded at deploy, so the realm renders
12// working proofs instead of an empty page. A real drop calls SetDrop with a
13// root computed off chain and stores no allocations at all.
14var demoAllocations = []Allocation{
15 {Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", Amount: 100},
16 {Address: "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj", Amount: 250},
17 {Address: "g1manfred47kzduec920z88wfr64ylksmdcedlf5", Amount: 500},
18 {Address: "g1sss9uxef4l6lwc0mxq8n5v0e4vqpml7c39cxq2", Amount: 750},
19}
20
21// seed installs the example drop. Also called by the example tests: realm
22// globals persist for a whole test binary and examples run after every Test,
23// so a pinned Render must start from a known state.
24func seed() {
25 demo = demoAllocations
26 tree := demoTree()
27 root = tree.Root()
28 total = len(demo)
29 closesAt = 0
30 claimed, paid = avl.Tree{}, 0
31}
32
33func demoTree() *merkle.Tree {
34 leaves := make([][]byte, len(demo))
35 for i, a := range demo {
36 leaves[i] = []byte(Leaf(i, a.Address, a.Amount))
37 }
38 return merkle.New(leaves)
39}
40
41// DemoProof returns the proof for one seeded allocation, so the drop can be
42// exercised end to end without an off-chain generator. It is empty once a real
43// drop replaces the seeded one.
44func DemoProof(index int) string {
45 if index < 0 || index >= len(demo) {
46 return ""
47 }
48 p, err := demoTree().Proof(index)
49 if err != nil {
50 return ""
51 }
52 return p.Hex()
53}
54
55// Render serves the drop overview at "" and one allocation at "proof/<index>".
56func Render(path string) string {
57 path = strings.TrimSpace(path)
58 if strings.HasPrefix(path, "proof/") {
59 return renderProof(strings.TrimPrefix(path, "proof/"))
60 }
61 return renderIndex()
62}
63
64func renderIndex() string {
65 var b strings.Builder
66 b.WriteString("# MerkleDrop v1\n\n")
67 b.WriteString("A Merkle root gates an airdrop that pays real ugnot. Successor to\n")
68 b.WriteString("[v0](/r/moul/x/daily/merkledrop/v0), which moved no coins and whose proof scheme was\n")
69 b.WriteString("safe only by accident of encoding lengths.\n\n")
70
71 b.WriteString("## Drop\n\n")
72 b.WriteString("| Field | Value |\n|---|---|\n")
73 if len(root) == 0 {
74 b.WriteString("| Root | _none configured_ |\n")
75 } else {
76 b.WriteString("| Root | `" + Root() + "` |\n")
77 }
78 b.WriteString("| Allocations | " + strconv.Itoa(total) + " |\n")
79 b.WriteString("| Closes at height | " + closesLabel() + " |\n")
80 b.WriteString("| Claimed | " + strconv.Itoa(Claims()) + " |\n")
81 b.WriteString("| Paid out | " + strconv.FormatInt(Paid(), 10) + " ugnot |\n")
82 b.WriteString("| Funding address | `" + Address().String() + "` |\n\n")
83
84 b.WriteString("## How it differs from v0\n\n")
85 b.WriteString("1. **Domain separated leaves.** v0 hashed leaves bare and combined nodes\n")
86 b.WriteString(" commutatively, so an inner-node hash was also a valid leaf hash. It was not\n")
87 b.WriteString(" exploitable, but only because a v0 leaf preimage tops out at 61 bytes while an\n")
88 b.WriteString(" inner preimage is always 64. v1 tags leaves `0x00` and inner nodes `0x01`.\n")
89 b.WriteString("2. **Proofs bound to a position.** v0 folded a sibling list of any length. v1\n")
90 b.WriteString(" carries index and total, rebuilds the tree shape, and caps the depth.\n")
91 b.WriteString("3. **A settable root and a closing height.** v0's root was a `const`.\n")
92 b.WriteString("4. **Real coins.** v0 kept a `uint64` ledger and moved nothing.\n\n")
93
94 b.WriteString("## Leaf encoding\n\n")
95 b.WriteString("Reproduce this off chain to rebuild the tree:\n\n")
96 b.WriteString("```\n")
97 b.WriteString("leaf = \"" + leafPrefix + "|<index>|<address>|<amount>\"\n")
98 b.WriteString("tree = Tendermint simple tree, leaf 0x00, inner 0x01\n")
99 b.WriteString("```\n\n")
100
101 if len(demo) == 0 {
102 b.WriteString("## Allocations\n\n")
103 b.WriteString("A live drop commits only its root, so there is nothing to list here.\n")
104 return b.String()
105 }
106 b.WriteString("## Example allocations\n\n")
107 b.WriteString("Seeded at deploy so the drop can be exercised without a generator. A live drop\n")
108 b.WriteString("commits only a root and stores none of this.\n\n")
109 b.WriteString("| # | Address | Amount | Claimed |\n|---|---|---|---|\n")
110 for i, a := range demo {
111 b.WriteString("| [" + strconv.Itoa(i) + "](/r/moul/x/daily/merkledrop/v1:proof/" + strconv.Itoa(i) + ") | `" +
112 a.Address.String() + "` | " + strconv.FormatInt(a.Amount, 10) + " | " + yesNo(HasClaimed(i)) + " |\n")
113 }
114 return b.String()
115}
116
117func renderProof(raw string) string {
118 i, err := strconv.Atoi(raw)
119 if err != nil || i < 0 || i >= len(demo) {
120 return "# MerkleDrop v1\n\nNo allocation `" + raw + "`.\n"
121 }
122 a := demo[i]
123 var b strings.Builder
124 b.WriteString("# MerkleDrop v1: allocation " + strconv.Itoa(i) + "\n\n")
125 b.WriteString("| Field | Value |\n|---|---|\n")
126 b.WriteString("| Index | " + strconv.Itoa(i) + " |\n")
127 b.WriteString("| Total | " + strconv.Itoa(total) + " |\n")
128 b.WriteString("| Address | `" + a.Address.String() + "` |\n")
129 b.WriteString("| Amount | " + strconv.FormatInt(a.Amount, 10) + " ugnot |\n")
130 b.WriteString("| Claimed | " + yesNo(HasClaimed(i)) + " |\n\n")
131 b.WriteString("## Leaf\n\n")
132 b.WriteString("```\n" + Leaf(i, a.Address, a.Amount) + "\n```\n\n")
133 b.WriteString("## Proof\n\n")
134 b.WriteString("```\n" + DemoProof(i) + "\n```\n\n")
135 b.WriteString("Only `" + a.Address.String() + "` can spend it: the address is inside the leaf, so\n")
136 b.WriteString("the proof verifies for no one else.\n")
137 return b.String()
138}
139
140func closesLabel() string {
141 if closesAt == 0 {
142 return "never"
143 }
144 return strconv.FormatInt(closesAt, 10)
145}
146
147func yesNo(b bool) string {
148 if b {
149 return "yes"
150 }
151 return "no"
152}