Skip to content
Merged
10 changes: 5 additions & 5 deletions docs/resources/effective-gno.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ In Gno, `init()` primarily serves two purposes:
```go
import "gno.land/r/some/registry"

func init() {
registry.Register(cross, "myID", myCallback)
func init(cur realm) {
registry.Register(cross(cur), "myID", myCallback)
}

func myCallback(a, b string) { /* ... */ }
Expand Down Expand Up @@ -809,7 +809,7 @@ have been consumed by the intermediary. Two attacker shapes bypass the check:

1. **Intermediate code realm.** User calls `r/attacker/wrapper.DoIt()` with
`-send 1000000ugnot`. The wrapper keeps the coins (via its own banker) and
then calls `BuyThing(cross, ...)` on your realm. Your realm sees
then calls `BuyThing(cross(cur), ...)` on your realm. Your realm sees
`OriginSend() = 1000000ugnot`, the `IsUser()` check passes because... actually
it doesn't — `IsUser()` rejects pure code realms. Which leads to:

Expand All @@ -818,7 +818,7 @@ have been consumed by the intermediary. Two attacker shapes bypass the check:
That script runs in an ephemeral code realm at path
`gno.land/e/{attacker}/run`. Inside main, the script consumes the origin-send
envelope (via its own `BankerTypeOriginSend`) or simply does whatever it
wants with the coins, then calls `BuyThing(cross, ...)`. Your realm sees
wants with the coins, then calls `BuyThing(cross(cur), ...)`. Your realm sees
`OriginSend() = 1000000ugnot` in the envelope and `IsUser() = true` because
**`IsUser()` accepts both `IsUserCall()` (pure EOA) AND `IsUserRun()` (user-run
ephemeral realm)**. The check passes but no coins reached your realm.
Expand Down Expand Up @@ -897,7 +897,7 @@ var (
)

func init(cur realm) {
Token, privateLedger = grc20.NewToken(0, cur, "Foo Token", "FOO", 4)
Token, privateLedger = grc20.NewToken("Foo Token", "FOO", 4, "token", cur)
UserTeller = Token.CallerTeller()
}

Expand Down
8 changes: 4 additions & 4 deletions docs/users/interact-with-gnokey.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,16 +419,16 @@ Now, we should have the following folder structure:
```

In the `script.gno` file, first define the package to be `main`. Then we can import
the Counter realm and define a `main()` function with no return values that will
the Counter realm and define a `main(cur realm)` function with no return values that will
be automatically detected and run. In it, we can call the `Increment()` function.

```go
package main

import "gno.land/r/demo/counter"

func main() {
println(counter.Increment(cross))
func main(cur realm) {
println(counter.Increment(cross(cur)))
}
```

Expand Down Expand Up @@ -1261,7 +1261,7 @@ var (
)

func init(cur realm) {
Token, adm = grc20.NewToken(0, cur, "wrapped GNOT", "wugnot", 0)
Token, adm = grc20.NewToken("wrapped GNOT", "wugnot", 0, "token", cur)
}

