token.gno
6.84 Kb ยท 274 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// BalanceOf returns the balance of the specified address.
51func (tok Token) BalanceOf(address_XXX std.Address) int64 {
52 return tok.ledger.balanceOf(address_XXX)
53}
54
55// Allowance returns the allowance of the specified owner and spender.
56func (tok Token) Allowance(owner, spender std.Address) int64 {
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// SpendAllowance decreases the allowance of the specified owner and spender.
70func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount int64) error {
71 if !owner.IsValid() {
72 return ErrInvalidAddress
73 }
74 if !spender.IsValid() {
75 return ErrInvalidAddress
76 }
77 if amount < 0 {
78 return ErrInvalidAmount
79 }
80
81 currentAllowance := led.allowance(owner, spender)
82 if currentAllowance < amount {
83 return ErrInsufficientAllowance
84 }
85
86 key := allowanceKey(owner, spender)
87 newAllowance := overflow.Sub64p(currentAllowance, amount)
88
89 if newAllowance == 0 {
90 led.allowances.Remove(key)
91 } else {
92 led.allowances.Set(key, newAllowance)
93 }
94
95 return nil
96}
97
98// Transfer transfers tokens from the specified from address to the specified to address.
99func (led *PrivateLedger) Transfer(from, to std.Address, amount int64) error {
100 if !from.IsValid() {
101 return ErrInvalidAddress
102 }
103 if !to.IsValid() {
104 return ErrInvalidAddress
105 }
106 if from == to {
107 return ErrCannotTransferToSelf
108 }
109 if amount < 0 {
110 return ErrInvalidAmount
111 }
112
113 var (
114 toBalance = led.balanceOf(to)
115 fromBalance = led.balanceOf(from)
116 )
117
118 if fromBalance < amount {
119 return ErrInsufficientBalance
120 }
121
122 var (
123 newToBalance = overflow.Add64p(toBalance, amount)
124 newFromBalance = overflow.Sub64p(fromBalance, amount)
125 )
126
127 led.balances.Set(string(to), newToBalance)
128 led.balances.Set(string(from), newFromBalance)
129
130 std.Emit(
131 TransferEvent,
132 "from", from.String(),
133 "to", to.String(),
134 "value", strconv.Itoa(int(amount)),
135 )
136
137 return nil
138}
139
140// TransferFrom transfers tokens from the specified owner to the specified to address.
141// It first checks if the owner has sufficient balance and then decreases the allowance.
142func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount int64) error {
143 if amount < 0 {
144 return ErrInvalidAmount
145 }
146 if led.balanceOf(owner) < amount {
147 return ErrInsufficientBalance
148 }
149
150 // allowance must be sufficient
151 currentAllowance := led.allowance(owner, spender)
152 if currentAllowance < amount {
153 return ErrInsufficientAllowance
154 }
155
156 if err := led.Transfer(owner, to, amount); err != nil {
157 return err
158 }
159
160 // decrease the allowance only when transfer is successful
161 key := allowanceKey(owner, spender)
162 newAllowance := overflow.Sub64p(currentAllowance, amount)
163
164 if newAllowance == 0 {
165 led.allowances.Remove(key)
166 } else {
167 led.allowances.Set(key, newAllowance)
168 }
169
170 return nil
171}
172
173// Approve sets the allowance of the specified owner and spender.
174func (led *PrivateLedger) Approve(owner, spender std.Address, amount int64) error {
175 if !owner.IsValid() || !spender.IsValid() {
176 return ErrInvalidAddress
177 }
178 if amount < 0 {
179 return ErrInvalidAmount
180 }
181
182 led.allowances.Set(allowanceKey(owner, spender), amount)
183
184 std.Emit(
185 ApprovalEvent,
186 "owner", string(owner),
187 "spender", string(spender),
188 "value", strconv.Itoa(int(amount)),
189 )
190
191 return nil
192}
193
194// Mint increases the total supply of the token and adds the specified amount to the specified address.
195func (led *PrivateLedger) Mint(address_XXX std.Address, amount int64) error {
196 if !address_XXX.IsValid() {
197 return ErrInvalidAddress
198 }
199 if amount < 0 {
200 return ErrInvalidAmount
201 }
202
203 // limit amount to MaxInt64 - totalSupply
204 if amount > overflow.Sub64p(math.MaxInt64, led.totalSupply) {
205 return ErrMintOverflow
206 }
207
208 led.totalSupply += amount
209 currentBalance := led.balanceOf(address_XXX)
210 newBalance := overflow.Add64p(currentBalance, amount)
211
212 led.balances.Set(string(address_XXX), newBalance)
213
214 std.Emit(
215 TransferEvent,
216 "from", "",
217 "to", string(address_XXX),
218 "value", strconv.Itoa(int(amount)),
219 )
220
221 return nil
222}
223
224// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
225func (led *PrivateLedger) Burn(address_XXX std.Address, amount int64) error {
226 if !address_XXX.IsValid() {
227 return ErrInvalidAddress
228 }
229 if amount < 0 {
230 return ErrInvalidAmount
231 }
232
233 currentBalance := led.balanceOf(address_XXX)
234 if currentBalance < amount {
235 return ErrInsufficientBalance
236 }
237
238 led.totalSupply = overflow.Sub64p(led.totalSupply, amount)
239 newBalance := overflow.Sub64p(currentBalance, amount)
240
241 led.balances.Set(string(address_XXX), newBalance)
242
243 std.Emit(
244 TransferEvent,
245 "from", string(address_XXX),
246 "to", "",
247 "value", strconv.Itoa(int(amount)),
248 )
249
250 return nil
251}
252
253// balanceOf returns the balance of the specified address.
254func (led PrivateLedger) balanceOf(address_XXX std.Address) int64 {
255 balance, found := led.balances.Get(address_XXX.String())
256 if !found {
257 return 0
258 }
259 return balance.(int64)
260}
261
262// allowance returns the allowance of the specified owner and spender.
263func (led PrivateLedger) allowance(owner, spender std.Address) int64 {
264 allowance, found := led.allowances.Get(allowanceKey(owner, spender))
265 if !found {
266 return 0
267 }
268 return allowance.(int64)
269}
270
271// allowanceKey returns the key for the allowance of the specified owner and spender.
272func allowanceKey(owner, spender std.Address) string {
273 return owner.String() + ":" + spender.String()
274}