forked from gnolang/gno
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): add grc20factory (gnolang#1913)
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
Showing
3 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
58
examples/gno.land/r/demo/grc20factory/grc20factory_test.gno
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} |