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

pullpaymentdemo.gno

3.62 Kb · 87 lines
 1// Package pullpaymentdemo is a small gnoweb demo of the escrow ledger provided
 2// by the [p/moul/x/daily/pullpayment](/p/moul/x/daily/pullpayment/v0) library:
 3// crediting a split, claiming it, and why a reentrant claim gets nothing.
 4//
 5// It contains no ledger logic of its own and holds no coins. Stateless, so
 6// Render is deterministic — which is precisely what the library is for.
 7package pullpaymentdemo
 8
 9import (
10	"strconv"
11	"strings"
12
13	"gno.land/p/moul/x/daily/pullpayment/v0"
14)
15
16// Render renders the demo for gnoweb.
17func Render(path string) string {
18	var b strings.Builder
19	b.WriteString("# Pull Payment\n\n")
20	b.WriteString("Credit and let recipients withdraw, demoing the ")
21	b.WriteString("[`p/moul/x/daily/pullpayment`](/p/moul/x/daily/pullpayment/v0) library.\n\n")
22
23	b.WriteString("## Why not just send?\n\n")
24	b.WriteString("Pushing value hands control to the recipient in the middle of your ")
25	b.WriteString("state transition, and a hostile recipient re-enters before you have ")
26	b.WriteString("finished updating. Pulling inverts it: you record a debt, and the ")
27	b.WriteString("recipient's own withdrawal is the only state being touched.\n\n")
28
29	l := pullpayment.New()
30	l.CreditMany([]string{"alice", "bob", "carol"}, []int64{500, 300, 200})
31
32	b.WriteString("## A split, credited\n\n")
33	b.WriteString(table(l))
34	b.WriteString("\nThe holding realm must keep `")
35	b.WriteString(strconv.FormatInt(l.TotalOwed(), 10))
36	b.WriteString("` in reserve — that is what `TotalOwed` is for.\n\n")
37
38	b.WriteString("## Bob claims\n\n")
39	amt, _ := l.Withdraw("bob")
40	b.WriteString("`Withdraw(\"bob\")` → `" + strconv.FormatInt(amt, 10) + "`, and the ledger ")
41	b.WriteString("is updated **before** the caller transfers anything:\n\n")
42	b.WriteString(table(l))
43	b.WriteString("\nReserve is now `" + strconv.FormatInt(l.TotalOwed(), 10) + "`.\n\n")
44
45	b.WriteString("## The reentrancy attempt\n\n")
46	b.WriteString("Bob's handler calls straight back in, before the transfer completes:\n\n")
47	again, err := l.Withdraw("bob")
48	b.WriteString("- second `Withdraw(\"bob\")` → `" + strconv.FormatInt(again, 10) + "`, error: `")
49	if err != nil {
50		b.WriteString(err.Error())
51	} else {
52		b.WriteString("<nil>")
53	}
54	b.WriteString("`\n\n")
55	b.WriteString("Nothing left to take. The balance was deleted before control left the ")
56	b.WriteString("function — checks, effects, *then* interactions. Lifetime withdrawn is ")
57	b.WriteString("still `" + strconv.FormatInt(l.TotalWithdrawn(), 10) + "`, not double.\n\n")
58
59	b.WriteString("## Credits accumulate\n\n")
60	l.Credit("alice", 100)
61	b.WriteString("`Credit(\"alice\", 100)` on top of her existing balance:\n\n")
62	b.WriteString(table(l))
63	b.WriteString("\nOne withdrawal collects the lot — no dust left behind.\n\n")
64
65	b.WriteString("## Batch credits are all-or-nothing\n\n")
66	b.WriteString("A split that is partly invalid applies **none** of itself; a ledger ")
67	b.WriteString("half-agreeing with the funds it guards is worse than a rejected call:\n\n")
68	before := l.TotalOwed()
69	batchErr := l.CreditMany([]string{"dave", "eve"}, []int64{10, -1})
70	b.WriteString("- `CreditMany([dave, eve], [10, -1])` → `")
71	if batchErr != nil {
72		b.WriteString(batchErr.Error())
73	}
74	b.WriteString("`\n- total owed before: `" + strconv.FormatInt(before, 10) + "`, after: `")
75	b.WriteString(strconv.FormatInt(l.TotalOwed(), 10) + "` — unchanged, and `dave` was not credited\n")
76	return b.String()
77}
78
79func table(l *pullpayment.Ledger) string {
80	var b strings.Builder
81	b.WriteString("| payee | owed |\n|---|---|\n")
82	l.Iterate(func(p string, amt int64) bool {
83		b.WriteString("| `" + p + "` | " + strconv.FormatInt(amt, 10) + " |\n")
84		return false
85	})
86	return b.String()
87}