Snowman Merkle Airdrop

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

Broken Metadata Logic results in Ghost NFTs (Broken Images)

Broken Metadata Logic results in Ghost NFTs (Broken Images)

Description

  • In a standard NFT project, when a user mints or claims an NFT, the contract should associate that tokenId with specific metadata (URI). When tokenURI() is called, it should return a JSON string containing a valid link to an image (IPFS/Arweave/HTTP) so marketplaces like OpenSea can display the asset correctly

  • The Snowman contract contains a mapping s_tokenIdToUri intended to store image links. However, there is no logic within the mintSnowman function to populate this mapping, nor is there a public setter function. Consequently, every minted NFT points to an uninitialized (empty) string in the s_tokenIdToUri mapping

// src/Snowman.sol
function tokenURI(uint256 tokenId) public view override returns (string memory) {
// ... (JSON construction)
@> '"image":"', s_tokenIdToUri[tokenId], '"}' // @> Root Cause: s_tokenIdToUri[tokenId] is always empty
// ...
}

Risk

Likelihood:

  • The vulnerability is embedded in the core minting logic. Every single NFT minted through the SnowmanAirdrop or directly via mintSnowman will be affected.

  • Since there is no administrative function to update the metadata post-minting, the state of the broken metadata is permanent unless the contract is redeployed.

Impact:

  • All minted assets are "Ghost NFTs" with no visual representation. This renders the collection useless for display on marketplaces and significantly devalues the assets for holders.

  • The integrity of the project is compromised as the core promise of providing a "Snowman NFT" is not fulfilled due to missing visual data.

Proof of Concept

function testPoC_GhostMetadata() public {
// 1. Setup Alice to claim her NFT
vm.prank(alice);
snow.approve(address(airdrop), 1 ether);
bytes32 digest = airdrop.getMessageHash(alice);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(aliceKey, digest);
// 2. Alice claims successfully
airdrop.claimSnowman(alice, AL_PROOF, v, r, s);
// 3. Verify Alice owns the NFT
assertEq(nft.ownerOf(0), alice);
// 4. Check Metadata URI
string memory uri = nft.tokenURI(0);
// The resulting URI contains empty image data: "image": ""
console2.log("Generated Metadata URI:", uri);
// Impact: The user holds an asset with broken visual logic
}

Recommended Mitigation

- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount, string memory imgUri) external {
for (uint256 i = 0; i < amount; i++) {
+ s_tokenIdToUri[s_TokenCounter] = imgUri;
_safeMint(receiver, s_TokenCounter);
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 19 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!