Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
Submission Details
Impact: medium
Likelihood: medium

ETH transfer DoS on claim (push-payment failure reverts entire claim)

Author Revealed upon completion

Description:
For first-time claimers, the function pushes ETH via 'call{value: sepEthAmountToDrip}("")'. If the recipient is a contract whose 'receive/fallback' reverts, the entire 'claimFaucetTokens' transaction reverts—no tokens are delivered. Users with smart-contract wallets (or misconfigured receive hooks) are DoS’ed.

Impact

  • First-time claimers using contract wallets can be permanently blocked from receiving tokens due to an ETH receive failure.

  • Operational fragility if ETH transfers intermittently fail.

Proof of Concept:
A contract that rejects ETH causes token claim to revert:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console2} from "../lib/lib/forge-std/src/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
contract RaiseBoxFaucetVulnerabilityTest is Test {
RaiseBoxFaucet public faucet;
address public owner;
address public user1;
address public user2;
function advanceBlockTime(uint256 duration_) internal {
vm.warp(duration_);
}
function setUp() public {
owner = makeAddr("owner");
user1 = makeAddr("user1");
user2 = makeAddr("user2");
vm.prank(owner);
faucet = new RaiseBoxFaucet(
"RaiseBoxToken",
"RBT",
1000 * 10**18, // 1000 tokens per claim
0.005 ether, // 0.005 ETH per first claim
1 ether // 1 ETH daily cap
);
vm.deal(address(faucet), 10 ether);
advanceBlockTime(3 days);
}
function test_ETHPushDoSBlocksTokenClaim() public {
RefusesETH refuser = new RefusesETH();
// First-time claimer tries to send ETH -> receive() reverts -> entire claim reverts
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_EthTransferFailed.selector);
refuser.claim(faucet);
// No tokens delivered due to ETH push failure
assertEq(faucet.balanceOf(address(refuser)), 0);
}
}
contract RefusesETH {
function claim(RaiseBoxFaucet faucet) external {
faucet.claimFaucetTokens();
}
receive() external payable { revert("no eth"); } // reject ETH
}

Mitigation:

  • Switch to a pull-payment model: record ETH entitlement (e.g., 'ethOwed[user] += sepEthAmountToDrip)' and let the user withdraw via a separate 'withdrawETH()' call.

  • Alternatively, make ETH optional: proceed with token claim even if ETH transfer fails, and let users claim ETH later.

Support

FAQs

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