Snowman Merkle Airdrop

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

Global Timer Vulnerability

Root + Impact

Description

  • The earnSnow() function is designed to allow users to earn 1 Snow token per week, creating a fair distribution mechanism where each user can claim tokens on their own weekly schedule.

  • The function uses a single global timer (s_earnTimer) shared across all users instead of individual per-user timers, which severely restricts token distribution by allowing only one user globally to earn tokens each week.

// Root cause in the codebase with @> marks to highlight the relevant section
function earnSnow() external canFarmSnow {
if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) { // @> Global timer blocks ALL users
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp; // @> Timer reset affects ALL future users
}

Risk

Likelihood:

  • Every user attempting to earn tokens after the first user each week will be blocked by the global timer - so very likely.

  • The vulnerability occurs automatically from the first earnSnow() call and affects all subsequent users until the next weekly cycle - again high liklihood this happens.

Impact:

  • Token distribution is reduced by approximately 99% compared to intended design (from N users × 52 weeks to just 52 tokens per year total) - almost grinds the contract to a halt and will severely limit involvement (against the spirit of the contract)

  • Creates an unfair competitive environment where only the fastest user each week can earn tokens while all others are permanently blocked

  • Breaks the fundamental tokenomics of the protocol by severely limiting token supply and user participation

Proof of Concept

  • Demonstrates that with 3 users over 3 weeks, only 3 tokens are distributed instead of the expected 9 tokens

  • Shows how the global timer prevents multiple users from earning in the same week

  • Proves that users cannot earn on their individual schedules as intended

function testGlobalTimerVulnerability() public {
// Week 0: Alice earns first token
vm.prank(alice);
snow.earnSnow(); // SUCCESS - timer was 0
// Bob tries to earn in same week
vm.prank(bob);
vm.expectRevert(); // BLOCKED by global timer
snow.earnSnow();
// Week 1: Wait 1 week, now Bob can earn
vm.warp(block.timestamp + 1 weeks + 1);
vm.prank(bob);
snow.earnSnow(); // SUCCESS - timer expired
// Alice tries to earn again
vm.prank(alice);
vm.expectRevert(); // BLOCKED again by Bob's timer reset
snow.earnSnow();
// Result: Only 2 tokens distributed instead of 4 (2 users * 2 weeks)
assertEq(snow.balanceOf(alice), 1);
assertEq(snow.balanceOf(bob), 1);
// Should have been 2 tokens each
}

Recommended Mitigation

  • Replace the global timer with individual per-user timers using a mapping

  • Allow each user to track their own earning schedule independently

  • Maintain the 1-week cooldown per user while enabling concurrent earning across different users

- remove this code
+ add this code
- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_userEarnTimer;
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
yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Appeal created

cheesesteak Submitter
3 months ago
yeahchibyke Lead Judge
3 months ago
cheesesteak Submitter
3 months ago
yeahchibyke Lead Judge
3 months ago
yeahchibyke Lead Judge 2 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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