Santa's List

AI First Flight #3
Beginner FriendlyFoundry
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Anyone can bypass the airdrop and mint unlimited Snowman NFTs

[H-01] Anyone can bypass the airdrop and mint unlimited Snowman NFTs

Summary

Snowman::mintSnowman is an unrestricted external function. Any address can call it directly and mint any number of Snowman NFTs without owning Snow, staking Snow, being included in the Merkle tree, or providing a valid signature.

Vulnerability Details

The protocol design says Snowman NFTs are distributed by SnowmanAirdrop after a user stakes Snow:

  • SnowmanAirdrop::claimSnowman verifies the receiver's Merkle proof and signature.

  • It transfers the receiver's Snow into the airdrop contract.

  • It then calls i_snowman.mintSnowman(receiver, amount).

However, the NFT contract does not restrict who can call mintSnowman:

function mintSnowman(address receiver, uint256 amount) external {
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

There is no onlyOwner, onlyAirdrop, minter role, or check that msg.sender is the airdrop contract.

Impact

This completely breaks the airdrop's core accounting and eligibility guarantees. An attacker can mint arbitrary Snowman NFTs for free, diluting or destroying the value and meaning of the collection. The Merkle proof, signature flow, and Snow staking requirement can all be bypassed.

Proof of Concept

Add the PoC test in test/AuditFindings.t.sol and run:

forge test --match-test testAnyoneCanMintSnowmenWithoutClaiming -vv

PoC:

function testAnyoneCanMintSnowmenWithoutClaiming() public {
vm.prank(attacker);
nft.mintSnowman(attacker, 10);
assertEq(nft.balanceOf(attacker), 10);
assertEq(nft.getTokenCounter(), 10);
}

Recommended Mitigation

Restrict mintSnowman so only the airdrop contract can mint. For example:

address private immutable i_airdrop;
modifier onlyAirdrop() {
if (msg.sender != i_airdrop) revert SM__NotAllowed();
_;
}
function mintSnowman(address receiver, uint256 amount) external onlyAirdrop {
...
}

Alternatively, use an explicit minter role and grant it only to the deployed SnowmanAirdrop.

Updates

Lead Judging Commences

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