Snowman Merkle Airdrop

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

Global Timer Blocks All Users From Earning Snow Tokens

Description

  • The Snow token is designed to allow users to earn one free token per week during the farming period, giving each user an equal opportunity to participate.

  • The contract uses a single global s_earnTimer variable instead of tracking each user's last earning time individually. This means when any user earns a token, all other users are blocked from earning for a week, regardless of their individual earning history.

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:

  • Any user earning a free token will reset the global timer, blocking all other users from earning.

  • In a contract with multiple active users, this will occur frequently, potentially every time the timer expires.

Impact:

  • Most users will be unable to earn free tokens as intended, as the first user to claim after the timer expires will reset it for everyone.

  • This creates an unfair "race condition" where only the fastest user after each timer expiration can earn tokens.

  • The protocol's stated functionality of allowing each user to earn one free token per week is severely compromised.

Proof of Concept

// Test demonstrating how one user blocks another
function testGlobalTimerBlocksOtherUsers() public {
// Fast forward to enable earning for all users
vm.warp(block.timestamp + 1 weeks);
// First user earns a token successfully
vm.prank(alice);
snow.earnSnow();
assert(snow.balanceOf(alice) == 1);
// Second user tries immediately after but is blocked
// even though they personally have never earned before
vm.prank(bob);
vm.expectRevert(); // Will revert with S__Timer
snow.earnSnow();
// Bob must wait a full week after Alice earned
vm.warp(block.timestamp + 1 weeks);
vm.prank(bob);
snow.earnSnow(); // Now succeeds
assert(snow.balanceOf(bob) == 1);
}

Recommended Mitigation

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