Raisebox Faucet

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

`dailyDrips` Counter Incorrectly Reset by Non-First-Time Claimers

Root + Impact

Description

  • The dailyDrips counter is designed to track the total amount of Sepolia ETH dripped per day to enforce the dailySepEthCap limit.

  • The function incorrectly resets dailyDrips to 0 in the else branch when a user who has already claimed ETH calls claimFaucetTokens, completely breaking the daily ETH cap enforcement mechanism.

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;
// ... ETH transfer logic
} else {
emit SepEthDripSkipped(/*...*/);
}
} else {
dailyDrips = 0; // @> BUG: This breaks daily ETH cap enforcement!
}

Risk

Likelihood:

  • This occurs every time a user who has previously claimed ETH calls claimFaucetTokens again

  • This happens frequently in normal operation as users claim tokens multiple times

  • The bug activates immediately after the first user claims ETH a second time

Impact:

  • Daily ETH cap is completely bypassed and becomes ineffective

  • Contract can drip unlimited ETH per day, far exceeding dailySepEthCap

  • Contract ETH reserves will be depleted much faster than intended

  • Economic model of the faucet is broken

  • Malicious users can coordinate to drain ETH by alternating first-time and repeat claimers

Proof of Concept

function testDailyDripsResetBypass() public {
// Setup: dailySepEthCap = 0.5 ether, sepEthAmountToDrip = 0.005 ether
// So max 100 first-time claims per day should be allowed
// First-time claimer gets ETH
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
assertEq(raiseBoxFaucet.dailyDrips(), 0.005 ether);
// Repeat claimer (user1 already has ETH) claims tokens again
vm.warp(block.timestamp + 4 days);
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
// BUG: dailyDrips is reset to 0!
assertEq(raiseBoxFaucet.dailyDrips(), 0);
// Now a new first-time claimer can get ETH again
vm.prank(user2);
raiseBoxFaucet.claimFaucetTokens();
// This cycle can repeat infinitely, bypassing the daily cap
}

Recommended Mitigation

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;
}

Alternative: Move daily reset logic to the beginning of the function

+ // Reset counters at the start of the function
+ uint256 currentDay = block.timestamp / 24 hours;
+ if (currentDay > lastDripDay) {
+ lastDripDay = currentDay;
+ dailyDrips = 0;
+ }
+
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) {
// ... rest of logic
}
- } else {
- dailyDrips = 0;
}
Updates

Lead Judging Commences

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