Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vanilla ERC20 without EIP-2612 (PERMIT) #404

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,27 @@ ERC20Test:testTransfer() (gas: 60272)
ERC20Test:testTransfer(address,uint256) (runs: 256, μ: 57996, ~: 60484)
ERC20Test:testTransferFrom() (gas: 83777)
ERC20Test:testTransferFrom(address,uint256,uint256) (runs: 256, μ: 86993, ~: 92841)
ERC20VainillaInvariants:invariantBalanceSum() (runs: 256, calls: 3840, reverts: 2369)
ERC20VainillaTest:invariantMetadata() (runs: 256, calls: 3840, reverts: 2350)
ERC20VainillaTest:testApprove() (gas: 31058)
ERC20VainillaTest:testApprove(address,uint256) (runs: 256, μ: 30020, ~: 31264)
ERC20VainillaTest:testBurn() (gas: 56881)
ERC20VainillaTest:testBurn(address,uint256,uint256) (runs: 256, μ: 57770, ~: 59540)
ERC20VainillaTest:testFailBurnInsufficientBalance(address,uint256,uint256) (runs: 256, μ: 52031, ~: 55432)
ERC20VainillaTest:testFailTransferFromInsufficientAllowance() (gas: 80816)
ERC20VainillaTest:testFailTransferFromInsufficientAllowance(address,uint256,uint256) (runs: 256, μ: 79484, ~: 83355)
ERC20VainillaTest:testFailTransferFromInsufficientBalance() (gas: 81314)
ERC20VainillaTest:testFailTransferFromInsufficientBalance(address,uint256,uint256) (runs: 256, μ: 79287, ~: 83832)
ERC20VainillaTest:testFailTransferInsufficientBalance() (gas: 52718)
ERC20VainillaTest:testFailTransferInsufficientBalance(address,uint256,uint256) (runs: 256, μ: 51855, ~: 55250)
ERC20VainillaTest:testInfiniteApproveTransferFrom() (gas: 89659)
ERC20VainillaTest:testMetadata(string,string,uint8) (runs: 256, μ: 551063, ~: 545180)
ERC20VainillaTest:testMint() (gas: 53635)
ERC20VainillaTest:testMint(address,uint256) (runs: 256, μ: 51331, ~: 53819)
ERC20VainillaTest:testTransfer() (gas: 60205)
ERC20VainillaTest:testTransfer(address,uint256) (runs: 256, μ: 57913, ~: 60401)
ERC20VainillaTest:testTransferFrom() (gas: 83724)
ERC20VainillaTest:testTransferFrom(address,uint256,uint256) (runs: 256, μ: 86938, ~: 92758)
ERC4626Test:invariantMetadata() (runs: 256, calls: 3840, reverts: 2883)
ERC4626Test:testFailDepositWithNoApproval() (gas: 13369)
ERC4626Test:testFailDepositWithNotEnoughApproval() (gas: 87005)
Expand Down
300 changes: 300 additions & 0 deletions src/test/ERC20Vainilla.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.15;

import {DSTestPlus} from "./utils/DSTestPlus.sol";
import {DSInvariantTest} from "./utils/DSInvariantTest.sol";

import {MockERC20Vainilla} from "./utils/mocks/MockERC20Vainilla.sol";

contract ERC20VainillaTest is DSTestPlus {
MockERC20Vainilla token;

function setUp() public {
token = new MockERC20Vainilla("Token", "TKN", 18);
}

function invariantMetadata() public {
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);

hevm.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);

hevm.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 testFailTransferInsufficientBalance() public {
token.mint(address(this), 0.9e18);
token.transfer(address(0xBEEF), 1e18);
}

function testFailTransferFromInsufficientAllowance() public {
address from = address(0xABCD);

token.mint(from, 1e18);

hevm.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);

hevm.prank(from);
token.approve(address(this), 1e18);

token.transferFrom(from, address(0xBEEF), 1e18);
}

function testMetadata(
string calldata name,
string calldata symbol,
uint8 decimals
) public {
MockERC20Vainilla tkn = new MockERC20Vainilla(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);

hevm.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 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);

hevm.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);

hevm.prank(from);
token.approve(address(this), sendAmount);

token.transferFrom(from, to, sendAmount);
}
}

contract ERC20VainillaInvariants is DSTestPlus, DSInvariantTest {
VainillaBalanceSum balanceSum;
MockERC20Vainilla token;

function setUp() public {
token = new MockERC20Vainilla("Token", "TKN", 18);
balanceSum = new VainillaBalanceSum(token);

addTargetContract(address(balanceSum));
}

function invariantBalanceSum() public {
assertEq(token.totalSupply(), balanceSum.sum());
}
}

contract VainillaBalanceSum {
MockERC20Vainilla token;
uint256 public sum;

constructor(MockERC20Vainilla _token) {
token = _token;
}

function mint(address from, uint256 amount) public {
token.mint(from, amount);
sum += amount;
}

function burn(address from, uint256 amount) public {
token.burn(from, amount);
sum -= amount;
}

function approve(address to, uint256 amount) public {
token.approve(to, amount);
}

function transferFrom(
address from,
address to,
uint256 amount
) public {
token.transferFrom(from, to, amount);
}

function transfer(address to, uint256 amount) public {
token.transfer(to, amount);
}
}
20 changes: 20 additions & 0 deletions src/test/utils/mocks/MockERC20Vainilla.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20Vainilla} from "../../../tokens/ERC20Vainilla.sol";

contract MockERC20Vainilla is ERC20Vainilla {
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) ERC20Vainilla(_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);
}
}
Loading
Loading