Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Global faucet freeze once daily limit is hit

Root + Impact

Description

  • The contract imposes a daily limit on the amount of claims that need to expire within 24 hours so that it can generate a new batch of claims for the current day. The reset logic checks whether 24 hours have passed after lastFaucetDripDay and sets dailyClaimCount to 0

  • The daily limit check is performed before the reset logic in the function execution flow. Once the limit is reached, all claims immediately fail at the check and never let the code reach the reset logic. This leaves the contract infinitely stuck after the first day the limit was reached

function claimFaucetTokens() public {
// ... initial checks ...
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
@> if (dailyClaimCount >= dailyClaimLimit) {
@> revert RaiseBoxFaucet_DailyClaimLimitReached(); // Reverts before reset can occur
@> }
// ... ETH drip logic ...
/**
* @param lastFaucetDripDay tracks the last day a claim was made
* @notice resets the @param dailyClaimCount every 24 hours
*/
@> if (block.timestamp > lastFaucetDripDay + 1 days) {
@> lastFaucetDripDay = block.timestamp;
@> dailyClaimCount = 0; // Never reached once limit is hit
@> }
lastClaimTime[faucetClaimer] = block.timestamp;
@> dailyClaimCount++; // Permanently stuck at dailyClaimLimit
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}

Risk

Likelihood:

  • This can be triggered naturally after exactly dailyClaimLimit number of claims occur on any given day through normal contract usage

  • No priviledge access required

Impact:

  • The Core functionality is permanently disabled for all users with all future claim attempts reverting

  • Complete loss of autonomous faucet functionality

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console} from "forge-std/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
contract FaucetFreezeTest is Test {
RaiseBoxFaucet public faucet;
address public owner = makeAddr("owner");
function setUp() public {
vm.startPrank(owner);
faucet = new RaiseBoxFaucet(
"TestToken",
"TEST",
1000 * 10**18, // faucetDrip
0.005 ether, // sepEthDrip
0.1 ether // dailySepEthCap
);
// Fund contract with ETH for drips
vm.deal(address(faucet), 10 ether);
vm.stopPrank();
}
function testFaucetPermanentFreeze() public {
// Simulate 100 users claiming
for (uint256 i = 1; i <= 100; i++) {
address user = makeAddr(string(abi.encodePacked("user", i)));
vm.prank(user);
faucet.claimFaucetTokens();
// Fast forward past cooldown for next iteration
vm.warp(block.timestamp + 3 days + 1);
}
console.log("Daily claim count after 100 claims:", faucet.dailyClaimCount());
assertEq(faucet.dailyClaimCount(), 100);
// Try to claim as 101st user
address user101 = makeAddr("user101");
vm.prank(user101);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens();
vm.warp(block.timestamp + 2 days);
// Try to claim again
address newDayUser = makeAddr("newDayUser");
vm.prank(newDayUser);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens(); // STILL REVERTS
console.log("Daily claim count after 24+ hours:", faucet.dailyClaimCount());
assertEq(faucet.dailyClaimCount(), 100); // Still stuck at 100
vm.warp(block.timestamp + 10 days);
address futureUser = makeAddr("futureUser");
vm.prank(futureUser);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens(); // PERMANENTLY FROZEN
console.log("CONTRACT IS PERMANENTLY FROZEN");
}
}

Recommended Mitigation

function claimFaucetTokens() public {
faucetClaimer = msg.sender;
+ // Reset day at function start so the limit check reflects the new day
+ if (block.timestamp > lastFaucetDripDay + 1 days) {
+ lastFaucetDripDay = block.timestamp;
+ dailyClaimCount = 0;
+ }
- if (dailyClaimCount >= dailyClaimLimit) {
+ if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
// ... ETH drip branch ...
- if (block.timestamp > lastFaucetDripDay + 1 days) {
- lastFaucetDripDay = block.timestamp;
- dailyClaimCount = 0;
- }
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
_transfer(address(this), faucetClaimer, faucetDrip);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 days ago
Submission Judgement Published
Validated
Assigned finding tags:

dailyClaimCount Reset Bug

Support

FAQs

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