Raisebox Faucet

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

burnFaucetTokens Function Drains All Faucet Tokens to Owner

Root + Impact

Description

  • The function burnFaucetTokens() is intended to burn a specified number of tokens from the faucet's balance, reducing the total supply.

  • However, the function first transfers the entire token balance of the faucet to the owner's address, then burns the specified amount from the owner's personal balance. This unexpectedly drains the faucet of all its tokens, misleading the owner about the function's true outcome and rendering the faucet inoperable.

// Root cause in the codebase with @> marks to highlight the relevant section
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:

  • Reason 1: The function is callable by the owner, who is a trusted and active participant in the protocol.

  • Reason 2: The function name is misleading. An owner will likely call it with the reasonable expectation that it only burns tokens, accidentally triggering the drain.

Impact:

  • Impact 1: The faucet is unexpectedly drained of its entire token supply, preventing it from serving its primary purpose of dispensing tokens to users.

  • Impact 2: An owner calling this function will inadvertently receive almost all of the faucet's tokens. This can be perceived as a hidden "rug pull" mechanism, damaging the project's reputation.

Proof of Concept

The following Foundry test proves that calling burnFaucetTokens drains the faucet's balance to the owner.

function test_burnDrainsFaucet() public {
// Arrange: Get initial state
address owner = faucet.owner();
uint256 initialFaucetBalance = faucet.balanceOf(address(faucet));
uint256 initialOwnerBalance = faucet.balanceOf(owner);
uint256 burnAmount = 100 * 10**18;
// Sanity check
assertEq(initialFaucetBalance, 1_000_000_000 * 10**18); // INITIAL_SUPPLY
assertEq(initialOwnerBalance, 0);
// Act: Owner calls the function to burn 100 tokens
vm.prank(owner);
faucet.burnFaucetTokens(burnAmount);
// Assert: Check the final state
uint256 finalFaucetBalance = faucet.balanceOf(address(faucet));
uint256 finalOwnerBalance = faucet.balanceOf(owner);
// The faucet's balance should be 0 (it was drained)
assertEq(finalFaucetBalance, 0, "Faucet balance should be zero");
// The owner's balance should be the entire initial supply minus the small burn amount
assertEq(finalOwnerBalance, initialFaucetBalance - burnAmount, "Owner should have all remaining tokens");
}

Recommended Mitigation

Remove the unnecessary _transfer call and burn the tokens directly from the faucet contract's balance by changing msg.sender to address(this).

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);
+ // Correctly burn tokens from the faucet's own balance.
+ _burn(address(this), amountToBurn);
}
Updates

Lead Judging Commences

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