Raisebox Faucet

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

[H-01] Critical Logic Error in `burnFaucetTokens` Allows Complete Drain of Faucet Funds


Description

  • The burnFaucetTokens function contains a critical logic error where it transfers the entire contract balance to the owner instead of only the amount specified for burning. This allows the owner to drain all tokens from the faucet contract while only burning a minimal amount.


function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
//Root cause @> _transfer(address(this), msg.sender, balanceOf(address(this)));
_burn(msg.sender, amountToBurn);
}

Risk

· Medium Likelihood - Requires owner action, but owners can be malicious, compromised, or make mistakes

  • · The bug is easily exploitable by the owner at any time with minimal gas cost


· High Impact - Complete drainage of all faucet tokens in a single transaction

  • Direct theft of tokens intended for user distribution

  • Permanent disruption of faucet functionality:


    Compounding Effect with Mint Function:
    The burnFaucetTokens vulnerability has a secondary devastating impact when combined with the minting logic. The mintFaucetTokens function contains a restrictive condition that prevents minting new tokens if the contract balance exceeds 1000 tokens:

    ```solidity
    if (balanceOf(address(to)) > 1000 * 10 ** 18) {
    revert RaiseBoxFaucet_FaucetNotOutOfTokens();
    }
    ```

    Complete System Failure Scenario:

    1. Initial State: Contract has 1,000,000,000 tokens for user distribution
    2. Exploitation: Owner calls burnFaucetTokens(1) and drains all tokens
    3. Permanent Damage:
    · Contract balance becomes 0 (drained)
    · Users cannot claim tokens (faucet empty)
    · Owner cannot mint new tokens because:
    · If they try to mint any amount > 1000 tokens, it will fail due to the mint condition
    · Even if they mint small amounts, they cannot restore the original supply

Proof of Concept

function testOwnerCanDrainAllFaucetFunds() public {
uint256 initialContractBalance = raiseBoxFaucet.getBalance(address(raiseBoxFaucet));
console.log("Contract balance before burn:", initialContractBalance);
// Owner burns just 1 token but receives entire contract balance
vm.startPrank(owner);
raiseBoxFaucet.burnFaucetTokens(1);
vm.stopPrank();
uint256 ownerBalanceAfter = raiseBoxFaucet.getBalance(owner);
uint256 contractBalanceAfter = raiseBoxFaucet.getBalance(address(raiseBoxFaucet));
console.log("Owner balance after burning 1 token:", ownerBalanceAfter);
console.log("Contract balance after burn:", contractBalanceAfter);
// Verify complete drainage
assertEq(contractBalanceAfter, 0);
assertEq(ownerBalanceAfter, initialContractBalance - 1);
}

Results:

[PASS] testOwnerCanDrainAllFaucetFunds()
Logs:
Contract balance before burn: 1000000000000000000000000000
Owner balance after burning 1 token: 999999999999999999999999999
Contract balance after burn: 0

Recommended Mitigation

Option 1: Burn directly from contract (Recommended)

Option 2: Transfer only the amount to burn

The function's comment is misleading and incorrect - it claims the transfer is necessary "to ensure owner has a balance before _burn", but the OpenZeppelin _burn function can be called directly on any address with a sufficient balance, including the contract itself.

- 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 {
+ require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
+ // Transfer only the amount to burn to owner
+ _transfer(address(this), msg.sender, amountToBurn);
+ // Burn that amount from owner
+ _burn(msg.sender, amountToBurn);
+ }
Other Options :
+ function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
+ require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
+ _burn(address(this), amountToBurn);
+ }
Updates

Lead Judging Commences

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