pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/MockUSDC.sol";
contract MockUSDCTest is Test {
MockUSDC public usdc;
address public owner = makeAddr("owner");
address public attacker = makeAddr("attacker");
address public user = makeAddr("user");
uint256 public constant MINT_AMOUNT = 1_000_000 * 10**6;
uint256 public constant SMALL_MINT = 100 * 10**6;
function setUp() public {
vm.prank(owner);
usdc = new MockUSDC();
vm.prank(owner);
usdc.mint(owner, SMALL_MINT);
}
function test_AnyUserCanMintToThemselves() public {
vm.startPrank(attacker);
usdc.mint(attacker, MINT_AMOUNT);
vm.stopPrank();
assertEq(usdc.balanceOf(attacker), MINT_AMOUNT, "Attacker should have received minted tokens");
emit log_named_address("Attacker address", attacker);
emit log_named_uint("Attacker balance after mint", usdc.balanceOf(attacker));
emit log_string("VULNERABILITY CONFIRMED: Any user can mint tokens without restrictions");
}
function test_AttackerCanMintToAnyAddress() public {
vm.prank(attacker);
usdc.mint(user, MINT_AMOUNT);
assertEq(usdc.balanceOf(user), MINT_AMOUNT, "User should have received minted tokens");
assertEq(usdc.balanceOf(attacker), 0, "Attacker's balance should remain zero");
emit log_named_address("Minter (attacker)", attacker);
emit log_named_address("Recipient (user)", user);
emit log_named_uint("User balance after mint", usdc.balanceOf(user));
}
function test_AttackerCanRepeatedlyMint() public {
vm.startPrank(attacker);
for(uint i = 0; i < 5; i++) {
usdc.mint(attacker, MINT_AMOUNT);
}
vm.stopPrank();
uint256 expectedBalance = MINT_AMOUNT * 5;
assertEq(usdc.balanceOf(attacker), expectedBalance, "Attacker should have minted multiple times");
emit log_named_uint("Total supply after multiple mints", usdc.totalSupply());
emit log_named_uint("Attacker final balance", usdc.balanceOf(attacker));
}
function test_NoAccessControlInMintFunction() public {
address[] memory addresses = new address[](3);
addresses[0] = attacker;
addresses[1] = user;
addresses[2] = address(0x123);
for(uint i = 0; i < addresses.length; i++) {
vm.prank(addresses[i]);
usdc.mint(addresses[i], SMALL_MINT);
assertGt(usdc.balanceOf(addresses[i]), 0, "Address should have received tokens");
emit log_named_address("Address", addresses[i]);
emit log_named_uint("Balance after mint", usdc.balanceOf(addresses[i]));
}
emit log_string("All addresses could mint successfully - NO ACCESS CONTROL");
}
function test_ExpectedBehaviorWithAccessControl() public {
vm.prank(owner);
usdc.mint(owner, SMALL_MINT);
vm.prank(attacker);
usdc.mint(attacker, SMALL_MINT);
emit log_string("In a secure contract, the above mint would have reverted!");
emit log_string("But in this contract, it succeeded - demonstrating the vulnerability");
}
function test_TotalSupplyInflation() public {
uint256 initialTotalSupply = usdc.totalSupply();
vm.prank(attacker);
usdc.mint(attacker, MINT_AMOUNT);
uint256 newTotalSupply = usdc.totalSupply();
assertGt(newTotalSupply, initialTotalSupply, "Total supply should increase");
assertEq(newTotalSupply - initialTotalSupply, MINT_AMOUNT, "Total supply increased by mint amount");
emit log_named_uint("Initial total supply", initialTotalSupply);
emit log_named_uint("New total supply", newTotalSupply);
emit log_named_uint("Inflation amount", newTotalSupply - initialTotalSupply);
}
}
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.34;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
- contract MockUSDC is ERC20 {
+ contract MockUSDC is ERC20, Ownable {
- constructor() ERC20("MockUSDC", "mUSDC") {}
+ constructor() ERC20("MockUSDC", "mUSDC") Ownable(msg.sender) {}
- function mint(address to, uint256 amount) external {
+ function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
function decimals() public pure override returns (uint8) {
return 6;
}
}