Raisebox Faucet

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

Token Claims Allowed When ETH Drips Are Paused

Root + Impact

There is no check to ensure token claims are paused when sepEthDripsPaused = true,
contrary to the likely intent of pausing all faucet distributions (ETH and tokens) during maintenance or emergencies

Description

The RaiseBoxFaucet contract’s claimFaucetTokens() function allows users to claim faucetDrip tokens even when sepEthDripsPaused = true. The pause mechanism sepEthDripsPaused only affects the ETH drip logic if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused), but the token transfer _transfer(address(this), faucetClaimer, faucetDrip) is executed unconditionally, as long as other checks (e.g., lastClaimTime, dailyClaimCount, token balance) pass. This allows attackers to claim tokens during a pause, undermining the intended control mechanism for the faucet’s distribution

Risk

Likelihood:

  • Reason 1 // Describe WHEN this will occur (avoid using "if" statements)

  • Reason 2

Impact:

Unauthorized Token Claims: Attackers can claim faucetDrip tokens when the faucet is supposed to be paused, potentially draining the contract’s token balance.

Bypasses Pause Mechanism: The pause functionality (sepEthDripsPaused) is rendered ineffective for token distribution, undermining administrative control over the faucet.

Proof of Concept

  1. Deploy attacker contract

  2. Call attack function

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.3;
interface IRaiseBoxFaucet {
function claimFaucetTokens() external;
function balanceOf(address account) external view returns (uint256);
function sepEthDripsPaused() external view returns (bool);
}
contract Attacker {
IRaiseBoxFaucet public faucet;
uint256 public claimedTokens;
constructor(IRaiseBoxFaucet _faucet) {
faucet = _faucet;
}
// Attempt to claim tokens when ETH drips are paused
function attack() external {
require(faucet.sepEthDripsPaused(), "ETH drips not paused");
faucet.claimFaucetTokens();
claimedTokens = faucet.balanceOf(address(this));
}
// Return claimed tokens for verification
function getClaimedTokens() external view returns (uint256) {
return claimedTokens;
}
}

Recommended Mitigation

Add a check for sepEthDripsPaused before executing_transfer to ensure token claims are also paused when ETH drips are paused

function claimFaucetTokens() public {
// Original checks
faucetClaimer = msg.sender;
if (block.timestamp < lastClaimTime[faucetClaimer] + CLAIM_COOLDOWN) {
revert RaiseBoxFaucet_ClaimCooldownOn();
}
if (faucetClaimer == address(0) || faucetClaimer == address(this) || faucetClaimer == owner()) {
revert RaiseBoxFaucet_OwnerOrZeroOrContractAddressCannotCallClaim();
}
if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
if (dailyClaimCount >= dailyClaimLimit) {
revert RaiseBoxFaucet_DailyClaimLimitReached();
}
// ETH drip logic
if (!hasClaimedEth[faucetClaimer] && !sepEthDripsPaused) {
uint256 currentDay = block.timestamp / 24 hours;
if (currentDay > lastDripDay) {
lastDripDay = currentDay;
dailyDrips = 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; // Original reset remains
}
// Additional state updates
if (block.timestamp > lastFaucetDripDay + 1 days) {
lastFaucetDripDay = block.timestamp;
dailyClaimCount = 0;
}
// Effects
lastClaimTime[faucetClaimer] = block.timestamp;
dailyClaimCount++;
+ // Check pause before token transfer
+ if (sepEthDripsPaused) {
+ revert RaiseBoxFaucet_TokenClaimsPaused();
+}
// Token transfer
_transfer(address(this), faucetClaimer, faucetDrip);
emit Claimed(msg.sender, faucetDrip);
}
event SepEthDripped(address indexed claimer, uint256 amount);
event SepEthDripSkipped(address indexed claimer, string reason);
event Claimed(address indexed claimer, uint256 amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 days ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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