token.gno

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