Search Apps Documentation Source Content File Folder Download Copy Actions Download

token.gno

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