Raisebox Faucet

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

Burn Logic Misimplementation

[H-02] Burn Logic Misimplementation

Impact - Exploiting this flaw allows an attacker to drain all faucet-held tokens and illegitimately mint or transfer large portions to arbitrary addresses, compromising the contract’s supply logic and trust model.


Description

  • Normal behavior:
    The burnFaucetTokens() function should allow the contract owner to burn a specified number of faucet tokens (amountToBurn) directly from the contract’s balance, thereby reducing total supply and maintaining consistency in the faucet’s token economy.

  • Issue:
    The original implementation transferred the entire contract balance to the owner before burning only amountToBurn tokens from the owner’s wallet. This resulted in the faucet contract’s balance becoming zero while the owner retained unburned tokens that were unintentionally transferred, breaking supply logic and faucet token flow.

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
// @> transfer faucet balance to owner first before burning
_transfer(address(this), msg.sender, balanceOf(address(this)));
// @> only burns the specified amount from owner instead of total transferred
_burn(msg.sender, amountToBurn);
}

Risk

Likelihood:

  • High — The condition triggers every time burnFaucetTokens() is executed, since the full contract balance is always transferred to the owner.

  • Deterministic — This occurs whenever the owner calls the function, independent of external input or edge cases.

Impact:

  • Owner receives all faucet tokens each time, regardless of burn amount.

  • Token supply metrics become inaccurate, leading to loss of trust and broken faucet mechanics.

  • Could enable accidental or malicious draining of faucet tokens.


Proof of Concept

Summary

A simple Foundry test demonstrates the bug: calling burnFaucetTokens(amountToBurn) transfers the entire faucet balance to the owner and only burns amountToBurn from the owner's balance, leaving the owner with leftover tokens and the faucet drained to zero.

Files used

  • src/RaiseBoxFaucet.sol — vulnerable contract (original implementation)

  • test/BurnLogic.t.sol — Foundry test performing the PoC (owner calls burnFaucetTokens)

Test Contract

// test's pseudocode
uint256 contractBalance = raiseBoxFaucet.getFaucetTotalSupply();
uint256 burnAmount = contractBalance / 2;
vm.prank(owner);
raiseBoxFaucet.burnFaucetTokens(burnAmount);
// Contract balance becomes 0 instead of (contractBalance - burnAmount)
assertEq(raiseBoxFaucet.getFaucetTotalSupply(), 0);
// Owner receives unburned tokens unintentionally
assertEq(raiseBoxFaucet.getBalance(owner), contractBalance, "Owner wrongly owns all faucet tokens");

How to reproduce locally

Run the PoC test with Foundry from project root:

forge test --match-test test_BurnLogic_PoC -vvvv

Observed test output

Initial faucet token balance: 1000000000000000000000000000
Initial owner token balance: 0
After burn: Faucet token balance: 0
After burn: Owner token balance: 500000000000000000000000000

Analysis

  • After calling burnFaucetTokens(contractBalance / 2), the faucet’s token balance is 0 (drained), whereas the owner ends up with contractBalance / 2 leftover tokens beyond the burned amount — demonstrating that the full contract balance was moved to the owner and only a portion was burned.

  • This proves the function does not burn tokens from the contract as intended and instead transfers tokens to the owner before burning, enabling unintended token takeover.


Recommended Mitigation

The fix ensures that the burn operation happens directly from the contract’s own token balance instead of transferring tokens to the owner first.

In the original version, _transfer(address(this), msg.sender, balanceOf(address(this))) moved all tokens to the owner before burning only amountToBurn — effectively draining the faucet and leaving unburned tokens in the owner’s wallet.

The corrected implementation:

  1. Removes the _transfer() call entirely (no tokens are moved to the owner).

  2. Adds a safety check to confirm the contract holds at least amountToBurn tokens.

  3. Burns the tokens from the contract’s address (_burn(address(this), amountToBurn)), ensuring only the intended amount is destroyed and total supply remains accurate.

- _transfer(address(this), msg.sender, balanceOf(address(this)));
- _burn(msg.sender, amountToBurn);
+ uint256 contractBalance = balanceOf(address(this));
+ require(amountToBurn <= contractBalance, "Faucet Token Balance: Insufficient");
+ _burn(address(this), amountToBurn);

This preserves faucet balance integrity, prevents unintentional token leakage, and keeps the burn logic consistent with standard ERC-20 burn behavior.

Updates

Lead Judging Commences

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