// SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; import {MockERC20} from "../../src/mocks/MockERC20.sol"; import {StdCheats} from "../../src/StdCheats.sol"; import {Test} from "../../src/Test.sol"; contract Token_ERC20 is MockERC20 { constructor(string memory name, string memory symbol, uint8 decimals) { initialize(name, symbol, decimals); } function mint(address to, uint256 value) public virtual { _mint(to, value); } function burn(address from, uint256 value) public virtual { _burn(from, value); } } contract MockERC20Test is StdCheats, Test { Token_ERC20 token; bytes32 constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); function setUp() public { token = new Token_ERC20("Token", "TKN", 18); } function invariantMetadata() public view { assertEq(token.name(), "Token"); assertEq(token.symbol(), "TKN"); assertEq(token.decimals(), 18); } function testMint() public { token.mint(address(0xBEEF), 1e18); assertEq(token.totalSupply(), 1e18); assertEq(token.balanceOf(address(0xBEEF)), 1e18); } function testBurn() public { token.mint(address(0xBEEF), 1e18); token.burn(address(0xBEEF), 0.9e18); assertEq(token.totalSupply(), 1e18 - 0.9e18); assertEq(token.balanceOf(address(0xBEEF)), 0.1e18); } function testApprove() public { assertTrue(token.approve(address(0xBEEF), 1e18)); assertEq(token.allowance(address(this), address(0xBEEF)), 1e18); } function testTransfer() public { token.mint(address(this), 1e18); assertTrue(token.transfer(address(0xBEEF), 1e18)); assertEq(token.totalSupply(), 1e18); assertEq(token.balanceOf(address(this)), 0); assertEq(token.balanceOf(address(0xBEEF)), 1e18); } function testTransferFrom() public { address from = address(0xABCD); token.mint(from, 1e18); vm.prank(from); token.approve(address(this), 1e18); assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); assertEq(token.totalSupply(), 1e18); assertEq(token.allowance(from, address(this)), 0); assertEq(token.balanceOf(from), 0); assertEq(token.balanceOf(address(0xBEEF)), 1e18); } function testInfiniteApproveTransferFrom() public { address from = address(0xABCD); token.mint(from, 1e18); vm.prank(from); token.approve(address(this), type(uint256).max); assertTrue(token.transferFrom(from, address(0xBEEF), 1e18)); assertEq(token.totalSupply(), 1e18); assertEq(token.allowance(from, address(this)), type(uint256).max); assertEq(token.balanceOf(from), 0); assertEq(token.balanceOf(address(0xBEEF)), 1e18); } function testPermit() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) ) ) ); token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); assertEq(token.allowance(owner, address(0xCAFE)), 1e18); assertEq(token.nonces(owner), 1); } function testFailTransferInsufficientBalance() public { token.mint(address(this), 0.9e18); token.transfer(address(0xBEEF), 1e18); } function testFailTransferFromInsufficientAllowance() public { address from = address(0xABCD); token.mint(from, 1e18); vm.prank(from); token.approve(address(this), 0.9e18); token.transferFrom(from, address(0xBEEF), 1e18); } function testFailTransferFromInsufficientBalance() public { address from = address(0xABCD); token.mint(from, 0.9e18); vm.prank(from); token.approve(address(this), 1e18); token.transferFrom(from, address(0xBEEF), 1e18); } function testFailPermitBadNonce() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 1, block.timestamp)) ) ) ); token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); } function testFailPermitBadDeadline() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) ) ) ); token.permit(owner, address(0xCAFE), 1e18, block.timestamp + 1, v, r, s); } function testFailPermitPastDeadline() public { uint256 oldTimestamp = block.timestamp; uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, oldTimestamp)) ) ) ); vm.warp(block.timestamp + 1); token.permit(owner, address(0xCAFE), 1e18, oldTimestamp, v, r, s); } function testFailPermitReplay() public { uint256 privateKey = 0xBEEF; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, address(0xCAFE), 1e18, 0, block.timestamp)) ) ) ); token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); token.permit(owner, address(0xCAFE), 1e18, block.timestamp, v, r, s); } function testMetadata(string calldata name, string calldata symbol, uint8 decimals) public { Token_ERC20 tkn = new Token_ERC20(name, symbol, decimals); assertEq(tkn.name(), name); assertEq(tkn.symbol(), symbol); assertEq(tkn.decimals(), decimals); } function testMint(address from, uint256 amount) public { token.mint(from, amount); assertEq(token.totalSupply(), amount); assertEq(token.balanceOf(from), amount); } function testBurn(address from, uint256 mintAmount, uint256 burnAmount) public { burnAmount = bound(burnAmount, 0, mintAmount); token.mint(from, mintAmount); token.burn(from, burnAmount); assertEq(token.totalSupply(), mintAmount - burnAmount); assertEq(token.balanceOf(from), mintAmount - burnAmount); } function testApprove(address to, uint256 amount) public { assertTrue(token.approve(to, amount)); assertEq(token.allowance(address(this), to), amount); } function testTransfer(address from, uint256 amount) public { token.mint(address(this), amount); assertTrue(token.transfer(from, amount)); assertEq(token.totalSupply(), amount); if (address(this) == from) { assertEq(token.balanceOf(address(this)), amount); } else { assertEq(token.balanceOf(address(this)), 0); assertEq(token.balanceOf(from), amount); } } function testTransferFrom(address to, uint256 approval, uint256 amount) public { amount = bound(amount, 0, approval); address from = address(0xABCD); token.mint(from, amount); vm.prank(from); token.approve(address(this), approval); assertTrue(token.transferFrom(from, to, amount)); assertEq(token.totalSupply(), amount); uint256 app = from == address(this) || approval == type(uint256).max ? approval : approval - amount; assertEq(token.allowance(from, address(this)), app); if (from == to) { assertEq(token.balanceOf(from), amount); } else { assertEq(token.balanceOf(from), 0); assertEq(token.balanceOf(to), amount); } } function testPermit(uint248 privKey, address to, uint256 amount, uint256 deadline) public { uint256 privateKey = privKey; if (deadline < block.timestamp) deadline = block.timestamp; if (privateKey == 0) privateKey = 1; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) ) ) ); token.permit(owner, to, amount, deadline, v, r, s); assertEq(token.allowance(owner, to), amount); assertEq(token.nonces(owner), 1); } function testFailBurnInsufficientBalance(address to, uint256 mintAmount, uint256 burnAmount) public { burnAmount = bound(burnAmount, mintAmount + 1, type(uint256).max); token.mint(to, mintAmount); token.burn(to, burnAmount); } function testFailTransferInsufficientBalance(address to, uint256 mintAmount, uint256 sendAmount) public { sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); token.mint(address(this), mintAmount); token.transfer(to, sendAmount); } function testFailTransferFromInsufficientAllowance(address to, uint256 approval, uint256 amount) public { amount = bound(amount, approval + 1, type(uint256).max); address from = address(0xABCD); token.mint(from, amount); vm.prank(from); token.approve(address(this), approval); token.transferFrom(from, to, amount); } function testFailTransferFromInsufficientBalance(address to, uint256 mintAmount, uint256 sendAmount) public { sendAmount = bound(sendAmount, mintAmount + 1, type(uint256).max); address from = address(0xABCD); token.mint(from, mintAmount); vm.prank(from); token.approve(address(this), sendAmount); token.transferFrom(from, to, sendAmount); } function testFailPermitBadNonce(uint256 privateKey, address to, uint256 amount, uint256 deadline, uint256 nonce) public { if (deadline < block.timestamp) deadline = block.timestamp; if (privateKey == 0) privateKey = 1; if (nonce == 0) nonce = 1; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, nonce, deadline)) ) ) ); token.permit(owner, to, amount, deadline, v, r, s); } function testFailPermitBadDeadline(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { if (deadline < block.timestamp) deadline = block.timestamp; if (privateKey == 0) privateKey = 1; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) ) ) ); token.permit(owner, to, amount, deadline + 1, v, r, s); } function testFailPermitPastDeadline(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { deadline = bound(deadline, 0, block.timestamp - 1); if (privateKey == 0) privateKey = 1; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) ) ) ); token.permit(owner, to, amount, deadline, v, r, s); } function testFailPermitReplay(uint256 privateKey, address to, uint256 amount, uint256 deadline) public { if (deadline < block.timestamp) deadline = block.timestamp; if (privateKey == 0) privateKey = 1; address owner = vm.addr(privateKey); (uint8 v, bytes32 r, bytes32 s) = vm.sign( privateKey, keccak256( abi.encodePacked( "\x19\x01", token.DOMAIN_SEPARATOR(), keccak256(abi.encode(PERMIT_TYPEHASH, owner, to, amount, 0, deadline)) ) ) ); token.permit(owner, to, amount, deadline, v, r, s); token.permit(owner, to, amount, deadline, v, r, s); } }