Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

No ETH Withdrawal Mechanism Locks Donated Funds

Root + Impact

Description

Expected Behavior

The owner should be able to withdraw or manage surplus ETH held by the contract.

Actual Behavior

All ETH sent to the contract remains permanently locked, inaccessible even to the owner.

Description:The faucet accepts ETH donations and refills but lacks any method for the owner to withdraw or reclaim funds.

// Root cause in the codebase with @> marks to highlight the relevant section
@> receive() external payable {
@> emit SepEthDonated(msg.sender, msg.value);
@> }
@> fallback() external payable {
@> emit SepEthDonated(msg.sender, msg.value);
@> }

Risk

Likelihood

High, since users or the team will frequently refill or donate ETH for faucet operations.

Over multiple cycles, this issue will lead to ETH buildup that can never be recovered, even by the owner.

Impact

1.Locked ETH funds: Any excess ETH beyond the daily distribution limit cannot be reclaimed or used for maintenance, migration, or upgrades.

2.Operational rigidity: The owner cannot rebalance ETH supply, migrate contracts, or fix overfunded faucets.

3.Lost resources: Over time, accumulated ETH becomes unrecoverable — potentially significant if users mistakenly send large amounts.

Proof of Concept

Explanation

The absence of a controlled withdrawal mechanism introduces a liquidity dead-end.

Even though the faucet accepts ETH via donations and refills, those funds are effectively unusable once sent, except through small user drips.

Over time, this makes the contract inefficient and unmanageable, especially for testing environments like Sepolia where drips might not match donation flow.

contract LockedEthTest {
RaiseBoxFaucet faucet;
function testLockedFunds() public {
faucet = new RaiseBoxFaucet("RaiseBox", "RBT", 1000 ether, 0.005 ether, 1 ether);
payable(address(faucet)).transfer(1 ether); // simulate donation
// Expected: Owner can withdraw later
// Actual: No withdrawal function exists, ETH remains stuck.
assert(address(faucet).balance == 1 ether);
}
}

Recommended Mitigation

Add a secure withdrawal function restricted to the owner to prevent ETH from being permanently locked:

Benefits:

Enables recovery of excess ETH.

Maintains control for legitimate maintenance and rebalancing.

Prevents loss of assets from accidental overfunding.

- remove this code
+ add this code
+ /// @notice Allows the owner to withdraw ETH from the contract
+ /// @dev Prevents ETH from being permanently locked after donations or refills
+ /// @param amount The amount to withdraw (must be <= current balance)
+ function withdrawEth(uint256 amount) external onlyOwner {
+ require(amount <= address(this).balance, "Insufficient balance");
+ (bool success, ) = owner().call{value: amount}("");
+ require(success, "Withdraw failed");
+ }
Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!