From 66366cd17d36166e2e221d7c626ceca3a108ec80 Mon Sep 17 00:00:00 2001 From: irreverentsimplicity <134286683+irreverentsimplicity@users.noreply.github.com> Date: Thu, 21 Dec 2023 10:44:06 +0000 Subject: [PATCH] feat(p/grc721): add SetTokenURI to `IGRC721` (#1309) The current grc721 implementation lacks the ability to set the tokenURI metadata for an NFT. There are placheolders inside the struct, but the actual setter was not written. This PR add the following functionality in the `grc` folder inside the `p` folder: - new type grc721 interface for `TokenURI` on top of the existing `TokenID` - new error type in `errors.gno` - SetTokenURI() implementation in `basic_nft.gno` - new test added to `basic_nft_test.gno` AFAIK there are no breaking changes in this PR, tests are passing locally. Let me know any issues. --------- Co-authored-by: Morgan --- .../gno.land/p/demo/grc/grc721/basic_nft.gno | 19 ++++++++ .../p/demo/grc/grc721/basic_nft_test.gno | 45 +++++++++++++++++++ examples/gno.land/p/demo/grc/grc721/gno.mod | 1 + .../gno.land/p/demo/grc/grc721/igrc721.gno | 6 ++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno index b707527c6a4..bec7338db42 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft.gno @@ -69,6 +69,25 @@ func (s *basicNFT) TokenURI(tid TokenID) (string, error) { 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 { 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 index 7ad378af2f7..925f1fca44b 100644 --- a/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/basic_nft_test.gno @@ -4,6 +4,7 @@ import ( "std" "testing" + "gno.land/p/demo/testutils" "gno.land/r/demo/users" ) @@ -367,3 +368,47 @@ func TestBurn(t *testing.T) { 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 := users.AddressOrName("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := users.AddressOrName("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + tokenURI := "http://example.com/token" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1.Resolve(), 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/gno.mod b/examples/gno.land/p/demo/grc/grc721/gno.mod index ea8c9c9e52e..952e0cb8ee4 100644 --- a/examples/gno.land/p/demo/grc/grc721/gno.mod +++ b/examples/gno.land/p/demo/grc/grc721/gno.mod @@ -2,6 +2,7 @@ module gno.land/p/demo/grc/grc721 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/r/demo/users v0.0.0-latest ) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721.gno b/examples/gno.land/p/demo/grc/grc721/igrc721.gno index d60308e11a1..387547a7e26 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721.gno @@ -5,6 +5,7 @@ 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 @@ -13,7 +14,10 @@ type IGRC721 interface { IsApprovedForAll(owner, operator std.Address) bool } -type TokenID string +type ( + TokenID string + TokenURI string +) type TransferEvent struct { From std.Address