Raisebox Faucet

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

burnFaucetTokens() transfers entire balance instead of specified amountToBurn parameter

Root + Impact

Description

  • Normal behavior:

The burnFaucetTokens() function should transfer only amountToBurn tokens from the faucet to the owner, then burn that specific amount.

  • Specific issue:

Line 137 uses balanceOf(address(this)) instead of the amountToBurn parameter, transferring the ENTIRE faucet balance to the owner, then burning only the requested amount. This leaves the faucet empty regardless of burn amount.

Root cause:

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
// @> BUG: Transfers ALL tokens instead of amountToBurn
_transfer(address(this), msg.sender, balanceOf(address(this)));
// Then burns only amountToBurn
_burn(msg.sender, amountToBurn);
}

Risk

Likelihood:

  • Reason 1: Owner will call burnFaucetTokens() during normal protocol operations to manage token supply.

  • Reason 2: Bug triggers automatically on every function call regardless of the amountToBurn parameter value.

  • Reason 3: No warning or check prevents the unintended full transfer - function executes silently.


    Impact:

  • Impact 1: Complete faucet DoS - after any burn operation, faucet balance becomes zero and users cannot claim tokens.

  • Impact 2: Unintended token transfer - owner receives all tokens instead of just the burn amount, potentially causing accounting issues.

  • Impact 3: Protocol functionality destroyed - requires contract redeployment or owner to transfer tokens back manually.

  • Impact 4: Loss of user trust - legitimate users attempting to claim will encounter failures, damaging protocol reputation.


Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console} from "forge-std/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
contract PoC_BurnBugTest is Test {
RaiseBoxFaucet public faucet;
address public owner;
function setUp() public {
owner = address(this);
faucet = new RaiseBoxFaucet("RBT", "RBT", 1000 * 10**18, 0.005 ether, 1 ether);
}
function testBurnBugTransfersAllBalance() public {
uint256 initialBalance = faucet.balanceOf(address(faucet));
console.log("Initial faucet balance:", initialBalance / 1e18, "tokens");
// Owner wants to burn only 100 tokens
uint256 amountToBurn = 100 * 10**18;
console.log("Owner calls burnFaucetTokens(", amountToBurn / 1e18, ")");
faucet.burnFaucetTokens(amountToBurn);
uint256 finalBalance = faucet.balanceOf(address(faucet));
uint256 ownerBalance = faucet.balanceOf(owner);
console.log("Final faucet balance:", finalBalance / 1e18);
console.log("Owner balance:", ownerBalance / 1e18);
console.log("Expected owner balance: 0 (should have burned)");
console.log("Actual owner balance:", ownerBalance / 1e18);
// Proof of bug
assertEq(finalBalance, 0, "BUG: Faucet completely emptied");
assertEq(ownerBalance, initialBalance - amountToBurn, "BUG: Owner got all tokens");
}
function testUserCannotClaimAfterBurn() public {
// Owner burns minimal amount
faucet.burnFaucetTokens(1 * 10**18);
// User tries to claim
address user = makeAddr("user");
vm.warp(block.timestamp + 4 days);
vm.prank(user);
vm.expectRevert(); // Will revert due to insufficient balance
faucet.claimFaucetTokens();
console.log("CONFIRMED: Users cannot claim after any burn operation");
}
}

Run:

forge test --match-contract PoC_BurnBugTest -vv

Output:

Initial faucet balance: 1000000000 tokens Owner calls burnFaucetTokens(100) Final faucet balance: 0 Owner balance: 999999900 Expected owner balance: 0 Actual owner balance: 999999900 ✓ BUG CONFIRMED: Faucet emptied, owner received 999,999,900 tokens instead of burning 100

Recommended Mitigation

Fix: Change line 137 to transfer only amountToBurn instead of entire balance.

Why this fixes it: The parameter amountToBurn represents the intended amount to burn. By transferring only this amount to the owner before burning, we ensure the faucet retains its remaining balance for future claims.

Implementation:

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
- // Bug: Transfers entire balance
- _transfer(address(this), msg.sender, balanceOf(address(this)));
+ // Fix: Transfer only amount to burn
+ _transfer(address(this), msg.sender, amountToBurn);
_burn(msg.sender, amountToBurn);
}
#Alternative (if burning directly from contract is acceptable):
function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Insufficient balance");
- _transfer(address(this), msg.sender, balanceOf(address(this)));
- _burn(msg.sender, amountToBurn);
+ // Burn directly from contract without transfer
+ _burn(address(this), amountToBurn);
}
Updates

Lead Judging Commences

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