package grc20 import ( "std" "strconv" "gno.land/p/demo/ufmt" ) const MaxUint64 = uint64(1<<63 - 1) // NewToken creates a new Token. // It returns a pointer to the Token and a pointer to the Ledger. // Expected usage: Token, admin := NewToken("Dummy", "DUMMY", 4) func NewToken(name, symbol string, decimals uint) (*Token, *PrivateLedger) { if name == "" { panic("name should not be empty") } if symbol == "" { panic("symbol should not be empty") } // XXX additional checks (length, characters, limits, etc) ledger := &PrivateLedger{} token := &Token{ name: name, symbol: symbol, decimals: decimals, ledger: ledger, } ledger.token = token return token, ledger } // GetName returns the name of the token. func (tok Token) GetName() string { return tok.name } // GetSymbol returns the symbol of the token. func (tok Token) GetSymbol() string { return tok.symbol } // GetDecimals returns the number of decimals used to get the token's precision. func (tok Token) GetDecimals() uint { return tok.decimals } // TotalSupply returns the total supply of the token. func (tok Token) TotalSupply() uint64 { return tok.ledger.totalSupply } // KnownAccounts returns the number of known accounts in the bank. func (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() } // BalanceOf returns the balance of the specified address. func (tok Token) BalanceOf(address std.Address) uint64 { return tok.ledger.balanceOf(address) } // Allowance returns the allowance of the specified owner and spender. func (tok Token) Allowance(owner, spender std.Address) uint64 { return tok.ledger.allowance(owner, spender) } func (tok *Token) RenderHome() string { str := "" str += ufmt.Sprintf("# %s ($%s)\n\n", tok.name, tok.symbol) str += ufmt.Sprintf("* **Decimals**: %d\n", tok.decimals) str += ufmt.Sprintf("* **Total supply**: %d\n", tok.ledger.totalSupply) str += ufmt.Sprintf("* **Known accounts**: %d\n", tok.KnownAccounts()) return str } // Getter returns a TokenGetter function that returns this token. This allows // storing indirect pointers to a token in a remote realm. func (tok *Token) Getter() TokenGetter { return func() *Token { return tok } } // SpendAllowance decreases the allowance of the specified owner and spender. func (led *PrivateLedger) SpendAllowance(owner, spender std.Address, amount uint64) error { if !owner.IsValid() { return ErrInvalidAddress } if !spender.IsValid() { return ErrInvalidAddress } currentAllowance := led.allowance(owner, spender) if currentAllowance < amount { return ErrInsufficientAllowance } key := allowanceKey(owner, spender) newAllowance := currentAllowance - amount if newAllowance == 0 { led.allowances.Remove(key) } else { led.allowances.Set(key, newAllowance) } return nil } // Transfer transfers tokens from the specified from address to the specified to address. func (led *PrivateLedger) Transfer(from, to std.Address, amount uint64) error { if !from.IsValid() { return ErrInvalidAddress } if !to.IsValid() { return ErrInvalidAddress } if from == to { return ErrCannotTransferToSelf } var ( toBalance = led.balanceOf(to) fromBalance = led.balanceOf(from) ) if fromBalance < amount { return ErrInsufficientBalance } var ( newToBalance = toBalance + amount newFromBalance = fromBalance - amount ) led.balances.Set(string(to), newToBalance) led.balances.Set(string(from), newFromBalance) std.Emit( TransferEvent, "from", from.String(), "to", to.String(), "value", strconv.Itoa(int(amount)), ) return nil } // TransferFrom transfers tokens from the specified owner to the specified to address. // It first checks if the owner has sufficient balance and then decreases the allowance. func (led *PrivateLedger) TransferFrom(owner, spender, to std.Address, amount uint64) error { if led.balanceOf(owner) < amount { return ErrInsufficientBalance } // allowance must be sufficient currentAllowance := led.allowance(owner, spender) if currentAllowance < amount { return ErrInsufficientAllowance } if err := led.Transfer(owner, to, amount); err != nil { return err } // decrease the allowance only when transfer is successful key := allowanceKey(owner, spender) newAllowance := currentAllowance - amount if newAllowance == 0 { led.allowances.Remove(key) } else { led.allowances.Set(key, newAllowance) } return nil } // Approve sets the allowance of the specified owner and spender. func (led *PrivateLedger) Approve(owner, spender std.Address, amount uint64) error { if !owner.IsValid() || !spender.IsValid() { return ErrInvalidAddress } led.allowances.Set(allowanceKey(owner, spender), amount) std.Emit( ApprovalEvent, "owner", string(owner), "spender", string(spender), "value", strconv.Itoa(int(amount)), ) return nil } // Mint increases the total supply of the token and adds the specified amount to the specified address. func (led *PrivateLedger) Mint(address std.Address, amount uint64) error { if !address.IsValid() { return ErrInvalidAddress } // limit totalSupply to MaxUint64 if led.totalSupply > MaxUint64 { return ErrOverflow } // limit amount to MaxUint64 - totalSupply if amount > MaxUint64-led.totalSupply { return ErrOverflow } led.totalSupply += amount currentBalance := led.balanceOf(address) newBalance := currentBalance + amount led.balances.Set(string(address), newBalance) std.Emit( TransferEvent, "from", "", "to", string(address), "value", strconv.Itoa(int(amount)), ) return nil } // Burn decreases the total supply of the token and subtracts the specified amount from the specified address. func (led *PrivateLedger) Burn(address std.Address, amount uint64) error { if !address.IsValid() { return ErrInvalidAddress } currentBalance := led.balanceOf(address) if currentBalance < amount { return ErrInsufficientBalance } led.totalSupply -= amount newBalance := currentBalance - amount led.balances.Set(string(address), newBalance) std.Emit( TransferEvent, "from", string(address), "to", "", "value", strconv.Itoa(int(amount)), ) return nil } // balanceOf returns the balance of the specified address. func (led PrivateLedger) balanceOf(address std.Address) uint64 { balance, found := led.balances.Get(address.String()) if !found { return 0 } return balance.(uint64) } // allowance returns the allowance of the specified owner and spender. func (led PrivateLedger) allowance(owner, spender std.Address) uint64 { allowance, found := led.allowances.Get(allowanceKey(owner, spender)) if !found { return 0 } return allowance.(uint64) } // allowanceKey returns the key for the allowance of the specified owner and spender. func allowanceKey(owner, spender std.Address) string { return owner.String() + ":" + spender.String() }