token.gno

6.70 Kb ยท 269 lines
  1package grc20
  2
  3import (
  4	"std"
  5	"strconv"
  6
  7	"gno.land/p/demo/ufmt"
  8)
  9
 10const MaxUint64 = uint64(1<<63 - 1)
 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 uint) (*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() uint { return tok.decimals }
 43
 44// TotalSupply returns the total supply of the token.
 45func (tok Token) TotalSupply() uint64 { 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 std.Address) uint64 {
 52	return tok.ledger.balanceOf(address)
 53}
 54
 55// Allowance returns the allowance of the specified owner and spender.
 56func (tok Token) Allowance(owner, spender std.Address) uint64 {
 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// Getter returns a TokenGetter function that returns this token. This allows
 70// storing indirect pointers to a token in a remote realm.
 71func (tok *Token) Getter() TokenGetter {
 72	return func() *Token {
 73		return tok
 74	}
 75}
 76
 77// SpendAllowance decreases the allowance of the specified owner and spender.
 78func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount uint64) error {
 79	if !owner.IsValid() {
 80		return ErrInvalidAddress
 81	}
 82	if !spender.IsValid() {
 83		return ErrInvalidAddress
 84	}
 85
 86	currentAllowance := led.allowance(owner, spender)
 87	if currentAllowance < amount {
 88		return ErrInsufficientAllowance
 89	}
 90
 91	key := allowanceKey(owner, spender)
 92	newAllowance := currentAllowance - amount
 93
 94	if newAllowance == 0 {
 95		led.allowances.Remove(key)
 96	} else {
 97		led.allowances.Set(key, newAllowance)
 98	}
 99
100	return nil
101}
102
103// Transfer transfers tokens from the specified from address to the specified to address.
104func (led *PrivateLedger) Transfer(from, to std.Address, amount uint64) error {
105	if !from.IsValid() {
106		return ErrInvalidAddress
107	}
108	if !to.IsValid() {
109		return ErrInvalidAddress
110	}
111	if from == to {
112		return ErrCannotTransferToSelf
113	}
114
115	var (
116		toBalance   = led.balanceOf(to)
117		fromBalance = led.balanceOf(from)
118	)
119
120	if fromBalance < amount {
121		return ErrInsufficientBalance
122	}
123
124	var (
125		newToBalance   = toBalance + amount
126		newFromBalance = fromBalance - amount
127	)
128
129	led.balances.Set(string(to), newToBalance)
130	led.balances.Set(string(from), newFromBalance)
131
132	std.Emit(
133		TransferEvent,
134		"from", from.String(),
135		"to", to.String(),
136		"value", strconv.Itoa(int(amount)),
137	)
138
139	return nil
140}
141
142// TransferFrom transfers tokens from the specified owner to the specified to address.
143// It first checks if the owner has sufficient balance and then decreases the allowance.
144func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount uint64) error {
145	if led.balanceOf(owner) < amount {
146		return ErrInsufficientBalance
147	}
148
149	// allowance must be sufficient
150	currentAllowance := led.allowance(owner, spender)
151	if currentAllowance < amount {
152		return ErrInsufficientAllowance
153	}
154
155	if err := led.Transfer(owner, to, amount); err != nil {
156		return err
157	}
158
159	// decrease the allowance only when transfer is successful
160	key := allowanceKey(owner, spender)
161	newAllowance := currentAllowance - amount
162
163	if newAllowance == 0 {
164		led.allowances.Remove(key)
165	} else {
166		led.allowances.Set(key, newAllowance)
167	}
168
169	return nil
170}
171
172// Approve sets the allowance of the specified owner and spender.
173func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) error {
174	if !owner.IsValid() || !spender.IsValid() {
175		return ErrInvalidAddress
176	}
177
178	led.allowances.Set(allowanceKey(owner, spender), amount)
179
180	std.Emit(
181		ApprovalEvent,
182		"owner", string(owner),
183		"spender", string(spender),
184		"value", strconv.Itoa(int(amount)),
185	)
186
187	return nil
188}
189
190// Mint increases the total supply of the token and adds the specified amount to the specified address.
191func (led *PrivateLedger) Mint(address std.Address, amount uint64) error {
192	if !address.IsValid() {
193		return ErrInvalidAddress
194	}
195
196	// limit totalSupply to MaxUint64
197	if led.totalSupply > MaxUint64 {
198		return ErrOverflow
199	}
200
201	// limit amount to MaxUint64 - totalSupply
202	if amount > MaxUint64-led.totalSupply {
203		return ErrOverflow
204	}
205
206	led.totalSupply += amount
207	currentBalance := led.balanceOf(address)
208	newBalance := currentBalance + amount
209
210	led.balances.Set(string(address), newBalance)
211
212	std.Emit(
213		TransferEvent,
214		"from", "",
215		"to", string(address),
216		"value", strconv.Itoa(int(amount)),
217	)
218
219	return nil
220}
221
222// Burn decreases the total supply of the token and subtracts the specified amount from the specified address.
223func (led *PrivateLedger) Burn(address std.Address, amount uint64) error {
224	if !address.IsValid() {
225		return ErrInvalidAddress
226	}
227
228	currentBalance := led.balanceOf(address)
229	if currentBalance < amount {
230		return ErrInsufficientBalance
231	}
232
233	led.totalSupply -= amount
234	newBalance := currentBalance - amount
235
236	led.balances.Set(string(address), newBalance)
237
238	std.Emit(
239		TransferEvent,
240		"from", string(address),
241		"to", "",
242		"value", strconv.Itoa(int(amount)),
243	)
244
245	return nil
246}
247
248// balanceOf returns the balance of the specified address.
249func (led PrivateLedger) balanceOf(address std.Address) uint64 {
250	balance, found := led.balances.Get(address.String())
251	if !found {
252		return 0
253	}
254	return balance.(uint64)
255}
256
257// allowance returns the allowance of the specified owner and spender.
258func (led PrivateLedger) allowance(owner, spender std.Address) uint64 {
259	allowance, found := led.allowances.Get(allowanceKey(owner, spender))
260	if !found {
261		return 0
262	}
263	return allowance.(uint64)
264}
265
266// allowanceKey returns the key for the allowance of the specified owner and spender.
267func allowanceKey(owner, spender std.Address) string {
268	return owner.String() + ":" + spender.String()
269}