Raisebox Faucet

First Flight #50
Beginner FriendlySolidity
100 EXP
Submission Details
Impact: high
Likelihood: high

Logical error in burning tokens from the contract address balance

Author Revealed upon completion

Root + Impact

Description

  • Transferring the entire contract balance → Destroying only a portion of the balance in the owner's wallet.

  • The faucet (contract) no longer holds any remaining tokens;

    The faucet also loses the ability to continue issuing (claiming) tokens

function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
// @> The faucet's token balance is completely taken away by the owner and The owner's actual tokens received ≠ the actual tokens burned
_transfer(address(this), msg.sender, balanceOf(address(this)));
_burn(msg.sender, amountToBurn);
}

Risk

Likelihood:

  • The faucet's token balance is completely taken away by the owner.

    The faucet no longer functions properly (claims will revert to: InsufficientContractBalance() );

    The owner's actual tokens received ≠ the actual tokens burned

Impact:

  • This results in a mismatch between the contract's book value and the supply (totalSupply decreases, but the owner's actual balance increases).

Proof of Concept

///notice Testing the logic error in the burnFaucetTokens function
/// @dev Proving that the function transfers the entire contract balance to the owner but only burns some of the tokens in the owner's wallet
function testBurnFaucetTokensLogicBug() public {
uint256 initialContractBalance = raiseBoxFaucet.getFaucetTotalSupply();
uint256 initialOwnerBalance = raiseBoxFaucet.getBalance(owner);
console.log("=== Status before test ===");
console.log("Contract token balance:", initialContractBalance);
console.log("Owner token balance:", initialOwnerBalance));
// Assume we want to burn half of the tokens
uint256 amountToBurn = initialContractBalance / 2;
console.log("=== Ready to burn ===");
console.log("Amount of tokens to burn:", amountToBurn);
// Call the burn function
vm.prank(owner);
raiseBoxFaucet.burnFaucetTokens(amountToBurn);
uint256 finalContractBalance = raiseBoxFaucet.getFaucetTotalSupply();
uint256 finalOwnerBalance = raiseBoxFaucet.getBalance(owner);
uint256 totalSupply = raiseBoxFaucet.totalSupply();
console.log("=== Status after burn ===");
console.log("Contract token balance:", finalContractBalance);
console.log("Owner token balance:", finalOwnerBalance);
console.log("Total supply:", totalSupply);
// Verification logic error:
// 1. Contract balance should be 0 (because it has all been transferred to the owner)
assertEq(finalContractBalance, 0, "Contract balance should be 0 because it has all been transferred to the owner");
// 2. The owner's balance should equal the initial contract balance minus the amount burned.
// But it should actually be: Initial contract balance - amount burned = expected owner balance
uint256 expectedOwnerBalance = initialContractBalance - amountToBurn;
assertEq(finalOwnerBalance, expectedOwnerBalance, "Owner balance should equal initial contract balance minus amount burned");
// 3. Total supply should decrease by amountToBurn
uint256 expectedTotalSupply = initialContractBalance - amountToBurn;
assertEq(totalSupply, expectedTotalSupply, "Total supply should decrease by amount burned");
console.log("=== Problem Analysis ===");
console.log("Problem 1: The entire contract balance was transferred to the owner, but only some of the tokens in the owner's wallet were burned");
console.log("Problem 2: This means the owner received additional tokens, but the total supply did not decrease as expected");
// Proof of the problem: If the owner now has initialContractBalance - amountToBurn amountToBurn of tokens
// The total supply is only reduced by amountToBurn
// This means the actual number of tokens burned is correct, but the owner receives all remaining tokens
console.log("Actual number of tokens received by the owner:", finalOwnerBalance - initialOwnerBalance);
console.log("This equals the initial contract balance minus the burn amount, indicating that the owner receives all remaining tokens");
}

Recommended Mitigation

function burnFaucetTokens(uint256 amountToBurn) external onlyOwner {
uint256 faucetBalance = balanceOf(address(this));
require(amountToBurn <= faucetBalance, "Faucet: insufficient balance to burn");
_burn(address(this), amountToBurn);
emit BurnedFaucetTokens(address(this), amountToBurn);
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.