Snowman Merkle Airdrop

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

Unbounded loop in mintSnowman: airdrop allocations of ~904+ Snow can never be claimed (gas DoS)

Root + Impact

Description

Snowman.mintSnowman mints amount NFTs in an UNBOUNDED loop, and the airdrop calls it with amount = i_snow.balanceOf(receiver) (the recipient's full allocation):

function mintSnowman(address receiver, uint256 amount) external {
for (uint256 i = 0; i < amount; i++) { // @> unbounded: one _safeMint per token
_safeMint(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Because the loop runs once per token, a large allocation makes the single claimSnowman transaction exceed the block gas limit, so the recipient can NEVER claim their airdrop (no batched/partial path).

Risk

Likelihood: Medium

  • Airdrop allocations routinely reach the hundreds/thousands. The claim amount equals the recipient's snapshot Snow balance and cannot be reduced (the merkle leaf is fixed to it).

Impact: Medium

  • Recipients with allocations >= ~904 Snow are permanently unable to claim their Snowman NFTs (locked entitlement).

Proof of Concept

Forge test measures the real cost (PASSES):

function test_FNEW1_unbounded_mint_gas() public {
Snowman s = new Snowman("uri");
uint256 n = 1000;
uint256 g0 = gasleft();
s.mintSnowman(makeAddr("r"), n);
uint256 used = g0 - gasleft(); // 33,156,420 gas for 1000 mints
}

Output: total gas 33,156,420 for amount=1000 (> 30M block limit); ~33,156 gas/mint; max claimable amount ~904.

Recommended Mitigation

Do not mint one NFT per token in an unbounded loop. Mint exactly ONE Snowman per claim, or cap amount per call with resumable state:

- for (uint256 i = 0; i < amount; i++) { _safeMint(receiver, s_TokenCounter); s_TokenCounter++; }
+ // mint a single Snowman per eligible claim (or bound `amount` per call, resumable)
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!