Snowman Merkle Airdrop

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

The `earnSnow` function can be DoS

Root + Impact

The earnSnow() function uses a single shared timer (s_earnTimer) for all users, which can be indefinitely reset by any call to buySnow(). This creates a Denial-of-Service (DoS) blocking users from earningSnow.

Description

Any user (or attacker) can prevent all others from earning snow by repeatedly calling buySnow(), resetting s_earnTimer to the latest block.timestamp. Legitimate users may never claim rewards within the 12-week farming period due to constant resets.

The issue persists whether the reset is malicious (intentional attacks) or accidental (normal user activity).

Key Issues:

  • A single storage variable (s_earnTimer) controls rewards for all users, creating a central point of failure.

  • The timer resets for everyone when any user calls buySnow(), violating the principle of fair reward distribution.

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);
}
&&
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: HIGH

  • Every time a user buys snow, the timer will be reset.

  • Every time a user earnSnow, the timer will be reset.

Impact:

  • User can be temporarily DoS, reducing their ability to claim snow.

  • User can be completely DoS, if the timer is repeatedly reset until the deadline.

Proof of Concept

Recommended Mitigation

Replace the shared timer with a per-user mapping to isolate earning schedules:

// Add to contract state
+ mapping(address => uint256) public s_userEarnTimers;
function earnSnow() external canFarmSnow {
+ uint256 userTimer = s_userEarnTimers[msg.sender];
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
+ if (userTimer != 0 && block.timestamp < (userTimer + 1 weeks)) {
revert S__Timer();
}
s_userEarnTimers[msg.sender] = block.timestamp;
_mint(msg.sender, 1);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 10 days ago
Submission Judgement Published
Validated
Assigned finding tags:

buying of snow resets global timer thus affecting earning of free snow

When buySnow is successfully called, the global timer is reset. This inadvertently affects the earning of snow as that particular action also depends on the global timer.

Support

FAQs

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