token.gno
7.40 Kb ยท 302 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() || !spender.IsValid() {
85 return ErrInvalidAddress
86 }
87
88 if amount < 0 {
89 return ErrInvalidAmount
90 }
91 // do nothing
92 if amount == 0 {
93 return nil
94 }
95
96 currentAllowance := led.allowance(owner, spender)
97 if currentAllowance < amount {
98 return ErrInsufficientAllowance
99 }
100
101 key := allowanceKey(owner, spender)
102 newAllowance := overflow.Sub64p(currentAllowance, amount)
103
104 if newAllowance == 0 {
105 led.allowances.Remove(key)
106 } else {
107 led.allowances.Set(key, newAllowance)
108 }
109
110 return nil
111}
112
113// Transfer transfers tokens from the specified from address to the specified to address.
114func (led *PrivateLedger) Transfer(from, to address, amount int64) error {
115 if !from.IsValid() {
116 return ErrInvalidAddress
117 }
118 if !to.IsValid() {
119 return ErrInvalidAddress
120 }
121 if from == to {
122 return ErrCannotTransferToSelf
123 }
124 if amount < 0 {
125 return ErrInvalidAmount
126 }
127
128 var (
129 toBalance = led.balanceOf(to)
130 fromBalance = led.balanceOf(from)
131 )
132
133 if fromBalance < amount {
134 return ErrInsufficientBalance
135 }
136
137 var (
138 newToBalance = overflow.Add64p(toBalance, amount)
139 newFromBalance = overflow.Sub64p(fromBalance, amount)
140 )
141
142 led.balances.Set(string(to), newToBalance)
143
144 if newFromBalance == 0 {
145 led.balances.Remove(string(from))
146 } else {
147 led.balances.Set(string(from), newFromBalance)
148 }
149
150 chain.Emit(
151 TransferEvent,
152 "token", led.token.ID(),
153 "from", from.String(),
154 "to", to.String(),
155 "value", strconv.Itoa(int(amount)),
156 )
157
158 return nil
159}
160
161// TransferFrom transfers tokens from the specified owner to the specified to address.
162// It first checks if the owner has sufficient balance and then decreases the allowance.
163func (led *PrivateLedger) TransferFrom(owner, spender, to address, amount int64) error {
164 if amount < 0 {
165 return ErrInvalidAmount
166 }
167
168 if !owner.IsValid() || !to.IsValid() {
169 return ErrInvalidAddress
170 }
171
172 if led.balanceOf(owner) < amount {
173 return ErrInsufficientBalance
174 }
175
176 // The check above guarantees that Transfer will succeed, ensuring
177 // atomicity for the subsequent operations.
178 if err := led.SpendAllowance(owner, spender, amount); err != nil {
179 return err
180 }
181
182 if err := led.Transfer(owner, to, amount); err != nil {
183 return err
184 }
185
186 return nil
187}
188
189// Approve sets the allowance of the specified owner and spender.
190func (led *PrivateLedger) Approve(owner, spender address, amount int64) error {
191 if !owner.IsValid() || !spender.IsValid() {
192 return ErrInvalidAddress
193 }
194 if amount < 0 {
195 return ErrInvalidAmount
196 }
197
198 led.allowances.Set(allowanceKey(owner, spender), amount)
199
200 chain.Emit(
201 ApprovalEvent,
202 "token", led.token.ID(),
203 "owner", string(owner),
204 "spender", string(spender),
205 "value", strconv.Itoa(int(amount)),
206 )
207
208 return nil
209}
210
211// Mint increases the total supply of the token and adds the specified amount to the specified address.
212func (led *PrivateLedger) Mint(addr address, amount int64) error {
213 if !addr.IsValid() {
214 return ErrInvalidAddress
215 }
216 if amount < 0 {
217 return ErrInvalidAmount
218 }
219
220 // limit amount to MaxInt64 - totalSupply
221 if amount > overflow.Sub64p(math.MaxInt64, led.totalSupply) {
222 return ErrMintOverflow
223 }
224
225 led.totalSupply += amount
226 currentBalance := led.balanceOf(addr)
227 newBalance := overflow.Add64p(currentBalance, amount)
228
229 led.balances.Set(string(addr), newBalance)
230
231 chain.Emit(
232 TransferEvent,
233 "token", led.token.ID(),
234 "from", "",
235 "to", string(addr),
236 "value", strconv.Itoa(int(amount)),
237 )
238
239 return nil
240}
241
242// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
243func (led *PrivateLedger) Burn(addr address, amount int64) error {
244 if !addr.IsValid() {
245 return ErrInvalidAddress
246 }
247 if amount < 0 {
248 return ErrInvalidAmount
249 }
250
251 currentBalance := led.balanceOf(addr)
252 if currentBalance < amount {
253 return ErrInsufficientBalance
254 }
255
256 led.totalSupply = overflow.Sub64p(led.totalSupply, amount)
257 newBalance := overflow.Sub64p(currentBalance, amount)
258
259 if newBalance == 0 {
260 led.balances.Remove(string(addr))
261 } else {
262 led.balances.Set(string(addr), newBalance)
263 }
264
265 chain.Emit(
266 TransferEvent,
267 "token", led.token.ID(),
268 "from", string(addr),
269 "to", "",
270 "value", strconv.Itoa(int(amount)),
271 )
272
273 return nil
274}
275
276// hasAddr checks if the specified address is a known account in the ledger.
277func (led PrivateLedger) hasAddr(addr address) bool {
278 return led.balances.Has(addr.String())
279}
280
281// balanceOf returns the balance of the specified address.
282func (led PrivateLedger) balanceOf(addr address) int64 {
283 balance, found := led.balances.Get(addr.String())
284 if !found {
285 return 0
286 }
287 return balance.(int64)
288}
289
290// allowance returns the allowance of the specified owner and spender.
291func (led PrivateLedger) allowance(owner, spender address) int64 {
292 allowance, found := led.allowances.Get(allowanceKey(owner, spender))
293 if !found {
294 return 0
295 }
296 return allowance.(int64)
297}
298
299// allowanceKey returns the key for the allowance of the specified owner and spender.
300func allowanceKey(owner, spender address) string {
301 return owner.String() + ":" + spender.String()
302}