Skip to content

Commit

Permalink
feat: add grc20reg that works... today (#3135)
Browse files Browse the repository at this point in the history
- [x] Switch to storing a `type XXX func() grc20.Token` instead of a
`grc20.Token` directly.
-  [x] Implement `grc20reg`.  
- [x] Add new tests in `gnovm/tests` to demonstrate the current VM's
management of the cross-realm feature and support potential changes in
#2743.
- [x] Create a demo in `atomicswap` or a similar application.
(#2510 (comment))
-  [x] Try using a `Token.Getter()` helper.  (Works! f99654e)
- [ ] Demonstrate how to manage "disappearing" functions during garbage
collection by checking if the function pointer is nil or non-resolvable.

Alternative to #2516  
NOT(!) depending on #2743

---------

Signed-off-by: moul <[email protected]>
  • Loading branch information
moul authored Dec 3, 2024
1 parent a6f1aba commit bc44a39
Show file tree
Hide file tree
Showing 29 changed files with 3,495 additions and 62 deletions.
2 changes: 1 addition & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test:

.PHONY: lint
lint:
go run ../gnovm/cmd/gno lint $(OFFICIAL_PACKAGES)
go run ../gnovm/cmd/gno lint -v $(OFFICIAL_PACKAGES)

.PHONY: test.sync
test.sync:
Expand Down
41 changes: 28 additions & 13 deletions examples/gno.land/p/demo/grc/grc20/types.gno
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ type Teller interface {
//
// Returns an error if the operation failed.
//
// 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:
// 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
Approve(spender std.Address, amount uint64) error

Expand All @@ -63,12 +63,23 @@ type Teller interface {
// name, symbol, and decimals, as well as methods for interacting with the
// ledger, including checking balances and allowances.
type Token struct {
name string // Name of the token (e.g., "Dummy Token").
symbol string // Symbol of the token (e.g., "DUMMY").
decimals uint // Number of decimal places used for the token's precision.
ledger *PrivateLedger // Pointer to the PrivateLedger that manages balances and allowances.
// Name of the token (e.g., "Dummy Token").
name string
// Symbol of the token (e.g., "DUMMY").
symbol string
// Number of decimal places used for the token's precision.
decimals uint
// Pointer to the PrivateLedger that manages balances and allowances.
ledger *PrivateLedger
}

// TokenGetter is a function type that returns a Token pointer. This type allows
// bypassing a limitation where we cannot directly pass Token pointers between
// realms. Instead, we pass this function which can then be called to get the
// Token pointer. For more details on this limitation and workaround, see:
// https://github.com/gnolang/gno/pull/3135
type TokenGetter func() *Token

// PrivateLedger is a struct that holds the balances and allowances for the
// token. It provides administrative functions for minting, burning,
// transferring tokens, and managing allowances.
Expand All @@ -77,10 +88,14 @@ type Token struct {
// information regarding token balances and allowances, and allows direct,
// unrestricted access to all administrative functions.
type PrivateLedger struct {
totalSupply uint64 // Total supply of the token managed by this ledger.
balances avl.Tree // std.Address -> uint64
allowances avl.Tree // owner.(std.Address)+":"+spender.(std.Address)) -> uint64
token *Token // Pointer to the associated Token struct
// Total supply of the token managed by this ledger.
totalSupply uint64
// std.Address -> uint64
balances avl.Tree
// owner.(std.Address)+":"+spender.(std.Address)) -> uint64
allowances avl.Tree
// Pointer to the associated Token struct
token *Token
}

var (
Expand Down
4 changes: 3 additions & 1 deletion examples/gno.land/r/demo/bar20/bar20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ufmt"
"gno.land/r/demo/grc20reg"
)

var (
Expand All @@ -17,7 +18,8 @@ var (
)

func init() {
// XXX: grc20reg.Register(Token, "")
getter := func() *grc20.Token { return Token }
grc20reg.Register(getter, "")
}

func Faucet() string {
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/bar20/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ require (
gno.land/p/demo/testutils v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/urequire v0.0.0-latest
gno.land/r/demo/grc20reg v0.0.0-latest
)
4 changes: 3 additions & 1 deletion examples/gno.land/r/demo/foo20/foo20.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"gno.land/p/demo/ownable"
"gno.land/p/demo/ufmt"
pusers "gno.land/p/demo/users"
"gno.land/r/demo/grc20reg"
"gno.land/r/demo/users"
)

Expand All @@ -21,7 +22,8 @@ var (

func init() {
privateLedger.Mint(owner.Owner(), 1_000_000*10_000) // @privateLedgeristrator (1M)
// XXX: grc20reg.Register(Token, "")
getter := func() *grc20.Token { return Token }
grc20reg.Register(getter, "")
}

func TotalSupply() uint64 {
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/foo20/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ require (
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/users v0.0.0-latest
gno.land/r/demo/grc20reg v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
)
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/grc20factory/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ require (
gno.land/p/demo/testutils v0.0.0-latest
gno.land/p/demo/uassert v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/r/demo/grc20reg v0.0.0-latest
)
4 changes: 3 additions & 1 deletion examples/gno.land/r/demo/grc20factory/grc20factory.gno
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ownable"
"gno.land/p/demo/ufmt"
"gno.land/r/demo/grc20reg"
)

var instances avl.Tree // symbol -> instance
Expand Down Expand Up @@ -42,7 +43,8 @@ func NewWithAdmin(name, symbol string, decimals uint, initialMint, faucet uint64
faucet: faucet,
}
instances.Set(symbol, &inst)
// XXX: grc20reg.Register(token, symbol)
getter := func() *grc20.Token { return token }
grc20reg.Register(getter, symbol)
}

func (inst instance) Token() *grc20.Token {
Expand Down
9 changes: 9 additions & 0 deletions examples/gno.land/r/demo/grc20reg/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module gno.land/r/demo/grc20reg

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

import (
"std"

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

var registry = avl.NewTree() // rlmPath[.slug] -> TokenGetter (slug is optional)

func Register(tokenGetter grc20.TokenGetter, slug string) {
rlmPath := std.PrevRealm().PkgPath()
key := fqname.Construct(rlmPath, slug)
registry.Set(key, tokenGetter)
std.Emit(
registerEvent,
"pkgpath", rlmPath,
"slug", slug,
)
}

func Get(key string) grc20.TokenGetter {
tokenGetter, ok := registry.Get(key)
if !ok {
return nil
}
return tokenGetter.(grc20.TokenGetter)
}

func MustGet(key string) grc20.TokenGetter {
tokenGetter := Get(key)
if tokenGetter == nil {
panic("unknown token: " + key)
}
return tokenGetter
}

func Render(path string) string {
switch {
case path == "": // home
// TODO: add pagination
s := ""
count := 0
registry.Iterate("", "", func(key string, tokenI interface{}) bool {
count++
tokenGetter := tokenI.(grc20.TokenGetter)
token := tokenGetter()
rlmPath, slug := fqname.Parse(key)
rlmLink := fqname.RenderLink(rlmPath, slug)
infoLink := "/r/demo/grc20reg:" + key
s += ufmt.Sprintf("- **%s** - %s - [info](%s)\n", token.GetName(), rlmLink, infoLink)
return false
})
if count == 0 {
return "No registered token."
}
return s
default: // specific token
key := path
tokenGetter := MustGet(key)
token := tokenGetter()
rlmPath, slug := fqname.Parse(key)
rlmLink := fqname.RenderLink(rlmPath, slug)
s := ufmt.Sprintf("# %s\n", token.GetName())
s += ufmt.Sprintf("- symbol: **%s**\n", token.GetSymbol())
s += ufmt.Sprintf("- realm: %s\n", rlmLink)
s += ufmt.Sprintf("- decimals: %d\n", token.GetDecimals())
s += ufmt.Sprintf("- total supply: %d\n", token.TotalSupply())
return s
}
}

const registerEvent = "register"
59 changes: 59 additions & 0 deletions examples/gno.land/r/demo/grc20reg/grc20reg_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package grc20reg

import (
"std"
"strings"
"testing"

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

func TestRegistry(t *testing.T) {
std.TestSetRealm(std.NewCodeRealm("gno.land/r/demo/foo"))
realmAddr := std.CurrentRealm().PkgPath()
token, ledger := grc20.NewToken("TestToken", "TST", 4)
ledger.Mint(std.CurrentRealm().Addr(), 1234567)
tokenGetter := func() *grc20.Token { return token }
// register
Register(tokenGetter, "")
regTokenGetter := Get(realmAddr)
regToken := regTokenGetter()
urequire.True(t, regToken != nil, "expected to find a token") // fixme: use urequire.NotNil
urequire.Equal(t, regToken.GetSymbol(), "TST")

expected := `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo) - [info](/r/demo/grc20reg:gno.land/r/demo/foo)
`
got := Render("")
urequire.True(t, strings.Contains(got, expected))
// 404
invalidToken := Get("0xdeadbeef")
urequire.True(t, invalidToken == nil)

// register with a slug
Register(tokenGetter, "mySlug")
regTokenGetter = Get(realmAddr + ".mySlug")
regToken = regTokenGetter()
urequire.True(t, regToken != nil, "expected to find a token") // fixme: use urequire.NotNil
urequire.Equal(t, regToken.GetSymbol(), "TST")

// override
Register(tokenGetter, "")
regTokenGetter = Get(realmAddr + "")
regToken = regTokenGetter()
urequire.True(t, regToken != nil, "expected to find a token") // fixme: use urequire.NotNil
urequire.Equal(t, regToken.GetSymbol(), "TST")

got = Render("")
urequire.True(t, strings.Contains(got, `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo) - [info](/r/demo/grc20reg:gno.land/r/demo/foo)`))
urequire.True(t, strings.Contains(got, `- **TestToken** - [gno.land/r/demo/foo](/r/demo/foo).mySlug - [info](/r/demo/grc20reg:gno.land/r/demo/foo.mySlug)`))

expected = `# TestToken
- symbol: **TST**
- realm: [gno.land/r/demo/foo](/r/demo/foo).mySlug
- decimals: 4
- total supply: 1234567
`
got = Render("gno.land/r/demo/foo.mySlug")
urequire.Equal(t, expected, got)
}
28 changes: 28 additions & 0 deletions examples/gno.land/r/demo/tests/crossrealm/crossrealm.gno
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,31 @@ func Make1() *p_crossrealm.Container {
B: local,
}
}

type Fooer interface{ Foo() }

var fooer Fooer

func SetFooer(f Fooer) Fooer {
fooer = f
return fooer
}

func GetFooer() Fooer { return fooer }

func CallFooerFoo() { fooer.Foo() }

type FooerGetter func() Fooer

var fooerGetter FooerGetter

func SetFooerGetter(fg FooerGetter) FooerGetter {
fooerGetter = fg
return fg
}

func GetFooerGetter() FooerGetter {
return fooerGetter
}

func CallFooerGetterFoo() { fooerGetter().Foo() }
25 changes: 25 additions & 0 deletions examples/gno.land/r/demo/tests/crossrealm_b/crossrealm.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package crossrealm_b

import (
"std"

"gno.land/r/demo/tests/crossrealm"
)

type fooer struct {
s string
}

func (f *fooer) SetS(newVal string) {
f.s = newVal
}

func (f *fooer) Foo() {
println("hello " + f.s + " cur=" + std.CurrentRealm().PkgPath() + " prev=" + std.PrevRealm().PkgPath())
}

var (
Fooer = &fooer{s: "A"}
FooerGetter = func() crossrealm.Fooer { return Fooer }
FooerGetterBuilder = func() crossrealm.FooerGetter { return func() crossrealm.Fooer { return Fooer } }
)
3 changes: 3 additions & 0 deletions examples/gno.land/r/demo/tests/crossrealm_b/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module gno.land/r/demo/tests/crossrealm_b

require gno.land/r/demo/tests/crossrealm v0.0.0-latest
2 changes: 2 additions & 0 deletions examples/gno.land/r/demo/tests/tests.gno
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type TestRealmObject struct {
Field string
}

var TestRealmObjectValue TestRealmObject

func ModifyTestRealmObject(t *TestRealmObject) {
t.Field += "_modified"
}
Expand Down
1 change: 1 addition & 0 deletions examples/gno.land/r/demo/wugnot/gno.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ require (
gno.land/p/demo/grc/grc20 v0.0.0-latest
gno.land/p/demo/ufmt v0.0.0-latest
gno.land/p/demo/users v0.0.0-latest
gno.land/r/demo/grc20reg v0.0.0-latest
gno.land/r/demo/users v0.0.0-latest
)
4 changes: 3 additions & 1 deletion examples/gno.land/r/demo/wugnot/wugnot.gno
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ufmt"
pusers "gno.land/p/demo/users"
"gno.land/r/demo/grc20reg"
"gno.land/r/demo/users"
)

Expand All @@ -18,7 +19,8 @@ const (
)

func init() {
// XXX: grc20reg.Register(Token, "")
getter := func() *grc20.Token { return Token }
grc20reg.Register(getter, "")
}

func Deposit() {
Expand Down
Loading

0 comments on commit bc44a39

Please sign in to comment.