diff --git a/examples/gno.land/r/x/grc20_dynamic_call/bar/bar.gno b/examples/gno.land/r/x/grc20_dynamic_call/bar/bar.gno new file mode 100644 index 00000000000..21abd647dbc --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/bar/bar.gno @@ -0,0 +1,131 @@ +package bar + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" +) + +var ( + bar *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // TODO: helper to change admin +) + +func init() { + bar = grc20.NewAdminToken("Bar", "BAR", 4) + bar.Mint(admin, 1000000*10000) // @administrator (1M) + bar.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k) + + bar.Mint("g1tkmrcu9m0xjddxh0c29r3zac8m2yjwaytlvh6u", 1000*10000) // REALM_gno.land/r/x/grc20_dynamic_call/registry +} + +// method proxies as public functions. +// + +// getters. + +func TotalSupply() uint64 { + return bar.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := bar.BalanceOf(owner.Resolve()) + if err != nil { + panic(err.Error()) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := bar.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err.Error()) + } + return allowance +} + +// setters. + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := bar.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := bar.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := bar.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +// faucet. + +func Faucet() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + err := bar.Mint(caller, 1000*10000) // 1k + if err != nil { + panic(err.Error()) + } +} + +// administration. + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := bar.Mint(address.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := bar.Burn(address.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return bar.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := bar.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/examples/gno.land/r/x/grc20_dynamic_call/bar/gno.mod b/examples/gno.land/r/x/grc20_dynamic_call/bar/gno.mod new file mode 100644 index 00000000000..97dc76d73b3 --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/bar/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/x/grc20_dynamic_call/bar + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/x/grc20_dynamic_call/foo/foo.gno b/examples/gno.land/r/x/grc20_dynamic_call/foo/foo.gno new file mode 100644 index 00000000000..d3e8d5c5e4b --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/foo/foo.gno @@ -0,0 +1,131 @@ +package foo + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" +) + +var ( + foo *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // TODO: helper to change admin +) + +func init() { + foo = grc20.NewAdminToken("Foo", "FOO", 4) + foo.Mint(admin, 1000000*10000) // @administrator (1M) + foo.Mint("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq", 10000*10000) // @manfred (10k) + + foo.Mint("g1tkmrcu9m0xjddxh0c29r3zac8m2yjwaytlvh6u", 1000*10000) // REALM_gno.land/r/x/grc20_dynamic_call/registry +} + +// method proxies as public functions. +// + +// getters. + +func TotalSupply() uint64 { + return foo.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := foo.BalanceOf(owner.Resolve()) + if err != nil { + panic(err.Error()) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := foo.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err.Error()) + } + return allowance +} + +// setters. + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := foo.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := foo.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := foo.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +// faucet. + +func Faucet() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + err := foo.Mint(caller, 1000*10000) // 1k + if err != nil { + panic(err.Error()) + } +} + +// administration. + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := foo.Mint(address.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := foo.Burn(address.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return foo.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := foo.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/examples/gno.land/r/x/grc20_dynamic_call/foo/gno.mod b/examples/gno.land/r/x/grc20_dynamic_call/foo/gno.mod new file mode 100644 index 00000000000..171f60edf53 --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/foo/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/x/grc20_dynamic_call/foo + +require ( + gno.land/p/demo/grc/grc20 v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/x/grc20_dynamic_call/registry/gno.mod b/examples/gno.land/r/x/grc20_dynamic_call/registry/gno.mod new file mode 100644 index 00000000000..c1c83c949c5 --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/registry/gno.mod @@ -0,0 +1,6 @@ +module gno.land/r/x/grc20_dynamic_call/registry + +require ( + gno.land/p/demo/ufmt v0.0.0-latest + gno.land/r/demo/users v0.0.0-latest +) diff --git a/examples/gno.land/r/x/grc20_dynamic_call/registry/registry.gno b/examples/gno.land/r/x/grc20_dynamic_call/registry/registry.gno new file mode 100644 index 00000000000..ddb1d6f567f --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/registry/registry.gno @@ -0,0 +1,80 @@ +package registry + +import ( + "std" + + "gno.land/p/demo/ufmt" + + "gno.land/r/demo/users" +) + +const ( + APPROVED_UNREGISTER_CALLER = "g1sqt92sa06ugh8nlt98kyghw83qy84paf4csyh6" // like admin +) + +type GRC20Interface interface { + Transfer() func(to users.AddressOrName, amount uint64) + TransferFrom() func(from, to users.AddressOrName, amount uint64) + BalanceOf() func(owner users.AddressOrName) uint64 +} + +var ( + registered = make(map[string]GRC20Interface) +) + +func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { + _, found := registered[pkgPath] + if found { + panic(ufmt.Sprintf("GRC20(pkgPath:%s) already registered", pkgPath)) + } + + registered[pkgPath] = igrc20 +} + +func UnregisterGRC20Interface(pkgPath string) { + // do not allow realm to unregister + std.AssertOriginCall() + + caller := std.GetOrigCaller() + if caller != APPROVED_UNREGISTER_CALLER { + panic("unauthorized") + } + + _, found := registered[pkgPath] + if found { + delete(registered, pkgPath) + } +} + +func BalanceOfByInterface(pkgPath string, owner std.Address) uint64 { + igrc20, found := registered[pkgPath] + if !found { + return false + } + + return igrc20.BalanceOf()(users.AddressOrName(owner)) +} + +func TransferByInterface(pkgPath string, to std.Address, amount uint64) bool { + igrc20, found := registered[pkgPath] + if !found { + return false + } + + // will panic if transfer fails + igrc20.Transfer()(users.AddressOrName(to), amount) + + return true +} + +func TransferFromByInterface(pkgPath string, from, to std.Address, amount uint64) bool { + igrc20, found := registered[pkgPath] + if !found { + return false + } + + // will panic if transferFrom fails + igrc20.TransferFrom()(users.AddressOrName(from), users.AddressOrName(to), amount) + + return true +} diff --git a/examples/gno.land/r/x/grc20_dynamic_call/registry/z_0_transfer_filetest.gno b/examples/gno.land/r/x/grc20_dynamic_call/registry/z_0_transfer_filetest.gno new file mode 100644 index 00000000000..6d7a7016a3f --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/registry/z_0_transfer_filetest.gno @@ -0,0 +1,87 @@ +// PKGPATH: gno.land/r/x/grc20_dynamic_call/registries_test +package registries_test + +import ( + "std" + + "gno.land/p/demo/testutils" + + "gno.land/r/x/grc20_dynamic_call/registry" + + "gno.land/r/x/grc20_dynamic_call/bar" + "gno.land/r/x/grc20_dynamic_call/foo" + + "gno.land/r/demo/users" +) + +var ( + actualRealmAddr std.Address + transferTo std.Address +) + +// FOO +type FooStruct struct{} + +func (FooStruct) Transfer() func(to users.AddressOrName, amount uint64) { + return foo.Transfer +} +func (FooStruct) TransferFrom() func(from, to users.AddressOrName, amount uint64) { + return foo.TransferFrom +} +func (FooStruct) BalanceOf() func(owner users.AddressOrName) uint64 { + return foo.BalanceOf +} + +// BAR +type BarStruct struct{} + +func (BarStruct) Transfer() func(to users.AddressOrName, amount uint64) { + return bar.Transfer +} +func (BarStruct) TransferFrom() func(from, to users.AddressOrName, amount uint64) { + return bar.TransferFrom +} +func (BarStruct) BalanceOf() func(owner users.AddressOrName) uint64 { + return bar.BalanceOf +} + +func init() { + registry.RegisterGRC20Interface("gno.land/r/x/grc20_dynamic_call/foo", FooStruct{}) + registry.RegisterGRC20Interface("gno.land/r/x/grc20_dynamic_call/bar", BarStruct{}) + + actualRealmAddr = std.DerivePkgAddr("gno.land/r/x/grc20_dynamic_call/registry") + + transferTo = testutils.TestAddress("transferTo") +} + +func main() { + // FOO + fooBalanceRealm := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", actualRealmAddr) + println(fooBalanceRealm) + + fooBalanceTo := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", transferTo) + println(fooBalanceTo) + + registry.TransferByInterface("gno.land/r/x/grc20_dynamic_call/foo", transferTo, 12345) + fooBalanceTo = registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", transferTo) + println(fooBalanceTo) + + // BAR + barBalanceRealm := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/bar", actualRealmAddr) + println(barBalanceRealm) + + barBalanceTo := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/bar", transferTo) + println(barBalanceTo) + + registry.TransferByInterface("gno.land/r/x/grc20_dynamic_call/bar", transferTo, 54321) + barBalanceTo = registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/bar", transferTo) + println(barBalanceTo) +} + +// Output: +// 10000000 +// 0 +// 12345 +// 10000000 +// 0 +// 54321 diff --git a/examples/gno.land/r/x/grc20_dynamic_call/registry/z_1_transferfrom_filetest.gno b/examples/gno.land/r/x/grc20_dynamic_call/registry/z_1_transferfrom_filetest.gno new file mode 100644 index 00000000000..a5f87ccf399 --- /dev/null +++ b/examples/gno.land/r/x/grc20_dynamic_call/registry/z_1_transferfrom_filetest.gno @@ -0,0 +1,79 @@ +package main + +import ( + "std" + + "gno.land/p/demo/testutils" + + "gno.land/r/x/grc20_dynamic_call/registry" + + "gno.land/r/x/grc20_dynamic_call/bar" + "gno.land/r/x/grc20_dynamic_call/foo" + + "gno.land/r/demo/users" +) + +var ( + actualRealmAddr std.Address + + transferFromFrom std.Address = testutils.TestAddress("transferFromFrom") + transferFromTo std.Address = testutils.TestAddress("transferFromTo") +) + +// FOO +type FooStruct struct{} + +func (FooStruct) Transfer() func(to users.AddressOrName, amount uint64) { + return foo.Transfer +} +func (FooStruct) TransferFrom() func(from, to users.AddressOrName, amount uint64) { + return foo.TransferFrom +} +func (FooStruct) BalanceOf() func(owner users.AddressOrName) uint64 { + return foo.BalanceOf +} + +// BAR +type BarStruct struct{} + +func (BarStruct) Transfer() func(to users.AddressOrName, amount uint64) { + return bar.Transfer +} +func (BarStruct) TransferFrom() func(from, to users.AddressOrName, amount uint64) { + return bar.TransferFrom +} +func (BarStruct) BalanceOf() func(owner users.AddressOrName) uint64 { + return bar.BalanceOf +} + +func init() { + registry.RegisterGRC20Interface("gno.land/r/x/grc20_dynamic_call/foo", FooStruct{}) + registry.RegisterGRC20Interface("gno.land/r/x/grc20_dynamic_call/bar", BarStruct{}) + + actualRealmAddr = std.DerivePkgAddr("gno.land/r/x/grc20_dynamic_call/registry") +} + +func main() { + foo.Faucet() + foo.Approve(users.AddressOrName(std.DerivePkgAddr("gno.land/r/x/grc20_dynamic_call/registry")), 1000*10000) + + fooBalanceOrigCaller := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", std.GetOrigCaller()) + println(fooBalanceOrigCaller) + + fooBalanceTo := registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", transferFromTo) + println(fooBalanceTo) + + registry.TransferFromByInterface("gno.land/r/x/grc20_dynamic_call/foo", std.GetOrigCaller(), transferFromTo, 54321) + + fooBalanceOrigCaller = registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", std.GetOrigCaller()) + println(fooBalanceOrigCaller) + + fooBalanceTo = registry.BalanceOfByInterface("gno.land/r/x/grc20_dynamic_call/foo", transferFromTo) + println(fooBalanceTo) +} + +// Output: +// 10000000 +// 0 +// 9945679 +// 54321 diff --git a/gno.land/cmd/gnoland/testdata/grc20_dynamic.txtar b/gno.land/cmd/gnoland/testdata/grc20_dynamic.txtar new file mode 100644 index 00000000000..6756ae5f0ca --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/grc20_dynamic.txtar @@ -0,0 +1,21 @@ +# test for calling grc20 token's function with registering interface + +## start gnoland node +gnoland start + +## faucet foo token by test1 +gnokey maketx call -pkgpath gno.land/r/x/grc20_dynamic_call/foo -func Faucet -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +## check test1 foo balance +gnokey maketx call -pkgpath gno.land/r/x/grc20_dynamic_call/registry -func BalanceOfByInterfaceName -args 'gno.land/r/x/grc20_dynamic_call/foo' -args 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(10000000 uint64)' + +## approve +gnokey maketx call -pkgpath gno.land/r/x/grc20_dynamic_call/foo -func Approve -args 'g1tkmrcu9m0xjddxh0c29r3zac8m2yjwaytlvh6u' -args '12345' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +## transferFrom foo token using registered interface +gnokey maketx call -pkgpath gno.land/r/x/grc20_dynamic_call/registry -func TransferFromByInterfaceName -args 'gno.land/r/x/grc20_dynamic_call/foo' -args 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' -args 'g12345' -args '12345' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +## check balance after TransferByInterfaceName +gnokey maketx call -pkgpath gno.land/r/x/grc20_dynamic_call/registry -func BalanceOfByInterfaceName -args 'gno.land/r/x/grc20_dynamic_call/foo' -args 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5' -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 +stdout '(9987655 uint64)' \ No newline at end of file diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 519b183ad3a..a6c5d3d6cdb 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1356,6 +1356,8 @@ func fillTypesOfValue(store Store, val Value) Value { for cur := cv.List.Head; cur != nil; cur = cur.Next { fillTypesTV(store, &cur.Key) fillTypesTV(store, &cur.Value) + + cv.vmap[cur.Key.ComputeMapKey(store, false)] = cur } return cv case TypeValue: