From 3cc910abc607d3300d761e27e67555d15def7b8f Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 24 Mar 2026 13:37:55 -0400 Subject: [PATCH] add ERC6909 tests --- .../Approve/ERC6909ApproveFacet.sol | 0 .../Approve/ERC6909ApproveMod.sol | 0 .../{ERC6909 => }/Burn/ERC6909BurnFacet.sol | 0 .../{ERC6909 => }/Burn/ERC6909BurnMod.sol | 9 +- .../{ERC6909 => }/Data/ERC6909DataFacet.sol | 0 .../{ERC6909 => }/Mint/ERC6909MintMod.sol | 0 .../Operator/ERC6909OperatorFacet.sol | 0 .../Operator/ERC6909OperatorMod.sol | 0 .../Transfer/ERC6909TransferFacet.sol | 0 .../Transfer/ERC6909TransferMod.sol | 0 test/token/ERC6909/ERC6909/ERC6909.t.sol | 464 ----------------- test/token/ERC6909/ERC6909/ERC6909Facet.t.sol | 492 ------------------ .../ERC6909/harnesses/ERC6909FacetHarness.sol | 26 - .../ERC6909/harnesses/ERC6909Harness.sol | 46 -- test/trees/ERC6909.tree | 94 ++++ .../Approve/ERC6909ApproveFacetBase.t.sol | 20 + .../Approve/ERC6909ApproveModBase.t.sol | 20 + .../ERC6909/Approve/facet/fuzz/approve.t.sol | 44 ++ .../Approve/facet/fuzz/exportSelectors.t.sol | 20 + .../ERC6909/Approve/mod/fuzz/approve.t.sol | 24 + .../ERC6909/Burn/ERC6909BurnFacetBase.t.sol | 20 + .../ERC6909/Burn/ERC6909BurnModBase.t.sol | 20 + .../token/ERC6909/Burn/facet/fuzz/burn.t.sol | 113 ++++ .../Burn/facet/fuzz/exportSelectors.t.sol | 20 + .../token/ERC6909/Burn/mod/fuzz/burn.t.sol | 53 ++ .../ERC6909/Data/ERC6909DataFacetBase.t.sol | 20 + .../token/ERC6909/Data/facet/fuzz/data.t.sol | 46 ++ .../Data/facet/fuzz/exportSelectors.t.sol | 24 + test/unit/token/ERC6909/ERC6909TestBase.sol | 33 ++ .../ERC6909/Mint/ERC6909MintModBase.t.sol | 20 + .../token/ERC6909/Mint/mod/fuzz/mint.t.sol | 51 ++ .../Operator/ERC6909OperatorFacetBase.t.sol | 20 + .../Operator/ERC6909OperatorModBase.t.sol | 20 + .../Operator/facet/fuzz/exportSelectors.t.sol | 20 + .../Operator/facet/fuzz/setOperator.t.sol | 34 ++ .../Operator/mod/fuzz/setOperator.t.sol | 24 + .../Transfer/ERC6909TransferFacetBase.t.sol | 20 + .../Transfer/ERC6909TransferModBase.t.sol | 20 + .../Transfer/facet/fuzz/exportSelectors.t.sol | 21 + .../Transfer/facet/fuzz/transfer.t.sol | 197 +++++++ .../ERC6909/Transfer/mod/fuzz/transfer.t.sol | 76 +++ .../ERC6909/ERC6909ApproveModHarness.sol | 17 + .../token/ERC6909/ERC6909BurnModHarness.sol | 21 + .../token/ERC6909/ERC6909MintModHarness.sol | 17 + .../ERC6909/ERC6909OperatorModHarness.sol | 17 + .../ERC6909/ERC6909TransferModHarness.sol | 24 + test/utils/storage/ERC6909StorageUtils.sol | 56 ++ 47 files changed, 1253 insertions(+), 1030 deletions(-) rename src/token/ERC6909/{ERC6909 => }/Approve/ERC6909ApproveFacet.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Approve/ERC6909ApproveMod.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Burn/ERC6909BurnFacet.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Burn/ERC6909BurnMod.sol (93%) rename src/token/ERC6909/{ERC6909 => }/Data/ERC6909DataFacet.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Mint/ERC6909MintMod.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Operator/ERC6909OperatorFacet.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Operator/ERC6909OperatorMod.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Transfer/ERC6909TransferFacet.sol (100%) rename src/token/ERC6909/{ERC6909 => }/Transfer/ERC6909TransferMod.sol (100%) delete mode 100644 test/token/ERC6909/ERC6909/ERC6909.t.sol delete mode 100644 test/token/ERC6909/ERC6909/ERC6909Facet.t.sol delete mode 100644 test/token/ERC6909/ERC6909/harnesses/ERC6909FacetHarness.sol delete mode 100644 test/token/ERC6909/ERC6909/harnesses/ERC6909Harness.sol create mode 100644 test/trees/ERC6909.tree create mode 100644 test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol create mode 100644 test/unit/token/ERC6909/Approve/ERC6909ApproveModBase.t.sol create mode 100644 test/unit/token/ERC6909/Approve/facet/fuzz/approve.t.sol create mode 100644 test/unit/token/ERC6909/Approve/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC6909/Approve/mod/fuzz/approve.t.sol create mode 100644 test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol create mode 100644 test/unit/token/ERC6909/Burn/ERC6909BurnModBase.t.sol create mode 100644 test/unit/token/ERC6909/Burn/facet/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC6909/Burn/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC6909/Burn/mod/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol create mode 100644 test/unit/token/ERC6909/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/token/ERC6909/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC6909/ERC6909TestBase.sol create mode 100644 test/unit/token/ERC6909/Mint/ERC6909MintModBase.t.sol create mode 100644 test/unit/token/ERC6909/Mint/mod/fuzz/mint.t.sol create mode 100644 test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol create mode 100644 test/unit/token/ERC6909/Operator/ERC6909OperatorModBase.t.sol create mode 100644 test/unit/token/ERC6909/Operator/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC6909/Operator/facet/fuzz/setOperator.t.sol create mode 100644 test/unit/token/ERC6909/Operator/mod/fuzz/setOperator.t.sol create mode 100644 test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol create mode 100644 test/unit/token/ERC6909/Transfer/ERC6909TransferModBase.t.sol create mode 100644 test/unit/token/ERC6909/Transfer/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC6909/Transfer/facet/fuzz/transfer.t.sol create mode 100644 test/unit/token/ERC6909/Transfer/mod/fuzz/transfer.t.sol create mode 100644 test/utils/harnesses/token/ERC6909/ERC6909ApproveModHarness.sol create mode 100644 test/utils/harnesses/token/ERC6909/ERC6909BurnModHarness.sol create mode 100644 test/utils/harnesses/token/ERC6909/ERC6909MintModHarness.sol create mode 100644 test/utils/harnesses/token/ERC6909/ERC6909OperatorModHarness.sol create mode 100644 test/utils/harnesses/token/ERC6909/ERC6909TransferModHarness.sol create mode 100644 test/utils/storage/ERC6909StorageUtils.sol diff --git a/src/token/ERC6909/ERC6909/Approve/ERC6909ApproveFacet.sol b/src/token/ERC6909/Approve/ERC6909ApproveFacet.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Approve/ERC6909ApproveFacet.sol rename to src/token/ERC6909/Approve/ERC6909ApproveFacet.sol diff --git a/src/token/ERC6909/ERC6909/Approve/ERC6909ApproveMod.sol b/src/token/ERC6909/Approve/ERC6909ApproveMod.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Approve/ERC6909ApproveMod.sol rename to src/token/ERC6909/Approve/ERC6909ApproveMod.sol diff --git a/src/token/ERC6909/ERC6909/Burn/ERC6909BurnFacet.sol b/src/token/ERC6909/Burn/ERC6909BurnFacet.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Burn/ERC6909BurnFacet.sol rename to src/token/ERC6909/Burn/ERC6909BurnFacet.sol diff --git a/src/token/ERC6909/ERC6909/Burn/ERC6909BurnMod.sol b/src/token/ERC6909/Burn/ERC6909BurnMod.sol similarity index 93% rename from src/token/ERC6909/ERC6909/Burn/ERC6909BurnMod.sol rename to src/token/ERC6909/Burn/ERC6909BurnMod.sol index 50ad8a19..00ecde94 100644 --- a/src/token/ERC6909/ERC6909/Burn/ERC6909BurnMod.sol +++ b/src/token/ERC6909/Burn/ERC6909BurnMod.sol @@ -14,6 +14,11 @@ error ERC6909InsufficientBalance(address _sender, uint256 _balance, uint256 _nee */ error ERC6909InsufficientAllowance(address _spender, uint256 _allowance, uint256 _needed, uint256 _id); +/** + * @notice Thrown when the sender address is invalid. + */ +error ERC6909InvalidSender(address _sender); + /** * @notice Emitted when a transfer occurs. */ @@ -52,7 +57,7 @@ function getStorage() pure returns (ERC6909Storage storage s) { * @dev Emits a {Transfer} event to the zero address. * @param _amount The amount of tokens to burn. */ -function burn(uint256 _id, uint256 _amount) external { +function burn(uint256 _id, uint256 _amount) { ERC6909Storage storage s = getStorage(); uint256 fromBalance = s.balanceOf[msg.sender][_id]; @@ -74,7 +79,7 @@ function burn(uint256 _id, uint256 _amount) external { * @param _from The address whose tokens will be burned. * @param _amount The amount of tokens to burn. */ -function burnFrom(address _from, uint256 _id, uint256 _amount) external { +function burnFrom(address _from, uint256 _id, uint256 _amount) { if (_from == address(0)) { revert ERC6909InvalidSender(_from); } diff --git a/src/token/ERC6909/ERC6909/Data/ERC6909DataFacet.sol b/src/token/ERC6909/Data/ERC6909DataFacet.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Data/ERC6909DataFacet.sol rename to src/token/ERC6909/Data/ERC6909DataFacet.sol diff --git a/src/token/ERC6909/ERC6909/Mint/ERC6909MintMod.sol b/src/token/ERC6909/Mint/ERC6909MintMod.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Mint/ERC6909MintMod.sol rename to src/token/ERC6909/Mint/ERC6909MintMod.sol diff --git a/src/token/ERC6909/ERC6909/Operator/ERC6909OperatorFacet.sol b/src/token/ERC6909/Operator/ERC6909OperatorFacet.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Operator/ERC6909OperatorFacet.sol rename to src/token/ERC6909/Operator/ERC6909OperatorFacet.sol diff --git a/src/token/ERC6909/ERC6909/Operator/ERC6909OperatorMod.sol b/src/token/ERC6909/Operator/ERC6909OperatorMod.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Operator/ERC6909OperatorMod.sol rename to src/token/ERC6909/Operator/ERC6909OperatorMod.sol diff --git a/src/token/ERC6909/ERC6909/Transfer/ERC6909TransferFacet.sol b/src/token/ERC6909/Transfer/ERC6909TransferFacet.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Transfer/ERC6909TransferFacet.sol rename to src/token/ERC6909/Transfer/ERC6909TransferFacet.sol diff --git a/src/token/ERC6909/ERC6909/Transfer/ERC6909TransferMod.sol b/src/token/ERC6909/Transfer/ERC6909TransferMod.sol similarity index 100% rename from src/token/ERC6909/ERC6909/Transfer/ERC6909TransferMod.sol rename to src/token/ERC6909/Transfer/ERC6909TransferMod.sol diff --git a/test/token/ERC6909/ERC6909/ERC6909.t.sol b/test/token/ERC6909/ERC6909/ERC6909.t.sol deleted file mode 100644 index 3779175c..00000000 --- a/test/token/ERC6909/ERC6909/ERC6909.t.sol +++ /dev/null @@ -1,464 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {stdError} from "forge-std/StdError.sol"; -import {ERC6909Harness} from "./harnesses/ERC6909Harness.sol"; -import "../../../../src/token/ERC6909/ERC6909/ERC6909Mod.sol" as ERC6909Mod; -import {Transfer, OperatorSet, Approval} from "../../../../src/token/ERC6909/ERC6909/ERC6909Mod.sol"; - -contract LibERC6909Test is Test { - ERC6909Harness internal harness; - - address internal alice; - - uint256 internal constant TOKEN_ID = 72; - uint256 internal constant AMOUNT = 1e24; - - function setUp() public { - alice = makeAddr("alice"); - - harness = new ERC6909Harness(); - } - - /** - * ============================================ - * Mint Tests - * ============================================ - */ - - function test_ShouldRevert_Mint_ToIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidReceiver.selector, address(0))); - harness.mint(address(0), TOKEN_ID, AMOUNT); - } - - function test_Mint() external { - vm.expectEmit(); - emit Transfer(address(this), address(0), alice, TOKEN_ID, AMOUNT); - - harness.mint(alice, TOKEN_ID, AMOUNT); - - assertEq(harness.balanceOf(alice, TOKEN_ID), AMOUNT); - } - - function test_ShouldRevert_Mint_BalanceOf_Overflows() external { - harness.mint(alice, TOKEN_ID, type(uint256).max); - vm.expectRevert(stdError.arithmeticError); - harness.mint(alice, TOKEN_ID, 1); - } - - function testFuzz_Mint(address caller, address to, uint256 id, uint256 amount) external { - vm.assume(to != address(0)); - - vm.expectEmit(); - emit Transfer(caller, address(0), to, id, amount); - - vm.prank(caller); - harness.mint(to, id, amount); - - assertEq(harness.balanceOf(to, id), amount); - } - - /** - * ============================================ - * Burn Tests - * ============================================ - */ - - function test_ShouldRevert_Burn_InsufficientBalance() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InsufficientBalance.selector, alice, 0, 1, TOKEN_ID)); - harness.burn(alice, TOKEN_ID, 1); - } - - function test_ShouldRevert_Burn_FromIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidSender.selector, address(0))); - harness.burn(address(0), TOKEN_ID, 1); - } - - function test_Burn() external { - harness.mint(alice, TOKEN_ID, AMOUNT); - - vm.expectEmit(); - emit Transfer(address(this), alice, address(0), TOKEN_ID, AMOUNT); - - harness.burn(alice, TOKEN_ID, AMOUNT); - - assertEq(harness.balanceOf(alice, TOKEN_ID), 0); - } - - /** - * @dev First mints tokens and then burns a fraction of them. - */ - function testFuzz_Burn(address caller, address from, uint256 id, uint256 amount, uint256 burnFrac) external { - vm.assume(from != address(0)); - amount = bound(amount, 1, type(uint256).max / 1e4); - burnFrac = bound(burnFrac, 1, 1e4); - uint256 burnAmount = (amount * burnFrac) / 1e4; - - harness.mint(from, id, amount); - - vm.expectEmit(); - emit Transfer(caller, from, address(0), id, burnAmount); - - vm.prank(caller); - harness.burn(from, id, burnAmount); - - assertEq(harness.balanceOf(from, id), amount - burnAmount); - } - - /** - * ============================================ - * Approve Tests - * ============================================ - */ - - function test_ShouldRevert_Approve_OwnerIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidApprover.selector, address(0))); - harness.approve(address(0), alice, TOKEN_ID, AMOUNT); - } - - function test_ShouldRevert_Approve_SpenderIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidSpender.selector, address(0))); - harness.approve(alice, address(0), TOKEN_ID, AMOUNT); - } - - function test_Approve() external { - vm.expectEmit(); - emit Approval(alice, address(this), TOKEN_ID, AMOUNT); - - harness.approve(alice, address(this), TOKEN_ID, AMOUNT); - - assertEq(harness.allowance(alice, address(this), TOKEN_ID), AMOUNT); - } - - function testFuzz_Approve(address owner, address spender, uint256 id, uint256 amount) external { - vm.assume(owner != address(0)); - vm.assume(spender != address(0)); - - vm.expectEmit(); - emit Approval(owner, spender, id, amount); - - harness.approve(owner, spender, id, amount); - - assertEq(harness.allowance(owner, spender, id), amount); - } - - /** - * ============================================ - * Set Operator Tests - * ============================================ - */ - - function test_ShouldRevert_SetOperator_OwnerIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidApprover.selector, address(0))); - harness.setOperator(address(0), alice, true); - } - - function test_ShouldRevert_SetOperator_SpenderIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidSpender.selector, address(0))); - harness.setOperator(alice, address(0), true); - } - - function test_SetOperator_IsApproved() external { - vm.expectEmit(); - emit OperatorSet(alice, address(this), true); - - harness.setOperator(alice, address(this), true); - assertEq(harness.isOperator(alice, address(this)), true); - } - - function test_SetOperator_RevokeOperator() external { - harness.setOperator(alice, address(this), true); - - vm.expectEmit(); - emit OperatorSet(alice, address(this), false); - - harness.setOperator(alice, address(this), false); - - assertEq(harness.isOperator(alice, address(this)), false); - } - - function testFuzz_SetOperator(address owner, address spender, bool approved) external { - vm.assume(owner != address(0)); - vm.assume(spender != address(0)); - - vm.expectEmit(); - emit OperatorSet(owner, spender, approved); - - harness.setOperator(owner, spender, approved); - - assertEq(harness.isOperator(owner, spender), approved); - } - - /** - * ============================================ - * Transfer Tests - * ============================================ - */ - - function testFuzz_Transfer(address from, address to, uint256 id, uint256 amount) external { - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - - harness.mint(from, id, amount); - - vm.expectEmit(); - emit Transfer(from, from, to, id, amount); - - harness.transfer(from, from, to, id, amount); - - assertEq(harness.balanceOf(from, id), 0); - assertEq(harness.balanceOf(to, id), amount); - } - - function testFuzz_Transfer_IsOperator(address by, address from, address to, uint256 id, uint256 amount) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - harness.mint(from, id, amount); - harness.setOperator(from, by, true); - - vm.expectEmit(); - emit Transfer(by, from, to, id, amount); - - harness.transfer(by, from, to, id, amount); - - assertEq(harness.balanceOf(from, id), 0); - assertEq(harness.balanceOf(to, id), amount); - } - - function testFuzz_Transfer_NonOperator_MaxAllowance( - address by, - address from, - address to, - uint256 id, - uint256 amount - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - harness.mint(from, id, amount); - harness.approve(from, by, id, type(uint256).max); - - vm.expectEmit(); - emit Transfer(by, from, to, id, amount); - - harness.transfer(by, from, to, id, amount); - - assertEq(harness.balanceOf(from, id), 0); - assertEq(harness.balanceOf(to, id), amount); - assertEq(harness.allowance(from, by, id), type(uint256).max); - } - - function testFuzz_Transfer_NonOperator_AllowanceLtMax( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - spend = bound(spend, 1, amount); - - harness.mint(from, id, amount); - harness.approve(from, by, id, amount); - - vm.expectEmit(); - emit Transfer(by, from, to, id, spend); - - harness.transfer(by, from, to, id, spend); - - assertEq(harness.balanceOf(from, id), amount - spend); - assertEq(harness.balanceOf(to, id), spend); - assertEq(harness.allowance(from, by, id), amount - spend); - } - - function test_ShouldRevert_Transfer_InvalidSender() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidSender.selector, address(0))); - - harness.transfer(address(0), address(0), alice, TOKEN_ID, AMOUNT); - } - - function testFuzz_ShouldRevert_NonOperator_InsufficientBalance( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 balance - ) external { - vm.assume(from != by); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - balance = bound(balance, 1, type(uint256).max - 1); - amount = bound(amount, balance + 1, type(uint256).max); - - harness.mint(from, id, balance); - harness.approve(from, by, id, amount); - - vm.expectRevert( - abi.encodeWithSelector(ERC6909Mod.ERC6909InsufficientBalance.selector, from, balance, amount, id) - ); - harness.transfer(by, from, to, id, amount); - } - - function testFuzz_ShouldRevert_Operator_InsufficientBalance( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 balance - ) external { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - balance = bound(balance, 1, type(uint256).max - 1); - amount = bound(amount, balance + 1, type(uint256).max); - - harness.mint(from, id, balance); - harness.setOperator(from, by, true); - - vm.expectRevert( - abi.encodeWithSelector(ERC6909Mod.ERC6909InsufficientBalance.selector, from, balance, amount, id) - ); - harness.transfer(by, from, to, id, amount); - } - - function testFuzz_ShouldRevert_NonOperator_AllowanceUnderflow( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != address(0)); - vm.assume(by != address(0)); - vm.assume(to != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - vm.assume(spend > amount); - - harness.mint(from, id, amount); - harness.approve(from, by, id, amount); - - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InsufficientAllowance.selector, by, amount, spend, id)); - harness.transfer(by, from, to, id, spend); - } - - function testFuzz_ShouldRevert_NonOperator_NoAllowance( - address by, - address from, - address to, - uint256 id, - uint256 amount - ) external { - vm.assume(from != by); - vm.assume(from != address(0)); - vm.assume(by != address(0)); - vm.assume(to != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - harness.mint(from, id, amount); - - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InsufficientAllowance.selector, by, 0, amount, id)); - harness.transfer(by, from, to, id, amount); - } - - function testFuzz_Transfer_SelfTransfer_NonOperator_FiniteAllowance( - address by, - address from, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - spend = bound(spend, 1, amount); - - harness.mint(from, id, amount); - harness.approve(from, by, id, amount); - - vm.expectEmit(); - emit Transfer(by, from, from, id, spend); - - harness.transfer(by, from, from, id, spend); - - assertEq(harness.balanceOf(from, id), amount); - assertEq(harness.allowance(from, by, id), amount - spend); - } - - function testFuzz_Transfer_ToZeroAddress_NonOperator_MaxAllowance( - address by, - address from, - uint256 id, - uint256 amount - ) external { - vm.assume(from != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - harness.mint(from, id, amount); - harness.approve(from, by, id, type(uint256).max); - - vm.expectRevert(abi.encodeWithSelector(ERC6909Mod.ERC6909InvalidReceiver.selector, address(0))); - harness.transfer(by, from, address(0), id, amount); - } - - function testFuzz_Transfer_ZeroAmount_NonOperator_FiniteAllowance( - address by, - address from, - address to, - uint256 id, - uint256 allowance - ) external { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - vm.assume(from != to); - - allowance = bound(allowance, 1, type(uint256).max - 1); - - harness.mint(from, id, allowance); - harness.approve(from, by, id, allowance); - - vm.expectEmit(); - emit Transfer(by, from, to, id, 0); - - harness.transfer(by, from, to, id, 0); - - assertEq(harness.balanceOf(from, id), allowance); - assertEq(harness.balanceOf(to, id), 0); - assertEq(harness.allowance(from, by, id), allowance); - } -} diff --git a/test/token/ERC6909/ERC6909/ERC6909Facet.t.sol b/test/token/ERC6909/ERC6909/ERC6909Facet.t.sol deleted file mode 100644 index 8733d47c..00000000 --- a/test/token/ERC6909/ERC6909/ERC6909Facet.t.sol +++ /dev/null @@ -1,492 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {stdError} from "forge-std/StdError.sol"; -import {ERC6909FacetHarness} from "./harnesses/ERC6909FacetHarness.sol"; -import {ERC6909Facet} from "../../../../src/token/ERC6909/ERC6909/ERC6909Facet.sol"; - -contract ERC6909FacetTest is Test { - ERC6909FacetHarness internal facet; - - address internal alice; - - uint256 internal constant TOKEN_ID = 72; - uint256 internal constant AMOUNT = 1e24; - - function setUp() public { - alice = makeAddr("alice"); - - facet = new ERC6909FacetHarness(); - } - - /** - * ============================================ - * Mint Tests - * ============================================ - */ - - function test_Mint() external { - vm.expectEmit(); - emit ERC6909Facet.Transfer(address(this), address(0), alice, TOKEN_ID, AMOUNT); - - facet.mint(alice, TOKEN_ID, AMOUNT); - - assertEq(facet.balanceOf(alice, TOKEN_ID), AMOUNT); - } - - function test_ShouldRevert_Mint_BalanceOf_Overflows() external { - facet.mint(alice, TOKEN_ID, type(uint256).max); - - vm.expectRevert(stdError.arithmeticError); - facet.mint(alice, TOKEN_ID, 1); - } - - function testFuzz_Mint(address caller, address to, uint256 id, uint256 amount) external { - vm.assume(to != address(0)); - vm.expectEmit(); - emit ERC6909Facet.Transfer(caller, address(0), to, id, amount); - - vm.prank(caller); - facet.mint(to, id, amount); - - assertEq(facet.balanceOf(to, id), amount); - } - - /** - * ============================================ - * Approve Tests - * ============================================ - */ - - function test_ShouldRevert_Approve_SpenderIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InvalidSpender.selector, address(0))); - facet.approve(address(0), TOKEN_ID, AMOUNT); - } - - function test_Approve() external { - vm.prank(alice); - - vm.expectEmit(); - emit ERC6909Facet.Approval(alice, address(this), TOKEN_ID, AMOUNT); - - facet.approve(address(this), TOKEN_ID, AMOUNT); - - assertEq(facet.allowance(alice, address(this), TOKEN_ID), AMOUNT); - } - - function testFuzz_Approve(address owner, address spender, uint256 id, uint256 amount) external { - vm.assume(spender != address(0)); - - vm.expectEmit(); - emit ERC6909Facet.Approval(owner, spender, id, amount); - - vm.prank(owner); - facet.approve(spender, id, amount); - - assertEq(facet.allowance(owner, spender, id), amount); - } - - /** - * ============================================ - * Set Operator Tests - * ============================================ - */ - - function test_ShouldRevert_SetOperator_SpenderIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InvalidSpender.selector, address(0))); - facet.setOperator(address(0), true); - } - - function test_SetOperator_IsApproved() external { - vm.prank(alice); - - vm.expectEmit(); - emit ERC6909Facet.OperatorSet(alice, address(this), true); - - facet.setOperator(address(this), true); - - assertEq(facet.isOperator(alice, address(this)), true); - } - - function test_SetOperator_RevokeOperator() external { - vm.prank(alice); - facet.setOperator(address(this), true); - - vm.prank(alice); - vm.expectEmit(); - emit ERC6909Facet.OperatorSet(alice, address(this), false); - - facet.setOperator(address(this), false); - - assertEq(facet.isOperator(alice, address(this)), false); - } - - function testFuzz_SetOperator(address owner, address spender, bool approved) external { - vm.assume(spender != address(0)); - - vm.expectEmit(); - emit ERC6909Facet.OperatorSet(owner, spender, approved); - - vm.prank(owner); - facet.setOperator(spender, approved); - - assertEq(facet.isOperator(owner, spender), approved); - } - - /** - * ============================================ - * Transfer Tests - * ============================================ - */ - - function test_ShouldRevert_Transfer_ReceiverIsZero() external { - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InvalidReceiver.selector, address(0))); - facet.transfer(address(0), TOKEN_ID, AMOUNT); - } - - function test_ShouldRevert_Transfer_InsufficientBalance() external { - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC6909Facet.ERC6909InsufficientBalance.selector, alice, 0, AMOUNT, TOKEN_ID) - ); - facet.transfer(alice, TOKEN_ID, AMOUNT); - } - - function test_ShouldRevert_Transfer_ReceiverBalanceOverflows() external { - facet.mint(alice, TOKEN_ID, 1); - facet.mint(address(this), TOKEN_ID, type(uint256).max); - vm.prank(alice); - vm.expectRevert(stdError.arithmeticError); - facet.transfer(address(this), TOKEN_ID, 1); - } - - function test_Transfer() external { - facet.mint(alice, TOKEN_ID, AMOUNT); - vm.prank(alice); - vm.expectEmit(); - emit ERC6909Facet.Transfer(alice, alice, address(this), TOKEN_ID, AMOUNT); - bool success = facet.transfer(address(this), TOKEN_ID, AMOUNT); - assertTrue(success); - assertEq(facet.balanceOf(alice, TOKEN_ID), 0); - assertEq(facet.balanceOf(address(this), TOKEN_ID), AMOUNT); - } - - function testFuzz_Transfer(address caller, address receiver, uint256 id, uint256 amount) external { - vm.assume(receiver != caller); - vm.assume(receiver != address(0)); - vm.assume(caller != address(0)); - - facet.mint(caller, id, amount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(caller, caller, receiver, id, amount); - - vm.prank(caller); - bool success = facet.transfer(receiver, id, amount); - - assertTrue(success); - assertEq(facet.balanceOf(caller, id), 0); - assertEq(facet.balanceOf(receiver, id), amount); - } - - function testFuzz_Transfer_Self(address caller, uint256 id, uint256 amount) external { - vm.assume(caller != address(0)); - - facet.mint(caller, id, amount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(caller, caller, caller, id, amount); - - vm.prank(caller); - bool success = facet.transfer(caller, id, amount); - - assertTrue(success); - assertEq(facet.balanceOf(caller, id), amount); - } - - function testFuzz_ShouldRevert_Transfer_ToZeroAddress(address caller, uint256 id, uint256 amount) external { - vm.assume(caller != address(0)); - - facet.mint(caller, id, amount); - - vm.prank(caller); - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InvalidReceiver.selector, address(0))); - facet.transfer(address(0), id, amount); - } - - function testFuzz_Transfer_ZeroAmount(address caller, address receiver, uint256 id, uint256 balance) external { - vm.assume(receiver != caller); - vm.assume(receiver != address(0)); - vm.assume(caller != address(0)); - - balance = bound(balance, 1, type(uint256).max); - - facet.mint(caller, id, balance); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(caller, caller, receiver, id, 0); - - vm.prank(caller); - bool success = facet.transfer(receiver, id, 0); - - assertTrue(success); - assertEq(facet.balanceOf(caller, id), balance); - assertEq(facet.balanceOf(receiver, id), 0); - } - - /** - * ============================================ - * Transfer From Tests - * ============================================ - */ - - function testFuzz_ShouldRevert_TransferFrom_InsufficientBalance( - address by, - address from, - address to, - uint256 id, - uint256 amount - ) external { - vm.assume(amount > 0); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - vm.prank(from); - facet.approve(by, id, type(uint256).max); - - vm.prank(by); - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InsufficientBalance.selector, from, 0, amount, id)); - facet.transferFrom(from, to, id, amount); - } - - function testFuzz_TransferFrom_BySender(address from, address to, uint256 id, uint256 amount) external { - vm.assume(to != from); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - facet.mint(from, id, amount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(from, from, to, id, amount); - - vm.prank(from); - bool success = facet.transferFrom(from, to, id, amount); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), 0); - assertEq(facet.balanceOf(to, id), amount); - } - - function testFuzz_TransferFrom_IsOperator(address by, address from, address to, uint256 id, uint256 amount) - external - { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.setOperator(by, true); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(by, from, to, id, amount); - - vm.prank(by); - bool success = facet.transferFrom(from, to, id, amount); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), 0); - assertEq(facet.balanceOf(to, id), amount); - assertEq(facet.allowance(from, by, id), 0); - } - - function testFuzz_TransferFrom_NonOperator_MaxAllowance( - address by, - address from, - address to, - uint256 id, - uint256 amount - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.approve(by, id, type(uint256).max); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(by, from, to, id, amount); - - vm.prank(by); - bool success = facet.transferFrom(from, to, id, amount); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), 0); - assertEq(facet.balanceOf(to, id), amount); - assertEq(facet.allowance(from, by, id), type(uint256).max); - } - - function testFuzz_TransferFrom_NonOperator_AllowanceLtMax( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - spend = bound(spend, 1, amount); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.approve(by, id, amount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(by, from, to, id, spend); - - vm.prank(by); - bool success = facet.transferFrom(from, to, id, spend); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), amount - spend); - assertEq(facet.balanceOf(to, id), spend); - assertEq(facet.allowance(from, by, id), amount - spend); - } - - function testFuzz_ShouldRevert_TransferFrom_NonOperator_AllowanceUnderflow( - address by, - address from, - address to, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(by != address(0)); - vm.assume(to != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - vm.assume(spend > amount); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.approve(by, id, amount); - - vm.prank(by); - vm.expectRevert( - abi.encodeWithSelector(ERC6909Facet.ERC6909InsufficientAllowance.selector, by, amount, spend, id) - ); - facet.transferFrom(from, to, id, spend); - } - - function testFuzz_TransferFrom_SelfTransfer_NonOperator_FiniteAllowance( - address by, - address from, - uint256 id, - uint256 amount, - uint256 spend - ) external { - vm.assume(from != by); - vm.assume(from != address(0)); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max - 1); - spend = bound(spend, 1, amount); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.approve(by, id, amount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(by, from, from, id, spend); - - vm.prank(by); - bool success = facet.transferFrom(from, from, id, spend); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), amount); - assertEq(facet.allowance(from, by, id), amount - spend); - } - - function testFuzz_TransferFrom_ToZeroAddress_NonOperator_MaxAllowance( - address by, - address from, - uint256 id, - uint256 amount - ) external { - vm.assume(from != address(0)); - vm.assume(from != by); - vm.assume(by != address(0)); - - amount = bound(amount, 1, type(uint256).max); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.approve(by, id, type(uint256).max); - - vm.prank(by); - vm.expectRevert(abi.encodeWithSelector(ERC6909Facet.ERC6909InvalidReceiver.selector, address(0))); - facet.transferFrom(from, address(0), id, amount); - } - - function testFuzz_TransferFrom_ZeroAmount_NonOperator_FiniteAllowance( - address by, - address from, - address to, - uint256 id, - uint256 allowanceAmount - ) external { - vm.assume(from != by); - vm.assume(from != to); - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(by != address(0)); - - allowanceAmount = bound(allowanceAmount, 1, type(uint256).max - 1); - - facet.mint(from, id, allowanceAmount); - - vm.prank(from); - facet.approve(by, id, allowanceAmount); - - vm.expectEmit(); - emit ERC6909Facet.Transfer(by, from, to, id, 0); - - vm.prank(by); - bool success = facet.transferFrom(from, to, id, 0); - - assertTrue(success); - assertEq(facet.balanceOf(from, id), allowanceAmount); - assertEq(facet.balanceOf(to, id), 0); - assertEq(facet.allowance(from, by, id), allowanceAmount); - } -} diff --git a/test/token/ERC6909/ERC6909/harnesses/ERC6909FacetHarness.sol b/test/token/ERC6909/ERC6909/harnesses/ERC6909FacetHarness.sol deleted file mode 100644 index 1bcf6039..00000000 --- a/test/token/ERC6909/ERC6909/harnesses/ERC6909FacetHarness.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC6909Facet} from "../../../../../src/token/ERC6909/ERC6909/ERC6909Facet.sol"; - -/** - * @notice Test harness for ERC6909Facet that adds initialization and minting for testing - */ -error ERC6909InvalidReceiver(address _receiver); - -contract ERC6909FacetHarness is ERC6909Facet { - function mint(address _to, uint256 _id, uint256 _amount) external { - if (_to == address(0)) { - revert ERC6909InvalidReceiver(address(0)); - } - ERC6909Storage storage s = getStorage(); - - s.balanceOf[_to][_id] += _amount; - - emit Transfer(msg.sender, address(0), _to, _id, _amount); - } -} diff --git a/test/token/ERC6909/ERC6909/harnesses/ERC6909Harness.sol b/test/token/ERC6909/ERC6909/harnesses/ERC6909Harness.sol deleted file mode 100644 index f71aad9c..00000000 --- a/test/token/ERC6909/ERC6909/harnesses/ERC6909Harness.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../../src/token/ERC6909/ERC6909/ERC6909Mod.sol" as ERC6909Mod; - -/** - * @notice Test harness that exposes LibERC6909's internal functions as external - * @dev Required for testing since LibERC6909 only has internal functions - */ -contract ERC6909Harness { - function mint(address _to, uint256 _id, uint256 _amount) external { - ERC6909Mod.mint(_to, _id, _amount); - } - - function burn(address _from, uint256 _id, uint256 _amount) external { - ERC6909Mod.burn(_from, _id, _amount); - } - - function transfer(address _by, address _from, address _to, uint256 _id, uint256 _amount) external { - ERC6909Mod.transfer(_by, _from, _to, _id, _amount); - } - - function approve(address _owner, address _spender, uint256 _id, uint256 _amount) external { - ERC6909Mod.approve(_owner, _spender, _id, _amount); - } - - function setOperator(address _owner, address _spender, bool _approved) external { - ERC6909Mod.setOperator(_owner, _spender, _approved); - } - - function balanceOf(address _owner, uint256 _id) external view returns (uint256) { - return ERC6909Mod.getStorage().balanceOf[_owner][_id]; - } - - function allowance(address _owner, address _spender, uint256 _id) external view returns (uint256) { - return ERC6909Mod.getStorage().allowance[_owner][_spender][_id]; - } - - function isOperator(address _owner, address _spender) external view returns (bool) { - return ERC6909Mod.getStorage().isOperator[_owner][_spender]; - } -} diff --git a/test/trees/ERC6909.tree b/test/trees/ERC6909.tree new file mode 100644 index 00000000..27c8e55a --- /dev/null +++ b/test/trees/ERC6909.tree @@ -0,0 +1,94 @@ +Data +├── BalanceOf +│ └── when owner and id are queried +│ └── it should return stored balance (including zero for untouched ids) +├── Allowance +│ └── when owner, spender, and id are queried +│ └── it should return stored allowance (including zero) +└── IsOperator + ├── when operator has not been set + │ └── it should return false + └── when operator has been set via setOperator + └── it should return true + +Approve +└── Approve + ├── when spender is the zero address + │ └── it should revert with ERC6909InvalidSpender + └── when spender is not the zero address + ├── it should set allowance[owner][spender][id] to amount + ├── it should emit Approval(owner, spender, id, amount) + └── when amount is zero + └── it should update storage and emit Approval + +Operator +└── SetOperator + ├── when spender is the zero address + │ └── it should revert with ERC6909InvalidSpender + └── when spender is not the zero address + ├── it should set isOperator[owner][spender] to approved + └── it should emit OperatorSet(owner, spender, approved) + +Transfer +├── Transfer +│ ├── when receiver is the zero address +│ │ └── it should revert with ERC6909InvalidReceiver +│ ├── when balance is less than amount +│ │ └── it should revert with ERC6909InsufficientBalance +│ └── when preconditions hold +│ ├── it should decrement caller balance and increment receiver balance by amount +│ ├── it should emit Transfer(caller, sender, receiver, id, amount) with caller == sender +│ └── when amount is zero +│ └── it should succeed without reverting +└── TransferFrom + ├── when sender is the zero address + │ └── it should revert with ERC6909InvalidSender + ├── when receiver is the zero address + │ └── it should revert with ERC6909InvalidReceiver + ├── when msg.sender is neither sender nor approved operator for sender + │ ├── when per-id allowance is less than amount and not type(uint256).max + │ │ └── it should revert with ERC6909InsufficientAllowance + │ └── when allowance is type(uint256).max + │ └── it should not decrease allowance + ├── when msg.sender is approved operator for sender + │ └── it should not require per-id allowance + ├── when msg.sender equals sender + │ └── it should not require per-id allowance + └── when preconditions hold + ├── it should decrement sender balance and increment receiver balance by amount + ├── when allowance is finite and sufficient + │ └── it should decrease allowance by amount + └── it should emit Transfer(caller, sender, receiver, id, amount) + +Burn +├── Burn +│ ├── when balance is less than amount +│ │ └── it should revert with ERC6909InsufficientBalance +│ └── when balance is sufficient +│ ├── it should decrement caller balance by amount +│ └── it should emit Transfer(caller, caller, address(0), id, amount) +└── BurnFrom + ├── when from is the zero address + │ └── it should revert with ERC6909InvalidSender + ├── when caller is not from + │ ├── when allowance is less than amount and not type(uint256).max + │ │ └── it should revert with ERC6909InsufficientAllowance + │ └── when allowance is type(uint256).max + │ └── it should not decrease allowance + └── when preconditions hold + ├── it should decrement from balance by amount + ├── when allowance is finite + │ └── it should decrease allowance by amount + └── it should emit Transfer(caller, from, address(0), id, amount) + +Mint (Mod only; privileged entry) +└── Mint + ├── when account is the zero address + │ └── it should revert with ERC6909InvalidReceiver + └── when account is not the zero address + ├── it should increase balanceOf[account][id] by value + └── it should emit Transfer(caller, address(0), account, id, value) + +ExportSelectors +└── for each ERC6909 facet (Data, Transfer, Approve, Operator, Burn) + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order diff --git a/test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol b/test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol new file mode 100644 index 00000000..fff83a77 --- /dev/null +++ b/test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909ApproveFacet} from "src/token/ERC6909/Approve/ERC6909ApproveFacet.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909ApproveFacet_Base_Test is ERC6909_Test_Base { + ERC6909ApproveFacet internal facet; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + facet = new ERC6909ApproveFacet(); + vm.label(address(facet), "ERC6909ApproveFacet"); + } +} diff --git a/test/unit/token/ERC6909/Approve/ERC6909ApproveModBase.t.sol b/test/unit/token/ERC6909/Approve/ERC6909ApproveModBase.t.sol new file mode 100644 index 00000000..5007ee87 --- /dev/null +++ b/test/unit/token/ERC6909/Approve/ERC6909ApproveModBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909ApproveModHarness} from "test/utils/harnesses/token/ERC6909/ERC6909ApproveModHarness.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909ApproveMod_Base_Test is ERC6909_Test_Base { + ERC6909ApproveModHarness internal harness; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + harness = new ERC6909ApproveModHarness(); + vm.label(address(harness), "ERC6909ApproveModHarness"); + } +} diff --git a/test/unit/token/ERC6909/Approve/facet/fuzz/approve.t.sol b/test/unit/token/ERC6909/Approve/facet/fuzz/approve.t.sol new file mode 100644 index 00000000..3aa312ba --- /dev/null +++ b/test/unit/token/ERC6909/Approve/facet/fuzz/approve.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909ApproveFacet_Base_Test} from "test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909ApproveFacet} from "src/token/ERC6909/Approve/ERC6909ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Approve_ERC6909ApproveFacet_Fuzz_Test is ERC6909ApproveFacet_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_SpenderZero_Approve(uint256 id, uint256 amount) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909ApproveFacet.ERC6909InvalidSpender.selector, address(0))); + facet.approve(address(0), id, amount); + } + + function testFuzz_ShouldSetAllowanceAndEmit_Approve(address spender, uint256 id, uint256 amount) external { + vm.assume(spender != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, true); + emit ERC6909ApproveFacet.Approval(users.alice, spender, id, amount); + facet.approve(spender, id, amount); + assertEq(address(facet).allowance(users.alice, spender, id), amount, "allowance"); + } + + function test_ShouldSetAllowanceAndEmit_Approve_WhenAmountZero() external { + uint256 id = 42; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, true); + emit ERC6909ApproveFacet.Approval(users.alice, users.bob, id, 0); + facet.approve(users.bob, id, 0); + assertEq(address(facet).allowance(users.alice, users.bob, id), 0, "allowance"); + } +} diff --git a/test/unit/token/ERC6909/Approve/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC6909/Approve/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..d1433ecd --- /dev/null +++ b/test/unit/token/ERC6909/Approve/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909ApproveFacet_Base_Test} from "test/unit/token/ERC6909/Approve/ERC6909ApproveFacetBase.t.sol"; +import {ERC6909ApproveFacet} from "src/token/ERC6909/Approve/ERC6909ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract ExportSelectors_ERC6909ApproveFacet_Unit_Test is ERC6909ApproveFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC6909ApproveFacet.approve.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC6909/Approve/mod/fuzz/approve.t.sol b/test/unit/token/ERC6909/Approve/mod/fuzz/approve.t.sol new file mode 100644 index 00000000..93cbc725 --- /dev/null +++ b/test/unit/token/ERC6909/Approve/mod/fuzz/approve.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909ApproveMod_Base_Test} from "test/unit/token/ERC6909/Approve/ERC6909ApproveModBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Approve_ERC6909ApproveMod_Fuzz_Test is ERC6909ApproveMod_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_ShouldSetAllowance_Approve(address spender, uint256 id, uint256 amount) external { + vm.assume(spender != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + harness.approve(spender, id, amount); + assertEq(address(harness).allowance(users.alice, spender, id), amount, "allowance"); + } +} diff --git a/test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol b/test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol new file mode 100644 index 00000000..b3dbfdb7 --- /dev/null +++ b/test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909BurnFacet} from "src/token/ERC6909/Burn/ERC6909BurnFacet.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909BurnFacet_Base_Test is ERC6909_Test_Base { + ERC6909BurnFacet internal facet; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + facet = new ERC6909BurnFacet(); + vm.label(address(facet), "ERC6909BurnFacet"); + } +} diff --git a/test/unit/token/ERC6909/Burn/ERC6909BurnModBase.t.sol b/test/unit/token/ERC6909/Burn/ERC6909BurnModBase.t.sol new file mode 100644 index 00000000..884bc2bf --- /dev/null +++ b/test/unit/token/ERC6909/Burn/ERC6909BurnModBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909BurnModHarness} from "test/utils/harnesses/token/ERC6909/ERC6909BurnModHarness.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909BurnMod_Base_Test is ERC6909_Test_Base { + ERC6909BurnModHarness internal harness; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + harness = new ERC6909BurnModHarness(); + vm.label(address(harness), "ERC6909BurnModHarness"); + } +} diff --git a/test/unit/token/ERC6909/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC6909/Burn/facet/fuzz/burn.t.sol new file mode 100644 index 00000000..a368e925 --- /dev/null +++ b/test/unit/token/ERC6909/Burn/facet/fuzz/burn.t.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909BurnFacet_Base_Test} from "test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909BurnFacet} from "src/token/ERC6909/Burn/ERC6909BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Burn_ERC6909BurnFacet_Fuzz_Test is ERC6909BurnFacet_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_InsufficientBalance_Burn(uint256 id, uint256 balance, uint256 amount) external { + vm.assume(balance < type(uint256).max); + amount = bound(amount, balance + 1, type(uint256).max); + seedBalance(address(facet), users.alice, id, balance); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909BurnFacet.ERC6909InsufficientBalance.selector, users.alice, balance, amount, id + ) + ); + facet.burn(id, amount); + } + + function testFuzz_ShouldDecrementAndEmit_Burn(uint256 id, uint256 amount) external { + vm.assume(amount != 0); + vm.assume(amount < type(uint256).max); + seedBalance(address(facet), users.alice, id, amount); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, true); + emit ERC6909BurnFacet.Transfer(users.alice, users.alice, address(0), id, amount); + facet.burn(id, amount); + assertEq(address(facet).balanceOf(users.alice, id), 0, "balance"); + } + + function testFuzz_RevertWhen_FromZero_BurnFrom(uint256 id, uint256 amount) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909BurnFacet.ERC6909InvalidSender.selector, address(0))); + facet.burnFrom(address(0), id, amount); + } + + function testFuzz_RevertWhen_InsufficientAllowance_BurnFrom( + address from, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 burnAmt + ) external { + vm.assume(from != address(0)); + vm.assume(from != users.bob); + balance = bound(balance, 1, type(uint256).max - 1); + allowanceAmt = bound(allowanceAmt, 0, balance - 1); + burnAmt = bound(burnAmt, allowanceAmt + 1, balance); + + seedBalance(address(facet), from, id, balance); + seedAllowance(address(facet), from, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909BurnFacet.ERC6909InsufficientAllowance.selector, users.bob, allowanceAmt, burnAmt, id + ) + ); + facet.burnFrom(from, id, burnAmt); + } + + function test_ShouldNotDecreaseAllowance_BurnFrom_WhenAllowanceMax() external { + uint256 id = 2; + uint256 amt = 30; + seedBalance(address(facet), users.alice, id, amt); + seedAllowance(address(facet), users.alice, users.bob, id, type(uint256).max); + vm.stopPrank(); + vm.prank(users.bob); + facet.burnFrom(users.alice, id, amt); + assertEq(address(facet).allowance(users.alice, users.bob, id), type(uint256).max, "allowance"); + } + + function testFuzz_ShouldDecreaseAllowanceAndBurn_BurnFrom( + address from, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 burnAmt + ) external { + vm.assume(from != address(0)); + vm.assume(from != users.bob); + balance = bound(balance, 2, type(uint256).max / 2); + burnAmt = bound(burnAmt, 1, balance); + allowanceAmt = bound(allowanceAmt, burnAmt, balance); + + seedBalance(address(facet), from, id, balance); + seedAllowance(address(facet), from, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectEmit(true, true, true, true); + emit ERC6909BurnFacet.Transfer(users.bob, from, address(0), id, burnAmt); + facet.burnFrom(from, id, burnAmt); + + assertEq(address(facet).balanceOf(from, id), balance - burnAmt, "balance"); + assertEq(address(facet).allowance(from, users.bob, id), allowanceAmt - burnAmt, "allowance"); + } +} diff --git a/test/unit/token/ERC6909/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC6909/Burn/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..bd9fd645 --- /dev/null +++ b/test/unit/token/ERC6909/Burn/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909BurnFacet_Base_Test} from "test/unit/token/ERC6909/Burn/ERC6909BurnFacetBase.t.sol"; +import {ERC6909BurnFacet} from "src/token/ERC6909/Burn/ERC6909BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract ExportSelectors_ERC6909BurnFacet_Unit_Test is ERC6909BurnFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC6909BurnFacet.burn.selector, ERC6909BurnFacet.burnFrom.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC6909/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC6909/Burn/mod/fuzz/burn.t.sol new file mode 100644 index 00000000..0e66a660 --- /dev/null +++ b/test/unit/token/ERC6909/Burn/mod/fuzz/burn.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909BurnMod_Base_Test} from "test/unit/token/ERC6909/Burn/ERC6909BurnModBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909BurnFacet} from "src/token/ERC6909/Burn/ERC6909BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Burn_ERC6909BurnMod_Fuzz_Test is ERC6909BurnMod_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_ShouldDecrement_Burn(uint256 id, uint256 amount) external { + vm.assume(amount != 0); + vm.assume(amount < type(uint256).max); + seedBalance(address(harness), users.alice, id, amount); + vm.stopPrank(); + vm.prank(users.alice); + harness.burn(id, amount); + assertEq(address(harness).balanceOf(users.alice, id), 0, "balance"); + } + + function testFuzz_RevertWhen_InsufficientAllowance_BurnFrom( + address from, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 burnAmt + ) external { + vm.assume(from != address(0)); + vm.assume(from != users.bob); + balance = bound(balance, 1, type(uint256).max - 1); + allowanceAmt = bound(allowanceAmt, 0, balance - 1); + burnAmt = bound(burnAmt, allowanceAmt + 1, balance); + + seedBalance(address(harness), from, id, balance); + seedAllowance(address(harness), from, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909BurnFacet.ERC6909InsufficientAllowance.selector, users.bob, allowanceAmt, burnAmt, id + ) + ); + harness.burnFrom(from, id, burnAmt); + } +} diff --git a/test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol b/test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol new file mode 100644 index 00000000..6c7ddc42 --- /dev/null +++ b/test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909DataFacet} from "src/token/ERC6909/Data/ERC6909DataFacet.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909DataFacet_Base_Test is ERC6909_Test_Base { + ERC6909DataFacet internal facet; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + facet = new ERC6909DataFacet(); + vm.label(address(facet), "ERC6909DataFacet"); + } +} diff --git a/test/unit/token/ERC6909/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC6909/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..aa529617 --- /dev/null +++ b/test/unit/token/ERC6909/Data/facet/fuzz/data.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909DataFacet_Base_Test} from "test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Data_ERC6909DataFacet_Fuzz_Test is ERC6909DataFacet_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_ShouldReturnBalance_BalanceOf_WhenOwnerAndIdQueried(address owner, uint256 id, uint256 value) + external + { + vm.assume(value != type(uint256).max); + seedBalance(address(facet), owner, id, value); + assertEq(facet.balanceOf(owner, id), value, "balanceOf"); + } + + function testFuzz_ShouldReturnAllowance_Allowance_WhenQueried( + address owner, + address spender, + uint256 id, + uint256 value + ) external { + vm.assume(value != type(uint256).max); + seedAllowance(address(facet), owner, spender, id, value); + assertEq(facet.allowance(owner, spender, id), value, "allowance"); + } + + function testFuzz_ShouldReturnFalse_IsOperator_WhenNotSet(address owner, address spender) external view { + assertEq(facet.isOperator(owner, spender), false, "isOperator"); + } + + function testFuzz_ShouldReturnTrue_IsOperator_WhenSet(address owner, address spender) external { + vm.assume(owner != address(0)); + vm.assume(spender != address(0)); + seedIsOperator(address(facet), owner, spender, true); + assertEq(facet.isOperator(owner, spender), true, "isOperator"); + } +} diff --git a/test/unit/token/ERC6909/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC6909/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..4eedc90d --- /dev/null +++ b/test/unit/token/ERC6909/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909DataFacet_Base_Test} from "test/unit/token/ERC6909/Data/ERC6909DataFacetBase.t.sol"; +import {ERC6909DataFacet} from "src/token/ERC6909/Data/ERC6909DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract ExportSelectors_ERC6909DataFacet_Unit_Test is ERC6909DataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC6909DataFacet.balanceOf.selector, + ERC6909DataFacet.allowance.selector, + ERC6909DataFacet.isOperator.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC6909/ERC6909TestBase.sol b/test/unit/token/ERC6909/ERC6909TestBase.sol new file mode 100644 index 00000000..5deabcd4 --- /dev/null +++ b/test/unit/token/ERC6909/ERC6909TestBase.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; + +/** + * @notice Shared ERC-6909 test setup: storage helpers for facet/mod harness contracts. + * @dev Mirrors `ERC1155*Base` seed patterns so `using ERC6909StorageUtils for address` is exercised. + */ +abstract contract ERC6909_Test_Base is Base_Test { + using ERC6909StorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + } + + function seedBalance(address target, address owner, uint256 id, uint256 value) internal { + target.setBalanceOf(owner, id, value); + } + + function seedAllowance(address target, address owner, address spender, uint256 id, uint256 value) internal { + target.setAllowance(owner, spender, id, value); + } + + function seedIsOperator(address target, address owner, address spender, bool value) internal { + target.setIsOperator(owner, spender, value); + } +} diff --git a/test/unit/token/ERC6909/Mint/ERC6909MintModBase.t.sol b/test/unit/token/ERC6909/Mint/ERC6909MintModBase.t.sol new file mode 100644 index 00000000..279ce3ad --- /dev/null +++ b/test/unit/token/ERC6909/Mint/ERC6909MintModBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909MintModHarness} from "test/utils/harnesses/token/ERC6909/ERC6909MintModHarness.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909MintMod_Base_Test is ERC6909_Test_Base { + ERC6909MintModHarness internal harness; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + harness = new ERC6909MintModHarness(); + vm.label(address(harness), "ERC6909MintModHarness"); + } +} diff --git a/test/unit/token/ERC6909/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC6909/Mint/mod/fuzz/mint.t.sol new file mode 100644 index 00000000..2a4f3804 --- /dev/null +++ b/test/unit/token/ERC6909/Mint/mod/fuzz/mint.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {ERC6909MintMod_Base_Test} from "test/unit/token/ERC6909/Mint/ERC6909MintModBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909TransferFacet} from "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Mint_ERC6909MintMod_Fuzz_Test is ERC6909MintMod_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_AccountZero_Mint(uint256 id, uint256 value) external { + vm.expectRevert(abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InvalidReceiver.selector, address(0))); + harness.mint(address(0), id, value); + } + + function testFuzz_ShouldIncreaseBalance_Mint(address account, uint256 id, uint256 value) external { + vm.assume(account != address(0)); + vm.assume(value != 0); + vm.assume(value < type(uint256).max); + + harness.mint(account, id, value); + + assertEq(address(harness).balanceOf(account, id), value, "balance"); + } + + function test_ShouldEmitTransfer_Mint() external { + uint256 id = 1; + uint256 value = 100; + vm.stopPrank(); + vm.recordLogs(); + harness.mint(users.alice, id, value); + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 1, "one log"); + assertEq(logs[0].topics[0], keccak256("Transfer(address,address,address,uint256,uint256)"), "event sig"); + assertEq(address(uint160(uint256(logs[0].topics[1]))), address(0), "indexed sender"); + assertEq(address(uint160(uint256(logs[0].topics[2]))), users.alice, "indexed receiver"); + assertEq(uint256(logs[0].topics[3]), id, "indexed id"); + (address caller, uint256 amount) = abi.decode(logs[0].data, (address, uint256)); + assertEq(caller, address(this), "caller"); + assertEq(amount, value, "amount"); + } +} diff --git a/test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol b/test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol new file mode 100644 index 00000000..218672bb --- /dev/null +++ b/test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909OperatorFacet} from "src/token/ERC6909/Operator/ERC6909OperatorFacet.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909OperatorFacet_Base_Test is ERC6909_Test_Base { + ERC6909OperatorFacet internal facet; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + facet = new ERC6909OperatorFacet(); + vm.label(address(facet), "ERC6909OperatorFacet"); + } +} diff --git a/test/unit/token/ERC6909/Operator/ERC6909OperatorModBase.t.sol b/test/unit/token/ERC6909/Operator/ERC6909OperatorModBase.t.sol new file mode 100644 index 00000000..791007de --- /dev/null +++ b/test/unit/token/ERC6909/Operator/ERC6909OperatorModBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909OperatorModHarness} from "test/utils/harnesses/token/ERC6909/ERC6909OperatorModHarness.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909OperatorMod_Base_Test is ERC6909_Test_Base { + ERC6909OperatorModHarness internal harness; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + harness = new ERC6909OperatorModHarness(); + vm.label(address(harness), "ERC6909OperatorModHarness"); + } +} diff --git a/test/unit/token/ERC6909/Operator/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC6909/Operator/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..3229777b --- /dev/null +++ b/test/unit/token/ERC6909/Operator/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909OperatorFacet_Base_Test} from "test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol"; +import {ERC6909OperatorFacet} from "src/token/ERC6909/Operator/ERC6909OperatorFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract ExportSelectors_ERC6909OperatorFacet_Unit_Test is ERC6909OperatorFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC6909OperatorFacet.setOperator.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC6909/Operator/facet/fuzz/setOperator.t.sol b/test/unit/token/ERC6909/Operator/facet/fuzz/setOperator.t.sol new file mode 100644 index 00000000..585c1d31 --- /dev/null +++ b/test/unit/token/ERC6909/Operator/facet/fuzz/setOperator.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909OperatorFacet_Base_Test} from "test/unit/token/ERC6909/Operator/ERC6909OperatorFacetBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909OperatorFacet} from "src/token/ERC6909/Operator/ERC6909OperatorFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract SetOperator_ERC6909OperatorFacet_Fuzz_Test is ERC6909OperatorFacet_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_SpenderZero_SetOperator(bool approved) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909OperatorFacet.ERC6909InvalidSpender.selector, address(0))); + facet.setOperator(address(0), approved); + } + + function testFuzz_ShouldSetOperatorAndEmit_SetOperator(address spender, bool approved) external { + vm.assume(spender != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, false, true); + emit ERC6909OperatorFacet.OperatorSet(users.alice, spender, approved); + facet.setOperator(spender, approved); + assertEq(address(facet).isOperator(users.alice, spender), approved, "isOperator"); + } +} diff --git a/test/unit/token/ERC6909/Operator/mod/fuzz/setOperator.t.sol b/test/unit/token/ERC6909/Operator/mod/fuzz/setOperator.t.sol new file mode 100644 index 00000000..f5588fc7 --- /dev/null +++ b/test/unit/token/ERC6909/Operator/mod/fuzz/setOperator.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909OperatorMod_Base_Test} from "test/unit/token/ERC6909/Operator/ERC6909OperatorModBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract SetOperator_ERC6909OperatorMod_Fuzz_Test is ERC6909OperatorMod_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_ShouldSetOperator_SetOperator(address spender, bool approved) external { + vm.assume(spender != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + harness.setOperator(spender, approved); + assertEq(address(harness).isOperator(users.alice, spender), approved, "isOperator"); + } +} diff --git a/test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol b/test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol new file mode 100644 index 00000000..99794e36 --- /dev/null +++ b/test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909TransferFacet} from "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909TransferFacet_Base_Test is ERC6909_Test_Base { + ERC6909TransferFacet internal facet; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + facet = new ERC6909TransferFacet(); + vm.label(address(facet), "ERC6909TransferFacet"); + } +} diff --git a/test/unit/token/ERC6909/Transfer/ERC6909TransferModBase.t.sol b/test/unit/token/ERC6909/Transfer/ERC6909TransferModBase.t.sol new file mode 100644 index 00000000..982b41bb --- /dev/null +++ b/test/unit/token/ERC6909/Transfer/ERC6909TransferModBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909TransferModHarness} from "test/utils/harnesses/token/ERC6909/ERC6909TransferModHarness.sol"; + +import {ERC6909_Test_Base} from "test/unit/token/ERC6909/ERC6909TestBase.sol"; + +abstract contract ERC6909TransferMod_Base_Test is ERC6909_Test_Base { + ERC6909TransferModHarness internal harness; + + function setUp() public virtual override { + ERC6909_Test_Base.setUp(); + harness = new ERC6909TransferModHarness(); + vm.label(address(harness), "ERC6909TransferModHarness"); + } +} diff --git a/test/unit/token/ERC6909/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC6909/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..2fb5c7d8 --- /dev/null +++ b/test/unit/token/ERC6909/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909TransferFacet_Base_Test} from "test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol"; +import {ERC6909TransferFacet} from "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract ExportSelectors_ERC6909TransferFacet_Unit_Test is ERC6909TransferFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = + abi.encodePacked(ERC6909TransferFacet.transfer.selector, ERC6909TransferFacet.transferFrom.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC6909/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC6909/Transfer/facet/fuzz/transfer.t.sol new file mode 100644 index 00000000..07231c18 --- /dev/null +++ b/test/unit/token/ERC6909/Transfer/facet/fuzz/transfer.t.sol @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909TransferFacet_Base_Test} from "test/unit/token/ERC6909/Transfer/ERC6909TransferFacetBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909TransferFacet} from "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Transfer_ERC6909TransferFacet_Fuzz_Test is ERC6909TransferFacet_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_ReceiverZero_Transfer(uint256 id, uint256 amount) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InvalidReceiver.selector, address(0))); + facet.transfer(address(0), id, amount); + } + + function testFuzz_RevertWhen_InsufficientBalance_Transfer(uint256 id, uint256 balance, uint256 amount) external { + vm.assume(balance < type(uint256).max); + amount = bound(amount, balance + 1, type(uint256).max); + seedBalance(address(facet), users.alice, id, balance); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909TransferFacet.ERC6909InsufficientBalance.selector, users.alice, balance, amount, id + ) + ); + facet.transfer(users.bob, id, amount); + } + + function testFuzz_ShouldUpdateBalancesAndEmit_Transfer(address receiver, uint256 id, uint256 amount) external { + vm.assume(receiver != address(0)); + vm.assume(receiver != users.alice); + vm.assume(amount != 0); + vm.assume(amount < type(uint256).max); + seedBalance(address(facet), users.alice, id, amount); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, true); + emit ERC6909TransferFacet.Transfer(users.alice, users.alice, receiver, id, amount); + facet.transfer(receiver, id, amount); + assertEq(address(facet).balanceOf(users.alice, id), 0, "from"); + assertEq(address(facet).balanceOf(receiver, id), amount, "to"); + } + + function test_ShouldSucceed_Transfer_WhenAmountZero() external { + uint256 id = 1; + seedBalance(address(facet), users.alice, id, 100); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, true); + emit ERC6909TransferFacet.Transfer(users.alice, users.alice, users.bob, id, 0); + facet.transfer(users.bob, id, 0); + assertEq(address(facet).balanceOf(users.alice, id), 100, "from unchanged"); + assertEq(address(facet).balanceOf(users.bob, id), 0, "to unchanged"); + } + + function testFuzz_RevertWhen_SenderZero_TransferFrom(address receiver, uint256 id, uint256 amount) external { + vm.assume(receiver != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InvalidSender.selector, address(0))); + facet.transferFrom(address(0), receiver, id, amount); + } + + function testFuzz_RevertWhen_ReceiverZero_TransferFrom(address sender, uint256 id, uint256 amount) external { + vm.assume(sender != address(0)); + seedBalance(address(facet), sender, id, amount); + vm.stopPrank(); + vm.prank(sender); + vm.expectRevert(abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InvalidReceiver.selector, address(0))); + facet.transferFrom(sender, address(0), id, amount); + } + + function testFuzz_RevertWhen_InsufficientBalance_TransferFrom( + address sender, + address receiver, + uint256 id, + uint256 balance, + uint256 spend + ) external { + vm.assume(sender != address(0) && receiver != address(0)); + vm.assume(sender != users.bob); + balance = bound(balance, 0, type(uint256).max - 2); + spend = bound(spend, balance + 1, type(uint256).max); + + seedBalance(address(facet), sender, id, balance); + seedAllowance(address(facet), sender, users.bob, id, type(uint256).max); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InsufficientBalance.selector, sender, balance, spend, id) + ); + facet.transferFrom(sender, receiver, id, spend); + } + + function testFuzz_RevertWhen_InsufficientAllowance_TransferFrom( + address sender, + address receiver, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 spend + ) external { + vm.assume(sender != address(0) && receiver != address(0)); + vm.assume(sender != users.bob); + balance = bound(balance, 1, type(uint256).max - 1); + allowanceAmt = bound(allowanceAmt, 0, balance - 1); + spend = bound(spend, allowanceAmt + 1, balance); + vm.assume(spend < type(uint256).max); + + seedBalance(address(facet), sender, id, balance); + seedAllowance(address(facet), sender, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909TransferFacet.ERC6909InsufficientAllowance.selector, users.bob, allowanceAmt, spend, id + ) + ); + facet.transferFrom(sender, receiver, id, spend); + } + + function test_ShouldNotDecreaseAllowance_TransferFrom_WhenAllowanceMax() external { + uint256 id = 7; + uint256 amt = 50; + seedBalance(address(facet), users.alice, id, amt); + seedAllowance(address(facet), users.alice, users.bob, id, type(uint256).max); + vm.stopPrank(); + vm.prank(users.bob); + facet.transferFrom(users.alice, users.charlee, id, amt); + assertEq(address(facet).allowance(users.alice, users.bob, id), type(uint256).max, "allowance still max"); + } + + function test_ShouldBypassAllowance_TransferFrom_WhenCallerIsOperator() external { + uint256 id = 3; + uint256 amt = 40; + seedBalance(address(facet), users.alice, id, amt); + seedIsOperator(address(facet), users.alice, users.bob, true); + vm.stopPrank(); + vm.prank(users.bob); + vm.expectEmit(true, true, true, true); + emit ERC6909TransferFacet.Transfer(users.bob, users.alice, users.charlee, id, amt); + facet.transferFrom(users.alice, users.charlee, id, amt); + assertEq(address(facet).balanceOf(users.alice, id), 0, "from"); + assertEq(address(facet).balanceOf(users.charlee, id), amt, "to"); + } + + function test_ShouldBypassAllowance_TransferFrom_WhenCallerIsSender() external { + uint256 id = 9; + uint256 amt = 25; + seedBalance(address(facet), users.alice, id, amt); + vm.stopPrank(); + vm.prank(users.alice); + facet.transferFrom(users.alice, users.bob, id, amt); + assertEq(address(facet).balanceOf(users.bob, id), amt, "to"); + } + + function testFuzz_ShouldDecreaseAllowanceAndBalances_TransferFrom( + address sender, + address receiver, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 spend + ) external { + vm.assume(sender != address(0) && receiver != address(0)); + vm.assume(sender != receiver); + vm.assume(sender != users.bob); + balance = bound(balance, 2, type(uint256).max / 2); + spend = bound(spend, 1, balance); + allowanceAmt = bound(allowanceAmt, spend, balance); + + seedBalance(address(facet), sender, id, balance); + seedAllowance(address(facet), sender, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectEmit(true, true, true, true); + emit ERC6909TransferFacet.Transfer(users.bob, sender, receiver, id, spend); + facet.transferFrom(sender, receiver, id, spend); + + assertEq(address(facet).balanceOf(sender, id), balance - spend, "from bal"); + assertEq(address(facet).balanceOf(receiver, id), spend, "to bal"); + assertEq(address(facet).allowance(sender, users.bob, id), allowanceAmt - spend, "allowance"); + } +} diff --git a/test/unit/token/ERC6909/Transfer/mod/fuzz/transfer.t.sol b/test/unit/token/ERC6909/Transfer/mod/fuzz/transfer.t.sol new file mode 100644 index 00000000..275a19d7 --- /dev/null +++ b/test/unit/token/ERC6909/Transfer/mod/fuzz/transfer.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC6909TransferMod_Base_Test} from "test/unit/token/ERC6909/Transfer/ERC6909TransferModBase.t.sol"; +import {ERC6909StorageUtils} from "test/utils/storage/ERC6909StorageUtils.sol"; +import {ERC6909TransferFacet} from "src/token/ERC6909/Transfer/ERC6909TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC6909.tree + */ +contract Transfer_ERC6909TransferMod_Fuzz_Test is ERC6909TransferMod_Base_Test { + using ERC6909StorageUtils for address; + + function testFuzz_RevertWhen_ReceiverZero_Transfer(uint256 id, uint256 amount) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC6909TransferFacet.ERC6909InvalidReceiver.selector, address(0))); + harness.transfer(address(0), id, amount); + } + + function testFuzz_ShouldUpdateBalances_Transfer(address receiver, uint256 id, uint256 amount) external { + vm.assume(receiver != address(0)); + vm.assume(receiver != users.alice); + vm.assume(amount != 0); + vm.assume(amount < type(uint256).max); + seedBalance(address(harness), users.alice, id, amount); + vm.stopPrank(); + vm.prank(users.alice); + harness.transfer(receiver, id, amount); + assertEq(address(harness).balanceOf(users.alice, id), 0, "from"); + assertEq(address(harness).balanceOf(receiver, id), amount, "to"); + } + + function testFuzz_RevertWhen_InsufficientAllowance_TransferFrom( + address sender, + address receiver, + uint256 id, + uint256 balance, + uint256 allowanceAmt, + uint256 spend + ) external { + vm.assume(sender != address(0) && receiver != address(0)); + vm.assume(sender != users.bob); + balance = bound(balance, 1, type(uint256).max - 1); + allowanceAmt = bound(allowanceAmt, 0, balance - 1); + spend = bound(spend, allowanceAmt + 1, balance); + + seedBalance(address(harness), sender, id, balance); + seedAllowance(address(harness), sender, users.bob, id, allowanceAmt); + + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector( + ERC6909TransferFacet.ERC6909InsufficientAllowance.selector, users.bob, allowanceAmt, spend, id + ) + ); + harness.transferFrom(sender, receiver, id, spend); + } + + function test_ShouldBypassAllowance_TransferFrom_WhenCallerIsOperator() external { + uint256 id = 3; + uint256 amt = 40; + seedBalance(address(harness), users.alice, id, amt); + seedIsOperator(address(harness), users.alice, users.bob, true); + vm.stopPrank(); + vm.prank(users.bob); + harness.transferFrom(users.alice, users.charlee, id, amt); + assertEq(address(harness).balanceOf(users.alice, id), 0, "from"); + assertEq(address(harness).balanceOf(users.charlee, id), amt, "to"); + } +} diff --git a/test/utils/harnesses/token/ERC6909/ERC6909ApproveModHarness.sol b/test/utils/harnesses/token/ERC6909/ERC6909ApproveModHarness.sol new file mode 100644 index 00000000..804b4234 --- /dev/null +++ b/test/utils/harnesses/token/ERC6909/ERC6909ApproveModHarness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {approve as erc6909Approve} from "src/token/ERC6909/Approve/ERC6909ApproveMod.sol"; + +/** + * @notice Exposes ERC6909ApproveMod `approve` for tests. + */ +contract ERC6909ApproveModHarness { + function approve(address spender, uint256 id, uint256 amount) external { + erc6909Approve(spender, id, amount); + } +} diff --git a/test/utils/harnesses/token/ERC6909/ERC6909BurnModHarness.sol b/test/utils/harnesses/token/ERC6909/ERC6909BurnModHarness.sol new file mode 100644 index 00000000..97d1a971 --- /dev/null +++ b/test/utils/harnesses/token/ERC6909/ERC6909BurnModHarness.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {burn as erc6909Burn, burnFrom as erc6909BurnFrom} from "src/token/ERC6909/Burn/ERC6909BurnMod.sol"; + +/** + * @notice Exposes ERC6909BurnMod burn functions for tests. + */ +contract ERC6909BurnModHarness { + function burn(uint256 id, uint256 amount) external { + erc6909Burn(id, amount); + } + + function burnFrom(address from, uint256 id, uint256 amount) external { + erc6909BurnFrom(from, id, amount); + } +} diff --git a/test/utils/harnesses/token/ERC6909/ERC6909MintModHarness.sol b/test/utils/harnesses/token/ERC6909/ERC6909MintModHarness.sol new file mode 100644 index 00000000..cd5943d2 --- /dev/null +++ b/test/utils/harnesses/token/ERC6909/ERC6909MintModHarness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {mint as erc6909Mint} from "src/token/ERC6909/Mint/ERC6909MintMod.sol"; + +/** + * @notice Exposes ERC6909MintMod `mint` for tests. + */ +contract ERC6909MintModHarness { + function mint(address account, uint256 id, uint256 value) external { + erc6909Mint(account, id, value); + } +} diff --git a/test/utils/harnesses/token/ERC6909/ERC6909OperatorModHarness.sol b/test/utils/harnesses/token/ERC6909/ERC6909OperatorModHarness.sol new file mode 100644 index 00000000..b86ff643 --- /dev/null +++ b/test/utils/harnesses/token/ERC6909/ERC6909OperatorModHarness.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {setOperator as erc6909SetOperator} from "src/token/ERC6909/Operator/ERC6909OperatorMod.sol"; + +/** + * @notice Exposes ERC6909OperatorMod `setOperator` for tests. + */ +contract ERC6909OperatorModHarness { + function setOperator(address spender, bool approved) external { + erc6909SetOperator(spender, approved); + } +} diff --git a/test/utils/harnesses/token/ERC6909/ERC6909TransferModHarness.sol b/test/utils/harnesses/token/ERC6909/ERC6909TransferModHarness.sol new file mode 100644 index 00000000..da4ca868 --- /dev/null +++ b/test/utils/harnesses/token/ERC6909/ERC6909TransferModHarness.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + transfer as erc6909Transfer, + transferFrom as erc6909TransferFrom +} from "src/token/ERC6909/Transfer/ERC6909TransferMod.sol"; + +/** + * @notice Exposes ERC6909TransferMod functions for tests. + */ +contract ERC6909TransferModHarness { + function transfer(address receiver, uint256 id, uint256 amount) external { + erc6909Transfer(receiver, id, amount); + } + + function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external { + erc6909TransferFrom(sender, receiver, id, amount); + } +} diff --git a/test/utils/storage/ERC6909StorageUtils.sol b/test/utils/storage/ERC6909StorageUtils.sol new file mode 100644 index 00000000..7a17bc8a --- /dev/null +++ b/test/utils/storage/ERC6909StorageUtils.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +/** + * @title ERC6909StorageUtils + * @notice Storage helpers for ERC-6909 tests (layout matches `keccak256("erc6909")`). + */ +library ERC6909StorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant ERC6909_STORAGE_POSITION = keccak256("erc6909"); + + function balanceOf(address target, address owner, uint256 id) internal view returns (uint256) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(id, ownerSlot)); + return uint256(vm.load(target, slot)); + } + + function allowance(address target, address owner, address spender, uint256 id) internal view returns (uint256) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION) + 1)); + bytes32 spenderSlot = keccak256(abi.encode(spender, ownerSlot)); + bytes32 slot = keccak256(abi.encode(id, spenderSlot)); + return uint256(vm.load(target, slot)); + } + + function isOperator(address target, address owner, address spender) internal view returns (bool) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(spender, ownerSlot)); + return uint256(vm.load(target, slot)) != 0; + } + + function setBalanceOf(address target, address owner, uint256 id, uint256 value) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(id, ownerSlot)); + vm.store(target, slot, bytes32(value)); + } + + function setAllowance(address target, address owner, address spender, uint256 id, uint256 value) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION) + 1)); + bytes32 spenderSlot = keccak256(abi.encode(spender, ownerSlot)); + bytes32 slot = keccak256(abi.encode(id, spenderSlot)); + vm.store(target, slot, bytes32(value)); + } + + function setIsOperator(address target, address owner, address spender, bool value) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC6909_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(spender, ownerSlot)); + vm.store(target, slot, value ? bytes32(uint256(1)) : bytes32(0)); + } +}