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

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}