Raisebox Faucet

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

Incorrect Balance Check Prevents Last Claim

Root + Impact

The claimFaucetTokens function uses an incorrect comparison operator (<= instead of <) when checking contract balance, preventing users from claiming tokens when the contract balance exactly equals the drip amount.

Description

  • The expected behavior is to allow claims as long as the contract has enough tokens to fulfill the claim (balance >= faucetDrip).

  • The bug on line 174 uses <= which reverts when balance equals faucetDrip, even though the contract has sufficient tokens to complete the transfer. This means the last user cannot claim the final tokens, leaving them permanently locked in the contract.

function claimFaucetTokens() public {
// ... checks ...
@> if (balanceOf(address(this)) <= faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
// ... rest of function ...
_transfer(address(this), faucetClaimer, faucetDrip);
}

Risk

Likelihood:

  • This occurs whenever the contract balance reaches exactly the faucetDrip amount (1000 tokens)

  • As the faucet is used over time, it will eventually reach this state where only 1000 tokens remain

Impact:

  • The last user who should be able to claim the final 1000 tokens will be unable to do so, wasting those tokens

  • Tokens become permanently locked in the contract with no way to recover them (owner cannot claim per requirements) if new tokens are not minted.

Proof of Concept

This test demonstrates how the incorrect comparison prevents the last valid claim even when sufficient tokens exist.

it("Should prevent claim when balance equals faucetDrip", async function () {
// Initial balance is 1 billion tokens
const initialBalance = await faucet.balanceOf(await faucet.getAddress());
console.log(`\nInitial balance: ${ethers.formatEther(initialBalance)} tokens`);
// Transfer almost all tokens away, leaving exactly 1000 tokens (faucetDrip amount)
const amountToTransfer = initialBalance - FAUCET_DRIP;
await faucet.connect(owner).transfer(user2.address, amountToTransfer);
const remainingBalance = await faucet.balanceOf(await faucet.getAddress());
console.log(`Remaining balance: ${ethers.formatEther(remainingBalance)} tokens`);
console.log(`Faucet drip amount: ${ethers.formatEther(FAUCET_DRIP)} tokens`);
// Verify balance equals faucetDrip
expect(remainingBalance).to.equal(FAUCET_DRIP);
console.log(`\n✅ Balance EQUALS faucetDrip (${ethers.formatEther(FAUCET_DRIP)} tokens)`);
// User tries to claim - should succeed but will fail
console.log(`\nUser1 attempts to claim...`);
await expect(
faucet.connect(user1).claimFaucetTokens()
).to.be.revertedWithCustomError(faucet, "RaiseBoxFaucet_InsufficientContractBalance");
console.log(`❌ Claim FAILED even though contract has enough tokens!`);
console.log(` - Contract has: ${ethers.formatEther(remainingBalance)} tokens`);
console.log(` - User needs: ${ethers.formatEther(FAUCET_DRIP)} tokens`);
console.log(` - Bug: Uses <= instead of < in balance check`);
console.log(` - Result: 1000 tokens permanently locked in contract`);
});
});

Recommended Mitigation

Change the comparison operator from <= to < to allow claims when balance equals the drip amount. The current check balanceOf(address(this)) <= faucetDrip is too restrictive. It should be balanceOf(address(this)) < faucetDrip because a balance equal to faucetDrip is sufficient to complete the transfer. The ERC20 _transfer function will handle the case where balance is insufficient.

function claimFaucetTokens() public {
// ... checks ...
- if (balanceOf(address(this)) <= faucetDrip) {
+ if (balanceOf(address(this)) < faucetDrip) {
revert RaiseBoxFaucet_InsufficientContractBalance();
}
// ... rest of function ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 15 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.