Search Apps Documentation Source Content File Folder Download Copy Actions Download

types.gno

6.81 Kb · 174 lines
  1package grc20
  2
  3import (
  4	"errors"
  5
  6	"gno.land/p/nt/avl/v0"
  7)
  8
  9// Teller interface defines the methods that a GRC20 token must implement. It
 10// extends the TokenMetadata interface to include methods for managing token
 11// transfers, allowances, and querying balances.
 12//
 13// The Teller interface is designed to ensure that any token adhering to this
 14// standard provides a consistent API for interacting with fungible tokens.
 15//
 16// SECURITY: Transfer/Approve/TransferFrom take (_ int, rlm realm, ...), so
 17// handing a Teller value to untrusted code yields a capability token to
 18// whatever Transfer/Approve/TransferFrom impl that code dispatches into.
 19// Any /p/ or /r/ function that accepts a Teller as a parameter from external
 20// callers MUST type-assert against the canonical concrete type (*fnTeller)
 21// via IsCanonicalTeller and reject otherwise. An unexported-marker "seal"
 22// does NOT defend against this — see
 23// p/test/seal/filetests/z_seal_iface_embedding_filetest.gno
 24// for the realistic embedding-bypass attack. Reference impl for the
 25// canonical-allowlist pattern: p/jaekwon/allowancesender.
 26type Teller interface {
 27	// Returns the name of the token.
 28	GetName() string
 29
 30	// Returns the symbol of the token, usually a shorter version of the
 31	// name.
 32	GetSymbol() string
 33
 34	// Returns the decimals places of the token.
 35	GetDecimals() int
 36
 37	// Returns the amount of tokens in existence.
 38	TotalSupply() int64
 39
 40	// Returns the amount of tokens owned by `account`.
 41	BalanceOf(account address) int64
 42
 43	// Moves `amount` tokens from the caller's account to `to`. rlm must
 44	// be the caller's own captured cur — verified via rlm.IsCurrent().
 45	//
 46	// Returns an error if the operation failed.
 47	Transfer(_ int, rlm realm, to address, amount int64) error
 48
 49	// Returns the remaining number of tokens that `spender` will be
 50	// allowed to spend on behalf of `owner` through {transferFrom}. This is
 51	// zero by default.
 52	//
 53	// This value changes when {approve} or {transferFrom} are called.
 54	Allowance(owner, spender address) int64
 55
 56	// Sets `amount` as the allowance of `spender` over the caller's tokens.
 57	//
 58	// Returns an error if the operation failed.
 59	//
 60	// IMPORTANT: Beware that changing an allowance with this method brings
 61	// the risk that someone may use both the old and the new allowance by
 62	// unfortunate transaction ordering. One possible solution to mitigate
 63	// this race condition is to first reduce the spender's allowance to 0
 64	// and set the desired value afterwards:
 65	// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 66	Approve(_ int, rlm realm, spender address, amount int64) error
 67
 68	// Moves `amount` tokens from `from` to `to` using the
 69	// allowance mechanism. `amount` is then deducted from the caller's
 70	// allowance.
 71	//
 72	// Returns an error if the operation failed.
 73	TransferFrom(_ int, rlm realm, from, to address, amount int64) error
 74}
 75
 76// Token represents a fungible token with a name, symbol, and a certain number
 77// of decimal places. It maintains a ledger for tracking balances and allowances
 78// of addresses.
 79//
 80// The Token struct provides methods for retrieving token metadata, such as the
 81// name, symbol, and decimals, as well as methods for interacting with the
 82// ledger, including checking balances and allowances.
 83type Token struct {
 84	// Name of the token (e.g., "Dummy Token").
 85	name string
 86	// Symbol of the token (e.g., "DUMMY").
 87	symbol string
 88	// Number of decimal places used for the token's precision.
 89	decimals int
 90	// Original realm of the token (e.g., "gno.land/r/demo/foo20").
 91	origRealm string
 92	// Pointer to the PrivateLedger that manages balances and allowances.
 93	ledger *PrivateLedger
 94}
 95
 96// PrivateLedger is a struct that holds the balances and allowances for the
 97// token. It provides administrative functions for minting, burning,
 98// transferring tokens, and managing allowances.
 99//
100// The PrivateLedger is not safe to expose publicly, as it contains sensitive
101// information regarding token balances and allowances, and allows direct,
102// unrestricted access to all administrative functions.
103type PrivateLedger struct {
104	// Total supply of the token managed by this ledger.
105	totalSupply int64
106	// chain.Address -> int64
107	balances avl.Tree
108	// owner.(chain.Address)+":"+spender.(chain.Address)) -> int64
109	allowances avl.Tree
110	// Pointer to the associated Token struct
111	token *Token
112}
113
114var (
115	ErrInsufficientBalance   = errors.New("insufficient balance")
116	ErrInsufficientAllowance = errors.New("insufficient allowance")
117	ErrInvalidAddress        = errors.New("invalid address")
118	ErrCannotTransferToSelf  = errors.New("cannot send transfer to self")
119	ErrReadonly              = errors.New("banker is readonly")
120	ErrRestrictedTokenOwner  = errors.New("restricted to bank owner")
121	ErrMintOverflow          = errors.New("mint overflow")
122	ErrInvalidAmount         = errors.New("invalid amount")
123	ErrSpoofedRealm          = errors.New("rlm does not match the current crossing frame")
124	ErrNotRealm              = errors.New("rlm must be a realm (got EOA/origin)")
125	ErrInvalidName           = errors.New("invalid token name (empty, too long, or contains control chars)")
126	ErrInvalidSymbol         = errors.New("invalid token symbol (empty, too long, or contains chars outside [A-Za-z0-9_-])")
127	ErrInvalidDecimals       = errors.New("invalid decimals (must be 0..18)")
128)
129
130// Construction limits. Symbol is restricted to the same charset as
131// grc20reg.validateSlug because Token.ID() = origRealm + "." + symbol is
132// emitted in events and frequently used as a registry slug; banning `.`
133// `/` and whitespace here prevents downstream parsers from being fooled
134// by ambiguous IDs (e.g. an attacker emitting tokens whose ID parses as
135// belonging to a victim realm). Name is for display only and allows
136// any valid UTF-8 except control characters.
137const (
138	MaxNameLen   = 64
139	MaxSymbolLen = 11
140	MaxDecimals  = 18
141)
142
143const (
144	MintEvent     = "Mint"
145	BurnEvent     = "Burn"
146	TransferEvent = "Transfer"
147	ApprovalEvent = "Approval"
148)
149
150type fnTeller struct {
151	accountFn func(_ int, rlm realm) address
152	*Token
153}
154
155var _ Teller = (*fnTeller)(nil)
156
157// IsCanonicalTeller reports whether t is the canonical *fnTeller produced by
158// Token.CallerTeller / RealmTeller / RealmSubTeller / ReadonlyTeller /
159// ImpersonateTeller. Use this at any public entry point that accepts a
160// Teller from an external caller before invoking its methods.
161//
162// Foreign types — including embedding-based wrappers like
163// `type Evil struct { grc20.Teller }` — are rejected because type
164// assertions are nominal: *Evil is not *fnTeller, regardless of method
165// promotion. This is the reliable defense; the unexported-marker "seal"
166// pattern is bypassable via embedding (see
167// p/test/seal/filetests/z_seal_iface_embedding_filetest.gno).
168//
169// Mirrors the precedent of chain/banker.IsCanonical and
170// p/jaekwon/allowancesender's canonical-impl check.
171func IsCanonicalTeller(t Teller) bool {
172	_, ok := t.(*fnTeller)
173	return ok
174}