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