Raisebox Faucet

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

Unintended Revert on Exact Balance in `RaiseBoxFaucet::claimFaucetTokens`

Root + Impact

Description

The claimFaucetTokens function performs a pre-transfer balance check to ensure the contract has enough tokens before proceeding with the claim. The check if (balanceOf(address(this)) <= faucetDrip) reverts with RaiseBoxFaucet_InsufficientContractBalance if the contract's balance is less than or equal to the amount to be dripped per claim (faucetDrip). While this prevents failed transfers when the balance is insufficient, the inclusion of equality (=) blocks the exact case where the balance matches faucetDrip precisely.

In ERC20 transfers, an exact match would succeed (leaving the balance at zero post-transfer), but this check prematurely reverts, leaving tokens stranded in the contract. This edge case becomes relevant after multiple claims deplete the balance to exactly faucetDrip, halting further usage without warning. The issue could be mitigated by using < to allow the exact equality case, relying on the internal ERC20 transfer logic to handle the final depletion safely.

// @> Root cause in the codebase
if (balanceOf(address(this)) <= faucetDrip) { // @> Uses <=, blocking exact equality
revert RaiseBoxFaucet_InsufficientContractBalance();
}

Risk

Likelihood:

  • Medium: The exact equality case occurs naturally after a series of claims that deplete the balance precisely to faucetDrip, especially in low-activity faucets or after manual minting to specific amounts.

  • Automated or repeated claims (e.g., via scripts) increase the chance of hitting this edge case without developer awareness.

Impact:

  • Medium: Tokens become stuck in the contract, requiring owner intervention (e.g., minting more) to resume operations, leading to operational delays and potential token waste.

  • Poor user experience: Users receive misleading "insufficient balance" errors despite available tokens, eroding trust in the faucet system.

Proof of Concept

The following Foundry test demonstrates the issue: The contract balance is set to exactly faucetDrip, causing the claim to revert despite sufficient tokens. This confirms the check blocks the final claim, leaving the balance unchanged.

Add the following to the RaiseBoxFaucetTest.t.sol test:

Proof of Code
function test__ExactBalanceClaimReverts() public {
// Arrange: Set contract balance to exactly faucetDrip
vm.startPrank(owner);
// Burn all existing tokens to reset balance to 0
uint256 currentBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
if (currentBalance > 0) {
raiseBoxFaucet.burnFaucetTokens(currentBalance);
}
// Mint exactly faucetDrip amount (now balance=0 < 1000e18 limit, so allowed)
uint256 faucetDrip = raiseBoxFaucet.faucetDrip();
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), faucetDrip);
vm.stopPrank();
// Check faucetDrip amount and contract token balance (now exact)
currentBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
console.log("Faucet Drip:", faucetDrip);
console.log("Contract Balance:", currentBalance);
// Act & Assert: Claim should revert due to exact balance
vm.prank(user1);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_InsufficientContractBalance.selector);
raiseBoxFaucet.claimFaucetTokens();
// Check contract balance unchanged (still exact faucetDrip)
assertEq(raiseBoxFaucet.balanceOf(address(raiseBoxFaucet)), faucetDrip);
}

Explanation

  • Setup: The test uses the owner to burn all existing tokens (resetting balance to 0) and then mint exactly faucetDrip amount, ensuring the contract balance equals the drip amount.

  • Issue Demonstration: A user attempts to claim, triggering the <= check, which evaluates to true and reverts with RaiseBoxFaucet_InsufficientContractBalance.

  • Result: The balance remains unchanged (still exactly faucetDrip), confirming tokens are stuck and the claim fails despite available funds.

  • The test passes, proving the check prevents the final claim and highlights the unintended blocking of exact equality.

Recommended Mitigation

Change the comparison to < to allow claims when the balance exactly equals faucetDrip, relying on the ERC20 _transfer to handle the depletion safely. This ensures the last claim succeeds without stranding tokens.

- if (balanceOf(address(this)) <= faucetDrip) {
+ if (balanceOf(address(this)) < faucetDrip) { // Allow exact equality for final claim
revert RaiseBoxFaucet_InsufficientContractBalance();
}
Updates

Lead Judging Commences

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

Off-by-one error in `claimFaucetTokens` prevents claiming when the balance is exactly equal to faucetDrip

Support

FAQs

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