Root + Impact
Description
-
DAccording to the protocol documentation, the owner is explicitly restricted from claiming faucet tokens. The owner's role is limited to deploying the contract, minting tokens, burning tokens, adjusting claim limits, and refilling the ETH balance.
-
The claimFaucetTokens() function does not implement any access control to prevent the owner from calling it. This allows the owner to bypass the intended restriction and claim tokens repeatedly, potentially draining the faucet.
require(block.timestamp >= lastClaim[msg.sender] + 3 days, "Claim too soon");
lastClaim[msg.sender] = block.timestamp;
_mint(msg.sender, 1000 * 10**18);
if (!hasClaimedEth[msg.sender]) {
hasClaimedEth[msg.sender] = true;
(bool success, ) = msg.sender.call{value: 0.005 ether}("");
require(success, "ETH transfer failed");
}
}
Risk
Likelihood:
-
The owner has access to the contract's private key and can call any public function
-
The owner has access to the contract's private key and can call any public function
-
The owner can claim every 3 days indefinitely
Impact:
Owner can drain faucet tokens intended for legitimate users
-
Reduces token availability for actual testers
-
Undermines trust in the protocol
-
Owner gains unfair advantage over regular users
Proof of Concept
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/RaiseBoxFaucet.sol";
contract OwnerClaimTest is Test {
RaiseBoxFaucet faucet;
address owner = address(1);
function setUp() public {
vm.startPrank(owner);
faucet = new RaiseBoxFaucet();
faucet.mint(owner, 1_000_000 * 10**18);
vm.deal(address(faucet), 1 ether);
vm.stopPrank();
}
function testOwnerCanClaim() public {
vm.startPrank(owner);
uint256 balanceBefore = faucet.balanceOf(owner);
faucet.claimFaucetTokens();
uint256 balanceAfter = faucet.balanceOf(owner);
assertEq(balanceAfter - balanceBefore, 1000 * 10**18);
vm.warp(block.timestamp + 3 days);
faucet.claimFaucetTokens();
vm.stopPrank();
}
}
Recommended Mitigation
function claimFaucetTokens() external {
+ require(msg.sender != owner(), "Owner cannot claim tokens");
require(block.timestamp >= lastClaim[msg.sender] + 3 days, "Claim too soon");
lastClaim[msg.sender] = block.timestamp;
_mint(msg.sender, 1000 * 10**18);
if (!hasClaimedEth[msg.sender]) {
hasClaimedEth[msg.sender] = true;
(bool success, ) = msg.sender.call{value: 0.005 ether}("");
require(success, "ETH transfer failed");
}
}