Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Unrestricted Token Minting via earnSnow()

Root + Impact

Description

  • Users can call earnSnow() freely and receive unlimited Snow tokens without any rate limit or access control.


function earnSnow() external {
// mints a fixed or variable amount of tokens to msg.sender
}

Risk

Likelihood:

Very Likely – No on‑chain guardrails exist. As soon as the contract is live, any external actor (EOA or contract) can exploit this by automating repeated calls.

  • with no modifiers or checks. Any address—including bots or malicious actors—can call this every block (or repeatedly within a block via flashloan or reentrancy) to mint unlimited tokens. This breaks the fundamental security guarantee that token supply and distribution are controlled and predictable.

    A malicious actor could spam calls:

    1. earnSnow() → mints N tokens

    2. Immediately repeat step 1 as often as gas allows

    3. Accumulate an arbitrarily large Snow balanc

Impact:

  • High – Inflation of the Snow token destroys tokenomics, devalues existing holdings, and undermines any staking or airdrop logic that relies on relative balances.

Proof of Concept

// Pseudocode using Hardhat
const Snow = await ethers.getContractFactory("Snow");
const snow = Snow.attach(<deployedAddress>);
for (let i = 0; i < 1000; i++) {
await snow.earnSnow(); // no revert, mints again
}
console.log("Balance after 1000 calls:", (await snow.balanceOf(attacker.address)).toString());

Recommended Mitigation

Restrict minting via either:

  • Access control: onlyOwner or MINTER_ROLE

  • Rate limiting: require(block.timestamp > lastMint[msg.sender] + interval)

  • Supply cap: track totalSupply and revert past a hard cap

- remove this code
+ add this code
mapping(address => uint256) public lastMint;
uint256 public constant MINT_INTERVAL = 1 days;
function earnSnow() external {
require(block.timestamp >= lastMint[msg.sender] + MINT_INTERVAL,
"earnSnow: too soon");
lastMint[msg.sender] = block.timestamp;
_mint(msg.sender, DAILY_AMOUNT);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.