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 6 months 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!