Skip to content

Commit

Permalink
chore: refactor gno.land/p/grc structure (#309)
Browse files Browse the repository at this point in the history
* chore: refactor gno.land/p/grc structure

Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
  • Loading branch information
moul committed Aug 4, 2022
1 parent 956b503 commit b840b9e
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 297 deletions.
2 changes: 1 addition & 1 deletion cmd/gnoland/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ func makeGenesisDoc(pvPub crypto.PubKey) *bft.GenesisDoc {
for _, path := range []string{
"p/ufmt",
"p/avl",
"p/grc/exts",
"p/grc/grc20",
"p/grc/grc20/impl",
"p/grc/grc721",
"p/maths",
"r/users",
Expand Down
14 changes: 14 additions & 0 deletions examples/gno.land/p/grc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# GRC: Gnoland Request for Comments

GRCs are the Gnoland's equivalent of Ethereum's ERCs and EIPs, or Bitcoin's BIPs.

This folder contains interfaces, examples, and implementations of standard smart-contracts and patterns.

## Acknowledgment

Based and adapted from the work and discussions with people from:

* https://ethereum.org/en/developers/docs/standards/
* https://www.openzeppelin.com/
* https://github.com/transmissions11/solmate
* https://github.com/bitcoin/bips
12 changes: 12 additions & 0 deletions examples/gno.land/p/grc/exts/token-metadata.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package exts

type TokenMetadata interface {
// Returns the name of the token.
Name() string

// Returns the symbol of the token.
Symbol() string

// Returns the decimals places of the token.
Decimals() uint
}
262 changes: 202 additions & 60 deletions examples/gno.land/p/grc/grc20/grc20.gno
Original file line number Diff line number Diff line change
@@ -1,64 +1,206 @@
package grc20

import "std"

type GRC20 interface {
// Returns the amount of tokens in existence.
TotalSupply() uint64

// Returns the amount of tokens owned by `account`.
BalanceOf(address std.Address) uint64

// Moves `amount` tokens from the caller's account to `to`.
//
// Returns a boolean value indicating whether the operation succeeded.
//
// Emits a {EventTransfer} event.
Transfer(to std.Address, amount uint64) bool

// Returns the remaining number of tokens that `spender` will be
// allowed to spend on behalf of `owner` through {transferFrom}. This is
// zero by default.
//
// This value changes when {approve} or {transferFrom} are called.
Allowance(owner, spender std.Address) uint64

// Sets `amount` as the allowance of `spender` over the caller's tokens.
// Returns a boolean value indicating whether the operation succeeded.
//
// IMPORTANT: Beware that changing an allowance with this method brings the risk
// that someone may use both the old and the new allowance by unfortunate
// transaction ordering. One possible solution to mitigate this race
// condition is to first reduce the spender's allowance to 0 and set the
// desired value afterwards:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
//
// Emits an {EventApproval} event.
Approve(spender std.Address, amount uint64) bool

// Moves `amount` tokens from `from` to `to` using the
// allowance mechanism. `amount` is then deducted from the caller's
// allowance.
//
// Returns a boolean value indicating whether the operation succeeded.
//
// Emits a {EventTransfer} event.
TransferFrom(from, to std.Address, amount uint64) bool
}

// Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
import (
"std"

"gno.land/p/avl"
"gno.land/p/ufmt"
)

// FIXME: helper that creates a Permissionless instance of the token (without mint, and with caller as address).

type Token struct {
IGRC20 // implements the GRC20 interface

name string
symbol string
decimals uint
totalSupply uint64
balances *avl.MutTree // std.Address(owner) -> uint64
allowances *avl.MutTree // string(owner+":"+spender) -> uint64
}

func NewToken(name, symbol string, decimals uint) *Token {
// FIXME: check for limits

return &Token{
name: name,
symbol: symbol,
decimals: decimals,

balances: avl.NewMutTree(),
allowances: avl.NewMutTree(),
}
}

const zeroAddress = std.Address("")

// GRC20 implementation.
//
// Note that `value` may be zero.
type TransferEvent struct {
From std.Address
To std.Address
Value uint64
}

// Emitted when the allowance of a `spender` for an `owner` is set by
// a call to {approve}. `value` is the new allowance.
type ApprovalEvent struct {
Owner std.Address
Spender std.Address
Value uint64

// TODO: create a reusable interface with optional hooks.
// TODO: simplify the API and try to use events when available.
// TODO: useful Render() method.
// TODO: add a lot of unit tests, really a lot.

func (t *Token) GetName() string { return t.name }
func (t *Token) GetSymbol() string { return t.symbol }
func (t *Token) GetDecimals() uint { return t.decimals }
func (t *Token) TotalSupply() uint64 { return t.totalSupply }

func (t *Token) BalanceOf(owner std.Address) uint64 {
return t.balanceOf(owner)
}

func (t *Token) Transfer(owner, to std.Address, amount uint64) {
t.transfer(owner, to, amount)
}

func (t *Token) Allowance(owner, spender std.Address) uint64 {
return t.allowance(owner, spender)
}

func (t *Token) Approve(owner, spender std.Address, amount uint64) {
t.approve(owner, spender, amount)
}

func (t *Token) TransferFrom(spender, from, to std.Address, amount uint64) {
t.spendAllowance(from, spender, amount)
t.transfer(from, to, amount)
}

// Administration helpers implementation.
//

func (t *Token) Mint(to std.Address, amount uint64) {
t.mint(to, amount)
}

func (t *Token) Burn(from std.Address, amount uint64) {
t.burn(from, amount)
}

// private helpers
//

func (t *Token) mint(address std.Address, amount uint64) {
checkIsValidAddress(address)
// TODO: check for overflow

t.totalSupply += amount
currentBalance := t.balanceOf(address)
newBalance := currentBalance + amount

t.balances.Set(string(address), newBalance)

event := TransferEvent{zeroAddress, address, amount}
emit(&event)
}

func (t *Token) burn(address std.Address, amount uint64) {
checkIsValidAddress(address)
// TODO: check for overflow

currentBalance := t.balanceOf(address)
if currentBalance < amount {
panic("insufficient balance")
}

t.totalSupply -= amount
newBalance := currentBalance - amount

t.balances.Set(string(address), newBalance)

event := TransferEvent{address, zeroAddress, amount}
emit(&event)
}

func (t *Token) balanceOf(address std.Address) uint64 {
checkIsValidAddress(address)

balance, found := t.balances.Get(address.String())
if !found {
return 0
}
return balance.(uint64)
}

func (t *Token) spendAllowance(owner, spender std.Address, amount uint64) {
checkIsValidAddress(owner)
checkIsValidAddress(spender)

currentAllowance := t.allowance(owner, spender)
if currentAllowance < amount {
panic("insufficient allowance")
}
}

func (t *Token) transfer(from, to std.Address, amount uint64) {
checkIsValidAddress(from)
checkIsValidAddress(to)

if from == to {
panic("cannot send transfer to self")
}

toBalance := t.balanceOf(to)
fromBalance := t.balanceOf(from)

if fromBalance < amount {
panic("insufficient balance")
}

newToBalance := toBalance + amount
newFromBalance := fromBalance - amount

t.balances.Set(string(to), newToBalance)
t.balances.Set(string(from), newFromBalance)

event := TransferEvent{from, to, amount}
emit(&event)
}

func (t *Token) allowance(owner, spender std.Address) uint64 {
checkIsValidAddress(owner)
checkIsValidAddress(spender)

key := owner.String() + ":" + spender.String()

allowance, found := t.allowances.Get(key)
if !found {
return 0
}

return allowance.(uint64)
}

func (t *Token) approve(owner, spender std.Address, amount uint64) {
checkIsValidAddress(owner)
checkIsValidAddress(spender)

key := owner.String() + ":" + spender.String()
t.allowances.Set(key, amount)

event := ApprovalEvent{owner, spender, amount}
emit(&event)
}

func checkIsValidAddress(addr std.Address) {
if addr.String() == "" {
panic("invalid address")
}
}

func (t *Token) RenderHome() string {
str := ""
str += ufmt.Sprintf("# %s ($%s)\n\n", t.name, t.symbol)
str += ufmt.Sprintf("* **Decimals**: %d\n", t.decimals)
str += ufmt.Sprintf("* **Total supply**: %d\n", t.totalSupply)
str += ufmt.Sprintf("* **Known accounts**: %d\n", t.balances.Size())
return str
}

func emit(event interface{}) {
// TODO: should we do something there?
// noop
}
72 changes: 72 additions & 0 deletions examples/gno.land/p/grc/grc20/igrc20.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package grc20

import (
"std"

"gno.land/p/grc/exts"
)

// TODO: use big.Int or a custom uint256 instead of uint64?

type IGRC20 interface {
exts.TokenMetadata

// Returns the amount of tokens in existence.
TotalSupply() uint64

// Returns the amount of tokens owned by `account`.
BalanceOf(account std.Address) uint64

// Moves `amount` tokens from the caller's account to `to`.
//
// Returns a boolean value indicating whether the operation succeeded.
//
// Emits a {EventTransfer} event.
Transfer(to std.Address, amount uint64) bool

// Returns the remaining number of tokens that `spender` will be
// allowed to spend on behalf of `owner` through {transferFrom}. This is
// zero by default.
//
// This value changes when {approve} or {transferFrom} are called.
Allowance(owner, spender std.Address) uint64

// Sets `amount` as the allowance of `spender` over the caller's tokens.
// Returns a boolean value indicating whether the operation succeeded.
//
// IMPORTANT: Beware that changing an allowance with this method brings the risk
// that someone may use both the old and the new allowance by unfortunate
// transaction ordering. One possible solution to mitigate this race
// condition is to first reduce the spender's allowance to 0 and set the
// desired value afterwards:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
//
// Emits an {EventApproval} event.
Approve(spender std.Address, amount uint64) bool

// Moves `amount` tokens from `from` to `to` using the
// allowance mechanism. `amount` is then deducted from the caller's
// allowance.
//
// Returns a boolean value indicating whether the operation succeeded.
//
// Emits a {EventTransfer} event.
TransferFrom(from, to std.Address, amount uint64) bool
}

// Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
//
// Note that `value` may be zero.
type TransferEvent struct {
From std.Address
To std.Address
Value uint64
}

// Emitted when the allowance of a `spender` for an `owner` is set by
// a call to {approve}. `value` is the new allowance.
type ApprovalEvent struct {
Owner std.Address
Spender std.Address
Value uint64
}
Loading

0 comments on commit b840b9e

Please sign in to comment.