Raisebox Faucet

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

burnFaucetTokens Drains Entire Faucet Balance to Owner

Wrong _transfer uses full balance instead of amountToBurn, emptying contract completely on any burn call

Description

  • The RaiseBoxFaucet::burnFaucetTokens function lets the owner burn some faucet tokens. Plan was: transfer exact amountToBurn from contract to owner, then burn that amount from owner's balance.

  • But line 132 says _transfer(address(this), msg.sender, balanceOf(address(this))) — it's transferring the entire contract balance to the owner, not just amountToBurn! Then it burns only the requested amount from the owner's new huge pile. Result? Contract = 0 tokens, faucet dead, users can't claim anything.

    function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
    require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
    @> _transfer(address(this), msg.sender, balanceOf(address(this))); // Transfers EVERYTHING, not amountToBurn!
    _burn(msg.sender, amountToBurn); // Burns only requested amount from owner's balance
    }

Risk

Likelihood: High

  • Triggers every single time the owner decides to burn tokens

Impact: High

  • Wipes out entire faucet — zero tokens left for users to claim

  • Breaks protocol completely, users get nothing

  • Malicious owner? Instant theft of all funds to their wallet

Proof Of Concept

  • Simple test: Owner burns just 1000 tokens... watch the whole 1B disappear to their wallet.

  • Add this test case to the RaiseBoxFaucet.t.sol file:

    function test__entireTokenBalanceGetsTransferredToOwnerWhileBurning() public {
    // Setup
    vm.prank(owner);
    RaiseBoxFaucet raiseBox = new RaiseBoxFaucet(
    "raiseBoxFaucet",
    "RBF",
    1000 * 10 ** 18,
    0.005 ether,
    1 ether
    );
    console.log("Initial token balance of the contract:", raiseBox.getBalance(address(raiseBox)) / 1e18, "tokens");
    console.log("Initial token balance of the owner:", raiseBox.getBalance(owner) / 1e18, "tokens");
    // Owner decides to burn some tokens for whatever reason, let's say, around 1000
    vm.prank(owner);
    raiseBox.burnFaucetTokens(1000 * 10 ** 18);
    console.log();
    console.log("Owner burns", 1000, "tokens...");
    console.log();
    console.log("Token balance of the contract:", raiseBox.getBalance(address(raiseBox)) / 1e18, "tokens");
    console.log("Owner's balance:", raiseBox.getBalance(owner) / 1e18, "tokens");
    }

  • Run the test using the following command:

    forge test --mt test__entireTokenBalanceGetsTransferredToOwnerWhileBurning -vv

  • Logs:

    Ran 1 test for test/RaiseBoxFaucet.t.sol:TestRaiseBoxFaucet
    [PASS] test__entireTokenBalanceGetsTransferredToOwnerWhileBurning() (gas: 2336174)
    Logs:
    Initial token balance of the contract: 1000000000 tokens
    Initial token balance of the owner: 0 tokens
    Owner burns 1000 tokens...
    Token balance of the contract: 0 tokens
    Owner's balance: 999999000 tokens
    Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.72ms (207.45µs CPU time)

Recommended Mitigation

Skip the unnecessary transfer step and burn directly from the contract balance.

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
- _transfer(address(this), msg.sender, balanceOf(address(this)));
- _burn(msg.sender, amountToBurn);
+ _burn(address(this), amountToBurn);
}
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.