Snowman Merkle Airdrop

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

Incorrect Use of Global Cooldown Timer in earnSnow Results in One-Claim-Per-Week Limitation

Author Revealed upon completion

Root + Impact

Description

However, the current implementation uses a single global timestamp variable s_earnTimer to enforce the weekly cooldown. This means once any user claims, all others are blocked for a full week — turning a per-user reward into a global "first-come-first-serve" mechanic.

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;
}

Risk

Likelihood:

  • This will occur every time more than one user attempts to claim earnSnow() within the same 1-week period.

Impact:

  • Only the first user to call earnSnow() each week succeeds; all other users are blocked until the timer resets.

  • Causes unfairness and poor UX, especially for late claimers who are unaware of the restriction.

Recommended Mitigation

Replace the single s_earnTimer global variable with a per-user mapping to track individual claim cooldowns:

mapping(address => uint256) public s_earnTimer;

Then update the earnSnow() function as follows:

function earnSnow() external canFarmSnow {
if (block.timestamp < s_earnTimer[msg.sender] + 1 weeks) {
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer[msg.sender] = block.timestamp;
}

At the same time, the buySnow() function should be as follows:

function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
// s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
3 days ago
yeahchibyke Lead Judge about 8 hours ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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