Snowman Merkle Airdrop

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

`Snow::s_earnTimer` is global for all users. This prevents multiple users from earning Snow tokens independently once per week. ## Description

Snow::s_earnTimer is global for all users. This prevents multiple users from earning Snow tokens independently once per week.

Description

Normal Behavior:

  • Each user should be able to claim 1 Snow token via the earnSnow() function once per week, independently of other users' activity.

Issues:

  • In the Snow contract, the s_earnTimer variable is global and is updated both in buySnow() and earnSnow(). This causes that, even if 7 days have passed, only one user can claim a Snow token per week. When a user buys or claims, the timer is reset for everyone, blocking others.

function buySnow(uint256 amount) external payable canFarmSnow {
if (msg.value == (s_buyFee * amount)) {
_mint(msg.sender, amount);
} else {
i_weth.safeTransferFrom(msg.sender, address(this), (s_buyFee * amount));
_mint(msg.sender, amount);
}
@> s_earnTimer = block.timestamp;
emit SnowBought(msg.sender, amount);
}
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: Medium

  • Reason 1
    There is no direct risk of loss of funds, but there is a clear disruption in protocol functionality.
    If the goal is to distribute weekly rewards fairly, this logic is completely broken. Any interaction (buy or earn) resets the global timer, blocking other users from claiming. This makes the expected system behavior (weekly farm per user) impossible.

Impact: Medium

  • Impact 1
    This limitation prevents almost all users from receiving the token for free, forcing them to use the paid function (buySnow) to obtain it.
    This barrier can negatively affect trust, participation, and usability of the protocol, as only one user can receive the token weekly for free.

Proof of Concept

  1. Alice makes the first earnSnow() and her balance is checked.

  2. s_earnTimer is updated for everyone.

  3. Bob immediately tries to call earnSnow() and it reverts.

  4. Advance time by 1 week.

  5. Clara can claim a Snow token.

function test_EarnTimer() public {
address alice = helper.alice();
vm.prank(alice);
snow.earnSnow(); // Alice can earn (first time, timer == 0)
uint256 aliceSnowBalance = snow.balanceOf(alice);
assertEq(aliceSnowBalance, 1);
address bob = helper.bob();
vm.prank(bob);
vm.expectRevert(Snow.S__Timer.selector);
snow.earnSnow(); // Reverts because the timer was updated for everyone
address clara = helper.clara();
vm.warp(block.timestamp + 1 weeks);
vm.prank(clara);
snow.earnSnow(); // Now Clara can earn
uint256 claraSnowBalance = snow.balanceOf(clara);
assertEq(claraSnowBalance, 1);
}

Recommended Mitigation

This change ensures that each user has their own timer to claim tokens, maintaining the individual weekly farm logic.

- uint256 private s_earnTimer;
+ mapping(address account => uint256 time) private s_earnTimer;
function earnSnow() external canFarmSnow {
+ uint256 time = s_earnTimer[msg.sender];
+ uint256 currentTime = block.timestamp;
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
+ if (time != 0 && block.timestamp < (time + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
+ s_earnTimer[msg.sender] = currentTime;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 29 days ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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