Over-Transfer of Tokens in BurnFaucetTokens Function
Description
The burnFaucetTokens function intends to burn amountToBurn tokens from the contract by transferring to owner then burning. It transfers full contract balance instead, letting owner keep excess after burn.
function burnFaucetTokens(uint256 amountToBurn) public onlyOwner {
require(amountToBurn <= balanceOf(address(this)), "Faucet Token Balance: Insufficient");
_transfer(address(this), msg.sender, balanceOf(address(this)));
_burn(msg.sender, amountToBurn);
}
Risk
Likelihood:
Impact:
-
Owner retains unintended tokens, disrupting supply control.
-
Enables owner enrichment, breaks burn accounting.
Proof of Concept
function testBurnFaucetTokensOverTransfersAllTokens() public {
uint256 initialSupply = 1_000_000_000e18;
uint256 amountToBurn = 1_000e18;
uint256 initialContractBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
uint256 initialOwnerBalance = raiseBoxFaucet.balanceOf(owner);
uint256 initialTotalSupply = raiseBoxFaucet.totalSupply();
assertEq(initialContractBalance, initialSupply);
assertEq(initialOwnerBalance, 0);
vm.prank(owner);
raiseBoxFaucet.burnFaucetTokens(amountToBurn);
uint256 finalContractBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
uint256 finalOwnerBalance = raiseBoxFaucet.balanceOf(owner);
uint256 finalTotalSupply = raiseBoxFaucet.totalSupply();
assertEq(finalContractBalance, 0);
assertEq(finalOwnerBalance, initialSupply - amountToBurn);
assertEq(initialTotalSupply - finalTotalSupply, amountToBurn);
}
POC Explanation: Test uses deployed initial supply (1e9 e18 tokens). Calls burnFaucetTokens(1_000e18). Asserts contract empties, owner retains ~1e9 e18 - 1_000e18 excess, supply drops only by 1_000e18, confirming over-transfer and retention.
Recommended Mitigation
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)));
+ // Transfer only the burn amount to owner for burning
+ _transfer(address(this), msg.sender, amountToBurn);
_burn(msg.sender, amountToBurn);
}
Mitigation Key Points: Transfer exactly amountToBurn to owner, burn that amount. Preserves CEI; owner gets/burns precise value, no excess. Adds event for transparency if needed.