Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

`Snowman::tokenURI` contains unreachable code - `ownerOf` reverts before returning `address(0)`

Root + Impact

Description

  • The tokenURI function checks if (ownerOf(tokenId) == address(0)) to detect non-existent tokens and revert with a custom error ERC721Metadata__URI_QueryFor_NonExistentToken.

  • However, in OpenZeppelin's ERC721 implementation (v5, used with Solidity ^0.8.24), the ownerOf function already reverts with ERC721NonexistentToken(tokenId) when the token does not exist. It never returns address(0).

  • This means the custom check is dead code — it can never evaluate to true. The custom error ERC721Metadata__URI_QueryFor_NonExistentToken is never reachable.

// src/Snowman.sol
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (ownerOf(tokenId) == address(0)) { // @> unreachable: ownerOf reverts for non-existent tokens
revert ERC721Metadata__URI_QueryFor_NonExistentToken(); // @> never reached
}
...
}

Risk

Likelihood:

  • The dead code is present in every deployment of the contract, though it causes no direct harm.

Impact:

  • When tokenURI is called with a non-existent tokenId, users receive OpenZeppelin's generic ERC721NonexistentToken error instead of the contract's intended custom error ERC721Metadata__URI_QueryFor_NonExistentToken.

  • This is misleading for developers and integrators who expect the custom error to be used.

  • The dead code adds unnecessary bytecode size and gas cost during deployment.


Proof of Concept

In OpenZeppelin v5's ERC721, the ownerOf function calls _requireOwned(tokenId), which reverts if the token owner is address(0). This means ownerOf either returns a valid non-zero address or reverts — it never returns address(0).

function testTokenURIDeadCode() public {
uint256 nonExistentTokenId = 999;
// Calling tokenURI with a non-existent token reverts with
// OpenZeppelin's ERC721NonexistentToken, NOT with the custom
// ERC721Metadata__URI_QueryFor_NonExistentToken error.
// The custom error is unreachable because ownerOf(999) reverts
// before the == address(0) check can evaluate.
vm.expectRevert(
abi.encodeWithSelector(
IERC721Errors.ERC721NonexistentToken.selector,
nonExistentTokenId
)
);
snowman.tokenURI(nonExistentTokenId);
}

Recommended Mitigation

Replace the ownerOf check with OpenZeppelin's internal _ownerOf function, which returns address(0) for non-existent tokens without reverting. This allows the custom error to work as intended. Alternatively, remove the custom check entirely and rely on OpenZeppelin's built-in revert.

function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
- if (ownerOf(tokenId) == address(0)) {
+ if (_ownerOf(tokenId) == address(0)) {
revert ERC721Metadata__URI_QueryFor_NonExistentToken();
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 4 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!