Pure package detail
grc721
gno.land/p/nym-helox118/grc721
Indexed deployment identity with independently loaded latest RPC source. Functions and Render are realm-only RPC capabilities.
Indexed deployment
Identity
- Package path
- gno.land/p/nym-helox118/grc721
- Block
- 29234
- Deployed (UTC)
- Transaction
- WvI3JaOGQdU0Y+qqS8KEhCaLDXFy1hFl8ZSjdMZ/BYI=
Latest RPC state
Source
package grc721
import (
"strings"
"testing"
"gno.land/p/nt/seqid/v0"
"gno.land/p/nt/testutils/v0"
"gno.land/p/nt/uassert/v0"
"gno.land/p/nt/urequire/v0"
)
func mustBalanceOf(t *testing.T, tok *Token, addr address) int64 {
balance, err := tok.BalanceOf(addr)
urequire.NoError(t, err)
return balance
}
// newTestToken builds via same-realm cross (NewToken requires rlm.IsCurrent()).
func newTestToken(name, symbol string, id seqid.ID, rlm realm) (tok *Token, led *PrivateLedger) {
func(cur realm) {
tok, led = NewToken(name, symbol, id, cur)
}(cross(rlm))
return
}
type mockExtension struct {
mints, transfers, burns int
lastMintTo address
lastTransferFrom address
lastTransferTo address
lastBurn TokenID
}
func (m *mockExtension) ExtensionKind() string { return "mock" }
func (m *mockExtension) OnMint(to address, tid TokenID) {
m.mints++
m.lastMintTo = to
}
func (m *mockExtension) OnTransfer(from, to address, tid TokenID) {
m.transfers++
m.lastTransferFrom = from
m.lastTransferTo = to
}
func (m *mockExtension) OnBurn(tid TokenID) {
m.burns++
m.lastBurn = tid
}
// Value type holding a slice: uncomparable, so `==` on two of them is a runtime fault.
type journalExtension struct{ seen []TokenID }
func (e journalExtension) ExtensionKind() string { return "journal" }
func (e journalExtension) OnMint(to address, tid TokenID) {}
func (e journalExtension) OnTransfer(from, to address, tid TokenID) {}
func (e journalExtension) OnBurn(tid TokenID) {}
var (
alice = testutils.TestAddress("alice")
bob = testutils.TestAddress("bob")
carl = testutils.TestAddress("carl")
)
func TestNewToken(cur realm, t *testing.T) {
tok, led := newTestToken("Foo NFT", "FOO", 0, cur)
urequire.False(t, tok == nil, "token should not be nil")
urequire.False(t, led == nil, "ledger should not be nil")
uassert.Equal(t, "Foo NFT", tok.GetName())
uassert.Equal(t, "FOO", tok.GetSymbol())
uassert.Equal(t, int64(0), tok.TotalSupply())
uassert.Equal(t, 0, tok.KnownAccounts())
uassert.True(t, strings.HasSuffix(tok.ID(), ".FOO."+seqid.ID(0).String()), "ID must end with .symbol.id")
uassert.True(t, strings.HasPrefix(tok.ID(), "gno.land/"), "ID must begin with the origin realm path")
uassert.Equal(t, tok.ID(), led.ReadToken().ID())
}
func TestNewTokenDistinctIDs(cur realm, t *testing.T) {
first, _ := newTestToken("Same", "DUP", 1, cur)
second, _ := newTestToken("Same", "DUP", 2, cur)
uassert.True(t, first.ID() != second.ID(), "distinct seqid must yield distinct IDs")
a, _ := newTestToken("Same", "DUP", 3, cur)
b, _ := newTestToken("Same", "DUP", 3, cur)
uassert.Equal(t, a.ID(), b.ID())
}
func TestNewTokenValidation(cur realm, t *testing.T) {
longName := strings.Repeat("a", MaxNameLen+1)
maxName := strings.Repeat("a", MaxNameLen)
longSymbol := strings.Repeat("A", MaxSymbolLen+1)
tests := []struct {
name string
tokenName string
symbol string
wantAbort bool
wantSubstr string
}{
{"valid name and symbol succeeds", "Foo", "FOO", false, ""},
{"symbol with full slug charset succeeds", "Foo", "aZ9_-", false, ""},
{"name at max length succeeds", maxName, "FOO", false, ""},
{"empty name returns ErrInvalidName", "", "FOO", true, "invalid token name"},
{"name over max length returns ErrInvalidName", longName, "FOO", true, "invalid token name"},
{"name with low control char returns ErrInvalidName", "Foo\x01", "FOO", true, "invalid token name"},
{"name with DEL control char returns ErrInvalidName", "Foo\x7f", "FOO", true, "invalid token name"},
{"empty symbol returns ErrInvalidSymbol", "Foo", "", true, "invalid token symbol"},
{"symbol over max length returns ErrInvalidSymbol", "Foo", longSymbol, true, "invalid token symbol"},
{"symbol with space returns ErrInvalidSymbol", "Foo", "has space", true, "invalid token symbol"},
{"symbol with dot returns ErrInvalidSymbol", "Foo", "FO.O", true, "invalid token symbol"},
{"symbol with slash returns ErrInvalidSymbol", "Foo", "FO/O", true, "invalid token symbol"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if tc.wantAbort {
urequire.AbortsContains(t, cur, tc.wantSubstr, func() {
newTestToken(tc.tokenName, tc.symbol, 0, cur)
})
return
}
tok, _ := newTestToken(tc.tokenName, tc.symbol, 0, cur)
uassert.Equal(t, tc.tokenName, tok.GetName())
uassert.Equal(t, tc.symbol, tok.GetSymbol())
})
}
}
func TestNewTokenRejectsSpoofedRealm(cur realm, t *testing.T) {
// Stale outer cur in a fresh frame → ErrSpoofedRealm.
urequire.AbortsContains(t, cur, "rlm does not match the current crossing frame", func() {
stale := cur
func(cur realm) {
NewToken("Foo", "FOO", 0, stale)
}(cross(cur))
})
}
func TestMint(cur realm, t *testing.T) {
tests := []struct {
name string
action func(led *PrivateLedger) error
wantErr error
verify func(t *testing.T, tok *Token, led *PrivateLedger)
}{
{
name: "mint to valid address succeeds",
action: func(led *PrivateLedger) error {
return led.Mint(alice, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, int64(1), tok.TotalSupply())
uassert.Equal(t, int64(1), mustBalanceOf(t, tok, alice))
uassert.Equal(t, 1, tok.KnownAccounts())
owner, err := tok.OwnerOf("1")
urequire.NoError(t, err)
uassert.Equal(t, alice, owner)
},
},
{
name: "mint to invalid address returns ErrInvalidAddress",
action: func(led *PrivateLedger) error {
return led.Mint(zeroAddress, "1")
},
wantErr: ErrInvalidAddress,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, int64(0), tok.TotalSupply())
},
},
{
name: "mint duplicate token id returns ErrTokenIdAlreadyExists",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Mint(bob, "1")
},
wantErr: ErrTokenIdAlreadyExists,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
owner, err := tok.OwnerOf("1")
urequire.NoError(t, err)
uassert.Equal(t, alice, owner)
uassert.Equal(t, int64(1), tok.TotalSupply())
},
},
{
name: "minting distinct ids to same owner accumulates balance",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Mint(alice, "2")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, int64(2), mustBalanceOf(t, tok, alice))
uassert.Equal(t, int64(2), tok.TotalSupply())
uassert.Equal(t, 1, tok.KnownAccounts())
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
err := tc.action(led)
if tc.wantErr == nil {
uassert.NoError(t, err)
} else {
uassert.ErrorIs(t, err, tc.wantErr)
}
if tc.verify != nil {
tc.verify(t, tok, led)
}
})
}
}
func TestBurn(cur realm, t *testing.T) {
tests := []struct {
name string
action func(led *PrivateLedger) error
wantErr error
verify func(t *testing.T, tok *Token, led *PrivateLedger)
}{
{
name: "burn existing token clears all state",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Burn("1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, int64(0), tok.TotalSupply())
uassert.Equal(t, int64(0), mustBalanceOf(t, tok, alice))
uassert.Equal(t, 0, tok.KnownAccounts())
_, err := tok.OwnerOf("1")
uassert.ErrorIs(t, err, ErrInvalidTokenId)
},
},
{
name: "burn clears any outstanding approval",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.Approve(alice, bob, "1"))
return led.Burn("1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, 0, led.tokenApprovals.Size())
_, err := tok.GetApproved("1")
uassert.ErrorIs(t, err, ErrInvalidTokenId)
},
},
{
name: "burn missing token returns ErrInvalidTokenId",
action: func(led *PrivateLedger) error {
return led.Burn("999")
},
wantErr: ErrInvalidTokenId,
},
{
name: "burn one of two tokens keeps the account known",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.Mint(alice, "2"))
return led.Burn("1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.Equal(t, int64(1), mustBalanceOf(t, tok, alice))
uassert.Equal(t, 1, tok.KnownAccounts())
uassert.Equal(t, int64(1), tok.TotalSupply())
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
err := tc.action(led)
if tc.wantErr == nil {
uassert.NoError(t, err)
} else {
uassert.ErrorIs(t, err, tc.wantErr)
}
if tc.verify != nil {
tc.verify(t, tok, led)
}
})
}
}
func TestApprove(cur realm, t *testing.T) {
tests := []struct {
name string
action func(led *PrivateLedger) error
wantErr error
verify func(t *testing.T, tok *Token, led *PrivateLedger)
}{
{
name: "owner approves another account",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Approve(alice, bob, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
approved, err := tok.GetApproved("1")
urequire.NoError(t, err)
uassert.Equal(t, bob, approved)
},
},
{
name: "operator approves on behalf of owner",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.SetApprovalForAll(alice, bob, true))
return led.Approve(bob, carl, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
approved, err := tok.GetApproved("1")
urequire.NoError(t, err)
uassert.Equal(t, carl, approved)
},
},
{
name: "approve to zero address revokes an existing approval (EIP-721)",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.Approve(alice, bob, "1"))
return led.Approve(alice, zeroAddress, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
_, err := tok.GetApproved("1")
uassert.ErrorIs(t, err, ErrTokenIdNotApproved)
},
},
{
name: "revoke with no prior approval is a no-op",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Approve(alice, zeroAddress, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
_, err := tok.GetApproved("1")
uassert.ErrorIs(t, err, ErrTokenIdNotApproved)
},
},
{
name: "approve on missing token returns ErrInvalidTokenId",
action: func(led *PrivateLedger) error {
return led.Approve(alice, bob, "999")
},
wantErr: ErrInvalidTokenId,
},
{
name: "approve to current owner returns ErrApprovalToCurrentOwner",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Approve(alice, alice, "1")
},
wantErr: ErrApprovalToCurrentOwner,
},
{
name: "non-owner non-operator approve returns ErrCallerIsNotOwnerOrApproved",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.Approve(bob, carl, "1")
},
wantErr: ErrCallerIsNotOwnerOrApproved,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
err := tc.action(led)
if tc.wantErr == nil {
uassert.NoError(t, err)
} else {
uassert.ErrorIs(t, err, tc.wantErr)
}
if tc.verify != nil {
tc.verify(t, tok, led)
}
})
}
}
func TestSetApprovalForAll(cur realm, t *testing.T) {
tests := []struct {
name string
action func(led *PrivateLedger) error
wantErr error
verify func(t *testing.T, tok *Token, led *PrivateLedger)
}{
{
name: "grant operator approval",
action: func(led *PrivateLedger) error {
return led.SetApprovalForAll(alice, bob, true)
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.True(t, tok.IsApprovedForAll(alice, bob))
},
},
{
name: "revoke operator approval releases the node",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.SetApprovalForAll(alice, bob, true))
return led.SetApprovalForAll(alice, bob, false)
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.False(t, tok.IsApprovedForAll(alice, bob))
uassert.Equal(t, 0, led.operatorApprovals.Size())
},
},
{
name: "revoke without a prior grant stores nothing",
action: func(led *PrivateLedger) error {
return led.SetApprovalForAll(alice, bob, false)
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
uassert.False(t, tok.IsApprovedForAll(alice, bob))
uassert.Equal(t, 0, led.operatorApprovals.Size())
},
},
{
name: "invalid operator returns ErrInvalidAddress",
action: func(led *PrivateLedger) error {
return led.SetApprovalForAll(alice, zeroAddress, true)
},
wantErr: ErrInvalidAddress,
},
{
name: "owner equals operator returns ErrApprovalToCurrentOwner",
action: func(led *PrivateLedger) error {
return led.SetApprovalForAll(alice, alice, true)
},
wantErr: ErrApprovalToCurrentOwner,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
err := tc.action(led)
if tc.wantErr == nil {
uassert.NoError(t, err)
} else {
uassert.ErrorIs(t, err, tc.wantErr)
}
if tc.verify != nil {
tc.verify(t, tok, led)
}
})
}
}
func TestTransferFrom(cur realm, t *testing.T) {
tests := []struct {
name string
action func(led *PrivateLedger) error
wantErr error
verify func(t *testing.T, tok *Token, led *PrivateLedger)
}{
{
name: "owner transfers own token",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(alice, alice, bob, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
owner, err := tok.OwnerOf("1")
urequire.NoError(t, err)
uassert.Equal(t, bob, owner)
uassert.Equal(t, int64(0), mustBalanceOf(t, tok, alice))
uassert.Equal(t, int64(1), mustBalanceOf(t, tok, bob))
},
},
{
name: "approved spender transfers and approval is cleared",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.Approve(alice, bob, "1"))
return led.TransferFrom(bob, alice, carl, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
owner, err := tok.OwnerOf("1")
urequire.NoError(t, err)
uassert.Equal(t, carl, owner)
_, err = tok.GetApproved("1")
uassert.ErrorIs(t, err, ErrTokenIdNotApproved)
},
},
{
name: "operator transfers owner token",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.SetApprovalForAll(alice, bob, true))
return led.TransferFrom(bob, alice, carl, "1")
},
wantErr: nil,
verify: func(t *testing.T, tok *Token, led *PrivateLedger) {
owner, err := tok.OwnerOf("1")
urequire.NoError(t, err)
uassert.Equal(t, carl, owner)
},
},
{
name: "unauthorized spender returns ErrCallerIsNotOwnerOrApproved",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(bob, alice, carl, "1")
},
wantErr: ErrCallerIsNotOwnerOrApproved,
},
{
name: "spender on missing token returns ErrCallerIsNotOwnerOrApproved",
action: func(led *PrivateLedger) error {
return led.TransferFrom(alice, alice, bob, "999")
},
wantErr: ErrCallerIsNotOwnerOrApproved,
},
{
name: "transfer to self returns ErrCannotTransferToSelf",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(alice, alice, alice, "1")
},
wantErr: ErrCannotTransferToSelf,
},
{
name: "transfer from invalid address returns ErrInvalidAddress",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(alice, zeroAddress, bob, "1")
},
wantErr: ErrInvalidAddress,
},
{
name: "transfer to invalid address returns ErrInvalidAddress",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(alice, alice, zeroAddress, "1")
},
wantErr: ErrInvalidAddress,
},
{
name: "transfer from incorrect owner returns ErrTransferFromIncorrectOwner",
action: func(led *PrivateLedger) error {
urequire.NoError(t, led.Mint(alice, "1"))
return led.TransferFrom(alice, bob, carl, "1")
},
wantErr: ErrTransferFromIncorrectOwner,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
err := tc.action(led)
if tc.wantErr == nil {
uassert.NoError(t, err)
} else {
uassert.ErrorIs(t, err, tc.wantErr)
}
if tc.verify != nil {
tc.verify(t, tok, led)
}
})
}
}
func TestReadViews(cur realm, t *testing.T) {
tok, led := newTestToken("Foo NFT", "FOO", 0, cur)
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.Mint(alice, "2"))
urequire.NoError(t, led.Mint(bob, "3"))
urequire.NoError(t, led.Approve(alice, carl, "1"))
urequire.NoError(t, led.SetApprovalForAll(alice, bob, true))
tests := []struct {
name string
check func(t *testing.T)
}{
{"GetName returns collection name", func(t *testing.T) {
uassert.Equal(t, "Foo NFT", tok.GetName())
}},
{"GetSymbol returns collection symbol", func(t *testing.T) {
uassert.Equal(t, "FOO", tok.GetSymbol())
}},
{"ID carries realm, symbol and seqid", func(t *testing.T) {
uassert.True(t, strings.HasPrefix(tok.ID(), "gno.land/"))
uassert.True(t, strings.Contains(tok.ID(), ".FOO."))
}},
{"TotalSupply counts live tokens", func(t *testing.T) {
uassert.Equal(t, int64(3), tok.TotalSupply())
}},
{"KnownAccounts counts distinct holders", func(t *testing.T) {
uassert.Equal(t, 2, tok.KnownAccounts())
}},
{"BalanceOf of a holder", func(t *testing.T) {
uassert.Equal(t, int64(2), mustBalanceOf(t, tok, alice))
}},
{"BalanceOf of a non-holder is zero", func(t *testing.T) {
uassert.Equal(t, int64(0), mustBalanceOf(t, tok, carl))
}},
{"BalanceOf of the zero address returns ErrInvalidAddress", func(t *testing.T) {
_, err := tok.BalanceOf(zeroAddress)
uassert.ErrorIs(t, err, ErrInvalidAddress)
}},
{"OwnerOf existing token", func(t *testing.T) {
owner, err := tok.OwnerOf("3")
urequire.NoError(t, err)
uassert.Equal(t, bob, owner)
}},
{"OwnerOf missing token returns ErrInvalidTokenId", func(t *testing.T) {
_, err := tok.OwnerOf("999")
uassert.ErrorIs(t, err, ErrInvalidTokenId)
}},
{"GetApproved returns the approved account", func(t *testing.T) {
approved, err := tok.GetApproved("1")
urequire.NoError(t, err)
uassert.Equal(t, carl, approved)
}},
{"GetApproved on unapproved token returns ErrTokenIdNotApproved", func(t *testing.T) {
_, err := tok.GetApproved("2")
uassert.ErrorIs(t, err, ErrTokenIdNotApproved)
}},
{"GetApproved on missing token returns ErrInvalidTokenId", func(t *testing.T) {
_, err := tok.GetApproved("999")
uassert.ErrorIs(t, err, ErrInvalidTokenId)
}},
{"IsApprovedForAll true for operator", func(t *testing.T) {
uassert.True(t, tok.IsApprovedForAll(alice, bob))
}},
{"IsApprovedForAll false for non-operator", func(t *testing.T) {
uassert.False(t, tok.IsApprovedForAll(alice, carl))
}},
{"RenderHome renders header and counters", func(t *testing.T) {
out := tok.RenderHome()
uassert.True(t, strings.Contains(out, "# Foo NFT ($FOO)"), "header")
uassert.True(t, strings.Contains(out, "**Total supply**: 3"), "supply")
uassert.True(t, strings.Contains(out, "**Known accounts**: 2"), "accounts")
}},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, tc.check)
}
}
func TestRegisterExtensionNil(cur realm, t *testing.T) {
_, led := newTestToken("Foo", "FOO", 0, cur)
uassert.PanicsWithMessage(t, cur, "grc721: nil extension", func() {
led.RegisterExtension(nil)
})
}
func TestRegisterExtensionRejectsDuplicateKind(cur realm, t *testing.T) {
_, led := newTestToken("Foo", "FOO", 0, cur)
ext := &mockExtension{}
led.RegisterExtension(ext)
uassert.PanicsWithMessage(t, cur, "grc721: extension kind already registered: mock", func() {
led.RegisterExtension(ext)
})
uassert.PanicsWithMessage(t, cur, "grc721: extension kind already registered: mock", func() {
led.RegisterExtension(&mockExtension{})
})
}
func TestRegisterExtensionRejectsDuplicateKindOfUncomparableType(cur realm, t *testing.T) {
_, led := newTestToken("Foo", "FOO", 0, cur)
led.RegisterExtension(journalExtension{seen: []TokenID{"1"}})
uassert.PanicsWithMessage(t, cur, "grc721: extension kind already registered: journal", func() {
led.RegisterExtension(journalExtension{seen: []TokenID{"2"}})
})
}
func TestExtensionHookFanOut(cur realm, t *testing.T) {
tests := []struct {
name string
run func(t *testing.T, led *PrivateLedger, ext *mockExtension)
verify func(t *testing.T, ext *mockExtension)
}{
{
name: "mint, transfer and burn each notify the extension",
run: func(t *testing.T, led *PrivateLedger, ext *mockExtension) {
led.RegisterExtension(ext)
urequire.NoError(t, led.Mint(alice, "1"))
urequire.NoError(t, led.TransferFrom(alice, alice, bob, "1"))
urequire.NoError(t, led.Burn("1"))
},
verify: func(t *testing.T, ext *mockExtension) {
uassert.Equal(t, 1, ext.mints)
uassert.Equal(t, 1, ext.transfers)
uassert.Equal(t, alice, ext.lastTransferFrom)
uassert.Equal(t, bob, ext.lastTransferTo)
uassert.Equal(t, 1, ext.burns)
uassert.Equal(t, "1", ext.lastBurn.String())
},
},
{
name: "failed operations do not notify the extension",
run: func(t *testing.T, led *PrivateLedger, ext *mockExtension) {
led.RegisterExtension(ext)
uassert.ErrorIs(t, led.Mint(zeroAddress, "1"), ErrInvalidAddress)
uassert.ErrorIs(t, led.Burn("999"), ErrInvalidTokenId)
},
verify: func(t *testing.T, ext *mockExtension) {
uassert.Equal(t, 0, ext.mints)
uassert.Equal(t, 0, ext.burns)
},
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
_, led := newTestToken("Foo", "FOO", 0, cur)
ext := &mockExtension{}
tc.run(t, led, ext)
tc.verify(t, ext)
})
}
}
func TestReadToken(cur realm, t *testing.T) {
tok, led := newTestToken("Foo", "FOO", 0, cur)
uassert.Equal(t, tok.ID(), led.ReadToken().ID())
uassert.True(t, led.ReadToken() == tok, "ReadToken returns the same *Token")
}
The verified vm/qfuncs operation accepts realm paths only.
Pure packages expose source files but do not have Realm Render.