Root + Impact
Description
-
The earnSnow()
function in the Snow
contract uses a global timestamp variable s_earnTimer
to enforce a cooldown period between token mints. However, this timer is shared across all users. This allows any user to call earnSnow()
and reset the timer, thereby preventing all other users from calling it until 1 week has passed.
-
This logic creates a denial-of-service condition where a malicious user can block others from earning snow tokens by invoking the function themselves at regular intervals.
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 High
Likelihood:
-
This occurs when the first user calls earnSnow()
-
The s_earnTimer
variable is shared across all users, making the system vulnerable to griefing or accidental lockouts by a single address.
Impact:
Proof of Concept: Below is a test case to demonstrate the issue using Foundry:
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import "../src/Snow.sol";
contract SnowTest is Test {
Snow public snow;
address public user1 = vm.addr(1);
address public user2 = vm.addr(2);
function setUp() public {
snow = new Snow(address(0x1234), 1, address(0x5678));
vm.warp(block.timestamp);
}
function test_GlobalTimerBlocksOtherUsers() public {
vm.startPrank(user1);
snow.earnSnow();
vm.stopPrank();
vm.startPrank(user2);
vm.expectRevert();
snow.earnSnow();
vm.stopPrank();
}
}
Recommended Mitigation : Use a per user mapping to store cooldowns individually:
- remove this code
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;
}
+ add this code
mapping(address => uint256) private s_lastEarned;
function earnSnow() external canFarmSnow {
if (s_lastEarned[msg.sender] != 0 && block.timestamp < (s_lastEarned[msg.sender] + 1 weeks)) {
revert S__Timer();
}
_mint(msg.sender, 1);
s_lastEarned[msg.sender] = block.timestamp;
}