Snowman Merkle Airdrop

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

Global Timer Prevents Multiple Users From Weekly Snow Claiming in `Snow.sol::earnSnow`

Root + Impact

Root: The earnSnow function uses a single global s_earnTimer variable to track the one-week cooldown period, causing the timer to reset for all users whenever any single user successfully claims Snow tokens.

Impact: Only one user can claim free Snow tokens per week across the entire protocol, as the first successful claim blocks all other users until the next week cycle begins, creating an unfair race condition and denying legitimate claims.

Description

  • Normal Behavior: Each user should be able to claim 1 free Snow token per week independently, with individual cooldown periods that don't interfere with other users' claiming abilities.

  • Specific Issue: The global s_earnTimer is shared across all users, so when one user successfully calls earnSnow(), the timer resets and prevents all other users from claiming until another full week passes, effectively limiting the entire protocol to one claim per week instead of one claim per user per week.

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 call to earnSnow() triggers this issue as the global timer always updates

  • Users will naturally compete to be first each week, creating predictable race conditions

  • The bug affects 100% of users except the single weekly winner, making it immediately noticeable

Impact: High

  • Denial of Service: Vast majority of users are prevented from accessing their intended weekly Snow token rewards

  • Unfair Distribution: Protocol favors users with faster transactions/higher gas fees rather than equal access

  • Economic Loss: Users lose expected token rewards, reducing protocol participation and value

Proof of Concept

Add the following test to TestSnow.t.sol and run it, the test illustrate the flow that only one user can get rewarded in a week.

function testGlobalTimerBlocksMultipleUsers() public {
// Setup multiple users
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
address user3 = makeAddr("user3");
// User1 claims first Snow token successfully
vm.prank(user1);
snow.earnSnow();
assert(snow.balanceOf(user1) == 1);
// User2 tries to claim immediately after user1 - should fail due to global timer
vm.prank(user2);
vm.expectRevert(); // Fails because s_earnTimer was just updated by user1
snow.earnSnow();
// User3 also cannot claim for the same reason
vm.prank(user3);
vm.expectRevert(); // Fails because s_earnTimer blocks everyone
snow.earnSnow();
// Fast forward 1 week
vm.warp(block.timestamp + 1 weeks);
// Now user2 can claim (first one after timer expires)
vm.prank(user2);
snow.earnSnow();
assert(snow.balanceOf(user2) == 1);
// But user3 still cannot claim because user2 just reset the global timer
vm.prank(user3);
vm.expectRevert(); // Still fails because user2 reset s_earnTimer
snow.earnSnow();
// Verify only 2 users got tokens despite 3 users trying over 2 weeks
assert(snow.balanceOf(user1) == 1);
assert(snow.balanceOf(user2) == 1);
assert(snow.balanceOf(user3) == 0); // user3 never got tokens
}

Recommended Mitigation

Replace the global timer with individual user timers to allow each user to claim Snow tokens independently on their own weekly schedule.

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;
+ if (s_userLastClaim[msg.sender] != 0 && block.timestamp < (s_userLastClaim[msg.sender] + 1 weeks)) {
+ revert S__Timer();
+ }
+ _mint(msg.sender, 1);
+ s_userLastClaim[msg.sender] = block.timestamp;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
5 months ago
yeahchibyke Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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