token.gno
6.70 Kb ยท 269 lines
1package grc20
2
3import (
4 "std"
5 "strconv"
6
7 "gno.land/p/demo/ufmt"
8)
9
10const MaxUint64 = uint64(1<<63 - 1)
11
12// NewToken creates a new Token.
13// It returns a pointer to the Token and a pointer to the Ledger.
14// Expected usage: Token, admin := NewToken("Dummy", "DUMMY", 4)
15func NewToken(name, symbol string, decimals uint) (*Token, *PrivateLedger) {
16 if name == "" {
17 panic("name should not be empty")
18 }
19 if symbol == "" {
20 panic("symbol should not be empty")
21 }
22 // XXX additional checks (length, characters, limits, etc)
23
24 ledger := &PrivateLedger{}
25 token := &Token{
26 name: name,
27 symbol: symbol,
28 decimals: decimals,
29 ledger: ledger,
30 }
31 ledger.token = token
32 return token, ledger
33}
34
35// GetName returns the name of the token.
36func (tok Token) GetName() string { return tok.name }
37
38// GetSymbol returns the symbol of the token.
39func (tok Token) GetSymbol() string { return tok.symbol }
40
41// GetDecimals returns the number of decimals used to get the token's precision.
42func (tok Token) GetDecimals() uint { return tok.decimals }
43
44// TotalSupply returns the total supply of the token.
45func (tok Token) TotalSupply() uint64 { return tok.ledger.totalSupply }
46
47// KnownAccounts returns the number of known accounts in the bank.
48func (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() }
49
50// BalanceOf returns the balance of the specified address.
51func (tok Token) BalanceOf(address std.Address) uint64 {
52 return tok.ledger.balanceOf(address)
53}
54
55// Allowance returns the allowance of the specified owner and spender.
56func (tok Token) Allowance(owner, spender std.Address) uint64 {
57 return tok.ledger.allowance(owner, spender)
58}
59
60func (tok *Token) RenderHome() string {
61 str := ""
62 str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
63 str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals)
64 str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
65 str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
66 return str
67}
68
69// Getter returns a TokenGetter function that returns this token. This allows
70// storing indirect pointers to a token in a remote realm.
71func (tok *Token) Getter() TokenGetter {
72 return func() *Token {
73 return tok
74 }
75}
76
77// SpendAllowance decreases the allowance of the specified owner and spender.
78func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount uint64) error {
79 if !owner.IsValid() {
80 return ErrInvalidAddress
81 }
82 if !spender.IsValid() {
83 return ErrInvalidAddress
84 }
85
86 currentAllowance := led.allowance(owner, spender)
87 if currentAllowance < amount {
88 return ErrInsufficientAllowance
89 }
90
91 key := allowanceKey(owner, spender)
92 newAllowance := currentAllowance - amount
93
94 if newAllowance == 0 {
95 led.allowances.Remove(key)
96 } else {
97 led.allowances.Set(key, newAllowance)
98 }
99
100 return nil
101}
102
103// Transfer transfers tokens from the specified from address to the specified to address.
104func (led *PrivateLedger) Transfer(from, to std.Address, amount uint64) error {
105 if !from.IsValid() {
106 return ErrInvalidAddress
107 }
108 if !to.IsValid() {
109 return ErrInvalidAddress
110 }
111 if from == to {
112 return ErrCannotTransferToSelf
113 }
114
115 var (
116 toBalance = led.balanceOf(to)
117 fromBalance = led.balanceOf(from)
118 )
119
120 if fromBalance < amount {
121 return ErrInsufficientBalance
122 }
123
124 var (
125 newToBalance = toBalance + amount
126 newFromBalance = fromBalance - amount
127 )
128
129 led.balances.Set(string(to), newToBalance)
130 led.balances.Set(string(from), newFromBalance)
131
132 std.Emit(
133 TransferEvent,
134 "from", from.String(),
135 "to", to.String(),
136 "value", strconv.Itoa(int(amount)),
137 )
138
139 return nil
140}
141
142// TransferFrom transfers tokens from the specified owner to the specified to address.
143// It first checks if the owner has sufficient balance and then decreases the allowance.
144func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount uint64) error {
145 if led.balanceOf(owner) < amount {
146 return ErrInsufficientBalance
147 }
148
149 // allowance must be sufficient
150 currentAllowance := led.allowance(owner, spender)
151 if currentAllowance < amount {
152 return ErrInsufficientAllowance
153 }
154
155 if err := led.Transfer(owner, to, amount); err != nil {
156 return err
157 }
158
159 // decrease the allowance only when transfer is successful
160 key := allowanceKey(owner, spender)
161 newAllowance := currentAllowance - amount
162
163 if newAllowance == 0 {
164 led.allowances.Remove(key)
165 } else {
166 led.allowances.Set(key, newAllowance)
167 }
168
169 return nil
170}
171
172// Approve sets the allowance of the specified owner and spender.
173func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) error {
174 if !owner.IsValid() || !spender.IsValid() {
175 return ErrInvalidAddress
176 }
177
178 led.allowances.Set(allowanceKey(owner, spender), amount)
179
180 std.Emit(
181 ApprovalEvent,
182 "owner", string(owner),
183 "spender", string(spender),
184 "value", strconv.Itoa(int(amount)),
185 )
186
187 return nil
188}
189
190// Mint increases the total supply of the token and adds the specified amount to the specified address.
191func (led *PrivateLedger) Mint(address std.Address, amount uint64) error {
192 if !address.IsValid() {
193 return ErrInvalidAddress
194 }
195
196 // limit totalSupply to MaxUint64
197 if led.totalSupply > MaxUint64 {
198 return ErrOverflow
199 }
200
201 // limit amount to MaxUint64 - totalSupply
202 if amount > MaxUint64-led.totalSupply {
203 return ErrOverflow
204 }
205
206 led.totalSupply += amount
207 currentBalance := led.balanceOf(address)
208 newBalance := currentBalance + amount
209
210 led.balances.Set(string(address), newBalance)
211
212 std.Emit(
213 TransferEvent,
214 "from", "",
215 "to", string(address),
216 "value", strconv.Itoa(int(amount)),
217 )
218
219 return nil
220}
221
222// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
223func (led *PrivateLedger) Burn(address std.Address, amount uint64) error {
224 if !address.IsValid() {
225 return ErrInvalidAddress
226 }
227
228 currentBalance := led.balanceOf(address)
229 if currentBalance < amount {
230 return ErrInsufficientBalance
231 }
232
233 led.totalSupply -= amount
234 newBalance := currentBalance - amount
235
236 led.balances.Set(string(address), newBalance)
237
238 std.Emit(
239 TransferEvent,
240 "from", string(address),
241 "to", "",
242 "value", strconv.Itoa(int(amount)),
243 )
244
245 return nil
246}
247
248// balanceOf returns the balance of the specified address.
249func (led PrivateLedger) balanceOf(address std.Address) uint64 {
250 balance, found := led.balances.Get(address.String())
251 if !found {
252 return 0
253 }
254 return balance.(uint64)
255}
256
257// allowance returns the allowance of the specified owner and spender.
258func (led PrivateLedger) allowance(owner, spender std.Address) uint64 {
259 allowance, found := led.allowances.Get(allowanceKey(owner, spender))
260 if !found {
261 return 0
262 }
263 return allowance.(uint64)
264}
265
266// allowanceKey returns the key for the allowance of the specified owner and spender.
267func allowanceKey(owner, spender std.Address) string {
268 return owner.String() + ":" + spender.String()
269}