Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Unrestricted Minting Function Allows Unlimited Token Creation in `Snowman::mintSnowman`

Root + Impact

Description

The `mintSnowman()` function in the `Snowman` contract lacks any access control mechanisms, allowing any external caller to mint unlimited NFT tokens to any address. This represents a critical security flaw that completely undermines the intended scarcity and economic model of the NFT collection. The contract imports Ownable from OpenZeppelin and defines an unused `error SM__NotAllowed()`, suggesting that access control was intended but not properly implemented.
@> 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++;
}
}

Risk

Likelihood:

  • **Anyone on the internet can spot **Missing access control and immediately exploit the contract

Impact:

- Unlimited Token Supply: Any user can mint infinite tokens, destroying the scarcity model fundamental to NFTs
- Complete Economic Breakdown: The NFT collection becomes worthless as supply becomes infinite
- Gas Griefing Attacks: Malicious actors can mint massive quantities of tokens, causing network congestion and increased gas costs for other users
- Storage Bloat: Unlimited minting increases blockchain storage requirements unnecessarily
- Project Failure: The fundamental economics of the NFT project are broken, leading to complete loss of user trust and investment

Proof of Concept

Below is test functions that demonstrate the missing access control vulnerability. Here are the key tests that should be added to TestSnowman contract:
1. `testAnyoneCanMintTokens()` - Shows that any user can mint tokens to themselves
2. `testUnauthorizedUserCanMintToAnyAddress()` - Demonstrates that unauthorized users can mint to arbitrary addresses
3. `test_MultipleUsersCanMintUnlimitedTokens()` - Shows multiple users can mint unlimited amounts
4. `test_ExtremelyLargeMintDoesNotRevert()` - Proves there's no supply cap protection
function testAnyoneCanMintTokens() public {
uint256 initialTokenCounter = nft.getTokenCounter();
uint256 mintAmount = 5;
vm.prank(alice);
nft.mintSnowman(alice, mintAmount);
assertEq(nft.getTokenCounter(), initialTokenCounter + mintAmount);
assertEq(nft.balanceOf(alice), mintAmount);
for (uint256 i = 0; i < mintAmount; i++) {
assertEq(nft.ownerOf(i), alice);
}
}
function testUnauthorizedUserCanMintToAnyAddress() public {
uint256 initialTokenCounter = nft.getTokenCounter();
uint256 mintAmount = 10;
vm.prank(bob);
nft.mintSnowman(alice, mintAmount);
assertEq(nft.getTokenCounter(), initialTokenCounter + mintAmount);
assertEq(nft.balanceOf(alice), mintAmount);
assertEq(nft.balanceOf(bob), 0);
for (uint256 i = 0; i < mintAmount; i++) {
assertEq(nft.ownerOf(i), alice);
}
}
function testMultipleUsersCanMintUnlimitedTokens() public {
uint256 aliceMintAmount = 100;
uint256 bobMintAmount = 1000;
vm.prank(alice);
nft.mintSnowman(alice, aliceMintAmount);
vm.prank(bob);
nft.mintSnowman(bob, bobMintAmount);
assertEq(nft.getTokenCounter(), aliceMintAmount + bobMintAmount);
assertEq(nft.balanceOf(alice), aliceMintAmount);
assertEq(nft.balanceOf(bob), bobMintAmount);
}
function testExtremelyLargeMintDoesNotRevert() public {
uint256 extremeAmount = 10000;
vm.prank(alice);
nft.mintSnowman(alice, extremeAmount);
assertEq(nft.getTokenCounter(), extremeAmount);
assertEq(nft.balanceOf(alice), extremeAmount);
}

Recommended Mitigation

1. Implement Access Control: Add the onlyOwner modifier to restrict minting to the contract owner:
+ function mintSnowman(address receiver, uint256 amount) external onlyOwner {// existing implementation}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Unrestricted NFT mint function

The mint function of the Snowman contract is unprotected. Hence, anyone can call it and mint NFTs without necessarily partaking in the airdrop.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.