Snowman Merkle Airdrop

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

Denial of Service in Snow Token's Free Claim Mechanism

Root + Impact

The implementation creates a Denial of Service (DOS) for free token earners by forcing them to wait 1 week after any user buys tokens, effectively blocking the free claim mechanism for all users.

Description

The Snow token contract implements two separate mechanisms for acquiring tokens:

  1. Buying tokens (can be done anytime)

  2. Claiming free tokens (once per week)
    However, the implementation creates a DOS vulnerability by using a global s_earnTimer that is updated in the buySnow() function. This means that when any user buys tokens, it blocks all users from claiming (earning) free tokens for 1 week, even if they have never claimed before.

@> uint256 private s_earnTimer; // Global timer that affects all users
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; // Affects all users
emit SnowBought(msg.sender, amount);
}

Risk

Likelihood:

HIGH - The DOS can be triggered by any user simply buying tokens, and it affects all users.

Impact:

HIGH - The impact is severe because:

  • Creates a permanent DOS for free token earners

  • Any user can block all others from claiming free tokens

  • Malicious users can keep buying tokens to maintain the DOS

Proof of Concept

The following test demonstrates the vulnerability:

function testCanBuySnowAndClaimFreeToken() public {
vm.prank(victory);
//victory buys snow
snow.buySnow{value: FEE}(1);
assert(snow.balanceOf(victory) == 1);
//victory trys to claim 1 token immediately after buying but reverts and still shows 1 token
vm.expectRevert();
snow.earnSnow();
assert(snow.balanceOf(victory) == 1);
vm.stopPrank();
//ashley tries to earn snow on the day of victory's purchase but she fails
vm.prank(ashley);
vm.expectRevert();
snow.earnSnow();
assert(snow.balanceOf(ashley) == 0); // DOS: Ashley is blocked despite never claiming
//wait for 1 week victory claims 1 free token
vm.warp(block.timestamp + 1 weeks);
vm.prank(victory);
snow.earnSnow();
assert(snow.balanceOf(victory) == 2);
//Ashley then tries again to earn a free token after victory's 1 week pass
//This is the only time she claim a free token
vm.warp(block.timestamp + 2 weeks);
vm.prank(ashley);
snow.earnSnow();
assert(snow.balanceOf(ashley) == 1);
}

When Victory buys Snow tokens, it sets the global timer,Ashley is blocked from claiming free tokens even though she never claimed before. Ashley must wait for Victory's timer to expire before she can claim. This creates an unfair advantage and a permanent DOS if users keep buying tokens.

Recommended Mitigation

Replace the global timer with a per-user timer using a mapping:

- uint256 private s_earnTimer;
+ mapping(address => uint256) private s_earnTimers;
function buySnow(uint256 amount) external payable canFarmSnow {
//fee code
}
- s_earnTimer = block.timestamp;
}
function earnSnow() external canFarmSnow {
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
// revert S__Timer();
+ if (s_earnTimers[msg.sender] != 0 &&
block.timestamp < (s_earnTimers[msg.sender] + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
+ s_earnTimers[msg.sender] = block.timestamp;
}

This fix ensures

  • Each user has their own independent timer

  • Buying tokens doesn't affect free claims

  • Users can only be blocked by their own previous claims

  • The free claim mechanism is protected from DOS attacks

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.