Transaction
51606BDCAE9717…644A372FC4E8
Block 26,098 · index 0 · indexed
Summary
- Hash
- 51606BDCAE9717E0875EA3DF8D086E17FD187885CA59653F5B48644A372FC4E8
- Block
- 26,098
- Size
- 5593 bytes
- Gas used
- 10,561,741 / 12,674,149
- Fee
- 1000000 ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1pullpaymentdemo
- #2README.md
- #3# `gno.land/r/moul/x/daily/pullpaymentdemo/v0` **Live demo of [`p/moul/x/daily/pullpayment`](https://github.com/moul/gno-contracts/tree/main/p/moul/x/daily/pullpayment/v0).** A thin, stateless realm: it holds no state of its own and contains none of the library's logic — `Render` just exercises the package and shows the result. Because it is stateless, the rendered page is identical on every call and on every node. Render it at [`/r/moul/x/daily/pullpaymentdemo/v0`](https://gno.land/r/moul/x/daily/pullpaymentdemo/v0). The library README explains the design and the trade-offs; this realm is the worked example. <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:**  > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4gnomod.toml
- #5module = "gno.land/r/moul/x/daily/pullpaymentdemo/v0" gno = "0.9"
- #6pullpaymentdemo.gno
- #7// Package pullpaymentdemo is a small gnoweb demo of the escrow ledger provided // by the [p/moul/x/daily/pullpayment](/p/moul/x/daily/pullpayment/v0) library: // crediting a split, claiming it, and why a reentrant claim gets nothing. // // It contains no ledger logic of its own and holds no coins. Stateless, so // Render is deterministic — which is precisely what the library is for. package pullpaymentdemo import ( "strconv" "strings" "gno.land/p/moul/x/daily/pullpayment/v0" ) // Render renders the demo for gnoweb. func Render(path string) string { var b strings.Builder b.WriteString("# Pull Payment\n\n") b.WriteString("Credit and let recipients withdraw, demoing the ") b.WriteString("[`p/moul/x/daily/pullpayment`](/p/moul/x/daily/pullpayment/v0) library.\n\n") b.WriteString("## Why not just send?\n\n") b.WriteString("Pushing value hands control to the recipient in the middle of your ") b.WriteString("state transition, and a hostile recipient re-enters before you have ") b.WriteString("finished updating. Pulling inverts it: you record a debt, and the ") b.WriteString("recipient's own withdrawal is the only state being touched.\n\n") l := pullpayment.New() l.CreditMany([]string{"alice", "bob", "carol"}, []int64{500, 300, 200}) b.WriteString("## A split, credited\n\n") b.WriteString(table(l)) b.WriteString("\nThe holding realm must keep `") b.WriteString(strconv.FormatInt(l.TotalOwed(), 10)) b.WriteString("` in reserve — that is what `TotalOwed` is for.\n\n") b.WriteString("## Bob claims\n\n") amt, _ := l.Withdraw("bob") b.WriteString("`Withdraw(\"bob\")` → `" + strconv.FormatInt(amt, 10) + "`, and the ledger ") b.WriteString("is updated **before** the caller transfers anything:\n\n") b.WriteString(table(l)) b.WriteString("\nReserve is now `" + strconv.FormatInt(l.TotalOwed(), 10) + "`.\n\n") b.WriteString("## The reentrancy attempt\n\n") b.WriteString("Bob's handler calls straight back in, before the transfer completes:\n\n") again, err := l.Withdraw("bob") b.WriteString("- second `Withdraw(\"bob\")` → `" + strconv.FormatInt(again, 10) + "`, error: `") if err != nil { b.WriteString(err.Error()) } else { b.WriteString("<nil>") } b.WriteString("`\n\n") b.WriteString("Nothing left to take. The balance was deleted before control left the ") b.WriteString("function — checks, effects, *then* interactions. Lifetime withdrawn is ") b.WriteString("still `" + strconv.FormatInt(l.TotalWithdrawn(), 10) + "`, not double.\n\n") b.WriteString("## Credits accumulate\n\n") l.Credit("alice", 100) b.WriteString("`Credit(\"alice\", 100)` on top of her existing balance:\n\n") b.WriteString(table(l)) b.WriteString("\nOne withdrawal collects the lot — no dust left behind.\n\n") b.WriteString("## Batch credits are all-or-nothing\n\n") b.WriteString("A split that is partly invalid applies **none** of itself; a ledger ") b.WriteString("half-agreeing with the funds it guards is worse than a rejected call:\n\n") before := l.TotalOwed() batchErr := l.CreditMany([]string{"dave", "eve"}, []int64{10, -1}) b.WriteString("- `CreditMany([dave, eve], [10, -1])` → `") if batchErr != nil { b.WriteString(batchErr.Error()) } b.WriteString("`\n- total owed before: `" + strconv.FormatInt(before, 10) + "`, after: `") b.WriteString(strconv.FormatInt(l.TotalOwed(), 10) + "` — unchanged, and `dave` was not credited\n") return b.String() } func table(l *pullpayment.Ledger) string { var b strings.Builder b.WriteString("| payee | owed |\n|---|---|\n") l.Iterate(func(p string, amt int64) bool { b.WriteString("| `" + p + "` | " + strconv.FormatInt(amt, 10) + " |\n") return false }) return b.String() }
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]