Raisebox Faucet

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

Faucet: Incorrect Burn — Owner Drains Remaining Balance

Root + Impact

Description

  • Normal behavior: The faucet contract should allow the owner to burn a specified amount of tokens held by the faucet contract, without moving more tokens than requested or unexpectedly transferring tokens to the owner

  • Specific issue: The function transfers the entire token balance of the contract to the owner and then burns only amountToBurn. This results in the owner receiving any surplus tokens that were intended to remain in the faucet.

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);
}

Risk

Likelihood:

  • This happens whenever the owner calls burnFaucetTokens with amountToBurn strictly less than the contract balance. The contract currently always transfers the entire contract balance before burning the requested amount.

  • It is easy for the caller or reviewer to assume the function burns only amountToBurn from the faucet, while in reality the owner will receive the rest. This will be triggered any time the faucet holds > amountToBurn.

Impact:

  • Owner can extract the faucet's remaining balance (not intended for extraction) and keep tokens leftover after burning. This subverts the faucet’s intended supply control and leads to privilege abuse or accidental loss of funds intended for users.

  • Users and integrators expecting faucet tokens to remain or be burned will be surprised; tokenomics and trust in the contract can be broken.

Proof of Concept

The owner ends up with all tokens taken from the faucet, while only 100 tokens were burned. The faucet lost all tokens instead of the intended 100.

function testBurnFaucetTokensDrainsOwner() public {
uint256 amountToBurn = 100 ether;
uint256 initial = raiseBoxFaucet.totalSupply();
vm.startPrank(owner);
raiseBoxFaucet.burnFaucetTokens(amountToBurn);
vm.stopPrank();
// balances after exploit
uint256 faucetBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
uint256 ownerBalance = raiseBoxFaucet.balanceOf(owner);
assertEq(faucetBalance, 0); // faucet drained
assertEq(ownerBalance, initial - 100 ether); // owner kept surplus
assertEq(raiseBoxFaucet.totalSupply(), initial - 100 ether); // only 100 burned
}

Recommended Mitigation

I proposed to safe gas burn directly from the faucet. _burn(address(this), amountToBurn) reduces the faucet's balance and totalSupply directly.

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);
+ // burn directly from the faucet
+ _burn(address(this), amountToBurn);
}
Updates

Lead Judging Commences

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