From 781dff870f62fe9a939ffb6a3697e02e461a0cfa Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Tue, 6 Jan 2026 18:01:40 +0100 Subject: [PATCH 1/3] test: remove unused ERC-20 harnesses --- .../ERC20/ERC20/ERC20BurnFacetHarness.sol | 56 ------------ .../ERC20/ERC20/ERC20PermitFacetHarness.sol | 85 ------------------- .../ERC20/ERC20/ERC20TransferFacetHarness.sol | 30 ------- 3 files changed, 171 deletions(-) delete mode 100644 test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol delete mode 100644 test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol delete mode 100644 test/harnesses/token/ERC20/ERC20/ERC20TransferFacetHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol deleted file mode 100644 index c117dae9..00000000 --- a/test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -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"); - ERC20TransferStorage 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 { - ERC20TransferStorage 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/ERC20PermitFacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol deleted file mode 100644 index e06ee93b..00000000 --- a/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -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 { - ERC20MetadataStorage storage s = getERC20MetadataStorage(); - 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 { - ERC20TransferStorage storage s = getERC20TransferStorage(); - 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 getERC20TransferStorage().balanceOf[_account]; - } - - function totalSupply() external view returns (uint256) { - return getERC20TransferStorage().totalSupply; - } - - function allowance(address _owner, address _spender) external view returns (uint256) { - return getERC20TransferStorage().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"); - ERC20TransferStorage storage s = getERC20TransferStorage(); - 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) { - ERC20TransferStorage storage s = getERC20TransferStorage(); - 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; - } -} diff --git a/test/harnesses/token/ERC20/ERC20/ERC20TransferFacetHarness.sol b/test/harnesses/token/ERC20/ERC20/ERC20TransferFacetHarness.sol deleted file mode 100644 index 83c2d177..00000000 --- a/test/harnesses/token/ERC20/ERC20/ERC20TransferFacetHarness.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC20TransferFacet} from "src/token/ERC20/ERC20/ERC20TransferFacet.sol"; - -/** - * @title ERC20TransferFacetHarness - * @notice Test harness for ERC20TransferFacet that adds minting for testing - */ -contract ERC20TransferFacetHarness is ERC20TransferFacet { - /** - * @notice Mint tokens to an address - * @dev Only used for testing - exposes internal mint functionality - */ - function mint(address _to, uint256 _value) external { - ERC20TransferStorage 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); - } -} From f2461aaf7ee45859eaabe10c30761d8970d47298 Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Tue, 6 Jan 2026 18:02:50 +0100 Subject: [PATCH 2/3] test: move harnesses to previous test suite location --- test/token/ERC20/ERC20/ERC20BurnFacet.t.sol | 2 +- test/token/ERC20/ERC20/ERC20PermitFacet.t.sol | 2 +- .../ERC20/harnesses/ERC20BurnFacetHarness.sol | 56 ++++++++++++ .../harnesses/ERC20PermitFacetHarness.sol | 85 +++++++++++++++++++ 4 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol create mode 100644 test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.sol diff --git a/test/token/ERC20/ERC20/ERC20BurnFacet.t.sol b/test/token/ERC20/ERC20/ERC20BurnFacet.t.sol index 1ca6cc40..c28ebb35 100644 --- a/test/token/ERC20/ERC20/ERC20BurnFacet.t.sol +++ b/test/token/ERC20/ERC20/ERC20BurnFacet.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {Test} from "forge-std/Test.sol"; import {ERC20BurnFacet} from "src/token/ERC20/ERC20/ERC20BurnFacet.sol"; -import {ERC20BurnFacetHarness} from "test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol"; +import {ERC20BurnFacetHarness} from "./harnesses/ERC20BurnFacetHarness.sol"; contract ERC20BurnFacetTest is Test { ERC20BurnFacetHarness public token; diff --git a/test/token/ERC20/ERC20/ERC20PermitFacet.t.sol b/test/token/ERC20/ERC20/ERC20PermitFacet.t.sol index 66d116fe..cdf6e98e 100644 --- a/test/token/ERC20/ERC20/ERC20PermitFacet.t.sol +++ b/test/token/ERC20/ERC20/ERC20PermitFacet.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {Test} from "forge-std/Test.sol"; import {ERC20PermitFacet} from "src/token/ERC20/ERC20Permit/ERC20PermitFacet.sol"; -import {ERC20PermitFacetHarness} from "test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol"; +import {ERC20PermitFacetHarness} from "./harnesses/ERC20PermitFacetHarness.sol"; contract ERC20BurnFacetTest is Test { ERC20PermitFacetHarness public token; diff --git a/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol b/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol new file mode 100644 index 00000000..c117dae9 --- /dev/null +++ b/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +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"); + ERC20TransferStorage 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 { + ERC20TransferStorage 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/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.sol b/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.sol new file mode 100644 index 00000000..e06ee93b --- /dev/null +++ b/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +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 { + ERC20MetadataStorage storage s = getERC20MetadataStorage(); + 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 { + ERC20TransferStorage storage s = getERC20TransferStorage(); + 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 getERC20TransferStorage().balanceOf[_account]; + } + + function totalSupply() external view returns (uint256) { + return getERC20TransferStorage().totalSupply; + } + + function allowance(address _owner, address _spender) external view returns (uint256) { + return getERC20TransferStorage().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"); + ERC20TransferStorage storage s = getERC20TransferStorage(); + 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) { + ERC20TransferStorage storage s = getERC20TransferStorage(); + 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 e3f19184538de58d857767856d5245315d50028b Mon Sep 17 00:00:00 2001 From: lumoswiz Date: Tue, 6 Jan 2026 18:03:26 +0100 Subject: [PATCH 3/3] test: add ERC20StorageUtils library --- test/utils/storage/ERC20StorageUtils.sol | 79 ++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/utils/storage/ERC20StorageUtils.sol diff --git a/test/utils/storage/ERC20StorageUtils.sol b/test/utils/storage/ERC20StorageUtils.sol new file mode 100644 index 00000000..630642a6 --- /dev/null +++ b/test/utils/storage/ERC20StorageUtils.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +/** + * @title ERC20StorageUtils + * @notice Storage manipulation utilities for ERC20 token testing + * @dev Uses vm.load and vm.store to directly manipulate storage slots + */ +library ERC20StorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant ERC20_TRANSFER_STORAGE_POSITION = keccak256("compose.erc20.transfer"); + + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + + /* + * @notice ERC-20 Transfer storage layout (ERC-8042 standard) + * @custom:storage-location erc8042:compose.erc20.transfer + * + * Slot 0: mapping(address owner => uint256 balance) balanceOf + * Slot 1: uint256 totalSupply + * Slot 2: mapping(address owner => mapping(address spender => uint256)) allowance + */ + + function balanceOf(address target, address owner) internal view returns (uint256) { + bytes32 slot = keccak256(abi.encode(owner, uint256(ERC20_TRANSFER_STORAGE_POSITION))); + return uint256(vm.load(target, slot)); + } + + function totalSupply(address target) internal view returns (uint256) { + bytes32 slot = bytes32(uint256(ERC20_TRANSFER_STORAGE_POSITION) + 1); + return uint256(vm.load(target, slot)); + } + + function allowance(address target, address owner, address spender) internal view returns (uint256) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC20_TRANSFER_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(spender, ownerSlot)); + return uint256(vm.load(target, slot)); + } + + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + function setBalance(address target, address owner, uint256 balance) internal { + bytes32 slot = keccak256(abi.encode(owner, uint256(ERC20_TRANSFER_STORAGE_POSITION))); + vm.store(target, slot, bytes32(balance)); + } + + function setTotalSupply(address target, uint256 supply) internal { + bytes32 slot = bytes32(uint256(ERC20_TRANSFER_STORAGE_POSITION) + 1); + vm.store(target, slot, bytes32(supply)); + } + + function setAllowance(address target, address owner, address spender, uint256 amount) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC20_TRANSFER_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(spender, ownerSlot)); + vm.store(target, slot, bytes32(amount)); + } + + /** + * @notice Mint tokens by updating balance and totalSupply + */ + function mint(address target, address to, uint256 amount) internal { + uint256 currentBalance = balanceOf(target, to); + uint256 currentSupply = totalSupply(target); + + setBalance(target, to, currentBalance + amount); + setTotalSupply(target, currentSupply + amount); + } +}