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}