Search Apps Documentation Source Content File Folder Download Copy

token.gno

6.45 Kb ยท 248 lines
  1package grc20
  2
  3import (
  4	"math/overflow"
  5	"std"
  6	"strconv"
  7
  8	"gno.land/p/demo/ufmt"
  9)
 10
 11// NewToken creates a new Token.
 12// It returns a pointer to the Token and a pointer to the Ledger.
 13// Expected usage: Token, admin := NewToken("Dummy", "DUMMY", 4)
 14func NewToken(name, symbol string, decimals uint) (*Token, *PrivateLedger) {
 15	if name == "" {
 16		panic("name should not be empty")
 17	}
 18	if symbol == "" {
 19		panic("symbol should not be empty")
 20	}
 21	// XXX additional checks (length, characters, limits, etc)
 22
 23	ledger := &PrivateLedger{}
 24	token := &Token{
 25		name:     name,
 26		symbol:   symbol,
 27		decimals: decimals,
 28		ledger:   ledger,
 29	}
 30	ledger.token = token
 31	return token, ledger
 32}
 33
 34// GetName returns the name of the token.
 35func (tok Token) GetName() string { return tok.name }
 36
 37// GetSymbol returns the symbol of the token.
 38func (tok Token) GetSymbol() string { return tok.symbol }
 39
 40// GetDecimals returns the number of decimals used to get the token's precision.
 41func (tok Token) GetDecimals() uint { return tok.decimals }
 42
 43// TotalSupply returns the total supply of the token.
 44func (tok Token) TotalSupply() uint64 { return tok.ledger.totalSupply }
 45
 46// KnownAccounts returns the number of known accounts in the bank.
 47func (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() }
 48
 49// BalanceOf returns the balance of the specified address.
 50func (tok Token) BalanceOf(address std.Address) uint64 {
 51	return tok.ledger.balanceOf(address)
 52}
 53
 54// Allowance returns the allowance of the specified owner and spender.
 55func (tok Token) Allowance(owner, spender std.Address) uint64 {
 56	return tok.ledger.allowance(owner, spender)
 57}
 58
 59func (tok *Token) RenderHome() string {
 60	str := ""
 61	str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
 62	str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals)
 63	str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
 64	str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
 65	return str
 66}
 67
 68// Getter returns a TokenGetter function that returns this token. This allows
 69// storing indirect pointers to a token in a remote realm.
 70func (tok *Token) Getter() TokenGetter {
 71	return func() *Token {
 72		return tok
 73	}
 74}
 75
 76// SpendAllowance decreases the allowance of the specified owner and spender.
 77func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount uint64) error {
 78	if !owner.IsValid() {
 79		return ErrInvalidAddress
 80	}
 81	if !spender.IsValid() {
 82		return ErrInvalidAddress
 83	}
 84
 85	currentAllowance := led.allowance(owner, spender)
 86	if currentAllowance < amount {
 87		return ErrInsufficientAllowance
 88	}
 89
 90	key := allowanceKey(owner, spender)
 91	newAllowance := currentAllowance - amount
 92
 93	if newAllowance == 0 {
 94		led.allowances.Remove(key)
 95	} else {
 96		led.allowances.Set(key, newAllowance)
 97	}
 98
 99	return nil
100}
101
102// Transfer transfers tokens from the specified from address to the specified to address.
103func (led *PrivateLedger) Transfer(from, to std.Address, amount uint64) error {
104	if !from.IsValid() {
105		return ErrInvalidAddress
106	}
107	if !to.IsValid() {
108		return ErrInvalidAddress
109	}
110	if from == to {
111		return ErrCannotTransferToSelf
112	}
113
114	var (
115		toBalance   = led.balanceOf(to)
116		fromBalance = led.balanceOf(from)
117	)
118
119	if fromBalance < amount {
120		return ErrInsufficientBalance
121	}
122
123	var (
124		newToBalance   = toBalance + amount
125		newFromBalance = fromBalance - amount
126	)
127
128	led.balances.Set(string(to), newToBalance)
129	led.balances.Set(string(from), newFromBalance)
130
131	std.Emit(
132		TransferEvent,
133		"from", from.String(),
134		"to", to.String(),
135		"value", strconv.Itoa(int(amount)),
136	)
137
138	return nil
139}
140
141// TransferFrom transfers tokens from the specified owner to the specified to address.
142// It first checks if the owner has sufficient balance and then decreases the allowance.
143func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount uint64) error {
144	if led.balanceOf(owner) < amount {
145		return ErrInsufficientBalance
146	}
147	if err := led.SpendAllowance(owner, spender, amount); err != nil {
148		return err
149	}
150	// XXX: since we don't "panic", we should take care of rollbacking spendAllowance if transfer fails.
151	return led.Transfer(owner, to, amount)
152}
153
154// Approve sets the allowance of the specified owner and spender.
155func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) error {
156	if !owner.IsValid() || !spender.IsValid() {
157		return ErrInvalidAddress
158	}
159
160	led.allowances.Set(allowanceKey(owner, spender), amount)
161
162	std.Emit(
163		ApprovalEvent,
164		"owner", string(owner),
165		"spender", string(spender),
166		"value", strconv.Itoa(int(amount)),
167	)
168
169	return nil
170}
171
172// Mint increases the total supply of the token and adds the specified amount to the specified address.
173func (led *PrivateLedger) Mint(address std.Address, amount uint64) error {
174	if !address.IsValid() {
175		return ErrInvalidAddress
176	}
177
178	// XXX: math/overflow is not supporting uint64.
179	// This checks prevents overflow but makes the totalSupply limited to a uint63.
180	sum, ok := overflow.Add64(int64(led.totalSupply), int64(amount))
181	if !ok {
182		return ErrOverflow
183	}
184
185	led.totalSupply = uint64(sum)
186	currentBalance := led.balanceOf(address)
187	newBalance := currentBalance + amount
188
189	led.balances.Set(string(address), newBalance)
190
191	std.Emit(
192		TransferEvent,
193		"from", "",
194		"to", string(address),
195		"value", strconv.Itoa(int(amount)),
196	)
197
198	return nil
199}
200
201// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
202func (led *PrivateLedger) Burn(address std.Address, amount uint64) error {
203	if !address.IsValid() {
204		return ErrInvalidAddress
205	}
206
207	currentBalance := led.balanceOf(address)
208	if currentBalance < amount {
209		return ErrInsufficientBalance
210	}
211
212	led.totalSupply -= amount
213	newBalance := currentBalance - amount
214
215	led.balances.Set(string(address), newBalance)
216
217	std.Emit(
218		TransferEvent,
219		"from", string(address),
220		"to", "",
221		"value", strconv.Itoa(int(amount)),
222	)
223
224	return nil
225}
226
227// balanceOf returns the balance of the specified address.
228func (led PrivateLedger) balanceOf(address std.Address) uint64 {
229	balance, found := led.balances.Get(address.String())
230	if !found {
231		return 0
232	}
233	return balance.(uint64)
234}
235
236// allowance returns the allowance of the specified owner and spender.
237func (led PrivateLedger) allowance(owner, spender std.Address) uint64 {
238	allowance, found := led.allowances.Get(allowanceKey(owner, spender))
239	if !found {
240		return 0
241	}
242	return allowance.(uint64)
243}
244
245// allowanceKey returns the key for the allowance of the specified owner and spender.
246func allowanceKey(owner, spender std.Address) string {
247	return owner.String() + ":" + spender.String()
248}