Minting should allow a dynamic threshold
Description
-
The initial supply is 1,000,000,000 tokens (1 billion) tokens and when used um by 1 million claims (1k per claim) the owner is allowed to refill the faucet by executing the minting function.
-
Design issue: The minting function, protected by onlyOwner, holds a immutable magic number 1000 * 10 ** 18that protects minting tokens by owner if the faucet holds more than 1k $RB.
In other words, with the initial/standard claim rate of 1000k$RB tokens per user every three days, the owner would only be able to refill for the next claimer until the faucet is dried up.
-
Further issues:
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 (High):
Impact (Medium):
-
Minting is impossible until faucets token balance is below 1k tokens
-
Owner would need to refill tokens immediately after a single claim to keep the faucet usable, just enough for the next claim
Proof of Concept
function test_mintFaucetTokens_OwnerCanOnlyMintForOneClaim() public {
uint256 initialFaucetTokenBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
vm.prank(address(raiseBoxFaucet));
raiseBoxFaucet.transfer(owner, initialFaucetTokenBalance - 1001 * 10 ** 18);
uint256 remainingFaucetTokenBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
assertEq(remainingFaucetTokenBalance, 1001 * 10 ** 18);
vm.prank(owner);
vm.expectRevert(RaiseBoxFaucet.RaiseBoxFaucet_FaucetNotOutOfTokens.selector);
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), 1000 * 10 ** 18);
vm.prank(address(raiseBoxFaucet));
raiseBoxFaucet.transfer(owner, 501 * 10 ** 18);
remainingFaucetTokenBalance = raiseBoxFaucet.balanceOf(address(raiseBoxFaucet));
assertEq(remainingFaucetTokenBalance, 500 * 10 ** 18);
vm.prank(owner);
raiseBoxFaucet.mintFaucetTokens(address(raiseBoxFaucet), 1000 * 10 ** 18);
}
Recommended Mitigation
-
Remove toparameter
-
Remove magic number
+ uint256 public constant MIN_BALANCE_FOR_MINT = 1000 * 10 ** 18; // 1,000 tokens
/// @notice Mints new faucet tokens to the contract
/// @dev Can only mint to the contract itself
/// @param to Address that will receive minted tokens (must be the contract itself)
/// @param amount Number of tokens to mint
- function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
+ function mintFaucetTokens(uint256 amount) public onlyOwner {
- if (to != address(this)) {
- revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
- }
- if (balanceOf(address(this)) > 1000 * 10 ** 18) {
+ if (balanceOf(address(this)) > MIN_BALANCE_FOR_MINT) {
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
- _mint(to, amount);
+ _mint(address(this), amount);
- emit MintedNewFaucetTokens(to, amount);
+ emit MintedNewFaucetTokens(address(this), amount);
}
Higher threshold or dynamic threshold along with faucetDrip, e.g. as implemented here
+ uint256 public constant MINT_DRIP_MULTIPLIER = 100;
/// @notice Mints new faucet tokens to the contract
/// @dev Can only mint to the contract itself
/// @param to Address that will receive minted tokens (must be the contract itself)
/// @param amount Number of tokens to mint
- function mintFaucetTokens(address to, uint256 amount) public onlyOwner {
+ function mintFaucetTokens(uint256 amount) public onlyOwner {
- if (to != address(this)) {
- revert RaiseBoxFaucet_MiningToNonContractAddressFailed();
- }
- if (balanceOf(address(this)) > 1000 * 10 ** 18) {
+ if (balanceOf(address(this)) > faucetDrip * MINT_DRIP_MULTIPLIER) {
revert RaiseBoxFaucet_FaucetNotOutOfTokens();
}
_mint(address(this), amount);
emit MintedNewFaucetTokens(address(this), amount);
}