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

types.gno

6.73 Kb · 173 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 an ID, name, symbol, and a certain
 77// number of decimal places. It maintains a ledger for tracking balances and
 78// allowances 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	// Identifier precomputed in NewToken to make ID() a cheap field read.
 85	id string
 86	// Name of the token (e.g., "Dummy Token").
 87	name string
 88	// Symbol of the token (e.g., "DUMMY").
 89	symbol string
 90	// Number of decimal places used for the token's precision.
 91	decimals int
 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 it is included in Token.ID(), which is
132// emitted in events and frequently used as a registry slug; banning `.` `/`
133// and whitespace here prevents downstream parsers from being fooled by
134// ambiguous IDs. Name is for display only and allows any valid UTF-8 except
135// control characters.
136const (
137	MaxNameLen   = 64
138	MaxSymbolLen = 11
139	MaxDecimals  = 18
140)
141
142const (
143	MintEvent     = "Mint"
144	BurnEvent     = "Burn"
145	TransferEvent = "Transfer"
146	ApprovalEvent = "Approval"
147)
148
149type fnTeller struct {
150	accountFn func(_ int, rlm realm) address
151	*Token
152}
153
154var _ Teller = (*fnTeller)(nil)
155
156// IsCanonicalTeller reports whether t is the canonical *fnTeller produced by
157// Token.CallerTeller / RealmTeller / RealmSubTeller / ReadonlyTeller /
158// ImpersonateTeller. Use this at any public entry point that accepts a
159// Teller from an external caller before invoking its methods.
160//
161// Foreign types — including embedding-based wrappers like
162// `type Evil struct { grc20.Teller }` — are rejected because type
163// assertions are nominal: *Evil is not *fnTeller, regardless of method
164// promotion. This is the reliable defense; the unexported-marker "seal"
165// pattern is bypassable via embedding (see
166// p/test/seal/filetests/z_seal_iface_embedding_filetest.gno).
167//
168// Mirrors the precedent of chain/banker.IsCanonical and
169// p/jaekwon/allowancesender's canonical-impl check.
170func IsCanonicalTeller(t Teller) bool {
171	_, ok := t.(*fnTeller)
172	return ok
173}