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.
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);
vm.prank(owner);
snow.mint(owner, type(uint256).max / 2);
}
}
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);
}