Snowman Merkle Airdrop

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

Snow::s_earnTimer is a single global timer - one earn or any buySnow blocks free earn for everyone

Root + Impact

Description

  • Each user is supposed to be able to earn free Snow once per week, on their own independent weekly schedule.

  • s_earnTimer is a single global storage variable rather than a per-user mapping. earnSnow reverts while block.timestamp < s_earnTimer + 1 weeks, and both earnSnow and buySnow overwrite s_earnTimer = block.timestamp. As a result only one address in the entire system can earn per week, and any buySnow call resets the window for everyone. (Separately, earnSnow mints 1 wei instead of 1e18, so the "free token" is 1e-18 of a token.)

```solidity
// src/Snow.sol
@> if (block.timestamp < s_earnTimer + 1 weeks) revert S__Timer();
_mint(msg.sender, 1); // mints 1 wei, not 1 whole token
@> s_earnTimer = block.timestamp; // GLOBAL timer, shared by all users; also set by buySnow()
```

Because the timer is global and not keyed by msg.sender, one user's action sets the cooldown for the whole protocol.

Risk

Likelihood:

  • Occurs the moment any single user calls earnSnow (or anyone calls buySnow) in a given week, which sets the global timer and reverts the next caller regardless of that caller's own history.

  • An attacker repeatedly calling buySnow keeps the timer pinned indefinitely, continuously griefing the free-earn feature for the entire user base at negligible cost.

Impact:

  • Denial-of-service of the core "earn weekly for free" mechanism for all users — at most one address can ever earn in any given week.

  • The advertised per-user weekly free earn is unusable for everyone but the first caller.

Proof of Concept

User A earns successfully, which sets the global timer. User B — who has never earned — is immediately blocked:

```solidity
function test_global_timer_blocks_other_users() public {
vm.prank(alice);
snow.earnSnow(); // sets the GLOBAL s_earnTimer

vm.prank(bob);
vm.expectRevert(); // S__Timer — bob never earned, yet he is blocked
snow.earnSnow();

}
```

The same revert occurs for every other user for a full week, and any buySnow call restarts the week.

Recommended Mitigation

Make the timer per-user by keying it on msg.sender, mint a whole token, and stop resetting it in buySnow:

```diff

  • uint256 private s_earnTimer;

  • mapping(address => uint256) private s_earnTimer;

    function earnSnow() external {


  • if (block.timestamp < s_earnTimer + 1 weeks) revert S__Timer();

  • _mint(msg.sender, 1);

  • s_earnTimer = block.timestamp;

  • if (block.timestamp < s_earnTimer[msg.sender] + 1 weeks) revert S__Timer();

  • _mint(msg.sender, 1e18);

  • s_earnTimer[msg.sender] = block.timestamp;

    }
    // remove the s_earnTimer assignment from buySnow() entirely
    ```

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 16 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!