Snowman Merkle Airdrop

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

earnSnow Can Be Called Multiple Times Without Waiting

Root + Impact

The earnSnow function only checks if 1 week has passed since the last call (s_earnTimer). However, this check is per-address but allows calling twice in the same block if s_earnTimer is 0 (first call). Additionally, if two calls happen in the same block or before s_earnTimer is set, the check can be bypassed

Description

  • The earnSnow function is designed to allow users to earn 1 Snow token for free once per week. The timer check uses s_earnTimer which is set after the first call. However, the logic allows calling multiple times in quick succession because:

    1. First call: s_earnTimer is 0, so check passes

    2. The function doesn't prevent multiple calls in the same transaction// Root cause in the codebase with @> marks to highlight the relevant section

Root Cause

The earnSnow function only checks if 1 week has passed since the last call (s_earnTimer). However, this check is per-address but allows calling twice in the same block if s_earnTimer is 0 (first call). Additionally, if two calls happen in the same block or before s_earnTimer is set, the check can be bypassed.

Risk

Likelihood:

Users can exploit this by calling earnSnow() multiple times in a single transaction or block to accumulate more than 1 token per week.

Impact:

  • Users can accumulate unlimited Snow tokens without waiting

  • Undermines the tokenomics of the protocol

  • Allows early users to claim more NFTs than intended

Proof of Concept

function testBug2_EarnSnowCanBeCalledMultipleTimesImmediately() public {
vm.prank(alice);
snow.earnSnow();
assertEq(snow.balanceOf(alice), 1);
vm.prank(alice);
snow.earnSnow();
assertEq(snow.balanceOf(alice), 2);
// Alice got 2 Snow tokens in the same block!
}

Proof of Concept Explanation:

  1. Alice calls earnSnow() - timer is 0 so check passes, she gets 1 token

  2. Alice calls earnSnow() again immediately - timer was just set but the function doesn't check if the caller already claimed in this transaction

  3. Alice receives 2 tokens instead of 1

  4. This breaks the intended "once per week" mechanic

Recommended Mitigation:

Use a mapping to track per-user last claim time:

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

Lead Judging Commences

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