Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
58 changes: 58 additions & 0 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30 <0.9.0;

import {StdCheats} from "forge-std/StdCheats.sol";
import {StdAssertions} from "forge-std/StdAssertions.sol";

import {Constants} from "./utils/Constants.sol";
import {Defaults} from "./utils/Defaults.sol";
import {Modifiers} from "./utils/Modifiers.sol";
import {Users} from "./utils/Types.sol";

abstract contract Base_Test is Constants, Modifiers, StdAssertions, StdCheats {
/*//////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////*/

Users internal users;

/*//////////////////////////////////////////////////////////////
TEST CONTRACTS
//////////////////////////////////////////////////////////////*/

Defaults internal defaults;

/*//////////////////////////////////////////////////////////////
SET-UP FUNCTION
//////////////////////////////////////////////////////////////*/

function setUp() public virtual {
defaults = new Defaults();

createTestUsers();
defaults.setUsers(users);

setVariables(defaults, users); // set in modifier contract

setMsgSender(users.alice); // alice default caller
}

/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/

function createUser(string memory name) internal returns (address payable user) {
user = payable(makeAddr(name)); // label implicitly created via Foundry
vm.deal({account: user, newBalance: 100 ether});
}

function createTestUsers() internal {
users.alice = createUser("Alice");
users.bob = createUser("Bob");
users.charlee = createUser("Charlee");

users.admin = createUser("Admin");
users.receiver = createUser("Receiver");
users.sender = createUser("Sender");
}
}
36 changes: 36 additions & 0 deletions test/token/ERC20/ERC20/mod/approve.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "../../../../Base.t.sol";

import {ERC20Harness} from "../harnesses/ERC20Harness.sol";
import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Approve_ERC20Mod_Fuzz_Unit_Test is Base_Test {
ERC20Harness internal harness;

event Approval(address indexed _owner, address indexed _spender, uint256 _value);

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
}

function testFuzz_ShouldRevert_SpenderIsZeroAddress(uint256 value) external {
vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidSpender.selector, ADDRESS_ZERO));
harness.approve(ADDRESS_ZERO, value);
}

function testFuzz_Approve(address spender, uint256 value) external whenSpenderNotZeroAddress {
vm.assume(spender != ADDRESS_ZERO);

vm.expectEmit(address(harness));
emit Approval(users.alice, spender, value);
harness.approve(spender, value);

assertEq(harness.allowance(users.alice, spender), value);
}
}

6 changes: 6 additions & 0 deletions test/token/ERC20/ERC20/mod/approve.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Approve_ERC20Mod_Fuzz_Unit_Test
├── when the spender is the zero address
│ └── it should revert
└── when the spender is not the zero address
├── it should set the spender's allowance from the caller
└── it should emit an {Approval} event
62 changes: 62 additions & 0 deletions test/token/ERC20/ERC20/mod/burn.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "../../../../Base.t.sol";

import {ERC20Harness} from "../harnesses/ERC20Harness.sol";
import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Burn_ERC20Mod_Fuzz_Unit_Test is Base_Test {
ERC20Harness internal harness;

event Transfer(address indexed _from, address indexed _to, uint256 _value);

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
}

function testFuzz_ShouldRevert_Account_ZeroAddress(uint256 value) external {
vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidSender.selector, address(0)));
harness.burn(ADDRESS_ZERO, value);
}

function testFuzz_ShouldRevert_AccountBalanceLtBurnAmount(address account, uint256 balance, uint256 value)
external
whenAccountNotZeroAddress
{
vm.assume(account != ADDRESS_ZERO);
vm.assume(balance < MAX_UINT256);
value = bound(value, balance + 1, MAX_UINT256);

harness.mint(account, balance);

vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, account, balance, value));
harness.burn(account, value);
}

function testFuzz_Burn(address account, uint256 balance, uint256 value)
external
whenAccountNotZeroAddress
givenWhenAccountBalanceGEBurnAmount
{
vm.assume(account != ADDRESS_ZERO);
balance = bound(balance, 1, MAX_UINT256);
value = bound(value, 1, balance);

harness.mint(account, balance);

uint256 beforeTotalSupply = harness.totalSupply();
uint256 beforeBalanceOfAccount = harness.balanceOf(account);

vm.expectEmit(address(harness));
emit Transfer(account, ADDRESS_ZERO, value);
harness.burn(account, value);

assertEq(harness.totalSupply(), beforeTotalSupply - value, "totalSupply");
assertEq(harness.balanceOf(account), beforeBalanceOfAccount - value, "balanceOf(account)");
}
}

