token.gno

6.84 Kb ยท 274 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// BalanceOf returns the balance of the specified address.
 51func (tok Token) BalanceOf(address_XXX std.Address) int64 {
 52	return tok.ledger.balanceOf(address_XXX)
 53}
 54
 55// Allowance returns the allowance of the specified owner and spender.
 56func (tok Token) Allowance(owner, spender std.Address) int64 {
 57	return tok.ledger.allowance(owner, spender)
 58}
 59
 60func (tok *Token) RenderHome() string {
 61	str := ""
 62	str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol)
 63	str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals)
 64	str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply)
 65	str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts())
 66	return str
 67}
 68
 69// SpendAllowance decreases the allowance of the specified owner and spender.
 70func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount int64) error {
 71	if !owner.IsValid() {
 72		return ErrInvalidAddress
 73	}
 74	if !spender.IsValid() {
 75		return ErrInvalidAddress
 76	}
 77	if amount < 0 {
 78		return ErrInvalidAmount
 79	}
 80
 81	currentAllowance := led.allowance(owner, spender)
 82	if currentAllowance < amount {
 83		return ErrInsufficientAllowance
 84	}
 85
 86	key := allowanceKey(owner, spender)
 87	newAllowance := overflow.Sub64p(currentAllowance, amount)
 88
 89	if newAllowance == 0 {
 90		led.allowances.Remove(key)
 91	} else {
 92		led.allowances.Set(key, newAllowance)
 93	}
 94
 95	return nil
 96}
 97
 98// Transfer transfers tokens from the specified from address to the specified to address.
 99func (led *PrivateLedger) Transfer(from, to std.Address, amount int64) error {
100	if !from.IsValid() {
101		return ErrInvalidAddress
102	}
103	if !to.IsValid() {
104		return ErrInvalidAddress
105	}
106	if from == to {
107		return ErrCannotTransferToSelf
108	}
109	if amount < 0 {
110		return ErrInvalidAmount
111	}
112
113	var (
114		toBalance   = led.balanceOf(to)
115		fromBalance = led.balanceOf(from)
116	)
117
118	if fromBalance < amount {
119		return ErrInsufficientBalance
120	}
121
122	var (
123		newToBalance   = overflow.Add64p(toBalance, amount)
124		newFromBalance = overflow.Sub64p(fromBalance, amount)
125	)
126
127	led.balances.Set(string(to), newToBalance)
128	led.balances.Set(string(from), newFromBalance)
129
130	std.Emit(
131		TransferEvent,
132		"from", from.String(),
133		"to", to.String(),
134		"value", strconv.Itoa(int(amount)),
135	)
136
137	return nil
138}
139
140// TransferFrom transfers tokens from the specified owner to the specified to address.
141// It first checks if the owner has sufficient balance and then decreases the allowance.
142func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount int64) error {
143	if amount < 0 {
144		return ErrInvalidAmount
145	}
146	if led.balanceOf(owner) < amount {
147		return ErrInsufficientBalance
148	}
149
150	// allowance must be sufficient
151	currentAllowance := led.allowance(owner, spender)
152	if currentAllowance < amount {
153		return ErrInsufficientAllowance
154	}
155
156	if err := led.Transfer(owner, to, amount); err != nil {
157		return err
158	}
159
160	// decrease the allowance only when transfer is successful
161	key := allowanceKey(owner, spender)
162	newAllowance := overflow.Sub64p(currentAllowance, amount)
163
164	if newAllowance == 0 {
165		led.allowances.Remove(key)
166	} else {
167		led.allowances.Set(key, newAllowance)
168	}
169
170	return nil
171}
172
173// Approve sets the allowance of the specified owner and spender.
174func (led *PrivateLedger) Approve(owner, spender std.Address, amount int64) error {
175	if !owner.IsValid() || !spender.IsValid() {
176		return ErrInvalidAddress
177	}
178	if amount < 0 {
179		return ErrInvalidAmount
180	}
181
182	led.allowances.Set(allowanceKey(owner, spender), amount)
183
184	std.Emit(
185		ApprovalEvent,
186		"owner", string(owner),
187		"spender", string(spender),
188		"value", strconv.Itoa(int(amount)),
189	)
190
191	return nil
192}
193
194// Mint increases the total supply of the token and adds the specified amount to the specified address.
195func (led *PrivateLedger) Mint(address_XXX std.Address, amount int64) error {
196	if !address_XXX.IsValid() {
197		return ErrInvalidAddress
198	}
199	if amount < 0 {
200		return ErrInvalidAmount
201	}
202
203	// limit amount to MaxInt64 - totalSupply
204	if amount > overflow.Sub64p(math.MaxInt64, led.totalSupply) {
205		return ErrMintOverflow
206	}
207
208	led.totalSupply += amount
209	currentBalance := led.balanceOf(address_XXX)
210	newBalance := overflow.Add64p(currentBalance, amount)
211
212	led.balances.Set(string(address_XXX), newBalance)
213
214	std.Emit(
215		TransferEvent,
216		"from", "",
217		"to", string(address_XXX),
218		"value", strconv.Itoa(int(amount)),
219	)
220
221	return nil
222}
223
224// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
225func (led *PrivateLedger) Burn(address_XXX std.Address, amount int64) error {
226	if !address_XXX.IsValid() {
227		return ErrInvalidAddress
228	}
229	if amount < 0 {
230		return ErrInvalidAmount
231	}
232
233	currentBalance := led.balanceOf(address_XXX)
234	if currentBalance < amount {
235		return ErrInsufficientBalance
236	}
237
238	led.totalSupply = overflow.Sub64p(led.totalSupply, amount)
239	newBalance := overflow.Sub64p(currentBalance, amount)
240
241	led.balances.Set(string(address_XXX), newBalance)
242
243	std.Emit(
244		TransferEvent,
245		"from", string(address_XXX),
246		"to", "",
247		"value", strconv.Itoa(int(amount)),
248	)
249
250	return nil
251}
252
253// balanceOf returns the balance of the specified address.
254func (led PrivateLedger) balanceOf(address_XXX std.Address) int64 {
255	balance, found := led.balances.Get(address_XXX.String())
256	if !found {
257		return 0
258	}
259	return balance.(int64)
260}
261
262// allowance returns the allowance of the specified owner and spender.
263func (led PrivateLedger) allowance(owner, spender std.Address) int64 {
264	allowance, found := led.allowances.Get(allowanceKey(owner, spender))
265	if !found {
266		return 0
267	}
268	return allowance.(int64)
269}
270
271// allowanceKey returns the key for the allowance of the specified owner and spender.
272func allowanceKey(owner, spender std.Address) string {
273	return owner.String() + ":" + spender.String()
274}