Bid Beasts

First Flight #49
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Missing tokenURI Implementation Breaks NFT Display and Trading

Missing tokenURI Implementation Breaks NFT Display and Trading

Description

  • The BidBeasts NFT contract should provide metadata for each token through the tokenURI() function, allowing wallets, marketplaces, and other applications to display NFT information such as name, description, and image. This is a fundamental requirement for NFT functionality in the Web3 ecosystem.

  • The contract inherits from OpenZeppelin's ERC721 but fails to implement the tokenURI() function or override _baseURI(), resulting in empty metadata for all minted NFTs. This renders the NFTs functionally useless across all major platforms and applications.

contract BidBeasts is ERC721, Ownable(msg.sender) {
// ... other code ...
@> // Missing tokenURI implementation
@> // Missing _baseURI override
@> // Results in empty string returned for all token metadata
}

Risk

Likelihood:

  • Every minted NFT will have empty metadata since there is no implementation of tokenURI functionality

  • All users attempting to view or trade NFTs on any platform will encounter this issue immediately upon interaction

Impact:

  • NFTs cannot be displayed properly on major marketplaces (OpenSea, Rarible, etc.) and will appear as "Unnamed" tokens

  • The NFT collection loses all commercial value and utility in the broader ecosystem. This will highly disturb BidBeastsMarketPlace's functionality.

Proof of Concept

First we need to make a quick fix in test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest::setUp()

function setUp() public {
// Deploy contracts
- vm.prank(OWNER);
+ vm.startPrank(OWNER);
nft = new BidBeasts();
market = new BidBeastsNFTMarket(address(nft));
rejector = new RejectEther();
vm.stopPrank();
// Fund users
vm.deal(SELLER, STARTING_BALANCE);
vm.deal(BIDDER_1, STARTING_BALANCE);
vm.deal(BIDDER_2, STARTING_BALANCE);
}

Please add the following test to test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest:

function testUserCanNotQueryValidNFTMetadata() public {
_mintNFT();
vm.prank(SELLER);
string memory tokenURI = nft.tokenURI(TOKEN_ID);
assertEq(tokenURI, "");
}

Then run: forge test --mt testUserCanNotQueryValidNFTMetadata

Output:

Ran 1 test for test/BidBeastsMarketPlaceTest.t.sol:BidBeastsNFTMarketTest
[PASS] testUserCanNotQueryValidNFTMetadata() (gas: 87490)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 528.63µs (60.13µs CPU time)

Recommended Mitigation

In src/BidBeasts_NFT_ERC721.sol: BidBeasts:

contract BidBeasts is ERC721, Ownable(msg.sender) {
// existing events...
+ event MetadataUpdate(uint256 indexed tokenId, string uri);
+ mapping(uint256 => string) private s_tokenURIs;
// existing state variables...
// existing constructor...
- function mint(address to) public onlyOwner returns (uint256) {
+ function mint(address to, string calldata uri) public onlyOwner returns (uint256) {
uint256 _tokenId = CurrenTokenID;
_safeMint(to, _tokenId);
+ s_tokenURIs[_tokenId] = uri;
+ emit MetadataUpdate(_tokenId, uri);
emit BidBeastsMinted(to, _tokenId);
CurrenTokenID++;
return _tokenId;
}
function burn(uint256 _tokenId) public {
_burn(_tokenId);
+ delete s_tokenURIs[_tokenId];
+ emit MetadataUpdate(_tokenId, "");
emit BidBeastsBurn(msg.sender, _tokenId);
}
+ function tokenURI(uint256 tokenId) public view override returns (string memory) {
+ _requireOwned(tokenId);
+ return s_tokenURIs[tokenId];
+ }
}

Alternative Solutions:

  1. Base URI + Token ID Pattern: Override _baseURI() to return a base URL and concatenate with token ID (e.g., https://api.example.com/metadata/{tokenId}.json)

  2. Centralized Metadata Server: Implement a single base URI pointing to a centralized metadata service that serves JSON metadata based on token ID

  3. IPFS Integration: Store metadata on IPFS and use IPFS hashes as token URIs for decentralized metadata storage

Updates

Lead Judging Commences

cryptoghost Lead Judge about 1 month ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.