DatingDapp

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

[L-01] `tokenURI()` custom error is unreachable because `ownerOf()` reverts first

Description

tokenURI() checks ownerOf(tokenId) == address(0) to detect non-existent tokens, but OpenZeppelin's ownerOf() reverts with ERC721NonexistentToken before it can return address(0). The custom error is dead code.

Vulnerability Details

// src/SoulboundProfileNFT.sol, line 80-83
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
if (ownerOf(tokenId) == address(0)) { // @> ownerOf() reverts before this check can fail
revert ERC721Metadata__URI_QueryFor_NonExistentToken();
}
// ...
}

OpenZeppelin's ERC721 ownerOf() implementation reverts with ERC721NonexistentToken(tokenId) for non-existent tokens — it never returns address(0). The comparison ownerOf(tokenId) == address(0) can never be true because ownerOf() always either returns a nonzero owner or reverts. The custom error ERC721Metadata__URI_QueryFor_NonExistentToken is dead code that can never execute.

Risk

Likelihood:

  • Every call to tokenURI() with a non-existent tokenId hits this. The custom error path is unreachable in all cases.

Impact:

  • Off-chain systems or frontends catching ERC721Metadata__URI_QueryFor_NonExistentToken will never see it. They receive ERC721NonexistentToken instead, potentially breaking error handling logic that depends on the custom error.

PoC

The test calls tokenURI() with a non-existent tokenId and shows that OpenZeppelin's ERC721NonexistentToken is the actual revert error, not the protocol's custom error. Any frontend listening for the custom error will never catch it.

function testTokenURICustomErrorUnreachable() public {
// Reverts with OZ's error, NOT the custom error
vm.expectRevert(
abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, 999)
);
profileNFT.tokenURI(999);
// ERC721Metadata__URI_QueryFor_NonExistentToken never fires
}

Recommendations

Use _ownerOf() instead of ownerOf(). The internal _ownerOf() returns address(0) for non-existent tokens without reverting, so the conditional check works as intended and the custom error actually fires.

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 16 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!