Snowman Merkle Airdrop

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

mintSnowman has no access control, so anyone can mint as many Snowman NFTs as they want and bypass the airdrop

Root + Impact

mintSnowman has no access control, so anyone can mint unlimited Snowman NFTs and completely bypass the airdrop mechanism.

Description

As I understand the protocol's logic, only the airdrop contract should call mintSnowman, after the user has proven eligibility and deposited their Snow tokens.

However, there is a mistake: anyone can access this function and mint as many NFTs as they want.

The contract does define the SM__NotAllowed error, but never uses it, probably by oversight.

@> 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++;
}
}

Risk

Likelihood: High. Any EOA or contract can call the function directly. There are no preconditions and the only cost is gas.

Impact: High. The entire airdrop mechanism (Merkle proof, signature check, Snow staking) is bypassed. The NFT supply becomes unlimited, so NFTs that users legitimately claimed lose their value.

Proof of Concept

The following test shows that a random attacker can mint 100 NFTs without going through the airdrop.

function testAnyoneCanMintSnowman() public {
address attacker = makeAddr("attacker");
​
vm.prank(attacker);
nft.mintSnowman(attacker, 100);
​
assertEq(nft.balanceOf(attacker), 100);
}

Recommended Mitigation

Restrict minting to the airdrop contract only. Since SnowmanAirdrop is deployed after Snowman, set its address once through an owner-only setter.

+ address private s_airdrop;
+
+ function setAirdrop(address _airdrop) external onlyOwner {
+ if (s_airdrop != address(0) || _airdrop == address(0)) revert SM__NotAllowed();
+ s_airdrop = _airdrop;
+ }
​
function mintSnowman(address receiver, uint256 amount) external {
+ if (msg.sender != s_airdrop) revert SM__NotAllowed();
for (uint256 i = 0; i < amount; i++) {
​
Updates

Lead Judging Commences

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