Snowman Merkle Airdrop

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

Unrestricted NFT Minting Allows Anyone to Bypass Protocol Logic

Root + Impact

Description

  • Normally, Snowman NFTs should only be minted as a result of legitimate claims or staking through the SnowmanAirdrop contract, ensuring users receive NFTs proportional to their Snow token holdings and following the protocol’s intended distribution logic.

  • However, the mintSnowman function in the Snowman.sol contract is publicly accessible, allowing any external user or contract to mint any amount of NFTs to any address, bypassing the airdrop and staking mechanisms.

// Root cause in the codebase with @> marks to highlight the relevant section
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++;
}
// @> No access control on mintSnowman <@
}

Risk

Likelihood:

  • This will occur whenever any user or contract calls mintSnowman, as there are no restrictions on who can call this function.

  • Automated bots or malicious actors can exploit this at any time, especially if the contract address is known.

Impact:

  • Unauthorized users can mint unlimited NFTs, inflating the supply and devaluing legitimate NFTs.

  • The protocol’s intended distribution and staking logic is completely bypassed, undermining trust and potentially causing financial and reputational damage.

Proof of Concept

Any user could mint snowman due to missing authorization check.

// Any user can mint NFTs directly, bypassing the airdrop logic:
address attacker = address(0xBEEF);
snowman.mintSnowman(attacker, 100);
// Attacker now owns 100 Snowman NFTs without staking or airdrop participation.
assertEq(snowman.balanceOf(attacker), 100);

Recommended Mitigation

Revert if mint called by anyone except of the airdrop contract

+ address public s_airdrop;
- constructor(string memory _SnowmanSvgUri) ERC721("Snowman Airdrop", "SNOWMAN") Ownable(msg.sender) {
- s_TokenCounter = 0;
- s_SnowmanSvgUri = _SnowmanSvgUri;
- }
+ constructor(string memory _SnowmanSvgUri, address airdrop) ERC721("Snowman Airdrop", "SNOWMAN") Ownable(msg.sender) {
+ s_TokenCounter = 0;
+ s_SnowmanSvgUri = _SnowmanSvgUri;
+ s_airdrop = airdrop;
+ }
-function mintSnowman(address receiver, uint256 amount) external {
+function mintSnowman(address receiver, uint256 amount) external onlyAirdrop {
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}
+
+modifier onlyAirdrop() {
+ if (msg.sender != s_airdrop) revert SM__NotAllowed();
+ _;
+}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 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.