Raisebox Faucet

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

burnFaucetTokens() permanently drains the faucet’s token supply, preventing further user claims

Root + Impact

Description

  • Normal behavior:
    Under normal circumstances, when the burnFaucetTokens() function is called, the owner should only burn the specified amount of tokens (amountToBurn) from the faucet’s balance — effectively reducing the faucet’s total token supply by that amount, while leaving the rest of the tokens in the faucet contract.

  • Actual behavior / Issue:

    Before performing the burn, the function transfers the entire token balance of the faucet contract to the owner’s address, and then burns only the amountToBurn from the owner’s balance.
    As a result, any remaining tokens (beyond amountToBurn) stay with the owner instead of remaining inside the RaiseBoxFaucet contract.

    This means that after a burnFaucetTokens() call, the faucet contract can be left with a zero token balance, preventing future users from claiming faucet tokens — effectively disabling the faucet functionality.

RaiseBoxFaucet// Root cause in the codebase with @> marks to highlight the relevant section
function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
// @> The entire faucet balance is transferred to the owner
// @> This depletes the faucet contract of all tokens, leaving it empty.
_transfer(address(this), msg.sender, balanceOf(address(this)));
// @> Only the specified amount is burned from the owner's balance,
// @> not from the transferred total. Remaining tokens stay with the owner.
_burn(msg.sender, amountToBurn);
}

Risk: High

Likelihood: Medium

  • The only one capable of triggering this behavior is the owner.

  • Owner could act maliciously or accidentally misuse

Impact: High

Impact Area Description
Functional Causes the faucet contract to lose all its tokens, breaking its core functionality.
Funds Loss The faucet no longer holds tokens to distribute to users — effectively drained.
Security Allows unintentional denial of service to the faucet’s purpose.
Maintainability Fix requires redeployment or manual token transfers.
Reputation Reduces trust in the protocol as funds appear mishandled.

Proof of Concept

  • The PoC proves the function's behavior: instead of burning the tokens from the faucet directly, the contract:

    1. Transfers the entire faucet token balance to the owner.

    2. Burns amountToBurn from the owner.

  • Net effect: the faucet contract is emptied, owner keeps the remainder, and total supply only reduces by amountToBurn.

function test_burnTransfersAllThenBurnsOnlyAmount() public {
// Pre-checks
uint256 contractBalanceBefore = raiseBoxFaucet.getFaucetTotalSupply();
assertEq(contractBalanceBefore, INITIAL_SUPPLY_MINTED, "initial contract token balance");
// Owner calls burnFaucetTokens with a small amount (e.g., 100 tokens)
uint256 amountToBurn = 100 * 10 ** 18;
vm.prank(owner);
raiseBoxFaucet.burnFaucetTokens(amountToBurn);
// After call:
// - Contract token balance should be zero (transfered whole balance to owner)
// - Owner token balance should equal (INITIAL_SUPPLY - amountToBurn)
// - totalSupply should have decreased by amountToBurn
uint256 contractBalanceAfter = raiseBoxFaucet.getFaucetTotalSupply();
uint256 ownerBalanceAfter = raiseBoxFaucet.getBalance(owner);
uint256 totalSupplyAfter = raiseBoxFaucet.totalSupply();
assertEq(contractBalanceAfter, 0, "contract token balance should be 0 after burn call");
assertEq(ownerBalanceAfter, INITIAL_SUPPLY_MINTED - amountToBurn,
"owner holds remaining tokens after transfer then burn");
assertEq(totalSupplyAfter, INITIAL_SUPPLY_MINTED - amountToBurn,
"totalSupply reduced only by amountToBurn");
// Now attempt a claim from user -> should revert due to insufficient contract balance
vm.prank(user1);
// Expect the custom error RaiseBoxFaucet_InsufficientContractBalance()
vm.expectRevert(
abi.encodeWithSelector(
RaiseBoxFaucet
.RaiseBoxFaucet_InsufficientContractBalance
.selector
)
);
raiseBoxFaucet.claimFaucetTokens();
}

Recommended Mitigation

Instead of transfering the entire faucet token balance to the owner, burning the amountToBurn tokens from the faucet directly.

- _transfer(address(this), msg.sender, balanceOf(address(this)));
- _burn(msg.sender, amountToBurn);
+ _burn(address(this), amountToBurn);
Updates

Lead Judging Commences

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

Unnecessary and convoluted logic in burnFaucetTokens

Support

FAQs

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