From afb69250eda45b3038bb506e3c31ed03ba0b45b2 Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Mon, 15 Dec 2025 19:51:55 +0100 Subject: [PATCH 1/5] test: add reusable test base and utils --- test/Base.t.sol | 62 +++++++++++++++++++++++++++++++++++ test/utils/Constants.sol | 70 ++++++++++++++++++++++++++++++++++++++++ test/utils/Defaults.sol | 21 ++++++++++++ test/utils/Modifiers.sol | 56 ++++++++++++++++++++++++++++++++ test/utils/Types.sol | 11 +++++++ test/utils/Utils.sol | 25 ++++++++++++++ 6 files changed, 245 insertions(+) create mode 100644 test/Base.t.sol create mode 100644 test/utils/Constants.sol create mode 100644 test/utils/Defaults.sol create mode 100644 test/utils/Modifiers.sol create mode 100644 test/utils/Types.sol create mode 100644 test/utils/Utils.sol diff --git a/test/Base.t.sol b/test/Base.t.sol new file mode 100644 index 00000000..47c983b6 --- /dev/null +++ b/test/Base.t.sol @@ -0,0 +1,62 @@ +// 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 { + // Deploy defaults contract + defaults = new Defaults(); + + // Create and set test users + createTestUsers(); + defaults.setUsers(users); + + // Set the variables in the Modifiers contract + setVariables(defaults, users); + + // Set alice as the default caller for the tests + setMsgSender(users.alice); + } + + /*////////////////////////////////////////////////////////////// + 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"); + } +} diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol new file mode 100644 index 00000000..e21f6c15 --- /dev/null +++ b/test/utils/Constants.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +abstract contract Constants { + /*////////////////////////////////////////////////////////////// + GENERIC + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant MAX_UINT256 = type(uint256).max; + + address internal constant ADDRESS_ZERO = address(0); + + /*////////////////////////////////////////////////////////////// + INTERFACE ID + //////////////////////////////////////////////////////////////*/ + + bytes4 internal constant IERC165_INTERFACE_ID = 0x01ffc9a7; + bytes4 internal constant IERC20_INTERFACE_ID = 0x36372b07; + bytes4 internal constant IERC721_INTERFACE_ID = 0x80ac58cd; + bytes4 internal constant IERC1155_INTERFACE_ID = 0xd9b67a26; + bytes4 internal constant INVALID_INTERFACE_ID = 0xffffffff; + bytes4 internal constant CUSTOM_INTERFACE_ID = 0x12345678; + bytes4 internal constant ZERO_INTERFACE_ID = 0x00000000; + + /*////////////////////////////////////////////////////////////// + ROLES + //////////////////////////////////////////////////////////////*/ + + bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00; + bytes32 internal constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 internal constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + bytes32 internal constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + bytes32 internal constant MODERATOR_ROLE = keccak256("MODERATOR_ROLE"); + bytes32 internal constant USER_ROLE = keccak256("USER_ROLE"); + bytes32 internal constant TRUSTED_BRIDGE_ROLE = keccak256("TRUSTED_BRIDGE_ROLE"); + + /*////////////////////////////////////////////////////////////// + TOKEN + //////////////////////////////////////////////////////////////*/ + + string public constant TOKEN_NAME = "Test Token"; + string public constant TOKEN_SYMBOL = "TEST"; + uint8 public constant TOKEN_DECIMALS = 18; + uint256 internal constant INITIAL_SUPPLY = 1_000_000e18; + + uint256 internal constant TOKEN_ID_1 = 1; + uint256 internal constant TOKEN_ID_2 = 2; + uint256 internal constant TOKEN_ID_3 = 3; + + /*////////////////////////////////////////////////////////////// + URI + //////////////////////////////////////////////////////////////*/ + + string internal constant BASE_URI = "https://example.com/api/nft/"; + string internal constant DEFAULT_URI = "https://token.uri/{id}.json"; + string internal constant TOKEN_URI = "token1.json"; + + /*////////////////////////////////////////////////////////////// + ERC-1155 RECEIVER MAGIC VALUES + //////////////////////////////////////////////////////////////*/ + + bytes4 internal constant RECEIVER_SINGLE_MAGIC_VALUE = 0xf23a6e61; // onERC1155Received + bytes4 internal constant RECEIVER_BATCH_MAGIC_VALUE = 0xbc197c81; // onERC1155BatchReceived + + /*////////////////////////////////////////////////////////////// + FEE + //////////////////////////////////////////////////////////////*/ + + uint256 internal constant FEE_DENOMINATOR = 10_000; +} diff --git a/test/utils/Defaults.sol b/test/utils/Defaults.sol new file mode 100644 index 00000000..6aa44f6b --- /dev/null +++ b/test/utils/Defaults.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {Constants} from "./Constants.sol"; +import {Users} from "./Types.sol"; + +contract Defaults is Constants { + /*////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + Users private users; + + /*////////////////////////////////////////////////////////////// + HELPERS + //////////////////////////////////////////////////////////////*/ + + function setUsers(Users memory users_) public { + users = users_; + } +} diff --git a/test/utils/Modifiers.sol b/test/utils/Modifiers.sol new file mode 100644 index 00000000..e33e7492 --- /dev/null +++ b/test/utils/Modifiers.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {Defaults} from "./Defaults.sol"; +import {Users} from "./Types.sol"; +import {Utils} from "./Utils.sol"; + +abstract contract Modifiers is Utils { + /*////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + Defaults private defaults; + Users private users; + + function setVariables(Defaults _defaults, Users memory _users) public { + defaults = _defaults; + users = _users; + } + + /*////////////////////////////////////////////////////////////// + ERC-20 + //////////////////////////////////////////////////////////////*/ + + modifier whenAccountNotZeroAddress() { + _; + } + + modifier whenReceiverNotZeroAddress() { + _; + } + + modifier whenSpenderNotZeroAddress() { + _; + } + + modifier whenSenderNotZeroAddress() { + _; + } + + modifier givenWhenTotalSupplyNotOverflow() { + _; + } + + modifier givenWhenAccountBalanceGEBurnAmount() { + _; + } + + modifier givenWhenSenderBalanceGETransferAmount() { + _; + } + + modifier givenWhenSpenderAllowanceGETransferAmount() { + _; + } +} diff --git a/test/utils/Types.sol b/test/utils/Types.sol new file mode 100644 index 00000000..6e29464a --- /dev/null +++ b/test/utils/Types.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +struct Users { + address payable alice; + address payable bob; + address payable charlee; + address payable admin; + address payable receiver; + address payable sender; +} diff --git a/test/utils/Utils.sol b/test/utils/Utils.sol new file mode 100644 index 00000000..e6032681 --- /dev/null +++ b/test/utils/Utils.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {CommonBase as StdBase} from "forge-std/Base.sol"; +import {StdUtils} from "forge-std/StdUtils.sol"; + +abstract contract Utils is StdBase, StdUtils { + /*////////////////////////////////////////////////////////////// + MISC + //////////////////////////////////////////////////////////////*/ + + /// @dev Retrieves the current block timestamp as an `uint40`. + function getBlockTimestamp() internal view returns (uint40) { + return uint40(vm.getBlockTimestamp()); + } + + /// @dev Stops the active prank and sets a new one. + function setMsgSender(address msgSender) internal { + vm.stopPrank(); + vm.startPrank(msgSender); + + // Deal some ETH to the new caller. + vm.deal(msgSender, 1 ether); + } +} From a19bb41614cb50b21b74c24a0916f286ce9d16c8 Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Mon, 15 Dec 2025 19:53:20 +0100 Subject: [PATCH 2/5] test: add fuzzed unit BTT tests for LibERC20 --- test/token/ERC20/ERC20/mod/approve.t.sol | 36 ++++ test/token/ERC20/ERC20/mod/approve.tree | 6 + test/token/ERC20/ERC20/mod/burn.t.sol | 66 ++++++++ test/token/ERC20/ERC20/mod/burn.tree | 10 ++ test/token/ERC20/ERC20/mod/mint.t.sol | 59 +++++++ test/token/ERC20/ERC20/mod/mint.tree | 10 ++ test/token/ERC20/ERC20/mod/transfer.t.sol | 66 ++++++++ test/token/ERC20/ERC20/mod/transfer.tree | 10 ++ test/token/ERC20/ERC20/mod/transferFrom.t.sol | 154 ++++++++++++++++++ test/token/ERC20/ERC20/mod/transferFrom.tree | 22 +++ 10 files changed, 439 insertions(+) create mode 100644 test/token/ERC20/ERC20/mod/approve.t.sol create mode 100644 test/token/ERC20/ERC20/mod/approve.tree create mode 100644 test/token/ERC20/ERC20/mod/burn.t.sol create mode 100644 test/token/ERC20/ERC20/mod/burn.tree create mode 100644 test/token/ERC20/ERC20/mod/mint.t.sol create mode 100644 test/token/ERC20/ERC20/mod/mint.tree create mode 100644 test/token/ERC20/ERC20/mod/transfer.t.sol create mode 100644 test/token/ERC20/ERC20/mod/transfer.tree create mode 100644 test/token/ERC20/ERC20/mod/transferFrom.t.sol create mode 100644 test/token/ERC20/ERC20/mod/transferFrom.tree diff --git a/test/token/ERC20/ERC20/mod/approve.t.sol b/test/token/ERC20/ERC20/mod/approve.t.sol new file mode 100644 index 00000000..671a0320 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/approve.t.sol @@ -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); + } +} + diff --git a/test/token/ERC20/ERC20/mod/approve.tree b/test/token/ERC20/ERC20/mod/approve.tree new file mode 100644 index 00000000..44b17877 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/approve.tree @@ -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 \ No newline at end of file diff --git a/test/token/ERC20/ERC20/mod/burn.t.sol b/test/token/ERC20/ERC20/mod/burn.t.sol new file mode 100644 index 00000000..3f77d72a --- /dev/null +++ b/test/token/ERC20/ERC20/mod/burn.t.sol @@ -0,0 +1,66 @@ +// 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); + + // Setup + harness.mint(account, balance); + + // Test + 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); + + // Setup + harness.mint(account, balance); + + uint256 beforeTotalSupply = harness.totalSupply(); + uint256 beforeBalanceOfAccount = harness.balanceOf(account); + + // Test + 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)"); + } +} + diff --git a/test/token/ERC20/ERC20/mod/burn.tree b/test/token/ERC20/ERC20/mod/burn.tree new file mode 100644 index 00000000..2faa2990 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/burn.tree @@ -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 diff --git a/test/token/ERC20/ERC20/mod/mint.t.sol b/test/token/ERC20/ERC20/mod/mint.t.sol new file mode 100644 index 00000000..a193dcfa --- /dev/null +++ b/test/token/ERC20/ERC20/mod/mint.t.sol @@ -0,0 +1,59 @@ +// 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); + + // Setup: mint MAX_UINT256 tokens to alice + harness.mint(users.alice, MAX_UINT256); + + // Test + 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)"); + } +} + diff --git a/test/token/ERC20/ERC20/mod/mint.tree b/test/token/ERC20/ERC20/mod/mint.tree new file mode 100644 index 00000000..3045ce31 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/mint.tree @@ -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 diff --git a/test/token/ERC20/ERC20/mod/transfer.t.sol b/test/token/ERC20/ERC20/mod/transfer.t.sol new file mode 100644 index 00000000..ed710675 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/transfer.t.sol @@ -0,0 +1,66 @@ +// 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); + + // Setup + harness.mint(users.alice, balance); + + // Test + 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); + balance = bound(balance, 1, MAX_UINT256); + value = bound(value, 1, balance); + + // Setup + harness.mint(users.alice, balance); + + uint256 beforeBalanceOfAlice = harness.balanceOf(users.alice); + uint256 beforeBalanceOfTo = harness.balanceOf(to); + + // Test + 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)"); + } +} + diff --git a/test/token/ERC20/ERC20/mod/transfer.tree b/test/token/ERC20/ERC20/mod/transfer.tree new file mode 100644 index 00000000..aae5f95c --- /dev/null +++ b/test/token/ERC20/ERC20/mod/transfer.tree @@ -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 diff --git a/test/token/ERC20/ERC20/mod/transferFrom.t.sol b/test/token/ERC20/ERC20/mod/transferFrom.t.sol new file mode 100644 index 00000000..3fe0289f --- /dev/null +++ b/test/token/ERC20/ERC20/mod/transferFrom.t.sol @@ -0,0 +1,154 @@ +// 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 TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { + ERC20Harness internal harness; + + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + function setUp() public override { + Base_Test.setUp(); + + harness = new ERC20Harness(); + } + + function testFuzz_ShouldRevert_SenderIsZeroAddress(address to, uint256 value) external { + vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidSender.selector, ADDRESS_ZERO)); + harness.transferFrom(ADDRESS_ZERO, to, value); + } + + function testFuzz_ShouldRevert_ReceiverIsZeroAddress(address from, uint256 value) + external + whenSenderNotZeroAddress + { + vm.assume(from != ADDRESS_ZERO); + + vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidReceiver.selector, ADDRESS_ZERO)); + harness.transferFrom(from, ADDRESS_ZERO, value); + } + + function testFuzz_ShouldRevert_SpenderAllowanceLtAmount(address from, address to, uint256 value, uint256 allowance) + external + whenSenderNotZeroAddress + whenReceiverNotZeroAddress + { + vm.assume(from != ADDRESS_ZERO); + vm.assume(to != ADDRESS_ZERO); + allowance = bound(allowance, 0, MAX_UINT256 - 1); + value = bound(value, allowance + 1, MAX_UINT256); + + // Setup + setMsgSender(from); + harness.approve(users.sender, allowance); + setMsgSender(users.sender); + + // Test + vm.expectRevert( + abi.encodeWithSelector(ERC20Mod.ERC20InsufficientAllowance.selector, users.sender, allowance, value) + ); + harness.transferFrom(from, to, value); + } + + function testFuzz_ShouldRevert_SenderBalanceLtAmount( + address from, + address to, + uint256 value, + uint256 allowance, + uint256 balance + ) external whenSenderNotZeroAddress whenReceiverNotZeroAddress givenWhenSpenderAllowanceGETransferAmount { + vm.assume(from != ADDRESS_ZERO); + vm.assume(to != ADDRESS_ZERO); + + value = bound(value, 1, MAX_UINT256); + allowance = bound(allowance, value, MAX_UINT256); // allowance >= value + balance = bound(balance, 0, value - 1); // balance < value + + // Setup + harness.mint(from, balance); + + setMsgSender(from); + harness.approve(users.sender, allowance); + setMsgSender(users.sender); + + // Test + vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, from, balance, value)); + harness.transferFrom(from, to, value); + } + + function testFuzz_TransferFrom_InfiniteApproval(address from, address to, uint256 value, uint256 balance) + external + whenSenderNotZeroAddress + whenReceiverNotZeroAddress + givenWhenSpenderAllowanceGETransferAmount + givenWhenSenderBalanceGETransferAmount + { + vm.assume(from != ADDRESS_ZERO); + vm.assume(to != ADDRESS_ZERO); + vm.assume(to != from); + vm.assume(users.sender != from); + + value = bound(value, 1, MAX_UINT256); + balance = bound(balance, value, MAX_UINT256); + + // Setup + harness.mint(from, balance); + + setMsgSender(from); + harness.approve(users.sender, MAX_UINT256); + setMsgSender(users.sender); + + // Test + uint256 beforeBalanceOfFrom = harness.balanceOf(from); + uint256 beforeBalanceOfTo = harness.balanceOf(to); + + vm.expectEmit(address(harness)); + emit Transfer(from, to, value); + harness.transferFrom(from, to, value); + + assertEq(harness.balanceOf(from), beforeBalanceOfFrom - value, "balanceOf(from)"); + assertEq(harness.balanceOf(to), beforeBalanceOfTo + value, "balanceOf(to)"); + } + + function testFuzz_TransferFrom(address from, address to, uint256 value, uint256 allowance, uint256 balance) + external + whenSenderNotZeroAddress + whenReceiverNotZeroAddress + givenWhenSpenderAllowanceGETransferAmount + givenWhenSenderBalanceGETransferAmount + { + vm.assume(from != ADDRESS_ZERO); + vm.assume(to != ADDRESS_ZERO); + vm.assume(to != from); + vm.assume(users.sender != from); + + value = bound(value, 1, MAX_UINT256 - 1); + allowance = bound(allowance, value, MAX_UINT256 - 1); + balance = bound(balance, value, MAX_UINT256); + + // Setup + harness.mint(from, balance); + + setMsgSender(from); + harness.approve(users.sender, allowance); + setMsgSender(users.sender); + + // Test + uint256 beforeBalanceOfFrom = harness.balanceOf(from); + uint256 beforeBalanceOfTo = harness.balanceOf(to); + + vm.expectEmit(address(harness)); + emit Transfer(from, to, value); + harness.transferFrom(from, to, value); + + assertEq(harness.balanceOf(from), beforeBalanceOfFrom - value, "balanceOf(from)"); + assertEq(harness.balanceOf(to), beforeBalanceOfTo + value, "balanceOf(to)"); + assertEq(harness.allowance(from, users.sender), allowance - value, "allowance(from, users.sender)"); + } +} diff --git a/test/token/ERC20/ERC20/mod/transferFrom.tree b/test/token/ERC20/ERC20/mod/transferFrom.tree new file mode 100644 index 00000000..e774d902 --- /dev/null +++ b/test/token/ERC20/ERC20/mod/transferFrom.tree @@ -0,0 +1,22 @@ +TransferFrom_ERC20Mod_Fuzz_Unit_Test +├── when the sender is the zero address +│ └── it should revert +└── when the sender is not the zero address + ├── when the receiver is the zero address + │ └── it should revert + └── when the receiver is not the zero address + ├── given when the spender's allowance is less than the transfer amount + │ └── it should revert + └── given when the spender's allowance is greater than, or equal to, the transfer amount + ├── given when the sender's balance is less than the transfer amount + │ └── it should revert + └── given when the sender's balance is greater than, or equal to, the transfer amount + ├── given when the spender's allowance equals the maximum uint256 value + │ ├── it should decrement the sender's balance by the transfer amount + │ ├── it should increment the receiver's balance by the transfer amount + │ └── it should emit a {Transfer} event + └── given when the sender's allowance does not equal the maximum uint256 value + ├── it should decrement the spender's allowance by the transfer amount + ├── it should decrement the sender's balance by the transfer amount + ├── it should increment the receiver's balance by the transfer amount + └── it should emit a {Transfer} event From 913156c3287a29553ed1a5aec4ca6a03f86712dc Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Mon, 15 Dec 2025 20:19:34 +0100 Subject: [PATCH 3/5] fix: ci --- test/Base.t.sol | 8 ++------ test/token/ERC20/ERC20/mod/burn.t.sol | 4 ---- test/token/ERC20/ERC20/mod/mint.t.sol | 2 -- test/token/ERC20/ERC20/mod/transfer.t.sol | 5 +---- test/token/ERC20/ERC20/mod/transferFrom.t.sol | 8 -------- test/utils/Utils.sol | 3 +-- 6 files changed, 4 insertions(+), 26 deletions(-) diff --git a/test/Base.t.sol b/test/Base.t.sol index 47c983b6..991c01aa 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -27,18 +27,14 @@ abstract contract Base_Test is Constants, Modifiers, StdAssertions, StdCheats { //////////////////////////////////////////////////////////////*/ function setUp() public virtual { - // Deploy defaults contract defaults = new Defaults(); - // Create and set test users createTestUsers(); defaults.setUsers(users); - // Set the variables in the Modifiers contract - setVariables(defaults, users); + setVariables(defaults, users); // set in modifier contract - // Set alice as the default caller for the tests - setMsgSender(users.alice); + setMsgSender(users.alice); // alice default caller } /*////////////////////////////////////////////////////////////// diff --git a/test/token/ERC20/ERC20/mod/burn.t.sol b/test/token/ERC20/ERC20/mod/burn.t.sol index 3f77d72a..ffa30e8d 100644 --- a/test/token/ERC20/ERC20/mod/burn.t.sol +++ b/test/token/ERC20/ERC20/mod/burn.t.sol @@ -31,10 +31,8 @@ contract Burn_ERC20Mod_Fuzz_Unit_Test is Base_Test { vm.assume(balance < MAX_UINT256); value = bound(value, balance + 1, MAX_UINT256); - // Setup harness.mint(account, balance); - // Test vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, account, balance, value)); harness.burn(account, value); } @@ -48,13 +46,11 @@ contract Burn_ERC20Mod_Fuzz_Unit_Test is Base_Test { balance = bound(balance, 1, MAX_UINT256); value = bound(value, 1, balance); - // Setup harness.mint(account, balance); uint256 beforeTotalSupply = harness.totalSupply(); uint256 beforeBalanceOfAccount = harness.balanceOf(account); - // Test vm.expectEmit(address(harness)); emit Transfer(account, ADDRESS_ZERO, value); harness.burn(account, value); diff --git a/test/token/ERC20/ERC20/mod/mint.t.sol b/test/token/ERC20/ERC20/mod/mint.t.sol index a193dcfa..d88823c2 100644 --- a/test/token/ERC20/ERC20/mod/mint.t.sol +++ b/test/token/ERC20/ERC20/mod/mint.t.sol @@ -30,10 +30,8 @@ contract Mint_ERC20Mod_Fuzz_Unit_Test is Base_Test { vm.assume(account != ADDRESS_ZERO); vm.assume(value > 0); - // Setup: mint MAX_UINT256 tokens to alice harness.mint(users.alice, MAX_UINT256); - // Test vm.expectRevert(stdError.arithmeticError); harness.mint(account, value); } diff --git a/test/token/ERC20/ERC20/mod/transfer.t.sol b/test/token/ERC20/ERC20/mod/transfer.t.sol index ed710675..a6ef99a9 100644 --- a/test/token/ERC20/ERC20/mod/transfer.t.sol +++ b/test/token/ERC20/ERC20/mod/transfer.t.sol @@ -31,10 +31,8 @@ contract Transfer_ERC20Mod_Fuzz_Unit_Test is Base_Test { vm.assume(balance < MAX_UINT256); value = bound(value, balance + 1, MAX_UINT256); - // Setup harness.mint(users.alice, balance); - // Test vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, users.alice, balance, value)); harness.transfer(to, value); } @@ -45,16 +43,15 @@ contract Transfer_ERC20Mod_Fuzz_Unit_Test is Base_Test { givenWhenSenderBalanceGETransferAmount { vm.assume(to != ADDRESS_ZERO); + vm.assume(to != users.alice); balance = bound(balance, 1, MAX_UINT256); value = bound(value, 1, balance); - // Setup harness.mint(users.alice, balance); uint256 beforeBalanceOfAlice = harness.balanceOf(users.alice); uint256 beforeBalanceOfTo = harness.balanceOf(to); - // Test vm.expectEmit(address(harness)); emit Transfer(users.alice, to, value); harness.transfer(to, value); diff --git a/test/token/ERC20/ERC20/mod/transferFrom.t.sol b/test/token/ERC20/ERC20/mod/transferFrom.t.sol index 3fe0289f..1f58bd5e 100644 --- a/test/token/ERC20/ERC20/mod/transferFrom.t.sol +++ b/test/token/ERC20/ERC20/mod/transferFrom.t.sol @@ -44,12 +44,10 @@ contract TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { allowance = bound(allowance, 0, MAX_UINT256 - 1); value = bound(value, allowance + 1, MAX_UINT256); - // Setup setMsgSender(from); harness.approve(users.sender, allowance); setMsgSender(users.sender); - // Test vm.expectRevert( abi.encodeWithSelector(ERC20Mod.ERC20InsufficientAllowance.selector, users.sender, allowance, value) ); @@ -70,14 +68,12 @@ contract TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { allowance = bound(allowance, value, MAX_UINT256); // allowance >= value balance = bound(balance, 0, value - 1); // balance < value - // Setup harness.mint(from, balance); setMsgSender(from); harness.approve(users.sender, allowance); setMsgSender(users.sender); - // Test vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InsufficientBalance.selector, from, balance, value)); harness.transferFrom(from, to, value); } @@ -97,14 +93,12 @@ contract TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { value = bound(value, 1, MAX_UINT256); balance = bound(balance, value, MAX_UINT256); - // Setup harness.mint(from, balance); setMsgSender(from); harness.approve(users.sender, MAX_UINT256); setMsgSender(users.sender); - // Test uint256 beforeBalanceOfFrom = harness.balanceOf(from); uint256 beforeBalanceOfTo = harness.balanceOf(to); @@ -132,14 +126,12 @@ contract TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { allowance = bound(allowance, value, MAX_UINT256 - 1); balance = bound(balance, value, MAX_UINT256); - // Setup harness.mint(from, balance); setMsgSender(from); harness.approve(users.sender, allowance); setMsgSender(users.sender); - // Test uint256 beforeBalanceOfFrom = harness.balanceOf(from); uint256 beforeBalanceOfTo = harness.balanceOf(to); diff --git a/test/utils/Utils.sol b/test/utils/Utils.sol index e6032681..6be22267 100644 --- a/test/utils/Utils.sol +++ b/test/utils/Utils.sol @@ -19,7 +19,6 @@ abstract contract Utils is StdBase, StdUtils { vm.stopPrank(); vm.startPrank(msgSender); - // Deal some ETH to the new caller. - vm.deal(msgSender, 1 ether); + vm.deal(msgSender, 1 ether); // Deal ETH to new caller. } } From 9a2eb544981cea71bf7b174ae168328f6f3ea88f Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Wed, 17 Dec 2025 10:16:31 +0100 Subject: [PATCH 4/5] test: centralise ERC-20 harnesses under test/harnesses/ --- .../ERC20/ERC20/ERC20BurnFacetHarness.sol | 52 ++++++++++++ .../token/ERC20/ERC20/ERC20FacetHarness.sol | 37 ++++++++ .../token/ERC20/ERC20/ERC20Harness.sol | 84 +++++++++++++++++++ .../ERC20/ERC20/ERC20PermitFacetHarness.sol | 81 ++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol create mode 100644 test/harnesses/token/ERC20/ERC20/ERC20FacetHarness.sol create mode 100644 test/harnesses/token/ERC20/ERC20/ERC20Harness.sol create mode 100644 test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol new file mode 100644 index 00000000..5f73e3a3 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {ERC20BurnFacet} from "src/token/ERC20/ERC20/ERC20BurnFacet.sol"; + +/** + * @title ERC20BurnFacetHarness + * @notice Test harness for ERC20BurnFacet that adds initialization and minting for testing + */ +contract ERC20BurnFacetHarness is ERC20BurnFacet { + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + /** + * @notice ERC20 view helpers so tests can call the standard API + */ + function balanceOf(address _account) external view returns (uint256) { + return getStorage().balanceOf[_account]; + } + + function totalSupply() external view returns (uint256) { + return getStorage().totalSupply; + } + + function allowance(address _owner, address _spender) external view returns (uint256) { + return getStorage().allowance[_owner][_spender]; + } + + /** + * @notice Minimal approve implementation for tests (writes into the same storage used by burnFrom) + */ + function approve(address _spender, uint256 _value) external returns (bool) { + require(_spender != address(0), "ERC20: approve to zero address"); + ERC20Storage storage s = getStorage(); + s.allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @notice Mint tokens to an address + * @dev Only used for testing - exposes internal mint functionality + */ + function mint(address _to, uint256 _value) external { + ERC20Storage storage s = getStorage(); + require(_to != address(0), "ERC20: mint to zero address"); + unchecked { + s.totalSupply += _value; + s.balanceOf[_to] += _value; + } + emit Transfer(address(0), _to, _value); + } +} diff --git a/test/harnesses/token/ERC20/ERC20/ERC20FacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20FacetHarness.sol new file mode 100644 index 00000000..3a3dbcf0 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20/ERC20FacetHarness.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {ERC20Facet} from "src/token/ERC20/ERC20/ERC20Facet.sol"; + +/** + * @title ERC20FacetHarness + * @notice Test harness for ERC20Facet that adds initialization and minting for testing + */ +contract ERC20FacetHarness is ERC20Facet { + /** + * @notice Initialize the ERC20 token storage + * @dev Only used for testing - production diamonds should initialize in constructor + */ + function initialize(string memory _name, string memory _symbol, uint8 _decimals) external { + ERC20Storage storage s = getStorage(); + s.name = _name; + s.symbol = _symbol; + s.decimals = _decimals; + } + + /** + * @notice Mint tokens to an address + * @dev Only used for testing - exposes internal mint functionality + */ + function mint(address _to, uint256 _value) external { + ERC20Storage storage s = getStorage(); + if (_to == address(0)) { + revert ERC20InvalidReceiver(address(0)); + } + unchecked { + s.totalSupply += _value; + s.balanceOf[_to] += _value; + } + emit Transfer(address(0), _to, _value); + } +} diff --git a/test/harnesses/token/ERC20/ERC20/ERC20Harness.sol b/test/harnesses/token/ERC20/ERC20/ERC20Harness.sol new file mode 100644 index 00000000..43622016 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20/ERC20Harness.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; + +/** + * @title ERC20Harness + * @notice Test harness that exposes LibERC20's internal functions as external + * @dev Required for testing since LibERC20 only has internal functions + */ +contract ERC20Harness { + /** + * @notice Initialize the ERC20 token storage + * @dev Only used for testing + */ + function initialize(string memory _name, string memory _symbol, uint8 _decimals) external { + ERC20Mod.ERC20Storage storage s = ERC20Mod.getStorage(); + s.name = _name; + s.symbol = _symbol; + s.decimals = _decimals; + } + + /** + * @notice Exposes ERC20Mod.mint as an external function + */ + function mint(address _account, uint256 _value) external { + ERC20Mod.mint(_account, _value); + } + + /** + * @notice Exposes ERC20Mod.burn as an external function + */ + function burn(address _account, uint256 _value) external { + ERC20Mod.burn(_account, _value); + } + + /** + * @notice Exposes ERC20Mod.transferFrom as an external function + */ + function transferFrom(address _from, address _to, uint256 _value) external { + ERC20Mod.transferFrom(_from, _to, _value); + } + + /** + * @notice Exposes ERC20Mod.transfer as an external function + */ + function transfer(address _to, uint256 _value) external { + ERC20Mod.transfer(_to, _value); + } + + /** + * @notice Exposes ERC20Mod.approve as an external function + */ + function approve(address _spender, uint256 _value) external { + ERC20Mod.approve(_spender, _value); + } + + /** + * @notice Get storage values for testing + */ + function name() external view returns (string memory) { + return ERC20Mod.getStorage().name; + } + + function symbol() external view returns (string memory) { + return ERC20Mod.getStorage().symbol; + } + + function decimals() external view returns (uint8) { + return ERC20Mod.getStorage().decimals; + } + + function totalSupply() external view returns (uint256) { + return ERC20Mod.getStorage().totalSupply; + } + + function balanceOf(address _account) external view returns (uint256) { + return ERC20Mod.getStorage().balanceOf[_account]; + } + + function allowance(address _owner, address _spender) external view returns (uint256) { + return ERC20Mod.getStorage().allowance[_owner][_spender]; + } +} diff --git a/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol new file mode 100644 index 00000000..20593c11 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {ERC20PermitFacet} from "src/token/ERC20/ERC20Permit/ERC20PermitFacet.sol"; + +/** + * @title ERC20PermitFacetHarness + * @notice Test harness for ERC20PermitFacet that adds initialization and minting for testing + */ +contract ERC20PermitFacetHarness is ERC20PermitFacet { + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + /** + * @notice Initialize the ERC20 token storage + * @dev Only used for testing - production diamonds should initialize in constructor + */ + function initialize(string memory _name) external { + ERC20Storage storage s = getERC20Storage(); + s.name = _name; + } + + /** + * @notice Mint tokens to an address + * @dev Only used for testing - exposes internal mint functionality + */ + function mint(address _to, uint256 _value) external { + ERC20Storage storage s = getERC20Storage(); + require(_to != address(0), "ERC20: mint to zero address"); + unchecked { + s.totalSupply += _value; + s.balanceOf[_to] += _value; + } + emit Transfer(address(0), _to, _value); + } + + /** + * @notice ERC20 view helpers so tests can call the standard API + */ + function balanceOf(address _account) external view returns (uint256) { + return getERC20Storage().balanceOf[_account]; + } + + function totalSupply() external view returns (uint256) { + return getERC20Storage().totalSupply; + } + + function allowance(address _owner, address _spender) external view returns (uint256) { + return getERC20Storage().allowance[_owner][_spender]; + } + + /** + * @notice Minimal approve implementation for tests + */ + function approve(address _spender, uint256 _value) external returns (bool) { + require(_spender != address(0), "ERC20: approve to zero address"); + ERC20Storage storage s = getERC20Storage(); + s.allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } + + /** + * @notice TransferFrom implementation for tests (needed by test_Permit_ThenTransferFrom) + */ + function transferFrom(address _from, address _to, uint256 _value) external returns (bool) { + ERC20Storage storage s = getERC20Storage(); + require(_to != address(0), "ERC20: transfer to zero address"); + require(s.balanceOf[_from] >= _value, "ERC20: insufficient balance"); + + uint256 currentAllowance = s.allowance[_from][msg.sender]; + require(currentAllowance >= _value, "ERC20: insufficient allowance"); + + unchecked { + s.allowance[_from][msg.sender] = currentAllowance - _value; + s.balanceOf[_from] -= _value; + } + s.balanceOf[_to] += _value; + emit Transfer(_from, _to, _value); + return true; + } +} From 37c318aa3171ed45f26b7301788cc43dd384329f Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Wed, 17 Dec 2025 10:18:16 +0100 Subject: [PATCH 5/5] test: move BTT ERC-20 module tests to test/unit/ structure --- .../concrete/token/ERC20/ERC20/metadata.t.sol | 35 +++++++++++++++++++ .../fuzz}/token/ERC20/ERC20/mod/approve.t.sol | 6 ++-- .../fuzz}/token/ERC20/ERC20/mod/approve.tree | 0 .../fuzz}/token/ERC20/ERC20/mod/burn.t.sol | 6 ++-- .../fuzz}/token/ERC20/ERC20/mod/burn.tree | 0 .../fuzz}/token/ERC20/ERC20/mod/mint.t.sol | 6 ++-- .../fuzz}/token/ERC20/ERC20/mod/mint.tree | 0 .../token/ERC20/ERC20/mod/transfer.t.sol | 6 ++-- .../fuzz}/token/ERC20/ERC20/mod/transfer.tree | 0 .../token/ERC20/ERC20/mod/transferFrom.t.sol | 6 ++-- .../token/ERC20/ERC20/mod/transferFrom.tree | 0 test/utils/Utils.sol | 2 -- 12 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 test/unit/concrete/token/ERC20/ERC20/metadata.t.sol rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/approve.t.sol (84%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/approve.tree (100%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/burn.t.sol (91%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/burn.tree (100%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/mint.t.sol (90%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/mint.tree (100%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/transfer.t.sol (91%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/transfer.tree (100%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/transferFrom.t.sol (96%) rename test/{ => unit/fuzz}/token/ERC20/ERC20/mod/transferFrom.tree (100%) diff --git a/test/unit/concrete/token/ERC20/ERC20/metadata.t.sol b/test/unit/concrete/token/ERC20/ERC20/metadata.t.sol new file mode 100644 index 00000000..4a7629db --- /dev/null +++ b/test/unit/concrete/token/ERC20/ERC20/metadata.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {stdError} from "forge-std/StdError.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; + +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; + +contract Metadata_ERC20Mod_Concrete_Unit_Test is Base_Test { + ERC20Harness internal harness; + + function setUp() public override { + Base_Test.setUp(); + + harness = new ERC20Harness(); + harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS); + } + + function test_Name() external view { + assertEq(harness.name(), "Test Token"); + } + + function test_Symbol() external view { + assertEq(harness.symbol(), "TEST"); + } + + function test_Decimals() external view { + assertEq(harness.decimals(), 18); + } + + function test_InitialTotalSupply() external view { + assertEq(harness.totalSupply(), 0); + } +} diff --git a/test/token/ERC20/ERC20/mod/approve.t.sol b/test/unit/fuzz/token/ERC20/ERC20/mod/approve.t.sol similarity index 84% rename from test/token/ERC20/ERC20/mod/approve.t.sol rename to test/unit/fuzz/token/ERC20/ERC20/mod/approve.t.sol index 671a0320..829efb6b 100644 --- a/test/token/ERC20/ERC20/mod/approve.t.sol +++ b/test/unit/fuzz/token/ERC20/ERC20/mod/approve.t.sol @@ -2,10 +2,10 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; -import {Base_Test} from "../../../../Base.t.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; -import {ERC20Harness} from "../harnesses/ERC20Harness.sol"; -import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; contract Approve_ERC20Mod_Fuzz_Unit_Test is Base_Test { ERC20Harness internal harness; diff --git a/test/token/ERC20/ERC20/mod/approve.tree b/test/unit/fuzz/token/ERC20/ERC20/mod/approve.tree similarity index 100% rename from test/token/ERC20/ERC20/mod/approve.tree rename to test/unit/fuzz/token/ERC20/ERC20/mod/approve.tree diff --git a/test/token/ERC20/ERC20/mod/burn.t.sol b/test/unit/fuzz/token/ERC20/ERC20/mod/burn.t.sol similarity index 91% rename from test/token/ERC20/ERC20/mod/burn.t.sol rename to test/unit/fuzz/token/ERC20/ERC20/mod/burn.t.sol index ffa30e8d..50b933bb 100644 --- a/test/token/ERC20/ERC20/mod/burn.t.sol +++ b/test/unit/fuzz/token/ERC20/ERC20/mod/burn.t.sol @@ -2,10 +2,10 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; -import {Base_Test} from "../../../../Base.t.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; -import {ERC20Harness} from "../harnesses/ERC20Harness.sol"; -import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; contract Burn_ERC20Mod_Fuzz_Unit_Test is Base_Test { ERC20Harness internal harness; diff --git a/test/token/ERC20/ERC20/mod/burn.tree b/test/unit/fuzz/token/ERC20/ERC20/mod/burn.tree similarity index 100% rename from test/token/ERC20/ERC20/mod/burn.tree rename to test/unit/fuzz/token/ERC20/ERC20/mod/burn.tree diff --git a/test/token/ERC20/ERC20/mod/mint.t.sol b/test/unit/fuzz/token/ERC20/ERC20/mod/mint.t.sol similarity index 90% rename from test/token/ERC20/ERC20/mod/mint.t.sol rename to test/unit/fuzz/token/ERC20/ERC20/mod/mint.t.sol index d88823c2..6bc10277 100644 --- a/test/token/ERC20/ERC20/mod/mint.t.sol +++ b/test/unit/fuzz/token/ERC20/ERC20/mod/mint.t.sol @@ -2,10 +2,10 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; -import {Base_Test} from "../../../../Base.t.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; -import {ERC20Harness} from "../harnesses/ERC20Harness.sol"; -import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; contract Mint_ERC20Mod_Fuzz_Unit_Test is Base_Test { ERC20Harness internal harness; diff --git a/test/token/ERC20/ERC20/mod/mint.tree b/test/unit/fuzz/token/ERC20/ERC20/mod/mint.tree similarity index 100% rename from test/token/ERC20/ERC20/mod/mint.tree rename to test/unit/fuzz/token/ERC20/ERC20/mod/mint.tree diff --git a/test/token/ERC20/ERC20/mod/transfer.t.sol b/test/unit/fuzz/token/ERC20/ERC20/mod/transfer.t.sol similarity index 91% rename from test/token/ERC20/ERC20/mod/transfer.t.sol rename to test/unit/fuzz/token/ERC20/ERC20/mod/transfer.t.sol index a6ef99a9..222b94ed 100644 --- a/test/token/ERC20/ERC20/mod/transfer.t.sol +++ b/test/unit/fuzz/token/ERC20/ERC20/mod/transfer.t.sol @@ -2,10 +2,10 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; -import {Base_Test} from "../../../../Base.t.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; -import {ERC20Harness} from "../harnesses/ERC20Harness.sol"; -import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; contract Transfer_ERC20Mod_Fuzz_Unit_Test is Base_Test { ERC20Harness internal harness; diff --git a/test/token/ERC20/ERC20/mod/transfer.tree b/test/unit/fuzz/token/ERC20/ERC20/mod/transfer.tree similarity index 100% rename from test/token/ERC20/ERC20/mod/transfer.tree rename to test/unit/fuzz/token/ERC20/ERC20/mod/transfer.tree diff --git a/test/token/ERC20/ERC20/mod/transferFrom.t.sol b/test/unit/fuzz/token/ERC20/ERC20/mod/transferFrom.t.sol similarity index 96% rename from test/token/ERC20/ERC20/mod/transferFrom.t.sol rename to test/unit/fuzz/token/ERC20/ERC20/mod/transferFrom.t.sol index 1f58bd5e..7e68aad4 100644 --- a/test/token/ERC20/ERC20/mod/transferFrom.t.sol +++ b/test/unit/fuzz/token/ERC20/ERC20/mod/transferFrom.t.sol @@ -2,10 +2,10 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; -import {Base_Test} from "../../../../Base.t.sol"; +import {Base_Test} from "test/Base.t.sol"; +import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol"; -import {ERC20Harness} from "../harnesses/ERC20Harness.sol"; -import "../../../../../src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; +import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod; contract TransferFrom_ERC20Mod_Fuzz_Unit_Test is Base_Test { ERC20Harness internal harness; diff --git a/test/token/ERC20/ERC20/mod/transferFrom.tree b/test/unit/fuzz/token/ERC20/ERC20/mod/transferFrom.tree similarity index 100% rename from test/token/ERC20/ERC20/mod/transferFrom.tree rename to test/unit/fuzz/token/ERC20/ERC20/mod/transferFrom.tree diff --git a/test/utils/Utils.sol b/test/utils/Utils.sol index 6be22267..32354477 100644 --- a/test/utils/Utils.sol +++ b/test/utils/Utils.sol @@ -9,12 +9,10 @@ abstract contract Utils is StdBase, StdUtils { MISC //////////////////////////////////////////////////////////////*/ - /// @dev Retrieves the current block timestamp as an `uint40`. function getBlockTimestamp() internal view returns (uint40) { return uint40(vm.getBlockTimestamp()); } - /// @dev Stops the active prank and sets a new one. function setMsgSender(address msgSender) internal { vm.stopPrank(); vm.startPrank(msgSender);