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

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}