Snowman Merkle Airdrop

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

Global `Snow.sol::s_earnTimer` Locks Rewards for All Users

[Medium] Global Snow.sol::s_earnTimer Locks Rewards for All Users


Description

The s_earnTimer in Snow.sol is implemented as a single, global state variable. This means that when any single user successfully calls the earnSnow() function, this s_earnTimer is updated, and the cooldown period (1 week) is applied to all users. Consequently, if one user earns Snow, no other user can earn for the entire 1-week duration, irrespective of their individual earning history.


Risk

This design creates a significant griefing risk. A malicious or uncoordinated user could repeatedly call earnSnow() (or multiple accounts could collaborate), continuously resetting the global timer. This effectively prevents the entire community from earning Snow tokens for extended periods, severely undermining the intended reward mechanism and user engagement.


Proof of Concept

1.  User A calls `earnSnow()`.
2.  `s_earnTimer` is updated to `block.timestamp`.
3.  User B, even if they have never earned before, attempts to call `earnSnow()`.
4.  The transaction for User B reverts with `S__Timer()`, because `block.timestamp < s_earnTimer + 1 weeks` is true for everyone.

Add the following test function in `TestSnow.t.sol` to demonstrate the vulnerability:
```javascript
function testGlobalTimerGriefing() public {
    vm.prank(ashley);
    snow.earnSnow(); // Ashley successfully earns, updating global timer
    assertEq(snow.balanceOf(ashley), 1);

    vm.prank(jerry);
    vm.expectRevert(); // Jerry attempts to earn immediately after, but is blocked
    snow.earnSnow();
}
```

Recommended Mitigation

Replace the single global s_earnTimer with individual timers for each user. This can be achieved by using a mapping mapping(address => uint256) private s_userEarnTimer; to store the last earned timestamp for each address.

```diff
+ mapping(address => uint256) private s_userEarnTimer; // New: Per-user timer mapping

 function earnSnow() external canEarnSnow {
-    if (block.timestamp < s_earnTimer + 1 weeks) {
+    if (block.timestamp < s_userEarnTimer[msg.sender] + 1 weeks) { // Check specific user's timer
         revert S__Timer();
     }
     _mint(msg.sender, 1); // Or whatever the earning amount is
-    s_earnTimer = block.timestamp; // Problematic: Updates global timer
+    s_userEarnTimer[msg.sender] = block.timestamp; // Recommended: Update user's specific timer
     emit SnowEarned(msg.sender, 1);
 }
```
Updates

Lead Judging Commences

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

Support

FAQs

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