Skip to content

Commit

Permalink
chore: add a grc20 example smart contract
Browse files Browse the repository at this point in the history
Signed-off-by: Manfred Touron <[email protected]>
  • Loading branch information
moul committed Apr 26, 2022
1 parent 922e6e2 commit 24a78c8
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 0 deletions.
64 changes: 64 additions & 0 deletions examples/gno.land/p/grc/grc20/grc20.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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`).
//
// 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
}
243 changes: 243 additions & 0 deletions examples/gno.land/r/grc20-foo/foo.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package foo

import (
"std"

"gno.land/p/avl"
"gno.land/p/grc/grc20"
)

type Token struct {
// TODO: use big.Int or a custom uint256 instead of uint64?

grc20.GRC20 // 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(),
}
}

var foo = NewToken("Foo", "FOO", 4)
var admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj"

const zeroAddress = std.Address("")

func init() {
foo.mint("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj", 1000000*10000) // @administrator (1M)
foo.mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k)
}

// GRC20 implementation.
//

// 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) TotalSupply() uint64 {
return t.totalSupply
}

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

func (t *Token) Transfer(to std.Address, amount uint64) {
std.AssertOriginCall()
caller := std.GetCallerAt(2)
t.transfer(caller, to, amount)
}

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

func (t *Token) Approve(spender std.Address, amount uint64) {
std.AssertOriginCall()
owner := std.GetCallerAt(2)
t.approve(owner, spender, amount)
}

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

// Administration helpers implementation.
//

func (t *Token) Mint(address std.Address, amount uint64) {
std.AssertOriginCall()
caller := std.GetCallerAt(2)

if caller != admin {
panic("restricted access")
}
t.mint(address, amount)
}

func (t *Token) Burn(address std.Address, amount uint64) {
std.AssertOriginCall()
caller := std.GetCallerAt(2)

if caller != admin {
panic("restricted access")
}
t.burn(address, 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 := grc20.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 := grc20.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)

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 := grc20.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 := grc20.ApprovalEvent{owner, spender, amount}
emit(&event)
}

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

// method proxies as public functions.
//

// getters.

func GetFoo() *Token { return foo }
func TotalSupply() uint64 { return foo.TotalSupply() }
func BalanceOf(address std.Address) uint64 { return foo.BalanceOf(address) }
func Allowance(owner, spender std.Address) uint64 { return foo.Allowance(owner, spender) }

// setters.

func Transfer(to std.Address, amount uint64) { foo.Transfer(to, amount) }
func Approve(spender std.Address, amount uint64) { foo.Approve(spender, amount) }
func TransferFrom(from, to std.Address, amount uint64) { foo.TransferFrom(from, to, amount) }

// administration.

func Mint(address std.Address, amount uint64) { foo.Mint(address, amount) }
func Burn(address std.Address, amount uint64) { foo.Burn(address, amount) }

// placeholders
//

func emit(event interface{}) {
// TODO: should we do something there?
// noop
}
25 changes: 25 additions & 0 deletions examples/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module github.com/gnolang/gno/examples

go 1.17

require github.com/gnolang/gno v0.0.0

require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/tools v0.1.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
)

replace github.com/gnolang/gno => /home/moul/go/src/moul.io/gno/gno/

0 comments on commit 24a78c8

Please sign in to comment.