Skip to content

Commit

Permalink
feat(examples): add grc20factory (gnolang#1913)
Browse files Browse the repository at this point in the history
Signed-off-by: moul <94029+moul@users.noreply.github.com>

<!-- please provide a detailed description of the changes made in this
pull request. -->

<details><summary>Contributors' checklist...</summary>

- [ ] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [ ] Updated the official documentation or not needed
- [ ] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Signed-off-by: moul <94029+moul@users.noreply.github.com>
  • Loading branch information
moul authored and omarsy committed Jun 3, 2024
1 parent 398a3fc commit 0caee28
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 0 deletions.
6 changes: 6 additions & 0 deletions examples/gno.land/r/demo/grc20factory/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module gno.land/r/demo/grc20factory

require (
gno.land/p/demo/avl v0.0.0-latest
gno.land/p/demo/grc/grc20 v0.0.0-latest
)
164 changes: 164 additions & 0 deletions examples/gno.land/r/demo/grc20factory/grc20factory.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package foo20

import (
"std"
"strconv"
"strings"

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

// XXX: the p/grc20 library needs a refactor to change names (i.e., adminToken)

type token struct {
adminToken *grc20.AdminToken
admin std.Address
}

var tokens avl.Tree // symbol -> token

func NewToken(name, symbol string, decimals uint, initialMint uint64) {
admin := std.PrevRealm().Addr()
NewTokenWithAdmin(name, symbol, decimals, initialMint, admin)
}

func NewTokenWithAdmin(name, symbol string, decimals uint, initialMint uint64, admin std.Address) {
exists := tokens.Has(symbol)
if exists {
panic("token already exists")
}

newToken := grc20.NewAdminToken(name, symbol, decimals)
newToken.Mint(admin, initialMint)
t := token{
adminToken: newToken,
admin: admin,
}
tokens.Set(symbol, &t)
}

// method proxies as public functions.
//

// getters.

func TotalSupply(symbol string) uint64 {
token := mustTokenBySymbol(symbol)
return token.adminToken.TotalSupply()
}

func BalanceOf(symbol string, owner std.Address) uint64 {
token := mustTokenBySymbol(symbol)
balance, err := token.adminToken.BalanceOf(owner)
checkErr(err)
return balance
}

func Allowance(symbol string, owner, spender std.Address) uint64 {
token := mustTokenBySymbol(symbol)
allowance, err := token.adminToken.Allowance(owner, spender)
checkErr(err)
return allowance
}

// setters.

func Transfer(symbol string, to std.Address, amount uint64) {
token := mustTokenBySymbol(symbol)
caller := std.PrevRealm().Addr()
err := token.adminToken.Transfer(caller, to, amount)
checkErr(err)
}

func Approve(symbol string, spender std.Address, amount uint64) {
token := mustTokenBySymbol(symbol)
caller := std.PrevRealm().Addr()
err := token.adminToken.Approve(caller, spender, amount)
checkErr(err)
}

func TransferFrom(symbol string, from, to std.Address, amount uint64) {
token := mustTokenBySymbol(symbol)
caller := std.PrevRealm().Addr()
err := token.adminToken.TransferFrom(caller, from, to, amount)
if err != nil {
panic(err)
}
}

// faucet.
func Faucet(symbol string) {
token := mustTokenBySymbol(symbol)
// FIXME: add limits?
// FIXME: add payment in gnot?
caller := std.PrevRealm().Addr()
err := token.adminToken.Mint(caller, 1000*10000) // 1k
checkErr(err)
}

// administration.

func Mint(symbol string, address std.Address, amount uint64) {
token := mustTokenBySymbol(symbol)
caller := std.PrevRealm().Addr()
assertIsAdmin(caller, token.admin)
err := token.adminToken.Mint(address, amount)
checkErr(err)
}

func Burn(symbol string, address std.Address, amount uint64) {
token := mustTokenBySymbol(symbol)
caller := std.PrevRealm().Addr()
assertIsAdmin(caller, token.admin)
err := token.adminToken.Burn(address, amount)
checkErr(err)
}

// render.
//

func Render(path string) string {
parts := strings.Split(path, "/")
c := len(parts)

switch {
case path == "":
return "TODO: list existing tokens and admins"
case c == 1:
symbol := parts[0]
token := mustTokenBySymbol(symbol)
return token.adminToken.RenderHome()
case c == 3 && parts[1] == "balance":
symbol := parts[0]
token := mustTokenBySymbol(symbol)
owner := std.Address(parts[2])
balance, _ := token.adminToken.BalanceOf(owner)
return strconv.FormatUint(balance, 10)
default:
return "404\n"
}
}

// helpers.
//

func assertIsAdmin(caller, admin std.Address) {
if caller != admin {
panic("restricted access")
}
}

func mustTokenBySymbol(symbol string) *token {
t, exists := tokens.Get(symbol)
if !exists {
panic("token does not exist")
}
return t.(*token)
}

func checkErr(err error) {
if err != nil {
panic(err)
}
}
58 changes: 58 additions & 0 deletions examples/gno.land/r/demo/grc20factory/grc20factory_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package foo20

import (
"std"
"testing"
)

func TestReadOnlyPublicMethods(t *testing.T) {
admin := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj")
manfred := std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq")
unknown := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") // valid but never used.
NewTokenWithAdmin("Foo", "FOO", 4, 10000000000, admin)
NewTokenWithAdmin("Bar", "BAR", 4, 10000000, admin)
mustTokenBySymbol("FOO").adminToken.Mint(manfred, 100000000)

type test struct {
name string
balance uint64
fn func() uint64
}

// check balances #1.
{
tests := []test{
{"TotalSupply", 10100000000, func() uint64 { return TotalSupply("FOO") }},
{"BalanceOf(admin)", 10000000000, func() uint64 { return BalanceOf("FOO", admin) }},
{"BalanceOf(manfred)", 100000000, func() uint64 { return BalanceOf("FOO", manfred) }},
{"Allowance(admin, manfred)", 0, func() uint64 { return Allowance("FOO", admin, manfred) }},
{"BalanceOf(unknown)", 0, func() uint64 { return BalanceOf("FOO", unknown) }},
}
for _, tc := range tests {
if tc.fn() != tc.balance {
t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance)
}
}
}
return

// unknown uses the faucet.
std.TestSetOrigCaller(unknown)
Faucet("FOO")

// check balances #2.
{
tests := []test{
{"TotalSupply", 10110000000, func() uint64 { return TotalSupply("FOO") }},
{"BalanceOf(admin)", 10000000000, func() uint64 { return BalanceOf("FOO", admin) }},
{"BalanceOf(manfred)", 100000000, func() uint64 { return BalanceOf("FOO", manfred) }},
{"Allowance(admin, manfred)", 0, func() uint64 { return Allowance("FOO", admin, manfred) }},
{"BalanceOf(unknown)", 10000000, func() uint64 { return BalanceOf("FOO", unknown) }},
}
for _, tc := range tests {
if tc.fn() != tc.balance {
t.Errorf("%s: have: %d want: %d", tc.name, tc.fn(), tc.balance)
}
}
}
}

0 comments on commit 0caee28

Please sign in to comment.