Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Unlimited Snow token mint via global earn timer

Root + Impact

Description

Normal Behavior

Each user should be able to earn 1 SNOW per week using earnSnow().
The cooldown should be enforced per user, so one user’s action does not affect others.

Issue

The contract uses a single global timer (s_earnTimer) to enforce the weekly cooldown for all users.
This allows any address to mint SNOW repeatedly every week without per-user restriction, and also enables griefing by resetting the timer.

// Root cause with @> markers
uint256 private s_earnTimer;
function earnSnow() external canFarmSnow {
@> if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
@> s_earnTimer = block.timestamp;
}

Because s_earnTimer is shared globally:

  • It does not track per-user cooldown

  • Any user can mint SNOW once per week forever

  • Supply inflation is unbounded during the farming period

Risk

Likelihood:

  • Reason 1: earnSnow() is permissionless and callable by anyone

  • Reason 2: Cooldown enforcement is global, not per user

  • Reason 3: No cap on how many unique addresses can call earnSnow()

Impact:

  • Impact 1: Unlimited SNOW can be minted by sybil addresses

  • Impact 2: Token supply inflation breaks protocol economics

  • Impact 3: Since SNOW is staked for Snowman NFTs, this leads to NFT inflation

Proof of Concept

Because the cooldown is global, each address can mint once per week without restriction.

1.Alice calls earnSnow() → receives 1 SNOW
2.One week later, Bob calls earnSnow() → receives 1 SNOW
3.Repeat with unlimited addresses
4.Total SNOW supply increases linearly with number of addresses

This behavior continues until FARMING_DURATION ends.

Recommended Mitigation

Track cooldown per user, not globally.

- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_lastEarn;
function earnSnow() external canFarmSnow {
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
+ if (
+ s_lastEarn[msg.sender] != 0 &&
+ block.timestamp < s_lastEarn[msg.sender] + 1 weeks
+ ) {
revert S__Timer();
}
_mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
+ s_lastEarn[msg.sender] = block.timestamp;
}

This ensures:

  • Each user has an independent cooldown

  • SNOW minting is rate-limited as intended

  • Tokenomics remain predictable

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!