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}