Snowman Merkle Airdrop

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

In snowman.sol anyone can mint NFTs, there is no restriction

Root + Impact

Description

  • A normal behavior should be to allow only the AirdropSnowman.sol contract to mint NFTs under certain conditions.

  • The actual behavior => anyone can mint NFTs, there is no restriction in the snowman.sol contract for the mintSnowman() function.

// 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++;
}
}

Risk

Likelihood: High

  • It occurs EVERYTIME the external function mintSnowman() is called for a specific receiver.

  • There no restriction on msg.sender to call this function nor receiverto receive the NFTs.

Impact: High

  • A user can mint as many NFTs as he wants without passing by the AidropSnowman.sol contract.

  • A user can mint as many NFTs as he wants without staking/possessing any SNOW token.

Proof of Concept

1) Do not buy / possess any SNOW token, do not stake any SNOW token.

2) Execute the mintSnowman() function in snowman.sol contract to mint an arbitrary number of NFTs to an arbitrary receiver => use the testMintSnowman() function from TestSnowman.t.sol defined as follow.

3) Check that the transaction goes through perfectly.

pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {Snowman} from "../src/Snowman.sol";
import {DeploySnowman} from "../script/DeploySnowman.s.sol";
contract TestSnowman is Test {
Snowman nft;
DeploySnowman deployer;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
string constant NAME = "Snowman Airdrop";
string constant SYMBOL = "SNOWMAN";
function setUp() public {
deployer = new DeploySnowman();
nft = deployer.run();
}
function testMintSnowman() public {
nft.mintSnowman(alice, 1);
nft.mintSnowman(bob, 2);
assert(nft.ownerOf(0) == alice);
assert(nft.ownerOf(1) == bob);
assert(nft.ownerOf(2) == bob);
assert(nft.balanceOf(alice) == 1);
assert(nft.balanceOf(bob) == 2);
}
}

Recommended Mitigation

Restrict the mintSnowman() function to be called only by the airdropSnoman.sol contract that should implement a good check on receiver.

You can use a Modifier :

modifier onlyAidropContract {
require(msg.sender == aidropContract);
_;
}
- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyAidropContract {
Updates

Lead Judging Commences

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