Root + Impact
Description
The RaiseBoxFaucet::claimFaucetTokens function prioritizes sepolia ETH drip over faucetDrip claim for new users, sending ETH to users before the faucetDrip tokens. The contract-based accounts that do not expect to receive sepolia ETH cause denial of service (DoS) reverting the transaction, blocking users from claiming RB tokens.
function claimFaucetTokens() public {
...
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"
);
}
...
}
Risk
Likelihood:
-
Since the main purpose of this contract is to distribute faucet RB tokens, it is quite probable that contracts would expect an ERC20 transfer instead of an ETH deposit.
-
Any contract-based account that claims the faucet for the first time is potentially affected by this issue.
Impact:
-
Denial of service (DoS) blocking users from claiming RB tokens.
-
Severely breaking the functionality of the protocol, as the distribution of faucet tokens is the main purpose of this protocol.
Proof of Concept
Add the following code snippet to the RaiseBoxFaucet.t.sol test file.
EthRejector contract:
contract EthRejector {
receive() external payable {
revert("Rejecting ETH");
}
fallback() external payable {
revert("Rejecting ETH");
}
}
This code snippet is designed to demonstrate the RaiseBoxFaucet::claimFaucetTokens function fails on ETH transfer causing denial of service (DoS)
function testSepEthTransferFailedCausesDoS() public {
EthRejector rejector = new EthRejector();
vm.prank(address(rejector));
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_EthTransferFailed.selector);
raiseBoxFaucet.claimFaucetTokens();
console.log("user balance of eth: ", address(rejector).balance);
console.log("user balance of faucet tokens: ", raiseBoxFaucet.balanceOf(address(rejector)));
}
Recommended Mitigation
Possible mitigation is to replace revert statement with emit of SepEthDripSkipped event, in order to allow users to claim RB tokens.
function claimFaucetTokens() public {
...
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();
+ emit SepEthDripSkipped(faucetClaimer, "Receiver has rejected ETH");
}
} else {
emit SepEthDripSkipped(
faucetClaimer,
address(this).balance < sepEthAmountToDrip ? "Faucet out of ETH" : "Daily ETH cap reached"
);
}
...
}