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

v0 source pure

Package pullpayment is the escrow ledger behind the pull-payment pattern, as a pure, reusable package.

Readme View source

gno.land/p/moul/x/daily/pullpayment/v0

Pull-payment escrow ledgerNew, Credit, CreditMany, Withdraw, Forfeit, Balance, TotalOwed, TotalWithdrawn, Payees, Iterate, Consistent, MaxPayees.

1import "gno.land/p/moul/x/daily/pullpayment/v0"
2
3l := pullpayment.New()
4l.CreditMany([]string{"alice", "bob"}, []int64{500, 300})
5l.TotalOwed()          // 800 — what the realm must keep in reserve
6
7amt, err := l.Withdraw("bob")   // 300; the balance is ALREADY zeroed
8// ...the caller transfers `amt` only now

The classic Solidity answer to reentrancy: never push value to an address, credit it and let the recipient withdraw. A push hands control to the recipient in the middle of your state transition, and a hostile recipient re-enters before you have finished updating.

This package is the bookkeeping half only — it moves no coins. The realm holding the funds transfers after calling Withdraw, which is exactly the ordering the pattern demands: checks, effects, then interactions. The balance is already deleted when control leaves, so a reentrant Withdraw returns ErrNothing and TotalWithdrawn is not double-counted. That property has its own test.

Other guarantees, each tested:

  • CreditMany is all-or-nothing. A batch with one bad entry applies none of itself — a ledger half-agreeing with the funds it guards is worse than a rejected call.
  • Overflow is refused, not wrapped, on both a single balance and the total.
  • Consistent() is exported: TotalOwed always equals the sum of the balances.

Payees comes back sorted, never in map order, so a Render built from it cannot differ between nodes.

Live demo: r/moul/x/daily/pullpaymentdemo · render it at /r/moul/x/daily/pullpaymentdemo/v0.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

🧪 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.

Overview

Package pullpayment is the escrow ledger behind the pull-payment pattern, as a pure, reusable package.

The pattern is the classic Solidity answer to reentrancy: never push value to an address, credit it and let the recipient withdraw. A push sends control to the recipient in the middle of your state transition, and a malicious recipient re-enters before you have finished updating. Pull inverts that — the recipient calls in, and their own withdrawal is the only state being touched.

This package is the BOOKKEEPING half only: who is owed what, and the checks-effects-interactions ordering that makes a withdrawal safe. It moves no coins. The realm that holds the funds performs the transfer AFTER calling Withdraw, which is exactly the ordering the pattern demands — the balance is already zeroed when the transfer happens, so a reentrant call finds nothing left to take.

Iteration is over sorted addresses, never a built-in map range: gno map iteration order is unspecified and a Render built from one can differ between nodes, which is a consensus bug rather than a cosmetic one.

A live demo of this package is at r/moul/x/daily/pullpaymentdemo(/r/moul/x/daily/pullpaymentdemo/v0).

Constants 1

const MaxPayees

1const MaxPayees = 4096
source

MaxPayees bounds the ledger so gas stays predictable.

Variables 1

var ErrBadAmount, ErrFull, ErrNothing, ErrOverflow

1var (
2	ErrBadAmount = errors.New("pullpayment: amount must be positive")
3	ErrFull      = errors.New("pullpayment: too many payees")
4	ErrNothing   = errors.New("pullpayment: nothing to withdraw")
5	ErrOverflow  = errors.New("pullpayment: credit would overflow")
6)
source

Functions 1

func New

1func New() *Ledger
source

New returns an empty Ledger.

Types 1

type Ledger

struct
1type Ledger struct {
2	owed      map[string]int64
3	total     int64
4	withdrawn int64
5}
source

Ledger records what each address is owed.

Methods on Ledger

func Balance

method on Ledger
1func (l *Ledger) Balance(payee string) int64
source

Balance returns what payee is currently owed; zero when nothing.

func Consistent

method on Ledger
1func (l *Ledger) Consistent() bool
source

Consistent reports whether TotalOwed equals the sum of the balances. Always true through the public API; exported so callers can assert the invariant.

func Count

method on Ledger
1func (l *Ledger) Count() int
source

Count returns how many payees are owed something.

func Credit

method on Ledger
1func (l *Ledger) Credit(payee string, amount int64) error
source

Credit records that payee is owed amount more. Amounts accumulate: crediting twice owes the sum.

func CreditMany

method on Ledger
1func (l *Ledger) CreditMany(payees []string, amounts []int64) error
source

CreditMany credits several payees, applying nothing unless every entry is valid — a partial split would leave the ledger disagreeing with the funds.

func Forfeit

method on Ledger
1func (l *Ledger) Forfeit(payee string) (int64, error)
source

Forfeit drops a payee's credit without paying it, returning what was dropped.

func IsEmpty

method on Ledger
1func (l *Ledger) IsEmpty() bool
source

IsEmpty reports whether nothing is owed to anyone.

func Iterate

method on Ledger
1func (l *Ledger) Iterate(fn func(payee string, amount int64) bool)
source

Iterate calls fn for each payee in sorted order. Returning true stops.

func Payees

method on Ledger
1func (l *Ledger) Payees() []string
source

Payees returns every address with an outstanding balance, sorted.

func TotalOwed

method on Ledger
1func (l *Ledger) TotalOwed() int64
source

TotalOwed returns the sum of every outstanding balance — what the holding realm must keep in reserve.

func TotalWithdrawn

method on Ledger
1func (l *Ledger) TotalWithdrawn() int64
source

TotalWithdrawn returns the lifetime sum of successful withdrawals.

func Withdraw

method on Ledger
1func (l *Ledger) Withdraw(payee string) (int64, error)
source

Withdraw zeroes payee's balance and returns what was owed.

The caller transfers the returned amount AFTER this call. That ordering is the point of the pattern: the credit is already gone from the ledger when control passes to the recipient, so a reentrant Withdraw returns ErrNothing.

Imports 2

  • errors stdlib
  • sort stdlib

Source Files 3