Snowman Merkle Airdrop

First Flight #42
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: low
Valid

Users prevented from earning weekly Snow tokens due to Snow::buySnow function

Root + Impact

Description

  • The earnSnow function in the Snow contract should allow everyone to earn Snow tokens once every week.

  • Users cannot earn Snow tokens weekly because when someone buys Snow tokens using the buySnow function, the s_earnTimer is reset. This forces users or even snowToken buyers to wait another week before they can earn Snow again, preventing them from earning free tokens every week.

// Root cause in the codebase with @> marks to highlight the relevant section
// >>> EXTERNAL FUNCTIONS
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;
}
  • Relevant Github link:

https://github.com/CodeHawks-Contests/2025-06-snowman-merkle-airdrop/blob/b63f391444e69240f176a14a577c78cb85e4cf71/src/Snow.sol#L87

Risk

Likelihood:

  • When someone buys Snow tokens using the buySnow function, the s_earnTimer will be reset. Thus preventing others or even themselves from earning free Snow tokens every week.

Impact:

  • Users are prevented from earning free weekly Snow until another full week passes

  • Reduces participation in the system

Proof of Concept

  • This test demonstrated when someone buys Snow tokens, others are prevented from earning free Snow tokens until another week has passed

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Test, console2} from "forge-std/Test.sol";
import {Snow} from "../src/Snow.sol";
import {DeploySnow} from "../script/DeploySnow.s.sol";
import {MockWETH} from "../src/mock/MockWETH.sol";
contract TestSnow is Test {
Snow snow;
DeploySnow deployer;
MockWETH weth;
uint256 FEE;
// Snow tokens buyer address
address jerry;
// Snow tokens earner address
address ed;
function setUp() public {
deployer = new DeploySnow();
snow = deployer.run();
weth = deployer.weth();
FEE = deployer.FEE();
jerry = makeAddr("jerry");
ed = makeAddr("ed");
// Mint WETH to jerry so he can buy Snow tokens
weth.mint(jerry, FEE);
}
function testCanEarnSnow() public {
// Jerry buys 1 Snow token using WETH
vm.startPrank(jerry);
weth.approve(address(snow), FEE);
snow.buySnow(1); // This will reset s_earnTimer
vm.stopPrank();
// Check that the contract received the fee and jerry got his Snow token
assert(weth.balanceOf(address(snow)) == FEE);
assert(snow.balanceOf(jerry) == 1);
// Ed tries to earn Snow immediately, but should revert since 1 week hasn't passed
vm.prank(ed);
vm.expectRevert(); // Should revert because 1 week has not passed for ed
snow.earnSnow();
assert(snow.balanceOf(ed) == 0);
// Fast forward time by 1 week so ed can earn Snow
vm.warp(block.timestamp + 1 weeks);
vm.prank(ed);
snow.earnSnow();
// Ed should now have 1 Snow token
assert(snow.balanceOf(ed) == 1);
}
}

Recommended Mitigation

  • Reset s_earnTimer in buySnow, prevent others from earning free Snow tokens, s_earnTimer is not needed for people buying Snow tokens so can be removed.

- s_earnTimer = block.timestamp;
  • Relevant Github link:

https://github.com/CodeHawks-Contests/2025-06-snowman-merkle-airdrop/blob/b63f391444e69240f176a14a577c78cb85e4cf71/src/Snow.sol#L87
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

buying of snow resets global timer thus affecting earning of free snow

When buySnow is successfully called, the global timer is reset. This inadvertently affects the earning of snow as that particular action also depends on the global timer.

Support

FAQs

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