token.gno
7.22 Kb ยท 293 lines
1package grc20
2
3import (
4 "math"
5 "math/overflow"
6 "std"
7 "strconv"
8
9 "gno.land/p/demo/ufmt"
10)
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 int) (*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() int { return tok.decimals }
43
44// TotalSupply returns the total supply of the token.
45func (tok Token) TotalSupply() int64 { 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// HasAddr checks if the specified address is a known account in the bank.
51func (tok Token) HasAddr(addr std.Address) bool {
52 return tok.ledger.hasAddr(addr)
53}
54
55// BalanceOf returns the balance of the specified address.
56func (tok Token) BalanceOf(addr std.Address) int64 {
57 return tok.ledger.balanceOf(addr)
58}
59
60// Allowance returns the allowance of the specified owner and spender.
61func (tok Token) Allowance(owner, spender std.Address) int64 {
62 return tok.ledger.allowance(owner, spender)
63}
64
65func (tok Token) RenderHome() string {
66 str := ""
67 str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
68 str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals)
69 str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
70 str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
71 return str
72}
73
74// SpendAllowance decreases the allowance of the specified owner and spender.
75func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount int64) error {
76 if !owner.IsValid() {
77 return ErrInvalidAddress
78 }
79 if !spender.IsValid() {
80 return ErrInvalidAddress
81 }
82 if amount < 0 {
83 return ErrInvalidAmount
84 }
85
86 currentAllowance := led.allowance(owner, spender)
87 if currentAllowance < amount {
88 return ErrInsufficientAllowance
89 }
90
91 key := allowanceKey(owner, spender)
92 newAllowance := overflow.Sub64p(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 int64) 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 if amount < 0 {
115 return ErrInvalidAmount
116 }
117
118 var (
119 toBalance = led.balanceOf(to)
120 fromBalance = led.balanceOf(from)
121 )
122
123 if fromBalance < amount {
124 return ErrInsufficientBalance
125 }
126
127 var (
128 newToBalance = overflow.Add64p(toBalance, amount)
129 newFromBalance = overflow.Sub64p(fromBalance, amount)
130 )
131
132 led.balances.Set(string(to), newToBalance)
133
134 if newFromBalance == 0 {
135 led.balances.Remove(string(from))
136 } else {
137 led.balances.Set(string(from), newFromBalance)
138 }
139
140 std.Emit(
141 TransferEvent,
142 "from", from.String(),
143 "to", to.String(),
144 "value", strconv.Itoa(int(amount)),
145 )
146
147 return nil
148}
149
150// TransferFrom transfers tokens from the specified owner to the specified to address.
151// It first checks if the owner has sufficient balance and then decreases the allowance.
152func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount int64) error {
153 if amount < 0 {
154 return ErrInvalidAmount
155 }
156 if led.balanceOf(owner) < amount {
157 return ErrInsufficientBalance
158 }
159
160 // allowance must be sufficient
161 currentAllowance := led.allowance(owner, spender)
162 if currentAllowance < amount {
163 return ErrInsufficientAllowance
164 }
165
166 if err := led.Transfer(owner, to, amount); err != nil {
167 return err
168 }
169
170 // decrease the allowance only when transfer is successful
171 key := allowanceKey(owner, spender)
172 newAllowance := overflow.Sub64p(currentAllowance, amount)
173
174 if newAllowance == 0 {
175 led.allowances.Remove(key)
176 } else {
177 led.allowances.Set(key, newAllowance)
178 }
179
180 return nil
181}
182
183// Approve sets the allowance of the specified owner and spender.
184func (led *PrivateLedger) Approve(owner, spender std.Address, amount int64) error {
185 if !owner.IsValid() || !spender.IsValid() {
186 return ErrInvalidAddress
187 }
188 if amount < 0 {
189 return ErrInvalidAmount
190 }
191
192 led.allowances.Set(allowanceKey(owner, spender), amount)
193
194 std.Emit(
195 ApprovalEvent,
196 "owner", string(owner),
197 "spender", string(spender),
198 "value", strconv.Itoa(int(amount)),
199 )
200
201 return nil
202}
203
204// Mint increases the total supply of the token and adds the specified amount to the specified address.
205func (led *PrivateLedger) Mint(addr std.Address, amount int64) error {
206 if !addr.IsValid() {
207 return ErrInvalidAddress
208 }
209 if amount < 0 {
210 return ErrInvalidAmount
211 }
212
213 // limit amount to MaxInt64 - totalSupply
214 if amount > overflow.Sub64p(math.MaxInt64, led.totalSupply) {
215 return ErrMintOverflow
216 }
217
218 led.totalSupply += amount
219 currentBalance := led.balanceOf(addr)
220 newBalance := overflow.Add64p(currentBalance, amount)
221
222 led.balances.Set(string(addr), newBalance)
223
224 std.Emit(
225 TransferEvent,
226 "from", "",
227 "to", string(addr),
228 "value", strconv.Itoa(int(amount)),
229 )
230
231 return nil
232}
233
234// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
235func (led *PrivateLedger) Burn(addr std.Address, amount int64) error {
236 if !addr.IsValid() {
237 return ErrInvalidAddress
238 }
239 if amount < 0 {
240 return ErrInvalidAmount
241 }
242
243 currentBalance := led.balanceOf(addr)
244 if currentBalance < amount {
245 return ErrInsufficientBalance
246 }
247
248 led.totalSupply = overflow.Sub64p(led.totalSupply, amount)
249 newBalance := overflow.Sub64p(currentBalance, amount)
250
251 if newBalance == 0 {
252 led.balances.Remove(string(addr))
253 } else {
254 led.balances.Set(string(addr), newBalance)
255 }
256
257 std.Emit(
258 TransferEvent,
259 "from", string(addr),
260 "to", "",
261 "value", strconv.Itoa(int(amount)),
262 )
263
264 return nil
265}
266
267// hasAddr checks if the specified address is a known account in the ledger.
268func (led PrivateLedger) hasAddr(addr std.Address) bool {
269 return led.balances.Has(addr.String())
270}
271
272// balanceOf returns the balance of the specified address.
273func (led PrivateLedger) balanceOf(addr std.Address) int64 {
274 balance, found := led.balances.Get(addr.String())
275 if !found {
276 return 0
277 }
278 return balance.(int64)
279}
280
281// allowance returns the allowance of the specified owner and spender.
282func (led PrivateLedger) allowance(owner, spender std.Address) int64 {
283 allowance, found := led.allowances.Get(allowanceKey(owner, spender))
284 if !found {
285 return 0
286 }
287 return allowance.(int64)
288}
289
290// allowanceKey returns the key for the allowance of the specified owner and spender.
291func allowanceKey(owner, spender std.Address) string {
292 return owner.String() + ":" + spender.String()
293}