Raisebox Faucet

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

`RaiseBoxFaucet:claimFaucetTokens` Function May Inaccurately Fill the Reason Field When Emitting `SepEthDripSkipped` Event

Author Revealed upon completion

RaiseBoxFaucet:claimFaucetTokens Function May Inaccurately Fill the Reason Field When Emitting SepEthDripSkipped Event

Description

  • Under normal circumstances, when the protocol's ETH balance is sufficient to cover a claim and the daily ETH claim limit has not been reached, users will receive ETH upon claiming.

  • When the above conditions are not met, users will not receive ETH, and the protocol will emit the SepEthDripSkipped event.

  • However, this event includes a reason field intended to inform the frontend whether "the protocol has insufficient ETH balance" or "the daily ETH claim limit has been reached".

  • Clearly, the logic for determining the reason field in the code is inaccurate.

  • Because both reasons might exist simultaneously.

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();
}
// 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);
}

Risk

Likelihood:

  • Only occurs when "the protocol balance is just insufficient, AND the daily ETH claim limit has been reached simultaneously".

Impact:

  • Causes the frontend to be unable to accurately learn the "true and complete reason".

Proof of Concept

  • Add the following test in RaiseBoxFaucet.t.sol:

function test__claimFaucetTokensUntilSepEthDripSkipped() public {
RaiseBoxFaucet testRaiseBoxContract = new RaiseBoxFaucet(
"raiseboxtoken",
"RB",
1000 * 10 ** 18,
0.1 ether,
1 ether
);
vm.prank(owner);
testRaiseBoxContract.refillSepEth{value: 1 ether}(1 ether);
for (uint i=0; i < 10; i++) {
address userTmp = vm.addr(i + 100);
vm.prank(userTmp);
testRaiseBoxContract.claimFaucetTokens();
}
// Verify that only one event is emitted with the reason "Faucet out of ETH"
vm.prank(user1);
vm.expectEmit(true, true, true, true);
emit RaiseBoxFaucet.SepEthDripSkipped(user1, "Faucet out of ETH");
testRaiseBoxContract.claimFaucetTokens();
// Verify condition: Faucet out of ETH
vm.assertTrue(testRaiseBoxContract.getContractSepEthBalance() < testRaiseBoxContract.sepEthAmountToDrip());
// Verify condition: Daily ETH cap reached
vm.assertTrue(testRaiseBoxContract.dailyDrips() + testRaiseBoxContract.sepEthAmountToDrip() > testRaiseBoxContract.dailySepEthCap());
}

Recommended Mitigation

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();
}
// 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"
- );
+ if (address(this).balance < sepEthAmountToDrip) {
+ emit SepEthDripSkipped(faucetClaimer, "Faucet out of ETH");
+ }
+ if (dailyDrips + sepEthAmountToDrip > dailySepEthCap) {
+ emit SepEthDripSkipped(faucetClaimer, "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);
}

Support

FAQs

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