Snowman Merkle Airdrop

AI First Flight #10
Beginner FriendlyFoundrySolidityNFT
EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Global earn timer allows one user to prevent all other users from claiming weekly SNOW rewards

Root + Impact

Description

Normal behavior

The protocol is designed to allow every eligible user to earn free SNOW once every week. Since this reward is intended to be distributed on a per-user basis, each participant should have an independent cooldown period. A successful claim by one user should not affect the eligibility of any other participant.

Issue

The contract tracks the weekly cooldown using a single global timestamp (s_earnTimer) instead of maintaining a separate timestamp for each user. As a result, whenever one user successfully calls earnSnow(), the cooldown is reset for every participant.

This allows a single active user to continuously claim the weekly reward as soon as the cooldown expires, effectively preventing every other user from earning their weekly SNOW rewards. Consequently, the reward distribution no longer functions independently for each user and can be monopolized by a single participant.

// Root cause in the codebase with @> marks to highlight the relevant section
@> uint256 private s_earnTimer;
function earnSnow() external {
...
@> require(block.timestamp >= s_earnTimer + 1 weeks, "Cooldown");
...
@> s_earnTimer = block.timestamp;
}

Risk

Likelihood:

  • Every successful call to earnSnow() updates the same global cooldown shared by all users.

  • An active participant can repeatedly claim immediately after the cooldown expires, preventing other users from successfully claiming their own weekly rewards.

Impact:

  • Legitimate users can be indefinitely prevented from receiving their weekly SNOW rewards.

  • The reward distribution becomes unfair because a single participant can monopolize the weekly reward mechanism.

Proof of Concept

// Alice claims the weekly reward.
vm.prank(alice);
snow.earnSnow();
// Bob immediately attempts to claim.
vm.prank(bob);
vm.expectRevert();
snow.earnSnow();
// One week later Alice claims again before Bob.
vm.warp(block.timestamp + 1 weeks);
vm.prank(alice);
snow.earnSnow();
// Bob attempts to claim again.
vm.prank(bob);
vm.expectRevert();
snow.earnSnow();

Recommended Mitigation

The cooldown should be tracked individually for each user instead of using a single global timestamp. By storing the last claim timestamp for every participant, each user receives an independent one-week cooldown, preventing one user from blocking all others from earning their weekly SNOW rewards.

- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_lastEarnTimestamp;
function earnSnow() external {
- require(block.timestamp >= s_earnTimer + 1 weeks, "Cooldown");
+ require(
+ block.timestamp >= s_lastEarnTimestamp[msg.sender] + 1 weeks,
+ "Cooldown"
+ );
...
- s_earnTimer = block.timestamp;
+ s_lastEarnTimestamp[msg.sender] = block.timestamp;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!