Snowman Merkle Airdrop

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

`Snowman::mintSnowman` mints NFTs in an unbounded loop using `_safeMint`, creating a DoS risk and potential reentrancy vector

Root + Impact

Description

The mintSnowman function in Snowman.sol (lines 36-44) loops amount times, calling _safeMint for each iteration. The _safeMint function calls onERC721Received on the recipient if it is a contract, which introduces two risks:
the loop completes.

  1. Denial of Service: If amount is very large, the loop may exceed the block gas limit, making the transaction impossible to execute.

  2. Reentrancy via onERC721Received: Each _safeMint call invokes the receiver's onERC721Received callback, which could re-enter the SnowmanAirdrop contract or other contracts before

// Snowman.sol, line 36-44
function mintSnowman(address receiver, uint256 amount) external {
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter); // @audit Callback to receiver on each iteration
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

A malicious receiver contract could use the onERC721Received callback to re-enter. While the SnowmanAirdrop::claimSnowman function uses nonReentrant, the Snowman contract itself has no such protection, and if mintSnowman is called directly (which is possible due to [H-1]), the callback can be exploited. Additionally, a legitimate user with a very large Snow token balance could find it impossible to claim because the gas required to mint that many NFTs exceeds the block gas limit.

Proof of Concept

Recommended Mitigation

Consider using _mint instead of _safeMint to avoid the callback (since the receiver has explicitly initiated the claim), or add a maximum mint cap:

function mintSnowman(address receiver, uint256 amount) external {
+ require(amount <= MAX_MINT_PER_CLAIM, "Exceeds max mint");
for (uint256 i = 0; i < amount; i++) {
- _safeMint(receiver, s_TokenCounter);
+ _mint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Updates

Lead Judging Commences

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