Snowman Merkle Airdrop

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

Incorrect NFT Minting Logic Causes Gas Exhaustion and DoS

Root + Impact

Description

  • ERC20 tokens like Snow typically use 18 decimals, meaning 1 token = 1e18 wei (1,000,000,000,000,000,000 base units).

The mintSnowman() function loops over the amount parameter to mint individual NFTs, but when this amount represents ERC20 token balances with 18 decimals, it attempts to mint an astronomically large number of NFTs, causing transactions to fail due to gas exhaustion.

function claimSnowman(address receiver, bytes32[] calldata merkleProof, uint8 v, bytes32 r, bytes32 s)
external
nonReentrant
{
// ...
// @> amount is in wei (18 decimals) - e.g., 1 token = 1e18
uint256 amount = i_snow.balanceOf(receiver);
// ...
// @> Attempts to mint 1e18 individual NFTs if user has 1 Snow token
i_snowman.mintSnowman(receiver, amount);
}
function mintSnowman(address receiver, uint256 amount) external {
// @> Loops 1,000,000,000,000,000,000 times for 1 token
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Risk

Likelihood:

  • Every single claim attempt with any non-zero Snow balance will trigger this issue

Even users with fractional amounts like 0.1 Snow (1e17 wei) cannot successfully claim

  • The vulnerability activates automatically during normal protocol operation

Impact:

  • Complete Denial of Service - no legitimate user can successfully claim their airdrop

Proof of Concept

Recommended Mitigation

Use ERC1155 Instead

- import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+ import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
- contract Snowman is ERC721, Ownable {
+ contract Snowman is ERC1155, Ownable {
+ uint256 public constant SNOWMAN_TOKEN_ID = 1;
- 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++;
- }
- }
+ function mintSnowman(address receiver, uint256 amount) external onlyAirdrop {
+ // Divide by 1e18 to convert wei to whole tokens
+ uint256 nftAmount = amount / 1e18;
+ _mint(receiver, SNOWMAN_TOKEN_ID, nftAmount, "");
+ emit SnowmanMinted(receiver, nftAmount);
+ }
}
Updates

Lead Judging Commences

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