tokenURI() opens with a guard intended to reject queries for non-existent tokens, reverting with
ERC721Metadata__URI_QueryFor_NonExistentToken. However, the condition used — ownerOf(tokenId) == address(0) — can
never be true in OpenZeppelin's ERC721 implementation.
OZ's ownerOf() calls _requireOwned(tokenId) internally, which reverts with ERC721NonexistentToken before
returning if the token does not exist. ownerOf() never returns address(0) — it either returns a valid owner
address or reverts. The custom guard therefore never fires, the custom error is dead code, and a caller querying
a non-existent token receives OZ's generic revert rather than the protocol's descriptive error.
The intent is clear and correct — the developer wanted to guard non-existent token queries — but the
implementation relies on a return value that OZ guarantees will never be address(0).
When any caller (NFT marketplace, indexer, wallet) queries tokenURI() for a token ID that has not been minted
Impact:
Callers receive OZ's ERC721NonexistentToken(tokenId) instead of the protocol's
ERC721Metadata__URI_QueryFor_NonExistentToken — a developer experience and debuggability degradation
The custom error is permanently unreachable dead code, misleading future maintainers into believing it provides
an active guard
No security impact — the function still reverts correctly for non-existent tokens, just with the wrong error
Statically verifiable. OZ ERC721.ownerOf() source:
Calling tokenURI(999) on an unminted token ID reverts with ERC721NonexistentToken(999), not
ERC721Metadata__URI_QueryFor_NonExistentToken. The custom error branch is never executed.
Replace the dead ownerOf() check with OZ's _ownerOf() internal, which returns address(0) without reverting for
non-existent tokens, making the guard reachable:
This preserves the protocol's custom error while correctly intercepting non-existent token queries before the
metadata assembly executes.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.