Raisebox Faucet

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

High — burnFaucetTokens Transfers Entire Faucet Balance to Owner Before Burning (amountToBurn)

Author Revealed upon completion

Root + Impact

Description

  • Normal behaviour: Calling burnFaucetTokens(amountToBurn) should reduce the faucet token supply by exactly amountToBurn, without transferring any unintended extra tokens to the owner.


  • Problem: The function first transfers the entire contract balance to the owner, then burns only the amountToBurn from the owner. This leaves the owner with the remainder (contractBalance - amountToBurn) as a windfall and does not burn the intended full faucet balance.


// 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"
);
// @> BUG: moves the entire faucet balance to owner
_transfer(address(this), msg.sender, balanceOf(address(this)));
// @> Only burns `amountToBurn` from owner, leaving the rest with owner
_burn(msg.sender, amountToBurn);
}

Risk

Likelihood:

  • Occurs whenever the owner invokes burnFaucetTokens during routine supply management (top-ups/deflation).


  • Triggers in normal operations where the owner intends to burn some portion of faucet-held tokens.


Impact:

  • The owner unintentionally (or intentionally) accrues the remainder of the faucet’s entire token balance.


  • Faucet tokenomics and supply controls are broken; the faucet may run out of tokens unexpectedly or become centralised.


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/RaiseBoxFaucet.sol";
contract BurnBugPoC is Test {
RaiseBoxFaucet faucet;
address owner = address(this);
function setUp() public {
faucet = new RaiseBoxFaucet("raiseboxtoken", "RB", 1000e18, 0.005 ether, 1 ether);
// faucet initially holds INITIAL_SUPPLY in address(this)
}
function test_BurnTransfersWholeBalanceToOwner_First() public {
uint256 contractBalBefore = faucet.balanceOf(address(faucet));
uint256 ownerBalBefore = faucet.balanceOf(owner);
// Owner burns a small amount
faucet.burnFaucetTokens(100e18);
uint256 contractBalAfter = faucet.balanceOf(address(faucet));
uint256 ownerBalAfter = faucet.balanceOf(owner);
// ❌ Contract sent its entire balance to owner, then only 100e18 was burned
assertEq(contractBalAfter, 0, "Entire faucet balance wrongly transferred out");
assertEq(ownerBalAfter, ownerBalBefore + contractBalBefore - 100e18, "Owner keeps the remainder");
}
}

Explanation: This test deploys the faucet (which mints all tokens to the contract), then calls burnFaucetTokens(100e18). Because the implementation transfers the entire faucet balance to the owner before burning, the faucet ends up with 0 tokens, and the owner keeps the remainder after burning 100e18. The assertions prove the faucet’s balance is emptied and the owner’s balance increases by more than intended.

Recommended Mitigation

- function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
- require(
- amountToBurn <= balanceOf(address(this)),
- "Faucet Token Balance: Insufficient"
- );
-
- // transfer faucet balance to owner first before burning
- // ensures owner has a balance before _burn (owner only function) can be called successfully
- _transfer(address(this), msg.sender, balanceOf(address(this)));
-
- _burn(msg.sender, amountToBurn);
- }
+ function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
+ uint256 faucetBal = balanceOf(address(this));
+ require(amountToBurn > 0, "Zero burn");
+ require(amountToBurn <= faucetBal, "Faucet Token Balance: Insufficient");
+
+ // ✅ Burn directly from the faucet's own balance (no accidental transfer to owner)
+ _burn(address(this), amountToBurn);
+ }

Explanation: Burning directly from the contract’s own balance removes exactly amountToBurn tokens without transferring any excess to the owner, preserving faucet reserves and intended tokenomics. If you must burn from the owner, transfer only amountToBurn to the owner then burn that exact amount—never transfer the full faucet balance.

Support

FAQs

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