Snowman Merkle Airdrop

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

`Snowman::mintSnowman()` Does Not Control Caller Allowing Anyone To Mint Arbitrary Number of NFTs To Themselves

[H-1] Snowman::mintSnowman() Does Not Control Caller Allowing Anyone To Mint Arbitrary Number of NFTs To Themselves

Description

  • Snowman NFTs should only be earned through the airdrop.

  • Snowman::mintSnowman() can be exploited by anyone to mint Snowman NFTs to themselves as the function is not protected by any modifier

@> function mintSnowman(address receiver, uint256 amount) external { // not protected by modifiers
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}

Risk

Likelihood: High

  • It is very simple for anyone to call this function directly at any point of time without going throught the airdrop

Impact: High

  • Since anyone can call this function with any arbitrary number of NFTs to be minted to themselves, the value of the NFT has basically become worthless

Proof of Concept

The following code proves that any address can mint any number of NFTs directly to themselves.

function testMintSnowmanDirectly() public {
assert(nft.balanceOf(alice) == 0);
vm.startPrank(alice);
nft.mintSnowman(alice, 5);
vm.stopPrank();
assert(nft.balanceOf(alice) == 5);
}

Recommended Mitigation

The most flexible solution would be for the contract to inherit from OpenZeppelin's AccessControl to allow the owner of the contract to whitelist addresses that can call the mintSnowman() function.

- contract Snowman is ERC721, Ownable {
+ contract Snowman is ERC721, Ownable, AccessControl {
+ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() ERC721("Snowman", "SNOW") Ownable(msg.sender) {
// original code...
+ _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // Admin can grant MINTER_ROLE to airdrop contract later
}
- function mintSnowman(address receiver, uint256 amount) external {
+ function mintSnowman(address receiver, uint256 amount) external onlyRole(MINTER_ROLE) {
// Called after airdrop contract is deployed
+ function setAirdropContract(address airdropContract) external onlyRole(DEFAULT_ADMIN_ROLE) {
+ _grantRole(MINTER_ROLE, airdropContract);
+ }
// Remove minting ability (if required)
+ function renounceAdminMinting() external onlyRole(DEFAULT_ADMIN_ROLE) {
+ _revokeRole(MINTER_ROLE, msg.sender);
+ }
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.