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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

coinio.gno

5.55 Kb · 142 lines
  1// Package coinio is the chain-facing coin plumbing that every
  2// value-holding realm on gno.land repeats: verified payment receipt,
  3// disciplined payout, and reserve-protected surplus sweeping. It was
  4// extracted from two audited, pearl-1-validated realms (vault and
  5// bounties) whose implementations of these mechanics were
  6// line-identical.
  7//
  8// The package is PURE and STATELESS: it holds no balances, no roles,
  9// and no configuration; all state stays in the consuming realm (and
 10// its accounting package, e.g. feeledger). It emits no events —
 11// consumers emit their own. Every function either succeeds or panics,
 12// aborting the transaction: these are payment guards, and no failure
 13// here has a meaningful recovery path.
 14//
 15// CAPABILITY CONTRACT: pure packages cannot declare crossing
 16// functions (a first `realm` parameter), so every coin-moving function
 17// here uses the canonical secondary-parameter shape `(_ int, rlm
 18// realm, ...)` — the same pattern as chain treasury packages. Pass 0
 19// and your crossing entrypoint's own live `cur` (forwarded through
 20// non-crossing calls keeps it current). Each function asserts
 21// rlm.IsCurrent() before acting, so a stale, stored, or Previous()
 22// realm value fails closed (the designation-forgery guard secondary
 23// realm parameters require). Coins can only move FROM rlm.Address() —
 24// the calling realm itself — so no consumer can spend another realm's
 25// funds through this package.
 26//
 27// AUTHORIZATION is the consumer's responsibility: coinio decides HOW
 28// coins move, never WHO may move them. Gate your entrypoints before
 29// calling in.
 30//
 31// ORDERING CONTRACT (the one invariant coinio cannot enforce): debit
 32// your own accounting BEFORE calling Payout or Sweep
 33// (checks-effects-interactions). A panic inside coinio aborts the
 34// whole transaction, reverting your debit with it — that is what makes
 35// the debit-first order safe.
 36package coinio
 37
 38import (
 39	"chain"
 40	"chain/banker"
 41	"chain/runtime/unsafe"
 42)
 43
 44// Receive verifies the canonical receipt-guaranteed payment shape and
 45// returns the payer and amount:
 46//
 47//   - the caller of the consuming realm's entrypoint is an EOA via
 48//     MsgCall (IsUserCall) — the only shape where the chain guarantees
 49//     the -send envelope landed at the realm address before the body
 50//     ran (realms and maketx-run are rejected);
 51//   - the envelope is exactly one coin of the given denom;
 52//   - the amount is positive.
 53//
 54// Receive is a READ of the transaction's send envelope, NOT a
 55// consumption: a stateless pure package cannot mark an envelope spent,
 56// so calling Receive N times in one transaction reports the same
 57// envelope N times. CALL IT AT MOST ONCE PER TRANSACTION and credit
 58// its result at most once — a consumer that credits per call mints
 59// unbacked liabilities from a single -send.
 60//
 61// Call it first (and once) in any payable crossing entrypoint:
 62//
 63//	func Deposit(cur realm) {
 64//		from, amount := coinio.Receive(0, cur, "ugnot")
 65//		// credit `from` with `amount` in your accounting
 66//	}
 67func Receive(_ int, rlm realm, denom string) (from address, amount int64) {
 68	if !rlm.IsCurrent() {
 69		panic("coinio: realm capability is not current")
 70	}
 71	if denom == "" {
 72		panic("coinio: empty denom")
 73	}
 74	if !rlm.Previous().IsUserCall() {
 75		panic("coinio: payment must be a direct EOA call with -send (realms and maketx-run are rejected)")
 76	}
 77	sent := unsafe.OriginSend()
 78	if len(sent) != 1 || sent[0].Denom != denom {
 79		panic("coinio: send exactly one coin type: " + denom)
 80	}
 81	if sent[0].Amount <= 0 {
 82		panic("coinio: amount must be positive")
 83	}
 84	return rlm.Previous().Address(), sent[0].Amount
 85}
 86
 87// Payout sends amount of denom from the calling realm's own address to
 88// `to`. DEBIT YOUR ACCOUNTING FIRST — a panic here (or anywhere later
 89// in the transaction) reverts the debit together with the send.
 90func Payout(_ int, rlm realm, to address, denom string, amount int64) {
 91	if !rlm.IsCurrent() {
 92		panic("coinio: realm capability is not current")
 93	}
 94	var zero address
 95	if to == zero {
 96		panic("coinio: empty payout address")
 97	}
 98	if denom == "" {
 99		panic("coinio: empty denom")
100	}
101	if amount <= 0 {
102		panic("coinio: amount must be positive")
103	}
104	bk := banker.NewBanker(banker.BankerTypeRealmSend, rlm)
105	bk.SendCoins(rlm.Address(), to, chain.Coins{chain.NewCoin(denom, amount)})
106}
107
108// Sweep sends the surplus of a single denomination — everything the
109// calling realm holds above `reserve` — to `to`, and returns the swept
110// amount. Pass your total liabilities as the reserve for the denom
111// your accounting tracks, and 0 for foreign denominations. Panics if
112// there is no positive surplus, so reserved funds are untouchable by
113// construction. One denomination per call keeps the operation gas-
114// bounded regardless of how many junk denoms third parties force-send.
115func Sweep(_ int, rlm realm, to address, denom string, reserve int64) int64 {
116	if !rlm.IsCurrent() {
117		panic("coinio: realm capability is not current")
118	}
119	var zero address
120	if to == zero {
121		panic("coinio: empty sweep address")
122	}
123	if denom == "" {
124		panic("coinio: empty denom")
125	}
126	if reserve < 0 {
127		panic("coinio: negative reserve")
128	}
129	bk := banker.NewBanker(banker.BankerTypeRealmSend, rlm)
130	surplus := bk.GetCoin(rlm.Address(), denom) - reserve
131	if surplus <= 0 {
132		panic("coinio: no surplus to sweep for " + denom)
133	}
134	bk.SendCoins(rlm.Address(), to, chain.Coins{chain.NewCoin(denom, surplus)})
135	return surplus
136}
137
138// HeldAt returns the amount of denom held at addr. Read-only; usable
139// from views and Render without a realm capability.
140func HeldAt(addr address, denom string) int64 {
141	return banker.NewReadonlyBanker().GetCoin(addr, denom)
142}