Snowman Merkle Airdrop

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

DoS in `Snow.earnSnow` due to a globally set s_earnTimer variable, blocking most users from earning snow tokens.

DoS in Snow.earnSnow due to a globally set s_earnTimer variable, blocking most users from earning snow tokens.

Description

The s_earnTimer is a global variable that applies to all users. Thus, one user's call to earnSnow resets the timer for all users. This only allows one user per week to call earnSnow and earn snow tokens.

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;
}

Risk

Likelihood: HIGH

  • This will occur with 99% of users attempting to call earnSnow, as only one user per week will be able to call earnSnow.

Impact: MEDIUM

  • This limitation severely and detrimentally affects the intended use of the overall protocol functionality. While funds aren't directly at risk, most users will be unable to participate in earning snow tokens.

Proof of Concept

Add and run this test testGlobalEarnTimerBlocksUserB in the TestSnow.t.sol file.

function testGlobalEarnTimerBlocksUserB() public {
// Manipulate block timestamp by one week to start
vm.warp(block.timestamp + 1 weeks);
// User A calls earnSnow
vm.prank(ashley);
snow.earnSnow();
uint256 earnTime = block.timestamp; // Record the time when User A called earnSnow
// Advance time by 1 day
vm.warp(earnTime + 1 days);
// User B tries to call earnSnow within one week of User A's call
vm.prank(jerry);
vm.expectRevert(abi.encodeWithSelector(Snow.S__Timer.selector));
snow.earnSnow();
// Verify User B's balance remains 0
assertEq(snow.balanceOf(jerry), 0, "User B balance should be 0 after failed earnSnow");
// Advance time to 1 week + 1 second after User A's call
vm.warp(earnTime + 1 weeks + 1 seconds);
// User B should now be able to call earnSnow without revert
vm.prank(jerry);
snow.earnSnow();
assertEq(snow.balanceOf(jerry), 1, "User B should have 1 token after successful earnSnow");
}

Recommended Mitigation

Initialize s_earnTimers as a private mapping at the top of the contract. In the snow.earnSnow function itself, remove each instance of s_earnTimer and replace it with s_earnTimers[msg.sender].

Here is how the s_earnTimer variable should be handled:

+ mapping(address => uint256) private s_earnTimers;
- uint256 private s_earnTimer;

Here is how the snow.earnSnow function should be edited:

function earnSnow() external canFarmSnow {
+ if (s_earnTimers[msg.sender] != 0 && block.timestamp < s_earnTimers[msg.sender] + 1 weeks) {
+ revert S__Timer();
+ }
+ _mint(msg.sender, 1);
+ s_earnTimers[msg.sender] = block.timestamp;
- if (s_earnTimer != 0 && block.timestamp < (s_earnTimer + 1 weeks)) {
- revert S__Timer();
- }
- _mint(msg.sender, 1);
- s_earnTimer = block.timestamp;
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge
3 months ago
yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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