Raisebox Faucet

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

Daily Claim Limit Permanent DoS Due to Reset Timing

Daily Claim Limit Permanent Denial of Service Due to Reset Timing

Description

The claimFaucetTokens function is designed to enforce a daily limit of 100 claims by incrementing dailyClaimCount on each successful claim and resetting it every 24 hours via a timestamp comparison. This allows up to 100 collective claims per rolling 24-hour period, with the reset intended to automatically unlock the next period.

However, the reset logic is positioned after the daily limit check, causing any claim attempt post-maxout to revert before reaching the reset block. As a result, dailyClaimCount becomes permanently stuck at 100, creating an irrecoverable denial of service state where no further claims are possible until manual owner intervention. The rolling window condition (> lastFaucetDripDay + 1 days) further complicates boundaries, but the core issue is the ordering.

if (dailyClaimCount >= dailyClaimLimit) {
@> revert RaiseBoxFaucet_DailyClaimLimitReached(); // Early revert skips reset
}
// ... snip ...
@> if (block.timestamp > lastFaucetDripDay + 1 days) { // Reset unreachable on failed claims
lastFaucetDripDay = block.timestamp;
@> dailyClaimCount = 0; // Never executes post maxout
}

Risk

Likelihood:

  • Activates after precisely 100 claims in a period (achievable via multi-user coordination or high traffic)

  • Persists indefinitely, as every subsequent claim fails the early check, skipping the reset regardless of time elapsed

Impact:

  • Faucet completely unusable for all users until the owner manually resets via adjustDailyClaimLimit

  • Violates the intended 100 claim per day model, destroying user trust and potentially leading to protocol leaving without automated recovery

Proof of Concept

This Foundry test simulates filling the daily claim limit with 100 unique users (bypassing per-user cooldowns), then warps time to and past the 24-hour boundary. It expects reverts on subsequent claims (confirming the early check blocks them) and asserts dailyClaimCount remains stuck at 100, proving the permanent DoS. The reset never executes due to the ordering flaw.

pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/RaiseBoxFaucet.sol";
contract DailyLimitPermanentDoSTest is Test {
RaiseBoxFaucet faucet;
function setUp() public {
faucet = new RaiseBoxFaucet("Test", "TST", 1000e18, 0.005 ether, 1 ether);
vm.deal(address(faucet), 10 ether);
}
function testDailyLimitPermanentDoS() public {
vm.warp(3 days + 1);
address[100] memory users;
for (uint i = 0; i < 100; i++) {
users[i] = address(uint160(0x100 + i));
}
for (uint i = 0; i < 100; i++) {
vm.prank(users[i]);
faucet.claimFaucetTokens();
}
assertEq(faucet.dailyClaimCount(), 100);
uint256 lastDripDay = faucet.lastFaucetDripDay();
uint256 day2Start = lastDripDay + 1 days;
vm.warp(day2Start);
address newUser = address(0x456);
vm.prank(newUser);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens();
assertEq(faucet.dailyClaimCount(), 100);
vm.warp(day2Start + 1);
vm.prank(newUser);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_DailyClaimLimitReached.selector);
faucet.claimFaucetTokens();
assertEq(faucet.dailyClaimCount(), 100); // Permanently stuck—DoS confirmed
}
}

Recommended Mitigation

Move the reset logic before the limit check to ensure it always executes, preventing permanent DoS. Switch to calendar days (block.timestamp / 1 days) for precise, drift-free resets first new day claim will reset to 0 and succeed.

// In claimFaucetTokens(), after initial checks (cooldown, address validity, balance)
/**
* @param lastFaucetDripDay tracks the last day a claim was made
* @notice resets the @param dailyClaimCount every 24 hours
*/
+uint256 currentDay = block.timestamp / 1 days;
+if (currentDay > lastFaucetDripDay) {
+ lastFaucetDripDay = currentDay;
+ dailyClaimCount = 0;
+}
-if (block.timestamp > lastFaucetDripDay + 1 days) {
- lastFaucetDripDay = block.timestamp;
- dailyClaimCount = 0;
-}
if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 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.