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
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
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.
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;
}
function attack() external {
require(faucet.sepEthDripsPaused(), "ETH drips not paused");
faucet.claimFaucetTokens();
claimedTokens = faucet.balanceOf(address(this));
}
function getClaimedTokens() external view returns (uint256) {
return claimedTokens;
}
}
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);
}