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
10 months ago
yeahchibyke Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Appeal created

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

Support

FAQs

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

Give us feedback!