Root + Impact
Description
The RaiseBoxFaucet::mintFaucetTokens function checks the balance of the contract against a magic number instead of faucetDrip before minting. The check verifies that the contract has a balance greater than 1000e18 no matter the value of faucetDrip, potentially causing a denial of service (DoS).
function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
if (to != address(this)) {
revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
}
@> if (balanceOf(address(to)) > 1000 * 10 ** 18) {
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
_mint(to, amount);
emit MintedNewFaucetTokens(to, amount);
}
Risk
Likelihood:
Since the mintFaucetTokens function compares the contract's balance to a constant value, it is highly possible that the faucetDrip value, if set incorrectly, breaks the logic.
Impact:
-
The faucetDrip is set higher than 1000 * 10 ** 18, causing denial of service (DoS) after the balance available for distribution to the faucet reaches an amount less than faucetDrip but higher than the static value of 1000 * 10 ** 18, completely breaking the protocol functionality as the owner would not be able to mint new tokens, and users would not be able to claim faucet tokens.
-
This issue, if missed at deploy, might require redeploy, which would allow all the previous claimers to receive sepolia ETH for the second time.
Proof of Concept
Add the following code snippet to the RaiseBoxFaucet.t.sol test file.
This code snippet is designed to demonstrate the RaiseBoxFaucet::mintFaucetTokens function checks the balance of the contract against a magic number instead of faucetDrip, causing a denial of service (DoS) blocking the minting of RB tokens required for claiming RB tokens.
function testMintedNewFaucetTokensEventsLessThanFaucetDrip() public {
owner = address(this);
raiseBoxFaucet = new RaiseBoxFaucet("raiseboxtoken", "RB", 49000000 * 10 ** 18, 0.02 ether, 0.5 ether);
raiseBoxFaucetContractAddress = address(raiseBoxFaucet);
vm.deal(raiseBoxFaucetContractAddress, 1 ether);
console.log("Initial balance of contract: ", raiseBoxFaucet.balanceOf(address(raiseBoxFaucet)));
address newUser;
for (uint256 i = 1; i <= 20; ++i) {
newUser = payable(address(uint160(200 + i)));
vm.prank(newUser);
raiseBoxFaucet.claimFaucetTokens();
console.log("User address: ", newUser);
console.log("User balance of ETH: ", newUser.balance);
console.log("User balance of faucet tokens: ", raiseBoxFaucet.balanceOf(newUser));
}
console.log("\n Actual balance: ", raiseBoxFaucet.balanceOf(address(raiseBoxFaucet)));
vm.prank(user1);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_InsufficientContractBalance.selector);
raiseBoxFaucet.claimFaucetTokens();
uint256 faucetDrip = raiseBoxFaucet.faucetDrip();
console.log("Balance required for claim: ", faucetDrip);
vm.prank(owner);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_FaucetNotOutOfTokens.selector);
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), faucetDrip);
assertGt(
faucetDrip,
raiseBoxFaucet.balanceOf(address(raiseBoxFaucet)),
"Balance required for claim is greater than balance of contract"
);
}
Recommended Mitigation
The magic number 1000 * 10 ** 18 should be replaced with faucetDrip to prevent denial of service (DoS) blocking minting RB tokens required for claiming RB tokens.
function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
if (to != address(this)) {
revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
}
- if (balanceOf(address(to)) > 1000 * 10 ** 18) {
+ if (balanceOf(address(to)) > faucetDrip) {
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
_mint(to, amount);
emit MintedNewFaucetTokens(to, amount);
}