Raisebox Faucet

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

`burnFaucetTokens` silently transfers all faucet tokens to the owner (dangerous owner backdoor)

Author Revealed upon completion

Description:
burnFaucetTokens first transfers the entire faucet balance to the owner, then burns only amountToBurn from the owner’s balance. This grants the owner all remaining faucet tokens minus the burned amount. Even if intentional, this is highly surprising and centralizes control over the faucet supply.

Impact:

  • Owner can drain nearly the full faucet token reserve to their wallet at any time.

  • Breaks user expectations of a neutral faucet and can destabilize any testing ecosystem that assumes the faucet holds the supply.

Proof of Concept:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console2} from "../lib/lib/forge-std/src/Test.sol";
import {RaiseBoxFaucet} from "../src/RaiseBoxFaucet.sol";
contract RaiseBoxFaucetVulnerabilityTest is Test {
RaiseBoxFaucet public faucet;
address public owner;
address public user1;
address public user2;
function setUp() public {
owner = makeAddr("owner");
user1 = makeAddr("user1");
user2 = makeAddr("user2");
vm.prank(owner);
faucet = new RaiseBoxFaucet(
"RaiseBoxToken",
"RBT",
1000 * 10**18, // 1000 tokens per claim
0.005 ether, // 0.005 ETH per first claim
1 ether // 1 ETH daily cap
);
vm.deal(address(faucet), 10 ether);
}
function test_BurnFaucetTokensDrainsAllTokens() public {
// Get initial balances
uint256 initialContractBalance = faucet.balanceOf(address(faucet));
uint256 initialOwnerBalance = faucet.balanceOf(owner);
uint256 burnAmount = 100 * 10**18; // Owner wants to burn only 100 tokens
console2.log("Initial contract balance:", initialContractBalance);
console2.log("Initial owner balance:", initialOwnerBalance);
console2.log("Amount to burn:", burnAmount);
// Owner calls burnFaucetTokens
vm.prank(owner);
faucet.burnFaucetTokens(burnAmount);
// Check results
uint256 finalContractBalance = faucet.balanceOf(address(faucet));
uint256 finalOwnerBalance = faucet.balanceOf(owner);
console2.log("Final contract balance:", finalContractBalance);
console2.log("Final owner balance:", finalOwnerBalance);
// Critical vulnerability: Owner receives ALL tokens, not just burn amount
assertEq(finalContractBalance, 0);
assertEq(finalOwnerBalance, initialContractBalance - burnAmount);
}
}

Mitigation:
Burn directly from the contract without transferring:

function burnFaucetTokens(uint256 amountToBurn) external onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
- _transfer(address(this), msg.sender, balanceOf(address(this)));
_burn(address(this), amountToBurn);
}

Optionally, add a governance timelock and emit clear events to make supply changes auditable.

Support

FAQs

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