Raisebox Faucet

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

Can get sepETH drip even after reaching daily limit due to Unwanted 'else statement' which resets the daily Drip Value to Zero

Risk

High - Can be used to empty the contract balance as well as all the legitimate users may not receive the sepETH Drip.

Target

src/RaiseBoxFaucet.sol

Description

  • As per the normal behaviour of the contract the dailySepEthCap variable is used to set the max daily limit of the sepEth dripped to new users.

  • This is tracked using dailyDrips which is increamented for each drip and also reset on new day. There is an else statement which resets the dailyDrips variable when an already claimed user calls the claimFaucetTokens() function. This Else statement is not necessary.

function claimFaucetTokens() public {
faucetClaimer = msg.sender;
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) {
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;```
}}

Impact

  • user can create multiple addresses and drain the SepETH balance of the contract whenever it gets refilled or whenever a donation is made.Making it impossible for other users to claim their first time bonus.

POC

  • user1(attacker) joins and claims the tokens as well as the SepETH drip which is set to 0.1 eth(for testing purpose).

  • After a few days user1(attacker) calls the claimFaucetTokens() function from different addresses which he owns.

  • After calling the function from five different addresses he will reach the daily limit for sepEth drip which is set to 0.5 ether and tracked by using the dailyDrips variable.

  • user1(Attacker) calls the function from his address again which in turn resets the dailyDrips value to zero.

  • He then repeats the same from different addresses until the contract is drained of its sepETH Balance.

  • He can keep track of the SepETH Balance of the contract and keep repeating the attack as soon as it gets refilled

function testClaimSepAfterLimitReached() public {
//user1 claims for the firsttime
vm.prank(user1);
raiseBoxFaucet.claimFaucetTokens();
assertEq(
address(user1).balance,
0.1 ether,
"Ether balance of user1 should be 0.1 as first timer"
);
advanceBlockTime(block.timestamp + 3 days);
address[8] memory faucetUsers = [
user2,
user3,
user4,
user5,
user6,
user1,
user7,
user8
];
for (uint256 i = 0; i < faucetUsers.length; i++) {
vm.prank(faucetUsers[i]);
raiseBoxFaucet.claimFaucetTokens();
}
// should have been zero as user7 shouldn't have received any SepEth
// because the daily sepEth limit is already reached but user can get SepETH even after daily drip limit reached
assertEq(
address(user7).balance,
0.1 ether
);
// should have been 0.7 as seven users claimed 0.1 each but got resetted to 0 due to an
// unwanted else statement which got triggered when a already claimed user tried to claim
assertEq(raiseBoxFaucet.dailyDrips(),0.2 ether);
}
}

Permalink:

https://github.com/CodeHawks-Contests/2025-10-raisebox-faucet/blob/daf8826cece87801a9d18745cf77e11e39838f5b/src/RaiseBoxFaucet.sol#L212-L212

Mitigation

  • Remove the else statement as it is not necessary.

function claimFaucetTokens() public {
faucetClaimer = msg.sender;
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) {
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;
}}
Updates

Lead Judging Commences

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