Snowman Merkle Airdrop

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

Global Timer Misdesign Enables Permanent Denial of Weekly Rewards

Root + Impact

Description

  • The earnSnow function should allow individual users to claim 1 Snow token weekly during the 12-week farming period, with each user having their own independent cooldown timer.

  • The s_earnTimer is implemented as a global variable that gets reset to the current timestamp on every single call to buySnow or earnSnow, continuously pushing the cooldown period forward for all users and effectively preventing anyone from ever successfully claiming weekly rewards.

// Root cause in the codebase with @> marks to highlight the relevant section
solidity
uint256 private s_earnTimer;// @> Global timer shared across all users
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;// @> Resets global timer affecting all users
emit SnowBought(msg.sender, amount);
}
function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {// @> Checks global timer
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp;// @> Resets global timer for all users
}

Risk

Likelihood:

  • Every single call to buySnow or earnSnow by any user resets the global timer for all users

  • Normal legitimate usage of buySnow continuously prevents all users from calling earnSnow

  • Attackers can sustain indefinite denial by calling buySnow once per week at minimal cost

  • No coordination or timing required - any protocol interaction triggers the vulnerability

Impact:

  • The earnSnow reward mechanism becomes permanently unusable for the entire 12-week farming period

  • All users are denied their expected weekly token distributions worth significant economic value

  • Protocol fails to deliver core tokenomics functionality, leading to complete loss of user trust


Proof of Concept

// Attacker griefs all users for 12 weeks with minimal cost
solidity
// Continuous denial attack over entire farming period
Snow snow = Snow(snowAddress);
// Attacker strategy: call buySnow once per week to reset timer
for(uint week = 0; week < 12; week++) {
// Wait 6 days and 23 hours (just before 1 week mark)
vm.warp(block.timestamp + 6 days + 23 hours);
// Reset global timer with minimal purchase
snow.buySnow{value: s_buyFee}(1);
// All users now must wait another full week// Any user trying earnSnow() will revert with S__Timer()
}
// Result: earnSnow() remains unusable for entire 12-week period// Cost to attacker: 12 * s_buyFee (minimal)// Damage: All users permanently denied weekly rewards

Recommended Mitigation

```diff
// Replace global timer with per-user timer to isolate cooldowns and prevent shared griefing
diff
- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_userEarnTimer;
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;
+ s_userEarnTimer[msg.sender] = block.timestamp;
emit SnowBought(msg.sender, amount);
}
function earnSnow() external canFarmSnow {
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
+ if (s_userEarnTimer[msg.sender] != 0 && block.timestamp < (s_userEarnTimer[msg.sender] + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
+ s_userEarnTimer[msg.sender] = block.timestamp;
}
```
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months 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.