From 375d1cf3772618485b00125407dee15e6fc85a65 Mon Sep 17 00:00:00 2001 From: leohhhn Date: Wed, 15 May 2024 22:44:13 +0200 Subject: [PATCH 1/8] copy over from hackerspace --- .../gno.land/r/x/grc721-by-spec/README.md | 14 + .../r/x/grc721-by-spec/exampleNFT/gno.mod | 7 + .../r/x/grc721-by-spec/exampleNFT/nft.gno | 102 +++++++ .../r/x/grc721-by-spec/grc721-gno/gno.mod | 7 + .../r/x/grc721-by-spec/grc721-gno/grc721.gno | 261 ++++++++++++++++++ .../grc721-by-spec/grc721-gno/grc721_test.gno | 226 +++++++++++++++ .../r/x/grc721-by-spec/grc721-gno/igrc721.gno | 37 +++ .../r/x/grc721-by-spec/grc721-go/go.mod | 10 + .../r/x/grc721-by-spec/grc721-go/go.sum | 118 ++++++++ .../r/x/grc721-by-spec/grc721-go/grc721.go | 203 ++++++++++++++ .../r/x/grc721-by-spec/grc721-go/igrc721.go | 37 +++ 11 files changed, 1022 insertions(+) create mode 100644 examples/gno.land/r/x/grc721-by-spec/README.md create mode 100644 examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod create mode 100644 examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go create mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go diff --git a/examples/gno.land/r/x/grc721-by-spec/README.md b/examples/gno.land/r/x/grc721-by-spec/README.md new file mode 100644 index 00000000000..dc75be27f06 --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/README.md @@ -0,0 +1,14 @@ +# grc721-by-spec + +This folder contains 3 main parts: +1. The Go implementation of the ERC721 standard, called GRC721, which was used +for the initial implementation & testing +2. The Gno package implementation ported from the Go implementation mentioned above +3. An example NFT collection realm utilizing the Gno package + +To test this out, install `gnodev` and run the following command in this folder: +```shell +gnodev ./grc721-gno ./exampleNFT +``` + +Then, visit [`localhost:8888/r/example/nft`](http://localhost:8888/r/example/nft). diff --git a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod new file mode 100644 index 00000000000..f03d86ae8db --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod @@ -0,0 +1,7 @@ +module gno.land/r/example/nft + +require ( + gno.land/p/demo/ownable v0.0.0-latest + gno.land/p/demo/seqid v0.0.0-latest + gno.land/p/demo/xgrc721 v0.0.0-latest +) diff --git a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno new file mode 100644 index 00000000000..cc6b99d7137 --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno @@ -0,0 +1,102 @@ +package nft + +import ( + "bytes" + "std" + "strconv" + + "gno.land/p/demo/ownable" + "gno.land/p/demo/ufmt" + "gno.land/p/demo/xgrc721" +) + +var token *xgrc721.Token +var o *ownable.Ownable + +var idCounter int +var ids []int + +func init() { + token = xgrc721.NewGRC721Token("Example NFT", "EX") + o = ownable.New() + + Mint(o.Owner(), strconv.Itoa(idCounter)) +} + +func BalanceOf(owner std.Address) uint64 { + return token.BalanceOf(owner) +} + +func OwnerOf(tokenID string) std.Address { + return token.OwnerOf(tokenID) +} + +func TransferFrom(from, to std.Address, tokenID string) { + token.TransferFrom(from, to, tokenID) +} + +func Approve(to std.Address, tokenID string) { + token.Approve(to, tokenID) +} + +func SetApprovalForAll(operator std.Address, approved bool) { + token.SetApprovalForAll(operator, approved) +} + +func GetApproved(tokenID string) std.Address { + return token.GetApproved(tokenID) +} + +func IsApprovedForAll(owner, operator std.Address) bool { + return token.IsApprovedForAll(owner, operator) +} + +func TokenURI(tokenID string) string { + return token.TokenURI(tokenID) +} + +func SetTokenURI(tokenID string, uri string) string { + if err := o.CallerIsOwner(); err != nil { + panic("only owner can mint NFTs") + } + + return token.SetTokenURI(tokenID, uri) +} + +func Name() string { + return token.Name() +} + +func Symbol() string { + return token.Symbol() +} + +func Mint(to std.Address, tokenID string) { + if err := o.CallerIsOwner(); err != nil { + panic("only owner can mint NFTs") + } + + token.Mint(to, tokenID) + + ids = append(ids, idCounter) + idCounter += 1 +} + +func GetCollectionOwner() std.Address { + return o.Owner() +} + +// Render renders tokens & their owners +func Render(_ string) string { + var buf bytes.Buffer + + buf.WriteString(ufmt.Sprintf("# NFT Collection: \"%s\" $%s\n\n", Name(), Symbol())) + + for i := 0; i < len(ids); i++ { + owner := token.OwnerOf(strconv.Itoa(ids[i])) + str := ufmt.Sprintf("#### TokenID #%d - owned by %s\n", ids[i], owner.String()) + buf.WriteString(str) + } + + return buf.String() +} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod new file mode 100644 index 00000000000..11780a616db --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod @@ -0,0 +1,7 @@ +module gno.land/p/demo/xgrc721 + +require ( + gno.land/p/demo/avl v0.0.0-latest + gno.land/p/demo/testutils v0.0.0-latest + gno.land/p/demo/ufmt v0.0.0-latest +) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno new file mode 100644 index 00000000000..8c7d2689a1a --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno @@ -0,0 +1,261 @@ +package xgrc721 + +import ( + "gno.land/p/demo/avl" + "gno.land/p/demo/ufmt" + "std" +) + +type Token struct { + name string + symbol string + owners *avl.Tree // tokenID > std.Address + balances *avl.Tree // std.Address > # of owned tokens + tokenApprovals *avl.Tree // tokenID > std.Address + operatorApprovals *avl.Tree // "OwnerAddress:OperatorAddress" -> bool + tokenURIs *avl.Tree // tokenID > URI +} + +var _ IGRC721 = (*Token)(nil) + +const emptyAddress = std.Address("") + +func NewGRC721Token(name, symbol string) *Token { + return &Token{ + name: name, + symbol: symbol, + owners: avl.NewTree(), + balances: avl.NewTree(), + // give an address permission to a specific tokenID + tokenApprovals: avl.NewTree(), + // give any addresses permissions for all owners' assets + operatorApprovals: avl.NewTree(), + tokenURIs: avl.NewTree(), + } +} + +func (nft Token) Name() string { return nft.name } +func (nft Token) Symbol() string { return nft.symbol } + +func (nft Token) BalanceOf(owner std.Address) uint64 { + mustBeValid(owner) + + balance, found := nft.balances.Get(owner.String()) + if !found { + return 0 + } + + return balance.(uint64) +} + +func (nft Token) OwnerOf(tokenId string) std.Address { + return nft.mustBeOwned(tokenId) +} + +func (nft Token) TransferFrom(from, to std.Address, tokenId string) { + caller := std.PrevRealm().Addr() + mustBeValid(to) + + prevOwner := nft.update(to, tokenId, caller) + if prevOwner != from { + panic("GRC721: incorrect owner") + } +} + +func (nft Token) Approve(to std.Address, tokenId string) { + caller := std.PrevRealm().Addr() + + if caller == to { + panic("GRC721: cannot approve yourself") + } + + mustBeValid(to) + nft.approve(to, tokenId, caller, true) +} + +func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { + caller := std.PrevRealm().Addr() + mustBeValid(operator) + + if caller == operator { + panic("GRC721: cannot set operator to yourself") + } + + nft.operatorApprovals.Set(operatorKey(caller, operator), approved) + + if approved { + std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", "true") + } else { + std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", "false") + } // We do not support strconv.FormatBool yet +} + +func (nft Token) GetApproved(tokenId string) std.Address { + _ = nft.mustBeOwned(tokenId) + return nft.getApproved(tokenId) +} + +func (nft Token) IsApprovedForAll(owner, operator std.Address) bool { + approved, exists := nft.operatorApprovals.Get(operatorKey(owner, operator)) + if !exists || approved == false { + return false + } + + return true +} + +func (nft Token) TokenURI(tokenId string) string { + nft.mustBeOwned(tokenId) + uri, exists := nft.tokenURIs.Get(tokenId) + if !exists { + return "" + } + + return uri.(string) +} + +func (nft Token) SetTokenURI(tokenId string, tokenURI string) string { + nft.tokenURIs.Set(tokenId, tokenURI) + return tokenURI +} + +func (nft Token) Mint(to std.Address, tokenId string) { + mustBeValid(to) + prevOwner := nft.update(to, tokenId, emptyAddress) + if prevOwner != emptyAddress { + str := ufmt.Sprintf("GRC721: token with id %s has already been minted", tokenId) + panic(str) + } +} + +func (nft Token) Burn(tokenId string) { + prevOwner := nft.update(emptyAddress, tokenId, emptyAddress) + + if prevOwner == emptyAddress { + str := ufmt.Sprintf("GRC721: Token with ID %s does not exist", tokenId) + panic(str) + } +} + +// Helpers +func (nft Token) requireOwner(caller std.Address, tokenId string) { + if caller != nft.mustBeOwned(tokenId) { + panic("GRC721: not owner") + } +} + +func (nft Token) getApproved(tokenId string) std.Address { + approved, exists := nft.tokenApprovals.Get(tokenId) + if !exists { + return "" // panic instead? + } + + return approved.(std.Address) +} + +// mustBeValid panics if the given address is not valid +func mustBeValid(address std.Address) { + if !address.IsValid() { + err := ufmt.Sprintf("GRC721: invalid address %s", address) + panic(err) + } +} + +// mustBeOwned panics if token is not owned by an address (does not exist) +// If the token is owned, mustBeOwned returns the owner of the token +func (nft Token) mustBeOwned(tokenId string) std.Address { + owner, exists := nft.owners.Get(tokenId) + if !exists { + err := ufmt.Sprintf("GRC721: token with ID %s does not exist", tokenId) + panic(err) + } + + return owner.(std.Address) +} + +// checkAuthorized checks if spender is authorized to spend specified token on behalf of owner +// Panics if token doesn't exist, or if spender is not authorized in any way +func (nft Token) checkAuthorized(owner, spender std.Address, tokenId string) { + _ = nft.mustBeOwned(tokenId) + + if !nft.isAuthorized(owner, spender, tokenId) { + str := ufmt.Sprintf("GRC721: %s is not authorized for %s", spender, tokenId) + panic(str) + } +} + +// isAuthorized returns if the spender is authorized to transfer the specified token +// Assumes addresses are valid and the token exists +func (nft Token) isAuthorized(owner, spender std.Address, tokenId string) bool { + return owner == spender || + nft.IsApprovedForAll(owner, spender) || + nft.getApproved(tokenId) == owner +} + +func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Address { + from := nft.ownerOf(tokenId) + + if auth != emptyAddress { + nft.checkAuthorized(from, auth, tokenId) + } + + // If token exists + if from != emptyAddress { + // Clear approval for this token + nft.approve(emptyAddress, tokenId, emptyAddress, false) + + // Set new balances + ownerNewBalance, _ := nft.balances.Get(from.String()) + nft.balances.Set(from.String(), ownerNewBalance.(uint64)-1) + } + + if to != emptyAddress { + toBalance, initialized := nft.balances.Get(to.String()) + if !initialized { + nft.balances.Set(to.String(), uint64(1)) + } else { + nft.balances.Set(to.String(), toBalance.(uint64)+1) + } + // Set new ownership + nft.owners.Set(tokenId, to) + } else { + // Burn + _, removed := nft.owners.Remove(tokenId) + if !removed { + str := ufmt.Sprintf("GRC721: Cannot burn token with id %s", tokenId) + panic(str) + } + } + + std.Emit("Transfer", "from", from.String(), "to", to.String(), "tokenID", tokenId) + return from +} + +func (nft Token) approve(to std.Address, tokenId string, auth std.Address, emitEvent bool) { + if emitEvent || auth != emptyAddress { + owner := nft.mustBeOwned(tokenId) + + if auth != emptyAddress && owner != auth && !nft.IsApprovedForAll(owner, auth) { + panic("GRC721: invalid approver") + } + if emitEvent { + std.Emit("Approval", "owner", owner.String(), "approved", to.String(), "tokenID", tokenId) + } + } + + nft.tokenApprovals.Set(tokenId, to) +} + +func (nft Token) ownerOf(tokenId string) std.Address { + owner, exists := nft.owners.Get(tokenId) + if !exists { + return emptyAddress + } + + return owner.(std.Address) +} + +// operatorKey is a helper to create the key for the operatorApproval tree +func operatorKey(owner, operator std.Address) string { + return owner.String() + ":" + operator.String() +} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno new file mode 100644 index 00000000000..836e51a52ab --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno @@ -0,0 +1,226 @@ +package xgrc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" +) + +var ( + exampleNFTName = "ExampleNFT" + exampleNFTSymbol = "EXNFT" + alice = testutils.TestAddress("alice") + bob = testutils.TestAddress("bob") +) + +func TestNewGRC721Token(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + if exampleNFT == nil { + t.Errorf("should not be nil") + } +} + +func TestName(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + if exampleNFT == nil { + t.Errorf("should not be nil") + } + name := exampleNFT.Name() + if name != exampleNFTName { + t.Errorf("expected: (%s), got: (%s)", exampleNFTName, name) + } +} + +func TestSymbol(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + symbol := exampleNFT.Symbol() + if symbol != exampleNFTSymbol { + t.Errorf("expected: (%s), got: (%s)", exampleNFTSymbol, symbol) + } +} + +func TestBalanceOf(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + balanceAddr1 := exampleNFT.BalanceOf(alice) + + if balanceAddr1 != 0 { + t.Errorf("expected: (%d), got: (%d)", 0, balanceAddr1) + } + + exampleNFT.Mint(alice, "1") + exampleNFT.Mint(alice, "2") + exampleNFT.Mint(bob, "3") + + balanceAddr1 = exampleNFT.BalanceOf(alice) + balanceAddr2 := exampleNFT.BalanceOf(bob) + + if balanceAddr1 != 2 { + t.Errorf("expected: (%d), got: (%d)", 2, balanceAddr1) + } + if balanceAddr2 != 1 { + t.Errorf("expected: (%d), got: (%d)", 1, balanceAddr2) + } +} + +func TestOwnerOf(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + exampleNFT.Mint(alice, "1") + exampleNFT.Mint(bob, "2") + + // Checking for token id "1" + owner := exampleNFT.OwnerOf("1") + + if owner != alice { + t.Errorf("expected: (%s), got: (%s)", alice.String(), owner.String()) + } + + // Checking for token id "2" + owner = exampleNFT.OwnerOf("2") + + if owner != bob { + t.Errorf("expected: (%s), got: (%s)", bob.String(), owner.String()) + } +} + +func TestIsApprovedForAll(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + isApprovedForAll := exampleNFT.IsApprovedForAll(alice, bob) + if isApprovedForAll != false { + t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) + } +} + +func TestSetApprovalForAll(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + caller := std.PrevRealm().Addr() + + isApprovedForAll := exampleNFT.IsApprovedForAll(caller, alice) + if isApprovedForAll != false { + t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) + } + + exampleNFT.SetApprovalForAll(alice, true) + + isApprovedForAll = exampleNFT.IsApprovedForAll(caller, alice) + if isApprovedForAll != true { + t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) + } +} + +func TestApprove(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + if exampleNFT == nil { + t.Errorf("should not be nil") + } + + caller := std.PrevRealm().Addr() + + exampleNFT.Mint(caller, "1") + + exampleNFT.Approve(alice, "1") + approvedAddr := exampleNFT.GetApproved("1") + + if approvedAddr != alice { + t.Errorf("expected: (%s), got: (%s)", alice.String(), approvedAddr.String()) + } +} + +func TestTransferFrom(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + caller := std.PrevRealm().Addr() + + exampleNFT.Mint(caller, "1") + exampleNFT.Mint(caller, "2") + + exampleNFT.TransferFrom(caller, alice, "1") + + // Check the balance of caller after transfer + balanceOfCaller := exampleNFT.BalanceOf(caller) + if balanceOfCaller != 1 { + t.Errorf("expected: (%d), got: (%d)", 1, balanceOfCaller) + } + + // Check balance of addr after transfer + balanceOfAlice := exampleNFT.BalanceOf(alice) + if balanceOfAlice != 1 { + t.Errorf("expected: (%d), got: (%d)", 1, balanceOfAlice) + } + + // Check Owner of transferred Token id + owner := exampleNFT.OwnerOf("1") + if owner != alice { + t.Errorf("expected: (%s), got: (%s)", alice.String(), owner.String()) + } +} + +func TestMint(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + exampleNFT.Mint(alice, "1") + exampleNFT.Mint(alice, "2") + exampleNFT.Mint(bob, "3") + + defer func() { + if r := recover(); r == nil { + t.Fatalf("Mint should have paniced") + } + }() + + // Try Minting duplicate token id + exampleNFT.Mint(bob, "1") + + // Check Owner of Token id + owner := exampleNFT.OwnerOf("1") + if owner != alice { + t.Errorf("expected: (%s), got: (%s)", alice.String(), owner.String()) + } + + balance := exampleNFT.BalanceOf(alice) + if balance != uint64(2) { + t.Errorf("balances expected: (%d), got: (%d)", 2, balance) + } + +} + +func TestBurn(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + exampleNFT.Mint(alice, "1") + exampleNFT.Burn("1") + + defer func() { + if r := recover(); r == nil { + t.Fatalf("OwnerOf should have paniced after burn") + } + }() + + // Make code panic + _ = exampleNFT.OwnerOf("1") +} + +func TestSetTokenURI(t *testing.T) { + exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + + tokenURI := "https://example.com/token" + exampleNFT.Mint(alice, "1") + + std.TestSetOrigCaller(alice) + uri := exampleNFT.SetTokenURI("1", tokenURI) + if uri != tokenURI { + t.Fatalf("expected %s, got %s", tokenURI, uri) + } + + defer func() { + if r := recover(); r == nil { + t.Fatalf("SetTokenURI should have paniced for bad token ID") + } + }() + + // Try to set URI of token that doesn't exist + _ = exampleNFT.SetTokenURI("3", tokenURI) +} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno new file mode 100644 index 00000000000..fdf76da706e --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno @@ -0,0 +1,37 @@ +package xgrc721 + +import ( + "std" +) + +type IGRC721 interface { + // BalanceOf returns the number of tokens in `owner`'s account + BalanceOf(owner std.Address) uint64 + + // OwnerOf returns the owner of the `tokenId` token + // tokenId must exist + OwnerOf(tokenId string) std.Address + + // TransferFrom transfers `tokenId` token from `from` to `to` + TransferFrom(from, to std.Address, tokenId string) + + // Approve gives permission to `to` to transfer `tokenId` token to another account + // The approval is cleared when the token is transferred + Approve(to std.Address, tokenId string) + + // SetApprovalForAll approves or removes `operator` as an operator for the caller + // Operators can call TransferFrom for any token owned by the caller + SetApprovalForAll(operator std.Address, approved bool) + + // GetApproved returns the account approved for `tokenId` token + GetApproved(tokenId string) std.Address + + // IsApprovedForAll returns if the `operator` is allowed to manage all the assets of `owner` + IsApprovedForAll(owner, operator std.Address) bool + + // TokenURI gets the tokenURI for matching `tokenId` + TokenURI(tokenId string) string + + // SetTokenURI sets `tokenURI` as the tokenURI of `tokenId` + SetTokenURI(tokenId string, tokenURI string) string +} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod new file mode 100644 index 00000000000..89eae667648 --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod @@ -0,0 +1,10 @@ +module grc721 + +go 1.21.6 + +require github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af + +require ( + github.com/btcsuite/btcd/btcutil v1.1.5 // indirect + google.golang.org/protobuf v1.33.0 // indirect +) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum new file mode 100644 index 00000000000..5aaea269e1d --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum @@ -0,0 +1,118 @@ +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= +github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= +github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= +github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= +github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= +github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gnolang/gno v0.0.0-20240402115239-831bb6f92e1a h1:WyozSr+GOTQUnQ1BX8CSsx/JZe1c3X3+rEsSW/MXJDY= +github.com/gnolang/gno v0.0.0-20240402115239-831bb6f92e1a/go.mod h1:we1MozTqYAQFayi2eIvNtdqtGtFB7DQYSVguWBurWRI= +github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af h1:dddvtkbVRlpT1Egb+zVG/eLfB8sCmEwkPwlNKZqKM+g= +github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af/go.mod h1:vbm5sS6KZj2rkwJ9C6AtdpPZB7aZDhfZ3lJUC+qa/to= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= +github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go b/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go new file mode 100644 index 00000000000..471ae77a204 --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go @@ -0,0 +1,203 @@ +package grc721 + +import ( + "github.com/gnolang/gno/examples/gno.land/p/demo/avl" + "github.com/gnolang/gno/examples/gno.land/p/demo/ufmt" + "github.com/gnolang/gno/gnovm/stdlibs/std" + "strconv" +) + +type Token struct { + name string + symbol string + owners *avl.Tree // tokenID > std.Address + balances *avl.Tree // std.Address > # of owned tokens + tokenApprovals *avl.Tree // tokenID > std.Address + operatorApprovals *avl.Tree // "OwnerAddress:OperatorAddress" -> bool + tokenURIs *avl.Tree // tokenID > URI +} + +var _ IGRC721 = (*Token)(nil) + +func NewGRC721Token(name, symbol string) *Token { + return &Token{ + name: name, + symbol: symbol, + owners: avl.NewTree(), + balances: avl.NewTree(), + // give an address permission to a specific tokenID + tokenApprovals: avl.NewTree(), + // give any addresses permissions for all owners' assets + operatorApprovals: avl.NewTree(), + tokenURIs: avl.NewTree(), + } +} + +func (nft Token) Name() string { return nft.name } +func (nft Token) Symbol() string { return nft.symbol } + +func (nft Token) BalanceOf(owner std.Address) uint64 { + mustBeValid(owner) + + balance, found := nft.balances.Get(owner.String()) + if !found { + return 0 + } + + return balance.(uint64) +} + +func (nft Token) OwnerOf(tokenId string) std.Address { + return nft.mustBeOwned(tokenId) +} + +func (nft Token) TransferFrom(from, to std.Address, tokenId string) { + caller := std.PrevRealm().Addr() + mustBeValid(to) + + prevOwner := nft.update(to, tokenId, caller) + if prevOwner != from { + panic("GRC721: incorrect owner") + } + + std.Emit("Transfer", "from", from.String(), "operator", to.String(), "tokenID", tokenId) +} + +func (nft Token) Approve(to std.Address, tokenId string) { + caller := std.PrevRealm().Addr() + + if caller == to { + panic("GRC721: cannot approve yourself") + } + + mustBeValid(to) + nft.requireOwner(caller, tokenId) + + nft.tokenApprovals.Set(tokenId, to) + std.Emit("Approval", "owner", caller.String(), "approved", to.String(), "tokenID", tokenId) +} + +func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { + caller := std.PrevRealm().Addr() + mustBeValid(operator) + + if caller == operator { + panic("GRC721: cannot set operator to yourself") + } + + nft.operatorApprovals.Set(operatorKey(caller, operator), approved) + std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", strconv.FormatBool(approved)) +} + +func (nft Token) GetApproved(tokenId string) std.Address { + _ = nft.mustBeOwned(tokenId) + return nft.getApproved(tokenId) +} + +func (nft Token) IsApprovedForAll(owner, operator std.Address) bool { + approved, exists := nft.operatorApprovals.Get(operatorKey(owner, operator)) + if !exists || approved == false { + return false + } + + return true +} + +func (nft Token) TokenURI(tokenId string) string { + nft.mustBeOwned(tokenId) + uri, exists := nft.tokenURIs.Get(tokenId) + if !exists { + return "" + } + + return uri.(string) +} + +func (nft Token) SetTokenURI(tokenId string, tokenURI string) string { + nft.requireOwner(std.PrevRealm().Addr(), tokenId) + nft.tokenURIs.Set(tokenId, tokenURI) + return tokenURI +} + +// Helpers +func (nft Token) requireOwner(caller std.Address, tokenId string) { + if caller != nft.mustBeOwned(tokenId) { + panic("GRC721: not owner") + } +} + +func (nft Token) getApproved(tokenId string) std.Address { + approved, exists := nft.tokenApprovals.Get(tokenId) + if !exists { + return "" // panic instead? + } + + return approved.(std.Address) +} + +// mustBeValid panics if the given address is not valid +func mustBeValid(address std.Address) { + if !address.IsValid() { + err := ufmt.Sprintf("GRC721: invalid address %s", address) + panic(err) + } +} + +// mustBeOwned panics if token is not owned by an address (does not exist) +// If the token is owned, mustBeOwned returns the owner of the token +func (nft Token) mustBeOwned(tokenId string) std.Address { + owner, exists := nft.balances.Get(tokenId) + if !exists { + err := ufmt.Sprintf("GRC721: token with id %s does not exist", tokenId) + panic(err) + } + + return owner.(std.Address) +} + +// checkAuthorized checks if spender is authorized to spend specified token on behalf of owner +// Panics if token doesn't exist, or if spender is not authorized in any way +func (nft Token) checkAuthorized(owner, spender std.Address, tokenId string) { + _ = nft.mustBeOwned(tokenId) + + if !nft.isAuthorized(owner, spender, tokenId) { + str := ufmt.Sprintf("GRC721: %s is not authorized for %s", spender, tokenId) + panic(str) + } +} + +// isAuthorized returns if the spender is authorized to transfer the specified token +// Assumes addresses are valid and the token exists +func (nft Token) isAuthorized(owner, spender std.Address, tokenId string) bool { + return owner == spender || + nft.IsApprovedForAll(owner, spender) || + nft.getApproved(tokenId) == owner +} + +func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Address { + owner := nft.mustBeOwned(tokenId) + + if auth != "" { + nft.checkAuthorized(owner, auth, tokenId) + } + + // Clear approval for this token + nft.tokenApprovals.Set(tokenId, "") + + // Set new balances + newOwnerBalance, _ := nft.balances.Get(owner.String()) + nft.balances.Set(owner.String(), newOwnerBalance.(int64)-1) + + toBalance, _ := nft.balances.Get(to.String()) + nft.balances.Set(to.String(), toBalance.(int64)+1) + + // Set new ownership + nft.owners.Set(tokenId, to.String()) + + return owner +} + +// operatorKey is a helper to create the key for the operatorApproval tree +func operatorKey(owner, operator std.Address) string { + return owner.String() + ":" + operator.String() +} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go b/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go new file mode 100644 index 00000000000..db118aefd26 --- /dev/null +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go @@ -0,0 +1,37 @@ +package grc721 + +import ( + "github.com/gnolang/gno/gnovm/stdlibs/std" +) + +type IGRC721 interface { + // BalanceOf returns the number of tokens in `owner`'s account + BalanceOf(owner std.Address) uint64 + + // OwnerOf returns the owner of the `tokenId` token + // tokenId must exist + OwnerOf(tokenId string) std.Address + + // TransferFrom transfers `tokenId` token from `from` to `to` + TransferFrom(from, to std.Address, tokenId string) + + // Approve gives permission to `to` to transfer `tokenId` token to another account + // The approval is cleared when the token is transferred + Approve(to std.Address, tokenId string) + + // SetApprovalForAll approves or removes `operator` as an operator for the caller + // Operators can call TransferFrom for any token owned by the caller + SetApprovalForAll(operator std.Address, approved bool) + + // GetApproved returns the account approved for `tokenId` token + GetApproved(tokenId string) std.Address + + // IsApprovedForAll returns if the `operator` is allowed to manage all the assets of `owner` + IsApprovedForAll(owner, operator std.Address) bool + + // TokenURI gets the tokenURI for matching `tokenId` + TokenURI(tokenId string) string + + // SetTokenURI sets `tokenURI` as the tokenURI of `tokenId` + SetTokenURI(tokenId string, tokenURI string) string +} From 7d874618c172e1eae3d0eda7c31e67fcdff7b20d Mon Sep 17 00:00:00 2001 From: leohhhn Date: Thu, 16 May 2024 11:50:25 +0200 Subject: [PATCH 2/8] remove unused test --- .../r/x/grc721-by-spec/grc721-gno/grc721_test.gno | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno index 836e51a52ab..3a229a37332 100644 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno +++ b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno @@ -214,13 +214,4 @@ func TestSetTokenURI(t *testing.T) { if uri != tokenURI { t.Fatalf("expected %s, got %s", tokenURI, uri) } - - defer func() { - if r := recover(); r == nil { - t.Fatalf("SetTokenURI should have paniced for bad token ID") - } - }() - - // Try to set URI of token that doesn't exist - _ = exampleNFT.SetTokenURI("3", tokenURI) } From ba52830822957ef66c1ddf73883c472237c1d08a Mon Sep 17 00:00:00 2001 From: leohhhn Date: Wed, 5 Jun 2024 17:33:25 +0200 Subject: [PATCH 3/8] remove old nft, add new --- .../gno.land/p/demo/grc/grc721/basic_nft.gno | 378 ---------------- .../p/demo/grc/grc721/basic_nft_test.gno | 414 ------------------ .../gno.land/p/demo/grc/grc721/errors.gno | 22 - examples/gno.land/p/demo/grc/grc721/gno.mod | 1 - .../demo/grc/grc721}/grc721.gno | 2 +- .../p/demo/grc/grc721/grc721_metadata.gno | 95 ---- .../demo/grc/grc721/grc721_metadata_test.gno | 133 ------ .../p/demo/grc/grc721/grc721_royalty.gno | 78 ---- .../p/demo/grc/grc721/grc721_royalty_test.gno | 93 ---- .../demo/grc/grc721}/grc721_test.gno | 2 +- .../gno.land/p/demo/grc/grc721/igrc721.gno | 57 ++- .../p/demo/grc/grc721/igrc721_metadata.gno | 38 -- .../p/demo/grc/grc721/igrc721_royalty.gno | 18 - examples/gno.land/p/demo/grc/grc721/util.gno | 18 - examples/gno.land/r/demo/foo721/foo721.gno | 142 +++--- .../gno.land/r/demo/foo721/foo721_test.gno | 33 -- examples/gno.land/r/demo/foo721/gno.mod | 3 +- examples/gno.land/r/demo/nft/README.md | 11 - examples/gno.land/r/demo/nft/gno.mod | 6 - examples/gno.land/r/demo/nft/nft.gno | 144 ------ examples/gno.land/r/demo/nft/z_0_filetest.gno | 223 ---------- examples/gno.land/r/demo/nft/z_1_filetest.gno | 20 - examples/gno.land/r/demo/nft/z_2_filetest.gno | 24 - examples/gno.land/r/demo/nft/z_3_filetest.gno | 26 -- examples/gno.land/r/demo/nft/z_4_filetest.gno | 26 -- .../gno.land/r/x/grc721-by-spec/README.md | 14 - .../r/x/grc721-by-spec/exampleNFT/gno.mod | 7 - .../r/x/grc721-by-spec/exampleNFT/nft.gno | 102 ----- .../r/x/grc721-by-spec/grc721-gno/gno.mod | 7 - .../r/x/grc721-by-spec/grc721-gno/igrc721.gno | 37 -- .../r/x/grc721-by-spec/grc721-go/go.mod | 10 - .../r/x/grc721-by-spec/grc721-go/go.sum | 118 ----- .../r/x/grc721-by-spec/grc721-go/grc721.go | 203 --------- .../r/x/grc721-by-spec/grc721-go/igrc721.go | 37 -- 34 files changed, 91 insertions(+), 2451 deletions(-) delete mode 100644 examples/gno.land/p/demo/grc/grc721/basic_nft.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/errors.gno rename examples/gno.land/{r/x/grc721-by-spec/grc721-gno => p/demo/grc/grc721}/grc721.gno (99%) delete mode 100644 examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno rename examples/gno.land/{r/x/grc721-by-spec/grc721-gno => p/demo/grc/grc721}/grc721_test.gno (99%) delete mode 100644 examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno delete mode 100644 examples/gno.land/p/demo/grc/grc721/util.gno delete mode 100644 examples/gno.land/r/demo/foo721/foo721_test.gno delete mode 100644 examples/gno.land/r/demo/nft/README.md delete mode 100644 examples/gno.land/r/demo/nft/gno.mod delete mode 100644 examples/gno.land/r/demo/nft/nft.gno delete mode 100644 examples/gno.land/r/demo/nft/z_0_filetest.gno delete mode 100644 examples/gno.land/r/demo/nft/z_1_filetest.gno delete mode 100644 examples/gno.land/r/demo/nft/z_2_filetest.gno delete mode 100644 examples/gno.land/r/demo/nft/z_3_filetest.gno delete mode 100644 examples/gno.land/r/demo/nft/z_4_filetest.gno delete mode 100644 examples/gno.land/r/x/grc721-by-spec/README.md delete mode 100644 examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod delete mode 100644 examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go delete mode 100644 examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno deleted file mode 100644 index bec7338db42..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ /dev/null @@ -1,378 +0,0 @@ -package grc721 - -import ( - "std" - - "gno.land/p/demo/avl" - "gno.land/p/demo/ufmt" -) - -type basicNFT struct { - name string - symbol string - owners avl.Tree // tokenId -> OwnerAddress - balances avl.Tree // OwnerAddress -> TokenCount - tokenApprovals avl.Tree // TokenId -> ApprovedAddress - tokenURIs avl.Tree // TokenId -> URIs - operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool -} - -// Returns new basic NFT -func NewBasicNFT(name string, symbol string) *basicNFT { - return &basicNFT{ - name: name, - symbol: symbol, - - owners: avl.Tree{}, - balances: avl.Tree{}, - tokenApprovals: avl.Tree{}, - tokenURIs: avl.Tree{}, - operatorApprovals: avl.Tree{}, - } -} - -func (s *basicNFT) Name() string { return s.name } -func (s *basicNFT) Symbol() string { return s.symbol } -func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } - -// BalanceOf returns balance of input address -func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) { - if err := isValidAddress(addr); err != nil { - return 0, err - } - - balance, found := s.balances.Get(addr.String()) - if !found { - return 0, nil - } - - return balance.(uint64), nil -} - -// OwnerOf returns owner of input token id -func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) { - owner, found := s.owners.Get(string(tid)) - if !found { - return "", ErrInvalidTokenId - } - - return owner.(std.Address), nil -} - -// TokenURI returns the URI of input token id -func (s *basicNFT) TokenURI(tid TokenID) (string, error) { - uri, found := s.tokenURIs.Get(string(tid)) - if !found { - return "", ErrInvalidTokenId - } - - return uri.(string), nil -} - -func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) { - // check for invalid TokenID - if !s.exists(tid) { - return false, ErrInvalidTokenId - } - - // check for the right owner - owner, err := s.OwnerOf(tid) - if err != nil { - return false, err - } - caller := std.PrevRealm().Addr() - if caller != owner { - return false, ErrCallerIsNotOwner - } - s.tokenURIs.Set(string(tid), string(tURI)) - return true, nil -} - -// IsApprovedForAll returns true if operator is approved for all by the owner. -// Otherwise, returns false -func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool { - key := owner.String() + ":" + operator.String() - _, found := s.operatorApprovals.Get(key) - if !found { - return false - } - - return true -} - -// Approve approves the input address for particular token -func (s *basicNFT) Approve(to std.Address, tid TokenID) error { - if err := isValidAddress(to); err != nil { - return err - } - - owner, err := s.OwnerOf(tid) - if err != nil { - return err - } - if owner == to { - return ErrApprovalToCurrentOwner - } - - caller := std.PrevRealm().Addr() - if caller != owner && !s.IsApprovedForAll(owner, caller) { - return ErrCallerIsNotOwnerOrApproved - } - - s.tokenApprovals.Set(string(tid), to.String()) - event := ApprovalEvent{owner, to, tid} - emit(&event) - - return nil -} - -// GetApproved return the approved address for token -func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) { - addr, found := s.tokenApprovals.Get(string(tid)) - if !found { - return zeroAddress, ErrTokenIdNotHasApproved - } - - return std.Address(addr.(string)), nil -} - -// SetApprovalForAll can approve the operator to operate on all tokens -func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error { - if err := isValidAddress(operator); err != nil { - return ErrInvalidAddress - } - - caller := std.PrevRealm().Addr() - return s.setApprovalForAll(caller, operator, approved) -} - -// Safely transfers `tokenId` token from `from` to `to`, checking that -// contract recipients are aware of the GRC721 protocol to prevent -// tokens from being forever locked. -func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() - if !s.isApprovedOrOwner(caller, tid) { - return ErrCallerIsNotOwnerOrApproved - } - - err := s.transfer(from, to, tid) - if err != nil { - return err - } - - if !s.checkOnGRC721Received(from, to, tid) { - return ErrTransferToNonGRC721Receiver - } - - return nil -} - -// Transfers `tokenId` token from `from` to `to`. -func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error { - caller := std.PrevRealm().Addr() - if !s.isApprovedOrOwner(caller, tid) { - return ErrCallerIsNotOwnerOrApproved - } - - err := s.transfer(from, to, tid) - if err != nil { - return err - } - - return nil -} - -// Mints `tokenId` and transfers it to `to`. -func (s *basicNFT) Mint(to std.Address, tid TokenID) error { - return s.mint(to, tid) -} - -// Mints `tokenId` and transfers it to `to`. Also checks that -// contract recipients are using GRC721 protocol -func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error { - err := s.mint(to, tid) - if err != nil { - return err - } - - if !s.checkOnGRC721Received(zeroAddress, to, tid) { - return ErrTransferToNonGRC721Receiver - } - - return nil -} - -func (s *basicNFT) Burn(tid TokenID) error { - owner, err := s.OwnerOf(tid) - if err != nil { - return err - } - - s.beforeTokenTransfer(owner, zeroAddress, tid, 1) - - s.tokenApprovals.Remove(string(tid)) - balance, err := s.BalanceOf(owner) - if err != nil { - return err - } - balance -= 1 - s.balances.Set(owner.String(), balance) - s.owners.Remove(string(tid)) - - event := TransferEvent{owner, zeroAddress, tid} - emit(&event) - - s.afterTokenTransfer(owner, zeroAddress, tid, 1) - - return nil -} - -/* Helper methods */ - -// Helper for SetApprovalForAll() -func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error { - if owner == operator { - return ErrApprovalToCurrentOwner - } - - key := owner.String() + ":" + operator.String() - s.operatorApprovals.Set(key, approved) - - event := ApprovalForAllEvent{owner, operator, approved} - emit(&event) - - return nil -} - -// Helper for TransferFrom() and SafeTransferFrom() -func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error { - if err := isValidAddress(from); err != nil { - return ErrInvalidAddress - } - if err := isValidAddress(to); err != nil { - return ErrInvalidAddress - } - - if from == to { - return ErrCannotTransferToSelf - } - - owner, err := s.OwnerOf(tid) - if err != nil { - return err - } - if owner != from { - return ErrTransferFromIncorrectOwner - } - - s.beforeTokenTransfer(from, to, tid, 1) - - // Check that tokenId was not transferred by `beforeTokenTransfer` - owner, err = s.OwnerOf(tid) - if err != nil { - return err - } - if owner != from { - return ErrTransferFromIncorrectOwner - } - - s.tokenApprovals.Remove(string(tid)) - fromBalance, err := s.BalanceOf(from) - if err != nil { - return err - } - toBalance, err := s.BalanceOf(to) - if err != nil { - return err - } - fromBalance -= 1 - toBalance += 1 - s.balances.Set(from.String(), fromBalance) - s.balances.Set(to.String(), toBalance) - s.owners.Set(string(tid), to) - - event := TransferEvent{from, to, tid} - emit(&event) - - s.afterTokenTransfer(from, to, tid, 1) - - return nil -} - -// Helper for Mint() and SafeMint() -func (s *basicNFT) mint(to std.Address, tid TokenID) error { - if err := isValidAddress(to); err != nil { - return err - } - - if s.exists(tid) { - return ErrTokenIdAlreadyExists - } - - s.beforeTokenTransfer(zeroAddress, to, tid, 1) - - // Check that tokenId was not minted by `beforeTokenTransfer` - if s.exists(tid) { - return ErrTokenIdAlreadyExists - } - - toBalance, err := s.BalanceOf(to) - if err != nil { - return err - } - toBalance += 1 - s.balances.Set(to.String(), toBalance) - s.owners.Set(string(tid), to) - - event := TransferEvent{zeroAddress, to, tid} - emit(&event) - - s.afterTokenTransfer(zeroAddress, to, tid, 1) - - return nil -} - -func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool { - owner, found := s.owners.Get(string(tid)) - if !found { - return false - } - - if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) { - return true - } - - _, err := s.GetApproved(tid) - if err != nil { - return false - } - - return true -} - -// Checks if token id already exists -func (s *basicNFT) exists(tid TokenID) bool { - _, found := s.owners.Get(string(tid)) - return found -} - -func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { - // TODO: Implementation -} - -func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { - // TODO: Implementation -} - -func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { - // TODO: Implementation - return true -} - -func (s *basicNFT) RenderHome() (str string) { - str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol) - str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount()) - str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size()) - - return -} diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno deleted file mode 100644 index 70255c5e9d1..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ /dev/null @@ -1,414 +0,0 @@ -package grc721 - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/users" -) - -var ( - dummyNFTName = "DummyNFT" - dummyNFTSymbol = "DNFT" -) - -func TestNewBasicNFT(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } -} - -func TestName(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - name := dummy.Name() - if name != dummyNFTName { - t.Errorf("expected: (%s), got: (%s)", dummyNFTName, name) - } -} - -func TestSymbol(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - symbol := dummy.Symbol() - if symbol != dummyNFTSymbol { - t.Errorf("expected: (%s), got: (%s)", dummyNFTSymbol, symbol) - } -} - -func TestTokenCount(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - count := dummy.TokenCount() - if count != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, count) - } - - dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("1")) - dummy.mint("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", TokenID("2")) - - count = dummy.TokenCount() - if count != 2 { - t.Errorf("expected: (%d), got: (%d)", 2, count) - } -} - -func TestBalanceOf(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - - balanceAddr1, err := dummy.BalanceOf(addr1) - if err != nil { - t.Errorf("should not result in error") - } - if balanceAddr1 != 0 { - t.Errorf("expected: (%d), got: (%d)", 0, balanceAddr1) - } - - dummy.mint(addr1, TokenID("1")) - dummy.mint(addr1, TokenID("2")) - dummy.mint(addr2, TokenID("3")) - - balanceAddr1, err = dummy.BalanceOf(addr1) - if err != nil { - t.Errorf("should not result in error") - } - balanceAddr2, err := dummy.BalanceOf(addr2) - if err != nil { - t.Errorf("should not result in error") - } - - if balanceAddr1 != 2 { - t.Errorf("expected: (%d), got: (%d)", 2, balanceAddr1) - } - if balanceAddr2 != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceAddr2) - } -} - -func TestOwnerOf(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - - owner, err := dummy.OwnerOf(TokenID("invalid")) - if err == nil { - t.Errorf("should result in error") - } - - dummy.mint(addr1, TokenID("1")) - dummy.mint(addr2, TokenID("2")) - - // Checking for token id "1" - owner, err = dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr1 { - t.Errorf("expected: (%s), got: (%s)", addr1.String(), owner.String()) - } - - // Checking for token id "2" - owner, err = dummy.OwnerOf(TokenID("2")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr2 { - t.Errorf("expected: (%s), got: (%s)", addr2.String(), owner.String()) - } -} - -func TestIsApprovedForAll(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - - isApprovedForAll := dummy.IsApprovedForAll(addr1, addr2) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } -} - -func TestSetApprovalForAll(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - caller := std.PrevRealm().Addr() - addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - isApprovedForAll := dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != false { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } - - err := dummy.SetApprovalForAll(addr, true) - if err != nil { - t.Errorf("should not result in error") - } - - isApprovedForAll = dummy.IsApprovedForAll(caller, addr) - if isApprovedForAll != true { - t.Errorf("expected: (%v), got: (%v)", false, isApprovedForAll) - } -} - -func TestGetApproved(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - approvedAddr, err := dummy.GetApproved(TokenID("invalid")) - if err == nil { - t.Errorf("should result in error") - } -} - -func TestApprove(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - caller := std.PrevRealm().Addr() - addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - dummy.mint(caller, TokenID("1")) - - _, err := dummy.GetApproved(TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } - - err = dummy.Approve(addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - - approvedAddr, err := dummy.GetApproved(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if approvedAddr != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), approvedAddr.String()) - } -} - -func TestTransferFrom(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - caller := std.PrevRealm().Addr() - addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - dummy.mint(caller, TokenID("1")) - dummy.mint(caller, TokenID("2")) - - err := dummy.TransferFrom(caller, addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - - // Check balance of caller after transfer - balanceOfCaller, err := dummy.BalanceOf(caller) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfCaller != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfCaller) - } - - // Check balance of addr after transfer - balanceOfAddr, err := dummy.BalanceOf(addr) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfAddr != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfAddr) - } - - // Check Owner of transferred Token id - owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), owner.String()) - } -} - -func TestSafeTransferFrom(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - caller := std.PrevRealm().Addr() - addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - dummy.mint(caller, TokenID("1")) - dummy.mint(caller, TokenID("2")) - - err := dummy.SafeTransferFrom(caller, addr, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - - // Check balance of caller after transfer - balanceOfCaller, err := dummy.BalanceOf(caller) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfCaller != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfCaller) - } - - // Check balance of addr after transfer - balanceOfAddr, err := dummy.BalanceOf(addr) - if err != nil { - t.Errorf("should not result in error") - } - if balanceOfAddr != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceOfAddr) - } - - // Check Owner of transferred Token id - owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr { - t.Errorf("expected: (%s), got: (%s)", addr.String(), owner.String()) - } -} - -func TestMint(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - - err := dummy.Mint(addr1, TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - err = dummy.Mint(addr1, TokenID("2")) - if err != nil { - t.Errorf("should not result in error") - } - err = dummy.Mint(addr2, TokenID("3")) - if err != nil { - t.Errorf("should not result in error") - } - - // Try minting duplicate token id - err = dummy.Mint(addr2, TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } - - // Check Owner of Token id - owner, err := dummy.OwnerOf(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - if owner != addr1 { - t.Errorf("expected: (%s), got: (%s)", addr1.String(), owner.String()) - } -} - -func TestBurn(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - dummy.mint(addr, TokenID("1")) - dummy.mint(addr, TokenID("2")) - - err := dummy.Burn(TokenID("1")) - if err != nil { - t.Errorf("should not result in error") - } - - // Check Owner of Token id - owner, err := dummy.OwnerOf(TokenID("1")) - if err == nil { - t.Errorf("should result in error") - } -} - -func TestSetTokenURI(t *testing.T) { - dummy := NewBasicNFT(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - tokenURI := "http://example.com/token" - - std.TestSetOrigCaller(std.Address(addr1)) // addr1 - - dummy.mint(addr1, TokenID("1")) - _, derr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) - - if derr != nil { - t.Errorf("Should not result in error ", derr.Error()) - } - - // Test case: Invalid token ID - _, err := dummy.SetTokenURI(TokenID("3"), TokenURI(tokenURI)) - if err != ErrInvalidTokenId { - t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) - } - - std.TestSetOrigCaller(std.Address(addr2)) // addr2 - - _, cerr := dummy.SetTokenURI(TokenID("1"), TokenURI(tokenURI)) // addr2 trying to set URI for token 1 - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, err) - } - - // Test case: Retrieving TokenURI - std.TestSetOrigCaller(std.Address(addr1)) // addr1 - - dummyTokenURI, err := dummy.TokenURI(TokenID("1")) - if err != nil { - t.Errorf("TokenURI error: %v, ", err.Error()) - } - if dummyTokenURI != tokenURI { - t.Errorf("Expected URI %v, got %v", tokenURI, dummyTokenURI) - } -} diff --git a/examples/gno.land/p/demo/grc/grc721/errors.gno b/examples/gno.land/p/demo/grc/grc721/errors.gno deleted file mode 100644 index 2d512db350d..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/errors.gno +++ /dev/null @@ -1,22 +0,0 @@ -package grc721 - -import "errors" - -var ( - ErrInvalidTokenId = errors.New("invalid token id") - ErrInvalidAddress = errors.New("invalid address") - ErrTokenIdNotHasApproved = errors.New("token id not approved for anyone") - ErrApprovalToCurrentOwner = errors.New("approval to current owner") - ErrCallerIsNotOwner = errors.New("caller is not token owner") - ErrCallerNotApprovedForAll = errors.New("caller is not approved for all") - ErrCannotTransferToSelf = errors.New("cannot send transfer to self") - ErrTransferFromIncorrectOwner = errors.New("transfer from incorrect owner") - ErrTransferToNonGRC721Receiver = errors.New("transfer to non GRC721Receiver implementer") - ErrCallerIsNotOwnerOrApproved = errors.New("caller is not token owner or approved") - ErrTokenIdAlreadyExists = errors.New("token id already exists") - - // ERC721Royalty - ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") - ErrInvalidRoyaltyPaymentAddress = errors.New("invalid royalty paymentAddress") - ErrCannotCalculateRoyaltyAmount = errors.New("cannot calculate royalty amount") -) diff --git a/examples/gno.land/p/demo/grc/grc721/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod index 1eada28e4ee..061ea2988c3 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -4,5 +4,4 @@ require ( gno.land/p/demo/avl v0.0.0-latest gno.land/p/demo/testutils v0.0.0-latest gno.land/p/demo/ufmt v0.0.0-latest - gno.land/p/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno b/examples/gno.land/p/demo/grc/grc721/grc721.gno similarity index 99% rename from examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno rename to examples/gno.land/p/demo/grc/grc721/grc721.gno index 8c7d2689a1a..4f2bd7bb568 100644 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721.gno @@ -1,4 +1,4 @@ -package xgrc721 +package grc721 import ( "gno.land/p/demo/avl" diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno deleted file mode 100644 index 360f73ed106..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ /dev/null @@ -1,95 +0,0 @@ -package grc721 - -import ( - "std" - - "gno.land/p/demo/avl" -) - -// metadataNFT represents an NFT with metadata extensions. -type metadataNFT struct { - *basicNFT // Embedded basicNFT struct for basic NFT functionality - extensions *avl.Tree // AVL tree for storing metadata extensions -} - -// Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. -var _ IGRC721MetadataOnchain = (*metadataNFT)(nil) - -// NewNFTWithMetadata creates a new basic NFT with metadata extensions. -func NewNFTWithMetadata(name string, symbol string) *metadataNFT { - // Create a new basic NFT - nft := NewBasicNFT(name, symbol) - - // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions - return &metadataNFT{ - basicNFT: nft, - extensions: avl.NewTree(), - } -} - -// SetTokenMetadata sets metadata for a given token ID. -func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) error { - // Check if the caller is the owner of the token - owner, err := s.basicNFT.OwnerOf(tid) - if err != nil { - return err - } - caller := std.PrevRealm().Addr() - if caller != owner { - return ErrCallerIsNotOwner - } - - // Set the metadata for the token ID in the extensions AVL tree - s.extensions.Set(string(tid), metadata) - return nil -} - -// TokenMetadata retrieves metadata for a given token ID. -func (s *metadataNFT) TokenMetadata(tid TokenID) (Metadata, error) { - // Retrieve metadata from the extensions AVL tree - metadata, found := s.extensions.Get(string(tid)) - if !found { - return Metadata{}, ErrInvalidTokenId - } - - return metadata.(Metadata), nil -} - -// mint mints a new token and assigns it to the specified address. -func (s *metadataNFT) mint(to std.Address, tid TokenID) error { - // Check if the address is valid - if err := isValidAddress(to); err != nil { - return err - } - - // Check if the token ID already exists - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } - - s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) - - // Check if the token ID was minted by beforeTokenTransfer - if s.basicNFT.exists(tid) { - return ErrTokenIdAlreadyExists - } - - // Increment balance of the recipient address - toBalance, err := s.basicNFT.BalanceOf(to) - if err != nil { - return err - } - toBalance += 1 - s.basicNFT.balances.Set(to.String(), toBalance) - - // Set owner of the token ID to the recipient address - s.basicNFT.owners.Set(string(tid), to) - - // Emit transfer event - event := TransferEvent{zeroAddress, to, tid} - emit(&event) - - s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) - - return nil -} diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno deleted file mode 100644 index b7ca6932fe1..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ /dev/null @@ -1,133 +0,0 @@ -package grc721 - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/users" -) - -func TestSetMetadata(t *testing.T) { - // Create a new dummy NFT with metadata - dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - // Define addresses for testing purposes - addr1 := testutils.TestAddress("alice") - addr2 := testutils.TestAddress("bob") - - // Define metadata attributes - name := "test" - description := "test" - image := "test" - imageData := "test" - externalURL := "test" - attributes := []Trait{} - backgroundColor := "test" - animationURL := "test" - youtubeURL := "test" - - // Set the original caller to addr1 - std.TestSetOrigCaller(addr1) // addr1 - - // Mint a new token for addr1 - dummy.mint(addr1, TokenID("1")) - - // Set metadata for token 1 - derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ - Name: name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, - BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, - }) - - // Check if there was an error setting metadata - if derr != nil { - t.Errorf("Should not result in error : %s", derr.Error()) - } - - // Test case: Invalid token ID - err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ - Name: name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, - BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, - }) - - // Check if the error returned matches the expected error - if err != ErrInvalidTokenId { - t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) - } - - // Set the original caller to addr2 - std.TestSetOrigCaller(addr2) // addr2 - - // Try to set metadata for token 1 from addr2 (should fail) - cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ - Name: name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, - BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, - }) - - // Check if the error returned matches the expected error - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) - } - - // Set the original caller back to addr1 - std.TestSetOrigCaller(addr1) // addr1 - - // Retrieve metadata for token 1 - dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) - if err != nil { - t.Errorf("Metadata error: %s", err.Error()) - } - - // Check if metadata attributes match expected values - if dummyMetadata.Image != image { - t.Errorf("Expected Metadata's image %s, got %s", image, dummyMetadata.Image) - } - if dummyMetadata.ImageData != imageData { - t.Errorf("Expected Metadata's imageData %s, got %s", imageData, dummyMetadata.ImageData) - } - if dummyMetadata.ExternalURL != externalURL { - t.Errorf("Expected Metadata's externalURL %s, got %s", externalURL, dummyMetadata.ExternalURL) - } - if dummyMetadata.Description != description { - t.Errorf("Expected Metadata's description %s, got %s", description, dummyMetadata.Description) - } - if dummyMetadata.Name != name { - t.Errorf("Expected Metadata's name %s, got %s", name, dummyMetadata.Name) - } - if len(dummyMetadata.Attributes) != len(attributes) { - t.Errorf("Expected %d Metadata's attributes, got %d", len(attributes), len(dummyMetadata.Attributes)) - } - if dummyMetadata.BackgroundColor != backgroundColor { - t.Errorf("Expected Metadata's backgroundColor %s, got %s", backgroundColor, dummyMetadata.BackgroundColor) - } - if dummyMetadata.AnimationURL != animationURL { - t.Errorf("Expected Metadata's animationURL %s, got %s", animationURL, dummyMetadata.AnimationURL) - } - if dummyMetadata.YoutubeURL != youtubeURL { - t.Errorf("Expected Metadata's youtubeURL %s, got %s", youtubeURL, dummyMetadata.YoutubeURL) - } -} diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno deleted file mode 100644 index 9831c709121..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ /dev/null @@ -1,78 +0,0 @@ -package grc721 - -import ( - "std" - - "gno.land/p/demo/avl" -) - -// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. -type royaltyNFT struct { - *metadataNFT // Embedding metadataNFT for NFT functionality - tokenRoyaltyInfo *avl.Tree // AVL tree to store royalty information for each token - maxRoyaltyPercentage uint64 // maxRoyaltyPercentage represents the maximum royalty percentage that can be charged every sale -} - -// Ensure that royaltyNFT implements the IGRC2981 interface. -var _ IGRC2981 = (*royaltyNFT)(nil) - -// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. -func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { - // Create a new NFT with metadata - nft := NewNFTWithMetadata(name, symbol) - - return &royaltyNFT{ - metadataNFT: nft, - tokenRoyaltyInfo: avl.NewTree(), - maxRoyaltyPercentage: 100, - } -} - -// SetTokenRoyalty sets the royalty information for a specific token ID. -func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { - // Validate the payment address - if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { - return ErrInvalidRoyaltyPaymentAddress - } - - // Check if royalty percentage exceeds maxRoyaltyPercentage - if royaltyInfo.Percentage > r.maxRoyaltyPercentage { - return ErrInvalidRoyaltyPercentage - } - - // Check if the caller is the owner of the token - owner, err := r.metadataNFT.OwnerOf(tid) - if err != nil { - return err - } - caller := std.PrevRealm().Addr() - if caller != owner { - return ErrCallerIsNotOwner - } - - // Set royalty information for the token - r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) - - return nil -} - -// RoyaltyInfo returns the royalty information for the given token ID and sale price. -func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, uint64, error) { - // Retrieve royalty information for the token - val, found := r.tokenRoyaltyInfo.Get(string(tid)) - if !found { - return "", 0, ErrInvalidTokenId - } - - royaltyInfo := val.(RoyaltyInfo) - - // Calculate royalty amount - royaltyAmount, _ := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) - - return royaltyInfo.PaymentAddress, royaltyAmount, nil -} - -func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { - royaltyAmount := (salePrice * percentage) / 100 - return royaltyAmount, nil -} diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno deleted file mode 100644 index 8c7bb33caa5..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ /dev/null @@ -1,93 +0,0 @@ -package grc721 - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - "gno.land/p/demo/ufmt" - "gno.land/p/demo/users" -) - -func TestSetTokenRoyalty(t *testing.T) { - dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := testutils.TestAddress("alice") - addr2 := testutils.TestAddress("bob") - - paymentAddress := testutils.TestAddress("john") - percentage := uint64(10) // 10% - - salePrice := uint64(1000) - expectRoyaltyAmount := uint64(100) - - std.TestSetOrigCaller(addr1) // addr1 - - dummy.mint(addr1, TokenID("1")) - - derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: percentage, - }) - - if derr != nil { - t.Errorf("Should not result in error : %s", derr.Error()) - } - - // Test case: Invalid token ID - err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: percentage, - }) - if err != ErrInvalidTokenId { - t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) - } - - std.TestSetOrigCaller(addr2) // addr2 - - cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: percentage, - }) - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) - } - - // Test case: Invalid payment address - aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ - PaymentAddress: std.Address("###"), // invalid address - Percentage: percentage, - }) - if aerr != ErrInvalidRoyaltyPaymentAddress { - t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPaymentAddress, aerr) - } - - // Test case: Invalid percentage - perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: uint64(200), // over maxRoyaltyPercentage - }) - - if perr != ErrInvalidRoyaltyPercentage { - t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) - } - - // Test case: Retrieving Royalty Info - std.TestSetOrigCaller(addr1) // addr1 - - dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) - if rerr != nil { - t.Errorf("RoyaltyInfo error: %s", rerr.Error()) - } - - if dummyPaymentAddress != paymentAddress { - t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) - } - - if dummyRoyaltyAmount != expectRoyaltyAmount { - t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) - } -} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno similarity index 99% rename from examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno rename to examples/gno.land/p/demo/grc/grc721/grc721_test.gno index 3a229a37332..180fed85bb4 100644 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/grc721_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno @@ -1,4 +1,4 @@ -package xgrc721 +package grc721 import ( "std" diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721.gno b/examples/gno.land/p/demo/grc/grc721/igrc721.gno index 387547a7e26..ae999d43623 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721.gno @@ -1,38 +1,37 @@ package grc721 -import "std" +import ( + "std" +) type IGRC721 interface { - BalanceOf(owner std.Address) (uint64, error) - OwnerOf(tid TokenID) (std.Address, error) - SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) - SafeTransferFrom(from, to std.Address, tid TokenID) error - TransferFrom(from, to std.Address, tid TokenID) error - Approve(approved std.Address, tid TokenID) error - SetApprovalForAll(operator std.Address, approved bool) error - GetApproved(tid TokenID) (std.Address, error) - IsApprovedForAll(owner, operator std.Address) bool -} + // BalanceOf returns the number of tokens in `owner`'s account + BalanceOf(owner std.Address) uint64 -type ( - TokenID string - TokenURI string -) + // OwnerOf returns the owner of the `tokenId` token + // tokenId must exist + OwnerOf(tokenId string) std.Address -type TransferEvent struct { - From std.Address - To std.Address - TokenID TokenID -} + // TransferFrom transfers `tokenId` token from `from` to `to` + TransferFrom(from, to std.Address, tokenId string) -type ApprovalEvent struct { - Owner std.Address - Approved std.Address - TokenID TokenID -} + // Approve gives permission to `to` to transfer `tokenId` token to another account + // The approval is cleared when the token is transferred + Approve(to std.Address, tokenId string) + + // SetApprovalForAll approves or removes `operator` as an operator for the caller + // Operators can call TransferFrom for any token owned by the caller + SetApprovalForAll(operator std.Address, approved bool) + + // GetApproved returns the account approved for `tokenId` token + GetApproved(tokenId string) std.Address + + // IsApprovedForAll returns if the `operator` is allowed to manage all the assets of `owner` + IsApprovedForAll(owner, operator std.Address) bool + + // TokenURI gets the tokenURI for matching `tokenId` + TokenURI(tokenId string) string -type ApprovalForAllEvent struct { - Owner std.Address - Operator std.Address - Approved bool + // SetTokenURI sets `tokenURI` as the tokenURI of `tokenId` + SetTokenURI(tokenId string, tokenURI string) string } diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno deleted file mode 100644 index 8a2be1ff75d..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ /dev/null @@ -1,38 +0,0 @@ -package grc721 - -// IGRC721CollectionMetadata describes basic information about an NFT collection. -type IGRC721CollectionMetadata interface { - Name() string // Name returns the name of the collection. - Symbol() string // Symbol returns the symbol of the collection. -} - -// IGRC721Metadata follows the Ethereum standard -type IGRC721Metadata interface { - IGRC721CollectionMetadata - TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. -} - -// IGRC721Metadata follows the OpenSea metadata standard -type IGRC721MetadataOnchain interface { - IGRC721CollectionMetadata - TokenMetadata(tid TokenID) (Metadata, error) -} - -type Trait struct { - DisplayType string - TraitType string - Value string -} - -// see: https://docs.opensea.io/docs/metadata-standards -type Metadata struct { - Image string // URL to the image of the item. Can be any type of image (including SVGs, which will be cached into PNGs by OpenSea), IPFS or Arweave URLs or paths. We recommend using a minimum 3000 x 3000 image. - ImageData string // Raw SVG image data, if you want to generate images on the fly (not recommended). Only use this if you're not including the image parameter. - ExternalURL string // URL that will appear below the asset's image on OpenSea and will allow users to leave OpenSea and view the item on your site. - Description string // Human-readable description of the item. Markdown is supported. - Name string // Name of the item. - Attributes []Trait // Attributes for the item, which will show up on the OpenSea page for the item. - BackgroundColor string // Background color of the item on OpenSea. Must be a six-character hexadecimal without a pre-pended # - AnimationURL string // URL to a multimedia attachment for the item. Supported file extensions: GLTF, GLB, WEBM, MP4, M4V, OGV, OGG, MP3, WAV, OGA, HTML (for rich experiences and interactive NFTs using JavaScript canvas, WebGL, etc.). Scripts and relative paths within the HTML page are now supported. Access to browser extensions is not supported. - YoutubeURL string // URL to a YouTube video (only used if animation_url is not provided). -} diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno deleted file mode 100644 index a8a74ea15cc..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ /dev/null @@ -1,18 +0,0 @@ -package grc721 - -import ( - "std" -) - -// IGRC2981 follows the Ethereum standard -type IGRC2981 interface { - // RoyaltyInfo retrieves royalty information for a tokenID and salePrice. - // It returns the payment address, royalty amount, and an error if any. - RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) -} - -// RoyaltyInfo represents royalty information for a token. -type RoyaltyInfo struct { - PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. - Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% -} diff --git a/examples/gno.land/p/demo/grc/grc721/util.gno b/examples/gno.land/p/demo/grc/grc721/util.gno deleted file mode 100644 index bb6bf24d984..00000000000 --- a/examples/gno.land/p/demo/grc/grc721/util.gno +++ /dev/null @@ -1,18 +0,0 @@ -package grc721 - -import ( - "std" -) - -var zeroAddress = std.Address("") - -func isValidAddress(addr std.Address) error { - if !addr.IsValid() { - return ErrInvalidAddress - } - return nil -} - -func emit(event interface{}) { - // TODO: setup a pubsub system here? -} diff --git a/examples/gno.land/r/demo/foo721/foo721.gno b/examples/gno.land/r/demo/foo721/foo721.gno index f7364d4185f..64e463cb0db 100644 --- a/examples/gno.land/r/demo/foo721/foo721.gno +++ b/examples/gno.land/r/demo/foo721/foo721.gno @@ -1,124 +1,102 @@ -package foo721 +package nft import ( + "bytes" "std" + "strconv" "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ownable" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" ) -var ( - admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" - foo = grc721.NewBasicNFT("FooNFT", "FNFT") -) +var token *grc721.Token +var o *ownable.Ownable + +var idCounter int +var ids []int func init() { - mintNNFT(admin, 10) // @administrator (10) - mintNNFT("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm", 5) // @hariom (5) + token = grc721.NewGRC721Token("Example NFT", "EX") + o = ownable.New() + + Mint(o.Owner(), strconv.Itoa(idCounter)) } -func mintNNFT(owner std.Address, n uint64) { - count := foo.TokenCount() - for i := count; i < count+n; i++ { - tid := grc721.TokenID(ufmt.Sprintf("%d", i)) - foo.Mint(owner, tid) - } +func BalanceOf(owner std.Address) uint64 { + return token.BalanceOf(owner) } -// Getters +func OwnerOf(tokenID string) std.Address { + return token.OwnerOf(tokenID) +} -func BalanceOf(user pusers.AddressOrName) uint64 { - balance, err := foo.BalanceOf(users.Resolve(user)) - if err != nil { - panic(err) - } +func TransferFrom(from, to std.Address, tokenID string) { + token.TransferFrom(from, to, tokenID) +} - return balance +func Approve(to std.Address, tokenID string) { + token.Approve(to, tokenID) } -func OwnerOf(tid grc721.TokenID) std.Address { - owner, err := foo.OwnerOf(tid) - if err != nil { - panic(err) - } +func SetApprovalForAll(operator std.Address, approved bool) { + token.SetApprovalForAll(operator, approved) +} + +func GetApproved(tokenID string) std.Address { + return token.GetApproved(tokenID) +} - return owner +func IsApprovedForAll(owner, operator std.Address) bool { + return token.IsApprovedForAll(owner, operator) } -func IsApprovedForAll(owner, user pusers.AddressOrName) bool { - return foo.IsApprovedForAll(users.Resolve(owner), users.Resolve(user)) +func TokenURI(tokenID string) string { + return token.TokenURI(tokenID) } -func GetApproved(tid grc721.TokenID) std.Address { - addr, err := foo.GetApproved(tid) - if err != nil { - panic(err) +func SetTokenURI(tokenID string, uri string) string { + if err := o.CallerIsOwner(); err != nil { + panic("only owner can mint NFTs") } - return addr + return token.SetTokenURI(tokenID, uri) } -// Setters - -func Approve(user pusers.AddressOrName, tid grc721.TokenID) { - err := foo.Approve(users.Resolve(user), tid) - if err != nil { - panic(err) - } +func Name() string { + return token.Name() } -func SetApprovalForAll(user pusers.AddressOrName, approved bool) { - err := foo.SetApprovalForAll(users.Resolve(user), approved) - if err != nil { - panic(err) - } +func Symbol() string { + return token.Symbol() } -func TransferFrom(from, to pusers.AddressOrName, tid grc721.TokenID) { - err := foo.TransferFrom(users.Resolve(from), users.Resolve(to), tid) - if err != nil { - panic(err) +func Mint(to std.Address, tokenID string) { + if err := o.CallerIsOwner(); err != nil { + panic("only owner can mint NFTs") } -} -// Admin + token.Mint(to, tokenID) -func Mint(to pusers.AddressOrName, tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := foo.Mint(users.Resolve(to), tid) - if err != nil { - panic(err) - } + ids = append(ids, idCounter) + idCounter += 1 } -func Burn(tid grc721.TokenID) { - caller := std.PrevRealm().Addr() - assertIsAdmin(caller) - err := foo.Burn(tid) - if err != nil { - panic(err) - } +func GetCollectionOwner() std.Address { + return o.Owner() } -// Render +// Render renders tokens & their owners +func Render(_ string) string { + var buf bytes.Buffer -func Render(path string) string { - switch { - case path == "": - return foo.RenderHome() - default: - return "404\n" - } -} + buf.WriteString(ufmt.Sprintf("# NFT Collection: \"%s\" $%s\n\n", Name(), Symbol())) -// Util - -func assertIsAdmin(address std.Address) { - if address != admin { - panic("restricted access") + for i := 0; i < len(ids); i++ { + owner := token.OwnerOf(strconv.Itoa(ids[i])) + str := ufmt.Sprintf("#### TokenID #%d - owned by %s\n", ids[i], owner.String()) + buf.WriteString(str) } + + return buf.String() } diff --git a/examples/gno.land/r/demo/foo721/foo721_test.gno b/examples/gno.land/r/demo/foo721/foo721_test.gno deleted file mode 100644 index 2477ca34fde..00000000000 --- a/examples/gno.land/r/demo/foo721/foo721_test.gno +++ /dev/null @@ -1,33 +0,0 @@ -package foo721 - -import ( - "testing" - - "gno.land/p/demo/grc/grc721" - "gno.land/r/demo/users" - - pusers "gno.land/p/demo/users" -) - -func TestFoo721(t *testing.T) { - admin := pusers.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - hariom := pusers.AddressOrName("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - - for i, tc := range []struct { - name string - expected interface{} - fn func() interface{} - }{ - {"BalanceOf(admin)", uint64(10), func() interface{} { return BalanceOf(admin) }}, - {"BalanceOf(hariom)", uint64(5), func() interface{} { return BalanceOf(hariom) }}, - {"OwnerOf(0)", users.Resolve(admin), func() interface{} { return OwnerOf(grc721.TokenID("0")) }}, - {"IsApprovedForAll(admin, hariom)", false, func() interface{} { return IsApprovedForAll(admin, hariom) }}, - } { - t.Run(tc.name, func(t *testing.T) { - got := tc.fn() - if tc.expected != got { - t.Errorf("expected: %v got: %v", tc.expected, got) - } - }) - } -} diff --git a/examples/gno.land/r/demo/foo721/gno.mod b/examples/gno.land/r/demo/foo721/gno.mod index e013677379d..80ac1edf141 100644 --- a/examples/gno.land/r/demo/foo721/gno.mod +++ b/examples/gno.land/r/demo/foo721/gno.mod @@ -2,7 +2,6 @@ module gno.land/r/demo/foo721 require ( gno.land/p/demo/grc/grc721 v0.0.0-latest + gno.land/p/demo/ownable 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/users v0.0.0-latest ) diff --git a/examples/gno.land/r/demo/nft/README.md b/examples/gno.land/r/demo/nft/README.md deleted file mode 100644 index 2b4bc827e88..00000000000 --- a/examples/gno.land/r/demo/nft/README.md +++ /dev/null @@ -1,11 +0,0 @@ -NFT's are all the rage these days, for various reasons. - -I read over EIP-721 which appears to be the de-facto NFT standard on Ethereum. Then, made a sample implementation of EIP-721 (let's here called GRC-721). The implementation isn't complete, but it demonstrates the main functionality. - - - [EIP-721](https://eips.ethereum.org/EIPS/eip-721) - - [gno.land/r/nft/nft.go](https://gno.land/r/nft/nft.go) - - [zrealm_nft3.go test](https://github.com/gnolang/gno/blob/master/tests/files2/zrealm_nft3.gno) - -In short, this demonstrates how to implement Ethereum contract interfaces in Gno.land; by using only standard Go language features. - -Please leave a comment ([guide](https://gno.land/r/boards:gnolang/1)). diff --git a/examples/gno.land/r/demo/nft/gno.mod b/examples/gno.land/r/demo/nft/gno.mod deleted file mode 100644 index 89e0055be51..00000000000 --- a/examples/gno.land/r/demo/nft/gno.mod +++ /dev/null @@ -1,6 +0,0 @@ -module gno.land/r/demo/nft - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/grc/grc721 v0.0.0-latest -) diff --git a/examples/gno.land/r/demo/nft/nft.gno b/examples/gno.land/r/demo/nft/nft.gno deleted file mode 100644 index f4340d6d2a9..00000000000 --- a/examples/gno.land/r/demo/nft/nft.gno +++ /dev/null @@ -1,144 +0,0 @@ -package nft - -import ( - "std" - "strconv" - - "gno.land/p/demo/avl" - "gno.land/p/demo/grc/grc721" -) - -type token struct { - grc721.IGRC721 // implements the GRC721 interface - - tokenCounter int - tokens avl.Tree // grc721.TokenID -> *NFToken{} - operators avl.Tree // owner std.Address -> operator std.Address -} - -type NFToken struct { - owner std.Address - approved std.Address - tokenID grc721.TokenID - data string -} - -var gToken = &token{} - -func GetToken() *token { return gToken } - -func (grc *token) nextTokenID() grc721.TokenID { - grc.tokenCounter++ - s := strconv.Itoa(grc.tokenCounter) - return grc721.TokenID(s) -} - -func (grc *token) getToken(tid grc721.TokenID) (*NFToken, bool) { - token, ok := grc.tokens.Get(string(tid)) - if !ok { - return nil, false - } - return token.(*NFToken), true -} - -func (grc *token) Mint(to std.Address, data string) grc721.TokenID { - tid := grc.nextTokenID() - grc.tokens.Set(string(tid), &NFToken{ - owner: to, - tokenID: tid, - data: data, - }) - return tid -} - -func (grc *token) BalanceOf(owner std.Address) (count int64) { - panic("not yet implemented") -} - -func (grc *token) OwnerOf(tid grc721.TokenID) std.Address { - token, ok := grc.getToken(tid) - if !ok { - panic("token does not exist") - } - return token.owner -} - -// XXX not fully implemented yet. -func (grc *token) SafeTransferFrom(from, to std.Address, tid grc721.TokenID) { - grc.TransferFrom(from, to, tid) - // When transfer is complete, this function checks if `_to` is a smart - // contract (code size > 0). If so, it calls `onERC721Received` on - // `_to` and throws if the return value is not - // `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. - // XXX ensure "to" is a realm with onERC721Received() signature. -} - -func (grc *token) TransferFrom(from, to std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) - token, ok := grc.getToken(tid) - // Throws if `_tokenId` is not a valid NFT. - if !ok { - panic("token does not exist") - } - // Throws unless `msg.sender` is the current owner, an authorized - // operator, or the approved address for this NFT. - if caller != token.owner && caller != token.approved { - operator, ok := grc.operators.Get(token.owner.String()) - if !ok || caller != operator.(std.Address) { - panic("unauthorized") - } - } - // Throws if `_from` is not the current owner. - if from != token.owner { - panic("from is not the current owner") - } - // Throws if `_to` is the zero address. - if to == "" { - panic("to cannot be empty") - } - // Good. - token.owner = to -} - -func (grc *token) Approve(approved std.Address, tid grc721.TokenID) { - caller := std.GetCallerAt(2) - token, ok := grc.getToken(tid) - // Throws if `_tokenId` is not a valid NFT. - if !ok { - panic("token does not exist") - } - // Throws unless `msg.sender` is the current owner, - // or an authorized operator. - if caller != token.owner { - operator, ok := grc.operators.Get(token.owner.String()) - if !ok || caller != operator.(std.Address) { - panic("unauthorized") - } - } - // Good. - token.approved = approved -} - -// XXX make it work for set of operators. -func (grc *token) SetApprovalForAll(operator std.Address, approved bool) { - caller := std.GetCallerAt(2) - grc.operators.Set(caller.String(), operator) -} - -func (grc *token) GetApproved(tid grc721.TokenID) std.Address { - token, ok := grc.getToken(tid) - // Throws if `_tokenId` is not a valid NFT. - if !ok { - panic("token does not exist") - } - return token.approved -} - -// XXX make it work for set of operators -func (grc *token) IsApprovedForAll(owner, operator std.Address) bool { - operator2, ok := grc.operators.Get(owner.String()) - if !ok { - return false - } - return operator == operator2.(std.Address) -} diff --git a/examples/gno.land/r/demo/nft/z_0_filetest.gno b/examples/gno.land/r/demo/nft/z_0_filetest.gno deleted file mode 100644 index 973d5f95eab..00000000000 --- a/examples/gno.land/r/demo/nft/z_0_filetest.gno +++ /dev/null @@ -1,223 +0,0 @@ -// PKGPATH: gno.land/r/nft_test -package nft_test - -import ( - "gno.land/p/demo/testutils" - "gno.land/r/demo/nft" -) - -func main() { - addr1 := testutils.TestAddress("addr1") - // addr2 := testutils.TestAddress("addr2") - grc721 := nft.GetToken() - tid := grc721.Mint(addr1, "NFT#1") - println(grc721.OwnerOf(tid)) - println(addr1) -} - -// Output: -// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5 -// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5 - -// Realm: -// switchrealm["gno.land/r/demo/nft"] -// switchrealm["gno.land/r/demo/nft"] -// c[67c479d3d51d4056b2f4111d5352912a00be311e:8]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "std.Address" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/grc/grc721.TokenID" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "NFT#1" -// } -// } -// ], -// "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:8", -// "ModTime": "0", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", -// "RefCount": "1" -// } -// } -// c[67c479d3d51d4056b2f4111d5352912a00be311e:7]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "16" -// }, -// "V": { -// "@type": "/gno.StringValue", -// "value": "1" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/nft.NFToken" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/nft.NFToken" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "c06f58d0ff2bc26ad3e65e953b127a0d03353e97", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:8" -// } -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "64" -// } -// }, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// }, -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:7", -// "ModTime": "0", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", -// "RefCount": "1" -// } -// } -// u[67c479d3d51d4056b2f4111d5352912a00be311e:5]={ -// "Fields": [ -// { -// "T": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// } -// }, -// "V": { -// "@type": "/gno.PointerValue", -// "Base": null, -// "Index": "0", -// "TV": { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Node" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "45a64533aa57b49b6b4a1d3f6de79db8bea3a710", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:7" -// } -// } -// } -// } -// ], -// "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:5", -// "ModTime": "6", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", -// "RefCount": "1" -// } -// } -// u[67c479d3d51d4056b2f4111d5352912a00be311e:4]={ -// "Fields": [ -// {}, -// { -// "N": "AQAAAAAAAAA=", -// "T": { -// "@type": "/gno.PrimitiveType", -// "value": "32" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "dad3106a54e1facb92bce473898b8aec0eb930ff", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:5" -// } -// }, -// { -// "T": { -// "@type": "/gno.RefType", -// "ID": "gno.land/p/demo/avl.Tree" -// }, -// "V": { -// "@type": "/gno.RefValue", -// "Hash": "05ab6746ea84b55ca133806af215d99a1c4b045e", -// "ObjectID": "67c479d3d51d4056b2f4111d5352912a00be311e:6" -// } -// } -// ], -// "ObjectInfo": { -// "ID": "67c479d3d51d4056b2f4111d5352912a00be311e:4", -// "ModTime": "6", -// "OwnerID": "67c479d3d51d4056b2f4111d5352912a00be311e:2", -// "RefCount": "1" -// } -// } -// switchrealm["gno.land/r/demo/nft"] -// switchrealm["gno.land/r/nft_test"] diff --git a/examples/gno.land/r/demo/nft/z_1_filetest.gno b/examples/gno.land/r/demo/nft/z_1_filetest.gno deleted file mode 100644 index 15b0a9a7785..00000000000 --- a/examples/gno.land/r/demo/nft/z_1_filetest.gno +++ /dev/null @@ -1,20 +0,0 @@ -// PKGPATH: gno.land/r/nft_test -package nft_test - -import ( - "gno.land/p/demo/testutils" - "gno.land/r/demo/nft" -) - -func main() { - addr1 := testutils.TestAddress("addr1") - addr2 := testutils.TestAddress("addr2") - grc721 := nft.GetToken() - tid := grc721.Mint(addr1, "NFT#1") - println(grc721.OwnerOf(tid)) - println(addr1) - grc721.TransferFrom(addr1, addr2, tid) -} - -// Error: -// unauthorized diff --git a/examples/gno.land/r/demo/nft/z_2_filetest.gno b/examples/gno.land/r/demo/nft/z_2_filetest.gno deleted file mode 100644 index 00d6eb0c0f0..00000000000 --- a/examples/gno.land/r/demo/nft/z_2_filetest.gno +++ /dev/null @@ -1,24 +0,0 @@ -// PKGPATH: gno.land/r/nft_test -package nft_test - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/nft" -) - -func main() { - caller := std.GetCallerAt(1) - addr1 := testutils.TestAddress("addr1") - // addr2 := testutils.TestAddress("addr2") - grc721 := nft.GetToken() - tid := grc721.Mint(caller, "NFT#1") - println(grc721.OwnerOf(tid)) - println(addr1) - grc721.TransferFrom(caller, addr1, tid) -} - -// Output: -// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5 diff --git a/examples/gno.land/r/demo/nft/z_3_filetest.gno b/examples/gno.land/r/demo/nft/z_3_filetest.gno deleted file mode 100644 index 00a4d00291b..00000000000 --- a/examples/gno.land/r/demo/nft/z_3_filetest.gno +++ /dev/null @@ -1,26 +0,0 @@ -// PKGPATH: gno.land/r/nft_test -package nft_test - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/nft" -) - -func main() { - caller := std.GetCallerAt(1) - addr1 := testutils.TestAddress("addr1") - addr2 := testutils.TestAddress("addr2") - grc721 := nft.GetToken() - tid := grc721.Mint(caller, "NFT#1") - println(grc721.OwnerOf(tid)) - println(addr1) - grc721.Approve(caller, tid) // approve self. - grc721.TransferFrom(caller, addr1, tid) - grc721.TransferFrom(addr1, addr2, tid) -} - -// Output: -// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm -// g1v9jxgu33ta047h6lta047h6lta047h6l43dqc5 diff --git a/examples/gno.land/r/demo/nft/z_4_filetest.gno b/examples/gno.land/r/demo/nft/z_4_filetest.gno deleted file mode 100644 index 773444ff8fa..00000000000 --- a/examples/gno.land/r/demo/nft/z_4_filetest.gno +++ /dev/null @@ -1,26 +0,0 @@ -// PKGPATH: gno.land/r/nft_test -package nft_test - -import ( - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/nft" -) - -func main() { - caller := std.GetCallerAt(1) - addr1 := testutils.TestAddress("addr1") - addr2 := testutils.TestAddress("addr2") - grc721 := nft.GetToken() - tid := grc721.Mint(caller, "NFT#1") - println(grc721.OwnerOf(tid)) - println(addr1) - grc721.Approve(caller, tid) // approve self. - grc721.TransferFrom(caller, addr1, tid) - grc721.Approve("", tid) // approve addr1. - grc721.TransferFrom(addr1, addr2, tid) -} - -// Error: -// unauthorized diff --git a/examples/gno.land/r/x/grc721-by-spec/README.md b/examples/gno.land/r/x/grc721-by-spec/README.md deleted file mode 100644 index dc75be27f06..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# grc721-by-spec - -This folder contains 3 main parts: -1. The Go implementation of the ERC721 standard, called GRC721, which was used -for the initial implementation & testing -2. The Gno package implementation ported from the Go implementation mentioned above -3. An example NFT collection realm utilizing the Gno package - -To test this out, install `gnodev` and run the following command in this folder: -```shell -gnodev ./grc721-gno ./exampleNFT -``` - -Then, visit [`localhost:8888/r/example/nft`](http://localhost:8888/r/example/nft). diff --git a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod deleted file mode 100644 index f03d86ae8db..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/r/example/nft - -require ( - gno.land/p/demo/ownable v0.0.0-latest - gno.land/p/demo/seqid v0.0.0-latest - gno.land/p/demo/xgrc721 v0.0.0-latest -) diff --git a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno b/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno deleted file mode 100644 index cc6b99d7137..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/exampleNFT/nft.gno +++ /dev/null @@ -1,102 +0,0 @@ -package nft - -import ( - "bytes" - "std" - "strconv" - - "gno.land/p/demo/ownable" - "gno.land/p/demo/ufmt" - "gno.land/p/demo/xgrc721" -) - -var token *xgrc721.Token -var o *ownable.Ownable - -var idCounter int -var ids []int - -func init() { - token = xgrc721.NewGRC721Token("Example NFT", "EX") - o = ownable.New() - - Mint(o.Owner(), strconv.Itoa(idCounter)) -} - -func BalanceOf(owner std.Address) uint64 { - return token.BalanceOf(owner) -} - -func OwnerOf(tokenID string) std.Address { - return token.OwnerOf(tokenID) -} - -func TransferFrom(from, to std.Address, tokenID string) { - token.TransferFrom(from, to, tokenID) -} - -func Approve(to std.Address, tokenID string) { - token.Approve(to, tokenID) -} - -func SetApprovalForAll(operator std.Address, approved bool) { - token.SetApprovalForAll(operator, approved) -} - -func GetApproved(tokenID string) std.Address { - return token.GetApproved(tokenID) -} - -func IsApprovedForAll(owner, operator std.Address) bool { - return token.IsApprovedForAll(owner, operator) -} - -func TokenURI(tokenID string) string { - return token.TokenURI(tokenID) -} - -func SetTokenURI(tokenID string, uri string) string { - if err := o.CallerIsOwner(); err != nil { - panic("only owner can mint NFTs") - } - - return token.SetTokenURI(tokenID, uri) -} - -func Name() string { - return token.Name() -} - -func Symbol() string { - return token.Symbol() -} - -func Mint(to std.Address, tokenID string) { - if err := o.CallerIsOwner(); err != nil { - panic("only owner can mint NFTs") - } - - token.Mint(to, tokenID) - - ids = append(ids, idCounter) - idCounter += 1 -} - -func GetCollectionOwner() std.Address { - return o.Owner() -} - -// Render renders tokens & their owners -func Render(_ string) string { - var buf bytes.Buffer - - buf.WriteString(ufmt.Sprintf("# NFT Collection: \"%s\" $%s\n\n", Name(), Symbol())) - - for i := 0; i < len(ids); i++ { - owner := token.OwnerOf(strconv.Itoa(ids[i])) - str := ufmt.Sprintf("#### TokenID #%d - owned by %s\n", ids[i], owner.String()) - buf.WriteString(str) - } - - return buf.String() -} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod deleted file mode 100644 index 11780a616db..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/gno.mod +++ /dev/null @@ -1,7 +0,0 @@ -module gno.land/p/demo/xgrc721 - -require ( - gno.land/p/demo/avl v0.0.0-latest - gno.land/p/demo/testutils v0.0.0-latest - gno.land/p/demo/ufmt v0.0.0-latest -) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno b/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno deleted file mode 100644 index fdf76da706e..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-gno/igrc721.gno +++ /dev/null @@ -1,37 +0,0 @@ -package xgrc721 - -import ( - "std" -) - -type IGRC721 interface { - // BalanceOf returns the number of tokens in `owner`'s account - BalanceOf(owner std.Address) uint64 - - // OwnerOf returns the owner of the `tokenId` token - // tokenId must exist - OwnerOf(tokenId string) std.Address - - // TransferFrom transfers `tokenId` token from `from` to `to` - TransferFrom(from, to std.Address, tokenId string) - - // Approve gives permission to `to` to transfer `tokenId` token to another account - // The approval is cleared when the token is transferred - Approve(to std.Address, tokenId string) - - // SetApprovalForAll approves or removes `operator` as an operator for the caller - // Operators can call TransferFrom for any token owned by the caller - SetApprovalForAll(operator std.Address, approved bool) - - // GetApproved returns the account approved for `tokenId` token - GetApproved(tokenId string) std.Address - - // IsApprovedForAll returns if the `operator` is allowed to manage all the assets of `owner` - IsApprovedForAll(owner, operator std.Address) bool - - // TokenURI gets the tokenURI for matching `tokenId` - TokenURI(tokenId string) string - - // SetTokenURI sets `tokenURI` as the tokenURI of `tokenId` - SetTokenURI(tokenId string, tokenURI string) string -} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod deleted file mode 100644 index 89eae667648..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module grc721 - -go 1.21.6 - -require github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af - -require ( - github.com/btcsuite/btcd/btcutil v1.1.5 // indirect - google.golang.org/protobuf v1.33.0 // indirect -) diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum b/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum deleted file mode 100644 index 5aaea269e1d..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-go/go.sum +++ /dev/null @@ -1,118 +0,0 @@ -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= -github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= -github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= -github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= -github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= -github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= -github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= -github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gnolang/gno v0.0.0-20240402115239-831bb6f92e1a h1:WyozSr+GOTQUnQ1BX8CSsx/JZe1c3X3+rEsSW/MXJDY= -github.com/gnolang/gno v0.0.0-20240402115239-831bb6f92e1a/go.mod h1:we1MozTqYAQFayi2eIvNtdqtGtFB7DQYSVguWBurWRI= -github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af h1:dddvtkbVRlpT1Egb+zVG/eLfB8sCmEwkPwlNKZqKM+g= -github.com/gnolang/gno v0.0.0-20240507185310-5a96926dc6af/go.mod h1:vbm5sS6KZj2rkwJ9C6AtdpPZB7aZDhfZ3lJUC+qa/to= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jaekwon/testify v1.6.1 h1:4AtAJcR9GzXN5W4DdY7ie74iCPiJV1JJUJL90t2ZUyw= -github.com/jaekwon/testify v1.6.1/go.mod h1:Oun0RXIHI7osufabQ60i4Lqkj0GXLbqI1I7kgzBNm1U= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go b/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go deleted file mode 100644 index 471ae77a204..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-go/grc721.go +++ /dev/null @@ -1,203 +0,0 @@ -package grc721 - -import ( - "github.com/gnolang/gno/examples/gno.land/p/demo/avl" - "github.com/gnolang/gno/examples/gno.land/p/demo/ufmt" - "github.com/gnolang/gno/gnovm/stdlibs/std" - "strconv" -) - -type Token struct { - name string - symbol string - owners *avl.Tree // tokenID > std.Address - balances *avl.Tree // std.Address > # of owned tokens - tokenApprovals *avl.Tree // tokenID > std.Address - operatorApprovals *avl.Tree // "OwnerAddress:OperatorAddress" -> bool - tokenURIs *avl.Tree // tokenID > URI -} - -var _ IGRC721 = (*Token)(nil) - -func NewGRC721Token(name, symbol string) *Token { - return &Token{ - name: name, - symbol: symbol, - owners: avl.NewTree(), - balances: avl.NewTree(), - // give an address permission to a specific tokenID - tokenApprovals: avl.NewTree(), - // give any addresses permissions for all owners' assets - operatorApprovals: avl.NewTree(), - tokenURIs: avl.NewTree(), - } -} - -func (nft Token) Name() string { return nft.name } -func (nft Token) Symbol() string { return nft.symbol } - -func (nft Token) BalanceOf(owner std.Address) uint64 { - mustBeValid(owner) - - balance, found := nft.balances.Get(owner.String()) - if !found { - return 0 - } - - return balance.(uint64) -} - -func (nft Token) OwnerOf(tokenId string) std.Address { - return nft.mustBeOwned(tokenId) -} - -func (nft Token) TransferFrom(from, to std.Address, tokenId string) { - caller := std.PrevRealm().Addr() - mustBeValid(to) - - prevOwner := nft.update(to, tokenId, caller) - if prevOwner != from { - panic("GRC721: incorrect owner") - } - - std.Emit("Transfer", "from", from.String(), "operator", to.String(), "tokenID", tokenId) -} - -func (nft Token) Approve(to std.Address, tokenId string) { - caller := std.PrevRealm().Addr() - - if caller == to { - panic("GRC721: cannot approve yourself") - } - - mustBeValid(to) - nft.requireOwner(caller, tokenId) - - nft.tokenApprovals.Set(tokenId, to) - std.Emit("Approval", "owner", caller.String(), "approved", to.String(), "tokenID", tokenId) -} - -func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { - caller := std.PrevRealm().Addr() - mustBeValid(operator) - - if caller == operator { - panic("GRC721: cannot set operator to yourself") - } - - nft.operatorApprovals.Set(operatorKey(caller, operator), approved) - std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", strconv.FormatBool(approved)) -} - -func (nft Token) GetApproved(tokenId string) std.Address { - _ = nft.mustBeOwned(tokenId) - return nft.getApproved(tokenId) -} - -func (nft Token) IsApprovedForAll(owner, operator std.Address) bool { - approved, exists := nft.operatorApprovals.Get(operatorKey(owner, operator)) - if !exists || approved == false { - return false - } - - return true -} - -func (nft Token) TokenURI(tokenId string) string { - nft.mustBeOwned(tokenId) - uri, exists := nft.tokenURIs.Get(tokenId) - if !exists { - return "" - } - - return uri.(string) -} - -func (nft Token) SetTokenURI(tokenId string, tokenURI string) string { - nft.requireOwner(std.PrevRealm().Addr(), tokenId) - nft.tokenURIs.Set(tokenId, tokenURI) - return tokenURI -} - -// Helpers -func (nft Token) requireOwner(caller std.Address, tokenId string) { - if caller != nft.mustBeOwned(tokenId) { - panic("GRC721: not owner") - } -} - -func (nft Token) getApproved(tokenId string) std.Address { - approved, exists := nft.tokenApprovals.Get(tokenId) - if !exists { - return "" // panic instead? - } - - return approved.(std.Address) -} - -// mustBeValid panics if the given address is not valid -func mustBeValid(address std.Address) { - if !address.IsValid() { - err := ufmt.Sprintf("GRC721: invalid address %s", address) - panic(err) - } -} - -// mustBeOwned panics if token is not owned by an address (does not exist) -// If the token is owned, mustBeOwned returns the owner of the token -func (nft Token) mustBeOwned(tokenId string) std.Address { - owner, exists := nft.balances.Get(tokenId) - if !exists { - err := ufmt.Sprintf("GRC721: token with id %s does not exist", tokenId) - panic(err) - } - - return owner.(std.Address) -} - -// checkAuthorized checks if spender is authorized to spend specified token on behalf of owner -// Panics if token doesn't exist, or if spender is not authorized in any way -func (nft Token) checkAuthorized(owner, spender std.Address, tokenId string) { - _ = nft.mustBeOwned(tokenId) - - if !nft.isAuthorized(owner, spender, tokenId) { - str := ufmt.Sprintf("GRC721: %s is not authorized for %s", spender, tokenId) - panic(str) - } -} - -// isAuthorized returns if the spender is authorized to transfer the specified token -// Assumes addresses are valid and the token exists -func (nft Token) isAuthorized(owner, spender std.Address, tokenId string) bool { - return owner == spender || - nft.IsApprovedForAll(owner, spender) || - nft.getApproved(tokenId) == owner -} - -func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Address { - owner := nft.mustBeOwned(tokenId) - - if auth != "" { - nft.checkAuthorized(owner, auth, tokenId) - } - - // Clear approval for this token - nft.tokenApprovals.Set(tokenId, "") - - // Set new balances - newOwnerBalance, _ := nft.balances.Get(owner.String()) - nft.balances.Set(owner.String(), newOwnerBalance.(int64)-1) - - toBalance, _ := nft.balances.Get(to.String()) - nft.balances.Set(to.String(), toBalance.(int64)+1) - - // Set new ownership - nft.owners.Set(tokenId, to.String()) - - return owner -} - -// operatorKey is a helper to create the key for the operatorApproval tree -func operatorKey(owner, operator std.Address) string { - return owner.String() + ":" + operator.String() -} diff --git a/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go b/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go deleted file mode 100644 index db118aefd26..00000000000 --- a/examples/gno.land/r/x/grc721-by-spec/grc721-go/igrc721.go +++ /dev/null @@ -1,37 +0,0 @@ -package grc721 - -import ( - "github.com/gnolang/gno/gnovm/stdlibs/std" -) - -type IGRC721 interface { - // BalanceOf returns the number of tokens in `owner`'s account - BalanceOf(owner std.Address) uint64 - - // OwnerOf returns the owner of the `tokenId` token - // tokenId must exist - OwnerOf(tokenId string) std.Address - - // TransferFrom transfers `tokenId` token from `from` to `to` - TransferFrom(from, to std.Address, tokenId string) - - // Approve gives permission to `to` to transfer `tokenId` token to another account - // The approval is cleared when the token is transferred - Approve(to std.Address, tokenId string) - - // SetApprovalForAll approves or removes `operator` as an operator for the caller - // Operators can call TransferFrom for any token owned by the caller - SetApprovalForAll(operator std.Address, approved bool) - - // GetApproved returns the account approved for `tokenId` token - GetApproved(tokenId string) std.Address - - // IsApprovedForAll returns if the `operator` is allowed to manage all the assets of `owner` - IsApprovedForAll(owner, operator std.Address) bool - - // TokenURI gets the tokenURI for matching `tokenId` - TokenURI(tokenId string) string - - // SetTokenURI sets `tokenURI` as the tokenURI of `tokenId` - SetTokenURI(tokenId string, tokenURI string) string -} From 0da8c9974a98bf09ff2e78d549e6ddb38eb6e8d0 Mon Sep 17 00:00:00 2001 From: leohhhn Date: Mon, 10 Jun 2024 18:36:20 +0200 Subject: [PATCH 4/8] typo --- examples/gno.land/p/demo/grc/grc721/grc721_test.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno index 180fed85bb4..3fc3de92378 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno @@ -167,7 +167,7 @@ func TestMint(t *testing.T) { defer func() { if r := recover(); r == nil { - t.Fatalf("Mint should have paniced") + t.Fatalf("Mint should have panicked") } }() @@ -195,7 +195,7 @@ func TestBurn(t *testing.T) { defer func() { if r := recover(); r == nil { - t.Fatalf("OwnerOf should have paniced after burn") + t.Fatalf("OwnerOf should have panicked after burn") } }() From a3c33fa7c353d9a469c7f174cf226384fb8e1fa6 Mon Sep 17 00:00:00 2001 From: leohhhn Date: Mon, 17 Jun 2024 13:44:24 +0200 Subject: [PATCH 5/8] format events --- .../gno.land/p/demo/grc/grc721/grc721.gno | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721.gno b/examples/gno.land/p/demo/grc/grc721/grc721.gno index 4f2bd7bb568..26d1391c81f 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721.gno @@ -16,6 +16,12 @@ type Token struct { tokenURIs *avl.Tree // tokenID > URI } +type tokenData struct { + owner std.Address + approval std.Address + uri string +} + var _ IGRC721 = (*Token)(nil) const emptyAddress = std.Address("") @@ -84,9 +90,17 @@ func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { nft.operatorApprovals.Set(operatorKey(caller, operator), approved) if approved { - std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", "true") + std.Emit("ApprovalForAll", + "owner", caller.String(), + "operator", operator.String(), + "approved", "true", + ) } else { - std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), "approved", "false") + std.Emit("ApprovalForAll", + "owner", caller.String(), + "operator", operator.String(), + "approved", "false", + ) } // We do not support strconv.FormatBool yet } @@ -227,7 +241,12 @@ func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Ad } } - std.Emit("Transfer", "from", from.String(), "to", to.String(), "tokenID", tokenId) + std.Emit("Transfer", + "from", from.String(), + "to", to.String(), + "tokenID", tokenId, + ) + return from } @@ -239,7 +258,11 @@ func (nft Token) approve(to std.Address, tokenId string, auth std.Address, emitE panic("GRC721: invalid approver") } if emitEvent { - std.Emit("Approval", "owner", owner.String(), "approved", to.String(), "tokenID", tokenId) + std.Emit("Approval", + "owner", owner.String(), + "approved", to.String(), + "tokenID", tokenId, + ) } } From 742d6d5191475c85f556e23bf1ce53bbdf2bb2fc Mon Sep 17 00:00:00 2001 From: leohhhn Date: Mon, 17 Jun 2024 14:36:04 +0200 Subject: [PATCH 6/8] collection --- .../gno.land/p/demo/grc/grc721/grc721.gno | 131 +++++++++--------- .../p/demo/grc/grc721/grc721_test.gno | 26 ++-- 2 files changed, 76 insertions(+), 81 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721.gno b/examples/gno.land/p/demo/grc/grc721/grc721.gno index 26d1391c81f..406679468c0 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721.gno @@ -1,12 +1,13 @@ package grc721 import ( + "std" + "gno.land/p/demo/avl" "gno.land/p/demo/ufmt" - "std" ) -type Token struct { +type Collection struct { name string symbol string owners *avl.Tree // tokenID > std.Address @@ -16,18 +17,12 @@ type Token struct { tokenURIs *avl.Tree // tokenID > URI } -type tokenData struct { - owner std.Address - approval std.Address - uri string -} - -var _ IGRC721 = (*Token)(nil) +var _ IGRC721 = (*Collection)(nil) const emptyAddress = std.Address("") -func NewGRC721Token(name, symbol string) *Token { - return &Token{ +func NewGRC721Collection(name, symbol string) *Collection { + return &Collection{ name: name, symbol: symbol, owners: avl.NewTree(), @@ -40,13 +35,13 @@ func NewGRC721Token(name, symbol string) *Token { } } -func (nft Token) Name() string { return nft.name } -func (nft Token) Symbol() string { return nft.symbol } +func (c Collection) Name() string { return c.name } +func (c Collection) Symbol() string { return c.symbol } -func (nft Token) BalanceOf(owner std.Address) uint64 { +func (c Collection) BalanceOf(owner std.Address) uint64 { mustBeValid(owner) - balance, found := nft.balances.Get(owner.String()) + balance, found := c.balances.Get(owner.String()) if !found { return 0 } @@ -54,21 +49,21 @@ func (nft Token) BalanceOf(owner std.Address) uint64 { return balance.(uint64) } -func (nft Token) OwnerOf(tokenId string) std.Address { - return nft.mustBeOwned(tokenId) +func (c Collection) OwnerOf(tokenId string) std.Address { + return c.mustBeOwned(tokenId) } -func (nft Token) TransferFrom(from, to std.Address, tokenId string) { +func (c Collection) TransferFrom(from, to std.Address, tokenId string) { caller := std.PrevRealm().Addr() mustBeValid(to) - prevOwner := nft.update(to, tokenId, caller) + prevOwner := c.update(to, tokenId, caller) if prevOwner != from { panic("GRC721: incorrect owner") } } -func (nft Token) Approve(to std.Address, tokenId string) { +func (c Collection) Approve(to std.Address, tokenId string) { caller := std.PrevRealm().Addr() if caller == to { @@ -76,10 +71,10 @@ func (nft Token) Approve(to std.Address, tokenId string) { } mustBeValid(to) - nft.approve(to, tokenId, caller, true) + c.approve(to, tokenId, caller, true) } -func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { +func (c Collection) SetApprovalForAll(operator std.Address, approved bool) { caller := std.PrevRealm().Addr() mustBeValid(operator) @@ -87,7 +82,7 @@ func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { panic("GRC721: cannot set operator to yourself") } - nft.operatorApprovals.Set(operatorKey(caller, operator), approved) + c.operatorApprovals.Set(operatorKey(caller, operator), approved) if approved { std.Emit("ApprovalForAll", @@ -99,18 +94,18 @@ func (nft Token) SetApprovalForAll(operator std.Address, approved bool) { std.Emit("ApprovalForAll", "owner", caller.String(), "operator", operator.String(), - "approved", "false", + "approved", "false", // We do not support strconv.FormatBool yet ) - } // We do not support strconv.FormatBool yet + } } -func (nft Token) GetApproved(tokenId string) std.Address { - _ = nft.mustBeOwned(tokenId) - return nft.getApproved(tokenId) +func (c Collection) GetApproved(tokenId string) std.Address { + _ = c.mustBeOwned(tokenId) + return c.getApproved(tokenId) } -func (nft Token) IsApprovedForAll(owner, operator std.Address) bool { - approved, exists := nft.operatorApprovals.Get(operatorKey(owner, operator)) +func (c Collection) IsApprovedForAll(owner, operator std.Address) bool { + approved, exists := c.operatorApprovals.Get(operatorKey(owner, operator)) if !exists || approved == false { return false } @@ -118,9 +113,9 @@ func (nft Token) IsApprovedForAll(owner, operator std.Address) bool { return true } -func (nft Token) TokenURI(tokenId string) string { - nft.mustBeOwned(tokenId) - uri, exists := nft.tokenURIs.Get(tokenId) +func (c Collection) TokenURI(tokenId string) string { + c.mustBeOwned(tokenId) + uri, exists := c.tokenURIs.Get(tokenId) if !exists { return "" } @@ -128,22 +123,22 @@ func (nft Token) TokenURI(tokenId string) string { return uri.(string) } -func (nft Token) SetTokenURI(tokenId string, tokenURI string) string { - nft.tokenURIs.Set(tokenId, tokenURI) +func (c Collection) SetTokenURI(tokenId string, tokenURI string) string { + c.tokenURIs.Set(tokenId, tokenURI) return tokenURI } -func (nft Token) Mint(to std.Address, tokenId string) { +func (c Collection) Mint(to std.Address, tokenId string) { mustBeValid(to) - prevOwner := nft.update(to, tokenId, emptyAddress) + prevOwner := c.update(to, tokenId, emptyAddress) if prevOwner != emptyAddress { str := ufmt.Sprintf("GRC721: token with id %s has already been minted", tokenId) panic(str) } } -func (nft Token) Burn(tokenId string) { - prevOwner := nft.update(emptyAddress, tokenId, emptyAddress) +func (c Collection) Burn(tokenId string) { + prevOwner := c.update(emptyAddress, tokenId, emptyAddress) if prevOwner == emptyAddress { str := ufmt.Sprintf("GRC721: Token with ID %s does not exist", tokenId) @@ -152,14 +147,14 @@ func (nft Token) Burn(tokenId string) { } // Helpers -func (nft Token) requireOwner(caller std.Address, tokenId string) { - if caller != nft.mustBeOwned(tokenId) { +func (c Collection) requireOwner(caller std.Address, tokenId string) { + if caller != c.mustBeOwned(tokenId) { panic("GRC721: not owner") } } -func (nft Token) getApproved(tokenId string) std.Address { - approved, exists := nft.tokenApprovals.Get(tokenId) +func (c Collection) getApproved(tokenId string) std.Address { + approved, exists := c.tokenApprovals.Get(tokenId) if !exists { return "" // panic instead? } @@ -177,8 +172,8 @@ func mustBeValid(address std.Address) { // mustBeOwned panics if token is not owned by an address (does not exist) // If the token is owned, mustBeOwned returns the owner of the token -func (nft Token) mustBeOwned(tokenId string) std.Address { - owner, exists := nft.owners.Get(tokenId) +func (c Collection) mustBeOwned(tokenId string) std.Address { + owner, exists := c.owners.Get(tokenId) if !exists { err := ufmt.Sprintf("GRC721: token with ID %s does not exist", tokenId) panic(err) @@ -189,10 +184,10 @@ func (nft Token) mustBeOwned(tokenId string) std.Address { // checkAuthorized checks if spender is authorized to spend specified token on behalf of owner // Panics if token doesn't exist, or if spender is not authorized in any way -func (nft Token) checkAuthorized(owner, spender std.Address, tokenId string) { - _ = nft.mustBeOwned(tokenId) +func (c Collection) checkAuthorized(owner, spender std.Address, tokenId string) { + _ = c.mustBeOwned(tokenId) - if !nft.isAuthorized(owner, spender, tokenId) { + if !c.isAuthorized(owner, spender, tokenId) { str := ufmt.Sprintf("GRC721: %s is not authorized for %s", spender, tokenId) panic(str) } @@ -200,41 +195,41 @@ func (nft Token) checkAuthorized(owner, spender std.Address, tokenId string) { // isAuthorized returns if the spender is authorized to transfer the specified token // Assumes addresses are valid and the token exists -func (nft Token) isAuthorized(owner, spender std.Address, tokenId string) bool { +func (c Collection) isAuthorized(owner, spender std.Address, tokenId string) bool { return owner == spender || - nft.IsApprovedForAll(owner, spender) || - nft.getApproved(tokenId) == owner + c.IsApprovedForAll(owner, spender) || + c.getApproved(tokenId) == owner } -func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Address { - from := nft.ownerOf(tokenId) +func (c Collection) update(to std.Address, tokenId string, auth std.Address) std.Address { + from := c.ownerOf(tokenId) if auth != emptyAddress { - nft.checkAuthorized(from, auth, tokenId) + c.checkAuthorized(from, auth, tokenId) } // If token exists if from != emptyAddress { // Clear approval for this token - nft.approve(emptyAddress, tokenId, emptyAddress, false) + c.approve(emptyAddress, tokenId, emptyAddress, false) // Set new balances - ownerNewBalance, _ := nft.balances.Get(from.String()) - nft.balances.Set(from.String(), ownerNewBalance.(uint64)-1) + ownerNewBalance, _ := c.balances.Get(from.String()) + c.balances.Set(from.String(), ownerNewBalance.(uint64)-1) } if to != emptyAddress { - toBalance, initialized := nft.balances.Get(to.String()) + toBalance, initialized := c.balances.Get(to.String()) if !initialized { - nft.balances.Set(to.String(), uint64(1)) + c.balances.Set(to.String(), uint64(1)) } else { - nft.balances.Set(to.String(), toBalance.(uint64)+1) + c.balances.Set(to.String(), toBalance.(uint64)+1) } // Set new ownership - nft.owners.Set(tokenId, to) + c.owners.Set(tokenId, to) } else { // Burn - _, removed := nft.owners.Remove(tokenId) + _, removed := c.owners.Remove(tokenId) if !removed { str := ufmt.Sprintf("GRC721: Cannot burn token with id %s", tokenId) panic(str) @@ -250,11 +245,11 @@ func (nft Token) update(to std.Address, tokenId string, auth std.Address) std.Ad return from } -func (nft Token) approve(to std.Address, tokenId string, auth std.Address, emitEvent bool) { +func (c Collection) approve(to std.Address, tokenId string, auth std.Address, emitEvent bool) { if emitEvent || auth != emptyAddress { - owner := nft.mustBeOwned(tokenId) + owner := c.mustBeOwned(tokenId) - if auth != emptyAddress && owner != auth && !nft.IsApprovedForAll(owner, auth) { + if auth != emptyAddress && owner != auth && !c.IsApprovedForAll(owner, auth) { panic("GRC721: invalid approver") } if emitEvent { @@ -266,11 +261,11 @@ func (nft Token) approve(to std.Address, tokenId string, auth std.Address, emitE } } - nft.tokenApprovals.Set(tokenId, to) + c.tokenApprovals.Set(tokenId, to) } -func (nft Token) ownerOf(tokenId string) std.Address { - owner, exists := nft.owners.Get(tokenId) +func (c Collection) ownerOf(tokenId string) std.Address { + owner, exists := c.owners.Get(tokenId) if !exists { return emptyAddress } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno index 3fc3de92378..3b3f070488f 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno @@ -14,15 +14,15 @@ var ( bob = testutils.TestAddress("bob") ) -func TestNewGRC721Token(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) +func TestNewGRC721Collection(t *testing.T) { + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } } func TestName(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } @@ -33,7 +33,7 @@ func TestName(t *testing.T) { } func TestSymbol(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) symbol := exampleNFT.Symbol() if symbol != exampleNFTSymbol { t.Errorf("expected: (%s), got: (%s)", exampleNFTSymbol, symbol) @@ -41,7 +41,7 @@ func TestSymbol(t *testing.T) { } func TestBalanceOf(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) balanceAddr1 := exampleNFT.BalanceOf(alice) @@ -65,7 +65,7 @@ func TestBalanceOf(t *testing.T) { } func TestOwnerOf(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Mint(bob, "2") @@ -86,7 +86,7 @@ func TestOwnerOf(t *testing.T) { } func TestIsApprovedForAll(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) isApprovedForAll := exampleNFT.IsApprovedForAll(alice, bob) if isApprovedForAll != false { @@ -95,7 +95,7 @@ func TestIsApprovedForAll(t *testing.T) { } func TestSetApprovalForAll(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) caller := std.PrevRealm().Addr() @@ -113,7 +113,7 @@ func TestSetApprovalForAll(t *testing.T) { } func TestApprove(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } @@ -131,7 +131,7 @@ func TestApprove(t *testing.T) { } func TestTransferFrom(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) caller := std.PrevRealm().Addr() exampleNFT.Mint(caller, "1") @@ -159,7 +159,7 @@ func TestTransferFrom(t *testing.T) { } func TestMint(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Mint(alice, "2") @@ -188,7 +188,7 @@ func TestMint(t *testing.T) { } func TestBurn(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Burn("1") @@ -204,7 +204,7 @@ func TestBurn(t *testing.T) { } func TestSetTokenURI(t *testing.T) { - exampleNFT := NewGRC721Token(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) tokenURI := "https://example.com/token" exampleNFT.Mint(alice, "1") From 1525fbb6c85f5b2cf817445aa11af32111c17d40 Mon Sep 17 00:00:00 2001 From: leohhhn Date: Tue, 25 Jun 2024 12:25:12 +0200 Subject: [PATCH 7/8] unify AVL tree --- .../gno.land/p/demo/grc/grc721/grc721.gno | 103 +++++++++++------- .../p/demo/grc/grc721/grc721_test.gno | 50 ++++----- 2 files changed, 89 insertions(+), 64 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721.gno b/examples/gno.land/p/demo/grc/grc721/grc721.gno index 406679468c0..e68aabdf46b 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721.gno @@ -10,28 +10,29 @@ import ( type Collection struct { name string symbol string - owners *avl.Tree // tokenID > std.Address balances *avl.Tree // std.Address > # of owned tokens - tokenApprovals *avl.Tree // tokenID > std.Address operatorApprovals *avl.Tree // "OwnerAddress:OperatorAddress" -> bool - tokenURIs *avl.Tree // tokenID > URI + tokenData *avl.Tree // tokenID > *tokenData +} + +type tokenData struct { + owner std.Address + approved std.Address + uri string } var _ IGRC721 = (*Collection)(nil) const emptyAddress = std.Address("") -func NewGRC721Collection(name, symbol string) *Collection { +func NewCollection(name, symbol string) *Collection { return &Collection{ name: name, symbol: symbol, - owners: avl.NewTree(), balances: avl.NewTree(), - // give an address permission to a specific tokenID - tokenApprovals: avl.NewTree(), // give any addresses permissions for all owners' assets operatorApprovals: avl.NewTree(), - tokenURIs: avl.NewTree(), + tokenData: avl.NewTree(), } } @@ -53,7 +54,7 @@ func (c Collection) OwnerOf(tokenId string) std.Address { return c.mustBeOwned(tokenId) } -func (c Collection) TransferFrom(from, to std.Address, tokenId string) { +func (c *Collection) TransferFrom(from, to std.Address, tokenId string) { caller := std.PrevRealm().Addr() mustBeValid(to) @@ -63,7 +64,7 @@ func (c Collection) TransferFrom(from, to std.Address, tokenId string) { } } -func (c Collection) Approve(to std.Address, tokenId string) { +func (c *Collection) Approve(to std.Address, tokenId string) { caller := std.PrevRealm().Addr() if caller == to { @@ -74,7 +75,7 @@ func (c Collection) Approve(to std.Address, tokenId string) { c.approve(to, tokenId, caller, true) } -func (c Collection) SetApprovalForAll(operator std.Address, approved bool) { +func (c *Collection) SetApprovalForAll(operator std.Address, approved bool) { caller := std.PrevRealm().Addr() mustBeValid(operator) @@ -104,7 +105,7 @@ func (c Collection) GetApproved(tokenId string) std.Address { return c.getApproved(tokenId) } -func (c Collection) IsApprovedForAll(owner, operator std.Address) bool { +func (c *Collection) IsApprovedForAll(owner, operator std.Address) bool { approved, exists := c.operatorApprovals.Get(operatorKey(owner, operator)) if !exists || approved == false { return false @@ -115,20 +116,28 @@ func (c Collection) IsApprovedForAll(owner, operator std.Address) bool { func (c Collection) TokenURI(tokenId string) string { c.mustBeOwned(tokenId) - uri, exists := c.tokenURIs.Get(tokenId) + rawData, _ := c.tokenData.Get(tokenId) + return rawData.(*tokenData).uri +} + +func (c *Collection) SetTokenURI(tokenId string, tokenURI string) string { + rawData, exists := c.tokenData.Get(tokenId) if !exists { - return "" + // Instantiate token data if it does not exist + c.tokenData.Set(tokenId, &tokenData{ + uri: tokenURI, + }) + } else { + // Update URI + data := rawData.(*tokenData) + data.uri = tokenURI + c.tokenData.Set(tokenId, data) } - return uri.(string) -} - -func (c Collection) SetTokenURI(tokenId string, tokenURI string) string { - c.tokenURIs.Set(tokenId, tokenURI) return tokenURI } -func (c Collection) Mint(to std.Address, tokenId string) { +func (c *Collection) Mint(to std.Address, tokenId string) { mustBeValid(to) prevOwner := c.update(to, tokenId, emptyAddress) if prevOwner != emptyAddress { @@ -137,7 +146,7 @@ func (c Collection) Mint(to std.Address, tokenId string) { } } -func (c Collection) Burn(tokenId string) { +func (c *Collection) Burn(tokenId string) { prevOwner := c.update(emptyAddress, tokenId, emptyAddress) if prevOwner == emptyAddress { @@ -154,12 +163,13 @@ func (c Collection) requireOwner(caller std.Address, tokenId string) { } func (c Collection) getApproved(tokenId string) std.Address { - approved, exists := c.tokenApprovals.Get(tokenId) + rawData, exists := c.tokenData.Get(tokenId) if !exists { - return "" // panic instead? + err := ufmt.Sprintf("GRC721: token with ID %s does not exist", tokenId) + panic(err) } - return approved.(std.Address) + return rawData.(*tokenData).approved } // mustBeValid panics if the given address is not valid @@ -173,13 +183,14 @@ func mustBeValid(address std.Address) { // mustBeOwned panics if token is not owned by an address (does not exist) // If the token is owned, mustBeOwned returns the owner of the token func (c Collection) mustBeOwned(tokenId string) std.Address { - owner, exists := c.owners.Get(tokenId) + rawData, exists := c.tokenData.Get(tokenId) + if !exists { err := ufmt.Sprintf("GRC721: token with ID %s does not exist", tokenId) panic(err) } - return owner.(std.Address) + return rawData.(*tokenData).owner } // checkAuthorized checks if spender is authorized to spend specified token on behalf of owner @@ -201,7 +212,7 @@ func (c Collection) isAuthorized(owner, spender std.Address, tokenId string) boo c.getApproved(tokenId) == owner } -func (c Collection) update(to std.Address, tokenId string, auth std.Address) std.Address { +func (c *Collection) update(to std.Address, tokenId string, auth std.Address) std.Address { from := c.ownerOf(tokenId) if auth != emptyAddress { @@ -225,15 +236,23 @@ func (c Collection) update(to std.Address, tokenId string, auth std.Address) std } else { c.balances.Set(to.String(), toBalance.(uint64)+1) } - // Set new ownership - c.owners.Set(tokenId, to) - } else { - // Burn - _, removed := c.owners.Remove(tokenId) - if !removed { - str := ufmt.Sprintf("GRC721: Cannot burn token with id %s", tokenId) - panic(str) + + rawData, initialized := c.tokenData.Get(tokenId) + if !initialized { + // Initialize new tokenData + c.tokenData.Set(tokenId, &tokenData{ + owner: to, + }) + } else { + // If tokenData exists, change only what's needed + data := rawData.(*tokenData) + data.owner = to + c.tokenData.Set(tokenId, data) } + } else { + // Burn case + // Remove token data completely + c.tokenData.Remove(tokenId) } std.Emit("Transfer", @@ -245,13 +264,14 @@ func (c Collection) update(to std.Address, tokenId string, auth std.Address) std return from } -func (c Collection) approve(to std.Address, tokenId string, auth std.Address, emitEvent bool) { +func (c *Collection) approve(to std.Address, tokenId string, auth std.Address, emitEvent bool) { if emitEvent || auth != emptyAddress { owner := c.mustBeOwned(tokenId) if auth != emptyAddress && owner != auth && !c.IsApprovedForAll(owner, auth) { panic("GRC721: invalid approver") } + if emitEvent { std.Emit("Approval", "owner", owner.String(), @@ -261,16 +281,21 @@ func (c Collection) approve(to std.Address, tokenId string, auth std.Address, em } } - c.tokenApprovals.Set(tokenId, to) + // No need for exist check, done by mustBeOwned already + rawData, _ := c.tokenData.Get(tokenId) + data := rawData.(*tokenData) + data.approved = to + + c.tokenData.Set(tokenId, data) } func (c Collection) ownerOf(tokenId string) std.Address { - owner, exists := c.owners.Get(tokenId) + data, exists := c.tokenData.Get(tokenId) if !exists { return emptyAddress } - return owner.(std.Address) + return data.(*tokenData).owner } // operatorKey is a helper to create the key for the operatorApproval tree diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno index 3b3f070488f..82f4d08e1cf 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_test.gno @@ -15,14 +15,14 @@ var ( ) func TestNewGRC721Collection(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } } func TestName(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } @@ -33,7 +33,7 @@ func TestName(t *testing.T) { } func TestSymbol(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) symbol := exampleNFT.Symbol() if symbol != exampleNFTSymbol { t.Errorf("expected: (%s), got: (%s)", exampleNFTSymbol, symbol) @@ -41,31 +41,31 @@ func TestSymbol(t *testing.T) { } func TestBalanceOf(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) balanceAddr1 := exampleNFT.BalanceOf(alice) if balanceAddr1 != 0 { t.Errorf("expected: (%d), got: (%d)", 0, balanceAddr1) } - + // exampleNFT.Mint(alice, "1") - exampleNFT.Mint(alice, "2") - exampleNFT.Mint(bob, "3") - - balanceAddr1 = exampleNFT.BalanceOf(alice) - balanceAddr2 := exampleNFT.BalanceOf(bob) - - if balanceAddr1 != 2 { - t.Errorf("expected: (%d), got: (%d)", 2, balanceAddr1) - } - if balanceAddr2 != 1 { - t.Errorf("expected: (%d), got: (%d)", 1, balanceAddr2) - } + //exampleNFT.Mint(alice, "2") + //exampleNFT.Mint(bob, "3") + + //balanceAddr1 = exampleNFT.BalanceOf(alice) + //balanceAddr2 := exampleNFT.BalanceOf(bob) + + //if balanceAddr1 != 2 { + // t.Errorf("expected: (%d), got: (%d)", 2, balanceAddr1) + //} + //if balanceAddr2 != 1 { + // t.Errorf("expected: (%d), got: (%d)", 1, balanceAddr2) + //} } func TestOwnerOf(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Mint(bob, "2") @@ -86,7 +86,7 @@ func TestOwnerOf(t *testing.T) { } func TestIsApprovedForAll(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) isApprovedForAll := exampleNFT.IsApprovedForAll(alice, bob) if isApprovedForAll != false { @@ -95,7 +95,7 @@ func TestIsApprovedForAll(t *testing.T) { } func TestSetApprovalForAll(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) caller := std.PrevRealm().Addr() @@ -113,7 +113,7 @@ func TestSetApprovalForAll(t *testing.T) { } func TestApprove(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) if exampleNFT == nil { t.Errorf("should not be nil") } @@ -131,7 +131,7 @@ func TestApprove(t *testing.T) { } func TestTransferFrom(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) caller := std.PrevRealm().Addr() exampleNFT.Mint(caller, "1") @@ -159,7 +159,7 @@ func TestTransferFrom(t *testing.T) { } func TestMint(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Mint(alice, "2") @@ -188,7 +188,7 @@ func TestMint(t *testing.T) { } func TestBurn(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) exampleNFT.Mint(alice, "1") exampleNFT.Burn("1") @@ -204,7 +204,7 @@ func TestBurn(t *testing.T) { } func TestSetTokenURI(t *testing.T) { - exampleNFT := NewGRC721Collection(exampleNFTName, exampleNFTSymbol) + exampleNFT := NewCollection(exampleNFTName, exampleNFTSymbol) tokenURI := "https://example.com/token" exampleNFT.Mint(alice, "1") From 15b7b71af0fa1ebf14132332c28240b4740896f1 Mon Sep 17 00:00:00 2001 From: leohhhn Date: Tue, 25 Jun 2024 12:54:02 +0200 Subject: [PATCH 8/8] add basetokenURI --- examples/gno.land/p/demo/grc/grc721/grc721.gno | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721.gno b/examples/gno.land/p/demo/grc/grc721/grc721.gno index e68aabdf46b..7dcbce7e7d6 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721.gno @@ -13,6 +13,7 @@ type Collection struct { balances *avl.Tree // std.Address > # of owned tokens operatorApprovals *avl.Tree // "OwnerAddress:OperatorAddress" -> bool tokenData *avl.Tree // tokenID > *tokenData + baseURI string // baseURI } type tokenData struct { @@ -114,12 +115,14 @@ func (c *Collection) IsApprovedForAll(owner, operator std.Address) bool { return true } +// TokenURI returns the URI of a specific token, prepended with the baseURI for the collection func (c Collection) TokenURI(tokenId string) string { c.mustBeOwned(tokenId) rawData, _ := c.tokenData.Get(tokenId) - return rawData.(*tokenData).uri + return c.baseURI + rawData.(*tokenData).uri } +// SetTokenURI sets a URI for a specific token func (c *Collection) SetTokenURI(tokenId string, tokenURI string) string { rawData, exists := c.tokenData.Get(tokenId) if !exists {