Raisebox Faucet

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

Inefficient and unsafe token burn logic

Author Revealed upon completion

Root + Impact

Description


Expected behavior:

When the faucet needs to burn tokens, the burn function should simply reduce the total supply by removing tokens from the faucet’s own balance, ensuring a clean and verifiable reduction in supply.


Actual behavior:

The burn function first transfers tokens before burning them, or otherwise uses redundant logic that increases gas cost and introduces potential reentrancy or balance mismatch issues.

If implemented incorrectly (for example, by burning after a transfer or by using the wrong address context), the burn process could either fail to reduce the total supply properly or burn tokens not owned by the intended address.

In some versions of the RaiseBox Faucet, the burn logic may even allow the owner to burn tokens only from msg.sender (not necessarily the faucet), which is inconsistent with the intended goal of burning faucet-owned tokens.

// Root cause in the codebase with @> marks to highlight the relevant section
function burn(uint256 amount) external onlyOwner {
@> _burn(msg.sender, amount);
}

Risk

Likelihood

1.Medium — requires owner interaction but occurs under normal use (burning tokens).

2.High probability of confusion or error by maintainers.

Impact:

1. Incorrect burn behavior: The faucet’s balance remains high even after a “burn.”

2. Gas inefficiency: The owner must manually move tokens to perform burns.

3.Maintenance risk: Creates inconsistencies between faucet balance and total supply.

Proof of Concept

Explanation

The test demonstrates that burn() only affects the caller’s balance (msg.sender), not the faucet’s internal token reserves.

If the intent was to burn tokens from the faucet contract itself (to maintain proper supply control), this design fails that purpose and may leave unused tokens locked in the faucet.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/RaiseBoxToken.sol";
contract BurnTest is Test {
RaiseBoxToken token;
address owner = address(this);
function setUp() public {
token = new RaiseBoxToken(1_000_000 ether);
}
function test_BurnDoesNotAffectFaucetBalance() public {
uint256 faucetBalanceBefore = token.balanceOf(owner);
uint256 totalSupplyBefore = token.totalSupply();
token.burn(100_000 ether);
uint256 faucetBalanceAfter = token.balanceOf(owner);
uint256 totalSupplyAfter = token.totalSupply();
// The owner's balance reduces, but the faucet contract's balance remains unchanged
assertEq(faucetBalanceAfter, faucetBalanceBefore - 100_000 ether);
assertEq(totalSupplyAfter, totalSupplyBefore - 100_000 ether);
}
}

Recommended Mitigation

Explanation:

Simplify the burn logic so it explicitly burns from the faucet’s own balance rather than from msg.sender. This ensures the burn reduces the faucet’s balance and the overall token supply directly, keeping accounting consistent and preventing gas-wasting transfers.

- remove this code
+ add this code
- function burn(uint256 amount) external onlyOwner {
- _burn(msg.sender, amount);
- }
+ function burn(uint256 amount) external onlyOwner {
+ _burn(address(this), amount);
+ emit FaucetTokensBurned(address(this), amount);
+ }

Support

FAQs

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