const (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// PKGPATH: gno.land/r/demo/grc20identity

package grc20identity

import (
"chain"

"gno.land/p/demo/tokens/grc20"
"gno.land/p/nt/seqid/v0"
)

var nextTokenID seqid.ID

func main(cur realm) {
first, firstLedger := newToken(cur)
second, secondLedger := newToken(cur)

if first.ID() != "gno.land/r/demo/grc20identity.0000001" {
panic("unexpected first token ID: " + first.ID())
}
if second.ID() != "gno.land/r/demo/grc20identity.0000002" {
panic("unexpected second token ID: " + second.ID())
}

holder := chain.PackageAddress("holder")
spender := chain.PackageAddress("spender")
firstLedger.Mint(holder, 1)
firstLedger.Approve(holder, spender, 1)
secondLedger.Mint(holder, 2)
secondLedger.Approve(holder, spender, 2)
}

func newToken(cur realm) (*grc20.Token, *grc20.PrivateLedger) {
id := nextTokenID.Next().String()
return grc20.NewToken("Same", "DUP", 6, id, cur)
}

// Events:
// [
// {
// "type": "Transfer",
// "attrs": [
// {
// "key": "token",
// "value": "gno.land/r/demo/grc20identity.0000001"
// },
// {
// "key": "from",
// "value": ""
// },
// {
// "key": "to",
// "value": "g18rwjsdf0kcy673ta0q4ujwvsncz4k2a8gcv94t"
// },
// {
// "key": "value",
// "value": "1"
// }
// ],
// "pkg_path": "gno.land/p/demo/tokens/grc20"
// },
// {
// "type": "Approval",
// "attrs": [
// {
// "key": "token",
// "value": "gno.land/r/demo/grc20identity.0000001"
// },
// {
// "key": "owner",
// "value": "g18rwjsdf0kcy673ta0q4ujwvsncz4k2a8gcv94t"
// },
// {
// "key": "spender",
// "value": "g148tp8xkvvk3l73lmxywpdlsxgdujjlclx7wfyg"
// },
// {
// "key": "value",
// "value": "1"
// }
// ],
// "pkg_path": "gno.land/p/demo/tokens/grc20"
// },
// {
// "type": "Transfer",
// "attrs": [
// {
// "key": "token",
// "value": "gno.land/r/demo/grc20identity.0000002"
// },
// {
// "key": "from",
// "value": ""
// },
// {
// "key": "to",
// "value": "g18rwjsdf0kcy673ta0q4ujwvsncz4k2a8gcv94t"
// },
// {
// "key": "value",
// "value": "2"
// }
// ],
// "pkg_path": "gno.land/p/demo/tokens/grc20"
// },
// {
// "type": "Approval",
// "attrs": [
// {
// "key": "token",
// "value": "gno.land/r/demo/grc20identity.0000002"
// },
// {
// "key": "owner",
// "value": "g18rwjsdf0kcy673ta0q4ujwvsncz4k2a8gcv94t"
// },
// {
// "key": "spender",
// "value": "g148tp8xkvvk3l73lmxywpdlsxgdujjlclx7wfyg"
// },
// {
// "key": "value",
// "value": "2"
// }
// ],
// "pkg_path": "gno.land/p/demo/tokens/grc20"
// }
// ]
8 changes: 4 additions & 4 deletions examples/gno.land/p/demo/tokens/grc20/tellers_test.gno
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestCallerTellerImpl(cur realm, t *testing.T) {
tok, _ := newTestToken(0, cur, "Dummy", "DUMMY", 4)
tok, _ := newTestToken("Dummy", "DUMMY", 4, "DUMMY", cur)
teller := tok.CallerTeller()
urequire.False(t, tok == nil)
var _ Teller = teller
Expand All @@ -24,7 +24,7 @@ type evilWrap struct {
}

func TestIsCanonicalTeller(cur realm, t *testing.T) {
tok, _ := newTestToken(0, cur, "Dummy", "DUMMY", 4)
tok, _ := newTestToken("Dummy", "DUMMY", 4, "DUMMY", cur)
legit := tok.CallerTeller()

uassert.True(t, IsCanonicalTeller(legit),
Expand All @@ -42,7 +42,7 @@ func TestTeller(cur realm, t *testing.T) {
carl = testutils.TestAddress("carl")
)

token, ledger := newTestToken(0, cur, "Dummy", "DUMMY", 6)
token, ledger := newTestToken("Dummy", "DUMMY", 6, "DUMMY", cur)

checkBalances := func(aliceEB, bobEB, carlEB int64) {
t.Helper()
Expand Down Expand Up @@ -104,7 +104,7 @@ func TestCallerTeller(cur realm, t *testing.T) {
bob := testutils.TestAddress("bob")
carl := testutils.TestAddress("carl")

token, ledger := newTestToken(0, cur, "Dummy", "DUMMY", 6)
token, ledger := newTestToken("Dummy", "DUMMY", 6, "DUMMY", cur)
teller := token.CallerTeller()

checkBalances := func(aliceEB, bobEB, carlEB int64) {
Expand Down
44 changes: 30 additions & 14 deletions examples/gno.land/p/demo/tokens/grc20/token.gno
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,24 @@ import (
// NewToken creates a Token whose origRealm is bound to the calling realm.
// rlm must be the caller's own captured cur (asserted via rlm.IsCurrent()),
// and rlm.PkgPath() — the calling realm itself — becomes the Token's
// origRealm. Token.ID() returns origRealm + "." + symbol.
// origRealm. Token.ID() returns origRealm + "." + id.
//
// Because IsCurrent runtime-validates that rlm came from the live
// crossing frame, origRealm is unforgeable: an external realm cannot
// fabricate a Token claiming to belong to a different package.
//
// Typical call from a realm's init(cur realm) or other crossing function:
// Realms that can create multiple tokens should allocate IDs from one
// persistent seqid.ID from gno.land/p/nt/seqid/v0, shared by every creation
Comment thread
aeddi marked this conversation as resolved.
Outdated
// path:
//
// Token, ledger := grc20.NewToken(0, cur, "Foo", "FOO", 4)
// var nextTokenID seqid.ID
// id := nextTokenID.Next().String()
// Token, ledger := grc20.NewToken("Foo", "FOO", 4, id, cur)
//
// If the Token should be discoverable, follow up with
// grc20reg.Register(cross, Token, slug).
func NewToken(_ int, rlm realm, name, symbol string, decimals int) (*Token, *PrivateLedger) {
// grc20reg.Register(cross(cur), Token, slug).
// rlm is last so this /p/ function remains non-crossing.
Comment thread
aeddi marked this conversation as resolved.
Outdated
func NewToken(name, symbol string, decimals int, id string, rlm realm) (*Token, *PrivateLedger) {
if !rlm.IsCurrent() {
panic(ErrSpoofedRealm)
}
Expand All @@ -35,6 +40,9 @@ func NewToken(_ int, rlm realm, name, symbol string, decimals int) (*Token, *Pri
if !validName(name) {
panic(ErrInvalidName)
}
if !validID(id) {
panic(ErrInvalidID)
}
if !validSymbol(symbol) {
panic(ErrInvalidSymbol)
}
Expand All @@ -43,6 +51,7 @@ func NewToken(_ int, rlm realm, name, symbol string, decimals int) (*Token, *Pri
}
ledger := &PrivateLedger{}
token := &Token{
id: id,
name: name,
symbol: symbol,
decimals: decimals,
Expand All @@ -53,6 +62,11 @@ func NewToken(_ int, rlm realm, name, symbol string, decimals int) (*Token, *Pri
return token, ledger
}

// validID reports whether id is a valid seqid-compatible slug.
func validID(id string) bool {
return validSlug(id, MaxIDLen)
}

// validName reports whether name is a valid display name: non-empty,
// within MaxNameLen, and contains no control characters (any rune
// below 0x20 or 0x7f). Permits Unicode letters, digits, punctuation,
Expand All @@ -69,15 +83,17 @@ func validName(name string) bool {
return true
}

// validSymbol reports whether symbol is a valid token symbol: non-empty,
// within MaxSymbolLen, and consists only of [A-Za-z0-9_-]. Matches
// grc20reg.validateSlug so the symbol round-trips cleanly through
// Token.ID() and any registry binding.
func validSymbol(symbol string) bool {
if symbol == "" || len(symbol) > MaxSymbolLen {
// validSymbol reports whether s is valid slug-compatible metadata: non-empty,
// within MaxSymbolLen, and consists only of [A-Za-z0-9_-].
func validSymbol(s string) bool {
return validSlug(s, MaxSymbolLen)
}

func validSlug(s string, maxLen int) bool {
if s == "" || len(s) > maxLen {
return false
}
for _, c := range symbol {
for _, c := range s {
if !isAlnum(c) && c != '_' && c != '-' {
return false
}
Expand Down Expand Up @@ -105,9 +121,9 @@ func (tok Token) TotalSupply() int64 { return tok.ledger.totalSupply }
func (tok Token) KnownAccounts() int { return tok.ledger.balances.Size() }

// ID returns the Identifier of the token.
// It is composed of the original realm and the provided symbol.
// It is composed of the original realm and the provided ID.
func (tok *Token) ID() string {
return tok.origRealm + "." + tok.symbol
return tok.origRealm + "." + tok.id
}

// HasAddr checks if the specified address is a known account in the bank.
Expand Down
Loading
Loading