Snowman Merkle Airdrop

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

Unrestricted Public Minting in `Snowman.sol` Allows Unlimited NFT Minting Without Eligibility Check

Root + Impact

Description

Normal behavior:

According to the project documentation, users are supposed to receive Snowman NFTs by staking Snow tokens via the SnowmanAirdrop contract. Only eligible users (verified through Merkle proofs or signatures) should be able to claim NFTs, and only in proportion to the Snow tokens staked.

Actual behavior:

The mintSnowman() function in Snowman.sol is publicly accessible with no access control or eligibility enforcement. Any address can mint any number of NFTs for any receiver, bypassing the intended staking and verification process.Risk

Likelihood:

  • Any externally owned account or contract can exploit this immediately after deployment.

  • No conditions or modifiers restrict minting, so even bots can farm NFTs.

  • The function is marked external and exposed directly in the ABI.


Impact:

  • Unlimited unauthorized minting of Snowman NFTs.

  • Destroys the economic value and scarcity of the NFTs.

  • Bypasses Snow staking and eligibility criteria, breaking project design.




The function `mintSnowman()` is meant to be triggered only by the `SnowmanAirdrop` contract when a user has staked eligible Snow tokens. However, due to the absence of any access control (`onlyOwner`, `onlyAirdrop`, etc.), anyone can invoke it to mint NFTs.
- The SM__NotAllowed error exists in the contract but is unused, indicating a potential oversight in implementing access control.
```solidity
1 function mintSnowman(address receiver, uint256 amount) external {
@> Anyone can call this and mint NFTs, no Snow token checks or Merkle proofs.
2 for (uint256 i = 0; i < amount; i++) {
3 _safeMint(receiver, s_TokenCounter);
4 emit SnowmanMinted(receiver, s_TokenCounter);
5 s_TokenCounter++;
6 }
7 }
```

Proof of Concept

```solidity
// Any attacker can run this:
Snowman snowman = Snowman(snowmanAddress);
snowman.mintSnowman(attackerEOA, 1000); // Mints 1000 NFTs without staking Snow
```

Recommended Mitigation

- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyAirdrop {
Also, define a setter for the airdrop contract and add access control:
```solidity
solidity
CopyEdit
address public airdropContract;
modifier onlyAirdrop() {
if (msg.sender != airdropContract) revert SM__NotAllowed();
_;
}
function setAirdropContract(address _airdrop) external onlyOwner {
airdropContract = _airdrop;
}
```
> Optionally, include additional logic to:
>
> - Verify that `receiver` has staked enough `Snow` tokens.
> - Prevent double claims (track already minted NFTs per address).
Updates

Lead Judging Commences

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