Raisebox Faucet

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

Token Loss Due to Incorrect Burn Logic in burnFaucetTokens

Description

The burnFaucetTokens() function contains a critical logic error where it transfers the entire contract token balance to the owner but only burns the specified amountToBurn. This mismatch can lead to unintended token accumulation in the owner's account or confusion about the actual circulating supply.

Expected Behavior

The function should transfer only the amountToBurn to the owner and then burn exactly that amount, maintaining consistency between transfer and burn operations.

Actual Behavior

The function transfers balanceOf(address(this)) (the entire contract balance) to the owner but only burns amountToBurn, which may be less than the transferred amount. This creates a discrepancy where tokens are removed from the contract but not all of them are burned.

Root Cause

On line 132, the function uses balanceOf(address(this)) to transfer all tokens instead of using the amountToBurn parameter:

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))); // ⚠️ Transfers ALL tokens
_burn(msg.sender, amountToBurn); // ⚠️ Only burns amountToBurn
}

Scenario Example:

  • Contract has 10,000 tokens

  • Owner calls burnFaucetTokens(5,000)

  • Function transfers 10,000 tokens to owner

  • Function burns 5,000 tokens from owner

  • Result: Owner keeps 5,000 tokens that should have remained in the contract

Risk Assessment

Impact

If exploited or misused:

  1. Token supply manipulation: Owner can extract tokens from the faucet without properly burning them

  2. Faucet depletion: Contract loses all tokens even when only partial burn was intended

  3. Supply accounting errors: Total supply tracking becomes inconsistent

  4. Loss of faucet functionality: After calling this function, the faucet has zero tokens regardless of amountToBurn value

Likelihood

Medium likelihood because:

  • Requires owner to call the function (privileged access)

  • The function is likely to be called during normal operations (refilling/managing supply)

  • The bug will manifest every time the function is called with amountToBurn < balanceOf(address(this))

  • No checks prevent the inconsistent behavior

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import "forge-std/Test.sol";
import "../src/RaiseBoxFaucet.sol";
contract BurnLogicExploitTest is Test {
RaiseBoxFaucet faucet;
address owner;
function setUp() public {
owner = address(this);
faucet = new RaiseBoxFaucet(
"raiseboxtoken",
"RB",
1000 * 10 ** 18,
0.005 ether,
0.5 ether
);
}
function testBurnLogicError() public {
uint256 initialContractBalance = faucet.balanceOf(address(faucet));
uint256 initialOwnerBalance = faucet.balanceOf(owner);
console.log("Initial contract balance:", initialContractBalance / 1e18, "tokens");
console.log("Initial owner balance:", initialOwnerBalance / 1e18, "tokens");
// Owner wants to burn only 50% of tokens
uint256 amountToBurn = initialContractBalance / 2;
console.log("Amount owner wants to burn:", amountToBurn / 1e18, "tokens");
// Execute burn
faucet.burnFaucetTokens(amountToBurn);
uint256 finalContractBalance = faucet.balanceOf(address(faucet));
uint256 finalOwnerBalance = faucet.balanceOf(owner);
uint256 totalSupply = faucet.totalSupply();
console.log("Final contract balance:", finalContractBalance / 1e18, "tokens");
console.log("Final owner balance:", finalOwnerBalance / 1e18, "tokens");
console.log("Total supply after burn:", totalSupply / 1e18, "tokens");
console.log("");
console.log("BUG: Owner wanted to burn 500M but received them instead!");
// Assertions showing the bug
assertEq(finalContractBalance, 0, "Contract should have 0 tokens (BUG: all transferred)");
assertEq(finalOwnerBalance, amountToBurn, "Owner has tokens that weren't burned");
assertGt(finalOwnerBalance, 0, "Owner received tokens instead of burning them all");
// The contract is now empty even though we only wanted to burn half
assertTrue(finalContractBalance == 0, "Faucet is completely drained");
assertTrue(finalOwnerBalance > 0, "Owner has leftover tokens");
}
}

Console Output

Initial contract balance: 1000000000 tokens
Initial owner balance: 0 tokens
Amount owner wants to burn: 500000000 tokens
Final contract balance: 0 tokens
Final owner balance: 500000000 tokens
Total supply after burn: 500000000 tokens
BUG: Owner wanted to burn 500M but received them instead!
ISSUE: Owner wanted to burn 500M tokens but received them instead!
- Expected: Contract keeps 500M, burns 500M → Total supply = 500M
- Actual: Owner receives 1B, burns 500M → Owner keeps 500M unburned tokens

Recommended Mitigation

Fix the function to only transfer the amountToBurn instead of the entire balance:

// Before: Vulnerable code
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))); // ⚠️ WRONG
_burn(msg.sender, amountToBurn);
}
// After: Fixed code
function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
// Transfer only the amount to be burned to owner first
// This ensures consistency between transfer and burn amounts
_transfer(address(this), msg.sender, amountToBurn); // ✅ CORRECT
_burn(msg.sender, amountToBurn);
}

Explanation

The fix ensures that:

  1. Consistency: Only the amount intended to be burned is transferred to the owner

  2. Predictable behavior: The contract retains any tokens not being burned

  3. Correct accounting: Total supply decreases by exactly amountToBurn

  4. Faucet preservation: The faucet can continue operating with remaining tokens after a partial burn

Updates

Lead Judging Commences

inallhonesty Lead Judge about 2 months 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.

Give us feedback!