Root + Impact
Description
-
The claimFaucetTokens() logic resets dailyDrips when currentDay > lastDripDay, but also resets it unconditionally for non-first-time claimers
-
Once any non-first-time claimers claims, it will reset accumulated daily drips, even it hasn't been a day
function claimFaucetTokens() public {
...
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) {
...
} else {
...
}
} else {
@>
dailyDrips = 0;
}
...
}
Risk
Likelihood: High
Impact:
-
Cap bypass: dailySepEthCap can be exceeded repeatedly within the same day by alternating first-time claims (which add to dailyDrips) and any repeat claim (which resets it)
-
Fund drain: accelerated ETH outflow beyond intended daily limits
Proof of Concept
Add the following test, then run this command: forge test --match-test testResetDailyETHCap -vv
function testResetDailyETHCap() public {
console.log("initial daily drips:", raiseBoxFaucet.dailyDrips());
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
console.log("daily drips after user1 first claim:", raiseBoxFaucet.dailyDrips());
advanceBlockTime(block.timestamp + 3 days);
console.log("time advanced by 3 days");
vm.prank(user2);
raiseBoxFaucet.claimFaucetTokens();
console.log("daily drips after user2 first claim:", raiseBoxFaucet.dailyDrips());
vm.prank(user3);
raiseBoxFaucet.claimFaucetTokens();
console.log("daily drips after user3 first claim:", raiseBoxFaucet.dailyDrips());
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
console.log("daily drips after user1 second claim:", raiseBoxFaucet.dailyDrips());
}
PoC Results:
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/RaiseBoxFaucet.t.sol:TestRaiseBoxFaucet
[PASS] testResetDailyETHCap() (gas: 480307)
Logs:
initial daily drips: 0
daily drips after user1 first claim: 5000000000000000
time advanced by 3 days
daily drips after user2 first claim: 5000000000000000
daily drips after user3 first claim: 10000000000000000
daily drips after user1 second claim: 0
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.73ms (1.40ms CPU time)
Ran 1 test suite in 327.42ms (7.73ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Recommended Mitigation
function claimFaucetTokens() public {
// Checks
...
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 0;
// dailyClaimCount = 0;
}
if (dailyDrips + sepEthAmountToDrip <= dailySepEthCap && address(this).balance >= sepEthAmountToDrip) {
...
} else {
...
}
+ }
- } else {
- dailyDrips = 0;
- }
...
}