Raisebox Faucet

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

Logic Error in Burn Function

Author Revealed upon completion

Logic Error in Burn Function

Description

The burnFaucetTokens() function should only transfer the amount of tokens that will be burned to prevent unintended token accumulation by the owner.

The function transfers the entire contract balance to the owner but only burns the specified amount, leaving excess tokens with the owner and potentially creating centralization risks through token hoarding.

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
@> _transfer(address(this), msg.sender, balanceOf(address(this))); // Transfers entire balance
@> _burn(msg.sender, amountToBurn); // Only burns specified amount
}

Risk

Likelihood: High

  • Owner calls burnFaucetTokens() with any amount less than the total contract balance

  • The function executes as designed but with unintended side effects every time

  • No additional conditions required - happens on every burn operation where amountToBurn < contract balance

Impact: Medium

  • Owner accumulates tokens beyond intended limits, centralizing token distribution

  • Token distribution mechanism becomes compromised through unintended owner token retention

  • Reduces trust in the faucet system due to owner token hoarding behavior

Proof of Concept

// Demonstrate the token accumulation bug
contract BurnFunctionExploit {
RaiseBoxFaucet public faucet;
address public owner;
constructor(address _faucet) {
faucet = RaiseBoxFaucet(_faucet);
owner = faucet.getOwner();
}
// Simulate the burn function behavior
function demonstrateBurnBug() external view returns (
uint256 contractBalanceBefore,
uint256 ownerBalanceBefore,
uint256 contractBalanceAfter,
uint256 ownerBalanceAfter,
uint256 tokensNotBurned
) {
// Initial state
contractBalanceBefore = faucet.getFaucetTotalSupply(); // e.g., 5000 tokens
ownerBalanceBefore = faucet.balanceOf(owner); // e.g., 0 tokens
uint256 amountToBurn = 1000; // Owner wants to burn 1000 tokens
// Simulate burn function execution:
// Step 1: Transfer ALL contract tokens to owner
uint256 transferredToOwner = contractBalanceBefore; // 5000 tokens
// Step 2: Burn only specified amount
uint256 actuallyBurned = amountToBurn; // 1000 tokens
// Final state
contractBalanceAfter = 0; // All tokens moved out
ownerBalanceAfter = transferredToOwner - actuallyBurned; // 5000 - 1000 = 4000
tokensNotBurned = ownerBalanceAfter - ownerBalanceBefore; // 4000 tokens kept by owner
return (
contractBalanceBefore,
ownerBalanceBefore,
contractBalanceAfter,
ownerBalanceAfter,
tokensNotBurned
);
}
// Show how owner can abuse this over time
function demonstrateAccumulation() external pure returns (uint256[] memory ownerBalance) {
ownerBalance = new uint256[](5);
// Scenario: Contract starts with 10,000 tokens each time owner "burns"
uint256 contractTokens = 10000;
uint256 currentOwnerBalance = 0;
// Burn operation 1: Burn 100, keep 9,900
currentOwnerBalance += (contractTokens - 100);
ownerBalance[0] = currentOwnerBalance; // 9,900
// Mint 10,000 new tokens to contract, then burn 200
currentOwnerBalance += (contractTokens - 200);
ownerBalance[1] = currentOwnerBalance; // 19,700
// Continue pattern...
currentOwnerBalance += (contractTokens - 300);
ownerBalance[2] = currentOwnerBalance; // 29,400
currentOwnerBalance += (contractTokens - 400);
ownerBalance[3] = currentOwnerBalance; // 39,000
currentOwnerBalance += (contractTokens - 500);
ownerBalance[4] = currentOwnerBalance; // 48,500
// Owner accumulates 48,500 tokens while only "burning" 1,500 total
return ownerBalance;
}
}

Attack scenario:

  1. Contract has 10,000 tokens, owner has 0

  2. Owner calls burnFaucetTokens(1000)

  3. Function transfers 10,000 to owner, burns 1,000

  4. Result: Contract has 0, owner has 9,000 (should have 0)

  5. Owner mints 5,000 new tokens to contract

  6. Owner calls burnFaucetTokens(500)

  7. Function transfers 5,000 to owner, burns 500

  8. Result: Owner now has 9,000 + 4,500 = 13,500 tokens

  9. Pattern repeats, owner accumulates massive token holdings

Recommended Mitigation

The mitigation fixes the logic error by ensuring only the specified burn amount is transferred to the owner before burning, preventing unintended token accumulation and maintaining proper token distribution mechanics.

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
+ require(amountToBurn > 0, "Amount must be greater than zero");
- // WRONG: Transfer entire balance then burn partial amount
- _transfer(address(this), msg.sender, balanceOf(address(this)));
- _burn(msg.sender, amountToBurn);
+ // CORRECT: Only transfer the amount that will be burned
+ _transfer(address(this), msg.sender, amountToBurn);
+ _burn(msg.sender, amountToBurn);
+
+ emit FaucetTokensBurned(msg.sender, amountToBurn);
}
Updates

Lead Judging Commences

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