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); - } -} 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/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol b/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol rename to test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol b/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol rename to test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness.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); + } +}