From d943b63f986394fdb7d1c069347000c3263eb6ce Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sat, 20 Apr 2024 20:02:38 +0700 Subject: [PATCH 01/19] add royalty info for grc721 realm --- .../p/demo/grc/grc721/igrc721_royalty.gno | 17 ++++ .../p/demo/grc/grc721/nft_royalty.gno | 68 +++++++++++++ .../p/demo/grc/grc721/nft_royalty_test.gno | 97 +++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno create mode 100644 examples/gno.land/p/demo/grc/grc721/nft_royalty.gno create mode 100644 examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno new file mode 100644 index 00000000000..671c61a9a76 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -0,0 +1,17 @@ +package grc721 + +import ( + "std" +) + +type IGRC721Royalty interface { + IGRC721Metadata + + SetTokenRoyalty(tokenID TokenID, royaltyInfo RoyaltyInfo) (bool, error) + RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) +} + +type RoyaltyInfo struct { + PaymentAddress std.Address + Percentage uint64 +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno new file mode 100644 index 00000000000..abe23108e04 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno @@ -0,0 +1,68 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + + +type royaltyNFT struct { + *metadataNFT + + tokenRoyaltyInfo avl.Tree +} + + +var _ IGRC721Royalty = (*royaltyNFT)(nil) + + +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { + + nft := NewNFTWithMetadata(name, symbol) + + return &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.Tree{}, + } +} + +// SetTokenRoyalty sets the royalty information for a specific token ID +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) (bool, error) { + if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { + return false, ErrInvalidRoyaltyPaymentAddress + } + + // RoyaltyAmount will exceed the sale price + if royaltyInfo.Percentage > 100 { + return false, ErrInvalidRoyaltyPercentage + } + + // Check for the right owner + owner, err := r.metadataNFT.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + return true, 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) { + val, found := r.tokenRoyaltyInfo.Get(string(tid)) + if !found { + return "", 0, ErrInvalidTokenId + } + + royaltyInfo := val.(RoyaltyInfo) + + royaltyAmount := (salePrice * royaltyInfo.Percentage) / 100 + + return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno new file mode 100644 index 00000000000..58a0f626259 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno @@ -0,0 +1,97 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/users" + "gno.land/p/demo/ufmt" +) + +func TestSetTokenRoyalty(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + paymentAddress := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100) + + std.TestSetOrigCaller(std.Address(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 ", derr.Error()) + } + + // Test case: Invalid token ID + _, err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + if err != ErrInvalidTokenId { + t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + } + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenRoyalty(TokenID("1"),RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + if cerr != ErrCallerIsNotOwner { + t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) + } + + // Test case: Invalid payment address + _, aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), + Percentage: percentage, + }) + if aerr != ErrInvalidRoyaltyPaymentAddress { + t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPaymentAddress, aerr) + } + + + // Test case: Invalid payment address + _, perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: uint64(10), + }) + + if perr != ErrInvalidRoyaltyPercentage { + // t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPercentage, perr) + } + + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + if rerr != nil { + t.Errorf("RoyaltyInfo error: %v", rerr.Error()) + + } else { + if dummyPaymentAddress != paymentAddress { + t.Errorf("Expected RoyaltyPaymentAddress %v, got %v", paymentAddress, dummyPaymentAddress) + } + + if dummyRoyaltyAmount != expectRoyaltyAmount { + t.Errorf("Expected RoyaltyAmount %v, got %v", expectRoyaltyAmount, dummyRoyaltyAmount) + } + } +} + From 297564eff8e2698c3f9b87a688eb1320fdbb1e1d Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Sat, 20 Apr 2024 20:04:23 +0700 Subject: [PATCH 02/19] chore(examlple): add royaltyInfo on the top of grc721Metadata --- .../gno.land/p/demo/grc/grc721/errors.gno | 4 + .../p/demo/grc/grc721/nft_metadata.gno | 85 ++++++++++++ .../p/demo/grc/grc721/nft_metadata_test.gno | 130 ++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 examples/gno.land/p/demo/grc/grc721/nft_metadata.gno create mode 100644 examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno diff --git a/examples/gno.land/p/demo/grc/grc721/errors.gno b/examples/gno.land/p/demo/grc/grc721/errors.gno index 08fb26b0cb5..3d7aff79873 100644 --- a/examples/gno.land/p/demo/grc/grc721/errors.gno +++ b/examples/gno.land/p/demo/grc/grc721/errors.gno @@ -14,4 +14,8 @@ var ( 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") ) diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno new file mode 100644 index 00000000000..62eb411c9e6 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno @@ -0,0 +1,85 @@ +package grc721 + +import ( + "std" + + "gno.land/p/demo/avl" +) + +// metadataNFT represents an NFT with metadata extensions. +type metadataNFT struct { + *basicNFT + + extensions avl.Tree +} + +// Ensure that metadataNFT implements the IGRC721Metadata interface. +var _ IGRC721Metadata = (*metadataNFT)(nil) + +// NewNFTWithMetadata creates a new basic NFT with metadata extensions. +func NewNFTWithMetadata(name string, symbol string) *metadataNFT { + + nft := NewBasicNFT(name, symbol) + + return &metadataNFT{ + basicNFT: nft, + extensions: avl.Tree{}, + } +} + +// SetExtension sets the metadata extension for the given token ID. +func (s *metadataNFT) SetExtension(tid TokenID, extension Metadata) (bool, error) { + // Check for the right owner + owner, err := s.basicNFT.OwnerOf(tid) + if err != nil { + return false, err + } + caller := std.PrevRealm().Addr() + if caller != owner { + return false, ErrCallerIsNotOwner + } + s.extensions.Set(string(tid), extension) + return true, nil +} + +// Extension retrieves the metadata extension for the given token ID. +func (s *metadataNFT) Extension(tid TokenID) (Metadata, error) { + extension, found := s.extensions.Get(string(tid)) + if !found { + return Metadata{}, ErrInvalidTokenId + } + + return extension.(Metadata), nil +} + +func (s *metadataNFT) mint(to std.Address, tid TokenID) error { + if err := isValidAddress(to); err != nil { + return err + } + + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) + + // Check that tokenId was not minted by `beforeTokenTransfer` + if s.basicNFT.exists(tid) { + return ErrTokenIdAlreadyExists + } + + toBalance, err := s.basicNFT.BalanceOf(to) + if err != nil { + return err + } + toBalance += 1 + s.basicNFT.balances.Set(to.String(), toBalance) + s.basicNFT.owners.Set(string(tid), to) + + event := TransferEvent{zeroAddress, to, tid} + emit(&event) + + s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) + + return nil +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno new file mode 100644 index 00000000000..f7fe881dd73 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno @@ -0,0 +1,130 @@ +package grc721 + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + "gno.land/p/demo/users" +) + +func TestSetExtension(t *testing.T) { + dummy := NewNFTWithMetadata(dummyNFTName, dummyNFTSymbol) + if dummy == nil { + t.Errorf("should not be nil") + } + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + name := "test" + description := "test" + image := "test" + imageData := "test" + externalURL := "test" + attributes := []Trait{} + backgroundColor := "test" + animationURL := "test" + youtubeURL := "test" + + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummy.mint(addr1, TokenID("1")) + + _, derr := dummy.SetExtension(TokenID("1"), Metadata{ + Name : name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + if derr != nil { + t.Errorf("Should not result in error ", derr.Error()) + } + + // Test case: Invalid token ID + _, err := dummy.SetExtension(TokenID("3"), Metadata{ + Name : name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + if err != ErrInvalidTokenId { + t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + } + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetExtension(TokenID("1"), Metadata{ + Name : name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, + BackgroundColor: backgroundColor, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) // addr2 trying to set Extension for token 1 + if cerr != ErrCallerIsNotOwner { + t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) + } + + // Test case: Retrieving Extension + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyExtension, err := dummy.Extension(TokenID("1")) + if err != nil { + t.Errorf("Extension error: %v", err.Error()) + + } else { + if dummyExtension.Image != image { + t.Errorf("Expected Extension's image %v, got %v", image, dummyExtension.Image) + } + + if dummyExtension.ImageData != imageData { + t.Errorf("Expected Extension's imageData %v, got %v", imageData, dummyExtension.ImageData) + } + + if dummyExtension.ExternalURL != externalURL { + t.Errorf("Expected Extension's externalURL %v, got %v", externalURL, dummyExtension.ExternalURL) + } + + if dummyExtension.Description != description { + t.Errorf("Expected Extension's description %v, got %v", description, dummyExtension.Description) + } + + if dummyExtension.Name != name { + t.Errorf("Expected Extension's name %v, got %v", name, dummyExtension.Name) + } + + if len(dummyExtension.Attributes) != len(attributes) { + t.Errorf("Expected %d Extension's attributes %v, got %v", len(attributes), len(dummyExtension.Attributes)) + } + + if dummyExtension.BackgroundColor != backgroundColor { + t.Errorf("Expected Extension's backgroundColor %v, got %v", backgroundColor, dummyExtension.BackgroundColor) + } + + if dummyExtension.AnimationURL != animationURL { + t.Errorf("Expected Extension's animationURL %v, got %v", animationURL, dummyExtension.AnimationURL) + } + + if dummyExtension.YoutubeURL != youtubeURL { + t.Errorf("Expected Extension's youtubeURL %v, got %v", youtubeURL, dummyExtension.YoutubeURL) + } + } + + +} + From 33f421a67d6845e54953fa364482c3b3e790a254 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 22 Apr 2024 11:29:53 +0700 Subject: [PATCH 03/19] define RoyaltyCalculator to support clients to customize the calculation of royaltyAmount --- .../gno.land/p/demo/grc/grc721/errors.gno | 1 + .../p/demo/grc/grc721/igrc721_metadata.gno | 27 ++++++ .../p/demo/grc/grc721/igrc721_royalty.gno | 5 ++ .../p/demo/grc/grc721/nft_royalty.gno | 67 +++++++++++---- .../p/demo/grc/grc721/nft_royalty_test.gno | 85 +++++++++++++++++++ 5 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno diff --git a/examples/gno.land/p/demo/grc/grc721/errors.gno b/examples/gno.land/p/demo/grc/grc721/errors.gno index 3d7aff79873..b65571578fc 100644 --- a/examples/gno.land/p/demo/grc/grc721/errors.gno +++ b/examples/gno.land/p/demo/grc/grc721/errors.gno @@ -18,4 +18,5 @@ var ( // 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/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno new file mode 100644 index 00000000000..a6deb53a7d6 --- /dev/null +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -0,0 +1,27 @@ + +package grc721 + +type IGRC721Metadata interface { + IGRC721 + + SetExtension(tid TokenID, extension Metadata) error + Extension(tid TokenID) (Metadata, error) +} + +type Trait struct{ + DisplayType string + TraitType string + Value string +} + +type Metadata struct { + Image string + ImageData string + ExternalURL string + Description string + Name string + Attributes []Trait + BackgroundColor string + AnimationURL string + YoutubeURL string +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index 671c61a9a76..5f267400b2d 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -11,6 +11,11 @@ type IGRC721Royalty interface { RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) } +// royaltyCalculator is an interface for calculating royalty amounts. +type RoyaltyCalculator interface { + RoyaltyAmount(salePrice, percentage uint64) (uint64, error) +} + type RoyaltyInfo struct { PaymentAddress std.Address Percentage uint64 diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno index abe23108e04..ef621555845 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno @@ -6,39 +6,65 @@ import ( "gno.land/p/demo/avl" ) - +// royaltyNFT represents a non-fungible token (NFT) with royalty functionality. type royaltyNFT struct { - *metadataNFT - - tokenRoyaltyInfo avl.Tree + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo avl.Tree // AVL tree to store royalty information for each token + calculator RoyaltyCalculator // Interface for calculating royalty amount } +type royaltyCalculator struct {} + +func NewDefaultRoyaltyCalculator() *royaltyCalculator { + return &royaltyCalculator{} +} -var _ IGRC721Royalty = (*royaltyNFT)(nil) +func (r *royaltyCalculator) RoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil +} +type Option func(*royaltyNFT) -func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { +func WithCalculator(c RoyaltyCalculator) Option { + return func(r *royaltyNFT) { + r.calculator = c + } +} +// NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. +func NewNFTWithRoyalty(name string, symbol string, opts ...Option) *royaltyNFT { + // Create a new NFT with metadata nft := NewNFTWithMetadata(name, symbol) - return &royaltyNFT{ - metadataNFT: nft, - tokenRoyaltyInfo: avl.Tree{}, + r := &royaltyNFT{ + metadataNFT: nft, + tokenRoyaltyInfo: avl.Tree{}, + calculator: NewDefaultRoyaltyCalculator(), + } + + for _, opt := range opts { + if opt != nil { + opt(r) + } } + + return r } -// SetTokenRoyalty sets the royalty information for a specific token ID +// SetTokenRoyalty sets the royalty information for a specific token ID. func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) (bool, error) { + // Validate the payment address if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { - return false, ErrInvalidRoyaltyPaymentAddress + return false, ErrInvalidRoyaltyPaymentAddress } - // RoyaltyAmount will exceed the sale price + // Check if royalty percentage exceeds 100% if royaltyInfo.Percentage > 100 { return false, ErrInvalidRoyaltyPercentage } - // Check for the right owner + // Check if the caller is the owner of the token owner, err := r.metadataNFT.OwnerOf(tid) if err != nil { return false, err @@ -47,13 +73,15 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) (bool if caller != owner { return false, ErrCallerIsNotOwner } + + // Set royalty information for the token r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) return true, nil } - -// RoyaltyInfo returns the royalty information for the given token ID and sale price +// 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 @@ -61,8 +89,11 @@ func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, ui royaltyInfo := val.(RoyaltyInfo) - royaltyAmount := (salePrice * royaltyInfo.Percentage) / 100 + // Calculate royalty amount using the provided calculator + royaltyAmount, err := r.calculator.RoyaltyAmount(salePrice, royaltyInfo.Percentage) + if err != nil { + return "", 0, ErrCannotCalculateRoyaltyAmount + } return royaltyInfo.PaymentAddress, royaltyAmount, nil -} - +} \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno index 58a0f626259..fd1bf0a4dd6 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno @@ -95,3 +95,88 @@ func TestSetTokenRoyalty(t *testing.T) { } } +type customRoyaltyCalculator struct {} + +func NewCustomRoyaltyCalculator() *customRoyaltyCalculator { + return &customRoyaltyCalculator{} +} + +func (r *customRoyaltyCalculator) RoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + 10 + return royaltyAmount, nil +} + +func TestSetTokenRoyaltyWithCustomCalculator(t *testing.T) { + dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol, WithCalculator(NewCustomRoyaltyCalculator())) + if dummy == nil { + t.Errorf("should not be nil") + } + + addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + + paymentAddress := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + percentage := uint64(10) // 10% + + salePrice := uint64(1000) + expectRoyaltyAmount := uint64(100 + 10) // 10% of 1000 + 10 + + std.TestSetOrigCaller(std.Address(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 ", derr.Error()) + } + + // Test case: Invalid token ID + _, err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + if err != ErrInvalidTokenId { + t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + } + + std.TestSetOrigCaller(std.Address(addr2)) // addr2 + + _, cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + PaymentAddress: paymentAddress, + Percentage: percentage, + }) + if cerr != ErrCallerIsNotOwner { + t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) + } + + // Test case: Invalid payment address + _, aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + PaymentAddress: std.Address("###"), + Percentage: percentage, + }) + if aerr != ErrInvalidRoyaltyPaymentAddress { + t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPaymentAddress, aerr) + } + + // Test case: Retrieving Royalty Info + std.TestSetOrigCaller(std.Address(addr1)) // addr1 + + dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) + if rerr != nil { + t.Errorf("RoyaltyInfo error: %v", rerr.Error()) + + } else { + if dummyPaymentAddress != paymentAddress { + t.Errorf("Expected RoyaltyPaymentAddress %v, got %v", paymentAddress, dummyPaymentAddress) + } + + if dummyRoyaltyAmount != expectRoyaltyAmount { + t.Errorf("Expected RoyaltyAmount %v, got %v", expectRoyaltyAmount, dummyRoyaltyAmount) + } + } +} + From 3acadc11d1c4bf4d1d33597eaa1c1e36899ab133 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 22 Apr 2024 20:19:10 +0700 Subject: [PATCH 04/19] redefine GRC721Royalty interface --- .../p/demo/grc/grc721/igrc721_royalty.gno | 8 -- .../p/demo/grc/grc721/nft_metadata_test.gno | 18 ++-- .../p/demo/grc/grc721/nft_royalty.gno | 39 ++------ .../p/demo/grc/grc721/nft_royalty_test.gno | 99 ++----------------- 4 files changed, 25 insertions(+), 139 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index 5f267400b2d..4f7d89fe81c 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -5,17 +5,9 @@ import ( ) type IGRC721Royalty interface { - IGRC721Metadata - - SetTokenRoyalty(tokenID TokenID, royaltyInfo RoyaltyInfo) (bool, error) RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) } -// royaltyCalculator is an interface for calculating royalty amounts. -type RoyaltyCalculator interface { - RoyaltyAmount(salePrice, percentage uint64) (uint64, error) -} - type RoyaltyInfo struct { PaymentAddress std.Address Percentage uint64 diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno index f7fe881dd73..296d700f37f 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno @@ -89,39 +89,39 @@ func TestSetExtension(t *testing.T) { } else { if dummyExtension.Image != image { - t.Errorf("Expected Extension's image %v, got %v", image, dummyExtension.Image) + t.Errorf("Expected Extension's image %s, got %s", image, dummyExtension.Image) } if dummyExtension.ImageData != imageData { - t.Errorf("Expected Extension's imageData %v, got %v", imageData, dummyExtension.ImageData) + t.Errorf("Expected Extension's imageData %s, got %s", imageData, dummyExtension.ImageData) } if dummyExtension.ExternalURL != externalURL { - t.Errorf("Expected Extension's externalURL %v, got %v", externalURL, dummyExtension.ExternalURL) + t.Errorf("Expected Extension's externalURL %s, got %s", externalURL, dummyExtension.ExternalURL) } if dummyExtension.Description != description { - t.Errorf("Expected Extension's description %v, got %v", description, dummyExtension.Description) + t.Errorf("Expected Extension's description %s, got %s", description, dummyExtension.Description) } if dummyExtension.Name != name { - t.Errorf("Expected Extension's name %v, got %v", name, dummyExtension.Name) + t.Errorf("Expected Extension's name %s, got %s", name, dummyExtension.Name) } if len(dummyExtension.Attributes) != len(attributes) { - t.Errorf("Expected %d Extension's attributes %v, got %v", len(attributes), len(dummyExtension.Attributes)) + t.Errorf("Expected %d Extension's attributes, got %d", len(attributes), len(dummyExtension.Attributes)) } if dummyExtension.BackgroundColor != backgroundColor { - t.Errorf("Expected Extension's backgroundColor %v, got %v", backgroundColor, dummyExtension.BackgroundColor) + t.Errorf("Expected Extension's backgroundColor %s, got %s", backgroundColor, dummyExtension.BackgroundColor) } if dummyExtension.AnimationURL != animationURL { - t.Errorf("Expected Extension's animationURL %v, got %v", animationURL, dummyExtension.AnimationURL) + t.Errorf("Expected Extension's animationURL %s, got %s", animationURL, dummyExtension.AnimationURL) } if dummyExtension.YoutubeURL != youtubeURL { - t.Errorf("Expected Extension's youtubeURL %v, got %v", youtubeURL, dummyExtension.YoutubeURL) + t.Errorf("Expected Extension's youtubeURL %s, got %s", youtubeURL, dummyExtension.YoutubeURL) } } diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno index ef621555845..316a057983d 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno @@ -10,46 +10,18 @@ import ( type royaltyNFT struct { *metadataNFT // Embedding metadataNFT for NFT functionality tokenRoyaltyInfo avl.Tree // AVL tree to store royalty information for each token - calculator RoyaltyCalculator // Interface for calculating royalty amount } -type royaltyCalculator struct {} - -func NewDefaultRoyaltyCalculator() *royaltyCalculator { - return &royaltyCalculator{} -} - -func (r *royaltyCalculator) RoyaltyAmount(salePrice, percentage uint64) (uint64, error) { - royaltyAmount := (salePrice * percentage) / 100 - return royaltyAmount, nil -} - -type Option func(*royaltyNFT) - -func WithCalculator(c RoyaltyCalculator) Option { - return func(r *royaltyNFT) { - r.calculator = c - } -} // NewNFTWithRoyalty creates a new royalty NFT with the specified name, symbol, and royalty calculator. -func NewNFTWithRoyalty(name string, symbol string, opts ...Option) *royaltyNFT { +func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { // Create a new NFT with metadata nft := NewNFTWithMetadata(name, symbol) - r := &royaltyNFT{ + return &royaltyNFT{ metadataNFT: nft, tokenRoyaltyInfo: avl.Tree{}, - calculator: NewDefaultRoyaltyCalculator(), - } - - for _, opt := range opts { - if opt != nil { - opt(r) - } } - - return r } // SetTokenRoyalty sets the royalty information for a specific token ID. @@ -90,10 +62,15 @@ func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, ui royaltyInfo := val.(RoyaltyInfo) // Calculate royalty amount using the provided calculator - royaltyAmount, err := r.calculator.RoyaltyAmount(salePrice, royaltyInfo.Percentage) + royaltyAmount, err := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) if err != nil { return "", 0, ErrCannotCalculateRoyaltyAmount } return royaltyInfo.PaymentAddress, royaltyAmount, nil +} + +func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { + royaltyAmount := (salePrice * percentage) / 100 + return royaltyAmount, nil } \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno index fd1bf0a4dd6..49d812b3f4a 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno @@ -34,7 +34,7 @@ func TestSetTokenRoyalty(t *testing.T) { }) if derr != nil { - t.Errorf("Should not result in error ", derr.Error()) + t.Errorf("Should not result in error : %s", derr.Error()) } // Test case: Invalid token ID @@ -43,7 +43,7 @@ func TestSetTokenRoyalty(t *testing.T) { Percentage: percentage, }) if err != ErrInvalidTokenId { - t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) } std.TestSetOrigCaller(std.Address(addr2)) // addr2 @@ -53,7 +53,7 @@ func TestSetTokenRoyalty(t *testing.T) { Percentage: percentage, }) if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) + t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) } // Test case: Invalid payment address @@ -62,7 +62,7 @@ func TestSetTokenRoyalty(t *testing.T) { Percentage: percentage, }) if aerr != ErrInvalidRoyaltyPaymentAddress { - t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPaymentAddress, aerr) + t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPaymentAddress, aerr) } @@ -73,7 +73,7 @@ func TestSetTokenRoyalty(t *testing.T) { }) if perr != ErrInvalidRoyaltyPercentage { - // t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPercentage, perr) + // t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) } @@ -82,101 +82,18 @@ func TestSetTokenRoyalty(t *testing.T) { dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) if rerr != nil { - t.Errorf("RoyaltyInfo error: %v", rerr.Error()) + t.Errorf("RoyaltyInfo error: %s", rerr.Error()) } else { if dummyPaymentAddress != paymentAddress { - t.Errorf("Expected RoyaltyPaymentAddress %v, got %v", paymentAddress, dummyPaymentAddress) + t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) } if dummyRoyaltyAmount != expectRoyaltyAmount { - t.Errorf("Expected RoyaltyAmount %v, got %v", expectRoyaltyAmount, dummyRoyaltyAmount) + t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) } } } -type customRoyaltyCalculator struct {} -func NewCustomRoyaltyCalculator() *customRoyaltyCalculator { - return &customRoyaltyCalculator{} -} - -func (r *customRoyaltyCalculator) RoyaltyAmount(salePrice, percentage uint64) (uint64, error) { - royaltyAmount := (salePrice * percentage) / 100 + 10 - return royaltyAmount, nil -} - -func TestSetTokenRoyaltyWithCustomCalculator(t *testing.T) { - dummy := NewNFTWithRoyalty(dummyNFTName, dummyNFTSymbol, WithCalculator(NewCustomRoyaltyCalculator())) - if dummy == nil { - t.Errorf("should not be nil") - } - - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") - - paymentAddress := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - percentage := uint64(10) // 10% - - salePrice := uint64(1000) - expectRoyaltyAmount := uint64(100 + 10) // 10% of 1000 + 10 - - std.TestSetOrigCaller(std.Address(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 ", derr.Error()) - } - - // Test case: Invalid token ID - _, err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: percentage, - }) - if err != ErrInvalidTokenId { - t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) - } - - std.TestSetOrigCaller(std.Address(addr2)) // addr2 - - _, cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ - PaymentAddress: paymentAddress, - Percentage: percentage, - }) - if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) - } - - // Test case: Invalid payment address - _, aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ - PaymentAddress: std.Address("###"), - Percentage: percentage, - }) - if aerr != ErrInvalidRoyaltyPaymentAddress { - t.Errorf("Expected error %v, got %v", ErrInvalidRoyaltyPaymentAddress, aerr) - } - - // Test case: Retrieving Royalty Info - std.TestSetOrigCaller(std.Address(addr1)) // addr1 - - dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) - if rerr != nil { - t.Errorf("RoyaltyInfo error: %v", rerr.Error()) - - } else { - if dummyPaymentAddress != paymentAddress { - t.Errorf("Expected RoyaltyPaymentAddress %v, got %v", paymentAddress, dummyPaymentAddress) - } - - if dummyRoyaltyAmount != expectRoyaltyAmount { - t.Errorf("Expected RoyaltyAmount %v, got %v", expectRoyaltyAmount, dummyRoyaltyAmount) - } - } -} From 9eb715eb4330ee763acae4d0bfb0185c4d1b458a Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 22 Apr 2024 22:55:22 +0700 Subject: [PATCH 05/19] redefine IGRC721Metadata interface --- .../p/demo/grc/grc721/igrc721_metadata.gno | 5 +- .../p/demo/grc/grc721/nft_metadata.gno | 37 +++-- .../p/demo/grc/grc721/nft_metadata_test.gno | 126 +++++++++--------- 3 files changed, 91 insertions(+), 77 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno index a6deb53a7d6..d43cb7e952f 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -2,10 +2,7 @@ package grc721 type IGRC721Metadata interface { - IGRC721 - - SetExtension(tid TokenID, extension Metadata) error - Extension(tid TokenID) (Metadata, error) + TokenMetadata(tid TokenID) (Metadata, error) } type Trait struct{ diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno index 62eb411c9e6..66af2e13048 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno @@ -8,9 +8,8 @@ import ( // metadataNFT represents an NFT with metadata extensions. type metadataNFT struct { - *basicNFT - - extensions avl.Tree + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions avl.Tree // AVL tree for storing metadata extensions } // Ensure that metadataNFT implements the IGRC721Metadata interface. @@ -19,17 +18,19 @@ var _ IGRC721Metadata = (*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.Tree{}, } } -// SetExtension sets the metadata extension for the given token ID. -func (s *metadataNFT) SetExtension(tid TokenID, extension Metadata) (bool, error) { - // Check for the right owner +// SetTokenMetadata sets metadata for a given token ID. +func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) (bool, error) { + // Check if the caller is the owner of the token owner, err := s.basicNFT.OwnerOf(tid) if err != nil { return false, err @@ -38,47 +39,59 @@ func (s *metadataNFT) SetExtension(tid TokenID, extension Metadata) (bool, error if caller != owner { return false, ErrCallerIsNotOwner } - s.extensions.Set(string(tid), extension) + + // Set the metadata for the token ID in the extensions AVL tree + s.extensions.Set(string(tid), metadata) return true, nil } -// Extension retrieves the metadata extension for the given token ID. -func (s *metadataNFT) Extension(tid TokenID) (Metadata, error) { - extension, found := s.extensions.Get(string(tid)) +// 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 extension.(Metadata), nil + 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 that tokenId was not minted by `beforeTokenTransfer` + // 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/nft_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno index 296d700f37f..69b8d049bd8 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno @@ -8,15 +8,18 @@ import ( "gno.land/p/demo/users" ) -func TestSetExtension(t *testing.T) { +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 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + // Define metadata attributes name := "test" description := "test" image := "test" @@ -27,104 +30,105 @@ func TestSetExtension(t *testing.T) { animationURL := "test" youtubeURL := "test" + // Set the original caller to addr1 std.TestSetOrigCaller(std.Address(addr1)) // addr1 + // Mint a new token for addr1 dummy.mint(addr1, TokenID("1")) - _, derr := dummy.SetExtension(TokenID("1"), Metadata{ - Name : name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, + // 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, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, }) + // Check if there was an error setting metadata if derr != nil { t.Errorf("Should not result in error ", derr.Error()) } // Test case: Invalid token ID - _, err := dummy.SetExtension(TokenID("3"), Metadata{ - Name : name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, + _, err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, }) + + // Check if the error returned matches the expected error if err != ErrInvalidTokenId { t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) } + // Set the original caller to addr2 std.TestSetOrigCaller(std.Address(addr2)) // addr2 - _, cerr := dummy.SetExtension(TokenID("1"), Metadata{ - Name : name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, + // 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, - }) // addr2 trying to set Extension for token 1 + AnimationURL: animationURL, + YoutubeURL: youtubeURL, + }) + + // Check if the error returned matches the expected error if cerr != ErrCallerIsNotOwner { t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) } - // Test case: Retrieving Extension + // Set the original caller back to addr1 std.TestSetOrigCaller(std.Address(addr1)) // addr1 - dummyExtension, err := dummy.Extension(TokenID("1")) + // Retrieve metadata for token 1 + dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) if err != nil { - t.Errorf("Extension error: %v", err.Error()) - + t.Errorf("Metadata error: %v", err.Error()) } else { - if dummyExtension.Image != image { - t.Errorf("Expected Extension's image %s, got %s", image, dummyExtension.Image) + // Check if metadata attributes match expected values + if dummyMetadata.Image != image { + t.Errorf("Expected Metadata's image %s, got %s", image, dummyMetadata.Image) } - - if dummyExtension.ImageData != imageData { - t.Errorf("Expected Extension's imageData %s, got %s", imageData, dummyExtension.ImageData) + if dummyMetadata.ImageData != imageData { + t.Errorf("Expected Metadata's imageData %s, got %s", imageData, dummyMetadata.ImageData) } - - if dummyExtension.ExternalURL != externalURL { - t.Errorf("Expected Extension's externalURL %s, got %s", externalURL, dummyExtension.ExternalURL) + if dummyMetadata.ExternalURL != externalURL { + t.Errorf("Expected Metadata's externalURL %s, got %s", externalURL, dummyMetadata.ExternalURL) } - - if dummyExtension.Description != description { - t.Errorf("Expected Extension's description %s, got %s", description, dummyExtension.Description) + if dummyMetadata.Description != description { + t.Errorf("Expected Metadata's description %s, got %s", description, dummyMetadata.Description) } - - if dummyExtension.Name != name { - t.Errorf("Expected Extension's name %s, got %s", name, dummyExtension.Name) + if dummyMetadata.Name != name { + t.Errorf("Expected Metadata's name %s, got %s", name, dummyMetadata.Name) } - - if len(dummyExtension.Attributes) != len(attributes) { - t.Errorf("Expected %d Extension's attributes, got %d", len(attributes), len(dummyExtension.Attributes)) + if len(dummyMetadata.Attributes) != len(attributes) { + t.Errorf("Expected %d Metadata's attributes, got %d", len(attributes), len(dummyMetadata.Attributes)) } - - if dummyExtension.BackgroundColor != backgroundColor { - t.Errorf("Expected Extension's backgroundColor %s, got %s", backgroundColor, dummyExtension.BackgroundColor) + if dummyMetadata.BackgroundColor != backgroundColor { + t.Errorf("Expected Metadata's backgroundColor %s, got %s", backgroundColor, dummyMetadata.BackgroundColor) } - - if dummyExtension.AnimationURL != animationURL { - t.Errorf("Expected Extension's animationURL %s, got %s", animationURL, dummyExtension.AnimationURL) + if dummyMetadata.AnimationURL != animationURL { + t.Errorf("Expected Metadata's animationURL %s, got %s", animationURL, dummyMetadata.AnimationURL) } - - if dummyExtension.YoutubeURL != youtubeURL { - t.Errorf("Expected Extension's youtubeURL %s, got %s", youtubeURL, dummyExtension.YoutubeURL) + if dummyMetadata.YoutubeURL != youtubeURL { + t.Errorf("Expected Metadata's youtubeURL %s, got %s", youtubeURL, dummyMetadata.YoutubeURL) } } - - } From b9951524f3f5e211bb0269b5c9effae6cf2ad1a0 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 22 Apr 2024 23:19:38 +0700 Subject: [PATCH 06/19] add needed functions for IRC721Metadata --- examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno index d43cb7e952f..214dba88e4f 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -1,7 +1,10 @@ package grc721 -type IGRC721Metadata interface { +type IGRC721Metadata interface { + Name() string + Symbol() string + TokenURI(tid TokenID) (string, error) TokenMetadata(tid TokenID) (Metadata, error) } From 8fb8341d3fa3e22897090bed8adfd6c1e7d20f2d Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Mon, 22 Apr 2024 23:21:57 +0700 Subject: [PATCH 07/19] format error with %s --- examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno index 69b8d049bd8..9ba2a141c6a 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno @@ -51,7 +51,7 @@ func TestSetMetadata(t *testing.T) { // Check if there was an error setting metadata if derr != nil { - t.Errorf("Should not result in error ", derr.Error()) + t.Errorf("Should not result in error : %s", derr.Error()) } // Test case: Invalid token ID @@ -69,7 +69,7 @@ func TestSetMetadata(t *testing.T) { // Check if the error returned matches the expected error if err != ErrInvalidTokenId { - t.Errorf("Expected error %v, got %v", ErrInvalidTokenId, err) + t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) } // Set the original caller to addr2 @@ -90,7 +90,7 @@ func TestSetMetadata(t *testing.T) { // Check if the error returned matches the expected error if cerr != ErrCallerIsNotOwner { - t.Errorf("Expected error %v, got %v", ErrCallerIsNotOwner, cerr) + t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) } // Set the original caller back to addr1 @@ -99,7 +99,7 @@ func TestSetMetadata(t *testing.T) { // Retrieve metadata for token 1 dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) if err != nil { - t.Errorf("Metadata error: %v", err.Error()) + t.Errorf("Metadata error: %s", err.Error()) } else { // Check if metadata attributes match expected values if dummyMetadata.Image != image { From 3945bef75bdbf259575de6ca20d7895d2bd1c621 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Tue, 23 Apr 2024 00:40:26 +0700 Subject: [PATCH 08/19] rename file + split interface --- .../grc/grc721/{nft_metadata.gno => grc721_metadata.gno} | 5 ++--- .../{nft_metadata_test.gno => grc721_metadata_test.gno} | 0 .../grc/grc721/{nft_royalty.gno => grc721_royalty.gno} | 2 ++ .../{nft_royalty_test.gno => grc721_royalty_test.gno} | 2 +- examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno | 7 ++++++- examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno | 3 ++- 6 files changed, 13 insertions(+), 6 deletions(-) rename examples/gno.land/p/demo/grc/grc721/{nft_metadata.gno => grc721_metadata.gno} (95%) rename examples/gno.land/p/demo/grc/grc721/{nft_metadata_test.gno => grc721_metadata_test.gno} (100%) rename examples/gno.land/p/demo/grc/grc721/{nft_royalty.gno => grc721_royalty.gno} (95%) rename examples/gno.land/p/demo/grc/grc721/{nft_royalty_test.gno => grc721_royalty_test.gno} (97%) diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno similarity index 95% rename from examples/gno.land/p/demo/grc/grc721/nft_metadata.gno rename to examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 66af2e13048..906ef64b324 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -12,12 +12,11 @@ type metadataNFT struct { extensions avl.Tree // AVL tree for storing metadata extensions } -// Ensure that metadataNFT implements the IGRC721Metadata interface. -var _ IGRC721Metadata = (*metadataNFT)(nil) +// 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) diff --git a/examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno similarity index 100% rename from examples/gno.land/p/demo/grc/grc721/nft_metadata_test.gno rename to examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno similarity index 95% rename from examples/gno.land/p/demo/grc/grc721/nft_royalty.gno rename to examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 316a057983d..b6c946b274f 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -12,6 +12,8 @@ type royaltyNFT struct { tokenRoyaltyInfo avl.Tree // AVL tree to store royalty information for each token } +// 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 { diff --git a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno similarity index 97% rename from examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno rename to examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno index 49d812b3f4a..d955a5fe0ad 100644 --- a/examples/gno.land/p/demo/grc/grc721/nft_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -48,7 +48,7 @@ func TestSetTokenRoyalty(t *testing.T) { std.TestSetOrigCaller(std.Address(addr2)) // addr2 - _, cerr := dummy.SetTokenRoyalty(TokenID("1"),RoyaltyInfo{ + _, cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno index 214dba88e4f..02788da6292 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -1,10 +1,15 @@ - package grc721 +// Base metadata interface type IGRC721Metadata interface { Name() string Symbol() string TokenURI(tid TokenID) (string, error) +} + +// see: https://docs.opensea.io/docs/metadata-standards +type IGRC721MetadataOnchain interface { + IGRC721Metadata TokenMetadata(tid TokenID) (Metadata, error) } diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index 4f7d89fe81c..c203c2125a9 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -4,7 +4,8 @@ import ( "std" ) -type IGRC721Royalty interface { +// Base grc2981 interface +type IGRC2981 interface { RoyaltyInfo(tokenID TokenID, salePrice uint64) (std.Address, uint64, error) } From 2d260bc58613e9ec5b1f233ca578b2ce6943c13e Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Tue, 23 Apr 2024 00:59:47 +0700 Subject: [PATCH 09/19] add more comments --- .../p/demo/grc/grc721/igrc721_metadata.gno | 36 +++++++++++-------- .../p/demo/grc/grc721/igrc721_royalty.gno | 9 +++-- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno index 02788da6292..3cf7ebb2a7e 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -1,15 +1,20 @@ package grc721 -// Base metadata interface +// 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 { - Name() string - Symbol() string - TokenURI(tid TokenID) (string, error) + IGRC721CollectionMetadata + TokenURI(tid TokenID) (string, error) // TokenURI returns the URI of a specific token. } -// see: https://docs.opensea.io/docs/metadata-standards +// IGRC721Metadata follows the OpenSea metadata standard type IGRC721MetadataOnchain interface { - IGRC721Metadata + IGRC721CollectionMetadata TokenMetadata(tid TokenID) (Metadata, error) } @@ -19,14 +24,15 @@ type Trait struct{ Value string } +// see: https://docs.opensea.io/docs/metadata-standards type Metadata struct { - Image string - ImageData string - ExternalURL string - Description string - Name string - Attributes []Trait - BackgroundColor string - AnimationURL string - YoutubeURL string + 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). } \ No newline at end of file diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index c203c2125a9..dff7ba5b98d 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -4,12 +4,15 @@ import ( "std" ) -// Base grc2981 interface +// 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 - Percentage uint64 + PaymentAddress std.Address // PaymentAddress is the address where royalty payment should be sent. + Percentage uint64 // Percentage is the royalty percentage. } \ No newline at end of file From 5831669bb3cf2fa52fdbec09171a99891786d334 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 10:33:55 +0700 Subject: [PATCH 10/19] tmp --- .../demo/grc/grc721/grc721_metadata_test.gno | 59 +++++++++---------- .../p/demo/grc/grc721/grc721_royalty_test.gno | 36 +++++------ 2 files changed, 44 insertions(+), 51 deletions(-) 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 index 9ba2a141c6a..9a3668a3ecc 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno @@ -16,8 +16,8 @@ func TestSetMetadata(t *testing.T) { } // Define addresses for testing purposes - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") // Define metadata attributes name := "test" @@ -31,22 +31,22 @@ func TestSetMetadata(t *testing.T) { youtubeURL := "test" // Set the original caller to addr1 - std.TestSetOrigCaller(std.Address(addr1)) // 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, + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, }) // Check if there was an error setting metadata @@ -56,15 +56,15 @@ func TestSetMetadata(t *testing.T) { // Test case: Invalid token ID _, err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ - Name: name, - Description: description, - Image: image, - ImageData: imageData, - ExternalURL: externalURL, - Attributes: attributes, + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, }) // Check if the error returned matches the expected error @@ -73,19 +73,19 @@ func TestSetMetadata(t *testing.T) { } // Set the original caller to addr2 - std.TestSetOrigCaller(std.Address(addr2)) // 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, + Name: name, + Description: description, + Image: image, + ImageData: imageData, + ExternalURL: externalURL, + Attributes: attributes, BackgroundColor: backgroundColor, - AnimationURL: animationURL, - YoutubeURL: youtubeURL, + AnimationURL: animationURL, + YoutubeURL: youtubeURL, }) // Check if the error returned matches the expected error @@ -94,7 +94,7 @@ func TestSetMetadata(t *testing.T) { } // Set the original caller back to addr1 - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOrigCaller(addr1) // addr1 // Retrieve metadata for token 1 dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) @@ -131,4 +131,3 @@ func TestSetMetadata(t *testing.T) { } } } - 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 index d955a5fe0ad..91d788a14d3 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -5,8 +5,8 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/p/demo/users" "gno.land/p/demo/ufmt" + "gno.land/p/demo/users" ) func TestSetTokenRoyalty(t *testing.T) { @@ -15,22 +15,22 @@ func TestSetTokenRoyalty(t *testing.T) { t.Errorf("should not be nil") } - addr1 := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") - addr2 := std.Address("g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj") + addr1 := testutils.TestAddress("alice") + addr2 := testutils.TestAddress("bob") - paymentAddress := std.Address("g1var589z07ppjsjd24ukm4uguzwdt0tw7g47cgm") + paymentAddress := testutils.TestAddress("john") percentage := uint64(10) // 10% salePrice := uint64(1000) expectRoyaltyAmount := uint64(100) - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOrigCaller(addr1) // addr1 dummy.mint(addr1, TokenID("1")) _, derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, - Percentage: percentage, + Percentage: percentage, }) if derr != nil { @@ -40,18 +40,18 @@ func TestSetTokenRoyalty(t *testing.T) { // Test case: Invalid token ID _, err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ PaymentAddress: paymentAddress, - Percentage: percentage, + Percentage: percentage, }) if err != ErrInvalidTokenId { t.Errorf("Expected error %s, got %s", ErrInvalidTokenId, err) } - std.TestSetOrigCaller(std.Address(addr2)) // addr2 + std.TestSetOrigCaller(addr2) // addr2 _, cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, - Percentage: percentage, - }) + Percentage: percentage, + }) if cerr != ErrCallerIsNotOwner { t.Errorf("Expected error %s, got %s", ErrCallerIsNotOwner, cerr) } @@ -59,41 +59,35 @@ func TestSetTokenRoyalty(t *testing.T) { // Test case: Invalid payment address _, aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ PaymentAddress: std.Address("###"), - Percentage: percentage, + Percentage: percentage, }) if aerr != ErrInvalidRoyaltyPaymentAddress { t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPaymentAddress, aerr) } - // Test case: Invalid payment address _, perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ PaymentAddress: paymentAddress, - Percentage: uint64(10), + Percentage: uint64(10), }) if perr != ErrInvalidRoyaltyPercentage { // t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) } - // Test case: Retrieving Royalty Info - std.TestSetOrigCaller(std.Address(addr1)) // addr1 + std.TestSetOrigCaller(addr1) // addr1 dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) if rerr != nil { t.Errorf("RoyaltyInfo error: %s", rerr.Error()) - } else { if dummyPaymentAddress != paymentAddress { t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) } - + if dummyRoyaltyAmount != expectRoyaltyAmount { t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) } - } + } } - - - From 2bc03b5acd0295b6df13adbe986e738030b64cc2 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:17:24 +0700 Subject: [PATCH 11/19] remove bool param from return --- .../p/demo/grc/grc721/grc721_metadata.gno | 16 ++++++-------- .../demo/grc/grc721/grc721_metadata_test.gno | 6 ++--- .../p/demo/grc/grc721/grc721_royalty.gno | 22 +++++++++---------- .../p/demo/grc/grc721/grc721_royalty_test.gno | 10 ++++----- 4 files changed, 26 insertions(+), 28 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 906ef64b324..9163821e9e7 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -8,8 +8,8 @@ import ( // 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 + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions avl.Tree // AVL tree for storing metadata extensions } // Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. @@ -28,20 +28,20 @@ func NewNFTWithMetadata(name string, symbol string) *metadataNFT { } // SetTokenMetadata sets metadata for a given token ID. -func (s *metadataNFT) SetTokenMetadata(tid TokenID, metadata Metadata) (bool, error) { +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 false, err + return err } caller := std.PrevRealm().Addr() if caller != owner { - return false, ErrCallerIsNotOwner + return ErrCallerIsNotOwner } // Set the metadata for the token ID in the extensions AVL tree s.extensions.Set(string(tid), metadata) - return true, nil + return nil } // TokenMetadata retrieves metadata for a given token ID. @@ -67,7 +67,6 @@ func (s *metadataNFT) mint(to std.Address, tid TokenID) error { return ErrTokenIdAlreadyExists } - s.basicNFT.beforeTokenTransfer(zeroAddress, to, tid, 1) // Check if the token ID was minted by beforeTokenTransfer @@ -90,8 +89,7 @@ func (s *metadataNFT) mint(to std.Address, tid TokenID) error { event := TransferEvent{zeroAddress, to, tid} emit(&event) - s.basicNFT.afterTokenTransfer(zeroAddress, to, tid, 1) return nil -} \ No newline at end of file +} 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 index 9a3668a3ecc..d6be3462229 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno @@ -37,7 +37,7 @@ func TestSetMetadata(t *testing.T) { dummy.mint(addr1, TokenID("1")) // Set metadata for token 1 - _, derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + derr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ Name: name, Description: description, Image: image, @@ -55,7 +55,7 @@ func TestSetMetadata(t *testing.T) { } // Test case: Invalid token ID - _, err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ + err := dummy.SetTokenMetadata(TokenID("3"), Metadata{ Name: name, Description: description, Image: image, @@ -76,7 +76,7 @@ func TestSetMetadata(t *testing.T) { std.TestSetOrigCaller(addr2) // addr2 // Try to set metadata for token 1 from addr2 (should fail) - _, cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ + cerr := dummy.SetTokenMetadata(TokenID("1"), Metadata{ Name: name, Description: description, Image: image, diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index b6c946b274f..a25b6bf3165 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -8,8 +8,8 @@ import ( // 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 + *metadataNFT // Embedding metadataNFT for NFT functionality + tokenRoyaltyInfo avl.Tree // AVL tree to store royalty information for each token } // Ensure that royaltyNFT implements the IGRC2981 interface. @@ -21,36 +21,36 @@ func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { nft := NewNFTWithMetadata(name, symbol) return &royaltyNFT{ - metadataNFT: nft, - tokenRoyaltyInfo: avl.Tree{}, + metadataNFT: nft, + tokenRoyaltyInfo: avl.Tree{}, } } // SetTokenRoyalty sets the royalty information for a specific token ID. -func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) (bool, error) { +func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error { // Validate the payment address if err := isValidAddress(royaltyInfo.PaymentAddress); err != nil { - return false, ErrInvalidRoyaltyPaymentAddress + return ErrInvalidRoyaltyPaymentAddress } // Check if royalty percentage exceeds 100% if royaltyInfo.Percentage > 100 { - return false, ErrInvalidRoyaltyPercentage + return ErrInvalidRoyaltyPercentage } // Check if the caller is the owner of the token owner, err := r.metadataNFT.OwnerOf(tid) if err != nil { - return false, err + return err } caller := std.PrevRealm().Addr() if caller != owner { - return false, ErrCallerIsNotOwner + return ErrCallerIsNotOwner } // Set royalty information for the token r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) - return true, nil + return nil } // RoyaltyInfo returns the royalty information for the given token ID and sale price. @@ -75,4 +75,4 @@ func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, ui func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { royaltyAmount := (salePrice * percentage) / 100 return royaltyAmount, nil -} \ No newline at end of file +} 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 index 91d788a14d3..6f73dcfd74e 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -28,7 +28,7 @@ func TestSetTokenRoyalty(t *testing.T) { dummy.mint(addr1, TokenID("1")) - _, derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + derr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) @@ -38,7 +38,7 @@ func TestSetTokenRoyalty(t *testing.T) { } // Test case: Invalid token ID - _, err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ + err := dummy.SetTokenRoyalty(TokenID("3"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) @@ -48,7 +48,7 @@ func TestSetTokenRoyalty(t *testing.T) { std.TestSetOrigCaller(addr2) // addr2 - _, cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ + cerr := dummy.SetTokenRoyalty(TokenID("1"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: percentage, }) @@ -57,7 +57,7 @@ func TestSetTokenRoyalty(t *testing.T) { } // Test case: Invalid payment address - _, aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ + aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ PaymentAddress: std.Address("###"), Percentage: percentage, }) @@ -66,7 +66,7 @@ func TestSetTokenRoyalty(t *testing.T) { } // Test case: Invalid payment address - _, perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ + perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ PaymentAddress: paymentAddress, Percentage: uint64(10), }) From 9067b07f2e89ce0bac176fecb8648086fcea23f3 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:28:17 +0700 Subject: [PATCH 12/19] add comment for RoyaltyInfo model --- examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno index dff7ba5b98d..a8a74ea15cc 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_royalty.gno @@ -4,8 +4,8 @@ import ( "std" ) -// IGRC2981 follows the Ethereum standard -type IGRC2981 interface { +// 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) @@ -14,5 +14,5 @@ type IGRC2981 interface { // 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. -} \ No newline at end of file + Percentage uint64 // Percentage is the royalty percentage. It indicates the percentage of royalty to be paid for each sale. For example : Percentage = 10 => 10% +} From aebba7474ad24f3d44ceced64b7fe4a02fc85084 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:44:46 +0700 Subject: [PATCH 13/19] define maxRoyaltyPercentage --- .../p/demo/grc/grc721/grc721_royalty.gno | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index a25b6bf3165..0d531d22fd5 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -8,8 +8,9 @@ import ( // 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 + *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. @@ -21,8 +22,9 @@ func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { nft := NewNFTWithMetadata(name, symbol) return &royaltyNFT{ - metadataNFT: nft, - tokenRoyaltyInfo: avl.Tree{}, + metadataNFT: nft, + tokenRoyaltyInfo: avl.Tree{}, + maxRoyaltyPercentage: 100, } } @@ -63,16 +65,13 @@ func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, ui royaltyInfo := val.(RoyaltyInfo) - // Calculate royalty amount using the provided calculator - royaltyAmount, err := r.calculateRoyaltyAmount(salePrice, royaltyInfo.Percentage) - if err != nil { - return "", 0, ErrCannotCalculateRoyaltyAmount - } + // 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 + royaltyAmount := (salePrice * percentage) / r.maxRoyaltyPercentage return royaltyAmount, nil } From fd49c8becf3529b8e856092d1f31ad9f2522ace1 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:46:57 +0700 Subject: [PATCH 14/19] update --- examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 0d531d22fd5..e147ee97b7b 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -36,7 +36,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error } // Check if royalty percentage exceeds 100% - if royaltyInfo.Percentage > 100 { + if royaltyInfo.Percentage > r.maxRoyaltyPercentage { return ErrInvalidRoyaltyPercentage } @@ -72,6 +72,6 @@ func (r *royaltyNFT) RoyaltyInfo(tid TokenID, salePrice uint64) (std.Address, ui } func (r *royaltyNFT) calculateRoyaltyAmount(salePrice, percentage uint64) (uint64, error) { - royaltyAmount := (salePrice * percentage) / r.maxRoyaltyPercentage + royaltyAmount := (salePrice * percentage) / 100 return royaltyAmount, nil } From b59c37880e317fe8e3c8e4df75e7d73aadb01ae4 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:47:23 +0700 Subject: [PATCH 15/19] update --- examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index e147ee97b7b..825540b7538 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -35,7 +35,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error return ErrInvalidRoyaltyPaymentAddress } - // Check if royalty percentage exceeds 100% + // Check if royalty percentage exceeds maxRoyaltyPercentage if royaltyInfo.Percentage > r.maxRoyaltyPercentage { return ErrInvalidRoyaltyPercentage } From c035de8d73314aad8969b16107d49db0a7fdb882 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 11:48:44 +0700 Subject: [PATCH 16/19] NewTree --- examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno | 2 +- examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 9163821e9e7..74f15f1b8fd 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -23,7 +23,7 @@ func NewNFTWithMetadata(name string, symbol string) *metadataNFT { // Return a metadataNFT with basicNFT embedded and an empty AVL tree for extensions return &metadataNFT{ basicNFT: nft, - extensions: avl.Tree{}, + extensions: avl.NewTree(), } } diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 825540b7538..7004e4fe026 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -23,7 +23,7 @@ func NewNFTWithRoyalty(name string, symbol string) *royaltyNFT { return &royaltyNFT{ metadataNFT: nft, - tokenRoyaltyInfo: avl.Tree{}, + tokenRoyaltyInfo: avl.NewTree(), maxRoyaltyPercentage: 100, } } From 01cdea581b346d01341d17286d918ec51ff3a859 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 13:39:19 +0700 Subject: [PATCH 17/19] update testcases --- .../gno.land/p/demo/grc/grc721/errors.gno | 2 +- .../p/demo/grc/grc721/grc721_metadata.gno | 4 ++-- .../p/demo/grc/grc721/grc721_royalty.gno | 7 +++--- .../p/demo/grc/grc721/grc721_royalty_test.gno | 24 +++++++++---------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/errors.gno b/examples/gno.land/p/demo/grc/grc721/errors.gno index b65571578fc..2d512db350d 100644 --- a/examples/gno.land/p/demo/grc/grc721/errors.gno +++ b/examples/gno.land/p/demo/grc/grc721/errors.gno @@ -16,7 +16,7 @@ var ( ErrTokenIdAlreadyExists = errors.New("token id already exists") // ERC721Royalty - ErrInvalidRoyaltyPercentage = errors.New("invalid royalty percentage") + 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/grc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno index 74f15f1b8fd..360f73ed106 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata.gno @@ -8,8 +8,8 @@ import ( // 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 + *basicNFT // Embedded basicNFT struct for basic NFT functionality + extensions *avl.Tree // AVL tree for storing metadata extensions } // Ensure that metadataNFT implements the IGRC721MetadataOnchain interface. diff --git a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno index 7004e4fe026..9831c709121 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty.gno @@ -8,9 +8,9 @@ import ( // 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 + *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. @@ -52,6 +52,7 @@ func (r *royaltyNFT) SetTokenRoyalty(tid TokenID, royaltyInfo RoyaltyInfo) error // Set royalty information for the token r.tokenRoyaltyInfo.Set(string(tid), royaltyInfo) + return 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 index 6f73dcfd74e..8c7bb33caa5 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_royalty_test.gno @@ -58,21 +58,21 @@ func TestSetTokenRoyalty(t *testing.T) { // Test case: Invalid payment address aerr := dummy.SetTokenRoyalty(TokenID("4"), RoyaltyInfo{ - PaymentAddress: std.Address("###"), + PaymentAddress: std.Address("###"), // invalid address Percentage: percentage, }) if aerr != ErrInvalidRoyaltyPaymentAddress { t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPaymentAddress, aerr) } - // Test case: Invalid payment address + // Test case: Invalid percentage perr := dummy.SetTokenRoyalty(TokenID("5"), RoyaltyInfo{ PaymentAddress: paymentAddress, - Percentage: uint64(10), + Percentage: uint64(200), // over maxRoyaltyPercentage }) if perr != ErrInvalidRoyaltyPercentage { - // t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) + t.Errorf("Expected error %s, got %s", ErrInvalidRoyaltyPercentage, perr) } // Test case: Retrieving Royalty Info @@ -81,13 +81,13 @@ func TestSetTokenRoyalty(t *testing.T) { dummyPaymentAddress, dummyRoyaltyAmount, rerr := dummy.RoyaltyInfo(TokenID("1"), salePrice) if rerr != nil { t.Errorf("RoyaltyInfo error: %s", rerr.Error()) - } else { - if dummyPaymentAddress != paymentAddress { - t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) - } - - if dummyRoyaltyAmount != expectRoyaltyAmount { - t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) - } + } + + if dummyPaymentAddress != paymentAddress { + t.Errorf("Expected RoyaltyPaymentAddress %s, got %s", paymentAddress, dummyPaymentAddress) + } + + if dummyRoyaltyAmount != expectRoyaltyAmount { + t.Errorf("Expected RoyaltyAmount %d, got %d", expectRoyaltyAmount, dummyRoyaltyAmount) } } From e1e10e8053efea1ee747bbcba38ec24a75d724cf Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Wed, 8 May 2024 13:43:28 +0700 Subject: [PATCH 18/19] remove redundant else --- .../demo/grc/grc721/grc721_metadata_test.gno | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) 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 index d6be3462229..b7ca6932fe1 100644 --- a/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno +++ b/examples/gno.land/p/demo/grc/grc721/grc721_metadata_test.gno @@ -100,34 +100,34 @@ func TestSetMetadata(t *testing.T) { dummyMetadata, err := dummy.TokenMetadata(TokenID("1")) if err != nil { t.Errorf("Metadata error: %s", err.Error()) - } else { - // 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) - } + } + + // 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) } } From ea00ba1a55e8361a6749255e3b63181e435b54a7 Mon Sep 17 00:00:00 2001 From: linhpn99 Date: Fri, 10 May 2024 10:23:06 +0700 Subject: [PATCH 19/19] format code --- .../p/demo/grc/grc721/igrc721_metadata.gno | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno index 3cf7ebb2a7e..8a2be1ff75d 100644 --- a/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno +++ b/examples/gno.land/p/demo/grc/grc721/igrc721_metadata.gno @@ -2,37 +2,37 @@ 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. + Name() string // Name returns the name of the collection. + Symbol() string // Symbol returns the symbol of the collection. } -// IGRC721Metadata follows the Ethereum standard +// 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 +// IGRC721Metadata follows the OpenSea metadata standard type IGRC721MetadataOnchain interface { IGRC721CollectionMetadata TokenMetadata(tid TokenID) (Metadata, error) } -type Trait struct{ - DisplayType string - TraitType string - Value string +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). -} \ No newline at end of file + 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). +}