Snowman Merkle Airdrop

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

Snowman::mintSnowman lacks access control allowing anyone including holders to mint Snowman NFT arbitrarily, this breaks one of protocol hidden invariants

Root + Impact

Snowman::mintSnowman Lacks access control, allowing anyone, including selected participants, to mint Snowman NFT arbitrarily, which breaks one of the protocol's hidden invariants

Description

  • When a user comes across the function Snowman::mintSnowman They can abuse the function by minting a large number of Snowman NFTs. The Protocol intends to reward users only through its staking mechanism, but it appears there is an obvious breach through the mintSnowman function, lacking access control

```javascript
@> 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:

  • This would occur when users call Snowman::mintSnowman directly


Impact:

  • Users end up with a large number of Snowman NFTs without having to stake Snow ERC20 tokens

Proof of Concept

Add this test to the TestSnowman Foundry Test suites, then add the Attacker Contract inheriting the IERC721Receiver interface from openzeppelin to receive the NFTs

import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
function testAnyOneCanMintNFT() public{
ARandomReceiver receiver = new ARandomReceiver();
vm.startPrank(address(receiver));
nft.mintSnowman(address(receiver), 100);
vm.stopPrank();
assertEq(nft.balanceOf(address(receiver)), 100);
}
contract ARandomReceiver is IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4){
return this.onERC721Received.selector;
}
}

Recommended Mitigation

The Snowman NFT contract is supposed to be owned by the SnowmanAirdrop contract, and a check is meant to be applied to ensure only the SnowmanAirdrop contract can call mintSnowman to reward selected participants

//import the SnowmanAirdrop contract and transfer ownership to the SnowmanAirdrop contract
+ SnowmanAirdrop immutable i_snowManAirdrop;
+ error SnowMan__AccessDenied();
+ constructor(string memory _SnowmanSvgUri, SnowmanAirdrop _airdropContract) ERC721("Snowman Airdrop", "SNOWMAN") Ownable(msg.sender) {
s_TokenCounter = 0; //@audit-issue
s_SnowmanSvgUri = _SnowmanSvgUri;
+ i_snowManAirdrop = _airdropContract;
+ transferOwnership(address(i_snowManAirdrop));
}
function mintSnowman(address receiver, uint256 amount) external {
+ if(msg.sender != address(i_snowManAirdrop)) revert SnowMan__AccessDenied();
for (uint256 i = 0; i < amount; i++) {
_safeMint(receiver, s_TokenCounter);
emit SnowmanMinted(receiver, s_TokenCounter);
s_TokenCounter++;
}
}
Updates

Lead Judging Commences

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