types.gno
7.30 Kb · 183 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 // origRealm is the PkgPath of the realm that created the token, captured
95 // unforgeably in NewToken. A frame-relative teller only works there.
96 origRealm string
97}
98
99// PrivateLedger is a struct that holds the balances and allowances for the
100// token. It provides administrative functions for minting, burning,
101// transferring tokens, and managing allowances.
102//
103// The PrivateLedger is not safe to expose publicly, as it contains sensitive
104// information regarding token balances and allowances, and allows direct,
105// unrestricted access to all administrative functions.
106type PrivateLedger struct {
107 // Total supply of the token managed by this ledger.
108 totalSupply int64
109 // chain.Address -> int64
110 balances avl.Tree
111 // owner.(chain.Address)+":"+spender.(chain.Address)) -> int64
112 allowances avl.Tree
113 // Pointer to the associated Token struct
114 token *Token
115}
116
117var (
118 ErrInsufficientBalance = errors.New("insufficient balance")
119 ErrInsufficientAllowance = errors.New("insufficient allowance")
120 ErrInvalidAddress = errors.New("invalid address")
121 ErrCannotTransferToSelf = errors.New("cannot send transfer to self")
122 ErrReadonly = errors.New("banker is readonly")
123 ErrRestrictedTokenOwner = errors.New("restricted to bank owner")
124 ErrMintOverflow = errors.New("mint overflow")
125 ErrInvalidAmount = errors.New("invalid amount")
126 ErrSpoofedRealm = errors.New("rlm does not match the current crossing frame")
127 ErrForeignCallerTeller = errors.New("caller teller is confined to the token's own realm")
128 ErrNotRealm = errors.New("rlm must be a realm (got EOA/origin)")
129 ErrInvalidName = errors.New("invalid token name (empty, too long, or contains control chars)")
130 ErrInvalidSymbol = errors.New("invalid token symbol (empty, too long, or contains chars outside [A-Za-z0-9_-])")
131 ErrInvalidDecimals = errors.New("invalid decimals (must be 0..18)")
132)
133
134// Construction limits. Symbol is restricted to the same charset as
135// grc20reg.validateSlug because it is included in Token.ID(), which is
136// emitted in events and frequently used as a registry slug; banning `.` `/`
137// and whitespace here prevents downstream parsers from being fooled by
138// ambiguous IDs. Name is for display only and allows any valid UTF-8 except
139// control characters.
140const (
141 MaxNameLen = 64
142 MaxSymbolLen = 11
143 MaxDecimals = 18
144)
145
146const (
147 NewTokenEvent = "NewToken"
148 MintEvent = "Mint"
149 BurnEvent = "Burn"
150 TransferEvent = "Transfer"
151 ApprovalEvent = "Approval"
152)
153
154type fnTeller struct {
155 accountFn func(_ int, rlm realm) address
156 // homeGuard marks a frame-relative teller (PrivateLedger.CallerTeller),
157 // whose actor is resolved from the invoking frame. Such a teller is only
158 // meaningful in the token's own realm and is refused anywhere else, so
159 // exporting the value cannot hand out a spend capability.
160 homeGuard bool
161 *Token
162}
163
164var _ Teller = (*fnTeller)(nil)
165
166// IsCanonicalTeller reports whether t is the canonical *fnTeller produced by
167// Token.CallerTeller / RealmTeller / RealmSubTeller / ReadonlyTeller /
168// ImpersonateTeller. Use this at any public entry point that accepts a
169// Teller from an external caller before invoking its methods.
170//
171// Foreign types — including embedding-based wrappers like
172// `type Evil struct { grc20.Teller }` — are rejected because type
173// assertions are nominal: *Evil is not *fnTeller, regardless of method
174// promotion. This is the reliable defense; the unexported-marker "seal"
175// pattern is bypassable via embedding (see
176// p/test/seal/filetests/z_seal_iface_embedding_filetest.gno).
177//
178// Mirrors the precedent of chain/banker.IsCanonical and
179// p/jaekwon/allowancesender's canonical-impl check.
180func IsCanonicalTeller(t Teller) bool {
181 _, ok := t.(*fnTeller)
182 return ok
183}