10 changes: 10 additions & 0 deletions test/token/ERC20/ERC20/mod/burn.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Burn_ERC20Mod_Fuzz_Unit_Test
├── when the account to burn for is the zero address
│ └── it should revert
└── when the account to burn for is not the zero address
├── when the balance of the account is less than the burn amount
│ └── it should revert
└── when the balance of the account is greater than, or equal to, the burn amount
├── it should decrement the account's balance by the burn amount
├── it should decrement the total supply by the burn amount
└── it should emit a {Transfer} event
57 changes: 57 additions & 0 deletions test/token/ERC20/ERC20/mod/mint.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "../../../../Base.t.sol";

import {ERC20Harness} from "../harnesses/ERC20Harness.sol";
import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Mint_ERC20Mod_Fuzz_Unit_Test is Base_Test {
ERC20Harness internal harness;

event Transfer(address indexed _from, address indexed _to, uint256 _value);

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
}

function testFuzz_ShouldRevert_Account_ZeroAddress(uint256 value) external {
vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidReceiver.selector, address(0)));
harness.mint(ADDRESS_ZERO, value);
}

function testFuzz_ShouldRevert_TotalSupply_Overflows(address account, uint256 value)
external
whenAccountNotZeroAddress
{
vm.assume(account != ADDRESS_ZERO);
vm.assume(value > 0);

harness.mint(users.alice, MAX_UINT256);

vm.expectRevert(stdError.arithmeticError);
harness.mint(account, value);
}

function testFuzz_Mint(address account, uint256 value)
external
whenAccountNotZeroAddress
givenWhenTotalSupplyNotOverflow
{
vm.assume(account != ADDRESS_ZERO);

uint256 beforeTotalSupply = harness.totalSupply();
uint256 beforeBalanceOfAccount = harness.balanceOf(account);

vm.expectEmit(address(harness));
emit Transfer(ADDRESS_ZERO, account, value);
harness.mint(account, value);

assertEq(harness.totalSupply(), beforeTotalSupply + value, "totalSupply");
assertEq(harness.balanceOf(account), beforeBalanceOfAccount + value, "balanceOf(account)");
}
}

10 changes: 10 additions & 0 deletions test/token/ERC20/ERC20/mod/mint.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Mint_ERC20Mod_Fuzz_Unit_Test
├── when the account to mint for is the zero address
│ └── it should revert
└── when the account to mint for is not the zero address
├── given when the value to mint causes the total supply to overflow
│ └── it should revert
└── given when the value to mint does not cause total supply to overflow
├── it should increment the total supply by the mint amount
├── it should increment the account's balance by the mint amount
└── it should emit a {Transfer} event
63 changes: 63 additions & 0 deletions test/token/ERC20/ERC20/mod/transfer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "../../../../Base.t.sol";

import {ERC20Harness} from "../harnesses/ERC20Harness.sol";
import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Transfer_ERC20Mod_Fuzz_Unit_Test is Base_Test {
ERC20Harness internal harness;

event Transfer(address indexed _from, address indexed _to, uint256 _value);

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
}

function testFuzz_ShouldRevert_ReceiverIsZeroAddress(uint256 value) external {
vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidReceiver.selector, ADDRESS_ZERO));
harness.transfer(ADDRESS_ZERO, value);
}

function testFuzz_ShouldRevert_CallerInsufficientBalance(address to, uint256 balance, uint256 value)
external
whenReceiverNotZeroAddress
{
vm.assume(to != ADDRESS_ZERO);
vm.assume(balance < MAX_UINT256);
value = bound(value, balance + 1, MAX_UINT256);

harness.mint(users.alice, balance);

vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, users.alice, balance, value));
harness.transfer(to, value);
}

function testFuzz_Transfer(address to, uint256 balance, uint256 value)
external
whenReceiverNotZeroAddress
givenWhenSenderBalanceGETransferAmount
{
vm.assume(to != ADDRESS_ZERO);
vm.assume(to != users.alice);
balance = bound(balance, 1, MAX_UINT256);
value = bound(value, 1, balance);

harness.mint(users.alice, balance);

uint256 beforeBalanceOfAlice = harness.balanceOf(users.alice);
uint256 beforeBalanceOfTo = harness.balanceOf(to);

vm.expectEmit(address(harness));
emit Transfer(users.alice, to, value);
harness.transfer(to, value);

assertEq(harness.balanceOf(users.alice), beforeBalanceOfAlice - value, "balanceOf(users.alice)");
assertEq(harness.balanceOf(to), beforeBalanceOfTo + value, "balanceOf(to)");
}
}

10 changes: 10 additions & 0 deletions test/token/ERC20/ERC20/mod/transfer.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Transfer_ERC20Mod_Fuzz_Unit_Test
├── when the receiver address is the zero address
│ └── it should revert
└── when the receiver is not the zero address
├── given when the balance of the sender is less than the transfer amount
│ └── it should revert
└── given when the balance of the sender is greater than, or equal to, the transfer amount
├── it should decrement the balance of the sender by the transfer amount
├── it should increment the balance of the receiver by the transfer amount
└── it should emit a {Transfer} event
Loading
Loading