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}