Normal Protocol Behavior: The Snowman.sol contract (ERC721) NFTs are intended to be distributed via the SnowmanAirdrop.sol contract. This airdrop contract uses a Merkle tree system. Recipients prove their eligibility, stake their Snow tokens into the SnowmanAirdrop.sol contract, and in return, the SnowmanAirdrop.sol contract is responsible for ensuring they receive Snowman NFTs equal to their staked Snow balance. This controlled mechanism is crucial for the NFT's intended distribution and value.
Specific Vulnerability: The mintSnowman(address receiver, uint256 amount) function in Snowman.sol is declared external and lacks any access control. This allows any external account to call it directly and mint an arbitrary number of Snowman NFTs to any address, completely bypassing the Snow token staking and Merkle verification process managed by SnowmanAirdrop.sol.
Likelihood: High
An attacker (any external account) can easily discover and call the public mintSnowman function.
Impact: High
Unlimited NFT Supply & Devaluation: Attackers can mint an unlimited number of Snowman NFTs, destroying their scarcity and value.
Circumvention of Airdrop Mechanism: The intended distribution via SnowmanAirdrop.sol (based on Merkle proofs and Snow token staking) is rendered ineffective.
Unfair Advantage and Market Manipulation: Attackers can pre-mint NFTs.
Loss of User Trust and Project Credibility: This fundamental flaw damages the project's reputation.
The following Foundry test, testVulnerability_AttackerCanMintSnowman from test/Snowman.t.sol, demonstrates that an arbitrary attacker address can successfully call mintSnowman.
To align with the protocol's described invariants, where SnowmanAirdrop.sol manages the eligibility and distribution of Snowman NFTs based on Snow token staking and Merkle proofs, the mintSnowman function in Snowman.sol must be restricted.
The most direct and secure approach is to designate SnowmanAirdrop.sol as the exclusive minter for Snowman.sol.
Modify Snowman.sol to implement a "Minter Role":
Add a state variable to store the address of the authorized minterContract.
Add a modifier (onlyMinter) that restricts the execution of mintSnowman to this minterContract address.
Add an onlyOwner function (setMinter) to allow the owner of Snowman.sol to set the address of the SnowmanAirdrop.sol contract as the minterContract.
Operational Flow:
After deploying both Snowman.sol and SnowmanAirdrop.sol, the owner of Snowman.sol calls setMinter(addressOfSnowmanAirdropContract) on Snowman.sol.
When a user (recipient) successfully calls claimSnowman (or claimSnowmanFor) in SnowmanAirdrop.sol:
SnowmanAirdrop.sol verifies the Merkle proof and signatures (if any).
SnowmanAirdrop.sol handles the staking of the user's Snow tokens.
SnowmanAirdrop.sol then calls Snowman.sol::mintSnowman(recipient, verifiedAmount), where verifiedAmount is the number of NFTs the recipient is entitled to, based on their Snow balance from the Merkle proof.
This mitigation ensures:
The mintSnowman function in Snowman.sol is no longer publicly callable.
Only SnowmanAirdrop.sol, after its internal verifications (Merkle proof, Snow staking), can trigger the minting of Snowman NFTs.
This directly implements the README's intent: "Recipients stake their Snow tokens and receive Snowman NFTS equal to their Snow balance in return" via the SnowmanAirdrop contract.
The mint function of the Snowman contract is unprotected. Hence, anyone can call it and mint NFTs without necessarily partaking in the airdrop.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.