Snowman Merkle Airdrop

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

Snow token owner can mint unlimited Snow and claim unlimited Snowman NFTs - no mint cap

Root + Impact: // src/Snow.sol
contract Snow is ERC20 {
// @> BUG: mint() has only owner check, no cap, no maxSupply
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount); // unlimited mint possible
}
}
// Attacker who is owner / compromised owner can mint 1e27 Snow
// Then call SnowmanAirdrop.claimSnowman() unlimited times with that Snow

Description: Description: Unlimited Snow mint breaks airdrop economics and allows infinite NFT claims

Snow is the token that must be burned to claim Snowman NFT. Its supply is supposed to be limited by Merkle airdrop distribution.
​
But Snow.mint() has no max supply, no cap, no time-lock. Owner can call mint() any number of times for any amount.
​
If owner is compromised, malicious, or key leaks, attacker can mint unlimited Snow, burn it in claimSnowman, and mint unlimited NFTs. Even honest owner can make mistake and inflate supply.
​
Result: Snow becomes worthless, Snowman NFTs become unlimited, whole scarcity model breaks.

Risk

Likelihood:High - Only needs owner key. Owner keys get leaked / phished very commonly in real protocols. One mint call is enough to break everything.

Impact:High - Unlimited inflation of Snow + unlimited mint of Snowman NFTs. Protocol economics fully broken, legit holders diluted to zero.

Proof of Concept:PoC: Owner mints 1M Snow and claims NFTs infinitely

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test} from "forge-std/Test.sol";
import {Snow} from "../src/Snow.sol";
​
contract SnowMintTest is Test {
Snow snow;
address owner = makeAddr("owner");
function setUp() public {
vm.prank(owner);
snow = new Snow();
}
function test_OwnerCanMintUnlimited() public {
vm.prank(owner);
snow.mint(owner, 1_000_000 ether);
assertEq(snow.balanceOf(owner), 1_000_000 ether);
// No cap check, second mint also succeeds
vm.prank(owner);
snow.mint(owner, type(uint256).max / 2);
// Supply is now unlimited, no revert
}
}

Recommended Mitigation:

Mitigation: Add MAX_SUPPLY cap + disable mint after distribution
​
Root Cause:
Snow.mint() has no supply cap. It only checks onlyOwner. Owner can mint any amount any number of times.
​
Why this fix works:
Cap creates a hard on-chain limit. Even if owner key is compromised, leaked, or phished, attacker cannot mint more than MAX_SUPPLY. This protects Snow price, airdrop scarcity, and Snowman NFT supply. Without cap, one mint call can destroy whole protocol economics.
​
Recommended Fix - Step by Step:
​
Step 1 - Add MAX_SUPPLY constant in Snow.sol:
Set MAX_SUPPLY equal to total intended airdrop amount, for example 1,000,000 ether. Make it immutable / constant so it cannot be changed later.
​
Step 2 - Enforce cap in mint():
In mint(), check totalSupply() + amount <= MAX_SUPPLY, if not, revert with custom error Snow__CapExceeded(). This check must happen before _mint.
​
Step 3 - Limit minting window:
After airdrop distribution is finished, revoke mint permission. Call renounceOwnership() or transfer mint role to zero address. This ensures no future mint is possible.
​
Step 4 - Use multi-sig / timelock for owner:
Owner should be a multi-sig wallet, not a single EOA, so one leaked key cannot mint.
​
After Fix Verification:
- Owner tries to mint 1,000,001 ether when cap is 1,000,000 ether -> should REVERT
- Owner tries to mint after renounceOwnership -> should REVERT
- Normal users can still claim with existing Snow -> should SUCCEED
​
Supporting Code for Fix:
​
// In Snow.sol
uint256 public constant MAX_SUPPLY = 1000000 ether;
error Snow__CapExceeded();
​
function mint(address to, uint256 amount) external onlyOwner {
if (totalSupply() + amount > MAX_SUPPLY) revert Snow__CapExceeded();
_mint(to, amount);
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 24 minutes 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!