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