Snowman Merkle Airdrop

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

Purchase Function Resets Global Cooldown, Permanently Disabling Earning Mechanism

Resetting the global earning cooldown timer within the buySnow function creates a permanent Denial of Service on the earnSnow feature, preventing any user from ever claiming their weekly reward.

Description

  • The protocol intends to allow users to claim one "Snow" token per week via the earnSnow() function. This feature is governed by a global one-week cooldown timer, s_earnTimer, which is updated every time earnSnow() is successfully called.

  • The vulnerability is that the buySnow() function—which is used for purchasing tokens—also resets this same global s_earnTimer. Because any user can call buySnow() at any time, a single purchase will reset the one-week waiting period for all users. This continuous resetting ensures that the time check in earnSnow(), block.timestamp < (s_earnTimer + 1 weeks), will always be true, causing the function to perpetually revert and making it impossible for anyone to ever earn a "Snow" token.

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; //@audit this cause earnSnow never work
emit SnowBought(msg.sender, amount);
}
function earnSnow() external canFarmSnow {
@> if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
// This check will always be true as long as someone buys Snow,
// causing the transaction to revert.
revert S__Timer();
}
_mint(msg.sender, 1);
s_earnTimer = block.timestamp;
}

Risk

Likelihood: High

  • This issue is triggered by any successful call to buySnow(). Normal, expected user activity (purchasing tokens) is sufficient to indefinitely break the earnSnow() function for the entire protocol.


Impact: High

  • Permanent Denial of Service on a Core Feature: The earnSnow() function is rendered completely and permanently unusable. A key advertised feature of the protocol is non-functional.

  • Broken Economic Incentive: The ability to earn a free token is a significant incentive for user participation and retention. The failure of this feature undermines the protocol's value proposition, breaks user trust, and may be considered a failure to deliver on the protocol's promise.

Proof of Concept

The following test demonstrates the DoS scenario. ashley successfully earns a token, starting the one-week cooldown. Just before the week is over, another user, jerry, buys a token. This resets the timer. When ashley tries to claim her token after the original week has passed, the transaction reverts because the timer was reset by jerry's purchase.

function testCanNotEarnSnowAfterBuySnow() public {
vm.prank(ashley);
snow.earnSnow();
assert(snow.balanceOf(ashley) == 1);
vm.warp(block.timestamp + 1 weeks);
vm.startPrank(jerry);
weth.approve(address(snow), FEE);
snow.buySnow(0);
vm.stopPrank();
vm.prank(ashley);
vm.expectRevert();
snow.earnSnow();
assert(snow.balanceOf(ashley) == 1);
}

Recommended Mitigation

The state variables for different protocol features should be kept separate to prevent unintended interactions. The buySnow() function should not modify the timer that exclusively governs the earnSnow() mechanic.

Remove the line that resets s_earnTimer from the buySnow() function.

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; //@audit this cause earnSnow never work
emit SnowBought(msg.sender, amount);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 5 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.