Snowman Merkle Airdrop

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

Global timer in earnSnow prevents fair token distribution and enables denial of service

Description:

The earnSnow function in Snow.sol uses a global timer variable s_earnTimer that is shared across all users, creating a critical design flaw that prevents fair token distribution. When any user calls earnSnow() or buySnow(), the global timer is updated, blocking all other users from earning free tokens for the next week.

In Snow.sol:

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; // Global timer updated by any user
}

The timer is also updated in buySnow(), further complicating the issue. This design means that in best-case scenario only one user per week across the entire protocol can earn free tokens, while all others are forced to purchase tokens with ETH/WETH. In the worst-case scenario, no free tokens can be distributed if the function buySnow()is called more than once a week.

Attack path:

  1. Natural blocking scenario:

    • Alice calls earnSnow() on day 1, successfully mints 1 token

    • s_earnTimer is set to current timestamp

    • Bob tries to call earnSnow() on day 2, transaction reverts with S__Timer() error

    • All users are blocked from earning tokens until day 8

  2. Intentional denial of service:

    • Attacker monitors the blockchain for successful earnSnow() calls

    • Exactly 7 days later, attacker immediately calls earnSnow() or makes a minimal buySnow() purchase

    • This resets the global timer, blocking legitimate users for another week

    • Attacker can repeat this indefinitely with minimal cost

Impact:

Only 1 user per week across the entire protocol can earn free tokens, violating the intended design

Any user can block all others from earning tokens for a full week

Recommended Mitigation:

Implement individual user timers instead of a global timer to allow each user to earn tokens independently

And delete timer update in buySnow()

mapping(address => uint256) private s_userEarnTimer;
function earnSnow() external canFarmSnow {
uint256 userLastEarn = s_userEarnTimer[msg.sender];
if (userLastEarn != 0 && block.timestamp < (userLastEarn + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
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.