From d05ccb4efd009a3297aadea4bee00527011fcdda Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 18:24:44 -0400 Subject: [PATCH 01/25] refactor owner & accesscontrol tests to BTT --- test/access/AccessControl/AccessControl.t.sol | 527 ---------- .../AccessControl/AccessControlFacet.t.sol | 945 ------------------ .../harnesses/AccessControlFacetHarness.sol | 134 --- .../harnesses/AccessControlHarness.sol | 105 -- .../AccessControlPausable.t.sol | 275 ----- .../AccessControlPausableFacet.t.sol | 330 ------ .../AccessControlPausableFacetHarness.sol | 49 - .../AccessControlPausableHarness.sol | 75 -- .../AccessControlTemporal.t.sol | 322 ------ .../AccessControlTemporalFacet.t.sol | 373 ------- .../AccessControlTemporalFacetHarness.sol | 49 - .../AccessControlTemporalHarness.sol | 82 -- test/access/Owner/Owner.t.sol | 329 ------ test/access/Owner/OwnerFacet.t.sol | 278 ------ .../Owner/harnesses/OwnerFacetHarness.sol | 39 - test/access/Owner/harnesses/OwnerHarness.sol | 58 -- test/access/OwnerTwoSteps/OwnerTwoSteps.t.sol | 571 ----------- .../OwnerTwoSteps/OwnerTwoStepsFacet.t.sol | 647 ------------ .../harnesses/OwnerTwoStepsFacetHarness.sol | 63 -- .../harnesses/OwnerTwoStepsHarness.sol | 98 -- .../AccessControlCoreModHarness.sol | 56 ++ .../AccessControlPausableModHarness.sol | 31 + .../AccessControlTemporalModHarness.sol | 38 + .../access/Owner/OwnerCoreModHarness.sol | 28 + .../access/Owner/OwnerTwoStepModHarness.sol | 36 + test/trees/AccessControl.tree | 179 ++++ test/trees/Owner.tree | 62 ++ .../Admin/AccessControlAdminBase.t.sol | 18 + .../Admin/facet/fuzz/setRoleAdmin.t.sol | 56 ++ .../Admin/mod/fuzz/setRoleAdmin.t.sol | 50 + .../Grant/AccessControlGrantBatchBase.t.sol | 22 + .../Grant/facet/fuzz/grantRoleBatch.t.sol | 100 ++ .../Batch/Grant/mod/fuzz/grantRoleBatch.t.sol | 92 ++ .../Revoke/AccessControlRevokeBatchBase.t.sol | 22 + .../Revoke/facet/fuzz/revokeRoleBatch.t.sol | 102 ++ .../Revoke/mod/fuzz/revokeRoleBatch.t.sol | 94 ++ .../Data/AccessControlDataBase.t.sol | 26 + .../AccessControl/Data/facet/fuzz/data.t.sol | 69 ++ .../AccessControl/Data/mod/fuzz/data.t.sol | 64 ++ .../Grant/AccessControlGrantBase.t.sol | 32 + .../Grant/facet/fuzz/grantRole.t.sol | 115 +++ .../Grant/mod/fuzz/grantRole.t.sol | 106 ++ .../Pausable/AccessControlPausableBase.t.sol | 26 + .../Pausable/facet/fuzz/pausable.t.sol | 124 +++ .../Pausable/mod/fuzz/pausable.t.sol | 86 ++ .../Renounce/AccessControlRenounceBase.t.sol | 11 + .../Renounce/facet/fuzz/renounceRole.t.sol | 68 ++ .../Renounce/mod/fuzz/renounceRole.t.sol | 62 ++ .../Revoke/AccessControlRevokeBase.t.sol | 26 + .../Revoke/facet/fuzz/revokeRole.t.sol | 81 ++ .../Revoke/mod/fuzz/revokeRole.t.sol | 77 ++ .../Data/AccessControlTemporalDataBase.t.sol | 22 + .../Temporal/Data/facet/fuzz/data.t.sol | 136 +++ .../Temporal/Data/mod/fuzz/data.t.sol | 124 +++ .../AccessControlTemporalGrantBase.t.sol | 18 + .../facet/fuzz/grantRoleWithExpiry.t.sol | 87 ++ .../Grant/mod/fuzz/grantRoleWithExpiry.t.sol | 77 ++ .../AccessControlTemporalRevokeBase.t.sol | 26 + .../facet/fuzz/revokeTemporalRole.t.sol | 80 ++ .../Revoke/mod/fuzz/revokeTemporalRole.t.sol | 72 ++ .../access/Owner/Data/OwnerDataBase.t.sol | 16 + .../access/Owner/Data/facet/fuzz/data.t.sol | 41 + .../access/Owner/Data/mod/fuzz/data.t.sol | 53 + .../Owner/Renounce/OwnerRenounceBase.t.sol | 16 + .../facet/fuzz/renounceOwnership.t.sol | 55 + .../Renounce/mod/fuzz/renounceOwnership.t.sol | 50 + .../Owner/Transfer/OwnerTransferBase.t.sol | 16 + .../facet/fuzz/transferOwnership.t.sol | 72 ++ .../Transfer/mod/fuzz/transferOwnership.t.sol | 67 ++ .../TwoSteps/Data/OwnerTwoStepDataBase.t.sol | 20 + .../Owner/TwoSteps/Data/facet/fuzz/data.t.sol | 41 + .../Owner/TwoSteps/Data/mod/fuzz/data.t.sol | 35 + .../Renounce/OwnerTwoStepRenounceBase.t.sol | 20 + .../facet/fuzz/renounceOwnership.t.sol | 58 ++ .../Renounce/mod/fuzz/renounceOwnership.t.sol | 52 + .../Transfer/OwnerTwoStepTransferBase.t.sol | 20 + .../facet/fuzz/transferOwnership.t.sol | 105 ++ .../Transfer/mod/fuzz/transferOwnership.t.sol | 96 ++ .../storage/AccessControlStorageUtils.sol | 65 ++ test/utils/storage/OwnerStorageUtils.sol | 37 + 80 files changed, 3536 insertions(+), 5349 deletions(-) delete mode 100644 test/access/AccessControl/AccessControl.t.sol delete mode 100644 test/access/AccessControl/AccessControlFacet.t.sol delete mode 100644 test/access/AccessControl/harnesses/AccessControlFacetHarness.sol delete mode 100644 test/access/AccessControl/harnesses/AccessControlHarness.sol delete mode 100644 test/access/AccessControlPausable/AccessControlPausable.t.sol delete mode 100644 test/access/AccessControlPausable/AccessControlPausableFacet.t.sol delete mode 100644 test/access/AccessControlPausable/harnesses/AccessControlPausableFacetHarness.sol delete mode 100644 test/access/AccessControlPausable/harnesses/AccessControlPausableHarness.sol delete mode 100644 test/access/AccessControlTemporal/AccessControlTemporal.t.sol delete mode 100644 test/access/AccessControlTemporal/AccessControlTemporalFacet.t.sol delete mode 100644 test/access/AccessControlTemporal/harnesses/AccessControlTemporalFacetHarness.sol delete mode 100644 test/access/AccessControlTemporal/harnesses/AccessControlTemporalHarness.sol delete mode 100644 test/access/Owner/Owner.t.sol delete mode 100644 test/access/Owner/OwnerFacet.t.sol delete mode 100644 test/access/Owner/harnesses/OwnerFacetHarness.sol delete mode 100644 test/access/Owner/harnesses/OwnerHarness.sol delete mode 100644 test/access/OwnerTwoSteps/OwnerTwoSteps.t.sol delete mode 100644 test/access/OwnerTwoSteps/OwnerTwoStepsFacet.t.sol delete mode 100644 test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsFacetHarness.sol delete mode 100644 test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsHarness.sol create mode 100644 test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol create mode 100644 test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol create mode 100644 test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol create mode 100644 test/harnesses/access/Owner/OwnerCoreModHarness.sol create mode 100644 test/harnesses/access/Owner/OwnerTwoStepModHarness.sol create mode 100644 test/trees/AccessControl.tree create mode 100644 test/trees/Owner.tree create mode 100644 test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol create mode 100644 test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol create mode 100644 test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol create mode 100644 test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol create mode 100644 test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol create mode 100644 test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol create mode 100644 test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol create mode 100644 test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol create mode 100644 test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol create mode 100644 test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol create mode 100644 test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol create mode 100644 test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol create mode 100644 test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol create mode 100644 test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol create mode 100644 test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol create mode 100644 test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol create mode 100644 test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol create mode 100644 test/unit/access/Owner/Data/OwnerDataBase.t.sol create mode 100644 test/unit/access/Owner/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/access/Owner/Data/mod/fuzz/data.t.sol create mode 100644 test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol create mode 100644 test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol create mode 100644 test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol create mode 100644 test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol create mode 100644 test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol create mode 100644 test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol create mode 100644 test/utils/storage/AccessControlStorageUtils.sol create mode 100644 test/utils/storage/OwnerStorageUtils.sol diff --git a/test/access/AccessControl/AccessControl.t.sol b/test/access/AccessControl/AccessControl.t.sol deleted file mode 100644 index 9f641616..00000000 --- a/test/access/AccessControl/AccessControl.t.sol +++ /dev/null @@ -1,527 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/access/AccessControl/AccessControlMod.sol" as AccessControlMod; -import {AccessControlHarness} from "./harnesses/AccessControlHarness.sol"; - -contract LibAccessControlTest is Test { - AccessControlHarness public harness; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address CHARLIE = makeAddr("charlie"); - address ZERO_ADDRESS = address(0); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); - bytes32 constant MODERATOR_ROLE = keccak256("MODERATOR_ROLE"); - - /** - * Events - */ - event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); - event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); - - function setUp() public { - harness = new AccessControlHarness(); - harness.initialize(ADMIN); - } - - /** - * ============================================ - * Storage Tests - * ============================================ - */ - - function test_GetStorage_ReturnsCorrectInitialState() public view { - assertEq(harness.hasRole(DEFAULT_ADMIN_ROLE, ADMIN), true); - assertEq(harness.getStorageHasRole(ADMIN, DEFAULT_ADMIN_ROLE), true); - } - - function test_StorageSlot_UsesCorrectPosition() public { - bytes32 expectedSlot = keccak256("compose.accesscontrol"); - - /** - * Grant a role - */ - vm.prank(address(harness)); - harness.grantRole(MINTER_ROLE, ALICE); - - /** - * The actual storage slot for hasRole[ALICE][MINTER_ROLE] is computed as: - * keccak256(abi.encode(MINTER_ROLE, keccak256(abi.encode(ALICE, expectedSlot)))) - */ - bytes32 accountSlot = keccak256(abi.encode(ALICE, expectedSlot)); - bytes32 roleSlot = keccak256(abi.encode(MINTER_ROLE, accountSlot)); - - /** - * Read directly from storage - */ - bytes32 storedValue = vm.load(address(harness), roleSlot); - bool hasRole = storedValue != bytes32(0); - - assertTrue(hasRole, "Role should be stored at correct position"); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_DefaultAdminRole_IsZeroBytes() public view { - assertEq(harness.getDefaultAdminRole(), bytes32(0)); - } - - /** - * ============================================ - * HasRole Tests - * ============================================ - */ - - function test_HasRole_ReturnsTrueForGrantedRole() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_HasRole_ReturnsFalseForNonGrantedRole() public view { - assertFalse(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_HasRole_ReturnsFalseForZeroAddress() public view { - assertFalse(harness.hasRole(DEFAULT_ADMIN_ROLE, ZERO_ADDRESS)); - } - - function test_HasRole_HandlesMultipleRolesPerAccount() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - harness.forceGrantRole(PAUSER_ROLE, ALICE); - harness.forceGrantRole(UPGRADER_ROLE, ALICE); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(harness.hasRole(UPGRADER_ROLE, ALICE)); - assertFalse(harness.hasRole(MODERATOR_ROLE, ALICE)); - } - - function test_HasRole_HandlesMultipleAccountsPerRole() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - harness.forceGrantRole(MINTER_ROLE, BOB); - harness.forceGrantRole(MINTER_ROLE, CHARLIE); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(MINTER_ROLE, BOB)); - assertTrue(harness.hasRole(MINTER_ROLE, CHARLIE)); - } - - /** - * ============================================ - * RequireRole Tests - * ============================================ - */ - - function test_RequireRole_PassesWhenAccountHasRole() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - harness.requireRole(MINTER_ROLE, ALICE); - /** - * No revert means success - */ - } - - function test_RevertWhen_RequireRole_AccountDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector(AccessControlMod.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE) - ); - harness.requireRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRole_ZeroAddressDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlMod.AccessControlUnauthorizedAccount.selector, ZERO_ADDRESS, DEFAULT_ADMIN_ROLE - ) - ); - harness.requireRole(DEFAULT_ADMIN_ROLE, ZERO_ADDRESS); - } - - /** - * ============================================ - * SetRoleAdmin Tests - * ============================================ - */ - - function test_SetRoleAdmin_UpdatesAdminRole() public { - harness.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - } - - function test_SetRoleAdmin_EmitsRoleAdminChangedEvent() public { - vm.expectEmit(true, true, true, true); - emit RoleAdminChanged(MINTER_ROLE, DEFAULT_ADMIN_ROLE, PAUSER_ROLE); - - harness.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - } - - function test_SetRoleAdmin_CanSetToDefaultAdminRole() public { - harness.setRoleAdmin(MINTER_ROLE, DEFAULT_ADMIN_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), DEFAULT_ADMIN_ROLE); - } - - function test_SetRoleAdmin_CanSetToSameRole() public { - harness.setRoleAdmin(MINTER_ROLE, MINTER_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), MINTER_ROLE); - } - - function test_SetRoleAdmin_MultipleChanges() public { - /** - * First change - */ - harness.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - - /** - * Second change - */ - harness.setRoleAdmin(MINTER_ROLE, UPGRADER_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), UPGRADER_ROLE); - - /** - * Back to default - */ - harness.setRoleAdmin(MINTER_ROLE, DEFAULT_ADMIN_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), DEFAULT_ADMIN_ROLE); - } - - /** - * ============================================ - * GrantRole Tests - * ============================================ - */ - - function test_GrantRole_GrantsRoleToAccount() public { - vm.prank(address(harness)); - bool granted = harness.grantRole(MINTER_ROLE, ALICE); - - assertTrue(granted); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_GrantRole_ReturnsFalseWhenAlreadyGranted() public { - vm.prank(address(harness)); - harness.grantRole(MINTER_ROLE, ALICE); - - vm.prank(address(harness)); - bool granted = harness.grantRole(MINTER_ROLE, ALICE); - - assertFalse(granted); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_GrantRole_EmitsRoleGrantedEvent() public { - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, ALICE, address(harness)); - - vm.prank(address(harness)); - harness.grantRole(MINTER_ROLE, ALICE); - } - - function test_GrantRole_DoesNotEmitEventWhenAlreadyGranted() public { - vm.prank(address(harness)); - harness.grantRole(MINTER_ROLE, ALICE); - - /** - * Should not emit event on second grant - * We check this by testing that no RoleGranted event is emitted - */ - vm.prank(address(harness)); - bool granted = harness.grantRole(MINTER_ROLE, ALICE); - - /** - * The function should return false when role is already granted - */ - assertFalse(granted, "Should return false when role already granted"); - } - - function test_GrantRole_CanGrantToZeroAddress() public { - vm.prank(address(harness)); - bool granted = harness.grantRole(MINTER_ROLE, ZERO_ADDRESS); - - assertTrue(granted); - assertTrue(harness.hasRole(MINTER_ROLE, ZERO_ADDRESS)); - } - - function test_GrantRole_MultipleRolesToSameAccount() public { - vm.startPrank(address(harness)); - - harness.grantRole(MINTER_ROLE, ALICE); - harness.grantRole(PAUSER_ROLE, ALICE); - harness.grantRole(UPGRADER_ROLE, ALICE); - - vm.stopPrank(); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(harness.hasRole(UPGRADER_ROLE, ALICE)); - } - - function test_GrantRole_SameRoleToMultipleAccounts() public { - vm.startPrank(address(harness)); - - harness.grantRole(MINTER_ROLE, ALICE); - harness.grantRole(MINTER_ROLE, BOB); - harness.grantRole(MINTER_ROLE, CHARLIE); - - vm.stopPrank(); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(MINTER_ROLE, BOB)); - assertTrue(harness.hasRole(MINTER_ROLE, CHARLIE)); - } - - /** - * ============================================ - * RevokeRole Tests - * ============================================ - */ - - function test_RevokeRole_RevokesRoleFromAccount() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - - vm.prank(address(harness)); - bool revoked = harness.revokeRole(MINTER_ROLE, ALICE); - - assertTrue(revoked); - assertFalse(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeRole_ReturnsFalseWhenNotGranted() public { - vm.prank(address(harness)); - bool revoked = harness.revokeRole(MINTER_ROLE, ALICE); - - assertFalse(revoked); - assertFalse(harness.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeRole_EmitsRoleRevokedEvent() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, address(harness)); - - vm.prank(address(harness)); - harness.revokeRole(MINTER_ROLE, ALICE); - } - - function test_RevokeRole_DoesNotEmitEventWhenNotGranted() public { - /** - * We check this by testing that the function returns false when no role to revoke - */ - vm.prank(address(harness)); - bool revoked = harness.revokeRole(MINTER_ROLE, ALICE); - - /** - * The function should return false when role is not granted - */ - assertFalse(revoked, "Should return false when role not granted"); - } - - function test_RevokeRole_OnlyRevokesSpecificRole() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - harness.forceGrantRole(PAUSER_ROLE, ALICE); - harness.forceGrantRole(UPGRADER_ROLE, ALICE); - - vm.prank(address(harness)); - harness.revokeRole(PAUSER_ROLE, ALICE); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertFalse(harness.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(harness.hasRole(UPGRADER_ROLE, ALICE)); - } - - function test_RevokeRole_OnlyRevokesFromSpecificAccount() public { - harness.forceGrantRole(MINTER_ROLE, ALICE); - harness.forceGrantRole(MINTER_ROLE, BOB); - harness.forceGrantRole(MINTER_ROLE, CHARLIE); - - vm.prank(address(harness)); - harness.revokeRole(MINTER_ROLE, BOB); - - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - assertFalse(harness.hasRole(MINTER_ROLE, BOB)); - assertTrue(harness.hasRole(MINTER_ROLE, CHARLIE)); - } - - /** - * ============================================ - * Edge Cases Tests - * ============================================ - */ - - function test_EdgeCase_GrantAndRevokeMultipleTimes() public { - vm.startPrank(address(harness)); - - /** - * Grant - */ - assertTrue(harness.grantRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - - /** - * Revoke - */ - assertTrue(harness.revokeRole(MINTER_ROLE, ALICE)); - assertFalse(harness.hasRole(MINTER_ROLE, ALICE)); - - /** - * Grant again - */ - assertTrue(harness.grantRole(MINTER_ROLE, ALICE)); - assertTrue(harness.hasRole(MINTER_ROLE, ALICE)); - - /** - * Revoke again - */ - assertTrue(harness.revokeRole(MINTER_ROLE, ALICE)); - assertFalse(harness.hasRole(MINTER_ROLE, ALICE)); - - vm.stopPrank(); - } - - function test_EdgeCase_RoleAdminOfItself() public { - harness.setRoleAdmin(MINTER_ROLE, MINTER_ROLE); - assertEq(harness.getRoleAdmin(MINTER_ROLE), MINTER_ROLE); - } - - function test_EdgeCase_CircularRoleAdminHierarchy() public { - harness.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - harness.setRoleAdmin(PAUSER_ROLE, UPGRADER_ROLE); - harness.setRoleAdmin(UPGRADER_ROLE, MINTER_ROLE); - - assertEq(harness.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - assertEq(harness.getRoleAdmin(PAUSER_ROLE), UPGRADER_ROLE); - assertEq(harness.getRoleAdmin(UPGRADER_ROLE), MINTER_ROLE); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_HasRole_ConsistentWithStorage(address account, bytes32 role, bool hasRole) public { - if (hasRole) { - harness.forceGrantRole(role, account); - } - - assertEq(harness.hasRole(role, account), hasRole); - assertEq(harness.getStorageHasRole(account, role), hasRole); - } - - function testFuzz_GrantRole_AlwaysReturnsCorrectBool(address account, bytes32 role) public { - vm.startPrank(address(harness)); - - /** - * First grant should return true - */ - bool firstGrant = harness.grantRole(role, account); - assertTrue(firstGrant); - assertTrue(harness.hasRole(role, account)); - - /** - * Second grant should return false - */ - bool secondGrant = harness.grantRole(role, account); - assertFalse(secondGrant); - assertTrue(harness.hasRole(role, account)); - - vm.stopPrank(); - } - - function testFuzz_RevokeRole_AlwaysReturnsCorrectBool(address account, bytes32 role) public { - vm.startPrank(address(harness)); - - /** - * First revoke (no role) should return false - */ - bool firstRevoke = harness.revokeRole(role, account); - assertFalse(firstRevoke); - assertFalse(harness.hasRole(role, account)); - - /** - * Grant role - */ - harness.grantRole(role, account); - assertTrue(harness.hasRole(role, account)); - - /** - * Second revoke (has role) should return true - */ - bool secondRevoke = harness.revokeRole(role, account); - assertTrue(secondRevoke); - assertFalse(harness.hasRole(role, account)); - - vm.stopPrank(); - } - - function testFuzz_SetRoleAdmin_AlwaysUpdatesCorrectly(bytes32 role, bytes32 adminRole) public { - harness.setRoleAdmin(role, adminRole); - assertEq(harness.getRoleAdmin(role), adminRole); - } - - function testFuzz_MultipleRolesPerAccount(address account) public { - vm.assume(account != address(0)); - - bytes32[] memory roles = new bytes32[](5); - roles[0] = keccak256("ROLE_1"); - roles[1] = keccak256("ROLE_2"); - roles[2] = keccak256("ROLE_3"); - roles[3] = keccak256("ROLE_4"); - roles[4] = keccak256("ROLE_5"); - - vm.startPrank(address(harness)); - - /** - * Grant all roles - */ - for (uint256 i = 0; i < roles.length; i++) { - harness.grantRole(roles[i], account); - } - - /** - * Verify all roles are granted - */ - for (uint256 i = 0; i < roles.length; i++) { - assertTrue(harness.hasRole(roles[i], account)); - } - - /** - * Revoke some roles (even indices) - */ - for (uint256 i = 0; i < roles.length; i += 2) { - harness.revokeRole(roles[i], account); - } - - /** - * Verify correct roles are revoked/kept - */ - for (uint256 i = 0; i < roles.length; i++) { - if (i % 2 == 0) { - assertFalse(harness.hasRole(roles[i], account)); - } else { - assertTrue(harness.hasRole(roles[i], account)); - } - } - - vm.stopPrank(); - } -} diff --git a/test/access/AccessControl/AccessControlFacet.t.sol b/test/access/AccessControl/AccessControlFacet.t.sol deleted file mode 100644 index 76348f52..00000000 --- a/test/access/AccessControl/AccessControlFacet.t.sol +++ /dev/null @@ -1,945 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {AccessControlFacet} from "../../../src/access/AccessControl/AccessControlFacet.sol"; -import {AccessControlFacetHarness} from "./harnesses/AccessControlFacetHarness.sol"; - -contract AccessControlFacetTest is Test { - AccessControlFacetHarness public accessControl; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address CHARLIE = makeAddr("charlie"); - address ZERO_ADDRESS = address(0); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); - bytes32 constant MODERATOR_ROLE = keccak256("MODERATOR_ROLE"); - bytes32 constant USER_ROLE = keccak256("USER_ROLE"); - - /** - * Events - */ - event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole); - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); - event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); - - function setUp() public { - accessControl = new AccessControlFacetHarness(); - accessControl.initialize(ADMIN); - } - - /** - * ============================================ - * HasRole Tests - * ============================================ - */ - - function test_HasRole_ReturnsCorrectInitialState() public view { - assertTrue(accessControl.hasRole(DEFAULT_ADMIN_ROLE, ADMIN)); - assertFalse(accessControl.hasRole(DEFAULT_ADMIN_ROLE, ALICE)); - } - - function test_HasRole_ReturnsTrueForGrantedRole() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_HasRole_ReturnsFalseForNonGrantedRole() public view { - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_HasRole_HandlesMultipleRolesPerAccount() public { - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.grantRole(PAUSER_ROLE, ALICE); - accessControl.grantRole(UPGRADER_ROLE, ALICE); - vm.stopPrank(); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(UPGRADER_ROLE, ALICE)); - assertFalse(accessControl.hasRole(MODERATOR_ROLE, ALICE)); - } - - function test_HasRole_HandlesMultipleAccountsPerRole() public { - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.grantRole(MINTER_ROLE, BOB); - accessControl.grantRole(MINTER_ROLE, CHARLIE); - vm.stopPrank(); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(MINTER_ROLE, BOB)); - assertTrue(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - /** - * ============================================ - * RequireRole Tests - * ============================================ - */ - - function test_RequireRole_PassesWhenAccountHasRole() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - accessControl.requireRole(MINTER_ROLE, ALICE); - /** - * No revert means success - */ - } - - function test_RevertWhen_RequireRole_AccountDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE) - ); - accessControl.requireRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRole_ZeroAddressDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, ZERO_ADDRESS, DEFAULT_ADMIN_ROLE - ) - ); - accessControl.requireRole(DEFAULT_ADMIN_ROLE, ZERO_ADDRESS); - } - - /** - * ============================================ - * GetRoleAdmin Tests - * ============================================ - */ - - function test_GetRoleAdmin_ReturnsDefaultAdminForNewRole() public view { - assertEq(accessControl.getRoleAdmin(MINTER_ROLE), DEFAULT_ADMIN_ROLE); - } - - function test_GetRoleAdmin_ReturnsCorrectAdminAfterChange() public { - /** - * Set up role hierarchy - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - assertEq(accessControl.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - } - - /** - * ============================================ - * setRoleAdmin Tests (external) - * ============================================ - */ - - function test_SetRoleAdmin_SucceedsWhenCallerIsCurrentAdmin() public { - /** - * DEFAULT_ADMIN_ROLE is current admin for new roles - */ - vm.expectEmit(true, true, true, true); - emit RoleAdminChanged(MINTER_ROLE, DEFAULT_ADMIN_ROLE, PAUSER_ROLE); - - vm.prank(ADMIN); - accessControl.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - assertEq(accessControl.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - } - - function test_RevertWhen_SetRoleAdmin_CallerIsNotCurrentAdmin() public { - /** - * Set current admin to PAUSER_ROLE - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - /** - * ADMIN has DEFAULT_ADMIN_ROLE but not PAUSER_ROLE - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedAccount.selector, ADMIN, PAUSER_ROLE) - ); - vm.prank(ADMIN); - accessControl.setRoleAdmin(MINTER_ROLE, UPGRADER_ROLE); - } - - /** - * ============================================ - * Batch Grant/Revoke Tests - * ============================================ - */ - - function test_GrantRoleBatch_SucceedsAndEmitsPerNewGrant() public { - address[] memory accounts = new address[](3); - accounts[0] = ALICE; - accounts[1] = BOB; - accounts[2] = CHARLIE; - - vm.startPrank(ADMIN); - /** - * Expect three RoleGranted events - */ - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, ALICE, ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, BOB, ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, CHARLIE, ADMIN); - accessControl.grantRoleBatch(MINTER_ROLE, accounts); - vm.stopPrank(); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(MINTER_ROLE, BOB)); - assertTrue(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - function test_GrantRoleBatch_SkipsAlreadyGrantedWithoutExtraEvents() public { - /** - * Pre-grant ALICE - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - address[] memory accounts = new address[](3); - accounts[0] = ALICE; // already granted - accounts[1] = BOB; - accounts[2] = CHARLIE; - - vm.startPrank(ADMIN); - /** - * Expect only two events for new grants - */ - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, BOB, ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, CHARLIE, ADMIN); - accessControl.grantRoleBatch(MINTER_ROLE, accounts); - vm.stopPrank(); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(MINTER_ROLE, BOB)); - assertTrue(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - function test_RevertWhen_GrantRoleBatch_CallerIsNotAdmin() public { - address[] memory accounts = new address[](2); - accounts[0] = ALICE; - accounts[1] = BOB; - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - accessControl.grantRoleBatch(MINTER_ROLE, accounts); - } - - function test_RevokeRoleBatch_SucceedsAndEmitsPerRevocation() public { - /** - * Setup grants - */ - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.grantRole(MINTER_ROLE, BOB); - accessControl.grantRole(MINTER_ROLE, CHARLIE); - vm.stopPrank(); - - address[] memory accounts = new address[](3); - accounts[0] = ALICE; - accounts[1] = BOB; - accounts[2] = CHARLIE; - - vm.startPrank(ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, BOB, ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, CHARLIE, ADMIN); - accessControl.revokeRoleBatch(MINTER_ROLE, accounts); - vm.stopPrank(); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertFalse(accessControl.hasRole(MINTER_ROLE, BOB)); - assertFalse(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - function test_RevokeRoleBatch_SkipsNotGrantedWithoutExtraEvents() public { - /** - * Only ALICE has the role - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - address[] memory accounts = new address[](3); - accounts[0] = ALICE; // granted - accounts[1] = BOB; // not granted - accounts[2] = CHARLIE; // not granted - - vm.startPrank(ADMIN); - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, ADMIN); - accessControl.revokeRoleBatch(MINTER_ROLE, accounts); - vm.stopPrank(); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertFalse(accessControl.hasRole(MINTER_ROLE, BOB)); - assertFalse(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - function test_RevertWhen_RevokeRoleBatch_CallerIsNotAdmin() public { - /** - * Setup grants - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - address[] memory accounts = new address[](1); - accounts[0] = ALICE; - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, BOB, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(BOB); - accessControl.revokeRoleBatch(MINTER_ROLE, accounts); - } - - function test_GrantRoleBatch_SucceedsWithEmptyArray() public { - address[] memory accounts = new address[](0); - - /** - * Should just succeed with no reverts and no events - */ - vm.prank(ADMIN); - accessControl.grantRoleBatch(MINTER_ROLE, accounts); - } - - function test_RevokeRoleBatch_SucceedsWithEmptyArray() public { - address[] memory accounts = new address[](0); - - /** - * Should just succeed with no reverts and no events - */ - vm.prank(ADMIN); - accessControl.revokeRoleBatch(MINTER_ROLE, accounts); - } - - function test_DelegatedAdminCanExerciseAdminPowers() public { - /** - * === Arrange === - */ - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, BOB); - accessControl.grantRole(PAUSER_ROLE, ALICE); - accessControl.setRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - vm.stopPrank(); - - /** - * Assert the setup is correct before acting - */ - assertTrue(accessControl.hasRole(MINTER_ROLE, BOB)); - assertTrue(accessControl.hasRole(PAUSER_ROLE, ALICE)); - - /** - * === Act === - * Expect the event to be emitted - */ - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, BOB, ALICE); - - vm.prank(ALICE); - accessControl.revokeRole(MINTER_ROLE, BOB); - - /** - * === Assert === - */ - assertFalse(accessControl.hasRole(MINTER_ROLE, BOB)); - } - - function test_GetRoleAdmin_DefaultAdminRoleAdminIsItself() public view { - assertEq(accessControl.getRoleAdmin(DEFAULT_ADMIN_ROLE), DEFAULT_ADMIN_ROLE); - } - - /** - * ============================================ - * GrantRole Tests - * ============================================ - */ - - function test_GrantRole_SucceedsWithDefaultAdmin() public { - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, ALICE, ADMIN); - - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_GrantRole_SucceedsWithCustomRoleAdmin() public { - /** - * Set up custom admin for MINTER_ROLE - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - accessControl.forceGrantRole(PAUSER_ROLE, BOB); - - vm.expectEmit(true, true, true, true); - emit RoleGranted(MINTER_ROLE, ALICE, BOB); - - vm.prank(BOB); - accessControl.grantRole(MINTER_ROLE, ALICE); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_GrantRole_DoesNotEmitEventWhenAlreadyGranted() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - /** - * Second grant should not emit an event - * The AccessControlFacet doesn't return a bool, but we can verify - * that the role is still granted and no duplicate event is emitted - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); // Should silently succeed - - /** - * Verify the role is still granted - */ - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevertWhen_GrantRole_CallerIsNotAdmin() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - accessControl.grantRole(MINTER_ROLE, BOB); - } - - function test_RevertWhen_GrantRole_CallerIsNotCustomAdmin() public { - /** - * Set up custom admin for MINTER_ROLE - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - /** - * ADMIN has DEFAULT_ADMIN_ROLE but not PAUSER_ROLE - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedAccount.selector, ADMIN, PAUSER_ROLE) - ); - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - } - - function test_GrantRole_CanGrantToZeroAddress() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ZERO_ADDRESS); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ZERO_ADDRESS)); - } - - function test_GrantRole_CanGrantToSelf() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ADMIN); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ADMIN)); - } - - /** - * ============================================ - * RevokeRole Tests - * ============================================ - */ - - function test_RevokeRole_SucceedsWithDefaultAdmin() public { - /** - * Setup - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - - /** - * Revoke - */ - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, ADMIN); - - vm.prank(ADMIN); - accessControl.revokeRole(MINTER_ROLE, ALICE); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeRole_SucceedsWithCustomRoleAdmin() public { - /** - * Set up custom admin for MINTER_ROLE - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - accessControl.forceGrantRole(PAUSER_ROLE, BOB); - accessControl.forceGrantRole(MINTER_ROLE, ALICE); - - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, BOB); - - vm.prank(BOB); - accessControl.revokeRole(MINTER_ROLE, ALICE); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeRole_DoesNotEmitEventWhenNotGranted() public { - /** - * Revoking a role that's not granted should succeed silently - */ - vm.prank(ADMIN); - accessControl.revokeRole(MINTER_ROLE, ALICE); - - /** - * Verify the role is still not granted - */ - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevertWhen_RevokeRole_CallerIsNotAdmin() public { - accessControl.forceGrantRole(MINTER_ROLE, ALICE); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - accessControl.revokeRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RevokeRole_CallerIsNotCustomAdmin() public { - /** - * Set up custom admin for MINTER_ROLE - */ - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - accessControl.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * ADMIN has DEFAULT_ADMIN_ROLE but not PAUSER_ROLE - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedAccount.selector, ADMIN, PAUSER_ROLE) - ); - vm.prank(ADMIN); - accessControl.revokeRole(MINTER_ROLE, ALICE); - } - - function test_RevokeRole_CanRevokeFromZeroAddress() public { - /** - * Setup - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ZERO_ADDRESS); - assertTrue(accessControl.hasRole(MINTER_ROLE, ZERO_ADDRESS)); - - /** - * Revoke - */ - vm.prank(ADMIN); - accessControl.revokeRole(MINTER_ROLE, ZERO_ADDRESS); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ZERO_ADDRESS)); - } - - /** - * ============================================ - * RenounceRole Tests - * ============================================ - */ - - function test_RenounceRole_SucceedsForOwnRole() public { - /** - * Setup - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - - /** - * Renounce - */ - vm.expectEmit(true, true, true, true); - emit RoleRevoked(MINTER_ROLE, ALICE, ALICE); - - vm.prank(ALICE); - accessControl.renounceRole(MINTER_ROLE, ALICE); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RenounceRole_DoesNotEmitEventWhenNotGranted() public { - /** - * Renouncing a role that's not granted should succeed silently - */ - vm.prank(ALICE); - accessControl.renounceRole(MINTER_ROLE, ALICE); - - /** - * Verify the role is still not granted - */ - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevertWhen_RenounceRole_CallerIsNotAccount() public { - accessControl.forceGrantRole(MINTER_ROLE, ALICE); - - vm.expectRevert(abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedSender.selector, BOB, ALICE)); - vm.prank(BOB); - accessControl.renounceRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RenounceRole_AdminCannotRenounceForOthers() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedSender.selector, ADMIN, ALICE) - ); - vm.prank(ADMIN); - accessControl.renounceRole(MINTER_ROLE, ALICE); - } - - function test_RenounceRole_CanRenounceMultipleRoles() public { - /** - * Setup - */ - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.grantRole(PAUSER_ROLE, ALICE); - accessControl.grantRole(UPGRADER_ROLE, ALICE); - vm.stopPrank(); - - /** - * Renounce roles one by one - */ - vm.startPrank(ALICE); - accessControl.renounceRole(MINTER_ROLE, ALICE); - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(UPGRADER_ROLE, ALICE)); - - accessControl.renounceRole(PAUSER_ROLE, ALICE); - assertFalse(accessControl.hasRole(PAUSER_ROLE, ALICE)); - assertTrue(accessControl.hasRole(UPGRADER_ROLE, ALICE)); - - accessControl.renounceRole(UPGRADER_ROLE, ALICE); - assertFalse(accessControl.hasRole(UPGRADER_ROLE, ALICE)); - vm.stopPrank(); - } - - function test_RenounceRole_CanRenounceDefaultAdminRole() public { - assertTrue(accessControl.hasRole(DEFAULT_ADMIN_ROLE, ADMIN)); - - vm.prank(ADMIN); - accessControl.renounceRole(DEFAULT_ADMIN_ROLE, ADMIN); - - assertFalse(accessControl.hasRole(DEFAULT_ADMIN_ROLE, ADMIN)); - } - - /** - * ============================================ - * Complex Role Hierarchy Tests - * ============================================ - */ - - function test_ComplexRoleHierarchy_Setup() public { - accessControl.setupComplexRoleHierarchy(); - - bytes32 ADMIN_ROLE = keccak256("ADMIN_ROLE"); - bytes32 MODERATOR_ROLE_LOCAL = keccak256("MODERATOR_ROLE"); - bytes32 USER_ROLE_LOCAL = keccak256("USER_ROLE"); - - assertEq(accessControl.getRoleAdmin(ADMIN_ROLE), DEFAULT_ADMIN_ROLE); - assertEq(accessControl.getRoleAdmin(MODERATOR_ROLE_LOCAL), ADMIN_ROLE); - assertEq(accessControl.getRoleAdmin(USER_ROLE_LOCAL), MODERATOR_ROLE_LOCAL); - } - - function test_ComplexRoleHierarchy_GrantingThroughHierarchy() public { - accessControl.setupComplexRoleHierarchy(); - - bytes32 ADMIN_ROLE = keccak256("ADMIN_ROLE"); - bytes32 MODERATOR_ROLE_LOCAL = keccak256("MODERATOR_ROLE"); - bytes32 USER_ROLE_LOCAL = keccak256("USER_ROLE"); - - /** - * Grant ADMIN_ROLE (only DEFAULT_ADMIN can do this) - */ - vm.prank(ADMIN); - accessControl.grantRole(ADMIN_ROLE, ALICE); - - /** - * Grant MODERATOR_ROLE (only ADMIN_ROLE can do this) - */ - vm.prank(ALICE); - accessControl.grantRole(MODERATOR_ROLE_LOCAL, BOB); - - /** - * Grant USER_ROLE (only MODERATOR_ROLE can do this) - */ - vm.prank(BOB); - accessControl.grantRole(USER_ROLE_LOCAL, CHARLIE); - - assertTrue(accessControl.hasRole(ADMIN_ROLE, ALICE)); - assertTrue(accessControl.hasRole(MODERATOR_ROLE_LOCAL, BOB)); - assertTrue(accessControl.hasRole(USER_ROLE_LOCAL, CHARLIE)); - } - - function test_ComplexRoleHierarchy_CannotGrantWithoutProperAdmin() public { - accessControl.setupComplexRoleHierarchy(); - - bytes32 ADMIN_ROLE = keccak256("ADMIN_ROLE"); - bytes32 MODERATOR_ROLE_LOCAL = keccak256("MODERATOR_ROLE"); - - /** - * ALICE doesn't have ADMIN_ROLE, so can't grant MODERATOR_ROLE - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedAccount.selector, ALICE, ADMIN_ROLE) - ); - vm.prank(ALICE); - accessControl.grantRole(MODERATOR_ROLE_LOCAL, BOB); - } - - /** - * ============================================ - * Edge Cases Tests - * ============================================ - */ - - function test_EdgeCase_RoleAdminOfItself() public { - accessControl.forceSetRoleAdmin(MINTER_ROLE, MINTER_ROLE); - accessControl.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * ALICE has MINTER_ROLE and MINTER_ROLE is its own admin - */ - vm.prank(ALICE); - accessControl.grantRole(MINTER_ROLE, BOB); - - assertTrue(accessControl.hasRole(MINTER_ROLE, BOB)); - } - - function test_EdgeCase_CircularRoleAdminHierarchy() public { - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - accessControl.forceSetRoleAdmin(PAUSER_ROLE, UPGRADER_ROLE); - accessControl.forceSetRoleAdmin(UPGRADER_ROLE, MINTER_ROLE); - - /** - * Grant MINTER_ROLE to ALICE (requires PAUSER_ROLE) - */ - accessControl.forceGrantRole(PAUSER_ROLE, BOB); - vm.prank(BOB); - accessControl.grantRole(MINTER_ROLE, ALICE); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_EdgeCase_GrantAndRevokeInSameBlock() public { - vm.startPrank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.revokeRole(MINTER_ROLE, ALICE); - vm.stopPrank(); - - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_EdgeCase_MultipleOperationsOnSameRole() public { - vm.startPrank(ADMIN); - - /** - * Grant to multiple accounts - */ - accessControl.grantRole(MINTER_ROLE, ALICE); - accessControl.grantRole(MINTER_ROLE, BOB); - accessControl.grantRole(MINTER_ROLE, CHARLIE); - - /** - * Revoke from one - */ - accessControl.revokeRole(MINTER_ROLE, BOB); - - vm.stopPrank(); - - /** - * BOB renounces (should have no effect since already revoked) - */ - vm.prank(BOB); - accessControl.renounceRole(MINTER_ROLE, BOB); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertFalse(accessControl.hasRole(MINTER_ROLE, BOB)); - assertTrue(accessControl.hasRole(MINTER_ROLE, CHARLIE)); - } - - /** - * ============================================ - * Storage Consistency Tests - * ============================================ - */ - - function test_StorageConsistency_HasRoleMatchesStorage() public { - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - assertTrue(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertTrue(accessControl.getStorageHasRole(ALICE, MINTER_ROLE)); - } - - function test_StorageConsistency_RoleAdminMatchesStorage() public { - accessControl.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - assertEq(accessControl.getRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - assertEq(accessControl.getStorageRoleAdmin(MINTER_ROLE), PAUSER_ROLE); - } - - function test_StorageSlot_UsesCorrectPosition() public view { - bytes32 expectedSlot = keccak256("compose.accesscontrol"); - assertEq(accessControl.getStoragePosition(), expectedSlot); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_GrantRole_OnlyAdminCanGrant(address caller, address account) public { - vm.assume(caller != ADMIN); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(caller); - accessControl.grantRole(MINTER_ROLE, account); - } - - function testFuzz_RevokeRole_OnlyAdminCanRevoke(address caller, address account) public { - vm.assume(caller != ADMIN); - - accessControl.forceGrantRole(MINTER_ROLE, account); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(caller); - accessControl.revokeRole(MINTER_ROLE, account); - } - - function testFuzz_RenounceRole_OnlyAccountCanRenounce(address caller, address account) public { - vm.assume(caller != account); - - accessControl.forceGrantRole(MINTER_ROLE, account); - - vm.expectRevert( - abi.encodeWithSelector(AccessControlFacet.AccessControlUnauthorizedSender.selector, caller, account) - ); - vm.prank(caller); - accessControl.renounceRole(MINTER_ROLE, account); - } - - function testFuzz_HasRole_ConsistentAcrossMultipleRoles(address account) public { - vm.assume(account != address(0)); - - bytes32[] memory roles = new bytes32[](10); - for (uint256 i = 0; i < roles.length; i++) { - roles[i] = keccak256(abi.encodePacked("ROLE_", i)); - } - - /** - * Grant all roles - */ - vm.startPrank(ADMIN); - for (uint256 i = 0; i < roles.length; i++) { - accessControl.grantRole(roles[i], account); - } - vm.stopPrank(); - - /** - * Verify all roles - */ - for (uint256 i = 0; i < roles.length; i++) { - assertTrue(accessControl.hasRole(roles[i], account)); - } - - /** - * Revoke half the roles - */ - vm.startPrank(ADMIN); - for (uint256 i = 0; i < roles.length; i += 2) { - accessControl.revokeRole(roles[i], account); - } - vm.stopPrank(); - - /** - * Verify correct roles are revoked - */ - for (uint256 i = 0; i < roles.length; i++) { - if (i % 2 == 0) { - assertFalse(accessControl.hasRole(roles[i], account)); - } else { - assertTrue(accessControl.hasRole(roles[i], account)); - } - } - } - - function testFuzz_RoleAdminHierarchy(bytes32 role1, bytes32 role2, bytes32 role3) public { - vm.assume(role1 != role2 && role2 != role3 && role1 != role3); - - /** - * Create hierarchy: role1 -> role2 -> role3 - */ - accessControl.forceSetRoleAdmin(role1, DEFAULT_ADMIN_ROLE); - accessControl.forceSetRoleAdmin(role2, role1); - accessControl.forceSetRoleAdmin(role3, role2); - - assertEq(accessControl.getRoleAdmin(role1), DEFAULT_ADMIN_ROLE); - assertEq(accessControl.getRoleAdmin(role2), role1); - assertEq(accessControl.getRoleAdmin(role3), role2); - - /** - * Grant roles in hierarchy - */ - vm.prank(ADMIN); - accessControl.grantRole(role1, ALICE); - - vm.prank(ALICE); - accessControl.grantRole(role2, BOB); - - vm.prank(BOB); - accessControl.grantRole(role3, CHARLIE); - - assertTrue(accessControl.hasRole(role1, ALICE)); - assertTrue(accessControl.hasRole(role2, BOB)); - assertTrue(accessControl.hasRole(role3, CHARLIE)); - } -} diff --git a/test/access/AccessControl/harnesses/AccessControlFacetHarness.sol b/test/access/AccessControl/harnesses/AccessControlFacetHarness.sol deleted file mode 100644 index cec0d47b..00000000 --- a/test/access/AccessControl/harnesses/AccessControlFacetHarness.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {AccessControlFacet} from "../../../../src/access/AccessControl/AccessControlFacet.sol"; - -/** - * @title AccessControlFacet Test Harness - * @notice Extends AccessControlFacet with initialization and test-specific functions - */ -contract AccessControlFacetHarness is AccessControlFacet { - /** - * @notice Initialize the DEFAULT_ADMIN_ROLE for testing - * @dev This function is only for testing purposes - */ - function initialize(address _admin) external { - AccessControlStorage storage s = getStorage(); - s.hasRole[_admin][DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Initialize with multiple admins for different roles - * @dev This sets up complex role hierarchies for testing - */ - function initializeWithRoles(address _defaultAdmin, bytes32 _role, address _roleAdmin, bytes32 _roleAdminRole) - external - { - AccessControlStorage storage s = getStorage(); - /** - * Set default admin - */ - s.hasRole[_defaultAdmin][DEFAULT_ADMIN_ROLE] = true; - /** - * Set up role hierarchy - */ - s.adminRole[_role] = _roleAdminRole; - s.hasRole[_roleAdmin][_roleAdminRole] = true; - } - - /** - * @notice Force grant a role without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlStorage storage s = getStorage(); - s.hasRole[_account][_role] = true; - } - - /** - * @notice Force revoke a role without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceRevokeRole(bytes32 _role, address _account) external { - AccessControlStorage storage s = getStorage(); - s.hasRole[_account][_role] = false; - } - - /** - * @notice Force set the admin role for a role without any checks - * @dev This bypasses all access control for testing purposes - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlStorage storage s = getStorage(); - s.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the raw storage hasRole value (for testing storage consistency) - */ - function getStorageHasRole(address _account, bytes32 _role) external view returns (bool) { - return getStorage().hasRole[_account][_role]; - } - - /** - * @notice Get the raw storage adminRole value (for testing storage consistency) - */ - function getStorageRoleAdmin(bytes32 _role) external view returns (bytes32) { - return getStorage().adminRole[_role]; - } - - /** - * @notice Setup a complex role hierarchy for testing - * @dev Creates multiple levels of roles with different admins - */ - function setupComplexRoleHierarchy() external { - AccessControlStorage storage s = getStorage(); - - /** - * Create role hierarchy: - * DEFAULT_ADMIN_ROLE -> ADMIN_ROLE -> MODERATOR_ROLE -> USER_ROLE - */ - bytes32 ADMIN_ROLE = keccak256("ADMIN_ROLE"); - bytes32 MODERATOR_ROLE = keccak256("MODERATOR_ROLE"); - bytes32 USER_ROLE = keccak256("USER_ROLE"); - - s.adminRole[ADMIN_ROLE] = DEFAULT_ADMIN_ROLE; - s.adminRole[MODERATOR_ROLE] = ADMIN_ROLE; - s.adminRole[USER_ROLE] = MODERATOR_ROLE; - } - - /** - * @notice Clear all roles for an account (for testing clean state) - */ - function clearAllRoles(address _account) external { - AccessControlStorage storage s = getStorage(); - - /** - * Clear some common test roles - */ - s.hasRole[_account][DEFAULT_ADMIN_ROLE] = false; - s.hasRole[_account][keccak256("ADMIN_ROLE")] = false; - s.hasRole[_account][keccak256("MODERATOR_ROLE")] = false; - s.hasRole[_account][keccak256("USER_ROLE")] = false; - s.hasRole[_account][keccak256("MINTER_ROLE")] = false; - s.hasRole[_account][keccak256("PAUSER_ROLE")] = false; - } - - /** - * @notice Get the DEFAULT_ADMIN_ROLE constant for testing - */ - function getDefaultAdminRole() external pure returns (bytes32) { - return DEFAULT_ADMIN_ROLE; - } - - /** - * @notice Get the STORAGE_POSITION constant for testing - */ - function getStoragePosition() external pure returns (bytes32) { - return STORAGE_POSITION; - } -} diff --git a/test/access/AccessControl/harnesses/AccessControlHarness.sol b/test/access/AccessControl/harnesses/AccessControlHarness.sol deleted file mode 100644 index dd64d71c..00000000 --- a/test/access/AccessControl/harnesses/AccessControlHarness.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../src/access/AccessControl/AccessControlMod.sol" as AccessControlMod; - -/** - * @title LibAccessControl Test Harness - * @notice Exposes internal LibAccessControl functions as external for testing - */ -contract AccessControlHarness { - /** - * @notice Initialize roles for testing - * @param _account The account to grant the default admin role to - */ - function initialize(address _account) external { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - s.hasRole[_account][AccessControlMod.DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Check if an account has a role - */ - function hasRole(bytes32 _role, address _account) external view returns (bool) { - return AccessControlMod.hasRole(_role, _account); - } - - /** - * @notice Require that an account has a role - */ - function requireRole(bytes32 _role, address _account) external view { - AccessControlMod.requireRole(_role, _account); - } - - /** - * @notice Set the admin role for a role - */ - function setRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlMod.setRoleAdmin(_role, _adminRole); - } - - /** - * @notice Grant a role to an account - */ - function grantRole(bytes32 _role, address _account) external returns (bool) { - return AccessControlMod.grantRole(_role, _account); - } - - /** - * @notice Revoke a role from an account - */ - function revokeRole(bytes32 _role, address _account) external returns (bool) { - return AccessControlMod.revokeRole(_role, _account); - } - - /** - * @notice Get the admin role for a role (for testing storage consistency) - */ - function getRoleAdmin(bytes32 _role) external view returns (bytes32) { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - return s.adminRole[_role]; - } - - /** - * @notice Get raw storage hasRole mapping (for testing storage consistency) - */ - function getStorageHasRole(address _account, bytes32 _role) external view returns (bool) { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - return s.hasRole[_account][_role]; - } - - /** - * @notice Force set a role without checks (for testing edge cases) - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - s.hasRole[_account][_role] = true; - } - - /** - * @notice Force revoke a role without checks (for testing edge cases) - */ - function forceRevokeRole(bytes32 _role, address _account) external { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - s.hasRole[_account][_role] = false; - } - - /** - * @notice Force set the admin role without checks or events (for testing edge cases) - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlMod.AccessControlStorage storage s = AccessControlMod.getStorage(); - s.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the DEFAULT_ADMIN_ROLE constant - */ - function getDefaultAdminRole() external pure returns (bytes32) { - return AccessControlMod.DEFAULT_ADMIN_ROLE; - } -} diff --git a/test/access/AccessControlPausable/AccessControlPausable.t.sol b/test/access/AccessControlPausable/AccessControlPausable.t.sol deleted file mode 100644 index 169286c2..00000000 --- a/test/access/AccessControlPausable/AccessControlPausable.t.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/access/AccessControlPausable/AccessControlPausableMod.sol" as AccessControlPausableMod; -import "../../../src/access/AccessControl/AccessControlMod.sol" as AccessControlMod; -import {AccessControlPausableHarness} from "./harnesses/AccessControlPausableHarness.sol"; -import {AccessControlHarness} from "../AccessControl/harnesses/AccessControlHarness.sol"; - -contract LibAccessControlPausableTest is Test { - AccessControlPausableHarness public harness; - AccessControlHarness public accessControl; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - /** - * Events - */ - event RolePaused(bytes32 indexed role, address indexed account); - event RoleUnpaused(bytes32 indexed role, address indexed account); - - function setUp() public { - /** - * Initialize AccessControl first (shared storage) - */ - accessControl = new AccessControlHarness(); - accessControl.initialize(ADMIN); - - /** - * Initialize Pausable harness (uses same AccessControl storage) - */ - harness = new AccessControlPausableHarness(); - harness.initialize(ADMIN); - } - - /** - * ============================================ - * IsRolePaused Tests - * ============================================ - */ - - function test_IsRolePaused_ReturnsFalseByDefault() public view { - assertFalse(harness.isRolePaused(MINTER_ROLE)); - } - - function test_IsRolePaused_ReturnsTrueWhenPaused() public { - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - assertTrue(harness.isRolePaused(MINTER_ROLE)); - assertTrue(harness.getStoragePaused(MINTER_ROLE)); - } - - /** - * ============================================ - * PauseRole Tests - * ============================================ - */ - - function test_PauseRole_EmitsRolePausedEvent() public { - vm.expectEmit(true, true, false, true); - emit RolePaused(MINTER_ROLE, address(harness)); - - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - } - - function test_PauseRole_CanBeCalledMultipleTimes() public { - vm.startPrank(address(harness)); - harness.pauseRole(MINTER_ROLE); - harness.pauseRole(MINTER_ROLE); - harness.pauseRole(MINTER_ROLE); - vm.stopPrank(); - - assertTrue(harness.isRolePaused(MINTER_ROLE)); - } - - /** - * ============================================ - * UnpauseRole Tests - * ============================================ - */ - - function test_UnpauseRole_EmitsRoleUnpausedEvent() public { - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - vm.expectEmit(true, true, false, true); - emit RoleUnpaused(MINTER_ROLE, address(harness)); - - vm.prank(address(harness)); - harness.unpauseRole(MINTER_ROLE); - } - - function test_UnpauseRole_CanBeCalledMultipleTimes() public { - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - vm.startPrank(address(harness)); - harness.unpauseRole(MINTER_ROLE); - harness.unpauseRole(MINTER_ROLE); - harness.unpauseRole(MINTER_ROLE); - vm.stopPrank(); - - assertFalse(harness.isRolePaused(MINTER_ROLE)); - } - - function test_PauseUnpauseCycle_MultipleCycles() public { - vm.startPrank(address(harness)); - - /** - * First cycle - */ - harness.pauseRole(MINTER_ROLE); - assertTrue(harness.isRolePaused(MINTER_ROLE)); - - harness.unpauseRole(MINTER_ROLE); - assertFalse(harness.isRolePaused(MINTER_ROLE)); - - /** - * Second cycle - */ - harness.pauseRole(MINTER_ROLE); - assertTrue(harness.isRolePaused(MINTER_ROLE)); - - harness.unpauseRole(MINTER_ROLE); - assertFalse(harness.isRolePaused(MINTER_ROLE)); - - vm.stopPrank(); - } - - /** - * ============================================ - * RequireRoleNotPaused Tests - * ============================================ - */ - - function test_RequireRoleNotPaused_PassesWhenRoleNotPaused() public { - /** - * Grant role to Alice - */ - harness.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Should pass (role exists and not paused) - */ - harness.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRoleNotPaused_RoleIsPaused() public { - /** - * Grant role to Alice - */ - harness.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Pause the role - */ - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - /** - * Should revert - */ - vm.expectRevert(abi.encodeWithSelector(AccessControlPausableMod.AccessControlRolePaused.selector, MINTER_ROLE)); - harness.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRoleNotPaused_AccountDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlPausableMod.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - harness.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RequireRoleNotPaused_AfterUnpause() public { - /** - * Grant role to Alice - */ - harness.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Pause - */ - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - /** - * Unpause - */ - vm.prank(address(harness)); - harness.unpauseRole(MINTER_ROLE); - - /** - * Should pass now - */ - harness.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_PauseRole_MultipleRolesCanBePaused() public { - vm.startPrank(address(harness)); - harness.pauseRole(MINTER_ROLE); - harness.pauseRole(PAUSER_ROLE); - vm.stopPrank(); - - assertTrue(harness.isRolePaused(MINTER_ROLE)); - assertTrue(harness.isRolePaused(PAUSER_ROLE)); - } - - /** - * ============================================ - * Storage Consistency Tests - * ============================================ - */ - - function test_StorageConsistency_PauseRole() public { - vm.prank(address(harness)); - harness.pauseRole(MINTER_ROLE); - - assertTrue(harness.isRolePaused(MINTER_ROLE)); - assertTrue(harness.getStoragePaused(MINTER_ROLE)); - } - - function test_StorageConsistency_UnpauseRole() public { - vm.startPrank(address(harness)); - harness.pauseRole(MINTER_ROLE); - assertTrue(harness.getStoragePaused(MINTER_ROLE)); - - harness.unpauseRole(MINTER_ROLE); - assertFalse(harness.getStoragePaused(MINTER_ROLE)); - vm.stopPrank(); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_PauseUnpauseCycle_ConsistentState(bytes32 role) public { - vm.startPrank(address(harness)); - - /** - * Pause - */ - harness.pauseRole(role); - assertTrue(harness.isRolePaused(role)); - - /** - * Unpause - */ - harness.unpauseRole(role); - assertFalse(harness.isRolePaused(role)); - - vm.stopPrank(); - } -} diff --git a/test/access/AccessControlPausable/AccessControlPausableFacet.t.sol b/test/access/AccessControlPausable/AccessControlPausableFacet.t.sol deleted file mode 100644 index 4a88cc31..00000000 --- a/test/access/AccessControlPausable/AccessControlPausableFacet.t.sol +++ /dev/null @@ -1,330 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {AccessControlPausableFacet} from "../../../src/access/AccessControlPausable/AccessControlPausableFacet.sol"; -import {AccessControlFacet} from "../../../src/access/AccessControl/AccessControlFacet.sol"; -import {AccessControlPausableFacetHarness} from "./harnesses/AccessControlPausableFacetHarness.sol"; -import {AccessControlFacetHarness} from "../AccessControl/harnesses/AccessControlFacetHarness.sol"; - -contract AccessControlPausableFacetTest is Test { - AccessControlPausableFacetHarness public pausableFacet; - AccessControlFacetHarness public accessControl; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address CHARLIE = makeAddr("charlie"); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); - - /** - * Events - */ - event RolePaused(bytes32 indexed role, address indexed account); - event RoleUnpaused(bytes32 indexed role, address indexed account); - event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender); - - function setUp() public { - /** - * Initialize AccessControl first (shared storage) - */ - accessControl = new AccessControlFacetHarness(); - accessControl.initialize(ADMIN); - - /** - * Initialize PausableFacet (uses same AccessControl storage) - */ - pausableFacet = new AccessControlPausableFacetHarness(); - pausableFacet.initialize(ADMIN); - } - - /** - * ============================================ - * IsRolePaused Tests - * ============================================ - */ - - function test_IsRolePaused_ReturnsFalseByDefault() public view { - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - function test_IsRolePaused_ReturnsTrueWhenPaused() public { - /** - * Grant role first - */ - vm.prank(ADMIN); - accessControl.grantRole(MINTER_ROLE, ALICE); - - /** - * Pause the role - */ - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - /** - * ============================================ - * PauseRole Tests - * ============================================ - */ - - function test_PauseRole_SucceedsWhenCallerIsAdmin() public { - vm.expectEmit(true, true, false, true); - emit RolePaused(MINTER_ROLE, ADMIN); - - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - assertTrue(pausableFacet.getStoragePaused(MINTER_ROLE)); - } - - function test_PauseRole_CanBeCalledMultipleTimes() public { - vm.startPrank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - pausableFacet.pauseRole(MINTER_ROLE); - pausableFacet.pauseRole(MINTER_ROLE); - vm.stopPrank(); - - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - function test_RevertWhen_PauseRole_CallerIsNotAdmin() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - pausableFacet.pauseRole(MINTER_ROLE); - } - - function test_PauseRole_MultipleRolesCanBePaused() public { - vm.startPrank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - pausableFacet.pauseRole(PAUSER_ROLE); - vm.stopPrank(); - - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - assertTrue(pausableFacet.isRolePaused(PAUSER_ROLE)); - } - - /** - * ============================================ - * UnpauseRole Tests - * ============================================ - */ - - function test_UnpauseRole_SucceedsWhenCallerIsAdmin() public { - /** - * First pause - */ - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - - /** - * Then unpause - */ - vm.expectEmit(true, true, false, true); - emit RoleUnpaused(MINTER_ROLE, ADMIN); - - vm.prank(ADMIN); - pausableFacet.unpauseRole(MINTER_ROLE); - - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - function test_UnpauseRole_CanBeCalledMultipleTimes() public { - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - vm.startPrank(ADMIN); - pausableFacet.unpauseRole(MINTER_ROLE); - pausableFacet.unpauseRole(MINTER_ROLE); - pausableFacet.unpauseRole(MINTER_ROLE); - vm.stopPrank(); - - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - function test_RevertWhen_UnpauseRole_CallerIsNotAdmin() public { - /** - * First pause with admin - */ - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - /** - * Then try to unpause without admin role - */ - vm.expectRevert( - abi.encodeWithSelector( - AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - pausableFacet.unpauseRole(MINTER_ROLE); - } - - function test_PauseUnpauseCycle_MultipleCycles() public { - vm.startPrank(ADMIN); - - /** - * First cycle - */ - pausableFacet.pauseRole(MINTER_ROLE); - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - - pausableFacet.unpauseRole(MINTER_ROLE); - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - - /** - * Second cycle - */ - pausableFacet.pauseRole(MINTER_ROLE); - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - - pausableFacet.unpauseRole(MINTER_ROLE); - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - - vm.stopPrank(); - } - - /** - * ============================================ - * RequireRoleNotPaused Tests - * ============================================ - */ - - function test_RequireRoleNotPaused_PassesWhenRoleNotPaused() public { - /** - * Grant role to Alice using harness (direct storage manipulation) - */ - pausableFacet.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Should pass (role exists and not paused) - */ - pausableFacet.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRoleNotPaused_RoleIsPaused() public { - /** - * Grant role to Alice using harness - */ - pausableFacet.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Pause the role - */ - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - /** - * Should revert - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlPausableFacet.AccessControlRolePaused.selector, MINTER_ROLE) - ); - pausableFacet.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireRoleNotPaused_AccountDoesNotHaveRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - pausableFacet.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - function test_RequireRoleNotPaused_AfterUnpause() public { - /** - * Grant role to Alice using harness - */ - pausableFacet.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Pause - */ - vm.prank(ADMIN); - pausableFacet.pauseRole(MINTER_ROLE); - - /** - * Unpause - */ - vm.prank(ADMIN); - pausableFacet.unpauseRole(MINTER_ROLE); - - /** - * Should pass now - */ - pausableFacet.requireRoleNotPaused(MINTER_ROLE, ALICE); - } - - /** - * ============================================ - * Custom Admin Role Tests - * ============================================ - */ - - function test_PauseRole_WithCustomRoleAdmin() public { - /** - * Set up custom admin using harness - */ - pausableFacet.forceGrantRole(PAUSER_ROLE, BOB); - pausableFacet.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - /** - * Bob can pause MINTER_ROLE - */ - vm.expectEmit(true, true, false, true); - emit RolePaused(MINTER_ROLE, BOB); - - vm.prank(BOB); - pausableFacet.pauseRole(MINTER_ROLE); - - assertTrue(pausableFacet.isRolePaused(MINTER_ROLE)); - } - - function test_UnpauseRole_WithCustomRoleAdmin() public { - /** - * Set up custom admin using harness - */ - pausableFacet.forceGrantRole(PAUSER_ROLE, BOB); - pausableFacet.forceSetRoleAdmin(MINTER_ROLE, PAUSER_ROLE); - - /** - * Pause with Bob - */ - vm.prank(BOB); - pausableFacet.pauseRole(MINTER_ROLE); - - /** - * Unpause with Bob - */ - vm.expectEmit(true, true, false, true); - emit RoleUnpaused(MINTER_ROLE, BOB); - - vm.prank(BOB); - pausableFacet.unpauseRole(MINTER_ROLE); - - assertFalse(pausableFacet.isRolePaused(MINTER_ROLE)); - } -} diff --git a/test/access/AccessControlPausable/harnesses/AccessControlPausableFacetHarness.sol b/test/access/AccessControlPausable/harnesses/AccessControlPausableFacetHarness.sol deleted file mode 100644 index 0eed4860..00000000 --- a/test/access/AccessControlPausable/harnesses/AccessControlPausableFacetHarness.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {AccessControlPausableFacet} from "../../../../src/access/AccessControlPausable/AccessControlPausableFacet.sol"; - -/** - * @title AccessControlPausableFacet Test Harness - * @notice Extends AccessControlPausableFacet with initialization and test-specific functions - */ -contract AccessControlPausableFacetHarness is AccessControlPausableFacet { - /** - * @notice Initialize the DEFAULT_ADMIN_ROLE for testing - * @dev This function is only for testing purposes - */ - function initialize(address _admin) external { - AccessControlStorage storage acs = getAccessControlStorage(); - bytes32 DEFAULT_ADMIN_ROLE = 0x00; - acs.hasRole[_admin][DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Force grant a role without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlStorage storage acs = getAccessControlStorage(); - acs.hasRole[_account][_role] = true; - } - - /** - * @notice Force set the admin role for a role without any checks - * @dev This bypasses all access control for testing purposes - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlStorage storage acs = getAccessControlStorage(); - acs.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the raw storage pausedRoles value (for testing storage consistency) - */ - function getStoragePaused(bytes32 _role) external view returns (bool) { - return getStorage().pausedRoles[_role]; - } -} diff --git a/test/access/AccessControlPausable/harnesses/AccessControlPausableHarness.sol b/test/access/AccessControlPausable/harnesses/AccessControlPausableHarness.sol deleted file mode 100644 index d17fd4dc..00000000 --- a/test/access/AccessControlPausable/harnesses/AccessControlPausableHarness.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../src/access/AccessControlPausable/AccessControlPausableMod.sol" as AccessControlPausableMod; - -/** - * @title LibAccessControlPausable Test Harness - * @notice Exposes internal LibAccessControlPausable functions as external for testing - */ -contract AccessControlPausableHarness { - /** - * @notice Initialize roles for testing - * @param _account The account to grant the default admin role to - */ - function initialize(address _account) external { - AccessControlPausableMod.AccessControlStorage storage acs = AccessControlPausableMod.getAccessControlStorage(); - bytes32 DEFAULT_ADMIN_ROLE = 0x00; - acs.hasRole[_account][DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Check if a role is paused - */ - function isRolePaused(bytes32 _role) external view returns (bool) { - return AccessControlPausableMod.isRolePaused(_role); - } - - /** - * @notice Pause a role - */ - function pauseRole(bytes32 _role) external { - AccessControlPausableMod.pauseRole(_role); - } - - /** - * @notice Unpause a role - */ - function unpauseRole(bytes32 _role) external { - AccessControlPausableMod.unpauseRole(_role); - } - - /** - * @notice Require that a role is not paused - */ - function requireRoleNotPaused(bytes32 _role, address _account) external view { - AccessControlPausableMod.requireRoleNotPaused(_role, _account); - } - - /** - * @notice Force grant a role without any checks (for testing edge cases) - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlPausableMod.AccessControlStorage storage acs = AccessControlPausableMod.getAccessControlStorage(); - acs.hasRole[_account][_role] = true; - } - - /** - * @notice Force set the admin role without checks (for testing edge cases) - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlPausableMod.AccessControlStorage storage acs = AccessControlPausableMod.getAccessControlStorage(); - acs.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the raw storage pausedRoles value (for testing storage consistency) - */ - function getStoragePaused(bytes32 _role) external view returns (bool) { - return AccessControlPausableMod.getStorage().pausedRoles[_role]; - } -} diff --git a/test/access/AccessControlTemporal/AccessControlTemporal.t.sol b/test/access/AccessControlTemporal/AccessControlTemporal.t.sol deleted file mode 100644 index dfe52759..00000000 --- a/test/access/AccessControlTemporal/AccessControlTemporal.t.sol +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/access/AccessControlTemporal/AccessControlTemporalMod.sol" as AccessControlTemporalMod; -import "../../../src/access/AccessControl/AccessControlMod.sol" as AccessControlMod; -import {AccessControlTemporalHarness} from "./harnesses/AccessControlTemporalHarness.sol"; -import {AccessControlHarness} from "../AccessControl/harnesses/AccessControlHarness.sol"; - -contract LibAccessControlTemporalTest is Test { - AccessControlTemporalHarness public harness; - AccessControlHarness public accessControl; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - - /** - * Events - */ - event RoleGrantedWithExpiry( - bytes32 indexed role, address indexed account, uint256 expiresAt, address indexed sender - ); - - function setUp() public { - /** - * Initialize AccessControl first (shared storage) - */ - accessControl = new AccessControlHarness(); - accessControl.initialize(ADMIN); - - /** - * Initialize Temporal harness (uses same AccessControl storage) - */ - harness = new AccessControlTemporalHarness(); - harness.initialize(ADMIN); - } - - /** - * ============================================ - * GetRoleExpiry Tests - * ============================================ - */ - - function test_GetRoleExpiry_ReturnsZeroForNonExistentRole() public view { - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), 0); - } - - function test_GetRoleExpiry_ReturnsExpiryWhenSet() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - assertEq(harness.getStorageExpiry(ALICE, MINTER_ROLE), expiry); - } - - /** - * ============================================ - * IsRoleExpired Tests - * ============================================ - */ - - function test_IsRoleExpired_ReturnsTrueForNoRole() public view { - assertTrue(harness.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsFalseForFutureExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertFalse(harness.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsTrueForPastExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Fast forward past expiry - */ - vm.warp(expiry + 1); - - assertTrue(harness.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsTrueAtExactExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Set time to exactly expiry - */ - vm.warp(expiry); - - /** - * At exact expiry time, role should be expired - */ - assertTrue(harness.isRoleExpired(MINTER_ROLE, ALICE)); - } - - /** - * ============================================ - * GrantRoleWithExpiry Tests - * ============================================ - */ - - function test_GrantRoleWithExpiry_GrantsRoleAndSetsExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.expectEmit(true, true, true, true); - emit RoleGrantedWithExpiry(MINTER_ROLE, ALICE, expiry, address(harness)); - - vm.prank(address(harness)); - bool granted = harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertTrue(granted); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - } - - function test_GrantRoleWithExpiry_CanUpdateExpiry() public { - uint256 expiry1 = block.timestamp + 7 days; - uint256 expiry2 = block.timestamp + 14 days; - - vm.startPrank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry1); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry1); - - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry2); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry2); - vm.stopPrank(); - } - - /** - * ============================================ - * RevokeTemporalRole Tests - * ============================================ - */ - - function test_RevokeTemporalRole_RevokesRoleAndClearsExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - vm.stopPrank(); - - vm.prank(address(harness)); - bool revoked = harness.revokeTemporalRole(MINTER_ROLE, ALICE); - - assertTrue(revoked); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), 0); - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeTemporalRole_ReturnsFalseWhenNoRole() public { - vm.prank(address(harness)); - bool revoked = harness.revokeTemporalRole(MINTER_ROLE, ALICE); - - assertFalse(revoked); - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), 0); - } - - /** - * ============================================ - * RequireValidRole Tests - * ============================================ - */ - - function test_RequireValidRole_PassesWithValidExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Should not revert - */ - harness.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RequireValidRole_PassesWithoutExpiry() public { - /** - * Grant role without expiry - */ - harness.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Should not revert (no expiry set means valid) - */ - harness.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireValidRole_Expired() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Fast forward past expiry - */ - vm.warp(expiry + 1); - - vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalMod.AccessControlRoleExpired.selector, MINTER_ROLE, ALICE) - ); - harness.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireValidRole_NoRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalMod.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - harness.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RequireValidRole_AfterRevoke() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - harness.revokeTemporalRole(MINTER_ROLE, ALICE); - vm.stopPrank(); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalMod.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - harness.requireValidRole(MINTER_ROLE, ALICE); - } - - /** - * ============================================ - * Storage Consistency Tests - * ============================================ - */ - - function test_StorageConsistency_GrantRoleWithExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertEq(harness.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - assertEq(harness.getStorageExpiry(ALICE, MINTER_ROLE), expiry); - } - - function test_StorageConsistency_RevokeTemporalRole() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(address(harness)); - harness.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - assertEq(harness.getStorageExpiry(ALICE, MINTER_ROLE), expiry); - - harness.revokeTemporalRole(MINTER_ROLE, ALICE); - assertEq(harness.getStorageExpiry(ALICE, MINTER_ROLE), 0); - vm.stopPrank(); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_GrantRoleWithExpiry_AlwaysSetsExpiry(address account, bytes32 role, uint256 expiryOffset) public { - vm.assume(account != address(0)); - vm.assume(expiryOffset > 0); // Must be in the future - vm.assume(expiryOffset <= 365 days); // Reasonable expiry window - uint256 expiry = block.timestamp + expiryOffset; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(role, account, expiry); - - assertEq(harness.getRoleExpiry(role, account), expiry); - } - - function testFuzz_IsRoleExpired_ConsistentWithExpiry(address account, bytes32 role, uint256 expiryOffset) public { - vm.assume(account != address(0)); - vm.assume(expiryOffset > 0); // Must be in the future - vm.assume(expiryOffset <= 365 days); - uint256 expiry = block.timestamp + expiryOffset; - - vm.prank(address(harness)); - harness.grantRoleWithExpiry(role, account, expiry); - - /** - * Before expiry - */ - assertFalse(harness.isRoleExpired(role, account)); - - /** - * After expiry - */ - vm.warp(expiry + 1); - assertTrue(harness.isRoleExpired(role, account)); - } -} diff --git a/test/access/AccessControlTemporal/AccessControlTemporalFacet.t.sol b/test/access/AccessControlTemporal/AccessControlTemporalFacet.t.sol deleted file mode 100644 index fe5449c3..00000000 --- a/test/access/AccessControlTemporal/AccessControlTemporalFacet.t.sol +++ /dev/null @@ -1,373 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {AccessControlTemporalFacet} from "../../../src/access/AccessControlTemporal/AccessControlTemporalFacet.sol"; -import {AccessControlFacet} from "../../../src/access/AccessControl/AccessControlFacet.sol"; -import {AccessControlTemporalFacetHarness} from "./harnesses/AccessControlTemporalFacetHarness.sol"; -import {AccessControlFacetHarness} from "../AccessControl/harnesses/AccessControlFacetHarness.sol"; - -contract AccessControlTemporalFacetTest is Test { - AccessControlTemporalFacetHarness public temporalFacet; - AccessControlFacetHarness public accessControl; - - /** - * Test addresses - */ - address ADMIN = makeAddr("admin"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - - /** - * Test roles - */ - bytes32 constant DEFAULT_ADMIN_ROLE = 0x00; - bytes32 constant MINTER_ROLE = keccak256("MINTER_ROLE"); - - /** - * Events - */ - event RoleGrantedWithExpiry( - bytes32 indexed role, address indexed account, uint256 expiresAt, address indexed sender - ); - event TemporalRoleRevoked(bytes32 indexed role, address indexed account, address indexed sender); - - function setUp() public { - /** - * Initialize AccessControl first (shared storage) - */ - accessControl = new AccessControlFacetHarness(); - accessControl.initialize(ADMIN); - - /** - * Initialize TemporalFacet (uses same AccessControl storage) - */ - temporalFacet = new AccessControlTemporalFacetHarness(); - temporalFacet.initialize(ADMIN); - } - - /** - * ============================================ - * GetRoleExpiry Tests - * ============================================ - */ - - function test_GetRoleExpiry_ReturnsZeroForNonExistentRole() public view { - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), 0); - } - - function test_GetRoleExpiry_ReturnsExpiryWhenSet() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - assertEq(temporalFacet.getStorageExpiry(ALICE, MINTER_ROLE), expiry); - } - - /** - * ============================================ - * IsRoleExpired Tests - * ============================================ - */ - - function test_IsRoleExpired_ReturnsTrueForNoRole() public view { - assertTrue(temporalFacet.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsFalseForFutureExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertFalse(temporalFacet.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsTrueForPastExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Fast forward past expiry - */ - vm.warp(expiry + 1); - - assertTrue(temporalFacet.isRoleExpired(MINTER_ROLE, ALICE)); - } - - function test_IsRoleExpired_ReturnsTrueAtExactExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Set time to exactly expiry - */ - vm.warp(expiry); - - /** - * At exact expiry time, role should be expired - */ - assertTrue(temporalFacet.isRoleExpired(MINTER_ROLE, ALICE)); - } - - /** - * ============================================ - * GrantRoleWithExpiry Tests - * ============================================ - */ - - function test_GrantRoleWithExpiry_SucceedsWithFutureExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.expectEmit(true, true, true, true); - emit RoleGrantedWithExpiry(MINTER_ROLE, ALICE, expiry, ADMIN); - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - } - - function test_GrantRoleWithExpiry_CanUpdateExpiry() public { - uint256 expiry1 = block.timestamp + 7 days; - uint256 expiry2 = block.timestamp + 14 days; - - vm.startPrank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry1); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), expiry1); - - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry2); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), expiry2); - vm.stopPrank(); - } - - function test_RevertWhen_GrantRoleWithExpiry_PastExpiry() public { - uint256 pastExpiry = block.timestamp - 1; - - vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalFacet.AccessControlRoleExpired.selector, MINTER_ROLE, ALICE) - ); - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, pastExpiry); - } - - function test_RevertWhen_GrantRoleWithExpiry_CurrentTimestamp() public { - uint256 currentTime = block.timestamp; - - vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalFacet.AccessControlRoleExpired.selector, MINTER_ROLE, ALICE) - ); - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, currentTime); - } - - function test_RevertWhen_GrantRoleWithExpiry_CallerIsNotAdmin() public { - uint256 expiry = block.timestamp + 7 days; - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalFacet.AccessControlUnauthorizedAccount.selector, ALICE, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(ALICE); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, BOB, expiry); - } - - /** - * ============================================ - * RevokeTemporalRole Tests - * ============================================ - */ - - function test_RevokeTemporalRole_Succeeds() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), expiry); - vm.stopPrank(); - - vm.expectEmit(true, true, true, true); - emit TemporalRoleRevoked(MINTER_ROLE, ALICE, ADMIN); - - vm.prank(ADMIN); - temporalFacet.revokeTemporalRole(MINTER_ROLE, ALICE); - - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), 0); - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - } - - function test_RevokeTemporalRole_ClearExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - temporalFacet.revokeTemporalRole(MINTER_ROLE, ALICE); - vm.stopPrank(); - - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), 0); - } - - function test_RevokeTemporalRole_NoExistingRole_DoesNothing() public { - vm.prank(ADMIN); - temporalFacet.revokeTemporalRole(MINTER_ROLE, ALICE); - - /** - * Ensure no state changes - */ - assertFalse(accessControl.hasRole(MINTER_ROLE, ALICE)); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), 0); - } - - function test_RevertWhen_RevokeTemporalRole_CallerIsNotAdmin() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalFacet.AccessControlUnauthorizedAccount.selector, BOB, DEFAULT_ADMIN_ROLE - ) - ); - vm.prank(BOB); - temporalFacet.revokeTemporalRole(MINTER_ROLE, ALICE); - } - - /** - * ============================================ - * RequireValidRole Tests - * ============================================ - */ - - function test_RequireValidRole_PassesWithValidExpiry() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Should not revert - */ - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RequireValidRole_PassesWithoutExpiry() public { - /** - * Grant role without expiry using harness (direct storage manipulation) - */ - temporalFacet.forceGrantRole(MINTER_ROLE, ALICE); - - /** - * Should not revert (no expiry set means valid) - */ - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireValidRole_Expired() public { - uint256 expiry = block.timestamp + 7 days; - - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - - /** - * Fast forward past expiry - */ - vm.warp(expiry + 1); - - vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalFacet.AccessControlRoleExpired.selector, MINTER_ROLE, ALICE) - ); - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RevertWhen_RequireValidRole_NoRole() public { - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalFacet.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_RequireValidRole_AfterRevoke() public { - uint256 expiry = block.timestamp + 7 days; - - vm.startPrank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, expiry); - temporalFacet.revokeTemporalRole(MINTER_ROLE, ALICE); - vm.stopPrank(); - - vm.expectRevert( - abi.encodeWithSelector( - AccessControlTemporalFacet.AccessControlUnauthorizedAccount.selector, ALICE, MINTER_ROLE - ) - ); - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - /** - * ============================================ - * Scenario Tests - * ============================================ - */ - - function test_Scenario_TemporaryContractorAccess() public { - uint256 contractEnd = block.timestamp + 30 days; - - /** - * Grant contractor role that expires in 30 days - */ - vm.prank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, contractEnd); - - /** - * Verify access works now - */ - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - - /** - * Fast forward past contract end - */ - vm.warp(contractEnd + 1); - - /** - * Access should be denied - */ - vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalFacet.AccessControlRoleExpired.selector, MINTER_ROLE, ALICE) - ); - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } - - function test_Scenario_ExtendExpiryBeforeExpiration() public { - uint256 initialExpiry = block.timestamp + 7 days; - uint256 newExpiry = block.timestamp + 14 days; - - vm.startPrank(ADMIN); - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, initialExpiry); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), initialExpiry); - - /** - * Extend before expiration - */ - temporalFacet.grantRoleWithExpiry(MINTER_ROLE, ALICE, newExpiry); - assertEq(temporalFacet.getRoleExpiry(MINTER_ROLE, ALICE), newExpiry); - vm.stopPrank(); - - /** - * Access should still work - */ - temporalFacet.requireValidRole(MINTER_ROLE, ALICE); - } -} diff --git a/test/access/AccessControlTemporal/harnesses/AccessControlTemporalFacetHarness.sol b/test/access/AccessControlTemporal/harnesses/AccessControlTemporalFacetHarness.sol deleted file mode 100644 index e001a05e..00000000 --- a/test/access/AccessControlTemporal/harnesses/AccessControlTemporalFacetHarness.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {AccessControlTemporalFacet} from "../../../../src/access/AccessControlTemporal/AccessControlTemporalFacet.sol"; - -/** - * @title AccessControlTemporalFacet Test Harness - * @notice Extends AccessControlTemporalFacet with initialization and test-specific functions - */ -contract AccessControlTemporalFacetHarness is AccessControlTemporalFacet { - /** - * @notice Initialize the DEFAULT_ADMIN_ROLE for testing - * @dev This function is only for testing purposes - */ - function initialize(address _admin) external { - AccessControlStorage storage acs = getAccessControlStorage(); - bytes32 DEFAULT_ADMIN_ROLE = 0x00; - acs.hasRole[_admin][DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Force grant a role without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlStorage storage acs = getAccessControlStorage(); - acs.hasRole[_account][_role] = true; - } - - /** - * @notice Force set the admin role for a role without any checks - * @dev This bypasses all access control for testing purposes - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlStorage storage acs = getAccessControlStorage(); - acs.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the raw storage roleExpiry value (for testing storage consistency) - */ - function getStorageExpiry(address _account, bytes32 _role) external view returns (uint256) { - return getStorage().roleExpiry[_account][_role]; - } -} diff --git a/test/access/AccessControlTemporal/harnesses/AccessControlTemporalHarness.sol b/test/access/AccessControlTemporal/harnesses/AccessControlTemporalHarness.sol deleted file mode 100644 index 2cd8cfc3..00000000 --- a/test/access/AccessControlTemporal/harnesses/AccessControlTemporalHarness.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../src/access/AccessControlTemporal/AccessControlTemporalMod.sol" as AccessControlTemporalMod; - -/** - * @title LibAccessControlTemporal Test Harness - * @notice Exposes internal LibAccessControlTemporal functions as external for testing - */ -contract AccessControlTemporalHarness { - /** - * @notice Initialize roles for testing - * @param _account The account to grant the default admin role to - */ - function initialize(address _account) external { - AccessControlTemporalMod.AccessControlStorage storage acs = AccessControlTemporalMod.getAccessControlStorage(); - bytes32 DEFAULT_ADMIN_ROLE = 0x00; - acs.hasRole[_account][DEFAULT_ADMIN_ROLE] = true; - } - - /** - * @notice Get the expiry timestamp for a role assignment - */ - function getRoleExpiry(bytes32 _role, address _account) external view returns (uint256) { - return AccessControlTemporalMod.getRoleExpiry(_role, _account); - } - - /** - * @notice Check if a role assignment has expired - */ - function isRoleExpired(bytes32 _role, address _account) external view returns (bool) { - return AccessControlTemporalMod.isRoleExpired(_role, _account); - } - - /** - * @notice Grant a role with an expiry timestamp - */ - function grantRoleWithExpiry(bytes32 _role, address _account, uint256 _expiresAt) external returns (bool) { - return AccessControlTemporalMod.grantRoleWithExpiry(_role, _account, _expiresAt); - } - - /** - * @notice Revoke a temporal role - */ - function revokeTemporalRole(bytes32 _role, address _account) external returns (bool) { - return AccessControlTemporalMod.revokeTemporalRole(_role, _account); - } - - /** - * @notice Require that an account has a valid (non-expired) role - */ - function requireValidRole(bytes32 _role, address _account) external view { - AccessControlTemporalMod.requireValidRole(_role, _account); - } - - /** - * @notice Force grant a role without any checks (for testing edge cases) - */ - function forceGrantRole(bytes32 _role, address _account) external { - AccessControlTemporalMod.AccessControlStorage storage acs = AccessControlTemporalMod.getAccessControlStorage(); - acs.hasRole[_account][_role] = true; - } - - /** - * @notice Force set the admin role without checks (for testing edge cases) - */ - function forceSetRoleAdmin(bytes32 _role, bytes32 _adminRole) external { - AccessControlTemporalMod.AccessControlStorage storage acs = AccessControlTemporalMod.getAccessControlStorage(); - acs.adminRole[_role] = _adminRole; - } - - /** - * @notice Get the raw storage roleExpiry value (for testing storage consistency) - */ - function getStorageExpiry(address _account, bytes32 _role) external view returns (uint256) { - return AccessControlTemporalMod.getStorage().roleExpiry[_account][_role]; - } -} diff --git a/test/access/Owner/Owner.t.sol b/test/access/Owner/Owner.t.sol deleted file mode 100644 index ccb2b34b..00000000 --- a/test/access/Owner/Owner.t.sol +++ /dev/null @@ -1,329 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/access/Owner/OwnerMod.sol" as OwnerMod; -import {OwnerHarness} from "./harnesses/OwnerHarness.sol"; - -contract LibOwnerTest is Test { - OwnerHarness public harness; - - address INITIAL_OWNER = makeAddr("owner"); - address NEW_OWNER = makeAddr("newOwner"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address ZERO_ADDRESS = address(0); - - /** - * Events - */ - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - function setUp() public { - harness = new OwnerHarness(); - harness.initialize(INITIAL_OWNER); - } - - /** - * ============================================ - * Storage Tests - * ============================================ - */ - - function test_GetStorage_ReturnsCorrectOwner() public view { - assertEq(harness.owner(), INITIAL_OWNER); - assertEq(harness.getStorageOwner(), INITIAL_OWNER); - } - - function test_StorageSlot_UsesCorrectPosition() public { - bytes32 expectedSlot = keccak256("compose.owner"); - - /** - * Change owner - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - /** - * Read directly from storage - */ - bytes32 storedValue = vm.load(address(harness), expectedSlot); - address storedOwner = address(uint160(uint256(storedValue))); - - assertEq(storedOwner, NEW_OWNER); - assertEq(harness.owner(), NEW_OWNER); - } - - /** - * ============================================ - * Owner Getter Tests - * ============================================ - */ - - function test_Owner_ReturnsCurrentOwner() public { - assertEq(harness.owner(), INITIAL_OWNER); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.owner(), NEW_OWNER); - } - - function test_Owner_ReturnsZeroAfterRenounce() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.owner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Transfer Ownership Tests - * ============================================ - */ - - function test_TransferOwnership_UpdatesOwner() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.owner(), NEW_OWNER); - } - - function test_TransferOwnership_EmitsOwnershipTransferredEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, NEW_OWNER); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - } - - function test_TransferOwnership_AllowsTransferToZeroAddress() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.owner(), ZERO_ADDRESS); - } - - function test_TransferOwnership_AllowsTransferToSelf() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(INITIAL_OWNER); - assertEq(harness.owner(), INITIAL_OWNER); - } - - function test_RevertWhen_TransferOwnership_FromRenouncedOwner() public { - /** - * Force renounce - */ - harness.forceRenounce(); - assertEq(harness.owner(), ZERO_ADDRESS); - - /** - * Should revert with OwnerAlreadyRenounced error - */ - vm.expectRevert(OwnerMod.OwnerAlreadyRenounced.selector); - harness.transferOwnership(NEW_OWNER); - } - - /** - * ============================================ - * Sequential Transfer Tests - * ============================================ - */ - - function test_MultipleTransfers() public { - /** - * First transfer - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ALICE); - assertEq(harness.owner(), ALICE); - - /** - * Second transfer - */ - vm.prank(ALICE); - harness.transferOwnership(BOB); - assertEq(harness.owner(), BOB); - - /** - * Third transfer - */ - vm.prank(BOB); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.owner(), NEW_OWNER); - } - - /** - * ============================================ - * Event Tests - * ============================================ - */ - - function test_Events_CorrectPreviousOwner() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ALICE); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ALICE); - - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(ALICE, BOB); - - vm.prank(ALICE); - harness.transferOwnership(BOB); - } - - function test_Events_RenounceEmitsZeroAddress() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_RenounceOwnership_PermanentlyDisablesTransfers() public { - /** - * Renounce ownership - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.owner(), ZERO_ADDRESS); - - /** - * Should revert with OwnerAlreadyRenounced error - */ - vm.expectRevert(OwnerMod.OwnerAlreadyRenounced.selector); - harness.transferOwnership(ALICE); - } - - function test_LibraryDoesNotCheckMsgSender() public { - /** - * The library doesn't check msg.sender - that's the facet's responsibility - * This test verifies the library works regardless of caller - * (In production, the facet should check permissions before calling the library) - */ - - vm.prank(ALICE); // Not the owner - harness.transferOwnership(BOB); - assertEq(harness.owner(), BOB); - - /** - * This shows the library itself doesn't enforce access control - * Access control should be implemented in the facet that uses the library - */ - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_TransferOwnership(address newOwner) public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(newOwner); - assertEq(harness.owner(), newOwner); - } - - function testFuzz_MultipleTransfers(address owner1, address owner2, address owner3) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(owner1); - assertEq(harness.owner(), owner1); - - vm.prank(owner1); - harness.transferOwnership(owner2); - assertEq(harness.owner(), owner2); - - vm.prank(owner2); - harness.transferOwnership(owner3); - assertEq(harness.owner(), owner3); - } - - function testFuzz_RevertWhen_RenouncedOwnerTransfers(address target) public { - vm.assume(target != address(0)); - - /** - * Renounce - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.owner(), ZERO_ADDRESS); - - /** - * Should revert with OwnerAlreadyRenounced error - */ - vm.expectRevert(OwnerMod.OwnerAlreadyRenounced.selector); - harness.transferOwnership(target); - } - - /** - * ============================================ - * Renounce Ownership Tests (Function not implemented in LibOwner) - * ============================================ - */ - - /** - * function test_RenounceOwnership_SetsOwnerToZero() public { - * // Use the new renounceOwnership function - * harness.renounceOwnership(); - * assertEq(harness.owner(), ZERO_ADDRESS); - * } - */ - - /** - * function test_RenounceOwnership_EmitsCorrectEvent() public { - * vm.expectEmit(true, true, false, true); - * emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - */ - - /** - * harness.renounceOwnership(); - * } - */ - - /** - * ============================================ - * Require Owner Tests (New Function) - * ============================================ - */ - - function test_RequireOwner_PassesForOwner() public { - /** - * Should not revert when called by owner - */ - vm.prank(INITIAL_OWNER); - harness.requireOwner(); - } - - function test_RevertWhen_RequireOwner_CalledByNonOwner() public { - vm.expectRevert(OwnerMod.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - harness.requireOwner(); - } - - function testFuzz_RequireOwner(address caller) public { - if (caller == INITIAL_OWNER) { - /** - * Should not revert for owner - */ - vm.prank(caller); - harness.requireOwner(); - } else { - /** - * Should revert for non-owner - */ - vm.expectRevert(OwnerMod.OwnerUnauthorizedAccount.selector); - vm.prank(caller); - harness.requireOwner(); - } - } -} diff --git a/test/access/Owner/OwnerFacet.t.sol b/test/access/Owner/OwnerFacet.t.sol deleted file mode 100644 index 8d744c16..00000000 --- a/test/access/Owner/OwnerFacet.t.sol +++ /dev/null @@ -1,278 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {OwnerFacet} from "../../../src/access/Owner/OwnerFacet.sol"; -import {OwnerFacetHarness} from "./harnesses/OwnerFacetHarness.sol"; - -contract OwnerFacetTest is Test { - OwnerFacetHarness public ownerFacet; - - address INITIAL_OWNER = makeAddr("owner"); - address NEW_OWNER = makeAddr("newOwner"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address ZERO_ADDRESS = address(0); - - /** - * Events - */ - event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); - - function setUp() public { - ownerFacet = new OwnerFacetHarness(); - ownerFacet.initialize(INITIAL_OWNER); - } - - /** - * ============================================ - * Ownership Getter Tests - * ============================================ - */ - - function test_Owner_ReturnsCorrectInitialOwner() public view { - assertEq(ownerFacet.owner(), INITIAL_OWNER); - } - - function test_Owner_ReturnsZeroWhenRenounced() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ZERO_ADDRESS); - - assertEq(ownerFacet.owner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Transfer Ownership Tests - * ============================================ - */ - - function test_TransferOwnership_ImmediateTransfer() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(NEW_OWNER); - - assertEq(ownerFacet.owner(), NEW_OWNER); - } - - function test_TransferOwnership_EmitsOwnershipTransferredEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, NEW_OWNER); - - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(NEW_OWNER); - } - - function test_TransferOwnership_MultipleTransfers() public { - /** - * First transfer - */ - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ALICE); - assertEq(ownerFacet.owner(), ALICE); - - /** - * Second transfer - */ - vm.prank(ALICE); - ownerFacet.transferOwnership(BOB); - assertEq(ownerFacet.owner(), BOB); - - /** - * Third transfer - */ - vm.prank(BOB); - ownerFacet.transferOwnership(NEW_OWNER); - assertEq(ownerFacet.owner(), NEW_OWNER); - } - - function test_TransferOwnership_ToSelf() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(INITIAL_OWNER); - - assertEq(ownerFacet.owner(), INITIAL_OWNER); - } - - function test_RevertWhen_TransferOwnership_CalledByNonOwner() public { - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerFacet.transferOwnership(ALICE); - } - - function test_RevertWhen_TransferOwnership_CalledByPreviousOwner() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(NEW_OWNER); - - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ALICE); - } - - /** - * ============================================ - * Renounce Ownership Tests - * ============================================ - */ - - function test_RenounceOwnership_SetsOwnerToZero() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ZERO_ADDRESS); - - assertEq(ownerFacet.owner(), ZERO_ADDRESS); - } - - function test_RenounceOwnership_EmitsEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ZERO_ADDRESS); - } - - function test_RenounceOwnership_PreventsAllFurtherTransfers() public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ZERO_ADDRESS); - - /** - * ALICE (non-owner) cannot transfer - */ - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerFacet.transferOwnership(BOB); - - /** - * BOB (non-owner) cannot transfer - */ - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - vm.prank(BOB); - ownerFacet.transferOwnership(ALICE); - - /** - * Note: Zero address cannot make any calls since it has no private key - */ - } - - /** - * ============================================ - * Renounce Ownership Direct Call Tests - * ============================================ - */ - - function test_RenounceOwnership_DirectCall_SetsOwnerToZero() public { - vm.prank(INITIAL_OWNER); - ownerFacet.renounceOwnership(); - - assertEq(ownerFacet.owner(), ZERO_ADDRESS); - } - - function test_RenounceOwnership_DirectCall_EmitsEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - ownerFacet.renounceOwnership(); - } - - function test_RevertWhen_RenounceOwnership_CalledByNonOwner() public { - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerFacet.renounceOwnership(); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_TransferOwnership_EmitsCorrectPreviousOwner() public { - vm.prank(INITIAL_OWNER); - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ALICE); - ownerFacet.transferOwnership(ALICE); - - vm.prank(ALICE); - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(ALICE, BOB); - ownerFacet.transferOwnership(BOB); - } - - function test_StorageSlot_Consistency() public { - bytes32 expectedSlot = keccak256("compose.owner"); - - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(NEW_OWNER); - - /** - * Read directly from storage - */ - bytes32 storedValue = vm.load(address(ownerFacet), expectedSlot); - address storedOwner = address(uint160(uint256(storedValue))); - - assertEq(storedOwner, NEW_OWNER); - assertEq(ownerFacet.owner(), NEW_OWNER); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_TransferOwnership(address newOwner) public { - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(newOwner); - - assertEq(ownerFacet.owner(), newOwner); - } - - function testFuzz_SequentialTransfers(address owner1, address owner2, address owner3) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - vm.assume(owner3 != address(0)); - - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(owner1); - assertEq(ownerFacet.owner(), owner1); - - vm.prank(owner1); - ownerFacet.transferOwnership(owner2); - assertEq(ownerFacet.owner(), owner2); - - vm.prank(owner2); - ownerFacet.transferOwnership(owner3); - assertEq(ownerFacet.owner(), owner3); - } - - function testFuzz_RevertWhen_UnauthorizedCaller(address caller, address target) public { - vm.assume(caller != INITIAL_OWNER); - - vm.prank(caller); - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - ownerFacet.transferOwnership(target); - } - - function testFuzz_RenouncePreventsAllTransfers(address caller, address target) public { - vm.assume(caller != address(0)); - - /** - * Renounce ownership - */ - vm.prank(INITIAL_OWNER); - ownerFacet.transferOwnership(ZERO_ADDRESS); - - /** - * No one can transfer anymore - */ - vm.prank(caller); - vm.expectRevert(OwnerFacet.OwnerUnauthorizedAccount.selector); - ownerFacet.transferOwnership(target); - } - - /** - * ============================================ - */ -} diff --git a/test/access/Owner/harnesses/OwnerFacetHarness.sol b/test/access/Owner/harnesses/OwnerFacetHarness.sol deleted file mode 100644 index 345d625f..00000000 --- a/test/access/Owner/harnesses/OwnerFacetHarness.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {OwnerFacet} from "../../../../src/access/Owner/OwnerFacet.sol"; - -/** - * @title OwnerFacet Test Harness - * @notice Extends OwnerFacet with initialization and test-specific functions - */ -contract OwnerFacetHarness is OwnerFacet { - /** - * @notice Initialize the owner for testing - * @dev This function is only for testing purposes - */ - function initialize(address _owner) external { - OwnerStorage storage s = getStorage(); - s.owner = _owner; - } - - /** - * @notice Force set owner without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceSetOwner(address _owner) external { - OwnerStorage storage s = getStorage(); - s.owner = _owner; - } - - /** - * @notice Get the raw storage owner value (for testing storage consistency) - */ - function getStorageOwner() external view returns (address) { - return getStorage().owner; - } -} diff --git a/test/access/Owner/harnesses/OwnerHarness.sol b/test/access/Owner/harnesses/OwnerHarness.sol deleted file mode 100644 index 8d24de8c..00000000 --- a/test/access/Owner/harnesses/OwnerHarness.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../src/access/Owner/OwnerMod.sol" as OwnerMod; - -/** - * @title LibOwner Test Harness - * @notice Exposes internal LibOwner functions as external for testing - */ -contract OwnerHarness { - /** - * @notice Initialize the owner (for testing) - */ - function initialize(address _owner) external { - OwnerMod.OwnerStorage storage s = OwnerMod.getStorage(); - s.owner = _owner; - } - - /** - * @notice Get the current owner - */ - function owner() external view returns (address) { - return OwnerMod.owner(); - } - - /** - * @notice Transfer ownership - */ - function transferOwnership(address _newOwner) external { - OwnerMod.transferOwnership(_newOwner); - } - - /** - * @notice Check if caller is owner (new function added by maintainer) - */ - function requireOwner() external view { - OwnerMod.requireOwner(); - } - - /** - * @notice Get storage directly (for testing storage consistency) - */ - function getStorageOwner() external view returns (address) { - return OwnerMod.getStorage().owner; - } - - /** - * @notice Force set owner to zero without checks (for testing renounced state) - */ - function forceRenounce() external { - OwnerMod.OwnerStorage storage s = OwnerMod.getStorage(); - s.owner = address(0); - } -} diff --git a/test/access/OwnerTwoSteps/OwnerTwoSteps.t.sol b/test/access/OwnerTwoSteps/OwnerTwoSteps.t.sol deleted file mode 100644 index 98ccb577..00000000 --- a/test/access/OwnerTwoSteps/OwnerTwoSteps.t.sol +++ /dev/null @@ -1,571 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/access/OwnerTwoSteps/OwnerTwoStepsMod.sol" as OwnerTwoStepsMod; -import {OwnerTwoStepsHarness} from "./harnesses/OwnerTwoStepsHarness.sol"; - -contract LibOwnerTwoStepsTest is Test { - OwnerTwoStepsHarness public harness; - - address INITIAL_OWNER = makeAddr("owner"); - address NEW_OWNER = makeAddr("newOwner"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address ZERO_ADDRESS = address(0); - - /** - * Events - */ - event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner); - event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); - - function setUp() public { - harness = new OwnerTwoStepsHarness(); - harness.initialize(INITIAL_OWNER); - } - - /** - * ============================================ - * Storage Tests - * ============================================ - */ - - function test_GetStorage_ReturnsCorrectOwner() public view { - assertEq(harness.owner(), INITIAL_OWNER); - assertEq(harness.getStorageOwner(), INITIAL_OWNER); - } - - function test_GetStorage_ReturnsCorrectPendingOwner() public { - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - assertEq(harness.getStoragePendingOwner(), ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - assertEq(harness.pendingOwner(), NEW_OWNER); - assertEq(harness.getStoragePendingOwner(), NEW_OWNER); - } - - function test_StorageSlot_UsesCorrectPosition() public { - bytes32 ownerSlot = keccak256("compose.owner"); - bytes32 pendingOwnerSlot = keccak256("compose.owner.pending"); - - /** - * Change pending owner - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - /** - * Read owner from storage - */ - bytes32 ownerValue = vm.load(address(harness), ownerSlot); - address storedOwner = address(uint160(uint256(ownerValue))); - assertEq(storedOwner, INITIAL_OWNER); - - /** - * Read pending owner from its separate storage location - */ - bytes32 pendingValue = vm.load(address(harness), pendingOwnerSlot); - address storedPendingOwner = address(uint160(uint256(pendingValue))); - assertEq(storedPendingOwner, NEW_OWNER); - } - - function test_StorageSlot_NoLongerCollides() public pure { - /** - * This test verifies that LibOwnerTwoSteps now uses separate storage locations - * Owner uses the same slot as LibOwner for compatibility - */ - bytes32 ownerSlot = keccak256("compose.owner"); - bytes32 pendingOwnerSlot = keccak256("compose.owner.pending"); - - /** - * They no longer collide - pendingOwner has its own slot - */ - assertTrue(ownerSlot != pendingOwnerSlot, "Storage slots should not collide"); - - /** - * Owner uses the standard slot - */ - assertEq(ownerSlot, keccak256("compose.owner"), "LibOwner slot"); - /** - * PendingOwner uses its own slot - */ - assertEq(pendingOwnerSlot, keccak256("compose.owner.pending"), "PendingOwner slot"); - } - - function test_StorageCollision_Fixed() public pure { - /** - * This test verifies that the storage collision bug has been fixed - * Owner uses keccak256("compose.owner") for compatibility with LibOwner - * PendingOwner uses keccak256("compose.owner.pending") for its own data - */ - bytes32 ownerSlot = keccak256("compose.owner"); - bytes32 pendingOwnerSlot = keccak256("compose.owner.pending"); - - /** - * Verify owner uses the standard slot for compatibility - */ - assertEq(ownerSlot, keccak256("compose.owner"), "Owner uses standard slot"); - - /** - * Verify pendingOwner uses its own separate slot - */ - assertEq(pendingOwnerSlot, keccak256("compose.owner.pending"), "PendingOwner uses separate slot"); - - /** - * Verify they don't collide - */ - assertTrue(ownerSlot != pendingOwnerSlot, "Slots should not collide"); - } - - /** - * ============================================ - * Owner Getter Tests - * ============================================ - */ - - function test_Owner_ReturnsCurrentOwner() public view { - assertEq(harness.owner(), INITIAL_OWNER); - } - - function test_PendingOwner_ReturnsCurrentPendingOwner() public { - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.pendingOwner(), NEW_OWNER); - } - - /** - * ============================================ - * Transfer Ownership Initiation Tests - * ============================================ - */ - - function test_TransferOwnership_SetsPendingOwner() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - assertEq(harness.owner(), INITIAL_OWNER); - assertEq(harness.pendingOwner(), NEW_OWNER); - } - - function test_TransferOwnership_EmitsOwnershipTransferStartedEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferStarted(INITIAL_OWNER, NEW_OWNER); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - } - - function test_TransferOwnership_AllowsTransferToZeroAddress() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_TransferOwnership_CanUpdatePendingOwner() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ALICE); - assertEq(harness.pendingOwner(), ALICE); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(BOB); - assertEq(harness.pendingOwner(), BOB); - } - - function test_TransferOwnership_LibraryDoesNotCheckCaller() public { - /** - * Library doesn't check msg.sender - that's the facet's responsibility - */ - vm.prank(ALICE); // Not the owner - harness.transferOwnership(ALICE); - - /** - * Transfer succeeds, pending owner is set - */ - assertEq(harness.pendingOwner(), ALICE); - assertEq(harness.owner(), INITIAL_OWNER); // Owner unchanged until accepted - } - - function test_RevertWhen_TransferOwnership_FromRenouncedOwner() public { - /** - * Force renounce - */ - harness.forceRenounce(); - assertEq(harness.owner(), ZERO_ADDRESS); - - /** - * Should revert with OwnerAlreadyRenounced error - */ - vm.expectRevert(OwnerTwoStepsMod.OwnerAlreadyRenounced.selector); - harness.transferOwnership(NEW_OWNER); - } - - function test_TransferOwnership_FromPendingOwner_Allowed() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.pendingOwner(), NEW_OWNER); - - /** - * Library allows pending owner to call transferOwnership - */ - vm.prank(NEW_OWNER); - harness.transferOwnership(ALICE); - assertEq(harness.pendingOwner(), ALICE); // Pending owner updated - assertEq(harness.owner(), INITIAL_OWNER); // Owner still unchanged - } - - /** - * ============================================ - * Accept Ownership Tests - * ============================================ - */ - - function test_AcceptOwnership_CompletesTransfer() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - vm.prank(NEW_OWNER); - harness.acceptOwnership(); - - assertEq(harness.owner(), NEW_OWNER); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_AcceptOwnership_EmitsOwnershipTransferredEvent() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, NEW_OWNER); - - vm.prank(NEW_OWNER); - harness.acceptOwnership(); - } - - function test_AcceptOwnership_ClearsPendingOwner() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - vm.prank(NEW_OWNER); - harness.acceptOwnership(); - - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_AcceptOwnership_LibraryAllowsAnyCaller() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - /** - * Library allows any caller - facets must check - */ - vm.prank(ALICE); // Not the pending owner - harness.acceptOwnership(); - - /** - * Ownership transferred even though Alice wasn't pending owner - */ - assertEq(harness.owner(), NEW_OWNER); // NEW_OWNER was pending, now owner - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_AcceptOwnership_CurrentOwnerCanCall() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - /** - * Library allows current owner to call acceptOwnership - */ - vm.prank(INITIAL_OWNER); - harness.acceptOwnership(); - - /** - * Ownership transferred to pending owner - */ - assertEq(harness.owner(), NEW_OWNER); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_AcceptOwnership_NoPendingOwner_SetsOwnerToZero() public { - /** - * No pending owner set - */ - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - - /** - * Library allows acceptOwnership even with no pending owner - */ - vm.prank(ALICE); - harness.acceptOwnership(); - - /** - * Owner becomes zero (the pending owner) - */ - assertEq(harness.owner(), ZERO_ADDRESS); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Renounce Ownership Tests (Zero Address) - * ============================================ - */ - - function test_RenounceOwnership_CannotBeCompleted() public { - /** - * Initiate transfer to zero address - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - - assertEq(harness.owner(), INITIAL_OWNER); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - - /** - * Zero address cannot accept (no private key) - * Owner remains unchanged - */ - } - - function test_RenounceOwnership_PreventsNewTransfersAfterForceRenounce() public { - harness.forceRenounce(); - assertEq(harness.owner(), ZERO_ADDRESS); - - /** - * Should revert with OwnerAlreadyRenounced error - */ - vm.expectRevert(OwnerTwoStepsMod.OwnerAlreadyRenounced.selector); - harness.transferOwnership(ALICE); - } - - function test_RenounceOwnership_DirectCall_SetsOwnerToZero() public { - vm.prank(INITIAL_OWNER); - harness.renounceOwnership(); - - assertEq(harness.owner(), ZERO_ADDRESS); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_RenounceOwnership_DirectCall_EmitsEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - harness.renounceOwnership(); - } - - function test_RequireOwner_SucceedsForOwner() public { - vm.prank(INITIAL_OWNER); - harness.requireOwner(); - } - - function test_RevertWhen_RequireOwner_CalledByNonOwner() public { - vm.expectRevert(OwnerTwoStepsMod.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - harness.requireOwner(); - } - - /** - * ============================================ - * Sequential Transfer Tests - * ============================================ - */ - - function test_SequentialTransfers() public { - /** - * First transfer - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ALICE); - - vm.prank(ALICE); - harness.acceptOwnership(); - assertEq(harness.owner(), ALICE); - - /** - * Second transfer - */ - vm.prank(ALICE); - harness.transferOwnership(BOB); - - vm.prank(BOB); - harness.acceptOwnership(); - assertEq(harness.owner(), BOB); - - /** - * Third transfer - */ - vm.prank(BOB); - harness.transferOwnership(NEW_OWNER); - - vm.prank(NEW_OWNER); - harness.acceptOwnership(); - assertEq(harness.owner(), NEW_OWNER); - } - - function test_TransferToSelf() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(INITIAL_OWNER); - assertEq(harness.pendingOwner(), INITIAL_OWNER); - - vm.prank(INITIAL_OWNER); - harness.acceptOwnership(); - assertEq(harness.owner(), INITIAL_OWNER); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_MultiplePendingChanges_OnlyLastOneMatters() public { - vm.startPrank(INITIAL_OWNER); - harness.transferOwnership(ALICE); - assertEq(harness.pendingOwner(), ALICE); - - harness.transferOwnership(BOB); - assertEq(harness.pendingOwner(), BOB); - - harness.transferOwnership(NEW_OWNER); - assertEq(harness.pendingOwner(), NEW_OWNER); - vm.stopPrank(); - - /** - * Library allows anyone to accept, but pending owner is NEW_OWNER - * So whoever calls acceptOwnership will transfer ownership to NEW_OWNER - */ - vm.prank(ALICE); - harness.acceptOwnership(); - - /** - * NEW_OWNER becomes owner regardless of who called acceptOwnership - */ - assertEq(harness.owner(), NEW_OWNER); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function test_CancelPendingTransfer() public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - assertEq(harness.pendingOwner(), NEW_OWNER); - - /** - * Cancel by setting to zero - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(ZERO_ADDRESS); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - - /** - * NEW_OWNER can still call acceptOwnership, but it will transfer to zero - */ - vm.prank(NEW_OWNER); - harness.acceptOwnership(); - - /** - * Owner becomes zero (the pending owner) - */ - assertEq(harness.owner(), ZERO_ADDRESS); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_TransferOwnership(address newOwner) public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(newOwner); - - assertEq(harness.owner(), INITIAL_OWNER); - assertEq(harness.pendingOwner(), newOwner); - } - - function testFuzz_AcceptOwnership(address newOwner) public { - vm.assume(newOwner != address(0)); - - vm.prank(INITIAL_OWNER); - harness.transferOwnership(newOwner); - - vm.prank(newOwner); - harness.acceptOwnership(); - - assertEq(harness.owner(), newOwner); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function testFuzz_TransferOwnership_AnyCaller(address caller, address target) public { - /** - * Library allows any caller - */ - vm.prank(caller); - harness.transferOwnership(target); - - /** - * Pending owner updated regardless of caller - */ - assertEq(harness.pendingOwner(), target); - assertEq(harness.owner(), INITIAL_OWNER); // Owner unchanged until acceptance - } - - function testFuzz_AcceptOwnership_AnyCaller(address caller) public { - vm.prank(INITIAL_OWNER); - harness.transferOwnership(NEW_OWNER); - - /** - * Library allows any caller to accept - */ - vm.prank(caller); - harness.acceptOwnership(); - - /** - * Ownership transferred to NEW_OWNER regardless of who called - */ - assertEq(harness.owner(), NEW_OWNER); - assertEq(harness.pendingOwner(), ZERO_ADDRESS); - } - - function testFuzz_SequentialTransfers(address owner1, address owner2, address owner3) public { - vm.assume(owner1 != address(0)); - vm.assume(owner2 != address(0)); - vm.assume(owner3 != address(0)); - - /** - * Transfer to owner1 - */ - vm.prank(INITIAL_OWNER); - harness.transferOwnership(owner1); - vm.prank(owner1); - harness.acceptOwnership(); - assertEq(harness.owner(), owner1); - - /** - * Transfer to owner2 - */ - vm.prank(owner1); - harness.transferOwnership(owner2); - vm.prank(owner2); - harness.acceptOwnership(); - assertEq(harness.owner(), owner2); - - /** - * Transfer to owner3 - */ - vm.prank(owner2); - harness.transferOwnership(owner3); - vm.prank(owner3); - harness.acceptOwnership(); - assertEq(harness.owner(), owner3); - } -} diff --git a/test/access/OwnerTwoSteps/OwnerTwoStepsFacet.t.sol b/test/access/OwnerTwoSteps/OwnerTwoStepsFacet.t.sol deleted file mode 100644 index 3f439b83..00000000 --- a/test/access/OwnerTwoSteps/OwnerTwoStepsFacet.t.sol +++ /dev/null @@ -1,647 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {OwnerTwoStepsFacet} from "../../../src/access/OwnerTwoSteps/OwnerTwoStepsFacet.sol"; -import {OwnerTwoStepsFacetHarness} from "./harnesses/OwnerTwoStepsFacetHarness.sol"; - -contract OwnerTwoStepsFacetTest is Test { - OwnerTwoStepsFacetHarness public ownerTwoSteps; - - address INITIAL_OWNER = makeAddr("owner"); - address NEW_OWNER = makeAddr("newOwner"); - address ALICE = makeAddr("alice"); - address BOB = makeAddr("bob"); - address ZERO_ADDRESS = address(0); - - /** - * Events - */ - event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner); - event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); - - function setUp() public { - ownerTwoSteps = new OwnerTwoStepsFacetHarness(); - ownerTwoSteps.initialize(INITIAL_OWNER); - } - - /** - * ============================================ - * Ownership Getter Tests - * ============================================ - */ - - function test_Owner_ReturnsCorrectInitialOwner() public view { - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - } - - function test_PendingOwner_InitiallyZero() public view { - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Transfer Ownership Initiation Tests - * ============================================ - */ - - function test_TransferOwnership_OnlyOwnerCanInitiate() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - } - - function test_TransferOwnership_EmitsOwnershipTransferStartedEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferStarted(INITIAL_OWNER, NEW_OWNER); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - } - - function test_TransferOwnership_CanTransferToZeroAddress() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ZERO_ADDRESS); - - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_TransferOwnership_CanUpdatePendingOwner() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ALICE); - assertEq(ownerTwoSteps.pendingOwner(), ALICE); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(BOB); - assertEq(ownerTwoSteps.pendingOwner(), BOB); - } - - function test_TransferOwnership_CanCancelBySettingToZero() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_RevertWhen_TransferOwnership_CalledByNonOwner() public { - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.transferOwnership(ALICE); - } - - function test_RevertWhen_TransferOwnership_CalledByPendingOwner() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(NEW_OWNER); - ownerTwoSteps.transferOwnership(ALICE); - } - - /** - * ============================================ - * Accept Ownership Tests - * ============================================ - */ - - function test_AcceptOwnership_CompletesTransfer() public { - /** - * Initiate transfer - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - /** - * Accept ownership - */ - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - - /** - * Verify ownership transferred - */ - assertEq(ownerTwoSteps.owner(), NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_AcceptOwnership_EmitsOwnershipTransferredEvent() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, NEW_OWNER); - - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - } - - function test_AcceptOwnership_ClearsPendingOwner() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_RevertWhen_AcceptOwnership_CalledByNonPendingOwner() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.acceptOwnership(); - } - - function test_RevertWhen_AcceptOwnership_CalledByCurrentOwner() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(INITIAL_OWNER); - ownerTwoSteps.acceptOwnership(); - } - - function test_RevertWhen_AcceptOwnership_NoPendingOwner() public { - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.acceptOwnership(); - } - - /** - * ============================================ - * Renounce Ownership Tests (via Zero Address) - * ============================================ - */ - - function test_RenounceOwnership_TwoStep() public { - /** - * Initiate renouncement - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ZERO_ADDRESS); - - /** - * Owner should still be the same - */ - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - - /** - * Note: Zero address cannot accept ownership (no private key) - * This effectively cancels the renouncement unless there's another mechanism - */ - } - - /** - * ============================================ - * Sequential Transfer Tests - * ============================================ - */ - - function test_SequentialTransfers() public { - /** - * First transfer - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ALICE); - - vm.prank(ALICE); - ownerTwoSteps.acceptOwnership(); - assertEq(ownerTwoSteps.owner(), ALICE); - - /** - * Second transfer - */ - vm.prank(ALICE); - ownerTwoSteps.transferOwnership(BOB); - - vm.prank(BOB); - ownerTwoSteps.acceptOwnership(); - assertEq(ownerTwoSteps.owner(), BOB); - - /** - * Third transfer back to initial - */ - vm.prank(BOB); - ownerTwoSteps.transferOwnership(INITIAL_OWNER); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.acceptOwnership(); - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - } - - function test_TransferToSelf() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(INITIAL_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), INITIAL_OWNER); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.acceptOwnership(); - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_MultiplePendingChanges_OnlyLastOneMatters() public { - vm.startPrank(INITIAL_OWNER); - - ownerTwoSteps.transferOwnership(ALICE); - assertEq(ownerTwoSteps.pendingOwner(), ALICE); - - ownerTwoSteps.transferOwnership(BOB); - assertEq(ownerTwoSteps.pendingOwner(), BOB); - - ownerTwoSteps.transferOwnership(NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - - vm.stopPrank(); - - /** - * Alice and Bob cannot accept - */ - vm.prank(ALICE); - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - ownerTwoSteps.acceptOwnership(); - - vm.prank(BOB); - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - ownerTwoSteps.acceptOwnership(); - - /** - * Only NEW_OWNER can accept - */ - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - assertEq(ownerTwoSteps.owner(), NEW_OWNER); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function testFuzz_TransferOwnership(address newOwner) public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(newOwner); - - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), newOwner); - } - - function testFuzz_AcceptOwnership(address newOwner) public { - vm.assume(newOwner != address(0)); // Zero address can't execute transactions - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(newOwner); - - vm.prank(newOwner); - ownerTwoSteps.acceptOwnership(); - - assertEq(ownerTwoSteps.owner(), newOwner); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function testFuzz_RevertWhen_UnauthorizedTransfer(address caller, address target) public { - vm.assume(caller != INITIAL_OWNER); - - vm.prank(caller); - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - ownerTwoSteps.transferOwnership(target); - } - - function testFuzz_RevertWhen_UnauthorizedAccept(address caller) public { - vm.assume(caller != NEW_OWNER); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - vm.prank(caller); - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - ownerTwoSteps.acceptOwnership(); - } - - /** - * ============================================ - * Direct Renouncement Tests (renounceOwnership) - * ============================================ - */ - - function test_RenounceOwnership_DirectlyRenounces() public { - /** - * Call renounceOwnership directly - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - /** - * Verify both owner and pendingOwner are zero - */ - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_RenounceOwnership_OnlyOwnerCanCall() public { - /** - * Only the owner should be able to renounce - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - } - - function test_RenounceOwnership_EmitsOwnershipTransferredEvent() public { - vm.expectEmit(true, true, false, true); - emit OwnershipTransferred(INITIAL_OWNER, ZERO_ADDRESS); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - } - - function test_RenounceOwnership_ClearsPendingOwner() public { - /** - * Set a pending owner first - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - - /** - * Renounce should clear pending owner - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - function test_RevertWhen_RenounceOwnership_CalledByNonOwner() public { - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.renounceOwnership(); - - /** - * Owner should remain unchanged - */ - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - } - - function test_RevertWhen_RenounceOwnership_CalledByPendingOwner() public { - /** - * Set pending owner - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - /** - * Pending owner cannot renounce - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(NEW_OWNER); - ownerTwoSteps.renounceOwnership(); - } - - function test_RenounceOwnership_PreventsAllFutureTransfers() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - /** - * Any address trying to transfer should fail - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.transferOwnership(BOB); - - /** - * Even the previous owner cannot transfer - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - } - - function test_RenounceOwnership_CannotBeReversed() public { - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - /** - * Cannot accept ownership (no pending owner) - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.acceptOwnership(); - - /** - * Cannot renounce again - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(ALICE); - ownerTwoSteps.renounceOwnership(); - } - - /** - * ============================================ - * Storage Tests - * ============================================ - */ - - function test_StorageSlot_Consistency() public { - bytes32 expectedSlot = keccak256("compose.owner"); - - /** - * Set pending owner - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - - /** - * Accept ownership - */ - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - - /** - * Read directly from storage - */ - bytes32 storedValue = vm.load(address(ownerTwoSteps), expectedSlot); - address storedOwner = address(uint160(uint256(storedValue))); - - assertEq(storedOwner, NEW_OWNER); - assertEq(ownerTwoSteps.owner(), NEW_OWNER); - } - - function test_StorageSlot_PendingOwner() public { - bytes32 pendingOwnerSlot = keccak256("compose.owner.pending"); - - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ALICE); - - /** - * Read pending owner from its separate storage location - */ - bytes32 pendingValue = vm.load(address(ownerTwoSteps), pendingOwnerSlot); - address storedPendingOwner = address(uint160(uint256(pendingValue))); - - assertEq(storedPendingOwner, ALICE); - assertEq(ownerTwoSteps.pendingOwner(), ALICE); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_RenounceOwnership_WithPendingTransfer() public { - /** - * Start a two-step transfer - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - - /** - * Direct renouncement should override pending transfer - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - /** - * Both should be zero - */ - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - - /** - * Pending owner can no longer accept - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - } - - function test_DirectRenounce_vs_TwoStepRenounce() public { - /** - * Test 1: Two-step renounce (can be cancelled) - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(ZERO_ADDRESS); - - /** - * Can still change mind - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(NEW_OWNER); - assertEq(ownerTwoSteps.pendingOwner(), NEW_OWNER); - - /** - * Reset for test 2 - */ - vm.prank(NEW_OWNER); - ownerTwoSteps.acceptOwnership(); - - /** - * Test 2: Direct renounce (immediate and irreversible) - */ - vm.prank(NEW_OWNER); - ownerTwoSteps.renounceOwnership(); - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - } - - /** - * ============================================ - * Additional Fuzz Tests - * ============================================ - */ - - function testFuzz_RenounceOwnership_OnlyOwner(address caller) public { - if (caller == INITIAL_OWNER) { - vm.prank(caller); - ownerTwoSteps.renounceOwnership(); - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - } else { - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(caller); - ownerTwoSteps.renounceOwnership(); - assertEq(ownerTwoSteps.owner(), INITIAL_OWNER); - } - } - - function testFuzz_StateAfterRenounce(address caller, address target) public { - /** - * Renounce ownership - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - /** - * Zero address can't make calls anyway - */ - if (caller != ZERO_ADDRESS) { - /** - * No matter who calls or with what target, transfers should fail - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(caller); - ownerTwoSteps.transferOwnership(target); - - /** - * Acceptance should also fail - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(caller); - ownerTwoSteps.acceptOwnership(); - - /** - * Renounce should also fail - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(caller); - ownerTwoSteps.renounceOwnership(); - } - } - - function testFuzz_RenounceWithPendingOwner(address pendingOwner) public { - vm.assume(pendingOwner != address(0)); - - /** - * Set pending owner - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.transferOwnership(pendingOwner); - - /** - * Renounce clears everything - */ - vm.prank(INITIAL_OWNER); - ownerTwoSteps.renounceOwnership(); - - assertEq(ownerTwoSteps.owner(), ZERO_ADDRESS); - assertEq(ownerTwoSteps.pendingOwner(), ZERO_ADDRESS); - - /** - * Pending owner can no longer accept - */ - vm.expectRevert(OwnerTwoStepsFacet.OwnerUnauthorizedAccount.selector); - vm.prank(pendingOwner); - ownerTwoSteps.acceptOwnership(); - } -} diff --git a/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsFacetHarness.sol b/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsFacetHarness.sol deleted file mode 100644 index 10b9c21f..00000000 --- a/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsFacetHarness.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {OwnerTwoStepsFacet} from "../../../../src/access/OwnerTwoSteps/OwnerTwoStepsFacet.sol"; - -/** - * @title OwnerTwoStepsFacet Test Harness - * @notice Extends OwnerTwoStepsFacet with initialization and test-specific functions - */ -contract OwnerTwoStepsFacetHarness is OwnerTwoStepsFacet { - /** - * @notice Initialize the owner for testing - * @dev This function is only for testing purposes - */ - function initialize(address _owner) external { - OwnerStorage storage ownerStorage = getOwnerStorage(); - PendingOwnerStorage storage pendingStorage = getPendingOwnerStorage(); - ownerStorage.owner = _owner; - pendingStorage.pendingOwner = address(0); - } - - /** - * @notice Force set owner without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceSetOwner(address _owner) external { - OwnerStorage storage ownerStorage = getOwnerStorage(); - ownerStorage.owner = _owner; - } - - /** - * @notice Force set pending owner without any checks (for testing edge cases) - * @dev This bypasses all access control for testing purposes - */ - function forceSetPendingOwner(address _pendingOwner) external { - PendingOwnerStorage storage pendingStorage = getPendingOwnerStorage(); - pendingStorage.pendingOwner = _pendingOwner; - } - - /** - * @notice Get the raw storage values (for testing storage consistency) - */ - function getStorageValues() external view returns (address currentOwner, address currentPendingOwner) { - OwnerStorage storage ownerStorage = getOwnerStorage(); - PendingOwnerStorage storage pendingStorage = getPendingOwnerStorage(); - currentOwner = ownerStorage.owner; - currentPendingOwner = pendingStorage.pendingOwner; - } - - /** - * @notice Force renounce ownership (for testing renounced state) - */ - function forceRenounce() external { - OwnerStorage storage ownerStorage = getOwnerStorage(); - PendingOwnerStorage storage pendingStorage = getPendingOwnerStorage(); - ownerStorage.owner = address(0); - pendingStorage.pendingOwner = address(0); - } -} diff --git a/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsHarness.sol b/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsHarness.sol deleted file mode 100644 index 3ec28c72..00000000 --- a/test/access/OwnerTwoSteps/harnesses/OwnerTwoStepsHarness.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../src/access/OwnerTwoSteps/OwnerTwoStepsMod.sol" as OwnerTwoStepsMod; - -/** - * @title LibOwnerTwoSteps Test Harness - * @notice Exposes internal LibOwnerTwoSteps functions as external for testing - */ -contract OwnerTwoStepsHarness { - /** - * @notice Initialize the owner (for testing) - */ - function initialize(address _owner) external { - OwnerTwoStepsMod.OwnerStorage storage ownerStorage = OwnerTwoStepsMod.getOwnerStorage(); - OwnerTwoStepsMod.PendingOwnerStorage storage pendingStorage = OwnerTwoStepsMod.getPendingOwnerStorage(); - ownerStorage.owner = _owner; - pendingStorage.pendingOwner = address(0); - } - - /** - * @notice Get the current owner - */ - function owner() external view returns (address) { - return OwnerTwoStepsMod.owner(); - } - - /** - * @notice Get the pending owner - */ - function pendingOwner() external view returns (address) { - return OwnerTwoStepsMod.pendingOwner(); - } - - /** - * @notice Initiate ownership transfer - */ - function transferOwnership(address _newOwner) external { - OwnerTwoStepsMod.transferOwnership(_newOwner); - } - - /** - * @notice Accept ownership transfer - */ - function acceptOwnership() external { - OwnerTwoStepsMod.acceptOwnership(); - } - - /** - * @notice Renounce ownership (new function added by maintainer) - */ - function renounceOwnership() external { - OwnerTwoStepsMod.renounceOwnership(); - } - - /** - * @notice Check if caller is owner (new function added by maintainer) - */ - function requireOwner() external view { - OwnerTwoStepsMod.requireOwner(); - } - - /** - * @notice Get storage directly (for testing storage consistency) - */ - function getStorageOwner() external view returns (address) { - return OwnerTwoStepsMod.getOwnerStorage().owner; - } - - /** - * @notice Get storage pending owner directly (for testing storage consistency) - */ - function getStoragePendingOwner() external view returns (address) { - return OwnerTwoStepsMod.getPendingOwnerStorage().pendingOwner; - } - - /** - * @notice Force set owner to zero without checks (for testing renounced state) - */ - function forceRenounce() external { - OwnerTwoStepsMod.OwnerStorage storage ownerStorage = OwnerTwoStepsMod.getOwnerStorage(); - OwnerTwoStepsMod.PendingOwnerStorage storage pendingStorage = OwnerTwoStepsMod.getPendingOwnerStorage(); - ownerStorage.owner = address(0); - pendingStorage.pendingOwner = address(0); - } - - /** - * @notice Force set pending owner (for testing edge cases) - */ - function forcePendingOwner(address _pendingOwner) external { - OwnerTwoStepsMod.PendingOwnerStorage storage pendingStorage = OwnerTwoStepsMod.getPendingOwnerStorage(); - pendingStorage.pendingOwner = _pendingOwner; - } -} diff --git a/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol b/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol new file mode 100644 index 00000000..39f8b350 --- /dev/null +++ b/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + DEFAULT_ADMIN_ROLE, + hasRole as accessControlHasRole, + requireRole as accessControlRequireRole +} from "src/access/AccessControl/Data/AccessControlDataMod.sol"; +import {setRoleAdmin as accessControlSetRoleAdmin} from "src/access/AccessControl/Admin/AccessControlAdminMod.sol"; +import {grantRole as accessControlGrantRole} from "src/access/AccessControl/Grant/AccessControlGrantMod.sol"; +import {revokeRole as accessControlRevokeRole} from "src/access/AccessControl/Revoke/AccessControlRevokeMod.sol"; +import {renounceRole as accessControlRenounceRole} from "src/access/AccessControl/Renounce/AccessControlRenounceMod.sol"; +import {grantRoleBatch as accessControlGrantRoleBatch} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol"; +import {revokeRoleBatch as accessControlRevokeRoleBatch} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol"; + +contract AccessControlCoreModHarness { + function DEFAULT_ADMIN_ROLE_VALUE() external pure returns (bytes32) { + return DEFAULT_ADMIN_ROLE; + } + + function hasRole(bytes32 role, address account) external view returns (bool) { + return accessControlHasRole(role, account); + } + + function requireRole(bytes32 role, address account) external view { + accessControlRequireRole(role, account); + } + + function setRoleAdmin(bytes32 role, bytes32 adminRole) external { + accessControlSetRoleAdmin(role, adminRole); + } + + function grantRole(bytes32 role, address account) external returns (bool) { + return accessControlGrantRole(role, account); + } + + function revokeRole(bytes32 role, address account) external returns (bool) { + return accessControlRevokeRole(role, account); + } + + function renounceRole(bytes32 role, address account) external { + accessControlRenounceRole(role, account); + } + + function grantRoleBatch(bytes32 role, address[] calldata accounts) external { + accessControlGrantRoleBatch(role, accounts); + } + + function revokeRoleBatch(bytes32 role, address[] calldata accounts) external { + accessControlRevokeRoleBatch(role, accounts); + } +} diff --git a/test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol b/test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol new file mode 100644 index 00000000..75ff5c1c --- /dev/null +++ b/test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + isRolePaused as accessControlIsRolePaused, + pauseRole as accessControlPauseRole, + unpauseRole as accessControlUnpauseRole, + requireRoleNotPaused as accessControlRequireRoleNotPaused +} from "src/access/AccessControl/Pausable/AccessControlPausableMod.sol"; + +contract AccessControlPausableModHarness { + function isRolePaused(bytes32 role) external view returns (bool) { + return accessControlIsRolePaused(role); + } + + function pauseRole(bytes32 role) external { + accessControlPauseRole(role); + } + + function unpauseRole(bytes32 role) external { + accessControlUnpauseRole(role); + } + + function requireRoleNotPaused(bytes32 role, address account) external view { + accessControlRequireRoleNotPaused(role, account); + } +} diff --git a/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol b/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol new file mode 100644 index 00000000..f76ae4e5 --- /dev/null +++ b/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + getRoleExpiry as accessControlGetRoleExpiry, + isRoleExpired as accessControlIsRoleExpired, + requireValidRole as accessControlRequireValidRole +} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol"; +import {grantRoleWithExpiry as accessControlGrantRoleWithExpiry} from + "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol"; +import {revokeTemporalRole as accessControlRevokeTemporalRole} from + "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.sol"; + +contract AccessControlTemporalModHarness { + function getRoleExpiry(bytes32 role, address account) external view returns (uint256) { + return accessControlGetRoleExpiry(role, account); + } + + function isRoleExpired(bytes32 role, address account) external view returns (bool) { + return accessControlIsRoleExpired(role, account); + } + + function requireValidRole(bytes32 role, address account) external view { + accessControlRequireValidRole(role, account); + } + + function grantRoleWithExpiry(bytes32 role, address account, uint256 expiresAt) external { + accessControlGrantRoleWithExpiry(role, account, expiresAt); + } + + function revokeTemporalRole(bytes32 role, address account) external { + accessControlRevokeTemporalRole(role, account); + } +} diff --git a/test/harnesses/access/Owner/OwnerCoreModHarness.sol b/test/harnesses/access/Owner/OwnerCoreModHarness.sol new file mode 100644 index 00000000..3ee02780 --- /dev/null +++ b/test/harnesses/access/Owner/OwnerCoreModHarness.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {owner as ownerDataOwner, requireOwner as ownerDataRequireOwner} from "src/access/Owner/Data/OwnerDataMod.sol"; +import {transferOwnership as ownerTransferOwnership} from "src/access/Owner/Transfer/OwnerTransferMod.sol"; +import {renounceOwnership as ownerRenounceOwnership} from "src/access/Owner/Renounce/OwnerRenounceMod.sol"; + +contract OwnerCoreModHarness { + function owner() external view returns (address) { + return ownerDataOwner(); + } + + function requireOwner() external view { + ownerDataRequireOwner(); + } + + function transferOwnership(address _newOwner) external { + ownerTransferOwnership(_newOwner); + } + + function renounceOwnership() external { + ownerRenounceOwnership(); + } +} diff --git a/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol b/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol new file mode 100644 index 00000000..b9ed793b --- /dev/null +++ b/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {owner as ownerDataOwner} from "src/access/Owner/Data/OwnerDataMod.sol"; +import {pendingOwner as twoStepPendingOwner} from "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataMod.sol"; +import { + transferOwnership as twoStepTransferOwnership, + acceptOwnership as twoStepAcceptOwnership +} from "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol"; +import {renounceOwnership as twoStepRenounceOwnership} from "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol"; + +contract OwnerTwoStepModHarness { + function owner() external view returns (address) { + return ownerDataOwner(); + } + + function pendingOwner() external view returns (address) { + return twoStepPendingOwner(); + } + + function transferOwnership(address _newOwner) external { + twoStepTransferOwnership(_newOwner); + } + + function acceptOwnership() external { + twoStepAcceptOwnership(); + } + + function renounceOwnership() external { + twoStepRenounceOwnership(); + } +} diff --git a/test/trees/AccessControl.tree b/test/trees/AccessControl.tree new file mode 100644 index 00000000..877086f2 --- /dev/null +++ b/test/trees/AccessControl.tree @@ -0,0 +1,179 @@ +Data +β”œβ”€β”€ HasRole +β”‚ β”œβ”€β”€ when the account has been granted the role +β”‚ β”‚ └── it should return true +β”‚ β”œβ”€β”€ when the account has not been granted the role +β”‚ β”‚ └── it should return false +β”‚ └── when the account is the zero address +β”‚ └── it should return false +└── RequireRole + β”œβ”€β”€ when the account has the role + β”‚ └── it should not revert + └── when the account does not have the role + └── it should revert with {AccessControlUnauthorizedAccount} + +Admin +β”œβ”€β”€ GetRoleAdmin +β”‚ β”œβ”€β”€ when the role has never had an explicit admin set +β”‚ β”‚ └── it should return the default admin role +β”‚ └── when the role has an explicit admin set +β”‚ └── it should return the stored admin role +└── SetRoleAdmin + β”œβ”€β”€ when the caller has the current admin role for the target role + β”‚ β”œβ”€β”€ it should update the admin role mapping + β”‚ └── it should emit a {RoleAdminChanged} event + └── when the caller does not have the current admin role for the target role + └── it should revert with {AccessControlUnauthorizedAccount} + +Grant +└── GrantRole + β”œβ”€β”€ when the caller has the admin role for the target role + β”‚ β”œβ”€β”€ and the account does not yet have the role + β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to true + β”‚ β”‚ β”œβ”€β”€ it should emit a {RoleGranted} event + β”‚ β”‚ └── it should return true from the module function + β”‚ └── and the account already has the role + β”‚ β”œβ”€β”€ it should leave hasRole[account][role] unchanged + β”‚ β”œβ”€β”€ it should not emit an additional {RoleGranted} event + β”‚ └── it should return false from the module function + └── when the caller does not have the admin role for the target role + └── it should revert with {AccessControlUnauthorizedAccount} + +Revoke +└── RevokeRole + β”œβ”€β”€ when the caller has the admin role for the target role + β”‚ β”œβ”€β”€ and the account currently has the role + β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to false + β”‚ β”‚ β”œβ”€β”€ it should emit a {RoleRevoked} event + β”‚ β”‚ └── it should return true from the module function + β”‚ └── and the account does not have the role + β”‚ β”œβ”€β”€ it should leave hasRole[account][role] unchanged + β”‚ └── it should return false from the module function + └── when the caller does not have the admin role for the target role + └── it should revert with {AccessControlUnauthorizedAccount} + +Renounce +└── RenounceRole + β”œβ”€β”€ when msg.sender equals the account + β”‚ β”œβ”€β”€ and the account currently has the role + β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to false + β”‚ β”‚ └── it should emit a {RoleRevoked} event + β”‚ └── and the account does not have the role + β”‚ └── it should be a no-op and not emit any event + └── when msg.sender does not equal the account + └── it should revert with {AccessControlUnauthorizedSender} + +Batch +β”œβ”€β”€ Grant +β”‚ └── GrantRoleBatch +β”‚ β”œβ”€β”€ when the caller has the admin role for the target role +β”‚ β”‚ β”œβ”€β”€ and the accounts array is empty +β”‚ β”‚ β”‚ └── it should succeed without emitting any events +β”‚ β”‚ └── and the accounts array is non-empty +β”‚ β”‚ β”œβ”€β”€ for each account without the role +β”‚ β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to true +β”‚ β”‚ β”‚ └── it should emit a {RoleGranted} event +β”‚ β”‚ └── for each account that already has the role +β”‚ β”‚ └── it should not change storage or emit {RoleGranted} +β”‚ └── when the caller does not have the admin role for the target role +β”‚ └── it should revert with {AccessControlUnauthorizedAccount} +└── Revoke + └── RevokeRoleBatch + β”œβ”€β”€ when the caller has the admin role for the target role + β”‚ β”œβ”€β”€ and the accounts array is empty + β”‚ β”‚ └── it should succeed without emitting any events + β”‚ └── and the accounts array is non-empty + β”‚ β”œβ”€β”€ for each account with the role + β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to false + β”‚ β”‚ └── it should emit a {RoleRevoked} event + β”‚ └── for each account that does not have the role + β”‚ └── it should not change storage or emit {RoleRevoked} + └── when the caller does not have the admin role for the target role + └── it should revert with {AccessControlUnauthorizedAccount} + +RoleHierarchy +β”œβ”€β”€ when a role’s admin role is itself +β”‚ └── an account with the role should be able to grant and revoke that same role +β”œβ”€β”€ when roles form a linear hierarchy (role1 admin is DEFAULT_ADMIN_ROLE, role2 admin is role1, role3 admin is role2) +β”‚ β”œβ”€β”€ only DEFAULT_ADMIN_ROLE holders can grant role1 +β”‚ β”œβ”€β”€ only role1 holders can grant role2 +β”‚ └── only role2 holders can grant role3 +└── when roles form a circular admin relationship + └── each grant or revoke must still satisfy the immediate admin-role check + +Pausable +β”œβ”€β”€ IsRolePaused +β”‚ β”œβ”€β”€ when the role has not been paused +β”‚ β”‚ └── it should return false +β”‚ └── when the role has been paused +β”‚ └── it should return true +β”œβ”€β”€ PauseRole +β”‚ β”œβ”€β”€ when the caller has the admin role for the target role +β”‚ β”‚ β”œβ”€β”€ it should set pausedRoles[role] to true +β”‚ β”‚ └── it should emit a {RolePaused} event +β”‚ └── when the caller does not have the admin role for the target role +β”‚ β”œβ”€β”€ on the facet surface it should revert with {AccessControlUnauthorizedAccount} +β”‚ └── on the module surface it should follow the current split implementation semantics +β”œβ”€β”€ UnpauseRole +β”‚ β”œβ”€β”€ when the caller has the admin role for the target role +β”‚ β”‚ β”œβ”€β”€ it should set pausedRoles[role] to false +β”‚ β”‚ └── it should emit a {RoleUnpaused} event +β”‚ └── when the caller does not have the admin role for the target role +β”‚ β”œβ”€β”€ on the facet surface it should revert with {AccessControlUnauthorizedAccount} +β”‚ └── on the module surface it should follow the current split implementation semantics +└── RequireRoleNotPaused + β”œβ”€β”€ when the account does not have the role + β”‚ └── it should revert with {AccessControlUnauthorizedAccount} + β”œβ”€β”€ when the account has the role and the role is paused + β”‚ └── it should revert with {AccessControlRolePaused} + └── when the account has the role and the role is not paused + └── it should not revert + +Temporal +β”œβ”€β”€ Data +β”‚ β”œβ”€β”€ GetRoleExpiry +β”‚ β”‚ β”œβ”€β”€ when no expiry has been set for the role assignment +β”‚ β”‚ β”‚ └── it should return 0 +β”‚ β”‚ └── when an expiry has been set for the role assignment +β”‚ β”‚ └── it should return the stored expiry timestamp +β”‚ β”œβ”€β”€ IsRoleExpired +β”‚ β”‚ β”œβ”€β”€ when no expiry is set and the account does not have the role +β”‚ β”‚ β”‚ └── it should return true +β”‚ β”‚ β”œβ”€β”€ when no expiry is set and the account has the role +β”‚ β”‚ β”‚ └── it should return false +β”‚ β”‚ β”œβ”€β”€ when an expiry is set in the future +β”‚ β”‚ β”‚ └── it should return false +β”‚ β”‚ └── when block.timestamp is greater than or equal to the stored expiry +β”‚ β”‚ └── it should return true +β”‚ └── RequireValidRole +β”‚ β”œβ”€β”€ when the account does not have the role +β”‚ β”‚ └── it should revert with {AccessControlUnauthorizedAccount} +β”‚ β”œβ”€β”€ when the account has the role and no expiry is set +β”‚ β”‚ └── it should not revert +β”‚ β”œβ”€β”€ when the account has the role and the expiry is in the future +β”‚ β”‚ └── it should not revert +β”‚ └── when the account has the role and block.timestamp is greater than or equal to the stored expiry +β”‚ └── it should revert with {AccessControlRoleExpired} +β”œβ”€β”€ Grant +β”‚ └── GrantRoleWithExpiry +β”‚ β”œβ”€β”€ when the caller has the admin role for the target role +β”‚ β”‚ β”œβ”€β”€ and the expiry is in the future +β”‚ β”‚ β”‚ β”œβ”€β”€ it should grant the role if it is not already granted +β”‚ β”‚ β”‚ β”œβ”€β”€ it should set the expiry timestamp +β”‚ β”‚ β”‚ └── it should emit a {RoleGrantedWithExpiry} event +β”‚ β”‚ └── and the expiry is less than or equal to block.timestamp +β”‚ β”‚ └── it should revert with {AccessControlRoleExpired} +β”‚ └── when the caller does not have the admin role for the target role +β”‚ └── it should revert with {AccessControlUnauthorizedAccount} +└── Revoke + └── RevokeTemporalRole + β”œβ”€β”€ when the caller has the admin role for the target role + β”‚ β”œβ”€β”€ and the account currently has the role + β”‚ β”‚ β”œβ”€β”€ it should set hasRole[account][role] to false + β”‚ β”‚ β”œβ”€β”€ it should clear the expiry timestamp + β”‚ β”‚ └── it should emit a {TemporalRoleRevoked} event + β”‚ └── and the account does not have the role + β”‚ └── it should be a no-op + └── when the caller does not have the admin role for the target role + └── it should revert with {AccessControlUnauthorizedAccount} + diff --git a/test/trees/Owner.tree b/test/trees/Owner.tree new file mode 100644 index 00000000..1c9218be --- /dev/null +++ b/test/trees/Owner.tree @@ -0,0 +1,62 @@ +Data +β”œβ”€β”€ Owner +β”‚ β”œβ”€β”€ when the owner has been set +β”‚ β”‚ └── it should return the stored owner address +β”‚ └── when the owner has been renounced (zero address) +β”‚ └── it should return the zero address +└── RequireOwner + β”œβ”€β”€ when the caller is the owner + β”‚ └── it should not revert + └── when the caller is not the owner + └── it should revert with {OwnerUnauthorizedAccount} + +Transfer +└── TransferOwnership + β”œβ”€β”€ when the caller is the owner + β”‚ β”œβ”€β”€ it should update the owner to the new address + β”‚ β”œβ”€β”€ it should emit an {OwnershipTransferred} event + β”‚ β”œβ”€β”€ when the new owner is the zero address + β”‚ β”‚ └── it should set owner to zero (renounce) + β”‚ └── when the new owner is the current owner (self) + β”‚ └── it should leave owner unchanged + └── when the caller is not the owner + └── it should revert with {OwnerUnauthorizedAccount} + +Renounce +└── RenounceOwnership + β”œβ”€β”€ when the caller is the owner + β”‚ β”œβ”€β”€ it should set the owner to the zero address + β”‚ └── it should emit an {OwnershipTransferred} event + └── when the caller is not the owner + └── it should revert with {OwnerUnauthorizedAccount} + +TwoSteps +β”œβ”€β”€ Data +β”‚ └── PendingOwner +β”‚ β”œβ”€β”€ when a pending owner has been set +β”‚ β”‚ └── it should return the pending owner address +β”‚ └── when no pending owner has been set +β”‚ └── it should return the zero address +β”œβ”€β”€ Transfer +β”‚ β”œβ”€β”€ TransferOwnership +β”‚ β”‚ β”œβ”€β”€ when the caller is the owner +β”‚ β”‚ β”‚ β”œβ”€β”€ it should set pendingOwner to the new address +β”‚ β”‚ β”‚ β”œβ”€β”€ it should emit an {OwnershipTransferStarted} event +β”‚ β”‚ β”‚ └── it should leave the current owner unchanged until acceptance +β”‚ β”‚ └── when the caller is not the owner +β”‚ β”‚ └── it should revert with {OwnerUnauthorizedAccount} +β”‚ └── AcceptOwnership +β”‚ β”œβ”€β”€ when the caller is the pending owner +β”‚ β”‚ β”œβ”€β”€ it should set the owner to the pending owner +β”‚ β”‚ β”œβ”€β”€ it should clear the pending owner to zero +β”‚ β”‚ └── it should emit an {OwnershipTransferred} event +β”‚ └── when the caller is not the pending owner +β”‚ └── it should revert with {OwnerUnauthorizedAccount} +└── Renounce + └── RenounceOwnership + β”œβ”€β”€ when the caller is the owner + β”‚ β”œβ”€β”€ it should set the owner to the zero address + β”‚ β”œβ”€β”€ it should clear the pending owner to zero + β”‚ └── it should emit an {OwnershipTransferred} event + └── when the caller is not the owner + └── it should revert with {OwnerUnauthorizedAccount} diff --git a/test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol b/test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol new file mode 100644 index 00000000..befb3868 --- /dev/null +++ b/test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlAdmin_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } +} diff --git a/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol new file mode 100644 index 00000000..80f85172 --- /dev/null +++ b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlAdmin_Base_Test} from "test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlAdminFacet} from "src/access/AccessControl/Admin/AccessControlAdminFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract SetRoleAdmin_AccessControlAdminFacet_Fuzz_Unit_Test is AccessControlAdmin_Base_Test { + using AccessControlStorageUtils for address; + + event RoleAdminChanged(bytes32 indexed _role, bytes32 indexed _previousAdminRole, bytes32 indexed _newAdminRole); + + AccessControlAdminFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlAdminFacet(); + vm.label(address(facet), "AccessControlAdminFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldSetRoleAdmin_WhenCallerHasCurrentAdmin(bytes32 role, bytes32 newAdminRole) external { + vm.expectEmit(address(facet)); + emit RoleAdminChanged(role, DEFAULT_ADMIN_ROLE, newAdminRole); + + vm.prank(users.admin); + facet.setRoleAdmin(role, newAdminRole); + + assertEq(address(facet).adminRole(role), newAdminRole, "adminRole"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveCurrentAdmin(bytes32 role, bytes32 newAdminRole, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector(AccessControlAdminFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + facet.setRoleAdmin(role, newAdminRole); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlAdminFacet.setRoleAdmin.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol b/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol new file mode 100644 index 00000000..ad0626f7 --- /dev/null +++ b/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlAdmin_Base_Test} from "test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract SetRoleAdmin_AccessControlAdminMod_Fuzz_Unit_Test is AccessControlAdmin_Base_Test { + using AccessControlStorageUtils for address; + + event RoleAdminChanged(bytes32 indexed _role, bytes32 indexed _previousAdminRole, bytes32 indexed _newAdminRole); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldSetRoleAdmin_WhenCallerHasCurrentAdmin(bytes32 role, bytes32 newAdminRole) external { + vm.expectEmit(address(harness)); + emit RoleAdminChanged(role, DEFAULT_ADMIN_ROLE, newAdminRole); + + vm.prank(users.admin); + harness.setRoleAdmin(role, newAdminRole); + + assertEq(address(harness).adminRole(role), newAdminRole, "adminRole"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveCurrentAdmin(bytes32 role, bytes32 newAdminRole, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.setRoleAdmin(role, newAdminRole); + } +} diff --git a/test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol b/test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol new file mode 100644 index 00000000..9500e358 --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlGrantBatch_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } +} diff --git a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol new file mode 100644 index 00000000..bcaced7f --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlGrantBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlGrantBatchFacet} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRoleBatch_AccessControlGrantBatchFacet_Unit_Test is AccessControlGrantBatch_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlGrantBatchFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlGrantBatchFacet(); + vm.label(address(facet), "AccessControlGrantBatchFacet"); + seedDefaultAdmin(address(facet)); + } + + function test_ShouldSucceedWithoutEvents_WhenAccountsArrayIsEmpty() external { + address[] memory accounts = new address[](0); + + vm.recordLogs(); + vm.prank(users.admin); + facet.grantRoleBatch(MINTER_ROLE, accounts); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function test_ShouldGrantRoleBatch_ForEachNewAccount() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, users.alice, users.admin); + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + facet.grantRoleBatch(MINTER_ROLE, accounts); + + assertEq(address(facet).hasRole(users.alice, MINTER_ROLE), true, "alice"); + assertEq(address(facet).hasRole(users.bob, MINTER_ROLE), true, "bob"); + assertEq(address(facet).hasRole(users.charlee, MINTER_ROLE), true, "charlee"); + } + + function test_ShouldSkipAccountsThatAlreadyHaveRole() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(facet), MINTER_ROLE, users.alice); + + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + facet.grantRoleBatch(MINTER_ROLE, accounts); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(address caller) external { + vm.assume(caller != users.admin); + + address[] memory accounts = new address[](1); + accounts[0] = users.alice; + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlGrantBatchFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.grantRoleBatch(MINTER_ROLE, accounts); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlGrantBatchFacet.grantRoleBatch.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol new file mode 100644 index 00000000..37c7242c --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlGrantBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRoleBatch_AccessControlGrantBatchMod_Unit_Test is AccessControlGrantBatch_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function test_ShouldSucceedWithoutEvents_WhenAccountsArrayIsEmpty() external { + address[] memory accounts = new address[](0); + + vm.recordLogs(); + vm.prank(users.admin); + harness.grantRoleBatch(MINTER_ROLE, accounts); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function test_ShouldGrantRoleBatch_ForEachNewAccount() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + vm.expectEmit(address(harness)); + emit RoleGranted(MINTER_ROLE, users.alice, users.admin); + vm.expectEmit(address(harness)); + emit RoleGranted(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(harness)); + emit RoleGranted(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + harness.grantRoleBatch(MINTER_ROLE, accounts); + + assertEq(address(harness).hasRole(users.alice, MINTER_ROLE), true, "alice"); + assertEq(address(harness).hasRole(users.bob, MINTER_ROLE), true, "bob"); + assertEq(address(harness).hasRole(users.charlee, MINTER_ROLE), true, "charlee"); + } + + function test_ShouldSkipAccountsThatAlreadyHaveRole() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(harness), MINTER_ROLE, users.alice); + + vm.expectEmit(address(harness)); + emit RoleGranted(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(harness)); + emit RoleGranted(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + harness.grantRoleBatch(MINTER_ROLE, accounts); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(address caller) external { + vm.assume(caller != users.admin); + + address[] memory accounts = new address[](1); + accounts[0] = users.alice; + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.grantRoleBatch(MINTER_ROLE, accounts); + } +} diff --git a/test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol b/test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol new file mode 100644 index 00000000..f149f52e --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlRevokeBatch_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } +} diff --git a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol new file mode 100644 index 00000000..76be19e7 --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRevokeBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlRevokeBatchFacet} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeRoleBatch_AccessControlRevokeBatchFacet_Unit_Test is AccessControlRevokeBatch_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlRevokeBatchFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlRevokeBatchFacet(); + vm.label(address(facet), "AccessControlRevokeBatchFacet"); + seedDefaultAdmin(address(facet)); + } + + function test_ShouldSucceedWithoutEvents_WhenAccountsArrayIsEmpty() external { + address[] memory accounts = new address[](0); + + vm.recordLogs(); + vm.prank(users.admin); + facet.revokeRoleBatch(MINTER_ROLE, accounts); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function test_ShouldRevokeRoleBatch_ForEachGrantedAccount() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(facet), MINTER_ROLE, users.alice); + seedRole(address(facet), MINTER_ROLE, users.bob); + seedRole(address(facet), MINTER_ROLE, users.charlee); + + vm.expectEmit(address(facet)); + emit RoleRevoked(MINTER_ROLE, users.alice, users.admin); + vm.expectEmit(address(facet)); + emit RoleRevoked(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(facet)); + emit RoleRevoked(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + facet.revokeRoleBatch(MINTER_ROLE, accounts); + + assertEq(address(facet).hasRole(users.alice, MINTER_ROLE), false, "alice"); + assertEq(address(facet).hasRole(users.bob, MINTER_ROLE), false, "bob"); + assertEq(address(facet).hasRole(users.charlee, MINTER_ROLE), false, "charlee"); + } + + function test_ShouldSkipAccountsThatDoNotHaveRole() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(facet), MINTER_ROLE, users.alice); + + vm.expectEmit(address(facet)); + emit RoleRevoked(MINTER_ROLE, users.alice, users.admin); + + vm.prank(users.admin); + facet.revokeRoleBatch(MINTER_ROLE, accounts); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(address caller) external { + vm.assume(caller != users.admin); + + address[] memory accounts = new address[](1); + accounts[0] = users.alice; + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlRevokeBatchFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.revokeRoleBatch(MINTER_ROLE, accounts); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRevokeBatchFacet.revokeRoleBatch.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol new file mode 100644 index 00000000..05db8aa5 --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRevokeBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeRoleBatch_AccessControlRevokeBatchMod_Unit_Test is AccessControlRevokeBatch_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function test_ShouldSucceedWithoutEvents_WhenAccountsArrayIsEmpty() external { + address[] memory accounts = new address[](0); + + vm.recordLogs(); + vm.prank(users.admin); + harness.revokeRoleBatch(MINTER_ROLE, accounts); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function test_ShouldRevokeRoleBatch_ForEachGrantedAccount() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(harness), MINTER_ROLE, users.alice); + seedRole(address(harness), MINTER_ROLE, users.bob); + seedRole(address(harness), MINTER_ROLE, users.charlee); + + vm.expectEmit(address(harness)); + emit RoleRevoked(MINTER_ROLE, users.alice, users.admin); + vm.expectEmit(address(harness)); + emit RoleRevoked(MINTER_ROLE, users.bob, users.admin); + vm.expectEmit(address(harness)); + emit RoleRevoked(MINTER_ROLE, users.charlee, users.admin); + + vm.prank(users.admin); + harness.revokeRoleBatch(MINTER_ROLE, accounts); + + assertEq(address(harness).hasRole(users.alice, MINTER_ROLE), false, "alice"); + assertEq(address(harness).hasRole(users.bob, MINTER_ROLE), false, "bob"); + assertEq(address(harness).hasRole(users.charlee, MINTER_ROLE), false, "charlee"); + } + + function test_ShouldSkipAccountsThatDoNotHaveRole() external { + address[] memory accounts = new address[](3); + accounts[0] = users.alice; + accounts[1] = users.bob; + accounts[2] = users.charlee; + + seedRole(address(harness), MINTER_ROLE, users.alice); + + vm.expectEmit(address(harness)); + emit RoleRevoked(MINTER_ROLE, users.alice, users.admin); + + vm.prank(users.admin); + harness.revokeRoleBatch(MINTER_ROLE, accounts); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(address caller) external { + vm.assume(caller != users.admin); + + address[] memory accounts = new address[](1); + accounts[0] = users.alice; + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.revokeRoleBatch(MINTER_ROLE, accounts); + } +} diff --git a/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol b/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol new file mode 100644 index 00000000..5fa2f457 --- /dev/null +++ b/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlData_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedAdminRole(address target, bytes32 role, bytes32 adminRole_) internal { + target.setAdminRole(role, adminRole_); + } +} diff --git a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..2ea90287 --- /dev/null +++ b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlData_Base_Test} from "test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlDataFacet} from "src/access/AccessControl/Data/AccessControlDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Data_AccessControlDataFacet_Fuzz_Unit_Test is AccessControlData_Base_Test { + using AccessControlStorageUtils for address; + + AccessControlDataFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlDataFacet(); + vm.label(address(facet), "AccessControlDataFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldReturnTrue_HasRole_WhenAccountHasRole(address account, bytes32 role) external { + address(facet).setHasRole(account, role, true); + + assertEq(facet.hasRole(role, account), true, "hasRole"); + } + + function testFuzz_ShouldReturnFalse_HasRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) external view { + assertEq(facet.hasRole(role, account), false, "hasRole"); + } + + function testFuzz_ShouldNotRevert_RequireRole_WhenAccountHasRole(address account, bytes32 role) external { + address(facet).setHasRole(account, role, true); + + facet.requireRole(role, account); + } + + function testFuzz_ShouldRevert_RequireRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) external { + vm.expectRevert( + abi.encodeWithSelector(AccessControlDataFacet.AccessControlUnauthorizedAccount.selector, account, role) + ); + facet.requireRole(role, account); + } + + function testFuzz_ShouldReturnDefaultAdminRole_GetRoleAdmin_WhenAdminNotSet(bytes32 role) external view { + assertEq(facet.getRoleAdmin(role), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); + } + + function testFuzz_ShouldReturnStoredAdminRole_GetRoleAdmin(bytes32 role, bytes32 adminRole) external { + address(facet).setAdminRole(role, adminRole); + + assertEq(facet.getRoleAdmin(role), adminRole, "getRoleAdmin"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlDataFacet.hasRole.selector, + AccessControlDataFacet.requireRole.selector, + AccessControlDataFacet.getRoleAdmin.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol new file mode 100644 index 00000000..75af5899 --- /dev/null +++ b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlData_Base_Test} from "test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Data_AccessControlMod_Fuzz_Unit_Test is AccessControlData_Base_Test { + using AccessControlStorageUtils for address; + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldReturnTrue_HasRole_WhenAccountHasRole(address account, bytes32 role) external { + address(harness).setHasRole(account, role, true); + + assertEq(harness.hasRole(role, account), true, "hasRole"); + } + + function testFuzz_ShouldReturnFalse_HasRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) + external + view + { + assertEq(harness.hasRole(role, account), false, "hasRole"); + } + + function testFuzz_ShouldNotRevert_RequireRole_WhenAccountHasRole(address account, bytes32 role) external { + address(harness).setHasRole(account, role, true); + + harness.requireRole(role, account); + } + + function testFuzz_ShouldRevert_RequireRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) external { + vm.expectRevert(abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role)); + harness.requireRole(role, account); + } + + function testFuzz_ShouldReturnDefaultAdminRole_GetRoleAdmin_WhenAdminNotSet(bytes32 role) external view { + assertEq(address(harness).adminRole(role), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); + } + + function testFuzz_ShouldReturnStoredAdminRole_GetRoleAdmin(bytes32 role, bytes32 adminRole) external { + address(harness).setAdminRole(role, adminRole); + + assertEq(address(harness).adminRole(role), adminRole, "getRoleAdmin"); + } + + function test_ShouldReturnZero_DEFAULT_ADMIN_ROLE_VALUE() external view { + assertEq(harness.DEFAULT_ADMIN_ROLE_VALUE(), bytes32(0), "DEFAULT_ADMIN_ROLE"); + } +} diff --git a/test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol b/test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol new file mode 100644 index 00000000..ecc529ba --- /dev/null +++ b/test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlGrant_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedAdminRole(address target, bytes32 role, bytes32 adminRole_) internal { + target.setAdminRole(role, adminRole_); + } + + function seedLinearHierarchy(address target, bytes32 role1, bytes32 role2, bytes32 role3) internal { + target.setAdminRole(role1, DEFAULT_ADMIN_ROLE); + target.setAdminRole(role2, role1); + target.setAdminRole(role3, role2); + } +} diff --git a/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol new file mode 100644 index 00000000..85d4cc3f --- /dev/null +++ b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlGrant_Base_Test} from "test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlGrantFacet} from "src/access/AccessControl/Grant/AccessControlGrantFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRole_AccessControlGrantFacet_Fuzz_Unit_Test is AccessControlGrant_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlGrantFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlGrantFacet(); + vm.label(address(facet), "AccessControlGrantFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldGrantRole_WhenCallerHasAdminRole(bytes32 role, address account) external { + vm.expectEmit(address(facet)); + emit RoleGranted(role, account, users.admin); + + vm.prank(users.admin); + facet.grantRole(role, account); + + assertEq(address(facet).hasRole(account, role), true, "hasRole"); + } + + function testFuzz_ShouldNotEmitAgain_WhenAccountAlreadyHasRole(bytes32 role, address account) external { + address(facet).setHasRole(account, role, true); + + vm.recordLogs(); + vm.prank(users.admin); + facet.grantRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + assertEq(address(facet).hasRole(account, role), true, "hasRole"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector(AccessControlGrantFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + facet.grantRole(role, account); + } + + function testFuzz_ShouldGrantRole_WhenRoleIsSelfAdmin(address caller, address account) external { + vm.assume(caller != account); + seedAdminRole(address(facet), MINTER_ROLE, MINTER_ROLE); + seedRole(address(facet), MINTER_ROLE, caller); + + vm.expectEmit(address(facet)); + emit RoleGranted(MINTER_ROLE, account, caller); + + vm.prank(caller); + facet.grantRole(MINTER_ROLE, account); + + assertEq(address(facet).hasRole(account, MINTER_ROLE), true, "hasRole"); + } + + function testFuzz_ShouldGrantThroughLinearHierarchy(address role1Holder, address role2Holder, address role3Holder) + external + { + seedLinearHierarchy(address(facet), MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE); + + vm.prank(users.admin); + facet.grantRole(MINTER_ROLE, role1Holder); + + vm.prank(role1Holder); + facet.grantRole(PAUSER_ROLE, role2Holder); + + vm.prank(role2Holder); + facet.grantRole(UPGRADER_ROLE, role3Holder); + + assertEq(address(facet).hasRole(role1Holder, MINTER_ROLE), true, "role1"); + assertEq(address(facet).hasRole(role2Holder, PAUSER_ROLE), true, "role2"); + assertEq(address(facet).hasRole(role3Holder, UPGRADER_ROLE), true, "role3"); + } + + function testFuzz_ShouldUseImmediateAdminRoleInCircularHierarchy(address caller, address account) external { + seedAdminRole(address(facet), MINTER_ROLE, PAUSER_ROLE); + seedAdminRole(address(facet), PAUSER_ROLE, UPGRADER_ROLE); + seedAdminRole(address(facet), UPGRADER_ROLE, MINTER_ROLE); + seedRole(address(facet), PAUSER_ROLE, caller); + + vm.prank(caller); + facet.grantRole(MINTER_ROLE, account); + + assertEq(address(facet).hasRole(account, MINTER_ROLE), true, "hasRole"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlGrantFacet.grantRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol new file mode 100644 index 00000000..35687da5 --- /dev/null +++ b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlGrant_Base_Test} from "test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRole_AccessControlGrantMod_Fuzz_Unit_Test is AccessControlGrant_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGranted(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldReturnTrue_WhenCallerHasAdminRole(bytes32 role, address account) external { + vm.expectEmit(address(harness)); + emit RoleGranted(role, account, users.admin); + + vm.prank(users.admin); + bool result = harness.grantRole(role, account); + + assertEq(result, true, "grantRole"); + assertEq(address(harness).hasRole(account, role), true, "hasRole"); + } + + function testFuzz_ShouldReturnFalse_WhenAccountAlreadyHasRole(bytes32 role, address account) external { + address(harness).setHasRole(account, role, true); + + vm.recordLogs(); + vm.prank(users.admin); + bool result = harness.grantRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(result, false, "grantRole"); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.grantRole(role, account); + } + + function testFuzz_ShouldReturnTrue_WhenRoleIsSelfAdmin(address caller, address account) external { + seedAdminRole(address(harness), MINTER_ROLE, MINTER_ROLE); + seedRole(address(harness), MINTER_ROLE, caller); + + vm.prank(caller); + bool result = harness.grantRole(MINTER_ROLE, account); + + assertEq(result, true, "grantRole"); + assertEq(address(harness).hasRole(account, MINTER_ROLE), true, "hasRole"); + } + + function testFuzz_ShouldGrantThroughLinearHierarchy(address role1Holder, address role2Holder, address role3Holder) + external + { + seedLinearHierarchy(address(harness), MINTER_ROLE, PAUSER_ROLE, UPGRADER_ROLE); + + vm.prank(users.admin); + assertEq(harness.grantRole(MINTER_ROLE, role1Holder), true, "grant role1"); + + vm.prank(role1Holder); + assertEq(harness.grantRole(PAUSER_ROLE, role2Holder), true, "grant role2"); + + vm.prank(role2Holder); + assertEq(harness.grantRole(UPGRADER_ROLE, role3Holder), true, "grant role3"); + + assertEq(address(harness).hasRole(role3Holder, UPGRADER_ROLE), true, "hasRole"); + } + + function testFuzz_ShouldUseImmediateAdminRoleInCircularHierarchy(address caller, address account) external { + seedAdminRole(address(harness), MINTER_ROLE, PAUSER_ROLE); + seedAdminRole(address(harness), PAUSER_ROLE, UPGRADER_ROLE); + seedAdminRole(address(harness), UPGRADER_ROLE, MINTER_ROLE); + seedRole(address(harness), PAUSER_ROLE, caller); + + vm.prank(caller); + bool result = harness.grantRole(MINTER_ROLE, account); + + assertEq(result, true, "grantRole"); + assertEq(address(harness).hasRole(account, MINTER_ROLE), true, "hasRole"); + } +} diff --git a/test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol b/test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol new file mode 100644 index 00000000..ddb8b327 --- /dev/null +++ b/test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlPausable_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedPausedRole(address target, bytes32 role, bool paused) internal { + target.setPausedRole(role, paused); + } +} diff --git a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol new file mode 100644 index 00000000..9d0b0e79 --- /dev/null +++ b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlPausable_Base_Test} from "test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlPausableFacet} from "src/access/AccessControl/Pausable/AccessControlPausableFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Pausable_AccessControlPausableFacet_Fuzz_Unit_Test is AccessControlPausable_Base_Test { + using AccessControlStorageUtils for address; + + event RolePaused(bytes32 indexed _role, address indexed _account); + event RoleUnpaused(bytes32 indexed _role, address indexed _account); + + AccessControlPausableFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlPausableFacet(); + vm.label(address(facet), "AccessControlPausableFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldReturnFalse_IsRolePaused_WhenRoleIsNotPaused(bytes32 role) external view { + assertEq(facet.isRolePaused(role), false, "isRolePaused"); + } + + function testFuzz_ShouldReturnTrue_IsRolePaused_WhenRoleIsPaused(bytes32 role) external { + seedPausedRole(address(facet), role, true); + + assertEq(facet.isRolePaused(role), true, "isRolePaused"); + } + + function testFuzz_ShouldPauseRole_WhenCallerHasAdminRole(bytes32 role) external { + vm.expectEmit(address(facet)); + emit RolePaused(role, users.admin); + + vm.prank(users.admin); + facet.pauseRole(role); + + assertEq(address(facet).isRolePaused(role), true, "isRolePaused"); + } + + function testFuzz_ShouldRevert_PauseRole_WhenCallerDoesNotHaveAdminRole(bytes32 role, address caller) external { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.pauseRole(role); + } + + function testFuzz_ShouldUnpauseRole_WhenCallerHasAdminRole(bytes32 role) external { + seedPausedRole(address(facet), role, true); + + vm.expectEmit(address(facet)); + emit RoleUnpaused(role, users.admin); + + vm.prank(users.admin); + facet.unpauseRole(role); + + assertEq(address(facet).isRolePaused(role), false, "isRolePaused"); + } + + function testFuzz_ShouldRevert_UnpauseRole_WhenCallerDoesNotHaveAdminRole(bytes32 role, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.unpauseRole(role); + } + + function testFuzz_ShouldNotRevert_RequireRoleNotPaused_WhenAccountHasRoleAndRoleIsNotPaused( + bytes32 role, + address account + ) external { + seedRole(address(facet), role, account); + + facet.requireRoleNotPaused(role, account); + } + + function testFuzz_ShouldRevert_RequireRoleNotPaused_WhenAccountDoesNotHaveRole(bytes32 role, address account) + external + { + vm.expectRevert( + abi.encodeWithSelector(AccessControlPausableFacet.AccessControlUnauthorizedAccount.selector, account, role) + ); + facet.requireRoleNotPaused(role, account); + } + + function testFuzz_ShouldRevert_RequireRoleNotPaused_WhenRoleIsPaused(bytes32 role, address account) external { + seedRole(address(facet), role, account); + seedPausedRole(address(facet), role, true); + + vm.expectRevert(abi.encodeWithSelector(AccessControlPausableFacet.AccessControlRolePaused.selector, role)); + facet.requireRoleNotPaused(role, account); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlPausableFacet.isRolePaused.selector, + AccessControlPausableFacet.pauseRole.selector, + AccessControlPausableFacet.unpauseRole.selector, + AccessControlPausableFacet.requireRoleNotPaused.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol new file mode 100644 index 00000000..244fb569 --- /dev/null +++ b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlPausable_Base_Test} from "test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlPausableModHarness} from "test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Pausable_AccessControlPausableMod_Fuzz_Unit_Test is AccessControlPausable_Base_Test { + using AccessControlStorageUtils for address; + + event RolePaused(bytes32 indexed _role, address indexed _account); + event RoleUnpaused(bytes32 indexed _role, address indexed _account); + + AccessControlPausableModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlPausableModHarness(); + vm.label(address(harness), "AccessControlPausableModHarness"); + } + + function testFuzz_ShouldReturnFalse_IsRolePaused_WhenRoleIsNotPaused(bytes32 role) external view { + assertEq(harness.isRolePaused(role), false, "isRolePaused"); + } + + function testFuzz_ShouldReturnTrue_IsRolePaused_WhenRoleIsPaused(bytes32 role) external { + seedPausedRole(address(harness), role, true); + + assertEq(harness.isRolePaused(role), true, "isRolePaused"); + } + + function testFuzz_ShouldPauseRole_ForCurrentSurfaceSemantics(bytes32 role, address caller) external { + vm.expectEmit(address(harness)); + emit RolePaused(role, caller); + + vm.prank(caller); + harness.pauseRole(role); + + assertEq(address(harness).isRolePaused(role), true, "isRolePaused"); + } + + function testFuzz_ShouldUnpauseRole_ForCurrentSurfaceSemantics(bytes32 role, address caller) external { + seedPausedRole(address(harness), role, true); + + vm.expectEmit(address(harness)); + emit RoleUnpaused(role, caller); + + vm.prank(caller); + harness.unpauseRole(role); + + assertEq(address(harness).isRolePaused(role), false, "isRolePaused"); + } + + function testFuzz_ShouldNotRevert_RequireRoleNotPaused_WhenAccountHasRoleAndRoleIsNotPaused( + bytes32 role, + address account + ) external { + seedRole(address(harness), role, account); + + harness.requireRoleNotPaused(role, account); + } + + function testFuzz_ShouldRevert_RequireRoleNotPaused_WhenAccountDoesNotHaveRole(bytes32 role, address account) + external + { + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role) + ); + harness.requireRoleNotPaused(role, account); + } + + function testFuzz_ShouldRevert_RequireRoleNotPaused_WhenRoleIsPaused(bytes32 role, address account) external { + seedRole(address(harness), role, account); + seedPausedRole(address(harness), role, true); + + vm.expectRevert(abi.encodeWithSignature("AccessControlRolePaused(bytes32)", role)); + harness.requireRoleNotPaused(role, account); + } +} diff --git a/test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol b/test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol new file mode 100644 index 00000000..768b139e --- /dev/null +++ b/test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; + +abstract contract AccessControlRenounce_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } +} diff --git a/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol b/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol new file mode 100644 index 00000000..e8b80dab --- /dev/null +++ b/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRenounce_Base_Test} from "test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlRenounceFacet} from "src/access/AccessControl/Renounce/AccessControlRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RenounceRole_AccessControlRenounceFacet_Fuzz_Unit_Test is AccessControlRenounce_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlRenounceFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlRenounceFacet(); + vm.label(address(facet), "AccessControlRenounceFacet"); + } + + function testFuzz_ShouldRenounceRole_WhenSenderMatchesAccount(bytes32 role, address account) external { + address(facet).setHasRole(account, role, true); + + vm.expectEmit(address(facet)); + emit RoleRevoked(role, account, account); + + vm.prank(account); + facet.renounceRole(role, account); + + assertEq(address(facet).hasRole(account, role), false, "hasRole"); + } + + function testFuzz_ShouldNotEmit_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(account); + facet.renounceRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenSenderDoesNotMatchAccount(bytes32 role, address sender, address account) + external + { + vm.assume(sender != account); + + vm.expectRevert( + abi.encodeWithSelector(AccessControlRenounceFacet.AccessControlUnauthorizedSender.selector, sender, account) + ); + vm.prank(sender); + facet.renounceRole(role, account); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRenounceFacet.renounceRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol new file mode 100644 index 00000000..fbe77c4b --- /dev/null +++ b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRenounce_Base_Test} from "test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RenounceRole_AccessControlRenounceMod_Fuzz_Unit_Test is AccessControlRenounce_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + } + + function testFuzz_ShouldRenounceRole_WhenSenderMatchesAccount(bytes32 role, address account) external { + address(harness).setHasRole(account, role, true); + + vm.expectEmit(address(harness)); + emit RoleRevoked(role, account, account); + + vm.prank(account); + harness.renounceRole(role, account); + + assertEq(address(harness).hasRole(account, role), false, "hasRole"); + } + + function testFuzz_ShouldNotEmit_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(account); + harness.renounceRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenSenderDoesNotMatchAccount(bytes32 role, address sender, address account) + external + { + vm.assume(sender != account); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedSender(address,address)", sender, account) + ); + vm.prank(sender); + harness.renounceRole(role, account); + } +} diff --git a/test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol b/test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol new file mode 100644 index 00000000..d417bd28 --- /dev/null +++ b/test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlRevoke_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedAdminRole(address target, bytes32 role, bytes32 adminRole_) internal { + target.setAdminRole(role, adminRole_); + } +} diff --git a/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol new file mode 100644 index 00000000..0ef5373f --- /dev/null +++ b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRevoke_Base_Test} from "test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlRevokeFacet} from "src/access/AccessControl/Revoke/AccessControlRevokeFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeRole_AccessControlRevokeFacet_Fuzz_Unit_Test is AccessControlRevoke_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlRevokeFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlRevokeFacet(); + vm.label(address(facet), "AccessControlRevokeFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldRevokeRole_WhenCallerHasAdminRole(bytes32 role, address account) external { + address(facet).setHasRole(account, role, true); + + vm.expectEmit(address(facet)); + emit RoleRevoked(role, account, users.admin); + + vm.prank(users.admin); + facet.revokeRole(role, account); + + assertEq(address(facet).hasRole(account, role), false, "hasRole"); + } + + function testFuzz_ShouldNotEmit_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(users.admin); + facet.revokeRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + assertEq(address(facet).hasRole(account, role), false, "hasRole"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector(AccessControlRevokeFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + facet.revokeRole(role, account); + } + + function testFuzz_ShouldRevokeRole_WhenRoleIsSelfAdmin(address caller, address account) external { + seedAdminRole(address(facet), MINTER_ROLE, MINTER_ROLE); + seedRole(address(facet), MINTER_ROLE, caller); + seedRole(address(facet), MINTER_ROLE, account); + + vm.prank(caller); + facet.revokeRole(MINTER_ROLE, account); + + assertEq(address(facet).hasRole(account, MINTER_ROLE), false, "hasRole"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRevokeFacet.revokeRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol b/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol new file mode 100644 index 00000000..46c1d116 --- /dev/null +++ b/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlRevoke_Base_Test} from "test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeRole_AccessControlRevokeMod_Fuzz_Unit_Test is AccessControlRevoke_Base_Test { + using AccessControlStorageUtils for address; + + event RoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlCoreModHarness(); + vm.label(address(harness), "AccessControlCoreModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldReturnTrue_WhenCallerHasAdminRole(bytes32 role, address account) external { + address(harness).setHasRole(account, role, true); + + vm.expectEmit(address(harness)); + emit RoleRevoked(role, account, users.admin); + + vm.prank(users.admin); + bool result = harness.revokeRole(role, account); + + assertEq(result, true, "revokeRole"); + assertEq(address(harness).hasRole(account, role), false, "hasRole"); + } + + function testFuzz_ShouldReturnFalse_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(users.admin); + bool result = harness.revokeRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(result, false, "revokeRole"); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.revokeRole(role, account); + } + + function testFuzz_ShouldReturnTrue_WhenRoleIsSelfAdmin(address caller, address account) external { + seedAdminRole(address(harness), MINTER_ROLE, MINTER_ROLE); + seedRole(address(harness), MINTER_ROLE, caller); + seedRole(address(harness), MINTER_ROLE, account); + + vm.prank(caller); + bool result = harness.revokeRole(MINTER_ROLE, account); + + assertEq(result, true, "revokeRole"); + assertEq(address(harness).hasRole(account, MINTER_ROLE), false, "hasRole"); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol b/test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol new file mode 100644 index 00000000..f930b204 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlTemporalData_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedRoleExpiry(address target, bytes32 role, address account, uint256 expiry) internal { + target.setRoleExpiry(account, role, expiry); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..29053d4d --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlTemporalData_Base_Test} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; +import {AccessControlTemporalDataFacet} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Data_AccessControlTemporalDataFacet_Fuzz_Unit_Test is AccessControlTemporalData_Base_Test { + AccessControlTemporalDataFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlTemporalDataFacet(); + vm.label(address(facet), "AccessControlTemporalDataFacet"); + } + + function testFuzz_ShouldReturnZero_GetRoleExpiry_WhenExpiryNotSet(bytes32 role, address account) external view { + assertEq(facet.getRoleExpiry(role, account), 0, "getRoleExpiry"); + } + + function testFuzz_ShouldReturnStoredExpiry_GetRoleExpiry(bytes32 role, address account, uint256 expiry) external { + seedRoleExpiry(address(facet), role, account, expiry); + + assertEq(facet.getRoleExpiry(role, account), expiry, "getRoleExpiry"); + } + + function testFuzz_ShouldReturnTrue_IsRoleExpired_WhenNoExpiryAndNoRole(bytes32 role, address account) + external + view + { + assertEq(facet.isRoleExpired(role, account), true, "isRoleExpired"); + } + + function testFuzz_ShouldReturnFalse_IsRoleExpired_WhenNoExpiryAndRoleExists(bytes32 role, address account) + external + { + seedRole(address(facet), role, account); + + assertEq(facet.isRoleExpired(role, account), false, "isRoleExpired"); + } + + function testFuzz_ShouldReturnFalse_IsRoleExpired_WhenExpiryIsInFuture( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRoleExpiry(address(facet), role, account, expiry); + + assertEq(facet.isRoleExpired(role, account), false, "isRoleExpired"); + } + + function testFuzz_ShouldReturnTrue_IsRoleExpired_WhenTimestampReachesExpiry( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRoleExpiry(address(facet), role, account, expiry); + + vm.warp(expiry); + assertEq(facet.isRoleExpired(role, account), true, "isRoleExpired"); + } + + function testFuzz_ShouldNotRevert_RequireValidRole_WhenRoleExistsWithoutExpiry(bytes32 role, address account) + external + { + seedRole(address(facet), role, account); + + facet.requireValidRole(role, account); + } + + function testFuzz_ShouldNotRevert_RequireValidRole_WhenExpiryIsInFuture( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + seedRole(address(facet), role, account); + seedRoleExpiry(address(facet), role, account, block.timestamp + expiryOffset); + + facet.requireValidRole(role, account); + } + + function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) + external + { + vm.expectRevert( + abi.encodeWithSelector(AccessControlTemporalDataFacet.AccessControlUnauthorizedAccount.selector, account, role) + ); + facet.requireValidRole(role, account); + } + + function testFuzz_ShouldRevert_RequireValidRole_WhenRoleHasExpired( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRole(address(facet), role, account); + seedRoleExpiry(address(facet), role, account, expiry); + + vm.warp(expiry); + vm.expectRevert( + abi.encodeWithSelector(AccessControlTemporalDataFacet.AccessControlRoleExpired.selector, role, account) + ); + facet.requireValidRole(role, account); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlTemporalDataFacet.getRoleExpiry.selector, + AccessControlTemporalDataFacet.isRoleExpired.selector, + AccessControlTemporalDataFacet.requireValidRole.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol new file mode 100644 index 00000000..0fefc868 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlTemporalData_Base_Test} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; +import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract Data_AccessControlTemporalDataMod_Fuzz_Unit_Test is AccessControlTemporalData_Base_Test { + AccessControlTemporalModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlTemporalModHarness(); + vm.label(address(harness), "AccessControlTemporalModHarness"); + } + + function testFuzz_ShouldReturnZero_GetRoleExpiry_WhenExpiryNotSet(bytes32 role, address account) external view { + assertEq(harness.getRoleExpiry(role, account), 0, "getRoleExpiry"); + } + + function testFuzz_ShouldReturnStoredExpiry_GetRoleExpiry(bytes32 role, address account, uint256 expiry) external { + seedRoleExpiry(address(harness), role, account, expiry); + + assertEq(harness.getRoleExpiry(role, account), expiry, "getRoleExpiry"); + } + + function testFuzz_ShouldReturnTrue_IsRoleExpired_WhenNoExpiryAndNoRole(bytes32 role, address account) + external + view + { + assertEq(harness.isRoleExpired(role, account), true, "isRoleExpired"); + } + + function testFuzz_ShouldReturnFalse_IsRoleExpired_WhenNoExpiryAndRoleExists(bytes32 role, address account) + external + { + seedRole(address(harness), role, account); + + assertEq(harness.isRoleExpired(role, account), false, "isRoleExpired"); + } + + function testFuzz_ShouldReturnFalse_IsRoleExpired_WhenExpiryIsInFuture( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRoleExpiry(address(harness), role, account, expiry); + + assertEq(harness.isRoleExpired(role, account), false, "isRoleExpired"); + } + + function testFuzz_ShouldReturnTrue_IsRoleExpired_WhenTimestampReachesExpiry( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRoleExpiry(address(harness), role, account, expiry); + + vm.warp(expiry); + assertEq(harness.isRoleExpired(role, account), true, "isRoleExpired"); + } + + function testFuzz_ShouldNotRevert_RequireValidRole_WhenRoleExistsWithoutExpiry(bytes32 role, address account) + external + { + seedRole(address(harness), role, account); + + harness.requireValidRole(role, account); + } + + function testFuzz_ShouldNotRevert_RequireValidRole_WhenExpiryIsInFuture( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + seedRole(address(harness), role, account); + seedRoleExpiry(address(harness), role, account, block.timestamp + expiryOffset); + + harness.requireValidRole(role, account); + } + + function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) + external + { + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role) + ); + harness.requireValidRole(role, account); + } + + function testFuzz_ShouldRevert_RequireValidRole_WhenRoleHasExpired( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiry = block.timestamp + expiryOffset; + seedRole(address(harness), role, account); + seedRoleExpiry(address(harness), role, account, expiry); + + vm.warp(expiry); + vm.expectRevert(abi.encodeWithSignature("AccessControlRoleExpired(bytes32,address)", role, account)); + harness.requireValidRole(role, account); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol b/test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol new file mode 100644 index 00000000..74e0692b --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlTemporalGrant_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol new file mode 100644 index 00000000..6eaa5f0b --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlTemporalGrant_Base_Test} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; +import {AccessControlTemporalGrantFacet} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRoleWithExpiry_AccessControlTemporalGrantFacet_Fuzz_Unit_Test is AccessControlTemporalGrant_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGrantedWithExpiry( + bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender + ); + + AccessControlTemporalGrantFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlTemporalGrantFacet(); + vm.label(address(facet), "AccessControlTemporalGrantFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldGrantRoleWithExpiry_WhenCallerHasAdminRole( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiresAt = block.timestamp + expiryOffset; + + vm.expectEmit(address(facet)); + emit RoleGrantedWithExpiry(role, account, expiresAt, users.admin); + + vm.prank(users.admin); + facet.grantRoleWithExpiry(role, account, expiresAt); + + assertEq(address(facet).hasRole(account, role), true, "hasRole"); + assertEq(address(facet).roleExpiry(account, role), expiresAt, "roleExpiry"); + } + + function testFuzz_ShouldRevert_WhenExpiryIsNotInTheFuture(bytes32 role, address account, uint256 expiresAt) + external + { + vm.assume(expiresAt <= block.timestamp); + + vm.expectRevert( + abi.encodeWithSelector(AccessControlTemporalGrantFacet.AccessControlRoleExpired.selector, role, account) + ); + vm.prank(users.admin); + facet.grantRoleWithExpiry(role, account, expiresAt); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole( + bytes32 role, + address account, + uint256 expiryOffset, + address caller + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlTemporalGrantFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.grantRoleWithExpiry(role, account, block.timestamp + expiryOffset); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlTemporalGrantFacet.grantRoleWithExpiry.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol new file mode 100644 index 00000000..b92b8987 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlTemporalGrant_Base_Test} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; +import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract GrantRoleWithExpiry_AccessControlTemporalGrantMod_Fuzz_Unit_Test is AccessControlTemporalGrant_Base_Test { + using AccessControlStorageUtils for address; + + event RoleGrantedWithExpiry( + bytes32 indexed _role, address indexed _account, uint256 _expiresAt, address indexed _sender + ); + + AccessControlTemporalModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlTemporalModHarness(); + vm.label(address(harness), "AccessControlTemporalModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldGrantRoleWithExpiry_WhenCallerHasAdminRole( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + uint256 expiresAt = block.timestamp + expiryOffset; + + vm.expectEmit(address(harness)); + emit RoleGrantedWithExpiry(role, account, expiresAt, users.admin); + + vm.prank(users.admin); + harness.grantRoleWithExpiry(role, account, expiresAt); + + assertEq(address(harness).hasRole(account, role), true, "hasRole"); + assertEq(address(harness).roleExpiry(account, role), expiresAt, "roleExpiry"); + } + + function testFuzz_ShouldRevert_WhenExpiryIsNotInTheFuture(bytes32 role, address account, uint256 expiresAt) + external + { + vm.assume(expiresAt <= block.timestamp); + + vm.expectRevert(abi.encodeWithSignature("AccessControlRoleExpired(bytes32,address)", role, account)); + vm.prank(users.admin); + harness.grantRoleWithExpiry(role, account, expiresAt); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole( + bytes32 role, + address account, + uint256 expiryOffset, + address caller + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.grantRoleWithExpiry(role, account, block.timestamp + expiryOffset); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol new file mode 100644 index 00000000..fe2ad558 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; + +abstract contract AccessControlTemporalRevoke_Base_Test is Base_Test { + using AccessControlStorageUtils for address; + + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedDefaultAdmin(address target) internal { + target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); + } + + function seedRole(address target, bytes32 role, address account) internal { + target.setHasRole(account, role, true); + } + + function seedRoleExpiry(address target, bytes32 role, address account, uint256 expiry) internal { + target.setRoleExpiry(account, role, expiry); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol new file mode 100644 index 00000000..cac529d7 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlTemporalRevoke_Base_Test} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; +import {AccessControlTemporalRevokeFacet} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeTemporalRole_AccessControlTemporalRevokeFacet_Fuzz_Unit_Test is AccessControlTemporalRevoke_Base_Test { + using AccessControlStorageUtils for address; + + event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlTemporalRevokeFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlTemporalRevokeFacet(); + vm.label(address(facet), "AccessControlTemporalRevokeFacet"); + seedDefaultAdmin(address(facet)); + } + + function testFuzz_ShouldRevokeTemporalRole_WhenCallerHasAdminRole( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + seedRole(address(facet), role, account); + seedRoleExpiry(address(facet), role, account, block.timestamp + expiryOffset); + + vm.expectEmit(address(facet)); + emit TemporalRoleRevoked(role, account, users.admin); + + vm.prank(users.admin); + facet.revokeTemporalRole(role, account); + + assertEq(address(facet).hasRole(account, role), false, "hasRole"); + assertEq(address(facet).roleExpiry(account, role), 0, "roleExpiry"); + } + + function testFuzz_ShouldBeNoOp_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(users.admin); + facet.revokeTemporalRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSelector( + AccessControlTemporalRevokeFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) + ); + vm.prank(caller); + facet.revokeTemporalRole(role, account); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlTemporalRevokeFacet.revokeTemporalRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol new file mode 100644 index 00000000..0bc55bfc --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {AccessControlTemporalRevoke_Base_Test} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; +import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract RevokeTemporalRole_AccessControlTemporalRevokeMod_Fuzz_Unit_Test is AccessControlTemporalRevoke_Base_Test { + using AccessControlStorageUtils for address; + + event TemporalRoleRevoked(bytes32 indexed _role, address indexed _account, address indexed _sender); + + AccessControlTemporalModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new AccessControlTemporalModHarness(); + vm.label(address(harness), "AccessControlTemporalModHarness"); + seedDefaultAdmin(address(harness)); + } + + function testFuzz_ShouldRevokeTemporalRole_WhenCallerHasAdminRole( + bytes32 role, + address account, + uint256 expiryOffset + ) external { + vm.assume(expiryOffset > 0); + vm.assume(expiryOffset < 365 days); + + seedRole(address(harness), role, account); + seedRoleExpiry(address(harness), role, account, block.timestamp + expiryOffset); + + vm.expectEmit(address(harness)); + emit TemporalRoleRevoked(role, account, users.admin); + + vm.prank(users.admin); + harness.revokeTemporalRole(role, account); + + assertEq(address(harness).hasRole(account, role), false, "hasRole"); + assertEq(address(harness).roleExpiry(account, role), 0, "roleExpiry"); + } + + function testFuzz_ShouldBeNoOp_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.recordLogs(); + vm.prank(users.admin); + harness.revokeTemporalRole(role, account); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 0, "unexpected logs"); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveAdminRole(bytes32 role, address account, address caller) + external + { + vm.assume(caller != users.admin); + + vm.expectRevert( + abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", caller, DEFAULT_ADMIN_ROLE) + ); + vm.prank(caller); + harness.revokeTemporalRole(role, account); + } +} diff --git a/test/unit/access/Owner/Data/OwnerDataBase.t.sol b/test/unit/access/Owner/Data/OwnerDataBase.t.sol new file mode 100644 index 00000000..ad359527 --- /dev/null +++ b/test/unit/access/Owner/Data/OwnerDataBase.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerData_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } +} diff --git a/test/unit/access/Owner/Data/facet/fuzz/data.t.sol b/test/unit/access/Owner/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..f17f7b93 --- /dev/null +++ b/test/unit/access/Owner/Data/facet/fuzz/data.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerData_Base_Test} from "test/unit/access/Owner/Data/OwnerDataBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerDataFacet} from "src/access/Owner/Data/OwnerDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract Data_OwnerDataFacet_Fuzz_Unit_Test is OwnerData_Base_Test { + OwnerDataFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerDataFacet(); + vm.label(address(facet), "OwnerDataFacet"); + } + + function testFuzz_ShouldReturnStoredOwner_Owner_WhenOwnerHasBeenSet(address owner_) external { + seedOwner(address(facet), owner_); + + assertEq(facet.owner(), owner_, "owner"); + } + + function testFuzz_ShouldReturnZero_Owner_WhenOwnerHasBeenRenounced() external { + seedOwner(address(facet), address(0)); + + assertEq(facet.owner(), address(0), "owner"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerDataFacet.owner.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/Data/mod/fuzz/data.t.sol b/test/unit/access/Owner/Data/mod/fuzz/data.t.sol new file mode 100644 index 00000000..b6618d8a --- /dev/null +++ b/test/unit/access/Owner/Data/mod/fuzz/data.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerData_Base_Test} from "test/unit/access/Owner/Data/OwnerDataBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract Data_OwnerMod_Fuzz_Unit_Test is OwnerData_Base_Test { + OwnerCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerCoreModHarness(); + vm.label(address(harness), "OwnerCoreModHarness"); + } + + function testFuzz_ShouldReturnStoredOwner_Owner_WhenOwnerHasBeenSet(address owner_) external { + seedOwner(address(harness), owner_); + + assertEq(harness.owner(), owner_, "owner"); + } + + function testFuzz_ShouldReturnZero_Owner_WhenOwnerHasBeenRenounced() external { + seedOwner(address(harness), address(0)); + + assertEq(harness.owner(), address(0), "owner"); + } + + function testFuzz_ShouldNotRevert_RequireOwner_WhenCallerIsOwner(address owner_) external { + vm.assume(owner_ != address(0)); + seedOwner(address(harness), owner_); + + vm.prank(owner_); + harness.requireOwner(); + } + + function testFuzz_ShouldRevert_RequireOwner_WhenCallerIsNotOwner(address owner_, address caller) external { + vm.assume(owner_ != address(0)); + vm.assume(caller != owner_); + seedOwner(address(harness), owner_); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.requireOwner(); + } +} diff --git a/test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol b/test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol new file mode 100644 index 00000000..04344bac --- /dev/null +++ b/test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerRenounce_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } +} diff --git a/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol new file mode 100644 index 00000000..806e0e65 --- /dev/null +++ b/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerRenounce_Base_Test} from "test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerRenounceFacet} from "src/access/Owner/Renounce/OwnerRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract RenounceOwnership_OwnerRenounceFacet_Fuzz_Unit_Test is OwnerRenounce_Base_Test { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + OwnerRenounceFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerRenounceFacet(); + vm.label(address(facet), "OwnerRenounceFacet"); + seedOwner(address(facet), users.admin); + } + + function testFuzz_ShouldSetOwnerToZero_RenounceOwnership_WhenCallerIsOwner() external { + vm.prank(users.admin); + facet.renounceOwnership(); + + assertEq(OwnerStorageUtils.owner(address(facet)), address(0), "owner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_RenounceOwnership_WhenCallerIsOwner() external { + vm.expectEmit(address(facet)); + emit OwnershipTransferred(users.admin, address(0)); + + vm.prank(users.admin); + facet.renounceOwnership(); + } + + function testFuzz_ShouldRevert_RenounceOwnership_WhenCallerIsNotOwner(address caller) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(OwnerRenounceFacet.OwnerUnauthorizedAccount.selector); + facet.renounceOwnership(); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerRenounceFacet.renounceOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol new file mode 100644 index 00000000..5f1ad4b0 --- /dev/null +++ b/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerRenounce_Base_Test} from "test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract RenounceOwnership_OwnerMod_Fuzz_Unit_Test is OwnerRenounce_Base_Test { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + OwnerCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerCoreModHarness(); + vm.label(address(harness), "OwnerCoreModHarness"); + seedOwner(address(harness), users.admin); + } + + function testFuzz_ShouldSetOwnerToZero_RenounceOwnership_WhenCallerIsOwner() external { + vm.prank(users.admin); + harness.renounceOwnership(); + + assertEq(OwnerStorageUtils.owner(address(harness)), address(0), "owner"); + assertEq(harness.owner(), address(0), "owner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_RenounceOwnership_WhenCallerIsOwner() external { + vm.expectEmit(address(harness)); + emit OwnershipTransferred(users.admin, address(0)); + + vm.prank(users.admin); + harness.renounceOwnership(); + } + + function testFuzz_ShouldRevert_RenounceOwnership_WhenCallerIsNotOwner(address caller) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.renounceOwnership(); + } +} diff --git a/test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol b/test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol new file mode 100644 index 00000000..9c5c6939 --- /dev/null +++ b/test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerTransfer_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } +} diff --git a/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol new file mode 100644 index 00000000..7c086b37 --- /dev/null +++ b/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTransfer_Base_Test} from "test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTransferFacet} from "src/access/Owner/Transfer/OwnerTransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract TransferOwnership_OwnerTransferFacet_Fuzz_Unit_Test is OwnerTransfer_Base_Test { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + OwnerTransferFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerTransferFacet(); + vm.label(address(facet), "OwnerTransferFacet"); + seedOwner(address(facet), users.admin); + } + + function testFuzz_ShouldUpdateOwner_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.prank(users.admin); + facet.transferOwnership(newOwner); + + assertEq(OwnerStorageUtils.owner(address(facet)), newOwner, "owner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.expectEmit(address(facet)); + emit OwnershipTransferred(users.admin, newOwner); + + vm.prank(users.admin); + facet.transferOwnership(newOwner); + } + + function testFuzz_ShouldSetOwnerToZero_TransferOwnership_WhenNewOwnerIsZero() external { + vm.prank(users.admin); + facet.transferOwnership(address(0)); + + assertEq(OwnerStorageUtils.owner(address(facet)), address(0), "owner"); + } + + function testFuzz_ShouldLeaveOwnerUnchanged_TransferOwnership_WhenNewOwnerIsSelf(address owner_) external { + vm.assume(owner_ != address(0)); + seedOwner(address(facet), owner_); + + vm.prank(owner_); + facet.transferOwnership(owner_); + + assertEq(OwnerStorageUtils.owner(address(facet)), owner_, "owner"); + } + + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(OwnerTransferFacet.OwnerUnauthorizedAccount.selector); + facet.transferOwnership(newOwner); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTransferFacet.transferOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol new file mode 100644 index 00000000..a153e900 --- /dev/null +++ b/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTransfer_Base_Test} from "test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract TransferOwnership_OwnerMod_Fuzz_Unit_Test is OwnerTransfer_Base_Test { + event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); + + OwnerCoreModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerCoreModHarness(); + vm.label(address(harness), "OwnerCoreModHarness"); + seedOwner(address(harness), users.admin); + } + + function testFuzz_ShouldUpdateOwner_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.prank(users.admin); + harness.transferOwnership(newOwner); + + assertEq(OwnerStorageUtils.owner(address(harness)), newOwner, "owner"); + assertEq(harness.owner(), newOwner, "owner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.expectEmit(address(harness)); + emit OwnershipTransferred(users.admin, newOwner); + + vm.prank(users.admin); + harness.transferOwnership(newOwner); + } + + function testFuzz_ShouldSetOwnerToZero_TransferOwnership_WhenNewOwnerIsZero() external { + vm.prank(users.admin); + harness.transferOwnership(address(0)); + + assertEq(harness.owner(), address(0), "owner"); + } + + function testFuzz_ShouldLeaveOwnerUnchanged_TransferOwnership_WhenNewOwnerIsSelf(address owner_) external { + vm.assume(owner_ != address(0)); + seedOwner(address(harness), owner_); + + vm.prank(owner_); + harness.transferOwnership(owner_); + + assertEq(harness.owner(), owner_, "owner"); + } + + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.transferOwnership(newOwner); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol b/test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol new file mode 100644 index 00000000..8953cad1 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerTwoStepData_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } + + function seedPendingOwner(address target, address pendingOwner_) internal { + OwnerStorageUtils.setPendingOwner(target, pendingOwner_); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..636040f3 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepData_Base_Test} from "test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepDataFacet} from "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract Data_OwnerTwoStepDataFacet_Fuzz_Unit_Test is OwnerTwoStepData_Base_Test { + OwnerTwoStepDataFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerTwoStepDataFacet(); + vm.label(address(facet), "OwnerTwoStepDataFacet"); + } + + function testFuzz_ShouldReturnStoredPendingOwner_PendingOwner_WhenPendingOwnerHasBeenSet(address pendingOwner_) + external + { + seedPendingOwner(address(facet), pendingOwner_); + + assertEq(facet.pendingOwner(), pendingOwner_, "pendingOwner"); + } + + function testFuzz_ShouldReturnZero_PendingOwner_WhenNoPendingOwnerHasBeenSet() external view { + assertEq(facet.pendingOwner(), address(0), "pendingOwner"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTwoStepDataFacet.pendingOwner.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol b/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol new file mode 100644 index 00000000..3936c0e4 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepData_Base_Test} from "test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract Data_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepData_Base_Test { + OwnerTwoStepModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerTwoStepModHarness(); + vm.label(address(harness), "OwnerTwoStepModHarness"); + } + + function testFuzz_ShouldReturnStoredPendingOwner_PendingOwner_WhenPendingOwnerHasBeenSet(address pendingOwner_) + external + { + seedPendingOwner(address(harness), pendingOwner_); + + assertEq(harness.pendingOwner(), pendingOwner_, "pendingOwner"); + } + + function testFuzz_ShouldReturnZero_PendingOwner_WhenNoPendingOwnerHasBeenSet() external view { + assertEq(harness.pendingOwner(), address(0), "pendingOwner"); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol new file mode 100644 index 00000000..999b96a8 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerTwoStepRenounce_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } + + function seedPendingOwner(address target, address pendingOwner_) internal { + OwnerStorageUtils.setPendingOwner(target, pendingOwner_); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol new file mode 100644 index 00000000..c4a96bf8 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepRenounce_Base_Test} from "test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepRenounceFacet} from "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract RenounceOwnership_OwnerTwoStepRenounceFacet_Fuzz_Unit_Test is OwnerTwoStepRenounce_Base_Test { + event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); + + OwnerTwoStepRenounceFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerTwoStepRenounceFacet(); + vm.label(address(facet), "OwnerTwoStepRenounceFacet"); + seedOwner(address(facet), users.admin); + } + + function testFuzz_ShouldSetOwnerAndPendingToZero_RenounceOwnership_WhenCallerIsOwner() external { + seedPendingOwner(address(facet), users.bob); + + vm.prank(users.admin); + facet.renounceOwnership(); + + assertEq(OwnerStorageUtils.owner(address(facet)), address(0), "owner"); + assertEq(OwnerStorageUtils.pendingOwner(address(facet)), address(0), "pendingOwner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_RenounceOwnership_WhenCallerIsOwner() external { + vm.expectEmit(address(facet)); + emit OwnershipTransferred(users.admin, address(0)); + + vm.prank(users.admin); + facet.renounceOwnership(); + } + + function testFuzz_ShouldRevert_RenounceOwnership_WhenCallerIsNotOwner(address caller) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(OwnerTwoStepRenounceFacet.OwnerUnauthorizedAccount.selector); + facet.renounceOwnership(); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTwoStepRenounceFacet.renounceOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol new file mode 100644 index 00000000..0a529b8d --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepRenounce_Base_Test} from "test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract RenounceOwnership_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepRenounce_Base_Test { + event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); + + OwnerTwoStepModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerTwoStepModHarness(); + vm.label(address(harness), "OwnerTwoStepModHarness"); + seedOwner(address(harness), users.admin); + } + + function testFuzz_ShouldSetOwnerAndPendingToZero_RenounceOwnership_WhenCallerIsOwner() external { + seedPendingOwner(address(harness), users.bob); + + vm.prank(users.admin); + harness.renounceOwnership(); + + assertEq(OwnerStorageUtils.owner(address(harness)), address(0), "owner"); + assertEq(OwnerStorageUtils.pendingOwner(address(harness)), address(0), "pendingOwner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_RenounceOwnership_WhenCallerIsOwner() external { + vm.expectEmit(address(harness)); + emit OwnershipTransferred(users.admin, address(0)); + + vm.prank(users.admin); + harness.renounceOwnership(); + } + + function testFuzz_ShouldRevert_RenounceOwnership_WhenCallerIsNotOwner(address caller) external { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.renounceOwnership(); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol new file mode 100644 index 00000000..8b5b24c2 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30 <0.9.0; + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; + +abstract contract OwnerTwoStepTransfer_Base_Test is Base_Test { + function setUp() public virtual override { + Base_Test.setUp(); + vm.stopPrank(); + } + + function seedOwner(address target, address owner_) internal { + OwnerStorageUtils.setOwner(target, owner_); + } + + function seedPendingOwner(address target, address pendingOwner_) internal { + OwnerStorageUtils.setPendingOwner(target, pendingOwner_); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol new file mode 100644 index 00000000..fdc880aa --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepTransfer_Base_Test} from "test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepTransferFacet} from "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoStepTransfer_Base_Test { + event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner); + event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); + + OwnerTwoStepTransferFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new OwnerTwoStepTransferFacet(); + vm.label(address(facet), "OwnerTwoStepTransferFacet"); + seedOwner(address(facet), users.admin); + } + + function testFuzz_ShouldSetPendingOwner_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.prank(users.admin); + facet.transferOwnership(newOwner); + + assertEq(OwnerStorageUtils.pendingOwner(address(facet)), newOwner, "pendingOwner"); + assertEq(OwnerStorageUtils.owner(address(facet)), users.admin, "owner unchanged"); + } + + function testFuzz_ShouldEmitOwnershipTransferStarted_TransferOwnership_WhenCallerIsOwner(address newOwner) + external + { + vm.expectEmit(address(facet)); + emit OwnershipTransferStarted(users.admin, newOwner); + + vm.prank(users.admin); + facet.transferOwnership(newOwner); + } + + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) + external + { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(OwnerTwoStepTransferFacet.OwnerUnauthorizedAccount.selector); + facet.transferOwnership(newOwner); + } + + function testFuzz_ShouldSetOwnerAndClearPending_AcceptOwnership_WhenCallerIsPendingOwner(address newOwner) + external + { + vm.assume(newOwner != address(0)); + seedOwner(address(facet), users.admin); + seedPendingOwner(address(facet), newOwner); + + vm.prank(newOwner); + facet.acceptOwnership(); + + assertEq(OwnerStorageUtils.owner(address(facet)), newOwner, "owner"); + assertEq(OwnerStorageUtils.pendingOwner(address(facet)), address(0), "pendingOwner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_AcceptOwnership_WhenCallerIsPendingOwner(address newOwner) + external + { + vm.assume(newOwner != address(0)); + seedOwner(address(facet), users.admin); + seedPendingOwner(address(facet), newOwner); + + vm.expectEmit(address(facet)); + emit OwnershipTransferred(users.admin, newOwner); + + vm.prank(newOwner); + facet.acceptOwnership(); + } + + function testFuzz_ShouldRevert_AcceptOwnership_WhenCallerIsNotPendingOwner(address pending, address caller) + external + { + vm.assume(pending != address(0)); + vm.assume(caller != pending); + seedOwner(address(facet), users.admin); + seedPendingOwner(address(facet), pending); + + vm.prank(caller); + vm.expectRevert(OwnerTwoStepTransferFacet.OwnerUnauthorizedAccount.selector); + facet.acceptOwnership(); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + OwnerTwoStepTransferFacet.transferOwnership.selector, + OwnerTwoStepTransferFacet.acceptOwnership.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol new file mode 100644 index 00000000..f3b0da1f --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {OwnerTwoStepTransfer_Base_Test} from "test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol"; +import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; +import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract TransferOwnership_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepTransfer_Base_Test { + event OwnershipTransferStarted(address indexed _previousOwner, address indexed _newOwner); + event OwnershipTransferred(address indexed _previousOwner, address indexed _newOwner); + + OwnerTwoStepModHarness internal harness; + + function setUp() public override { + super.setUp(); + harness = new OwnerTwoStepModHarness(); + vm.label(address(harness), "OwnerTwoStepModHarness"); + seedOwner(address(harness), users.admin); + } + + function testFuzz_ShouldSetPendingOwner_TransferOwnership_WhenCallerIsOwner(address newOwner) external { + vm.prank(users.admin); + harness.transferOwnership(newOwner); + + assertEq(OwnerStorageUtils.pendingOwner(address(harness)), newOwner, "pendingOwner"); + assertEq(harness.owner(), users.admin, "owner unchanged"); + } + + function testFuzz_ShouldEmitOwnershipTransferStarted_TransferOwnership_WhenCallerIsOwner(address newOwner) + external + { + vm.expectEmit(address(harness)); + emit OwnershipTransferStarted(users.admin, newOwner); + + vm.prank(users.admin); + harness.transferOwnership(newOwner); + } + + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) + external + { + vm.assume(caller != users.admin); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.transferOwnership(newOwner); + } + + function testFuzz_ShouldSetOwnerAndClearPending_AcceptOwnership_WhenCallerIsPendingOwner(address newOwner) + external + { + vm.assume(newOwner != address(0)); + seedOwner(address(harness), users.admin); + seedPendingOwner(address(harness), newOwner); + + vm.prank(newOwner); + harness.acceptOwnership(); + + assertEq(harness.owner(), newOwner, "owner"); + assertEq(OwnerStorageUtils.pendingOwner(address(harness)), address(0), "pendingOwner"); + } + + function testFuzz_ShouldEmitOwnershipTransferred_AcceptOwnership_WhenCallerIsPendingOwner(address newOwner) + external + { + vm.assume(newOwner != address(0)); + seedOwner(address(harness), users.admin); + seedPendingOwner(address(harness), newOwner); + + vm.expectEmit(address(harness)); + emit OwnershipTransferred(users.admin, newOwner); + + vm.prank(newOwner); + harness.acceptOwnership(); + } + + function testFuzz_ShouldRevert_AcceptOwnership_WhenCallerIsNotPendingOwner(address pending, address caller) + external + { + vm.assume(pending != address(0)); + vm.assume(caller != pending); + seedOwner(address(harness), users.admin); + seedPendingOwner(address(harness), pending); + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.acceptOwnership(); + } +} diff --git a/test/utils/storage/AccessControlStorageUtils.sol b/test/utils/storage/AccessControlStorageUtils.sol new file mode 100644 index 00000000..7b902841 --- /dev/null +++ b/test/utils/storage/AccessControlStorageUtils.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +/** + * @title AccessControlStorageUtils + * @notice Storage manipulation utilities for AccessControl-related testing. + * @dev Uses vm.load and vm.store to directly manipulate split AccessControl storage. + */ +library AccessControlStorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant ACCESS_CONTROL_STORAGE_POSITION = keccak256("compose.accesscontrol"); + bytes32 internal constant PAUSABLE_STORAGE_POSITION = keccak256("compose.accesscontrol.pausable"); + bytes32 internal constant TEMPORAL_STORAGE_POSITION = keccak256("compose.accesscontrol.temporal"); + + function hasRole(address target, address account, bytes32 role) internal view returns (bool) { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(ACCESS_CONTROL_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(role, accountSlot)); + return uint256(vm.load(target, slot)) != 0; + } + + function adminRole(address target, bytes32 role) internal view returns (bytes32) { + bytes32 slot = keccak256(abi.encode(role, uint256(ACCESS_CONTROL_STORAGE_POSITION) + 1)); + return vm.load(target, slot); + } + + function isRolePaused(address target, bytes32 role) internal view returns (bool) { + bytes32 slot = keccak256(abi.encode(role, uint256(PAUSABLE_STORAGE_POSITION))); + return uint256(vm.load(target, slot)) != 0; + } + + function roleExpiry(address target, address account, bytes32 role) internal view returns (uint256) { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(TEMPORAL_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(role, accountSlot)); + return uint256(vm.load(target, slot)); + } + + function setHasRole(address target, address account, bytes32 role, bool value) internal { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(ACCESS_CONTROL_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(role, accountSlot)); + vm.store(target, slot, value ? bytes32(uint256(1)) : bytes32(0)); + } + + function setAdminRole(address target, bytes32 role, bytes32 value) internal { + bytes32 slot = keccak256(abi.encode(role, uint256(ACCESS_CONTROL_STORAGE_POSITION) + 1)); + vm.store(target, slot, value); + } + + function setPausedRole(address target, bytes32 role, bool value) internal { + bytes32 slot = keccak256(abi.encode(role, uint256(PAUSABLE_STORAGE_POSITION))); + vm.store(target, slot, value ? bytes32(uint256(1)) : bytes32(0)); + } + + function setRoleExpiry(address target, address account, bytes32 role, uint256 expiry) internal { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(TEMPORAL_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(role, accountSlot)); + vm.store(target, slot, bytes32(expiry)); + } +} diff --git a/test/utils/storage/OwnerStorageUtils.sol b/test/utils/storage/OwnerStorageUtils.sol new file mode 100644 index 00000000..ef96d086 --- /dev/null +++ b/test/utils/storage/OwnerStorageUtils.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +/** + * @title OwnerStorageUtils + * @notice Storage manipulation utilities for Owner-related testing. + * @dev Uses vm.load and vm.store to directly manipulate Owner storage. + * Slots match src/access/Owner: erc173.owner and erc173.owner.pending. + */ +library OwnerStorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant OWNER_STORAGE_POSITION = keccak256("erc173.owner"); + bytes32 internal constant PENDING_OWNER_STORAGE_POSITION = keccak256("erc173.owner.pending"); + + function owner(address target) internal view returns (address) { + return address(uint160(uint256(vm.load(target, OWNER_STORAGE_POSITION)))); + } + + function pendingOwner(address target) internal view returns (address) { + return address(uint160(uint256(vm.load(target, PENDING_OWNER_STORAGE_POSITION)))); + } + + function setOwner(address target, address value) internal { + vm.store(target, OWNER_STORAGE_POSITION, bytes32(uint256(uint160(value)))); + } + + function setPendingOwner(address target, address value) internal { + vm.store(target, PENDING_OWNER_STORAGE_POSITION, bytes32(uint256(uint160(value)))); + } +} From 8f15afe9d1b97600cba9b17ddc801cc4d2bf9f97 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 18:25:21 -0400 Subject: [PATCH 02/25] fix erc165 storage position --- test/interfaceDetection/ERC165/ERC165.t.sol | 4 ++-- test/interfaceDetection/ERC165/ERC165Facet.t.sol | 4 ++-- test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/interfaceDetection/ERC165/ERC165.t.sol b/test/interfaceDetection/ERC165/ERC165.t.sol index 006e9290..49db015f 100644 --- a/test/interfaceDetection/ERC165/ERC165.t.sol +++ b/test/interfaceDetection/ERC165/ERC165.t.sol @@ -33,12 +33,12 @@ contract LibERC165Test is Test { */ function test_GetStorage_ReturnsCorrectStoragePosition() public view { - bytes32 expectedSlot = keccak256("compose.erc165"); + bytes32 expectedSlot = keccak256("erc165"); assertEq(harness.getStoragePosition(), expectedSlot); } function test_StorageSlot_UsesCorrectPosition() public { - bytes32 expectedSlot = keccak256("compose.erc165"); + bytes32 expectedSlot = keccak256("erc165"); harness.registerInterface(IERC721_INTERFACE_ID); diff --git a/test/interfaceDetection/ERC165/ERC165Facet.t.sol b/test/interfaceDetection/ERC165/ERC165Facet.t.sol index d41a31d7..c54a171f 100644 --- a/test/interfaceDetection/ERC165/ERC165Facet.t.sol +++ b/test/interfaceDetection/ERC165/ERC165Facet.t.sol @@ -158,12 +158,12 @@ contract ERC165FacetTest is Test { */ function test_StorageSlot_UsesCorrectPosition() public view { - bytes32 expectedSlot = keccak256("compose.erc165"); + bytes32 expectedSlot = keccak256("erc165"); assertEq(erc165Facet.exposedGetStorage(), expectedSlot); } function test_StorageSlot_Consistency() public { - bytes32 expectedSlot = keccak256("compose.erc165"); + bytes32 expectedSlot = keccak256("erc165"); erc165Facet.registerInterface(IERC721_INTERFACE_ID); diff --git a/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol b/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol index 90bb3d4b..dbd6c8b8 100644 --- a/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol +++ b/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol @@ -49,10 +49,10 @@ contract ERC165Harness { } /** - * @notice Get the storage position + * @notice Get the storage position (must match ERC165Mod.sol STORAGE_POSITION) */ function getStoragePosition() external pure returns (bytes32) { - return keccak256("compose.erc165"); + return keccak256("erc165"); } /** From 819484f33e668c0f83b99c01df577332a8ff2aa9 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 18:28:13 -0400 Subject: [PATCH 03/25] format --- .../AccessControlCoreModHarness.sol | 12 +++++++++--- .../AccessControlTemporalModHarness.sol | 10 ++++++---- .../access/Owner/OwnerTwoStepModHarness.sol | 4 +++- .../Admin/facet/fuzz/setRoleAdmin.t.sol | 4 +++- .../Batch/Grant/facet/fuzz/grantRoleBatch.t.sol | 4 +++- .../Batch/Grant/mod/fuzz/grantRoleBatch.t.sol | 4 +++- .../Revoke/facet/fuzz/revokeRoleBatch.t.sol | 4 +++- .../Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol | 4 +++- .../AccessControl/Data/facet/fuzz/data.t.sol | 5 ++++- .../Grant/facet/fuzz/grantRole.t.sol | 4 +++- .../Pausable/facet/fuzz/pausable.t.sol | 4 +--- .../Pausable/mod/fuzz/pausable.t.sol | 4 +--- .../Renounce/mod/fuzz/renounceRole.t.sol | 4 +--- .../Revoke/facet/fuzz/revokeRole.t.sol | 4 +++- .../Temporal/Data/facet/fuzz/data.t.sol | 16 ++++++++++------ .../Temporal/Data/mod/fuzz/data.t.sol | 12 +++++------- .../Grant/facet/fuzz/grantRoleWithExpiry.t.sol | 8 ++++++-- .../Revoke/facet/fuzz/revokeTemporalRole.t.sol | 8 ++++++-- .../Revoke/mod/fuzz/revokeTemporalRole.t.sol | 4 +++- .../Transfer/facet/fuzz/transferOwnership.t.sol | 7 ++----- .../Transfer/mod/fuzz/transferOwnership.t.sol | 4 +--- 21 files changed, 79 insertions(+), 51 deletions(-) diff --git a/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol b/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol index 39f8b350..8bd8c2f7 100644 --- a/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol +++ b/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol @@ -13,9 +13,15 @@ import { import {setRoleAdmin as accessControlSetRoleAdmin} from "src/access/AccessControl/Admin/AccessControlAdminMod.sol"; import {grantRole as accessControlGrantRole} from "src/access/AccessControl/Grant/AccessControlGrantMod.sol"; import {revokeRole as accessControlRevokeRole} from "src/access/AccessControl/Revoke/AccessControlRevokeMod.sol"; -import {renounceRole as accessControlRenounceRole} from "src/access/AccessControl/Renounce/AccessControlRenounceMod.sol"; -import {grantRoleBatch as accessControlGrantRoleBatch} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol"; -import {revokeRoleBatch as accessControlRevokeRoleBatch} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol"; +import { + renounceRole as accessControlRenounceRole +} from "src/access/AccessControl/Renounce/AccessControlRenounceMod.sol"; +import { + grantRoleBatch as accessControlGrantRoleBatch +} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchMod.sol"; +import { + revokeRoleBatch as accessControlRevokeRoleBatch +} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchMod.sol"; contract AccessControlCoreModHarness { function DEFAULT_ADMIN_ROLE_VALUE() external pure returns (bytes32) { diff --git a/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol b/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol index f76ae4e5..5f78accf 100644 --- a/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol +++ b/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol @@ -10,10 +10,12 @@ import { isRoleExpired as accessControlIsRoleExpired, requireValidRole as accessControlRequireValidRole } from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataMod.sol"; -import {grantRoleWithExpiry as accessControlGrantRoleWithExpiry} from - "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol"; -import {revokeTemporalRole as accessControlRevokeTemporalRole} from - "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.sol"; +import { + grantRoleWithExpiry as accessControlGrantRoleWithExpiry +} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantMod.sol"; +import { + revokeTemporalRole as accessControlRevokeTemporalRole +} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeMod.sol"; contract AccessControlTemporalModHarness { function getRoleExpiry(bytes32 role, address account) external view returns (uint256) { diff --git a/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol b/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol index b9ed793b..6323de59 100644 --- a/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol +++ b/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol @@ -11,7 +11,9 @@ import { transferOwnership as twoStepTransferOwnership, acceptOwnership as twoStepAcceptOwnership } from "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferMod.sol"; -import {renounceOwnership as twoStepRenounceOwnership} from "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol"; +import { + renounceOwnership as twoStepRenounceOwnership +} from "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceMod.sol"; contract OwnerTwoStepModHarness { function owner() external view returns (address) { diff --git a/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol index 80f85172..0f6e6f3e 100644 --- a/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol +++ b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol @@ -42,7 +42,9 @@ contract SetRoleAdmin_AccessControlAdminFacet_Fuzz_Unit_Test is AccessControlAdm vm.assume(caller != users.admin); vm.expectRevert( - abi.encodeWithSelector(AccessControlAdminFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + AccessControlAdminFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) ); vm.prank(caller); facet.setRoleAdmin(role, newAdminRole); diff --git a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol index bcaced7f..ae26d3e1 100644 --- a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol @@ -7,7 +7,9 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; -import {AccessControlGrantBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; +import { + AccessControlGrantBatch_Base_Test +} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; import {AccessControlGrantBatchFacet} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol"; diff --git a/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol index 37c7242c..81b4aa38 100644 --- a/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol @@ -7,7 +7,9 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; -import {AccessControlGrantBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; +import { + AccessControlGrantBatch_Base_Test +} from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; diff --git a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol index 76be19e7..becfccee 100644 --- a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol @@ -7,7 +7,9 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; -import {AccessControlRevokeBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; +import { + AccessControlRevokeBatch_Base_Test +} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; import {AccessControlRevokeBatchFacet} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol"; diff --git a/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol index 05db8aa5..0293f43f 100644 --- a/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol @@ -7,7 +7,9 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; -import {AccessControlRevokeBatch_Base_Test} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; +import { + AccessControlRevokeBatch_Base_Test +} from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; diff --git a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol index 2ea90287..21837027 100644 --- a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol @@ -30,7 +30,10 @@ contract Data_AccessControlDataFacet_Fuzz_Unit_Test is AccessControlData_Base_Te assertEq(facet.hasRole(role, account), true, "hasRole"); } - function testFuzz_ShouldReturnFalse_HasRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) external view { + function testFuzz_ShouldReturnFalse_HasRole_WhenAccountDoesNotHaveRole(address account, bytes32 role) + external + view + { assertEq(facet.hasRole(role, account), false, "hasRole"); } diff --git a/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol index 85d4cc3f..e13bf3e8 100644 --- a/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol +++ b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol @@ -56,7 +56,9 @@ contract GrantRole_AccessControlGrantFacet_Fuzz_Unit_Test is AccessControlGrant_ vm.assume(caller != users.admin); vm.expectRevert( - abi.encodeWithSelector(AccessControlGrantFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + AccessControlGrantFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) ); vm.prank(caller); facet.grantRole(role, account); diff --git a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol index 9d0b0e79..01d12140 100644 --- a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol @@ -71,9 +71,7 @@ contract Pausable_AccessControlPausableFacet_Fuzz_Unit_Test is AccessControlPaus assertEq(address(facet).isRolePaused(role), false, "isRolePaused"); } - function testFuzz_ShouldRevert_UnpauseRole_WhenCallerDoesNotHaveAdminRole(bytes32 role, address caller) - external - { + function testFuzz_ShouldRevert_UnpauseRole_WhenCallerDoesNotHaveAdminRole(bytes32 role, address caller) external { vm.assume(caller != users.admin); vm.expectRevert( diff --git a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol index 244fb569..26654fdf 100644 --- a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol @@ -70,9 +70,7 @@ contract Pausable_AccessControlPausableMod_Fuzz_Unit_Test is AccessControlPausab function testFuzz_ShouldRevert_RequireRoleNotPaused_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { - vm.expectRevert( - abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role) - ); + vm.expectRevert(abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role)); harness.requireRoleNotPaused(role, account); } diff --git a/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol index fbe77c4b..8a8ebe55 100644 --- a/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol +++ b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol @@ -53,9 +53,7 @@ contract RenounceRole_AccessControlRenounceMod_Fuzz_Unit_Test is AccessControlRe { vm.assume(sender != account); - vm.expectRevert( - abi.encodeWithSignature("AccessControlUnauthorizedSender(address,address)", sender, account) - ); + vm.expectRevert(abi.encodeWithSignature("AccessControlUnauthorizedSender(address,address)", sender, account)); vm.prank(sender); harness.renounceRole(role, account); } diff --git a/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol index 0ef5373f..61d40511 100644 --- a/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol +++ b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol @@ -56,7 +56,9 @@ contract RevokeRole_AccessControlRevokeFacet_Fuzz_Unit_Test is AccessControlRevo vm.assume(caller != users.admin); vm.expectRevert( - abi.encodeWithSelector(AccessControlRevokeFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + AccessControlRevokeFacet.AccessControlUnauthorizedAccount.selector, caller, DEFAULT_ADMIN_ROLE + ) ); vm.prank(caller); facet.revokeRole(role, account); diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol index 29053d4d..f1a75576 100644 --- a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol @@ -5,8 +5,12 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import {AccessControlTemporalData_Base_Test} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; -import {AccessControlTemporalDataFacet} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; +import { + AccessControlTemporalData_Base_Test +} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; +import { + AccessControlTemporalDataFacet +} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree @@ -96,11 +100,11 @@ contract Data_AccessControlTemporalDataFacet_Fuzz_Unit_Test is AccessControlTemp facet.requireValidRole(role, account); } - function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) - external - { + function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { vm.expectRevert( - abi.encodeWithSelector(AccessControlTemporalDataFacet.AccessControlUnauthorizedAccount.selector, account, role) + abi.encodeWithSelector( + AccessControlTemporalDataFacet.AccessControlUnauthorizedAccount.selector, account, role + ) ); facet.requireValidRole(role, account); } diff --git a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol index 0fefc868..9e80cb63 100644 --- a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol @@ -5,7 +5,9 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import {AccessControlTemporalData_Base_Test} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; +import { + AccessControlTemporalData_Base_Test +} from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** @@ -96,12 +98,8 @@ contract Data_AccessControlTemporalDataMod_Fuzz_Unit_Test is AccessControlTempor harness.requireValidRole(role, account); } - function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) - external - { - vm.expectRevert( - abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role) - ); + function testFuzz_ShouldRevert_RequireValidRole_WhenAccountDoesNotHaveRole(bytes32 role, address account) external { + vm.expectRevert(abi.encodeWithSignature("AccessControlUnauthorizedAccount(address,bytes32)", account, role)); harness.requireValidRole(role, account); } diff --git a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol index 6eaa5f0b..f1dd1c15 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol @@ -6,8 +6,12 @@ pragma solidity >=0.8.30; */ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlTemporalGrant_Base_Test} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; -import {AccessControlTemporalGrantFacet} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; +import { + AccessControlTemporalGrant_Base_Test +} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; +import { + AccessControlTemporalGrantFacet +} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol index cac529d7..c8987d7e 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol @@ -8,8 +8,12 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlTemporalRevoke_Base_Test} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; -import {AccessControlTemporalRevokeFacet} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; +import { + AccessControlTemporalRevoke_Base_Test +} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; +import { + AccessControlTemporalRevokeFacet +} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol index 0bc55bfc..2d601c04 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol @@ -8,7 +8,9 @@ pragma solidity >=0.8.30; import {Vm} from "forge-std/Vm.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlTemporalRevoke_Base_Test} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; +import { + AccessControlTemporalRevoke_Base_Test +} from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol index fdc880aa..25cb6d86 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol @@ -43,9 +43,7 @@ contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoS facet.transferOwnership(newOwner); } - function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) - external - { + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) external { vm.assume(caller != users.admin); vm.prank(caller); @@ -97,8 +95,7 @@ contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoS function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); bytes memory expected = abi.encodePacked( - OwnerTwoStepTransferFacet.transferOwnership.selector, - OwnerTwoStepTransferFacet.acceptOwnership.selector + OwnerTwoStepTransferFacet.transferOwnership.selector, OwnerTwoStepTransferFacet.acceptOwnership.selector ); assertEq(selectors, expected, "exportSelectors"); } diff --git a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol index f3b0da1f..76a3c910 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol @@ -43,9 +43,7 @@ contract TransferOwnership_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepTransfe harness.transferOwnership(newOwner); } - function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) - external - { + function testFuzz_ShouldRevert_TransferOwnership_WhenCallerIsNotOwner(address caller, address newOwner) external { vm.assume(caller != users.admin); vm.prank(caller); From 60d672e72bf3a9e4f2fbe18922bf2f7c0d637e4b Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 18:28:24 -0400 Subject: [PATCH 04/25] format --- .../Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol index b92b8987..0f2a32f3 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol @@ -6,7 +6,9 @@ pragma solidity >=0.8.30; */ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlTemporalGrant_Base_Test} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; +import { + AccessControlTemporalGrant_Base_Test +} from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** From 2e547416a99c3fbaeaddf20018805b7ceed81d84 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 21:52:53 -0400 Subject: [PATCH 05/25] add ERC1155 BTT test --- .../ERC1155/ERC1155ApproveModHarness.sol | 18 + .../token/ERC1155/ERC1155BurnModHarness.sol | 22 + .../ERC1155/ERC1155MetadataModHarness.sol | 27 + .../token/ERC1155/ERC1155MintModHarness.sol | 22 + .../ERC1155/ERC1155TransferModHarness.sol | 28 + .../token/ERC20/ERC20MetadataModHarness.sol | 19 + .../token/ERC20/ERC20PermitFacetHarness.sol | 19 + test/mocks/ERC1155ReceiverMock.sol | 94 ++ test/token/ERC1155/ERC1155/ERC1155.t.sol | 1160 ----------------- test/token/ERC1155/ERC1155/ERC1155Facet.t.sol | 1148 ---------------- .../ERC1155/harnesses/ERC1155FacetHarness.sol | 119 -- .../ERC1155/harnesses/ERC1155Harness.sol | 130 -- .../ERC1155/mocks/ERC1155ReceiverMock.sol | 80 -- test/trees/ERC1155.tree | 102 ++ .../Approve/ERC1155ApproveFacetBase.t.sol | 19 + .../Approve/ERC1155ApproveModBase.t.sol | 22 + .../facet/fuzz/setApprovalForAll.t.sol | 42 + .../Approve/mod/fuzz/setApprovalForAll.t.sol | 37 + .../ERC1155/Burn/ERC1155BurnFacetBase.t.sol | 30 + .../ERC1155/Burn/ERC1155BurnModBase.t.sol | 26 + .../token/ERC1155/Burn/facet/fuzz/burn.t.sol | 103 ++ .../ERC1155/Burn/facet/fuzz/burnBatch.t.sol | 131 ++ .../token/ERC1155/Burn/mod/fuzz/burn.t.sol | 50 + .../ERC1155/Burn/mod/fuzz/burnBatch.t.sol | 71 + .../ERC1155/Data/ERC1155DataFacetBase.t.sol | 30 + .../token/ERC1155/Data/facet/fuzz/data.t.sol | 108 ++ .../Metadata/ERC1155MetadataFacetBase.t.sol | 19 + .../Metadata/ERC1155MetadataModBase.t.sol | 19 + .../ERC1155/Metadata/facet/fuzz/uri.t.sol | 29 + .../ERC1155/Metadata/mod/fuzz/metadata.t.sol | 49 + .../ERC1155/Mint/ERC1155MintModBase.t.sol | 22 + .../token/ERC1155/Mint/mod/fuzz/mint.t.sol | 80 ++ .../ERC1155/Mint/mod/fuzz/mintBatch.t.sol | 127 ++ .../Transfer/ERC1155TransferFacetBase.t.sol | 30 + .../Transfer/ERC1155TransferModBase.t.sol | 30 + .../facet/fuzz/safeBatchTransferFrom.t.sol | 302 +++++ .../facet/fuzz/safeTransferFrom.t.sol | 262 ++++ .../mod/fuzz/safeBatchTransferFrom.t.sol | 89 ++ .../Transfer/mod/fuzz/safeTransferFrom.t.sol | 83 ++ test/utils/storage/ERC1155StorageUtils.sol | 56 + 40 files changed, 2217 insertions(+), 2637 deletions(-) create mode 100644 test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol create mode 100644 test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol create mode 100644 test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol create mode 100644 test/harnesses/token/ERC1155/ERC1155MintModHarness.sol create mode 100644 test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol create mode 100644 test/harnesses/token/ERC20/ERC20MetadataModHarness.sol create mode 100644 test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol create mode 100644 test/mocks/ERC1155ReceiverMock.sol delete mode 100644 test/token/ERC1155/ERC1155/ERC1155.t.sol delete mode 100644 test/token/ERC1155/ERC1155/ERC1155Facet.t.sol delete mode 100644 test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol delete mode 100644 test/token/ERC1155/ERC1155/harnesses/ERC1155Harness.sol delete mode 100644 test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol create mode 100644 test/trees/ERC1155.tree create mode 100644 test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol create mode 100644 test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol create mode 100644 test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol create mode 100644 test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol create mode 100644 test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol create mode 100644 test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol create mode 100644 test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol create mode 100644 test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol create mode 100644 test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol create mode 100644 test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol create mode 100644 test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol create mode 100644 test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol create mode 100644 test/unit/token/ERC1155/Metadata/mod/fuzz/metadata.t.sol create mode 100644 test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol create mode 100644 test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol create mode 100644 test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol create mode 100644 test/utils/storage/ERC1155StorageUtils.sol diff --git a/test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol b/test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol new file mode 100644 index 00000000..9d908581 --- /dev/null +++ b/test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC1155/Approve/ERC1155ApproveMod.sol" as ERC1155ApproveMod; + +/** + * @title ERC1155ApproveModHarness + * @notice Test harness that exposes ERC1155ApproveMod functions as external + */ +contract ERC1155ApproveModHarness { + function setApprovalForAll(address _user, address _operator, bool _approved) external { + ERC1155ApproveMod.setApprovalForAll(_user, _operator, _approved); + } +} diff --git a/test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol b/test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol new file mode 100644 index 00000000..4b181963 --- /dev/null +++ b/test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC1155/Burn/ERC1155BurnMod.sol" as ERC1155BurnMod; + +/** + * @title ERC1155BurnModHarness + * @notice Test harness that exposes ERC1155BurnMod functions as external + */ +contract ERC1155BurnModHarness { + function burn(address _from, uint256 _id, uint256 _value) external { + ERC1155BurnMod.burn(_from, _id, _value); + } + + function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) external { + ERC1155BurnMod.burnBatch(_from, _ids, _values); + } +} diff --git a/test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol b/test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol new file mode 100644 index 00000000..a9c85aca --- /dev/null +++ b/test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MetadataFacet} from "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol"; +import "src/token/ERC1155/Metadata/ERC1155MetadataMod.sol" as ERC1155MetadataMod; + +/** + * @title ERC1155MetadataModHarness + * @notice Test harness that exposes ERC1155MetadataFacet.uri and ERC1155MetadataMod setters (same storage). + */ +contract ERC1155MetadataModHarness is ERC1155MetadataFacet { + function setURI(string memory _uri) external { + ERC1155MetadataMod.setURI(_uri); + } + + function setBaseURI(string memory _baseURI) external { + ERC1155MetadataMod.setBaseURI(_baseURI); + } + + function setTokenURI(uint256 _tokenId, string memory _tokenURI) external { + ERC1155MetadataMod.setTokenURI(_tokenId, _tokenURI); + } +} diff --git a/test/harnesses/token/ERC1155/ERC1155MintModHarness.sol b/test/harnesses/token/ERC1155/ERC1155MintModHarness.sol new file mode 100644 index 00000000..d9a9c35d --- /dev/null +++ b/test/harnesses/token/ERC1155/ERC1155MintModHarness.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC1155/Mint/ERC1155MintMod.sol" as ERC1155MintMod; + +/** + * @title ERC1155MintModHarness + * @notice Test harness that exposes ERC1155MintMod functions as external + */ +contract ERC1155MintModHarness { + function mint(address _to, uint256 _id, uint256 _value, bytes memory _data) external { + ERC1155MintMod.mint(_to, _id, _value, _data); + } + + function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values, bytes memory _data) external { + ERC1155MintMod.mintBatch(_to, _ids, _values, _data); + } +} diff --git a/test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol b/test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol new file mode 100644 index 00000000..69630855 --- /dev/null +++ b/test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC1155/Transfer/ERC1155TransferMod.sol" as ERC1155TransferMod; + +/** + * @title ERC1155TransferModHarness + * @notice Test harness that exposes ERC1155TransferMod functions as external + */ +contract ERC1155TransferModHarness { + function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, address _operator) external { + ERC1155TransferMod.safeTransferFrom(_from, _to, _id, _value, _operator); + } + + function safeBatchTransferFrom( + address _from, + address _to, + uint256[] memory _ids, + uint256[] memory _values, + address _operator + ) external { + ERC1155TransferMod.safeBatchTransferFrom(_from, _to, _ids, _values, _operator); + } +} diff --git a/test/harnesses/token/ERC20/ERC20MetadataModHarness.sol b/test/harnesses/token/ERC20/ERC20MetadataModHarness.sol new file mode 100644 index 00000000..3a0499d7 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20MetadataModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20MetadataFacet} from "src/token/ERC20/Metadata/ERC20MetadataFacet.sol"; +import "src/token/ERC20/Metadata/ERC20MetadataMod.sol" as ERC20MetadataMod; + +/** + * @title ERC20MetadataModHarness + * @notice Test harness that exposes ERC20MetadataFacet view functions and ERC20MetadataMod setMetadata (same storage). + */ +contract ERC20MetadataModHarness is ERC20MetadataFacet { + function setMetadata(string memory _name, string memory _symbol, uint8 _decimals) external { + ERC20MetadataMod.setMetadata(_name, _symbol, _decimals); + } +} diff --git a/test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol b/test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol new file mode 100644 index 00000000..346bb9b0 --- /dev/null +++ b/test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; +import "src/token/ERC20/Metadata/ERC20MetadataMod.sol" as ERC20MetadataMod; + +/** + * @title ERC20PermitFacetHarness + * @notice Test harness for ERC20PermitFacet: adds setMetadata so DOMAIN_SEPARATOR and permit hashes can be tested. + */ +contract ERC20PermitFacetHarness is ERC20PermitFacet { + function setMetadata(string memory _name, string memory _symbol, uint8 _decimals) external { + ERC20MetadataMod.setMetadata(_name, _symbol, _decimals); + } +} diff --git a/test/mocks/ERC1155ReceiverMock.sol b/test/mocks/ERC1155ReceiverMock.sol new file mode 100644 index 00000000..f0467ac7 --- /dev/null +++ b/test/mocks/ERC1155ReceiverMock.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {IERC1155Receiver} from "src/interfaces/IERC1155Receiver.sol"; + +/** + * @title ERC1155ReceiverMock + * @notice Mock implementation of IERC1155Receiver for testing. + * @dev Supports configurable return values, revert types (message, no message, custom error, panic), + * and emits Received/BatchReceived so callers can assert the data parameter is forwarded. + */ +contract ERC1155ReceiverMock is IERC1155Receiver { + enum RevertType { + None, + RevertWithoutMessage, + RevertWithMessage, + RevertWithCustomError, + Panic + } + + error CustomError(bytes4); + + event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); + event BatchReceived( + address operator, + address from, + uint256[] ids, + uint256[] values, + bytes data, + uint256 gas + ); + + bytes4 private immutable _singleRetval; + bytes4 private immutable _batchRetval; + RevertType private immutable _revertType; + + constructor(bytes4 singleRetval, bytes4 batchRetval, RevertType revertType) { + _singleRetval = singleRetval; + _batchRetval = batchRetval; + _revertType = revertType; + } + + function onERC1155Received( + address operator, + address from, + uint256 id, + uint256 value, + bytes calldata data + ) external override returns (bytes4) { + if (_revertType == RevertType.RevertWithoutMessage) { + revert(); + } + if (_revertType == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on receive"); + } + if (_revertType == RevertType.RevertWithCustomError) { + revert CustomError(_singleRetval); + } + if (_revertType == RevertType.Panic) { + uint256 x = 0; + x = 1 / x; // division by zero panics + } + emit Received(operator, from, id, value, data, gasleft()); + return _singleRetval; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external override returns (bytes4) { + if (_revertType == RevertType.RevertWithoutMessage) { + revert(); + } + if (_revertType == RevertType.RevertWithMessage) { + revert("ERC1155ReceiverMock: reverting on batch receive"); + } + if (_revertType == RevertType.RevertWithCustomError) { + revert CustomError(_batchRetval); + } + if (_revertType == RevertType.Panic) { + uint256 x = 0; + x = 1 / x; // division by zero panics + } + emit BatchReceived(operator, from, ids, values, data, gasleft()); + return _batchRetval; + } +} diff --git a/test/token/ERC1155/ERC1155/ERC1155.t.sol b/test/token/ERC1155/ERC1155/ERC1155.t.sol deleted file mode 100644 index 503a4d08..00000000 --- a/test/token/ERC1155/ERC1155/ERC1155.t.sol +++ /dev/null @@ -1,1160 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC1155Harness} from "./harnesses/ERC1155Harness.sol"; -import "../../../../src/token/ERC1155/ERC1155Mod.sol" as ERC1155Mod; -import {ERC1155ReceiverMock} from "./mocks/ERC1155ReceiverMock.sol"; - -contract LibERC1155Test is Test { - ERC1155Harness public harness; - - address public alice; - address public bob; - address public charlie; - - string constant DEFAULT_URI = "https://token.uri/{id}.json"; - string constant BASE_URI = "https://base.uri/"; - string constant TOKEN_URI = "token1.json"; - - uint256 constant TOKEN_ID_1 = 1; - uint256 constant TOKEN_ID_2 = 2; - uint256 constant TOKEN_ID_3 = 3; - - bytes4 constant RECEIVER_SINGLE_MAGIC_VALUE = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) - bytes4 constant RECEIVER_BATCH_MAGIC_VALUE = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) - - event TransferSingle( - address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value - ); - event TransferBatch( - address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values - ); - event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved); - event URI(string _value, uint256 indexed _id); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC1155Harness(); - harness.initialize(DEFAULT_URI); - } - - /** - * ============================================ - * URI Tests - * ============================================ - */ - - function test_Uri_DefaultUri() public view { - assertEq(harness.uri(TOKEN_ID_1), DEFAULT_URI); - } - - function test_Uri_SetBaseURI() public { - harness.setBaseURI(BASE_URI); - harness.setTokenURI(TOKEN_ID_1, TOKEN_URI); - assertEq(harness.uri(TOKEN_ID_1), string.concat(BASE_URI, TOKEN_URI)); - } - - function test_Uri_SetTokenURI() public { - vm.expectEmit(true, true, true, true); - emit URI(TOKEN_URI, TOKEN_ID_1); - harness.setTokenURI(TOKEN_ID_1, TOKEN_URI); - } - - /** - * ============================================ - * Mint Tests - * ============================================ - */ - - function test_Mint() public { - uint256 amount = 100; - - vm.expectEmit(true, true, true, true); - emit TransferSingle(address(this), address(0), alice, TOKEN_ID_1, amount); - harness.mint(alice, TOKEN_ID_1, amount); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), amount); - } - - function test_Mint_Multiple() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(bob, TOKEN_ID_1, 200); - harness.mint(alice, TOKEN_ID_2, 50); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 200); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 50); - } - - function test_Mint_SameTokenMultipleTimes() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(alice, TOKEN_ID_1, 50); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 150); - } - - function testFuzz_Mint(address to, uint256 id, uint256 amount) public { - vm.assume(to != address(0)); - vm.assume(to.code.length == 0); - vm.assume(amount < type(uint256).max / 2); - - harness.mint(to, id, amount); - - assertEq(harness.balanceOf(to, id), amount); - } - - function test_RevertWhen_MintToZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(0))); - harness.mint(address(0), TOKEN_ID_1, 100); - } - - /** - * ============================================ - * MintBatch Tests - * ============================================ - */ - - function test_MintBatch() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory amounts = new uint256[](3); - amounts[0] = 100; - amounts[1] = 200; - amounts[2] = 300; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), alice, ids, amounts); - harness.mintBatch(alice, ids, amounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); - assertEq(harness.balanceOf(alice, TOKEN_ID_3), 300); - } - - function test_RevertWhen_MintBatchToZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(0))); - harness.mintBatch(address(0), ids, amounts); - } - - function test_RevertWhen_MintBatchArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidArrayLength.selector, 2, 1)); - harness.mintBatch(alice, ids, amounts); - } - - function test_MintBatch_EmptyArrays() public { - uint256[] memory ids = new uint256[](0); - uint256[] memory amounts = new uint256[](0); - - harness.mintBatch(alice, ids, amounts); - /** - * Should not revert - */ - } - - function test_Mint_ToReceiverContract() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - harness.mint(address(receiver), TOKEN_ID_1, 100); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 100); - } - - function test_RevertWhen_Mint_ToReceiverContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = - new ERC1155ReceiverMock(0x00c0ffee, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None); - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.mint(address(receiver), TOKEN_ID_1, 100); - } - - function test_RevertWhen_Mint_ToReceiverContractWithRevert() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage - ); - - vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); - harness.mint(address(receiver), TOKEN_ID_1, 100); - } - - function test_MintBatch_ToReceiverContract() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - harness.mintBatch(address(receiver), ids, amounts); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 100); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_2), 200); - } - - function test_RevertWhen_MintBatch_ToReceiverContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_SINGLE_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.mintBatch(address(receiver), ids, amounts); - } - - /** - * ============================================ - * Burn Tests - * ============================================ - */ - - function test_Burn() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.expectEmit(true, true, true, true); - emit TransferSingle(address(this), alice, address(0), TOKEN_ID_1, 30); - harness.burn(alice, TOKEN_ID_1, 30); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - } - - function test_Burn_AllBalance() public { - harness.mint(alice, TOKEN_ID_1, 100); - - harness.burn(alice, TOKEN_ID_1, 100); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 0); - } - - function testFuzz_Burn(address from, uint256 id, uint256 mintAmount, uint256 burnAmount) public { - vm.assume(from != address(0)); - vm.assume(from.code.length == 0); - vm.assume(mintAmount < type(uint256).max / 2); - vm.assume(burnAmount <= mintAmount); - - harness.mint(from, id, mintAmount); - harness.burn(from, id, burnAmount); - - assertEq(harness.balanceOf(from, id), mintAmount - burnAmount); - } - - function test_RevertWhen_BurnFromZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidSender.selector, address(0))); - harness.burn(address(0), TOKEN_ID_1, 100); - } - - function test_RevertWhen_BurnInsufficientBalance() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.expectRevert( - abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) - ); - harness.burn(alice, TOKEN_ID_1, 150); - } - - function test_RevertWhen_BurnZeroBalance() public { - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1)); - harness.burn(alice, TOKEN_ID_1, 1); - } - - /** - * ============================================ - * BurnBatch Tests - * ============================================ - */ - - function test_BurnBatch() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory mintAmounts = new uint256[](3); - mintAmounts[0] = 100; - mintAmounts[1] = 200; - mintAmounts[2] = 300; - - harness.mintBatch(alice, ids, mintAmounts); - - uint256[] memory burnAmounts = new uint256[](3); - burnAmounts[0] = 30; - burnAmounts[1] = 50; - burnAmounts[2] = 100; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), alice, address(0), ids, burnAmounts); - harness.burnBatch(alice, ids, burnAmounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(harness.balanceOf(alice, TOKEN_ID_3), 200); - } - - function test_RevertWhen_BurnBatchFromZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidSender.selector, address(0))); - harness.burnBatch(address(0), ids, amounts); - } - - function test_RevertWhen_BurnBatchArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidArrayLength.selector, 2, 1)); - harness.burnBatch(alice, ids, amounts); - } - - function test_RevertWhen_BurnBatchInsufficientBalance() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory mintAmounts = new uint256[](2); - mintAmounts[0] = 100; - mintAmounts[1] = 50; - - harness.mintBatch(alice, ids, mintAmounts); - - uint256[] memory burnAmounts = new uint256[](2); - burnAmounts[0] = 50; - burnAmounts[1] = 100; // More than balance - - vm.expectRevert( - abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) - ); - harness.burnBatch(alice, ids, burnAmounts); - } - - /** - * ============================================ - * SafeTransferFrom Tests - * ============================================ - */ - - function test_SafeTransferFrom_ByOwner() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.expectEmit(true, true, true, true); - emit TransferSingle(alice, alice, bob, TOKEN_ID_1, 30); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 30); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); - } - - function test_SafeTransferFrom_ByApprovedOperator() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - vm.prank(bob); - harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 30); - } - - function test_SafeTransferFrom_ToSelf() public { - uint256 amount = 100; - - harness.mint(alice, TOKEN_ID_1, amount); - - vm.prank(alice); - harness.safeTransferFrom(alice, alice, TOKEN_ID_1, 50); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), amount); - } - - function test_SafeTransferFrom_ZeroAmount() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 0); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 0); - } - - function testFuzz_SafeTransferFrom(address from, address to, uint256 id, uint256 amount) public { - vm.assume(from != address(0) && to != address(0)); - vm.assume(from.code.length == 0 && to.code.length == 0); - vm.assume(amount < type(uint256).max / 2); - - harness.mint(from, id, amount); - - vm.prank(from); - harness.safeTransferFrom(from, to, id, amount); - - if (from == to) { - /** - * Self-transfer is a no-op; balance remains unchanged - */ - assertEq(harness.balanceOf(from, id), amount); - } else { - assertEq(harness.balanceOf(from, id), 0); - assertEq(harness.balanceOf(to, id), amount); - } - } - - function test_RevertWhen_SafeTransferFromToZeroAddress() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(0))); - harness.safeTransferFrom(alice, address(0), TOKEN_ID_1, 30); - } - - function test_RevertWhen_SafeTransferFromFromZeroAddress() public { - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidSender.selector, address(0))); - harness.safeTransferFrom(address(0), bob, TOKEN_ID_1, 30); - } - - function test_RevertWhen_SafeTransferFromWithoutApproval() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155MissingApprovalForAll.selector, bob, alice)); - harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30); - } - - function test_RevertWhen_SafeTransferFromInsufficientBalance() public { - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) - ); - harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 150); - } - - function test_RevertWhen_SafeTransferFromZeroBalance() public { - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1)); - harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 1); - } - - function test_RevertWhen_MintOverflowsRecipient() public { - uint256 nearMaxBalance = type(uint256).max - 100; - - /** - * Mint near-max tokens to alice - */ - harness.mint(alice, TOKEN_ID_1, nearMaxBalance); - - /** - * Try to mint more, which would overflow - */ - vm.expectRevert(); // Arithmetic overflow - harness.mint(alice, TOKEN_ID_1, 200); - } - - /** - * ============================================ - * SafeBatchTransferFrom Tests - * ============================================ - */ - - function test_SafeBatchTransferFrom_ByOwner() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - harness.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 50; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(alice, alice, bob, ids, transferAmounts); - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); - assertEq(harness.balanceOf(bob, TOKEN_ID_2), 50); - } - - function test_SafeBatchTransferFrom_ByApprovedOperator() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - harness.mintBatch(alice, ids, amounts); - - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 50; - - vm.prank(bob); - harness.safeBatchTransferFrom(alice, charlie, ids, transferAmounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 30); - assertEq(harness.balanceOf(charlie, TOKEN_ID_2), 50); - } - - function test_RevertWhen_SafeBatchTransferFromToZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(0))); - harness.safeBatchTransferFrom(alice, address(0), ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFromFromZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidSender.selector, address(0))); - harness.safeBatchTransferFrom(address(0), bob, ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFromArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidArrayLength.selector, 2, 1)); - harness.safeBatchTransferFrom(alice, bob, ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFromWithoutApproval() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155MissingApprovalForAll.selector, bob, alice)); - harness.safeBatchTransferFrom(alice, charlie, ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFromInsufficientBalance() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory mintAmounts = new uint256[](2); - mintAmounts[0] = 100; - mintAmounts[1] = 50; - - harness.mintBatch(alice, ids, mintAmounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 100; // More than balance - - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155Mod.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) - ); - harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); - } - - /** - * ============================================ - * BalanceOf Tests - * ============================================ - */ - - function test_BalanceOf_AfterMint() public { - harness.mint(alice, TOKEN_ID_1, 100); - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); - } - - function test_BalanceOf_MultipleTokens() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(alice, TOKEN_ID_2, 200); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); - } - - function test_BalanceOf_ZeroAddress() public view { - assertEq(harness.balanceOf(address(0), TOKEN_ID_1), 0); - } - - /** - * ============================================ - * BalanceOfBatch Tests - * ============================================ - */ - - function test_BalanceOfBatch() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(bob, TOKEN_ID_2, 200); - harness.mint(charlie, TOKEN_ID_3, 300); - - address[] memory accounts = new address[](3); - accounts[0] = alice; - accounts[1] = bob; - accounts[2] = charlie; - - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory balances = harness.balanceOfBatch(accounts, ids); - - assertEq(balances[0], 100); - assertEq(balances[1], 200); - assertEq(balances[2], 300); - } - - function test_BalanceOfBatch_EmptyArrays() public view { - address[] memory accounts = new address[](0); - uint256[] memory ids = new uint256[](0); - - uint256[] memory balances = harness.balanceOfBatch(accounts, ids); - assertEq(balances.length, 0); - } - - function test_BalanceOfBatch_SameAccountMultipleTimes() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(alice, TOKEN_ID_2, 200); - - address[] memory accounts = new address[](2); - accounts[0] = alice; - accounts[1] = alice; - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory balances = harness.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 200); - } - - function test_BalanceOfBatch_DifferentAccountsSameTokenId() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(bob, TOKEN_ID_1, 200); - - address[] memory accounts = new address[](2); - accounts[0] = alice; - accounts[1] = bob; - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_1; - - uint256[] memory balances = harness.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 200); - } - - function test_BalanceOfBatch_WithZeroAddress() public { - harness.mint(alice, TOKEN_ID_1, 100); - harness.mint(bob, TOKEN_ID_3, 300); - - address[] memory accounts = new address[](3); - accounts[0] = alice; - accounts[1] = address(0); - accounts[2] = bob; - - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory balances = harness.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 0); - assertEq(balances[2], 300); - } - - /** - * ============================================ - * Approval Tests - * ============================================ - */ - - function test_SetApprovalForAll() public { - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - assertTrue(harness.isApprovedForAll(alice, bob)); - } - - function test_SetApprovalForAll_Revoke() public { - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - vm.prank(alice); - harness.setApprovalForAll(bob, false); - - assertFalse(harness.isApprovedForAll(alice, bob)); - } - - function test_SetApprovalForAll_Self() public { - vm.prank(alice); - harness.setApprovalForAll(alice, true); - - assertTrue(harness.isApprovedForAll(alice, alice)); - } - - function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { - vm.assume(owner != address(0) && operator != address(0)); - - vm.prank(owner); - harness.setApprovalForAll(operator, approved); - - assertEq(harness.isApprovedForAll(owner, operator), approved); - } - - /** - * ============================================ - * Receiver Hook Tests - * ============================================ - */ - - function test_SafeTransferFrom_ToContractWithAcceptance() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 50); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 50); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - 0x00c0ffee, // Wrong return value - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithRevertMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithRevertNoMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - } - - function test_SafeBatchTransferFrom_ToContractWithAcceptance() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - harness.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 50; - transferAmounts[1] = 100; - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, address(receiver), ids, transferAmounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 50); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 100); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_1), 50); - assertEq(harness.balanceOf(address(receiver), TOKEN_ID_2), 100); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertNoMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_SINGLE_MAGIC_VALUE, // Wrong return value (correct for single, wrong for batch) - ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155InvalidReceiver.selector, address(receiver))); - harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert("ERC1155ReceiverMock: reverting on batch receive"); - harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithPanic() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithPanic() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(); - harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithCustomError() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.RevertWithCustomError - ); - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE)); - harness.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithCustomError() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.RevertWithCustomError - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - harness.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE)); - harness.safeBatchTransferFrom(alice, address(receiver), ids, amounts); - } - - function test_SafeBatchTransferFrom_EmptyArrays() public { - uint256[] memory ids = new uint256[](0); - uint256[] memory amounts = new uint256[](0); - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, bob, ids, amounts); - /** - * Should not revert - */ - } - - function test_SafeBatchTransferFrom_WithZeroAmounts() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory amounts = new uint256[](3); - amounts[0] = 100; - amounts[1] = 200; - amounts[2] = 300; - - harness.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](3); - transferAmounts[0] = 30; - transferAmounts[1] = 0; // Zero amount - transferAmounts[2] = 50; - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 200); - assertEq(harness.balanceOf(alice, TOKEN_ID_3), 250); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); - assertEq(harness.balanceOf(bob, TOKEN_ID_2), 0); - assertEq(harness.balanceOf(bob, TOKEN_ID_3), 50); - } - - function test_SafeBatchTransferFrom_DuplicateTokenIds() public { - harness.mint(alice, TOKEN_ID_1, 100); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_1; // Duplicate - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 10; - amounts[1] = 20; - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, bob, ids, amounts); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 30); - } - - /** - * ============================================ - * Integration Tests - * ============================================ - */ - - function test_MintTransferBurn_Flow() public { - /** - * Mint to alice - */ - harness.mint(alice, TOKEN_ID_1, 1000); - harness.mint(alice, TOKEN_ID_2, 500); - - /** - * Alice transfers some to bob - */ - vm.prank(alice); - harness.safeTransferFrom(alice, bob, TOKEN_ID_1, 300); - - /** - * Bob transfers some to charlie - */ - vm.prank(bob); - harness.safeTransferFrom(bob, charlie, TOKEN_ID_1, 100); - - /** - * Burn from alice - */ - harness.burn(alice, TOKEN_ID_1, 200); - - /** - * Verify final balances - */ - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 500); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 500); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 200); - assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 100); - } - - function test_MintBatchTransferBatchBurnBatch_Flow() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory mintAmounts = new uint256[](3); - mintAmounts[0] = 1000; - mintAmounts[1] = 2000; - mintAmounts[2] = 3000; - - /** - * Mint batch to alice - */ - harness.mintBatch(alice, ids, mintAmounts); - - /** - * Alice transfers batch to bob - */ - uint256[] memory transferAmounts = new uint256[](3); - transferAmounts[0] = 300; - transferAmounts[1] = 400; - transferAmounts[2] = 500; - - vm.prank(alice); - harness.safeBatchTransferFrom(alice, bob, ids, transferAmounts); - - /** - * Burn batch from alice - */ - uint256[] memory burnAmounts = new uint256[](3); - burnAmounts[0] = 200; - burnAmounts[1] = 300; - burnAmounts[2] = 400; - - harness.burnBatch(alice, ids, burnAmounts); - - /** - * Verify final balances - */ - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 500); - assertEq(harness.balanceOf(alice, TOKEN_ID_2), 1300); - assertEq(harness.balanceOf(alice, TOKEN_ID_3), 2100); - assertEq(harness.balanceOf(bob, TOKEN_ID_1), 300); - assertEq(harness.balanceOf(bob, TOKEN_ID_2), 400); - assertEq(harness.balanceOf(bob, TOKEN_ID_3), 500); - } - - function test_ApprovalAndTransfer_Flow() public { - harness.mint(alice, TOKEN_ID_1, 1000); - - /** - * Alice approves bob - */ - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - /** - * Bob transfers on behalf of alice - */ - vm.prank(bob); - harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 300); - - assertEq(harness.balanceOf(alice, TOKEN_ID_1), 700); - assertEq(harness.balanceOf(charlie, TOKEN_ID_1), 300); - - /** - * Alice revokes approval - */ - vm.prank(alice); - harness.setApprovalForAll(bob, false); - - /** - * Bob can no longer transfer - */ - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Mod.ERC1155MissingApprovalForAll.selector, bob, alice)); - harness.safeTransferFrom(alice, charlie, TOKEN_ID_1, 100); - } -} diff --git a/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol b/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol deleted file mode 100644 index 8633b4e0..00000000 --- a/test/token/ERC1155/ERC1155/ERC1155Facet.t.sol +++ /dev/null @@ -1,1148 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC1155FacetHarness} from "./harnesses/ERC1155FacetHarness.sol"; -import {ERC1155Facet} from "../../../../src/token/ERC1155/ERC1155Facet.sol"; -import {ERC1155ReceiverMock} from "./mocks/ERC1155ReceiverMock.sol"; - -contract ERC1155FacetTest is Test { - ERC1155FacetHarness public facet; - - address public alice; - address public bob; - address public charlie; - - string constant DEFAULT_URI = "https://token.uri/{id}.json"; - string constant BASE_URI = "https://base.uri/"; - string constant TOKEN_URI = "token1.json"; - - uint256 constant TOKEN_ID_1 = 1; - uint256 constant TOKEN_ID_2 = 2; - uint256 constant TOKEN_ID_3 = 3; - - bytes4 constant RECEIVER_SINGLE_MAGIC_VALUE = 0xf23a6e61; // bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)")) - bytes4 constant RECEIVER_BATCH_MAGIC_VALUE = 0xbc197c81; // bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)")) - - event TransferSingle( - address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value - ); - event TransferBatch( - address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values - ); - event ApprovalForAll(address indexed _account, address indexed _operator, bool _approved); - event URI(string _value, uint256 indexed _id); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - facet = new ERC1155FacetHarness(); - facet.initialize(DEFAULT_URI); - } - - /** - * ============================================ - * URI Tests - * ============================================ - */ - - function test_Uri_DefaultUri() public view { - assertEq(facet.uri(TOKEN_ID_1), DEFAULT_URI); - } - - function test_Uri_SetBaseURI() public { - facet.setBaseURI(BASE_URI); - facet.setTokenURI(TOKEN_ID_1, TOKEN_URI); - assertEq(facet.uri(TOKEN_ID_1), string.concat(BASE_URI, TOKEN_URI)); - } - - function test_Uri_SetTokenURI() public { - vm.expectEmit(true, true, true, true); - emit URI(TOKEN_URI, TOKEN_ID_1); - facet.setTokenURI(TOKEN_ID_1, TOKEN_URI); - } - - /** - * ============================================ - * BalanceOf Tests - * ============================================ - */ - - function test_BalanceOf_AfterMint() public { - facet.mint(alice, TOKEN_ID_1, 100); - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - } - - function test_BalanceOf_MultipleTokens() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(alice, TOKEN_ID_2, 200); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); - } - - function test_BalanceOf_ZeroAddress() public view { - assertEq(facet.balanceOf(address(0), TOKEN_ID_1), 0); - } - - /** - * ============================================ - * BalanceOfBatch Tests - * ============================================ - */ - - function test_BalanceOfBatch() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(bob, TOKEN_ID_2, 200); - facet.mint(charlie, TOKEN_ID_3, 300); - - address[] memory accounts = new address[](3); - accounts[0] = alice; - accounts[1] = bob; - accounts[2] = charlie; - - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory balances = facet.balanceOfBatch(accounts, ids); - - assertEq(balances[0], 100); - assertEq(balances[1], 200); - assertEq(balances[2], 300); - } - - function test_RevertWhen_BalanceOfBatchArrayLengthMismatch() public { - address[] memory accounts = new address[](2); - accounts[0] = alice; - accounts[1] = bob; - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 1, 2)); - facet.balanceOfBatch(accounts, ids); - } - - function test_BalanceOfBatch_EmptyArrays() public view { - address[] memory accounts = new address[](0); - uint256[] memory ids = new uint256[](0); - - uint256[] memory balances = facet.balanceOfBatch(accounts, ids); - assertEq(balances.length, 0); - } - - function test_BalanceOfBatch_SameAccountMultipleTimes() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(alice, TOKEN_ID_2, 200); - - address[] memory accounts = new address[](2); - accounts[0] = alice; - accounts[1] = alice; - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory balances = facet.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 200); - } - - function test_BalanceOfBatch_DifferentAccountsSameTokenId() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(bob, TOKEN_ID_1, 200); - - address[] memory accounts = new address[](2); - accounts[0] = alice; - accounts[1] = bob; - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_1; - - uint256[] memory balances = facet.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 200); - } - - function test_BalanceOfBatch_WithZeroAddress() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(bob, TOKEN_ID_3, 300); - - address[] memory accounts = new address[](3); - accounts[0] = alice; - accounts[1] = address(0); - accounts[2] = bob; - - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory balances = facet.balanceOfBatch(accounts, ids); - assertEq(balances[0], 100); - assertEq(balances[1], 0); - assertEq(balances[2], 300); - } - - /** - * ============================================ - * SetApprovalForAll Tests - * ============================================ - */ - - function test_SetApprovalForAll() public { - vm.expectEmit(true, true, true, true); - emit ApprovalForAll(alice, bob, true); - - vm.prank(alice); - facet.setApprovalForAll(bob, true); - - assertTrue(facet.isApprovedForAll(alice, bob)); - } - - function test_SetApprovalForAll_Revoke() public { - vm.prank(alice); - facet.setApprovalForAll(bob, true); - - vm.expectEmit(true, true, true, true); - emit ApprovalForAll(alice, bob, false); - - vm.prank(alice); - facet.setApprovalForAll(bob, false); - - assertFalse(facet.isApprovedForAll(alice, bob)); - } - - function test_RevertWhen_SetApprovalForAllZeroAddress() public { - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidOperator.selector, address(0))); - facet.setApprovalForAll(address(0), true); - } - - function test_SetApprovalForAll_Self() public { - vm.prank(alice); - facet.setApprovalForAll(alice, true); - - assertTrue(facet.isApprovedForAll(alice, alice)); - } - - function testFuzz_SetApprovalForAll(address owner, address operator, bool approved) public { - vm.assume(owner != address(0) && operator != address(0)); - - vm.prank(owner); - facet.setApprovalForAll(operator, approved); - - assertEq(facet.isApprovedForAll(owner, operator), approved); - } - - /** - * ============================================ - * SafeTransferFrom Tests - * ============================================ - */ - - function test_SafeTransferFrom_ByOwner() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.expectEmit(true, true, true, true); - emit TransferSingle(alice, alice, bob, TOKEN_ID_1, 30); - - vm.prank(alice); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 30, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); - } - - function test_SafeTransferFrom_ByApprovedOperator() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - facet.setApprovalForAll(bob, true); - - vm.expectEmit(true, true, true, true); - emit TransferSingle(bob, alice, charlie, TOKEN_ID_1, 30); - - vm.prank(bob); - facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 30); - } - - function test_SafeTransferFrom_ToSelf() public { - uint256 amount = 100; - - facet.mint(alice, TOKEN_ID_1, amount); - - vm.prank(alice); - facet.safeTransferFrom(alice, alice, TOKEN_ID_1, 50, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), amount); - } - - function test_SafeTransferFrom_ZeroAmount() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 0, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 0); - } - - function test_SafeTransferFrom_AllBalance() public { - uint256 amount = 100; - - facet.mint(alice, TOKEN_ID_1, amount); - - vm.prank(alice); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, amount, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 0); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), amount); - } - - function testFuzz_SafeTransferFrom(address from, address to, uint256 id, uint256 amount) public { - vm.assume(from != address(0) && to != address(0)); - vm.assume(from != to); - vm.assume(from.code.length == 0 && to.code.length == 0); - vm.assume(amount < type(uint256).max / 2); - - facet.mint(from, id, amount); - - vm.prank(from); - facet.safeTransferFrom(from, to, id, amount, ""); - - assertEq(facet.balanceOf(from, id), 0); - assertEq(facet.balanceOf(to, id), amount); - } - - function test_RevertWhen_SafeTransferFromToZeroAddress() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); - facet.safeTransferFrom(alice, address(0), TOKEN_ID_1, 30, ""); - } - - function test_RevertWhen_SafeTransferFromFromZeroAddress() public { - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); - facet.safeTransferFrom(address(0), bob, TOKEN_ID_1, 30, ""); - } - - function test_RevertWhen_SafeTransferFromWithoutApproval() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); - facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 30, ""); - } - - function test_RevertWhen_SafeTransferFromInsufficientBalance() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) - ); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 150, ""); - } - - function test_RevertWhen_SafeTransferFromZeroBalance() public { - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1) - ); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 1, ""); - } - - function test_RevertWhen_MintOverflowsRecipient() public { - uint256 nearMaxBalance = type(uint256).max - 100; - - /** - * Mint near-max tokens to alice - */ - facet.mint(alice, TOKEN_ID_1, nearMaxBalance); - - /** - * Try to mint more, which would overflow - */ - vm.expectRevert(); // Arithmetic overflow - facet.mint(alice, TOKEN_ID_1, 200); - } - - /** - * ============================================ - * SafeBatchTransferFrom Tests - * ============================================ - */ - - function test_SafeBatchTransferFrom_ByOwner() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - facet.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 50; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(alice, alice, bob, ids, transferAmounts); - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); - assertEq(facet.balanceOf(bob, TOKEN_ID_2), 50); - } - - function test_SafeBatchTransferFrom_ByApprovedOperator() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - facet.mintBatch(alice, ids, amounts); - - vm.prank(alice); - facet.setApprovalForAll(bob, true); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 50; - - vm.prank(bob); - facet.safeBatchTransferFrom(alice, charlie, ids, transferAmounts, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 30); - assertEq(facet.balanceOf(charlie, TOKEN_ID_2), 50); - } - - function test_RevertWhen_SafeBatchTransferFromToZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); - facet.safeBatchTransferFrom(alice, address(0), ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFromFromZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); - facet.safeBatchTransferFrom(address(0), bob, ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFromArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); - facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFromWithoutApproval() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 30; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); - facet.safeBatchTransferFrom(alice, charlie, ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFromInsufficientBalance() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory mintAmounts = new uint256[](2); - mintAmounts[0] = 100; - mintAmounts[1] = 50; - - facet.mintBatch(alice, ids, mintAmounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 30; - transferAmounts[1] = 100; // More than balance - - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) - ); - facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); - } - - /** - * ============================================ - * Mint Tests (via Harness) - * ============================================ - */ - - function test_Mint() public { - uint256 amount = 100; - - vm.expectEmit(true, true, true, true); - emit TransferSingle(address(this), address(0), alice, TOKEN_ID_1, amount); - facet.mint(alice, TOKEN_ID_1, amount); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), amount); - } - - function test_Mint_Multiple() public { - facet.mint(alice, TOKEN_ID_1, 100); - facet.mint(bob, TOKEN_ID_1, 200); - facet.mint(alice, TOKEN_ID_2, 50); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 50); - } - - function test_RevertWhen_MintToZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); - facet.mint(address(0), TOKEN_ID_1, 100); - } - - /** - * ============================================ - * MintBatch Tests (via Harness) - * ============================================ - */ - - function test_MintBatch() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory amounts = new uint256[](3); - amounts[0] = 100; - amounts[1] = 200; - amounts[2] = 300; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), address(0), alice, ids, amounts); - facet.mintBatch(alice, ids, amounts); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 100); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); - assertEq(facet.balanceOf(alice, TOKEN_ID_3), 300); - } - - function test_RevertWhen_MintBatchToZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(0))); - facet.mintBatch(address(0), ids, amounts); - } - - function test_RevertWhen_MintBatchArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); - facet.mintBatch(alice, ids, amounts); - } - - function test_MintBatch_EmptyArrays() public { - uint256[] memory ids = new uint256[](0); - uint256[] memory amounts = new uint256[](0); - - facet.mintBatch(alice, ids, amounts); - /** - * Should not revert - */ - } - - /** - * ============================================ - * Burn Tests (via Harness) - * ============================================ - */ - - function test_Burn() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.expectEmit(true, true, true, true); - emit TransferSingle(address(this), alice, address(0), TOKEN_ID_1, 30); - facet.burn(alice, TOKEN_ID_1, 30); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - } - - function test_Burn_AllBalance() public { - facet.mint(alice, TOKEN_ID_1, 100); - - facet.burn(alice, TOKEN_ID_1, 100); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 0); - } - - function test_RevertWhen_BurnFromZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); - facet.burn(address(0), TOKEN_ID_1, 100); - } - - function test_RevertWhen_BurnInsufficientBalance() public { - facet.mint(alice, TOKEN_ID_1, 100); - - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 100, 150, TOKEN_ID_1) - ); - facet.burn(alice, TOKEN_ID_1, 150); - } - - function test_RevertWhen_BurnZeroBalance() public { - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 0, 1, TOKEN_ID_1) - ); - facet.burn(alice, TOKEN_ID_1, 1); - } - - /** - * ============================================ - * BurnBatch Tests (via Harness) - * ============================================ - */ - - function test_BurnBatch() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory mintAmounts = new uint256[](3); - mintAmounts[0] = 100; - mintAmounts[1] = 200; - mintAmounts[2] = 300; - - facet.mintBatch(alice, ids, mintAmounts); - - uint256[] memory burnAmounts = new uint256[](3); - burnAmounts[0] = 30; - burnAmounts[1] = 50; - burnAmounts[2] = 100; - - vm.expectEmit(true, true, true, true); - emit TransferBatch(address(this), alice, address(0), ids, burnAmounts); - facet.burnBatch(alice, ids, burnAmounts); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 150); - assertEq(facet.balanceOf(alice, TOKEN_ID_3), 200); - } - - function test_RevertWhen_BurnBatchFromZeroAddress() public { - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidSender.selector, address(0))); - facet.burnBatch(address(0), ids, amounts); - } - - function test_RevertWhen_BurnBatchArrayLengthMismatch() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 100; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidArrayLength.selector, 2, 1)); - facet.burnBatch(alice, ids, amounts); - } - - function test_RevertWhen_BurnBatchInsufficientBalance() public { - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory mintAmounts = new uint256[](2); - mintAmounts[0] = 100; - mintAmounts[1] = 50; - - facet.mintBatch(alice, ids, mintAmounts); - - uint256[] memory burnAmounts = new uint256[](2); - burnAmounts[0] = 50; - burnAmounts[1] = 100; // More than balance - - vm.expectRevert( - abi.encodeWithSelector(ERC1155Facet.ERC1155InsufficientBalance.selector, alice, 50, 100, TOKEN_ID_2) - ); - facet.burnBatch(alice, ids, burnAmounts); - } - - /** - * ============================================ - * Receiver Hook Tests - * ============================================ - */ - - function test_SafeTransferFrom_ToContractWithAcceptance() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 50); - assertEq(facet.balanceOf(address(receiver), TOKEN_ID_1), 50); - } - - function test_SafeTransferFrom_ForwardsDataParameter() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - bytes memory data = hex"deadbeef"; - - vm.prank(alice); - vm.expectEmit(true, true, true, false); - emit ERC1155ReceiverMock.Received(alice, alice, TOKEN_ID_1, 50, data, 0); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, data); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - 0x00c0ffee, // Wrong return value - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithRevertMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithRevertNoMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - } - - function test_SafeBatchTransferFrom_ToContractWithAcceptance() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 100; - amounts[1] = 200; - - facet.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](2); - transferAmounts[0] = 50; - transferAmounts[1] = 100; - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, address(receiver), ids, transferAmounts, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 50); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 100); - assertEq(facet.balanceOf(address(receiver), TOKEN_ID_1), 50); - assertEq(facet.balanceOf(address(receiver), TOKEN_ID_2), 100); - } - - function test_SafeBatchTransferFrom_ForwardsDataParameter() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 50; - amounts[1] = 100; - - facet.mintBatch(alice, ids, amounts); - - bytes memory data = hex"c0ffee"; - - vm.prank(alice); - vm.expectEmit(true, true, true, false); - emit ERC1155ReceiverMock.BatchReceived(alice, alice, ids, amounts, data, 0); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, data); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertNoMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithoutMessage - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithWrongReturnValue() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_SINGLE_MAGIC_VALUE, // Wrong return value (correct for single, wrong for batch) - ERC1155ReceiverMock.RevertType.None - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155InvalidReceiver.selector, address(receiver))); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithRevertMessage() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert("ERC1155ReceiverMock: reverting on batch receive"); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithPanic() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithPanic() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); - } - - function test_RevertWhen_SafeTransferFrom_ToContractWithCustomError() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.RevertWithCustomError - ); - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE)); - facet.safeTransferFrom(alice, address(receiver), TOKEN_ID_1, 50, ""); - } - - function test_RevertWhen_SafeBatchTransferFrom_ToContractWithCustomError() public { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.RevertWithCustomError - ); - - uint256[] memory ids = new uint256[](1); - ids[0] = TOKEN_ID_1; - uint256[] memory amounts = new uint256[](1); - amounts[0] = 50; - - facet.mint(alice, TOKEN_ID_1, 100); - - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE)); - facet.safeBatchTransferFrom(alice, address(receiver), ids, amounts, ""); - } - - function test_SafeBatchTransferFrom_EmptyArrays() public { - uint256[] memory ids = new uint256[](0); - uint256[] memory amounts = new uint256[](0); - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); - /** - * Should not revert - */ - } - - function test_SafeBatchTransferFrom_WithZeroAmounts() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory amounts = new uint256[](3); - amounts[0] = 100; - amounts[1] = 200; - amounts[2] = 300; - - facet.mintBatch(alice, ids, amounts); - - uint256[] memory transferAmounts = new uint256[](3); - transferAmounts[0] = 30; - transferAmounts[1] = 0; // Zero amount - transferAmounts[2] = 50; - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 200); - assertEq(facet.balanceOf(alice, TOKEN_ID_3), 250); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); - assertEq(facet.balanceOf(bob, TOKEN_ID_2), 0); - assertEq(facet.balanceOf(bob, TOKEN_ID_3), 50); - } - - function test_SafeBatchTransferFrom_DuplicateTokenIds() public { - facet.mint(alice, TOKEN_ID_1, 100); - - uint256[] memory ids = new uint256[](2); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_1; // Duplicate - - uint256[] memory amounts = new uint256[](2); - amounts[0] = 10; - amounts[1] = 20; - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, bob, ids, amounts, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 70); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 30); - } - - /** - * ============================================ - * Integration Tests - * ============================================ - */ - - function test_MintTransferBurn_Flow() public { - /** - * Mint to alice - */ - facet.mint(alice, TOKEN_ID_1, 1000); - facet.mint(alice, TOKEN_ID_2, 500); - - /** - * Alice transfers some to bob - */ - vm.prank(alice); - facet.safeTransferFrom(alice, bob, TOKEN_ID_1, 300, ""); - - /** - * Bob transfers some to charlie - */ - vm.prank(bob); - facet.safeTransferFrom(bob, charlie, TOKEN_ID_1, 100, ""); - - /** - * Burn from alice - */ - facet.burn(alice, TOKEN_ID_1, 200); - - /** - * Verify final balances - */ - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 500); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 500); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 200); - assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 100); - } - - function test_MintBatchTransferBatchBurnBatch_Flow() public { - uint256[] memory ids = new uint256[](3); - ids[0] = TOKEN_ID_1; - ids[1] = TOKEN_ID_2; - ids[2] = TOKEN_ID_3; - - uint256[] memory mintAmounts = new uint256[](3); - mintAmounts[0] = 1000; - mintAmounts[1] = 2000; - mintAmounts[2] = 3000; - - /** - * Mint batch to alice - */ - facet.mintBatch(alice, ids, mintAmounts); - - /** - * Alice transfers batch to bob - */ - uint256[] memory transferAmounts = new uint256[](3); - transferAmounts[0] = 300; - transferAmounts[1] = 400; - transferAmounts[2] = 500; - - vm.prank(alice); - facet.safeBatchTransferFrom(alice, bob, ids, transferAmounts, ""); - - /** - * Burn batch from alice - */ - uint256[] memory burnAmounts = new uint256[](3); - burnAmounts[0] = 200; - burnAmounts[1] = 300; - burnAmounts[2] = 400; - - facet.burnBatch(alice, ids, burnAmounts); - - /** - * Verify final balances - */ - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 500); - assertEq(facet.balanceOf(alice, TOKEN_ID_2), 1300); - assertEq(facet.balanceOf(alice, TOKEN_ID_3), 2100); - assertEq(facet.balanceOf(bob, TOKEN_ID_1), 300); - assertEq(facet.balanceOf(bob, TOKEN_ID_2), 400); - assertEq(facet.balanceOf(bob, TOKEN_ID_3), 500); - } - - function test_ApprovalAndTransfer_Flow() public { - facet.mint(alice, TOKEN_ID_1, 1000); - - /** - * Alice approves bob - */ - vm.prank(alice); - facet.setApprovalForAll(bob, true); - - /** - * Bob transfers on behalf of alice - */ - vm.prank(bob); - facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 300, ""); - - assertEq(facet.balanceOf(alice, TOKEN_ID_1), 700); - assertEq(facet.balanceOf(charlie, TOKEN_ID_1), 300); - - /** - * Alice revokes approval - */ - vm.prank(alice); - facet.setApprovalForAll(bob, false); - - /** - * Bob can no longer transfer - */ - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC1155Facet.ERC1155MissingApprovalForAll.selector, bob, alice)); - facet.safeTransferFrom(alice, charlie, TOKEN_ID_1, 100, ""); - } -} diff --git a/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol b/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol deleted file mode 100644 index 9b1078f2..00000000 --- a/test/token/ERC1155/ERC1155/harnesses/ERC1155FacetHarness.sol +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC1155Facet} from "../../../../../src/token/ERC1155/ERC1155Facet.sol"; - -/** - * @title ERC1155FacetHarness - * @notice Test harness for ERC1155Facet that adds initialization and minting for testing - */ -contract ERC1155FacetHarness is ERC1155Facet { - /** - * @notice Initialize the ERC1155 storage - * @dev Only used for testing - production diamonds should initialize in constructor - */ - function initialize(string memory _uri) external { - ERC1155Storage storage s = getStorage(); - s.uri = _uri; - } - - /** - * @notice Set the base URI - * @dev Only used for testing - */ - function setBaseURI(string memory _baseURI) external { - ERC1155Storage storage s = getStorage(); - s.baseURI = _baseURI; - } - - /** - * @notice Set a token-specific URI - * @dev Only used for testing - */ - function setTokenURI(uint256 _tokenId, string memory _tokenURI) external { - ERC1155Storage storage s = getStorage(); - s.tokenURIs[_tokenId] = _tokenURI; - string memory fullURI = bytes(_tokenURI).length > 0 ? string.concat(s.baseURI, _tokenURI) : s.uri; - emit URI(fullURI, _tokenId); - } - - /** - * @notice Mint tokens to an address - * @dev Only used for testing - exposes internal mint functionality - */ - function mint(address _to, uint256 _id, uint256 _value) external { - if (_to == address(0)) { - revert ERC1155InvalidReceiver(address(0)); - } - ERC1155Storage storage s = getStorage(); - s.balanceOf[_id][_to] += _value; - emit TransferSingle(msg.sender, address(0), _to, _id, _value); - } - - /** - * @notice Mint multiple token types to an address - * @dev Only used for testing - exposes internal mintBatch functionality - */ - function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values) external { - if (_to == address(0)) { - revert ERC1155InvalidReceiver(address(0)); - } - if (_ids.length != _values.length) { - revert ERC1155InvalidArrayLength(_ids.length, _values.length); - } - ERC1155Storage storage s = getStorage(); - for (uint256 i = 0; i < _ids.length; i++) { - s.balanceOf[_ids[i]][_to] += _values[i]; - } - emit TransferBatch(msg.sender, address(0), _to, _ids, _values); - } - - /** - * @notice Burn tokens from an address - * @dev Only used for testing - exposes internal burn functionality - */ - function burn(address _from, uint256 _id, uint256 _value) external { - if (_from == address(0)) { - revert ERC1155InvalidSender(address(0)); - } - ERC1155Storage storage s = getStorage(); - uint256 fromBalance = s.balanceOf[_id][_from]; - if (fromBalance < _value) { - revert ERC1155InsufficientBalance(_from, fromBalance, _value, _id); - } - unchecked { - s.balanceOf[_id][_from] = fromBalance - _value; - } - emit TransferSingle(msg.sender, _from, address(0), _id, _value); - } - - /** - * @notice Burn multiple token types from an address - * @dev Only used for testing - exposes internal burnBatch functionality - */ - function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) external { - if (_from == address(0)) { - revert ERC1155InvalidSender(address(0)); - } - if (_ids.length != _values.length) { - revert ERC1155InvalidArrayLength(_ids.length, _values.length); - } - ERC1155Storage storage s = getStorage(); - for (uint256 i = 0; i < _ids.length; i++) { - uint256 id = _ids[i]; - uint256 value = _values[i]; - uint256 fromBalance = s.balanceOf[id][_from]; - if (fromBalance < value) { - revert ERC1155InsufficientBalance(_from, fromBalance, value, id); - } - unchecked { - s.balanceOf[id][_from] = fromBalance - value; - } - } - emit TransferBatch(msg.sender, _from, address(0), _ids, _values); - } -} diff --git a/test/token/ERC1155/ERC1155/harnesses/ERC1155Harness.sol b/test/token/ERC1155/ERC1155/harnesses/ERC1155Harness.sol deleted file mode 100644 index f2259b7c..00000000 --- a/test/token/ERC1155/ERC1155/harnesses/ERC1155Harness.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../../src/token/ERC1155/ERC1155Mod.sol" as ERC1155Mod; - -/** - * @title ERC1155Harness - * @notice Test harness that exposes LibERC1155's internal functions as external - * @dev Required for testing since LibERC1155 only has internal functions - */ -contract ERC1155Harness { - /** - * @notice Initialize the ERC1155 storage - * @dev Only used for testing - */ - function initialize(string memory _uri) external { - ERC1155Mod.ERC1155Storage storage s = ERC1155Mod.getStorage(); - s.uri = _uri; - } - - /** - * @notice Set the base URI - */ - function setBaseURI(string memory _baseURI) external { - ERC1155Mod.setBaseURI(_baseURI); - } - - /** - * @notice Set a token-specific URI - */ - function setTokenURI(uint256 _tokenId, string memory _tokenURI) external { - ERC1155Mod.setTokenURI(_tokenId, _tokenURI); - } - - /** - * @notice Exposes ERC1155Mod.mint as an external function - */ - function mint(address _to, uint256 _id, uint256 _value) external { - ERC1155Mod.mint(_to, _id, _value, new bytes(0)); - } - - /** - * @notice Exposes ERC1155Mod.mintBatch as an external function - */ - function mintBatch(address _to, uint256[] memory _ids, uint256[] memory _values) external { - ERC1155Mod.mintBatch(_to, _ids, _values, new bytes(0)); - } - - /** - * @notice Exposes ERC1155Mod.burn as an external function - */ - function burn(address _from, uint256 _id, uint256 _value) external { - ERC1155Mod.burn(_from, _id, _value); - } - - /** - * @notice Exposes ERC1155Mod.burnBatch as an external function - */ - function burnBatch(address _from, uint256[] memory _ids, uint256[] memory _values) external { - ERC1155Mod.burnBatch(_from, _ids, _values); - } - - /** - * @notice Exposes ERC1155Mod.safeTransferFrom as an external function - */ - function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value) external { - ERC1155Mod.safeTransferFrom(_from, _to, _id, _value, msg.sender); - } - - /** - * @notice Exposes ERC1155Mod.safeBatchTransferFrom as an external function - */ - function safeBatchTransferFrom(address _from, address _to, uint256[] memory _ids, uint256[] memory _values) - external - { - ERC1155Mod.safeBatchTransferFrom(_from, _to, _ids, _values, msg.sender); - } - - /** - * @notice Get the URI for a token type - */ - function uri(uint256 _id) external view returns (string memory) { - ERC1155Mod.ERC1155Storage storage s = ERC1155Mod.getStorage(); - string memory tokenURI = s.tokenURIs[_id]; - return bytes(tokenURI).length > 0 ? string.concat(s.baseURI, tokenURI) : s.uri; - } - - /** - * @notice Get the balance of an account for a token type - */ - function balanceOf(address _account, uint256 _id) external view returns (uint256) { - return ERC1155Mod.getStorage().balanceOf[_id][_account]; - } - - /** - * @notice Get balances for multiple account/token pairs - */ - function balanceOfBatch(address[] memory _accounts, uint256[] memory _ids) - external - view - returns (uint256[] memory) - { - require(_accounts.length == _ids.length, "Length mismatch"); - uint256[] memory balances = new uint256[](_accounts.length); - ERC1155Mod.ERC1155Storage storage s = ERC1155Mod.getStorage(); - for (uint256 i = 0; i < _accounts.length; i++) { - balances[i] = s.balanceOf[_ids[i]][_accounts[i]]; - } - return balances; - } - - /** - * @notice Check if an operator is approved for all tokens of an account - */ - function isApprovedForAll(address _account, address _operator) external view returns (bool) { - return ERC1155Mod.getStorage().isApprovedForAll[_account][_operator]; - } - - /** - * @notice Set approval for an operator - */ - function setApprovalForAll(address _operator, bool _approved) external { - ERC1155Mod.ERC1155Storage storage s = ERC1155Mod.getStorage(); - s.isApprovedForAll[msg.sender][_operator] = _approved; - } -} diff --git a/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol b/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol deleted file mode 100644 index 96af32b9..00000000 --- a/test/token/ERC1155/ERC1155/mocks/ERC1155ReceiverMock.sol +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {IERC1155Receiver} from "../../../../../src/interfaces/IERC1155Receiver.sol"; - -/** - * @title ERC1155ReceiverMock - * @notice Mock implementation of IERC1155Receiver for testing transfer acceptance/rejection - * @dev Supports configurable return values and error conditions to test various scenarios - */ -contract ERC1155ReceiverMock is IERC1155Receiver { - enum RevertType { - None, - RevertWithoutMessage, - RevertWithMessage, - RevertWithCustomError, - Panic - } - - bytes4 private immutable _recRetval; - bytes4 private immutable _batRetval; - RevertType private immutable _error; - - event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); - event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); - - error CustomError(bytes4); - - constructor(bytes4 recRetval, bytes4 batRetval, RevertType error) { - _recRetval = recRetval; - _batRetval = batRetval; - _error = error; - } - - function onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes calldata data) - external - override - returns (bytes4) - { - if (_error == RevertType.RevertWithoutMessage) { - revert(); - } else if (_error == RevertType.RevertWithMessage) { - revert("ERC1155ReceiverMock: reverting on receive"); - } else if (_error == RevertType.RevertWithCustomError) { - revert CustomError(_recRetval); - } else if (_error == RevertType.Panic) { - uint256 a = uint256(0) / uint256(0); - a; - } - - emit Received(operator, from, id, value, data, gasleft()); - return _recRetval; - } - - function onERC1155BatchReceived( - address operator, - address from, - uint256[] calldata ids, - uint256[] calldata values, - bytes calldata data - ) external override returns (bytes4) { - if (_error == RevertType.RevertWithoutMessage) { - revert(); - } else if (_error == RevertType.RevertWithMessage) { - revert("ERC1155ReceiverMock: reverting on batch receive"); - } else if (_error == RevertType.RevertWithCustomError) { - revert CustomError(_batRetval); - } else if (_error == RevertType.Panic) { - uint256 a = uint256(0) / uint256(0); - a; - } - - emit BatchReceived(operator, from, ids, values, data, gasleft()); - return _batRetval; - } -} diff --git a/test/trees/ERC1155.tree b/test/trees/ERC1155.tree new file mode 100644 index 00000000..0363de21 --- /dev/null +++ b/test/trees/ERC1155.tree @@ -0,0 +1,102 @@ +Data +β”œβ”€β”€ BalanceOf +β”‚ β”œβ”€β”€ when account and id are queried +β”‚ β”‚ └── it should return the balance for that (account, id) +β”‚ └── when account is zero address +β”‚ └── it may return 0 (no revert) +β”œβ”€β”€ BalanceOfBatch +β”‚ β”œβ”€β”€ when accounts.length != ids.length +β”‚ β”‚ └── it should revert with ERC1155InvalidArrayLength +β”‚ └── when accounts.length == ids.length +β”‚ β”œβ”€β”€ it should return an array of balances in order +β”‚ └── when arrays are empty +β”‚ └── it should return an empty array +└── IsApprovedForAll + β”œβ”€β”€ when operator has not been approved + β”‚ └── it should return false + └── when operator has been approved + └── it should return true + +Approve +└── SetApprovalForAll + β”œβ”€β”€ when operator is the zero address + β”‚ └── it should revert (if enforced) or document behavior + └── when operator is not the zero address + β”œβ”€β”€ it should set isApprovedForAll[account][operator] to approved + └── it should emit ApprovalForAll + +Transfer +β”œβ”€β”€ SafeTransferFrom +β”‚ β”œβ”€β”€ when from or to is zero address +β”‚ β”‚ └── it should revert +β”‚ β”œβ”€β”€ when caller is not owner and not approved for all +β”‚ β”‚ └── it should revert with ERC1155MissingApprovalForAll +β”‚ β”œβ”€β”€ when balance of from for id is less than value +β”‚ β”‚ └── it should revert with ERC1155InsufficientBalance +β”‚ └── when preconditions hold +β”‚ β”œβ”€β”€ it should decrement balance of from for id by value +β”‚ β”œβ”€β”€ it should increment balance of to for id by value +β”‚ β”œβ”€β”€ it should emit TransferSingle +β”‚ └── when to is a contract +β”‚ └── it should call onERC1155Received and respect return value or revert +└── SafeBatchTransferFrom + β”œβ”€β”€ when ids.length != values.length + β”‚ └── it should revert with ERC1155InvalidArrayLength + β”œβ”€β”€ when from or to is zero address + β”‚ └── it should revert + β”œβ”€β”€ when caller is not owner and not approved for all + β”‚ └── it should revert with ERC1155MissingApprovalForAll + β”œβ”€β”€ when any balance is insufficient + β”‚ └── it should revert with ERC1155InsufficientBalance + └── when preconditions hold + β”œβ”€β”€ it should update all balances and emit TransferBatch + └── when to is a contract + └── it should call onERC1155BatchReceived and respect return value or revert + +Mint +β”œβ”€β”€ Mint +β”‚ β”œβ”€β”€ when to is zero address +β”‚ β”‚ └── it should revert with ERC1155InvalidReceiver +β”‚ └── when to is not zero address +β”‚ β”œβ”€β”€ it should increment balance of to for id by value +β”‚ β”œβ”€β”€ it should emit TransferSingle from address(0) to to +β”‚ └── when to is a contract +β”‚ └── it should call onERC1155Received and respect return value or revert +└── MintBatch + β”œβ”€β”€ when to is zero address + β”‚ └── it should revert with ERC1155InvalidReceiver + β”œβ”€β”€ when ids.length != values.length + β”‚ └── it should revert with ERC1155InvalidArrayLength + └── when preconditions hold + β”œβ”€β”€ it should increment balances and emit TransferBatch + └── when to is a contract + └── it should call onERC1155BatchReceived and respect return value or revert + +Burn +β”œβ”€β”€ Burn +β”‚ β”œβ”€β”€ when from is zero address +β”‚ β”‚ └── it should revert +β”‚ β”œβ”€β”€ when balance of from for id is less than value +β”‚ β”‚ └── it should revert with ERC1155InsufficientBalance +β”‚ └── when balance is sufficient +β”‚ β”œβ”€β”€ it should decrement balance of from for id by value +β”‚ └── it should emit TransferSingle to address(0) +└── BurnBatch + β”œβ”€β”€ when from is zero address + β”‚ └── it should revert + β”œβ”€β”€ when ids.length != values.length + β”‚ └── it should revert with ERC1155InvalidArrayLength + β”œβ”€β”€ when any balance is insufficient + β”‚ └── it should revert with ERC1155InsufficientBalance + └── when preconditions hold + β”œβ”€β”€ it should decrement all balances + └── it should emit TransferBatch to address(0) + +Metadata +β”œβ”€β”€ Uri +β”‚ β”œβ”€β”€ when no token-specific URI is set +β”‚ β”‚ └── it should return the default uri +β”‚ └── when a token-specific URI is set (via Mod) +β”‚ └── it should return baseURI + tokenURIs[id] or documented behavior +└── SetBaseURI_SetTokenURI + └── it should update storage so uri(id) returns the expected value diff --git a/test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol b/test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol new file mode 100644 index 00000000..686058f9 --- /dev/null +++ b/test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol @@ -0,0 +1,19 @@ +// 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 {ERC1155ApproveFacet} from "src/token/ERC1155/Approve/ERC1155ApproveFacet.sol"; + +contract ERC1155ApproveFacet_Base_Test is Base_Test { + ERC1155ApproveFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC1155ApproveFacet(); + vm.label(address(facet), "ERC1155ApproveFacet"); + } +} diff --git a/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol b/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol new file mode 100644 index 00000000..1d5f537b --- /dev/null +++ b/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol @@ -0,0 +1,22 @@ +// 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 {ERC1155ApproveModHarness} from "test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155ApproveMod_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155ApproveModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC1155ApproveModHarness(); + vm.label(address(harness), "ERC1155ApproveModHarness"); + } +} diff --git a/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol b/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol new file mode 100644 index 00000000..298fdde3 --- /dev/null +++ b/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155ApproveFacet_Base_Test} from "test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155ApproveFacet} from "src/token/ERC1155/Approve/ERC1155ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SetApprovalForAll_ERC1155ApproveFacet_Fuzz_Test is ERC1155ApproveFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress(bool approved) external { + vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(ERC1155ApproveFacet.ERC1155InvalidOperator.selector, address(0))); + vm.prank(users.alice); + facet.setApprovalForAll(address(0), approved); + } + + function testFuzz_ShouldSetApprovalAndEmit_SetApprovalForAll_WhenOperatorNotZero( + address operator, + bool approved + ) external { + vm.assume(operator != address(0)); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(address(facet)); + emit ERC1155ApproveFacet.ApprovalForAll(users.alice, operator, approved); + facet.setApprovalForAll(operator, approved); + assertEq(address(facet).isApprovedForAll(users.alice, operator), approved, "isApprovedForAll"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + assertEq(selectors, abi.encodePacked(ERC1155ApproveFacet.setApprovalForAll.selector), "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol b/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol new file mode 100644 index 00000000..41025f95 --- /dev/null +++ b/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155ApproveMod_Base_Test} from "test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import "src/token/ERC1155/Approve/ERC1155ApproveMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SetApprovalForAll_ERC1155ApproveMod_Fuzz_Test is ERC1155ApproveMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress( + address user, + bool approved + ) external { + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidOperator.selector, address(0))); + harness.setApprovalForAll(user, address(0), approved); + } + + function testFuzz_ShouldSetApprovalAndEmit_SetApprovalForAll_WhenOperatorNotZero( + address user, + address operator, + bool approved + ) external { + vm.assume(operator != address(0)); + vm.expectEmit(address(harness)); + emit ApprovalForAll(user, operator, approved); + harness.setApprovalForAll(user, operator, approved); + assertEq(address(harness).isApprovedForAll(user, operator), approved, "isApprovedForAll"); + } +} diff --git a/test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol b/test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol new file mode 100644 index 00000000..b8872119 --- /dev/null +++ b/test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol @@ -0,0 +1,30 @@ +// 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 {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155BurnFacet_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155BurnFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC1155BurnFacet(); + vm.label(address(facet), "ERC1155BurnFacet"); + } + + function seedBalance(address target, uint256 id, address account, uint256 value) internal { + target.setBalanceOf(id, account, value); + } + + function seedApprovalForAll(address target, address account, address operator, bool value) internal { + target.setApprovedForAll(account, operator, value); + } +} diff --git a/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol b/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol new file mode 100644 index 00000000..50ae53b4 --- /dev/null +++ b/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol @@ -0,0 +1,26 @@ +// 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 {ERC1155BurnModHarness} from "test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155BurnMod_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155BurnModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC1155BurnModHarness(); + vm.label(address(harness), "ERC1155BurnModHarness"); + } + + function seedBalance(address target, uint256 id, address account, uint256 value) internal { + target.setBalanceOf(id, account, value); + } +} diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol new file mode 100644 index 00000000..a1235ceb --- /dev/null +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155BurnFacet_Base_Test} from "test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract Burn_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_Burn_WhenFromIsZeroAddress(uint256 id, uint256 value) external { + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InvalidSender.selector, address(0))); + facet.burn(address(0), id, value); + } + + function testFuzz_ShouldRevert_Burn_WhenNotApprovedAndNotOwner( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(from != users.bob); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector(ERC1155BurnFacet.ERC1155MissingApprovalForAll.selector, users.bob, from) + ); + facet.burn(from, id, value); + } + + function testFuzz_ShouldRevert_Burn_WhenInsufficientBalance( + address from, + uint256 id, + uint256 balance, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(balance < type(uint256).max); + value = bound(value, balance + 1, type(uint256).max); + address(facet).setBalanceOf(id, from, balance); + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector( + ERC1155BurnFacet.ERC1155InsufficientBalance.selector, + from, + balance, + value, + id + ) + ); + facet.burn(from, id, value); + } + + function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + vm.stopPrank(); + vm.prank(from); + facet.burn(from, id, value); + assertEq(address(facet).balanceOf(id, from), 0, "balance"); + } + + function testFuzz_ShouldDecrementBalance_Burn_WhenApprovedOperator( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + address(facet).setApprovedForAll(from, users.alice, true); + vm.stopPrank(); + vm.prank(users.alice); + facet.burn(from, id, value); + assertEq(address(facet).balanceOf(id, from), 0, "balance"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC1155BurnFacet.burn.selector, + ERC1155BurnFacet.burnBatch.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol new file mode 100644 index 00000000..8cee821b --- /dev/null +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155BurnFacet_Base_Test} from "test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract BurnBatch_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress( + uint256 id, + uint256 value + ) external { + vm.stopPrank(); + vm.prank(users.alice); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = id; + values[0] = value; + vm.expectRevert(abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InvalidSender.selector, address(0))); + facet.burnBatch(address(0), ids, values); + } + + function testFuzz_ShouldRevert_BurnBatch_WhenIdsLengthNotEqualToValuesLength( + address from, + uint256 idsLen, + uint256 valuesLen + ) external { + vm.assume(from != address(0)); + idsLen = bound(idsLen, 0, 5); + valuesLen = bound(valuesLen, 0, 5); + if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](valuesLen); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InvalidArrayLength.selector, idsLen, valuesLen) + ); + facet.burnBatch(from, ids, values); + } + + function test_ShouldRevert_BurnBatch_WhenNotApprovedAndNotOwner() external { + address(facet).setBalanceOf(1, users.alice, 100); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 50; + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector(ERC1155BurnFacet.ERC1155MissingApprovalForAll.selector, users.bob, users.alice) + ); + facet.burnBatch(users.alice, ids, values); + } + + function test_ShouldRevert_BurnBatch_WhenInsufficientBalanceInLoop() external { + address(facet).setBalanceOf(1, users.alice, 10); + address(facet).setBalanceOf(2, users.alice, 0); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + values[0] = 10; + values[1] = 1; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert( + abi.encodeWithSelector( + ERC1155BurnFacet.ERC1155InsufficientBalance.selector, + users.alice, + 0, + 1, + 2 + ) + ); + facet.burnBatch(users.alice, ids, values); + } + + function test_ShouldDecrementBalances_BurnBatch_WhenApprovedOperator() external { + address(facet).setBalanceOf(1, users.alice, 40); + address(facet).setBalanceOf(2, users.alice, 60); + address(facet).setApprovedForAll(users.alice, users.bob, true); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + values[0] = 40; + values[1] = 60; + vm.stopPrank(); + vm.prank(users.bob); + facet.burnBatch(users.alice, ids, values); + assertEq(address(facet).balanceOf(1, users.alice), 0, "id1"); + assertEq(address(facet).balanceOf(2, users.alice), 0, "id2"); + } + + function testFuzz_ShouldDecrementBalances_BurnBatch_WhenPreconditionsHold( + address from, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(from != address(0)); + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + address(facet).setBalanceOf(id0, from, v0); + address(facet).setBalanceOf(id1, from, v1); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + values[0] = v0; + values[1] = v1; + vm.stopPrank(); + vm.prank(from); + facet.burnBatch(from, ids, values); + assertEq(address(facet).balanceOf(id0, from), 0, "balance id0"); + assertEq(address(facet).balanceOf(id1, from), 0, "balance id1"); + } +} diff --git a/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol new file mode 100644 index 00000000..c41b9c51 --- /dev/null +++ b/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155BurnMod_Base_Test} from "test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import "src/token/ERC1155/Burn/ERC1155BurnMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract Burn_ERC1155BurnMod_Fuzz_Test is ERC1155BurnMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_Burn_WhenFromIsZeroAddress(uint256 id, uint256 value) external { + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidSender.selector, address(0))); + harness.burn(address(0), id, value); + } + + function testFuzz_ShouldRevert_Burn_WhenInsufficientBalance( + address from, + uint256 id, + uint256 balance, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(balance < type(uint256).max); + value = bound(value, balance + 1, type(uint256).max); + address(harness).setBalanceOf(id, from, balance); + vm.expectRevert( + abi.encodeWithSelector(ERC1155InsufficientBalance.selector, from, balance, value, id) + ); + harness.burn(from, id, value); + } + + function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(value != type(uint256).max); + address(harness).setBalanceOf(id, from, value); + harness.burn(from, id, value); + assertEq(address(harness).balanceOf(id, from), 0, "balance"); + } +} diff --git a/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol b/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol new file mode 100644 index 00000000..e706c462 --- /dev/null +++ b/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155BurnMod_Base_Test} from "test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import "src/token/ERC1155/Burn/ERC1155BurnMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract BurnBatch_ERC1155BurnMod_Fuzz_Test is ERC1155BurnMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress( + uint256 id, + uint256 value + ) external { + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = id; + values[0] = value; + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidSender.selector, address(0))); + harness.burnBatch(address(0), ids, values); + } + + function testFuzz_ShouldRevert_BurnBatch_WhenIdsLengthNotEqualToValuesLength( + address from, + uint256 idsLen, + uint256 valuesLen + ) external { + vm.assume(from != address(0)); + idsLen = bound(idsLen, 0, 5); + valuesLen = bound(valuesLen, 0, 5); + if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](valuesLen); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + vm.expectRevert( + abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) + ); + harness.burnBatch(from, ids, values); + } + + function testFuzz_ShouldDecrementBalances_BurnBatch_WhenPreconditionsHold( + address from, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(from != address(0)); + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + address(harness).setBalanceOf(id0, from, v0); + address(harness).setBalanceOf(id1, from, v1); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + values[0] = v0; + values[1] = v1; + harness.burnBatch(from, ids, values); + assertEq(address(harness).balanceOf(id0, from), 0, "balance id0"); + assertEq(address(harness).balanceOf(id1, from), 0, "balance id1"); + } +} diff --git a/test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol b/test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol new file mode 100644 index 00000000..6f03d18a --- /dev/null +++ b/test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol @@ -0,0 +1,30 @@ +// 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 {ERC1155DataFacet} from "src/token/ERC1155/Data/ERC1155DataFacet.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155DataFacet_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155DataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC1155DataFacet(); + vm.label(address(facet), "ERC1155DataFacet"); + } + + function seedBalance(address target, uint256 id, address account, uint256 value) internal { + target.setBalanceOf(id, account, value); + } + + function seedApprovalForAll(address target, address account, address operator, bool value) internal { + target.setApprovedForAll(account, operator, value); + } +} diff --git a/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..35a71b8c --- /dev/null +++ b/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155DataFacet_Base_Test} from "test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155DataFacet} from "src/token/ERC1155/Data/ERC1155DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract Data_ERC1155DataFacet_Fuzz_Test is ERC1155DataFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldReturnBalance_BalanceOf_WhenAccountAndIdQueried( + address account, + uint256 id, + uint256 value + ) external { + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, account, value); + + assertEq(facet.balanceOf(account, id), value, "balanceOf"); + } + + function testFuzz_ShouldReturnZero_BalanceOf_WhenAccountIsZeroAddress(uint256 id) external view { + assertEq(facet.balanceOf(address(0), id), 0, "balanceOf zero address"); + } + + function testFuzz_ShouldRevert_BalanceOfBatch_WhenAccountsLengthNotEqualToIdsLength( + uint256 accountsLen, + uint256 idsLen + ) external { + accountsLen = bound(accountsLen, 0, 10); + idsLen = bound(idsLen, 0, 10); + vm.assume(accountsLen != idsLen); + + address[] memory accounts = new address[](accountsLen); + uint256[] memory ids = new uint256[](idsLen); + for (uint256 i = 0; i < accountsLen; i++) accounts[i] = makeAddr(string.concat("a", vm.toString(i))); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + + vm.expectRevert( + abi.encodeWithSelector(ERC1155DataFacet.ERC1155InvalidArrayLength.selector, idsLen, accountsLen) + ); + facet.balanceOfBatch(accounts, ids); + } + + function testFuzz_ShouldReturnBalancesInOrder_BalanceOfBatch( + address a0, + address a1, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + address(facet).setBalanceOf(id0, a0, v0); + address(facet).setBalanceOf(id1, a1, v1); + + address[] memory accounts = new address[](2); + accounts[0] = a0; + accounts[1] = a1; + uint256[] memory ids = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances.length, 2, "length"); + assertEq(balances[0], v0, "balance 0"); + assertEq(balances[1], v1, "balance 1"); + } + + function test_ShouldReturnEmptyArray_BalanceOfBatch_WhenArraysEmpty() external view { + address[] memory accounts; + uint256[] memory ids; + uint256[] memory balances = facet.balanceOfBatch(accounts, ids); + assertEq(balances.length, 0, "empty"); + } + + function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotApproved( + address account, + address operator + ) external view { + assertEq(facet.isApprovedForAll(account, operator), false, "isApprovedForAll"); + } + + function testFuzz_ShouldReturnTrue_IsApprovedForAll_WhenApproved( + address account, + address operator + ) external { + address(facet).setApprovedForAll(account, operator, true); + assertEq(facet.isApprovedForAll(account, operator), true, "isApprovedForAll"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC1155DataFacet.balanceOf.selector, + ERC1155DataFacet.balanceOfBatch.selector, + ERC1155DataFacet.isApprovedForAll.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol b/test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol new file mode 100644 index 00000000..d9d64b63 --- /dev/null +++ b/test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol @@ -0,0 +1,19 @@ +// 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 {ERC1155MetadataFacet} from "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol"; + +contract ERC1155MetadataFacet_Base_Test is Base_Test { + ERC1155MetadataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC1155MetadataFacet(); + vm.label(address(facet), "ERC1155MetadataFacet"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol b/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol new file mode 100644 index 00000000..1d00b001 --- /dev/null +++ b/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol @@ -0,0 +1,19 @@ +// 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 {ERC1155MetadataModHarness} from "test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol"; + +abstract contract ERC1155MetadataMod_Base_Test is Base_Test { + ERC1155MetadataModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC1155MetadataModHarness(); + vm.label(address(harness), "ERC1155MetadataModHarness"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol b/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol new file mode 100644 index 00000000..695d7fb3 --- /dev/null +++ b/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MetadataFacet_Base_Test} from "test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol"; +import {ERC1155MetadataFacet} from "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + * + * Note: Default URI and token-specific URI are set via the mod (setURI, setBaseURI, setTokenURI). + * The facet only exposes uri(id). So when testing the facet in isolation, storage is never set; + * uri() returns empty string for default and for token-specific. We test that uri() is callable + * and returns the empty string when uninitialized. For "default uri" and "baseURI+tokenURI" + * behavior we use the Metadata mod harness (same storage as facet) in Metadata/mod/fuzz/metadata.t.sol. + */ +contract Uri_ERC1155MetadataFacet_Fuzz_Test is ERC1155MetadataFacet_Base_Test { + function testFuzz_ShouldReturnUri_Uri_WhenUninitialized(uint256 id) external view { + assertEq(facet.uri(id), "", "uri uninitialized"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + assertEq(selectors, abi.encodePacked(ERC1155MetadataFacet.uri.selector), "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/mod/fuzz/metadata.t.sol b/test/unit/token/ERC1155/Metadata/mod/fuzz/metadata.t.sol new file mode 100644 index 00000000..e6850cfd --- /dev/null +++ b/test/unit/token/ERC1155/Metadata/mod/fuzz/metadata.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MetadataMod_Base_Test} from "test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + * + * Tests setBaseURI, setTokenURI (and setURI) via the mod harness; asserts via uri() that + * storage was updated so uri(id) returns the expected value. + */ +contract Metadata_ERC1155MetadataMod_Fuzz_Test is ERC1155MetadataMod_Base_Test { + function test_ShouldReturnDefaultUri_Uri_WhenNoTokenSpecificUriSet() external { + string memory defaultUri = "https://example.com/{id}.json"; + harness.setURI(defaultUri); + assertEq(harness.uri(0), defaultUri, "default uri"); + assertEq(harness.uri(1), defaultUri, "default uri id 1"); + } + + function test_ShouldReturnBaseURIAndTokenURI_Uri_WhenTokenSpecificUriSet() external { + string memory baseURI = "https://base.uri/"; + string memory tokenURI = "token1.json"; + harness.setBaseURI(baseURI); + harness.setTokenURI(1, tokenURI); + assertEq(harness.uri(1), string.concat(baseURI, tokenURI), "uri(1)"); + } + + function testFuzz_ShouldReturnExpectedUri_Uri_WhenSetViaMod( + uint256 id, + string memory baseURI, + string memory tokenURI + ) external { + vm.assume(bytes(baseURI).length <= 100); + vm.assume(bytes(tokenURI).length > 0); + vm.assume(bytes(tokenURI).length <= 100); + harness.setBaseURI(baseURI); + harness.setTokenURI(id, tokenURI); + assertEq(harness.uri(id), string.concat(baseURI, tokenURI), "uri"); + } + + function test_ShouldUpdateUri_SetURI() external { + harness.setURI("https://default.com/"); + assertEq(harness.uri(999), "https://default.com/", "default"); + } +} diff --git a/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol b/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol new file mode 100644 index 00000000..2a7aa224 --- /dev/null +++ b/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol @@ -0,0 +1,22 @@ +// 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 {ERC1155MintModHarness} from "test/harnesses/token/ERC1155/ERC1155MintModHarness.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155MintMod_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155MintModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC1155MintModHarness(); + vm.label(address(harness), "ERC1155MintModHarness"); + } +} diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol new file mode 100644 index 00000000..849a387c --- /dev/null +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MintMod_Base_Test} from "test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import "src/token/ERC1155/Mint/ERC1155MintMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract Mint_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_Mint_WhenToIsZeroAddress(uint256 id, uint256 value) external { + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(0))); + harness.mint(address(0), id, value, ""); + } + + function testFuzz_ShouldIncrementBalanceAndEmit_Mint_WhenToNotZero( + address to, + uint256 id, + uint256 value + ) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + vm.assume(value != type(uint256).max); + + harness.mint(to, id, value, ""); + + assertEq(address(harness).balanceOf(id, to), value, "balanceOf"); + } + + function testFuzz_ShouldIncrementBalance_Mint_WhenToIsReceiverContract(uint256 id, uint256 value) external { + vm.assume(value != type(uint256).max); + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + + harness.mint(address(receiver), id, value, ""); + + assertEq(address(harness).balanceOf(id, address(receiver)), value, "balanceOf"); + } + + function testFuzz_ShouldRevert_Mint_WhenReceiverContractReturnsWrongValue(uint256 id, uint256 value) external { + vm.assume(value != type(uint256).max); + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + bytes4(0), + bytes4(0), + ERC1155ReceiverMock.RevertType.None + ); + + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(receiver))); + harness.mint(address(receiver), id, value, ""); + } + + function test_ShouldRevert_Mint_WhenReceiverContractRevertsWithMessage(uint256 id, uint256 value) external { + vm.assume(value != type(uint256).max); + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithMessage + ); + vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); + harness.mint(address(receiver), id, value, ""); + } + + function test_ShouldRevert_Mint_WhenOverflowsRecipient() external { + uint256 nearMax = type(uint256).max - 100; + address(harness).setBalanceOf(1, users.alice, nearMax); + vm.expectRevert(); + harness.mint(users.alice, 1, 200, ""); + } +} diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol new file mode 100644 index 00000000..9238f588 --- /dev/null +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MintMod_Base_Test} from "test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import "src/token/ERC1155/Mint/ERC1155MintMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract MintBatch_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_MintBatch_WhenToIsZeroAddress(uint256 idsLen, uint256 v) external { + vm.assume(idsLen <= 10); + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](idsLen); + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + values[i] = v; + } + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(0))); + harness.mintBatch(address(0), ids, values, ""); + } + + function testFuzz_ShouldRevert_MintBatch_WhenIdsLengthNotEqualToValuesLength( + address to, + uint256 idsLen, + uint256 valuesLen + ) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + idsLen = bound(idsLen, 0, 5); + valuesLen = bound(valuesLen, 0, 5); + if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; + + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](valuesLen); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + + vm.expectRevert( + abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) + ); + harness.mintBatch(to, ids, values, ""); + } + + function testFuzz_ShouldIncrementBalancesAndEmit_MintBatch_WhenPreconditionsHold( + address to, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + + uint256[] memory ids = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + uint256[] memory values = new uint256[](2); + values[0] = v0; + values[1] = v1; + + harness.mintBatch(to, ids, values, ""); + + assertEq(address(harness).balanceOf(id0, to), v0, "balance id0"); + assertEq(address(harness).balanceOf(id1, to), v1, "balance id1"); + } + + function test_ShouldSucceed_MintBatch_WhenEmptyArrays(address to) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + uint256[] memory ids; + uint256[] memory values; + harness.mintBatch(to, ids, values, ""); + } + + function testFuzz_ShouldIncrementBalances_MintBatch_WhenToIsReceiverContract( + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + + uint256[] memory ids = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + uint256[] memory values = new uint256[](2); + values[0] = v0; + values[1] = v1; + + harness.mintBatch(address(receiver), ids, values, ""); + + assertEq(address(harness).balanceOf(id0, address(receiver)), v0, "balance id0"); + assertEq(address(harness).balanceOf(id1, address(receiver)), v1, "balance id1"); + } + + function test_ShouldRevert_MintBatch_WhenReceiverContractReturnsWrongValue() external { + // Receiver returns single magic for batch (wrong return for onERC1155BatchReceived) + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_SINGLE_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 100; + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(receiver))); + harness.mintBatch(address(receiver), ids, values, ""); + } +} diff --git a/test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol b/test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol new file mode 100644 index 00000000..407229f6 --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol @@ -0,0 +1,30 @@ +// 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 {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155TransferFacet_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155TransferFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC1155TransferFacet(); + vm.label(address(facet), "ERC1155TransferFacet"); + } + + function seedBalance(address target, uint256 id, address account, uint256 value) internal { + target.setBalanceOf(id, account, value); + } + + function seedApprovalForAll(address target, address account, address operator, bool value) internal { + target.setApprovedForAll(account, operator, value); + } +} diff --git a/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol b/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol new file mode 100644 index 00000000..38d1709f --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol @@ -0,0 +1,30 @@ +// 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 {ERC1155TransferModHarness} from "test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; + +abstract contract ERC1155TransferMod_Base_Test is Base_Test { + using ERC1155StorageUtils for address; + + ERC1155TransferModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC1155TransferModHarness(); + vm.label(address(harness), "ERC1155TransferModHarness"); + } + + function seedBalance(address target, uint256 id, address account, uint256 value) internal { + target.setBalanceOf(id, account, value); + } + + function seedApprovalForAll(address target, address account, address operator, bool value) internal { + target.setApprovedForAll(account, operator, value); + } +} diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol new file mode 100644 index 00000000..8d5e133f --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155TransferFacet_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; +import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {RevertingReceiver} from "test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenIdsLengthNotEqualToValuesLength( + address from, + address to, + uint256 idsLen, + uint256 valuesLen + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + idsLen = bound(idsLen, 0, 5); + valuesLen = bound(valuesLen, 0, 5); + if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](valuesLen); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidArrayLength.selector, idsLen, valuesLen) + ); + facet.safeBatchTransferFrom(from, to, ids, values, ""); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenFromIsZeroAddress() external { + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidSender.selector, address(0))); + facet.safeBatchTransferFrom(address(0), users.bob, ids, values, ""); + } + + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenToIsZeroAddress( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + address(facet).setBalanceOf(id, from, value); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = id; + values[0] = value; + vm.stopPrank(); + vm.prank(from); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(0))); + facet.safeBatchTransferFrom(from, address(0), ids, values, ""); + } + + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenInsufficientBalance( + address from, + address to, + uint256 id, + uint256 balance, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to != from); + vm.assume(to.code.length == 0); + vm.assume(balance < type(uint256).max); + value = bound(value, balance + 1, type(uint256).max); + address(facet).setBalanceOf(id, from, balance); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = id; + values[0] = value; + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector( + ERC1155TransferFacet.ERC1155InsufficientBalance.selector, + from, + balance, + value, + id + ) + ); + facet.safeBatchTransferFrom(from, to, ids, values, ""); + } + + function testFuzz_ShouldUpdateBalances_SafeBatchTransferFrom_WhenPreconditionsHold( + address from, + address to, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(from != to); + vm.assume(to.code.length == 0); + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + address(facet).setBalanceOf(id0, from, v0); + address(facet).setBalanceOf(id1, from, v1); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + values[0] = v0; + values[1] = v1; + vm.stopPrank(); + vm.prank(from); + facet.safeBatchTransferFrom(from, to, ids, values, ""); + assertEq(address(facet).balanceOf(id0, from), 0, "from id0"); + assertEq(address(facet).balanceOf(id1, from), 0, "from id1"); + assertEq(address(facet).balanceOf(id0, to), v0, "to id0"); + assertEq(address(facet).balanceOf(id1, to), v1, "to id1"); + } + + function test_ShouldUpdateBalances_SafeBatchTransferFrom_WhenToIsReceiverContractAccepting() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 20); + address(facet).setBalanceOf(2, users.alice, 30); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + values[0] = 20; + values[1] = 30; + vm.stopPrank(); + vm.prank(users.alice); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + assertEq(address(facet).balanceOf(1, address(receiver)), 20, "to id1"); + assertEq(address(facet).balanceOf(2, address(receiver)), 30, "to id2"); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverContractReturnsWrongValue() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + bytes4(0), + bytes4(0), + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 10); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverRevertsWithNoData() external { + RevertingReceiver receiver = new RevertingReceiver(false); + address(facet).setBalanceOf(1, users.alice, 10); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverRevertsWithMessage() external { + RevertingReceiver receiver = new RevertingReceiver(true); + address(facet).setBalanceOf(1, users.alice, 10); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert("ERC1155Receiver: revert"); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverPanics() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.Panic + ); + address(facet).setBalanceOf(1, users.alice, 10); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + } + + function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverRevertsWithCustomError() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + address(facet).setBalanceOf(1, users.alice, 10); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = 1; + values[0] = 10; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert( + abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE) + ); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); + } + + function test_ShouldForwardData_SafeBatchTransferFrom_WhenToIsReceiverContract() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 50); + address(facet).setBalanceOf(2, users.alice, 100); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = 1; + ids[1] = 2; + values[0] = 50; + values[1] = 100; + bytes memory data = hex"c0ffee"; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, false); + emit ERC1155ReceiverMock.BatchReceived(users.alice, users.alice, ids, values, data, 0); + facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, data); + } + + function test_ShouldSucceed_SafeBatchTransferFrom_WhenEmptyArrays() external { + uint256[] memory ids; + uint256[] memory values; + vm.stopPrank(); + vm.prank(users.alice); + facet.safeBatchTransferFrom(users.alice, users.bob, ids, values, ""); + } + + function test_ShouldUpdateBalances_SafeBatchTransferFrom_WhenSomeAmountsZero() external { + address(facet).setBalanceOf(1, users.alice, 100); + address(facet).setBalanceOf(2, users.alice, 200); + address(facet).setBalanceOf(3, users.alice, 300); + uint256[] memory ids = new uint256[](3); + uint256[] memory values = new uint256[](3); + ids[0] = 1; + ids[1] = 2; + ids[2] = 3; + values[0] = 30; + values[1] = 0; + values[2] = 50; + vm.stopPrank(); + vm.prank(users.alice); + facet.safeBatchTransferFrom(users.alice, users.bob, ids, values, ""); + assertEq(address(facet).balanceOf(1, users.alice), 70); + assertEq(address(facet).balanceOf(2, users.alice), 200); + assertEq(address(facet).balanceOf(3, users.alice), 250); + assertEq(address(facet).balanceOf(1, users.bob), 30); + assertEq(address(facet).balanceOf(2, users.bob), 0); + assertEq(address(facet).balanceOf(3, users.bob), 50); + } + + function test_ShouldUpdateBalances_SafeBatchTransferFrom_WhenDuplicateTokenIds() external { + address(facet).setBalanceOf(1, users.alice, 100); + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = 1; + ids[1] = 1; + values[0] = 10; + values[1] = 20; + vm.stopPrank(); + vm.prank(users.alice); + facet.safeBatchTransferFrom(users.alice, users.bob, ids, values, ""); + assertEq(address(facet).balanceOf(1, users.alice), 70); + assertEq(address(facet).balanceOf(1, users.bob), 30); + } +} diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol new file mode 100644 index 00000000..ff5b2373 --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155TransferFacet_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; +import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {IERC1155Receiver} from "src/interfaces/IERC1155Receiver.sol"; + +/** + * @dev Reverting receiver to cover catch branches (reason.length == 0 and reason.length > 0). + */ +contract RevertingReceiver is IERC1155Receiver { + bool public revertWithMessage; + + constructor(bool _revertWithMessage) { + revertWithMessage = _revertWithMessage; + } + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) + external + view + override + returns (bytes4) + { + if (revertWithMessage) revert("ERC1155Receiver: revert"); + revert(); + } + + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) + external + view + override + returns (bytes4) + { + if (revertWithMessage) revert("ERC1155Receiver: revert"); + revert(); + } +} + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress( + address from, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + address(facet).setBalanceOf(id, from, value); + vm.stopPrank(); + vm.prank(from); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(0))); + facet.safeTransferFrom(from, address(0), id, value, ""); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress( + address to, + uint256 id, + uint256 value + ) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidSender.selector, address(0))); + facet.safeTransferFrom(address(0), to, id, value, ""); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenNotApprovedAndNotOwner( + address from, + address to, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to != from); + vm.assume(from != users.bob); + vm.assume(to.code.length == 0); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + vm.stopPrank(); + vm.prank(users.bob); + vm.expectRevert( + abi.encodeWithSelector(ERC1155TransferFacet.ERC1155MissingApprovalForAll.selector, users.bob, from) + ); + facet.safeTransferFrom(from, to, id, value, ""); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenInsufficientBalance( + address from, + address to, + uint256 id, + uint256 balance, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to != from); + vm.assume(to.code.length == 0); + vm.assume(balance < type(uint256).max); + value = bound(value, balance + 1, type(uint256).max); + address(facet).setBalanceOf(id, from, balance); + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector( + ERC1155TransferFacet.ERC1155InsufficientBalance.selector, + from, + balance, + value, + id + ) + ); + facet.safeTransferFrom(from, to, id, value, ""); + } + + function testFuzz_ShouldUpdateBalances_SafeTransferFrom_WhenPreconditionsHold( + address from, + address to, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(from != to); + vm.assume(to.code.length == 0); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + vm.stopPrank(); + vm.prank(from); + facet.safeTransferFrom(from, to, id, value, ""); + assertEq(address(facet).balanceOf(id, from), 0, "from balance"); + assertEq(address(facet).balanceOf(id, to), value, "to balance"); + } + + function testFuzz_ShouldUpdateBalances_SafeTransferFrom_WhenApprovedOperator( + address from, + address to, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(from != to); + vm.assume(to != users.alice); + vm.assume(to.code.length == 0); + vm.assume(value != type(uint256).max); + address(facet).setBalanceOf(id, from, value); + address(facet).setApprovedForAll(from, users.alice, true); + vm.stopPrank(); + vm.prank(users.alice); + facet.safeTransferFrom(from, to, id, value, ""); + assertEq(address(facet).balanceOf(id, from), 0, "from balance"); + assertEq(address(facet).balanceOf(id, to), value, "to balance"); + } + + function test_ShouldUpdateBalances_SafeTransferFrom_WhenValueIsZero() external { + address(facet).setBalanceOf(1, users.alice, 100); + vm.stopPrank(); + vm.prank(users.alice); + facet.safeTransferFrom(users.alice, users.bob, 1, 0, ""); + assertEq(address(facet).balanceOf(1, users.alice), 100, "from unchanged"); + assertEq(address(facet).balanceOf(1, users.bob), 0, "to unchanged"); + } + + function test_ShouldUpdateBalances_SafeTransferFrom_WhenToIsReceiverContractAccepting() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 50); + vm.stopPrank(); + vm.prank(users.alice); + facet.safeTransferFrom(users.alice, address(receiver), 1, 50, ""); + assertEq(address(facet).balanceOf(1, users.alice), 0, "from balance"); + assertEq(address(facet).balanceOf(1, address(receiver)), 50, "to balance"); + } + + function test_ShouldRevert_SafeTransferFrom_WhenReceiverContractReturnsWrongValue() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + bytes4(0), + bytes4(0), + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 10); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); + } + + function test_ShouldRevert_SafeTransferFrom_WhenReceiverRevertsWithNoData() external { + RevertingReceiver receiver = new RevertingReceiver(false); + address(facet).setBalanceOf(1, users.alice, 10); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); + } + + function test_ShouldRevert_SafeTransferFrom_WhenReceiverRevertsWithMessage() external { + RevertingReceiver receiver = new RevertingReceiver(true); + address(facet).setBalanceOf(1, users.alice, 10); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert("ERC1155Receiver: revert"); + facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); + } + + function test_ShouldRevert_SafeTransferFrom_WhenReceiverPanics() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.Panic + ); + address(facet).setBalanceOf(1, users.alice, 10); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert(); + facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); + } + + function test_ShouldRevert_SafeTransferFrom_WhenReceiverRevertsWithCustomError() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.RevertWithCustomError + ); + address(facet).setBalanceOf(1, users.alice, 10); + vm.stopPrank(); + vm.prank(users.alice); + vm.expectRevert( + abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE) + ); + facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); + } + + function test_ShouldForwardData_SafeTransferFrom_WhenToIsReceiverContract() external { + ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( + RECEIVER_SINGLE_MAGIC_VALUE, + RECEIVER_BATCH_MAGIC_VALUE, + ERC1155ReceiverMock.RevertType.None + ); + address(facet).setBalanceOf(1, users.alice, 50); + bytes memory data = hex"deadbeef"; + vm.stopPrank(); + vm.prank(users.alice); + vm.expectEmit(true, true, true, false); + emit ERC1155ReceiverMock.Received(users.alice, users.alice, 1, 50, data, 0); + facet.safeTransferFrom(users.alice, address(receiver), 1, 50, data); + } +} diff --git a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol new file mode 100644 index 00000000..c7097a7a --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155TransferMod_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import "src/token/ERC1155/Transfer/ERC1155TransferMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SafeBatchTransferFrom_ERC1155TransferMod_Fuzz_Test is ERC1155TransferMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenIdsLengthNotEqualToValuesLength( + address from, + address to, + address operator, + uint256 idsLen, + uint256 valuesLen + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + idsLen = bound(idsLen, 0, 5); + valuesLen = bound(valuesLen, 0, 5); + if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; + uint256[] memory ids = new uint256[](idsLen); + uint256[] memory values = new uint256[](valuesLen); + for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + vm.expectRevert( + abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) + ); + harness.safeBatchTransferFrom(from, to, ids, values, operator); + } + + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenToIsZeroAddress( + address from, + address operator, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + address(harness).setBalanceOf(id, from, value); + uint256[] memory ids = new uint256[](1); + uint256[] memory values = new uint256[](1); + ids[0] = id; + values[0] = value; + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(0))); + harness.safeBatchTransferFrom(from, address(0), ids, values, operator); + } + + function testFuzz_ShouldUpdateBalances_SafeBatchTransferFrom_WhenPreconditionsHold( + address from, + address to, + address operator, + uint256 id0, + uint256 id1, + uint256 v0, + uint256 v1 + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(from != to); + vm.assume(to.code.length == 0); + vm.assume(id0 != id1); + vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); + address(harness).setBalanceOf(id0, from, v0); + address(harness).setBalanceOf(id1, from, v1); + if (operator != from) { + address(harness).setApprovedForAll(from, operator, true); + } + uint256[] memory ids = new uint256[](2); + uint256[] memory values = new uint256[](2); + ids[0] = id0; + ids[1] = id1; + values[0] = v0; + values[1] = v1; + harness.safeBatchTransferFrom(from, to, ids, values, operator); + assertEq(address(harness).balanceOf(id0, from), 0, "from id0"); + assertEq(address(harness).balanceOf(id1, from), 0, "from id1"); + assertEq(address(harness).balanceOf(id0, to), v0, "to id0"); + assertEq(address(harness).balanceOf(id1, to), v1, "to id1"); + } +} diff --git a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol new file mode 100644 index 00000000..0d1b1396 --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155TransferMod_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol"; +import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; +import "src/token/ERC1155/Transfer/ERC1155TransferMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract SafeTransferFrom_ERC1155TransferMod_Fuzz_Test is ERC1155TransferMod_Base_Test { + using ERC1155StorageUtils for address; + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress( + address from, + address operator, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + address(harness).setBalanceOf(id, from, value); + address(harness).setApprovedForAll(from, operator, true); + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(0))); + harness.safeTransferFrom(from, address(0), id, value, operator); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress( + address to, + uint256 id, + uint256 value + ) external { + vm.assume(to != address(0)); + vm.assume(to.code.length == 0); + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidSender.selector, address(0))); + harness.safeTransferFrom(address(0), to, id, value, users.alice); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenNotApprovedAndNotOwner( + address from, + address to, + address operator, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(to != from); + vm.assume(to.code.length == 0); + vm.assume(operator != from); + vm.assume(value != type(uint256).max); + address(harness).setBalanceOf(id, from, value); + vm.expectRevert( + abi.encodeWithSelector(ERC1155MissingApprovalForAll.selector, operator, from) + ); + harness.safeTransferFrom(from, to, id, value, operator); + } + + function testFuzz_ShouldUpdateBalances_SafeTransferFrom_WhenPreconditionsHold( + address from, + address to, + address operator, + uint256 id, + uint256 value + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + vm.assume(from != to); + vm.assume(to.code.length == 0); + vm.assume(operator == from || operator != from); + vm.assume(value != type(uint256).max); + address(harness).setBalanceOf(id, from, value); + if (operator != from) { + address(harness).setApprovedForAll(from, operator, true); + } + harness.safeTransferFrom(from, to, id, value, operator); + assertEq(address(harness).balanceOf(id, from), 0, "from balance"); + assertEq(address(harness).balanceOf(id, to), value, "to balance"); + } +} diff --git a/test/utils/storage/ERC1155StorageUtils.sol b/test/utils/storage/ERC1155StorageUtils.sol new file mode 100644 index 00000000..ff7cc664 --- /dev/null +++ b/test/utils/storage/ERC1155StorageUtils.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 ERC1155StorageUtils + * @notice Storage manipulation utilities for ERC1155-related testing. + * @dev Uses vm.load and vm.store to directly manipulate ERC1155 storage. + * Layout matches src/token/ERC1155: keccak256("erc1155") for core data. + */ +library ERC1155StorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant ERC1155_STORAGE_POSITION = keccak256("erc1155"); + + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + + /* + * @notice ERC-1155 storage layout (ERC-8042 standard) + * @custom:storage-location erc8042:erc1155 + * + * Slot 0: mapping(uint256 id => mapping(address account => uint256)) balanceOf + * Slot 1: mapping(address account => mapping(address operator => bool)) isApprovedForAll + */ + + function balanceOf(address target, uint256 id, address account) internal view returns (uint256) { + bytes32 idSlot = keccak256(abi.encode(id, uint256(ERC1155_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(account, idSlot)); + return uint256(vm.load(target, slot)); + } + + function setBalanceOf(address target, uint256 id, address account, uint256 value) internal { + bytes32 idSlot = keccak256(abi.encode(id, uint256(ERC1155_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(account, idSlot)); + vm.store(target, slot, bytes32(value)); + } + + function isApprovedForAll(address target, address account, address operator) internal view returns (bool) { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(ERC1155_STORAGE_POSITION) + 1)); + bytes32 slot = keccak256(abi.encode(operator, accountSlot)); + return uint256(vm.load(target, slot)) != 0; + } + + function setApprovedForAll(address target, address account, address operator, bool value) internal { + bytes32 accountSlot = keccak256(abi.encode(account, uint256(ERC1155_STORAGE_POSITION) + 1)); + bytes32 slot = keccak256(abi.encode(operator, accountSlot)); + vm.store(target, slot, value ? bytes32(uint256(1)) : bytes32(0)); + } +} From 76441064368af92161d8d1e4ffc07e24f73d66a0 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 22:26:10 -0400 Subject: [PATCH 06/25] complete erc20 tests --- test/trees/ERC20.tree | 74 +++++++++- .../ERC20/Approve/facet/fuzz/approve.t.sol | 1 + .../Approve/facet/fuzz/exportSelectors.t.sol | 28 ++++ .../Bridgeable/ERC20BridgeableFacetBase.t.sol | 30 +++++ .../facet/fuzz/checkTokenBridge.t.sol | 35 +++++ .../facet/fuzz/crosschainBurn.t.sol | 84 ++++++++++++ .../facet/fuzz/crosschainMint.t.sol | 59 ++++++++ .../facet/fuzz/exportSelectors.t.sol | 24 ++++ .../ERC20/Burn/facet/ERC20BurnFacetBase.t.sol | 19 +++ .../token/ERC20/Burn/facet/fuzz/burn.t.sol | 55 ++++++++ .../ERC20/Burn/facet/fuzz/burnFrom.t.sol | 126 ++++++++++++++++++ .../Burn/facet/fuzz/exportSelectors.t.sol | 23 ++++ .../token/ERC20/Data/ERC20DataFacetBase.t.sol | 22 +++ .../token/ERC20/Data/facet/fuzz/data.t.sol | 54 ++++++++ .../Data/facet/fuzz/exportSelectors.t.sol | 24 ++++ .../Metadata/ERC20MetadataFacetBase.t.sol | 20 +++ .../Metadata/facet/fuzz/exportSelectors.t.sol | 24 ++++ .../ERC20/Metadata/facet/fuzz/metadata.t.sol | 46 +++++++ .../ERC20/Permit/ERC20PermitFacetBase.t.sol | 46 +++++++ .../Permit/facet/fuzz/domainSeparator.t.sol | 26 ++++ .../Permit/facet/fuzz/exportSelectors.t.sol | 24 ++++ .../ERC20/Permit/facet/fuzz/nonces.t.sol | 32 +++++ .../ERC20/Permit/facet/fuzz/permit.t.sol | 126 ++++++++++++++++++ .../Transfer/facet/fuzz/exportSelectors.t.sol | 23 ++++ .../ERC20/Transfer/mod/fuzz/transfer.t.sol | 27 ++++ .../Transfer/mod/fuzz/transferFrom.t.sol | 39 ++++++ 26 files changed, 1090 insertions(+), 1 deletion(-) create mode 100644 test/unit/token/ERC20/Approve/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol create mode 100644 test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol create mode 100644 test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol create mode 100644 test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol create mode 100644 test/unit/token/ERC20/Bridgeable/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Burn/facet/ERC20BurnFacetBase.t.sol create mode 100644 test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol create mode 100644 test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol create mode 100644 test/unit/token/ERC20/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol create mode 100644 test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Metadata/facet/fuzz/metadata.t.sol create mode 100644 test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol create mode 100644 test/unit/token/ERC20/Permit/facet/fuzz/domainSeparator.t.sol create mode 100644 test/unit/token/ERC20/Permit/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC20/Permit/facet/fuzz/nonces.t.sol create mode 100644 test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol create mode 100644 test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol diff --git a/test/trees/ERC20.tree b/test/trees/ERC20.tree index a8bae69e..58f2d79e 100644 --- a/test/trees/ERC20.tree +++ b/test/trees/ERC20.tree @@ -1,3 +1,16 @@ +Data +β”œβ”€β”€ BalanceOf +β”‚ β”œβ”€β”€ when account is queried +β”‚ β”‚ └── it should return the balance for that account +β”‚ └── when account is zero address +β”‚ └── it should return 0 +β”œβ”€β”€ TotalSupply +β”‚ └── when queried +β”‚ └── it should return the total token supply +└── Allowance + └── when owner and spender are queried + └── it should return the remaining allowance for that (owner, spender) + Approve β”œβ”€β”€ when the spender is the zero address β”‚ └── it should revert @@ -73,4 +86,63 @@ Burn └── when the account balance is greater than or equal to the burn amount β”œβ”€β”€ it should decrement the account balance by the burn amount β”œβ”€β”€ it should decrement total supply by the burn amount - └── it should emit a {Transfer} event to the zero address \ No newline at end of file + └── it should emit a {Transfer} event to the zero address + +Metadata +β”œβ”€β”€ Name +β”‚ └── when set (via Mod) +β”‚ └── it should return the token name +β”œβ”€β”€ Symbol +β”‚ └── when set (via Mod) +β”‚ └── it should return the token symbol +└── Decimals + └── when set (via Mod) + └── it should return the number of decimals + +Bridgeable +β”œβ”€β”€ CrosschainMint +β”‚ β”œβ”€β”€ when caller does not have trusted-bridge role +β”‚ β”‚ └── it should revert with AccessControlUnauthorizedAccount +β”‚ β”œβ”€β”€ when account is zero address +β”‚ β”‚ └── it should revert with ERC20InvalidReceiver +β”‚ └── when caller has trusted-bridge role and account is not zero +β”‚ β”œβ”€β”€ it should increment total supply and account balance by the amount +β”‚ └── it should emit {Transfer} from zero and {CrosschainMint} +β”œβ”€β”€ CrosschainBurn +β”‚ β”œβ”€β”€ when caller does not have trusted-bridge role +β”‚ β”‚ └── it should revert with AccessControlUnauthorizedAccount +β”‚ β”œβ”€β”€ when from is zero address +β”‚ β”‚ └── it should revert with ERC20InvalidReceiver +β”‚ β”œβ”€β”€ when from balance is less than the amount +β”‚ β”‚ └── it should revert with ERC20InsufficientBalance +β”‚ └── when caller has trusted-bridge role and from has sufficient balance +β”‚ β”œβ”€β”€ it should decrement total supply and from balance by the amount +β”‚ └── it should emit {Transfer} to zero and {CrosschainBurn} +└── CheckTokenBridge + β”œβ”€β”€ when caller is zero address + β”‚ └── it should revert with ERC20InvalidBridgeAccount + β”œβ”€β”€ when caller does not have trusted-bridge role + β”‚ └── it should revert with ERC20InvalidBridgeAccount + └── when caller has trusted-bridge role + └── it should not revert + +Permit +β”œβ”€β”€ Nonces +β”‚ β”œβ”€β”€ when owner has not used permit +β”‚ β”‚ └── it should return 0 +β”‚ └── when owner has used permit +β”‚ └── it should return the incremented nonce +β”œβ”€β”€ DOMAIN_SEPARATOR +β”‚ └── when queried +β”‚ └── it should return the EIP-712 domain separator (name, version, chainId, verifyingContract) +└── Permit + β”œβ”€β”€ when spender is zero address + β”‚ └── it should revert with ERC20InvalidSpender + β”œβ”€β”€ when deadline has passed + β”‚ └── it should revert with ERC2612InvalidSignature + β”œβ”€β”€ when signature is invalid (wrong signer or malformed) + β”‚ └── it should revert with ERC2612InvalidSignature + └── when signature is valid and deadline not passed + β”œβ”€β”€ it should set allowance(owner, spender) to value + β”œβ”€β”€ it should increment nonces(owner) + └── it should emit {Approval} \ No newline at end of file diff --git a/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol b/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol index 84b65f22..5ae50a88 100644 --- a/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol +++ b/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol @@ -40,5 +40,6 @@ contract Approve_ERC20ApproveFacet_Fuzz_Unit_Test is Base_Test { assertEq(result, true, "approve failed"); assertEq(address(facet).allowance(users.alice, spender), value); } + } diff --git a/test/unit/token/ERC20/Approve/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Approve/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..17f39833 --- /dev/null +++ b/test/unit/token/ERC20/Approve/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {ERC20ApproveFacet} from "src/token/ERC20/Approve/ERC20ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20ApproveFacet_Unit_Test is Base_Test { + ERC20ApproveFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20ApproveFacet(); + vm.label(address(facet), "ERC20ApproveFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC20ApproveFacet.approve.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol b/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol new file mode 100644 index 00000000..9329fdc9 --- /dev/null +++ b/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol @@ -0,0 +1,30 @@ +// 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 {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol"; +import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; + +abstract contract ERC20BridgeableFacet_Base_Test is Base_Test { + /// @dev Role identifier used by ERC20BridgeableFacet (literal "trusted-bridge" as bytes32). + bytes32 internal constant ERC20_BRIDGE_ROLE = bytes32("trusted-bridge"); + using AccessControlStorageUtils for address; + using ERC20StorageUtils for address; + + ERC20BridgeableFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20BridgeableFacet(); + vm.label(address(facet), "ERC20BridgeableFacet"); + } + + function seedTrustedBridge(address bridge) internal { + address(facet).setHasRole(bridge, ERC20_BRIDGE_ROLE, true); + } +} diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol new file mode 100644 index 00000000..87f8fb4b --- /dev/null +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BridgeableFacet_Base_Test} from "test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol"; +import {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract CheckTokenBridge_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFacet_Base_Test { + function testFuzz_ShouldRevert_WhenCallerIsZeroAddress() external { + vm.expectRevert( + abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, ADDRESS_ZERO) + ); + facet.checkTokenBridge(ADDRESS_ZERO); + } + + function testFuzz_ShouldRevert_WhenCallerDoesNotHaveBridgeRole(address account) external { + vm.assume(account != ADDRESS_ZERO); + vm.expectRevert( + abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, account) + ); + facet.checkTokenBridge(account); + } + + function testFuzz_ShouldNotRevert_WhenCallerHasBridgeRole(address bridge) external { + vm.assume(bridge != ADDRESS_ZERO); + seedTrustedBridge(bridge); + facet.checkTokenBridge(bridge); + } +} diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol new file mode 100644 index 00000000..24d6de4b --- /dev/null +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BridgeableFacet_Base_Test} from "test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract CrosschainBurn_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldRevert_WhenCallerNotTrustedBridge(address from, uint256 value) external { + vm.assume(from != ADDRESS_ZERO); + address(facet).mint(from, value); + address bridge = users.admin; + seedTrustedBridge(bridge); + vm.assume(from != bridge); + vm.stopPrank(); + vm.prank(from); + vm.expectRevert( + abi.encodeWithSelector( + ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, + from, + ERC20_BRIDGE_ROLE + ) + ); + facet.crosschainBurn(from, value); + } + + function testFuzz_ShouldRevert_WhenFromIsZeroAddress(uint256 value) external { + seedTrustedBridge(users.admin); + vm.stopPrank(); + vm.prank(users.admin); + vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReceiver.selector, ADDRESS_ZERO)); + facet.crosschainBurn(ADDRESS_ZERO, value); + } + + function testFuzz_ShouldRevert_WhenInsufficientBalance( + address from, + uint256 balance, + uint256 value + ) external { + vm.assume(from != ADDRESS_ZERO); + vm.assume(balance < MAX_UINT256); + value = bound(value, balance + 1, MAX_UINT256); + address(facet).mint(from, balance); + seedTrustedBridge(users.admin); + vm.stopPrank(); + vm.prank(users.admin); + vm.expectRevert( + abi.encodeWithSelector( + ERC20BridgeableFacet.ERC20InsufficientBalance.selector, + from, + balance, + value + ) + ); + facet.crosschainBurn(from, value); + } + + function testFuzz_CrosschainBurn(address from, uint256 value) external { + vm.assume(from != ADDRESS_ZERO); + value = bound(value, 1, MAX_UINT256 - 1); + address(facet).mint(from, value); + seedTrustedBridge(users.admin); + uint256 beforeTotalSupply = address(facet).totalSupply(); + uint256 beforeBalanceOfFrom = address(facet).balanceOf(from); + vm.stopPrank(); + vm.prank(users.admin); + vm.expectEmit(address(facet)); + emit ERC20BridgeableFacet.Transfer(from, address(0), value); + vm.expectEmit(address(facet)); + emit ERC20BridgeableFacet.CrosschainBurn(from, value, users.admin); + facet.crosschainBurn(from, value); + assertEq(address(facet).totalSupply(), beforeTotalSupply - value, "totalSupply"); + assertEq(address(facet).balanceOf(from), beforeBalanceOfFrom - value, "balanceOf(from)"); + } +} diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol new file mode 100644 index 00000000..08807f3e --- /dev/null +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BridgeableFacet_Base_Test} from "test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract CrosschainMint_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldRevert_WhenCallerNotTrustedBridge(address to, uint256 value) external { + vm.assume(to != ADDRESS_ZERO); + address bridge = users.admin; + seedTrustedBridge(bridge); + vm.assume(to != bridge); + vm.stopPrank(); + vm.prank(to); + vm.expectRevert( + abi.encodeWithSelector( + ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, + to, + ERC20_BRIDGE_ROLE + ) + ); + facet.crosschainMint(to, value); + } + + function testFuzz_ShouldRevert_WhenAccountIsZeroAddress(uint256 value) external { + seedTrustedBridge(users.admin); + vm.stopPrank(); + vm.prank(users.admin); + vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReceiver.selector, ADDRESS_ZERO)); + facet.crosschainMint(ADDRESS_ZERO, value); + } + + function testFuzz_CrosschainMint(address to, uint256 value) external { + vm.assume(to != ADDRESS_ZERO); + value = bound(value, 1, MAX_UINT256 - 1); + seedTrustedBridge(users.admin); + uint256 beforeTotalSupply = address(facet).totalSupply(); + uint256 beforeBalanceOfTo = address(facet).balanceOf(to); + vm.stopPrank(); + vm.prank(users.admin); + vm.expectEmit(address(facet)); + emit ERC20BridgeableFacet.Transfer(address(0), to, value); + vm.expectEmit(address(facet)); + emit ERC20BridgeableFacet.CrosschainMint(to, value, users.admin); + facet.crosschainMint(to, value); + assertEq(address(facet).totalSupply(), beforeTotalSupply + value, "totalSupply"); + assertEq(address(facet).balanceOf(to), beforeBalanceOfTo + value, "balanceOf(to)"); + } +} diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..b33bbb98 --- /dev/null +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BridgeableFacet_Base_Test} from "test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol"; +import {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20BridgeableFacet_Unit_Test is ERC20BridgeableFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20BridgeableFacet.crosschainMint.selector, + ERC20BridgeableFacet.crosschainBurn.selector, + ERC20BridgeableFacet.checkTokenBridge.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Burn/facet/ERC20BurnFacetBase.t.sol b/test/unit/token/ERC20/Burn/facet/ERC20BurnFacetBase.t.sol new file mode 100644 index 00000000..3d09106d --- /dev/null +++ b/test/unit/token/ERC20/Burn/facet/ERC20BurnFacetBase.t.sol @@ -0,0 +1,19 @@ +// 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 {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; + +contract ERC20BurnFacet_Base_Test is Base_Test { + ERC20BurnFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20BurnFacet(); + vm.label(address(facet), "ERC20BurnFacet"); + } +} diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol new file mode 100644 index 00000000..046b96eb --- /dev/null +++ b/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BurnFacet_Base_Test} from "../ERC20BurnFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract Burn_ERC20BurnFacet_Fuzz_Unit_Test is ERC20BurnFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldRevert_CallerInsufficientBalance(address caller, uint256 balance, uint256 value) + external + { + vm.assume(balance < MAX_UINT256); + value = bound(value, balance + 1, MAX_UINT256); + + address(facet).mint(caller, balance); + vm.stopPrank(); + vm.prank(caller); + vm.expectRevert( + abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientBalance.selector, caller, balance, value) + ); + facet.burn(value); + } + + function testFuzz_Burn(address caller, uint256 balance, uint256 value) + external + whenAccountNotZeroAddress + givenWhenAccountBalanceGEBurnAmount + { + vm.assume(caller != ADDRESS_ZERO); + balance = bound(balance, 1, MAX_UINT256); + value = bound(value, 1, balance); + + address(facet).mint(caller, balance); + uint256 beforeTotalSupply = address(facet).totalSupply(); + uint256 beforeBalanceOfCaller = address(facet).balanceOf(caller); + + vm.expectEmit(address(facet)); + emit ERC20BurnFacet.Transfer(caller, address(0), value); + vm.stopPrank(); + vm.prank(caller); + facet.burn(value); + + assertEq(address(facet).totalSupply(), beforeTotalSupply - value, "totalSupply"); + assertEq(address(facet).balanceOf(caller), beforeBalanceOfCaller - value, "balanceOf(caller)"); + } +} diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol new file mode 100644 index 00000000..e016e5ff --- /dev/null +++ b/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BurnFacet_Base_Test} from "../ERC20BurnFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract BurnFrom_ERC20BurnFacet_Fuzz_Unit_Test is ERC20BurnFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldRevert_SpenderAllowanceLtAmount( + address account, + address spender, + uint256 allowance, + uint256 value + ) external { + vm.assume(account != ADDRESS_ZERO); + vm.assume(spender != ADDRESS_ZERO); + vm.assume(account != spender); + allowance = bound(allowance, 0, MAX_UINT256 - 1); + value = bound(value, allowance + 1, MAX_UINT256); + + address(facet).mint(account, value); + address(facet).setAllowance(account, spender, allowance); + vm.stopPrank(); + vm.prank(spender); + vm.expectRevert( + abi.encodeWithSelector( + ERC20BurnFacet.ERC20InsufficientAllowance.selector, + spender, + allowance, + value + ) + ); + facet.burnFrom(account, value); + } + + function testFuzz_ShouldRevert_AccountBalanceLtAmount( + address account, + address spender, + uint256 balance, + uint256 value + ) external { + vm.assume(account != ADDRESS_ZERO); + vm.assume(spender != ADDRESS_ZERO); + vm.assume(account != spender); + vm.assume(balance < MAX_UINT256); + value = bound(value, balance + 1, MAX_UINT256); + + address(facet).mint(account, balance); + address(facet).setAllowance(account, spender, value); + vm.stopPrank(); + vm.prank(spender); + vm.expectRevert( + abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientBalance.selector, account, balance, value) + ); + facet.burnFrom(account, value); + } + + function testFuzz_BurnFrom_FiniteAllowance( + address account, + address spender, + uint256 value, + uint256 allowance, + uint256 balance + ) external { + vm.assume(account != ADDRESS_ZERO); + vm.assume(spender != ADDRESS_ZERO); + vm.assume(account != spender); + value = bound(value, 1, MAX_UINT256 - 1); + allowance = bound(allowance, value, MAX_UINT256 - 1); + balance = bound(balance, value, MAX_UINT256); + + address(facet).mint(account, balance); + address(facet).setAllowance(account, spender, allowance); + vm.stopPrank(); + vm.prank(spender); + + uint256 beforeTotalSupply = address(facet).totalSupply(); + uint256 beforeBalanceOfAccount = address(facet).balanceOf(account); + + vm.expectEmit(address(facet)); + emit ERC20BurnFacet.Transfer(account, address(0), value); + facet.burnFrom(account, value); + + assertEq(address(facet).totalSupply(), beforeTotalSupply - value, "totalSupply"); + assertEq(address(facet).balanceOf(account), beforeBalanceOfAccount - value, "balanceOf(account)"); + assertEq(address(facet).allowance(account, spender), allowance - value, "allowance"); + } + + function testFuzz_BurnFrom_InfiniteApproval( + address account, + address spender, + uint256 value, + uint256 balance + ) external { + vm.assume(account != ADDRESS_ZERO); + vm.assume(spender != ADDRESS_ZERO); + vm.assume(account != spender); + value = bound(value, 1, MAX_UINT256); + balance = bound(balance, value, MAX_UINT256); + + address(facet).mint(account, balance); + address(facet).setAllowance(account, spender, MAX_UINT256); + vm.stopPrank(); + vm.prank(spender); + + uint256 beforeTotalSupply = address(facet).totalSupply(); + uint256 beforeBalanceOfAccount = address(facet).balanceOf(account); + + vm.expectEmit(address(facet)); + emit ERC20BurnFacet.Transfer(account, address(0), value); + facet.burnFrom(account, value); + + assertEq(address(facet).totalSupply(), beforeTotalSupply - value, "totalSupply"); + assertEq(address(facet).balanceOf(account), beforeBalanceOfAccount - value, "balanceOf(account)"); + assertEq(address(facet).allowance(account, spender), MAX_UINT256, "allowance unchanged"); + } +} diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..44d1dcf1 --- /dev/null +++ b/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20BurnFacet_Base_Test} from "test/unit/token/ERC20/Burn/facet/ERC20BurnFacetBase.t.sol"; +import {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20BurnFacet_Unit_Test is ERC20BurnFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20BurnFacet.burn.selector, + ERC20BurnFacet.burnFrom.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol b/test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol new file mode 100644 index 00000000..a4824255 --- /dev/null +++ b/test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol @@ -0,0 +1,22 @@ +// 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 {ERC20DataFacet} from "src/token/ERC20/Data/ERC20DataFacet.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; + +abstract contract ERC20DataFacet_Base_Test is Base_Test { + using ERC20StorageUtils for address; + + ERC20DataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20DataFacet(); + vm.label(address(facet), "ERC20DataFacet"); + } +} diff --git a/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..3f096b02 --- /dev/null +++ b/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20DataFacet_Base_Test} from "test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20DataFacet} from "src/token/ERC20/Data/ERC20DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract Data_ERC20DataFacet_Fuzz_Unit_Test is ERC20DataFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldReturnBalance_BalanceOf_WhenAccountQueried(address account, uint256 value) external { + vm.assume(value != type(uint256).max); + address(facet).setBalance(account, value); + + assertEq(facet.balanceOf(account), value, "balanceOf"); + } + + function testFuzz_ShouldReturnZero_BalanceOf_WhenAccountIsZeroAddress() external view { + assertEq(facet.balanceOf(address(0)), 0, "balanceOf zero address"); + } + + function testFuzz_ShouldReturnTotalSupply_WhenQueried(uint256 supply) external { + vm.assume(supply != type(uint256).max); + address(facet).setTotalSupply(supply); + + assertEq(facet.totalSupply(), supply, "totalSupply"); + } + + function testFuzz_ShouldReturnAllowance_WhenOwnerAndSpenderQueried(address owner, address spender, uint256 amount) + external + { + vm.assume(owner != ADDRESS_ZERO); + vm.assume(spender != ADDRESS_ZERO); + vm.assume(amount != type(uint256).max); + address(facet).setAllowance(owner, spender, amount); + + assertEq(facet.allowance(owner, spender), amount, "allowance"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20DataFacet.totalSupply.selector, ERC20DataFacet.balanceOf.selector, ERC20DataFacet.allowance.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..8b56b878 --- /dev/null +++ b/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20DataFacet_Base_Test} from "test/unit/token/ERC20/Data/ERC20DataFacetBase.t.sol"; +import {ERC20DataFacet} from "src/token/ERC20/Data/ERC20DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20DataFacet_Unit_Test is ERC20DataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20DataFacet.totalSupply.selector, + ERC20DataFacet.balanceOf.selector, + ERC20DataFacet.allowance.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol b/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol new file mode 100644 index 00000000..025e82ac --- /dev/null +++ b/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol @@ -0,0 +1,20 @@ +// 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 {ERC20MetadataFacet} from "src/token/ERC20/Metadata/ERC20MetadataFacet.sol"; +import {ERC20MetadataModHarness} from "test/harnesses/token/ERC20/ERC20MetadataModHarness.sol"; + +abstract contract ERC20MetadataFacet_Base_Test is Base_Test { + ERC20MetadataModHarness internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20MetadataModHarness(); + vm.label(address(facet), "ERC20MetadataModHarness"); + } +} diff --git a/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..a77d850b --- /dev/null +++ b/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20MetadataFacet_Base_Test} from "test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol"; +import {ERC20MetadataFacet} from "src/token/ERC20/Metadata/ERC20MetadataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20MetadataFacet_Unit_Test is ERC20MetadataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20MetadataFacet.name.selector, + ERC20MetadataFacet.symbol.selector, + ERC20MetadataFacet.decimals.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Metadata/facet/fuzz/metadata.t.sol b/test/unit/token/ERC20/Metadata/facet/fuzz/metadata.t.sol new file mode 100644 index 00000000..1d2f2e47 --- /dev/null +++ b/test/unit/token/ERC20/Metadata/facet/fuzz/metadata.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20MetadataFacet_Base_Test} from "test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol"; +import {ERC20MetadataFacet} from "src/token/ERC20/Metadata/ERC20MetadataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract Metadata_ERC20MetadataFacet_Fuzz_Unit_Test is ERC20MetadataFacet_Base_Test { + function test_ShouldReturnName_WhenSetViaMod() external { + facet.setMetadata(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS); + assertEq(facet.name(), TOKEN_NAME, "name"); + } + + function test_ShouldReturnSymbol_WhenSetViaMod() external { + facet.setMetadata(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS); + assertEq(facet.symbol(), TOKEN_SYMBOL, "symbol"); + } + + function test_ShouldReturnDecimals_WhenSetViaMod() external { + facet.setMetadata(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS); + assertEq(facet.decimals(), TOKEN_DECIMALS, "decimals"); + } + + function testFuzz_ShouldReturnName_WhenSetViaMod(string memory name) external { + vm.assume(bytes(name).length <= 100); + facet.setMetadata(name, "SYM", 18); + assertEq(facet.name(), name, "name"); + } + + function testFuzz_ShouldReturnSymbol_WhenSetViaMod(string memory symbol) external { + vm.assume(bytes(symbol).length <= 100); + facet.setMetadata("Name", symbol, 18); + assertEq(facet.symbol(), symbol, "symbol"); + } + + function testFuzz_ShouldReturnDecimals_WhenSetViaMod(uint8 decimals) external { + facet.setMetadata("Name", "SYM", decimals); + assertEq(facet.decimals(), decimals, "decimals"); + } +} diff --git a/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol new file mode 100644 index 00000000..618e8f3c --- /dev/null +++ b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol @@ -0,0 +1,46 @@ +// 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 {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; +import {ERC20PermitFacetHarness} from "test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol"; + +abstract contract ERC20PermitFacet_Base_Test is Base_Test { + ERC20PermitFacetHarness internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC20PermitFacetHarness(); + facet.setMetadata(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS); + vm.label(address(facet), "ERC20PermitFacetHarness"); + } + + /// @dev Computes EIP-712 digest for Permit(owner, spender, value, nonce, deadline) for the current facet. + function _getPermitDigest( + address owner, + address spender, + uint256 value, + uint256 nonce, + uint256 deadline + ) + internal + view + returns (bytes32) + { + bytes32 structHash = keccak256( + abi.encode( + keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), + owner, + spender, + value, + nonce, + deadline + ) + ); + return keccak256(abi.encodePacked("\x19\x01", facet.DOMAIN_SEPARATOR(), structHash)); + } +} diff --git a/test/unit/token/ERC20/Permit/facet/fuzz/domainSeparator.t.sol b/test/unit/token/ERC20/Permit/facet/fuzz/domainSeparator.t.sol new file mode 100644 index 00000000..c4f9e2bd --- /dev/null +++ b/test/unit/token/ERC20/Permit/facet/fuzz/domainSeparator.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20PermitFacet_Base_Test} from "test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract DomainSeparator_ERC20PermitFacet_Unit_Test is ERC20PermitFacet_Base_Test { + function test_ShouldReturnExpected_DOMAIN_SEPARATOR() external view { + bytes32 expected = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256(bytes(TOKEN_NAME)), + keccak256("1"), + block.chainid, + address(facet) + ) + ); + assertEq(facet.DOMAIN_SEPARATOR(), expected, "DOMAIN_SEPARATOR"); + } +} diff --git a/test/unit/token/ERC20/Permit/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Permit/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..0f153dce --- /dev/null +++ b/test/unit/token/ERC20/Permit/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20PermitFacet_Base_Test} from "test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol"; +import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20PermitFacet_Unit_Test is ERC20PermitFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20PermitFacet.nonces.selector, + ERC20PermitFacet.DOMAIN_SEPARATOR.selector, + ERC20PermitFacet.permit.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Permit/facet/fuzz/nonces.t.sol b/test/unit/token/ERC20/Permit/facet/fuzz/nonces.t.sol new file mode 100644 index 00000000..8b35395b --- /dev/null +++ b/test/unit/token/ERC20/Permit/facet/fuzz/nonces.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20PermitFacet_Base_Test} from "test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol"; +import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract Nonces_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { + function testFuzz_ShouldReturnZero_Nonces_WhenOwnerHasNotUsedPermit(address owner) external view { + assertEq(facet.nonces(owner), 0, "nonces"); + } + + function test_ShouldIncrementNonce_AfterPermit() external { + uint256 ownerPrivateKey = 0xA11CE; + address owner = vm.addr(ownerPrivateKey); + address spender = users.bob; + uint256 value = 100e18; + uint256 deadline = block.timestamp + 1 hours; + bytes32 hash = _getPermitDigest(owner, spender, value, 0, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); + + assertEq(facet.nonces(owner), 0, "nonce before"); + facet.permit(owner, spender, value, deadline, v, r, s); + assertEq(facet.nonces(owner), 1, "nonce after"); + } +} diff --git a/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol b/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol new file mode 100644 index 00000000..837e8f62 --- /dev/null +++ b/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20PermitFacet_Base_Test} from "test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol"; +import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; +import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract Permit_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { + using ERC20StorageUtils for address; + + function testFuzz_ShouldRevert_WhenSpenderIsZeroAddress( + address owner, + uint256 value, + uint256 deadline + ) external { + uint256 ownerPrivateKey = 0xA11CE; + owner = vm.addr(ownerPrivateKey); + deadline = bound(deadline, block.timestamp + 1, type(uint256).max); + value = bound(value, 0, MAX_UINT256); + bytes32 hash = _getPermitDigest(owner, ADDRESS_ZERO, value, 0, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); + + vm.expectRevert(abi.encodeWithSelector(ERC20PermitFacet.ERC20InvalidSpender.selector, ADDRESS_ZERO)); + facet.permit(owner, ADDRESS_ZERO, value, deadline, v, r, s); + } + + function testFuzz_ShouldRevert_WhenDeadlineExpired( + address owner, + address spender, + uint256 value + ) external { + vm.assume(spender != ADDRESS_ZERO); + uint256 ownerPrivateKey = 0xB0B; + owner = vm.addr(ownerPrivateKey); + value = bound(value, 0, MAX_UINT256); + uint256 deadline = block.timestamp - 1; + bytes32 hash = _getPermitDigest(owner, spender, value, 0, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); + + vm.expectRevert( + abi.encodeWithSelector( + ERC20PermitFacet.ERC2612InvalidSignature.selector, + owner, + spender, + value, + deadline, + v, + r, + s + ) + ); + facet.permit(owner, spender, value, deadline, v, r, s); + } + + function testFuzz_ShouldRevert_WhenSignatureInvalid( + address spender, + uint256 value, + uint256 deadline + ) external { + vm.assume(spender != ADDRESS_ZERO); + uint256 ownerPrivateKey = 0xA11CE; + address owner = vm.addr(ownerPrivateKey); + uint256 wrongPrivateKey = 0xB0B; + deadline = bound(deadline, block.timestamp + 1, type(uint256).max); + value = bound(value, 0, MAX_UINT256); + bytes32 hash = _getPermitDigest(owner, spender, value, 0, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongPrivateKey, hash); + + vm.expectRevert( + abi.encodeWithSelector( + ERC20PermitFacet.ERC2612InvalidSignature.selector, + owner, + spender, + value, + deadline, + v, + r, + s + ) + ); + facet.permit(owner, spender, value, deadline, v, r, s); + } + + function testFuzz_Permit( + address spender, + uint256 value, + uint256 deadline + ) external { + vm.assume(spender != ADDRESS_ZERO); + uint256 ownerPrivateKey = 0xA11CE; + address owner = vm.addr(ownerPrivateKey); + deadline = bound(deadline, block.timestamp + 1, type(uint256).max); + value = bound(value, 0, MAX_UINT256); + bytes32 hash = _getPermitDigest(owner, spender, value, 0, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); + + vm.expectEmit(address(facet)); + emit ERC20PermitFacet.Approval(owner, spender, value); + facet.permit(owner, spender, value, deadline, v, r, s); + + assertEq(address(facet).allowance(owner, spender), value, "allowance"); + assertEq(facet.nonces(owner), 1, "nonce"); + } + + function test_Permit_IncrementsNonce() external { + uint256 ownerPrivateKey = 0xC0C; + address owner = vm.addr(ownerPrivateKey); + address spender = users.bob; + uint256 deadline = block.timestamp + 1 hours; + + for (uint256 i = 0; i < 3; i++) { + uint256 value = (i + 1) * 100e18; + bytes32 hash = _getPermitDigest(owner, spender, value, i, deadline); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); + facet.permit(owner, spender, value, deadline, v, r, s); + assertEq(facet.nonces(owner), i + 1, "nonce after permit"); + } + } +} diff --git a/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..096f7a45 --- /dev/null +++ b/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC20TransferFacet_Base_Test} from "test/unit/token/ERC20/Transfer/facet/ERC20TransferFacetBase.t.sol"; +import {ERC20TransferFacet} from "src/token/ERC20/Transfer/ERC20TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC20.tree + */ +contract ExportSelectors_ERC20TransferFacet_Unit_Test is ERC20TransferFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC20TransferFacet.transfer.selector, + ERC20TransferFacet.transferFrom.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol b/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol index b253f068..69b22150 100644 --- a/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol +++ b/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol @@ -36,6 +36,33 @@ contract Transfer_ERC20TransferMod_Fuzz_Unit_Test is ERC20TransferMod_Base_Test harness.transfer(to, value); } + function testFuzz_ShouldReturnTrue_AmountIsZero_NoBalanceChange( + address to, + uint256 senderBalance, + uint256 receiverBalance + ) + external + whenReceiverNotZeroAddress + givenWhenSenderBalanceGETransferAmount + { + vm.assume(to != ADDRESS_ZERO); + vm.assume(to != users.alice); + + senderBalance = bound(senderBalance, 0, MAX_UINT256 / 2); + receiverBalance = bound(receiverBalance, 0, MAX_UINT256 / 2); + + address(harness).mint(users.alice, senderBalance); + address(harness).mint(to, receiverBalance); + + vm.expectEmit(address(harness)); + emit Transfer(users.alice, to, 0); + bool result = harness.transfer(to, 0); + + assertEq(result, true, "transfer failed"); + assertEq(address(harness).balanceOf(users.alice), senderBalance, "balanceOf(users.alice)"); + assertEq(address(harness).balanceOf(to), receiverBalance, "balanceOf(to)"); + } + function testFuzz_Transfer(address to, uint256 balance, uint256 value) external whenReceiverNotZeroAddress diff --git a/test/unit/token/ERC20/Transfer/mod/fuzz/transferFrom.t.sol b/test/unit/token/ERC20/Transfer/mod/fuzz/transferFrom.t.sol index c8cb1792..090c502e 100644 --- a/test/unit/token/ERC20/Transfer/mod/fuzz/transferFrom.t.sol +++ b/test/unit/token/ERC20/Transfer/mod/fuzz/transferFrom.t.sol @@ -71,6 +71,45 @@ contract TransferFrom_ERC20TransferMod_Fuzz_Unit_Test is ERC20TransferMod_Base_T harness.transferFrom(from, to, value); } + function testFuzz_ShouldReturnTrue_AmountIsZero_NoBalanceChange_NoAllowanceDecrement( + address from, + address to, + uint256 allowance, + uint256 fromBalance, + uint256 toBalance + ) + external + whenSenderNotZeroAddress + whenReceiverNotZeroAddress + givenWhenSpenderAllowanceGETransferAmount + givenWhenSenderBalanceGETransferAmount + { + vm.assume(from != ADDRESS_ZERO); + vm.assume(to != ADDRESS_ZERO); + vm.assume(to != from); + vm.assume(users.sender != from); + + allowance = bound(allowance, 0, MAX_UINT256); + fromBalance = bound(fromBalance, 0, MAX_UINT256 / 2); + toBalance = bound(toBalance, 0, MAX_UINT256 / 2); + + address(harness).mint(from, fromBalance); + address(harness).mint(to, toBalance); + address(harness).setAllowance(from, users.sender, allowance); + setMsgSender(users.sender); + + uint256 beforeAllowance = address(harness).allowance(from, users.sender); + + vm.expectEmit(address(harness)); + emit Transfer(from, to, 0); + bool result = harness.transferFrom(from, to, 0); + + assertEq(result, true, "transferFrom failed"); + assertEq(address(harness).balanceOf(from), fromBalance, "balanceOf(from)"); + assertEq(address(harness).balanceOf(to), toBalance, "balanceOf(to)"); + assertEq(address(harness).allowance(from, users.sender), beforeAllowance, "allowance should be unchanged"); + } + function testFuzz_TransferFrom_InfiniteApproval(address from, address to, uint256 value, uint256 balance) external whenSenderNotZeroAddress From b48543d1d34f457456929b3de05192b2a87fe6c5 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 23:09:22 -0400 Subject: [PATCH 07/25] separate exportSelectors test file --- .../Admin/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Admin/facet/fuzz/setRoleAdmin.t.sol | 6 --- .../Grant/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Grant/facet/fuzz/grantRoleBatch.t.sol | 6 --- .../Revoke/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Revoke/facet/fuzz/revokeRoleBatch.t.sol | 6 --- .../AccessControl/Data/facet/fuzz/data.t.sol | 10 ----- .../Data/facet/fuzz/exportSelectors.t.sol | 33 ++++++++++++++ .../Grant/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Grant/facet/fuzz/grantRole.t.sol | 6 --- .../Pausable/facet/fuzz/exportSelectors.t.sol | 34 +++++++++++++++ .../Pausable/facet/fuzz/pausable.t.sol | 11 ----- .../Renounce/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Renounce/facet/fuzz/renounceRole.t.sol | 6 --- .../Revoke/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Revoke/facet/fuzz/revokeRole.t.sol | 6 --- .../Temporal/Data/facet/fuzz/data.t.sol | 10 ----- .../Data/facet/fuzz/exportSelectors.t.sol | 33 ++++++++++++++ .../Grant/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../facet/fuzz/grantRoleWithExpiry.t.sol | 6 --- .../Revoke/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../facet/fuzz/revokeTemporalRole.t.sol | 6 --- .../access/Owner/Data/facet/fuzz/data.t.sol | 6 --- .../Data/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Renounce/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../facet/fuzz/renounceOwnership.t.sol | 6 --- .../Transfer/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../facet/fuzz/transferOwnership.t.sol | 6 --- .../Owner/TwoSteps/Data/facet/fuzz/data.t.sol | 6 --- .../Data/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../Renounce/facet/fuzz/exportSelectors.t.sol | 29 +++++++++++++ .../facet/fuzz/renounceOwnership.t.sol | 6 --- .../Transfer/facet/fuzz/exportSelectors.t.sol | 32 ++++++++++++++ .../facet/fuzz/transferOwnership.t.sol | 8 ---- .../Approve/facet/fuzz/exportSelectors.t.sol | 20 +++++++++ .../facet/fuzz/setApprovalForAll.t.sol | 12 ++---- .../token/ERC1155/Burn/facet/fuzz/burn.t.sol | 43 ++++--------------- .../Burn/facet/fuzz/exportSelectors.t.sol | 23 ++++++++++ .../token/ERC1155/Data/facet/fuzz/data.t.sol | 39 ++++++----------- .../Data/facet/fuzz/exportSelectors.t.sol | 24 +++++++++++ .../Metadata/facet/fuzz/exportSelectors.t.sol | 20 +++++++++ .../ERC1155/Metadata/facet/fuzz/uri.t.sol | 5 --- .../Transfer/facet/fuzz/exportSelectors.t.sol | 23 ++++++++++ .../token/ERC20/Data/facet/fuzz/data.t.sol | 8 ---- .../storage/AccessControlStorageUtils.sol | 20 +++++++++ test/utils/storage/OwnerStorageUtils.sol | 14 ++++++ 46 files changed, 679 insertions(+), 198 deletions(-) create mode 100644 test/unit/access/AccessControl/Admin/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Grant/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Grant/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Pausable/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Renounce/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Revoke/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/Renounce/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/Transfer/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC1155/Approve/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC1155/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC1155/Metadata/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol diff --git a/test/unit/access/AccessControl/Admin/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Admin/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..9ad22eca --- /dev/null +++ b/test/unit/access/AccessControl/Admin/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlAdminFacet} from "src/access/AccessControl/Admin/AccessControlAdminFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlAdminFacet_Unit_Test is Base_Test { + AccessControlAdminFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlAdminFacet(); + vm.label(address(facet), "AccessControlAdminFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlAdminFacet.setRoleAdmin.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol index 0f6e6f3e..f45637ac 100644 --- a/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol +++ b/test/unit/access/AccessControl/Admin/facet/fuzz/setRoleAdmin.t.sol @@ -49,10 +49,4 @@ contract SetRoleAdmin_AccessControlAdminFacet_Fuzz_Unit_Test is AccessControlAdm vm.prank(caller); facet.setRoleAdmin(role, newAdminRole); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlAdminFacet.setRoleAdmin.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..c6f7a075 --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlGrantBatchFacet} from "src/access/AccessControl/Batch/Grant/AccessControlGrantBatchFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlGrantBatchFacet_Unit_Test is Base_Test { + AccessControlGrantBatchFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlGrantBatchFacet(); + vm.label(address(facet), "AccessControlGrantBatchFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlGrantBatchFacet.grantRoleBatch.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol index ae26d3e1..f482904c 100644 --- a/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Grant/facet/fuzz/grantRoleBatch.t.sol @@ -93,10 +93,4 @@ contract GrantRoleBatch_AccessControlGrantBatchFacet_Unit_Test is AccessControlG vm.prank(caller); facet.grantRoleBatch(MINTER_ROLE, accounts); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlGrantBatchFacet.grantRoleBatch.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..de382419 --- /dev/null +++ b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlRevokeBatchFacet} from "src/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlRevokeBatchFacet_Unit_Test is Base_Test { + AccessControlRevokeBatchFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlRevokeBatchFacet(); + vm.label(address(facet), "AccessControlRevokeBatchFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRevokeBatchFacet.revokeRoleBatch.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol index becfccee..9cf88222 100644 --- a/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Revoke/facet/fuzz/revokeRoleBatch.t.sol @@ -95,10 +95,4 @@ contract RevokeRoleBatch_AccessControlRevokeBatchFacet_Unit_Test is AccessContro vm.prank(caller); facet.revokeRoleBatch(MINTER_ROLE, accounts); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlRevokeBatchFacet.revokeRoleBatch.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol index 21837027..eb8d7c99 100644 --- a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol @@ -59,14 +59,4 @@ contract Data_AccessControlDataFacet_Fuzz_Unit_Test is AccessControlData_Base_Te assertEq(facet.getRoleAdmin(role), adminRole, "getRoleAdmin"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - AccessControlDataFacet.hasRole.selector, - AccessControlDataFacet.requireRole.selector, - AccessControlDataFacet.getRoleAdmin.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..ca98433b --- /dev/null +++ b/test/unit/access/AccessControl/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlDataFacet} from "src/access/AccessControl/Data/AccessControlDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlDataFacet_Unit_Test is Base_Test { + AccessControlDataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlDataFacet(); + vm.label(address(facet), "AccessControlDataFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlDataFacet.hasRole.selector, + AccessControlDataFacet.requireRole.selector, + AccessControlDataFacet.getRoleAdmin.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Grant/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Grant/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..61d78abb --- /dev/null +++ b/test/unit/access/AccessControl/Grant/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlGrantFacet} from "src/access/AccessControl/Grant/AccessControlGrantFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlGrantFacet_Unit_Test is Base_Test { + AccessControlGrantFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlGrantFacet(); + vm.label(address(facet), "AccessControlGrantFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlGrantFacet.grantRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol index e13bf3e8..813e86a7 100644 --- a/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol +++ b/test/unit/access/AccessControl/Grant/facet/fuzz/grantRole.t.sol @@ -108,10 +108,4 @@ contract GrantRole_AccessControlGrantFacet_Fuzz_Unit_Test is AccessControlGrant_ assertEq(address(facet).hasRole(account, MINTER_ROLE), true, "hasRole"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlGrantFacet.grantRole.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Pausable/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Pausable/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..f9bd3f55 --- /dev/null +++ b/test/unit/access/AccessControl/Pausable/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlPausableFacet} from "src/access/AccessControl/Pausable/AccessControlPausableFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlPausableFacet_Unit_Test is Base_Test { + AccessControlPausableFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlPausableFacet(); + vm.label(address(facet), "AccessControlPausableFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlPausableFacet.isRolePaused.selector, + AccessControlPausableFacet.pauseRole.selector, + AccessControlPausableFacet.unpauseRole.selector, + AccessControlPausableFacet.requireRoleNotPaused.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol index 01d12140..8efe92b2 100644 --- a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol @@ -108,15 +108,4 @@ contract Pausable_AccessControlPausableFacet_Fuzz_Unit_Test is AccessControlPaus vm.expectRevert(abi.encodeWithSelector(AccessControlPausableFacet.AccessControlRolePaused.selector, role)); facet.requireRoleNotPaused(role, account); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - AccessControlPausableFacet.isRolePaused.selector, - AccessControlPausableFacet.pauseRole.selector, - AccessControlPausableFacet.unpauseRole.selector, - AccessControlPausableFacet.requireRoleNotPaused.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Renounce/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Renounce/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..2687df46 --- /dev/null +++ b/test/unit/access/AccessControl/Renounce/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlRenounceFacet} from "src/access/AccessControl/Renounce/AccessControlRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlRenounceFacet_Unit_Test is Base_Test { + AccessControlRenounceFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlRenounceFacet(); + vm.label(address(facet), "AccessControlRenounceFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRenounceFacet.renounceRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol b/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol index e8b80dab..7c8ffef2 100644 --- a/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol +++ b/test/unit/access/AccessControl/Renounce/facet/fuzz/renounceRole.t.sol @@ -59,10 +59,4 @@ contract RenounceRole_AccessControlRenounceFacet_Fuzz_Unit_Test is AccessControl vm.prank(sender); facet.renounceRole(role, account); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlRenounceFacet.renounceRole.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Revoke/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Revoke/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..cc574216 --- /dev/null +++ b/test/unit/access/AccessControl/Revoke/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlRevokeFacet} from "src/access/AccessControl/Revoke/AccessControlRevokeFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlRevokeFacet_Unit_Test is Base_Test { + AccessControlRevokeFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlRevokeFacet(); + vm.label(address(facet), "AccessControlRevokeFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlRevokeFacet.revokeRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol index 61d40511..835d2f52 100644 --- a/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol +++ b/test/unit/access/AccessControl/Revoke/facet/fuzz/revokeRole.t.sol @@ -74,10 +74,4 @@ contract RevokeRole_AccessControlRevokeFacet_Fuzz_Unit_Test is AccessControlRevo assertEq(address(facet).hasRole(account, MINTER_ROLE), false, "hasRole"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlRevokeFacet.revokeRole.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol index f1a75576..c922a73a 100644 --- a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol @@ -127,14 +127,4 @@ contract Data_AccessControlTemporalDataFacet_Fuzz_Unit_Test is AccessControlTemp ); facet.requireValidRole(role, account); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - AccessControlTemporalDataFacet.getRoleExpiry.selector, - AccessControlTemporalDataFacet.isRoleExpired.selector, - AccessControlTemporalDataFacet.requireValidRole.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..fd44d16b --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlTemporalDataFacet} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlTemporalDataFacet_Unit_Test is Base_Test { + AccessControlTemporalDataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlTemporalDataFacet(); + vm.label(address(facet), "AccessControlTemporalDataFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + AccessControlTemporalDataFacet.getRoleExpiry.selector, + AccessControlTemporalDataFacet.isRoleExpired.selector, + AccessControlTemporalDataFacet.requireValidRole.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..37db08a6 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlTemporalGrantFacet} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlTemporalGrantFacet_Unit_Test is Base_Test { + AccessControlTemporalGrantFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlTemporalGrantFacet(); + vm.label(address(facet), "AccessControlTemporalGrantFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlTemporalGrantFacet.grantRoleWithExpiry.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol index f1dd1c15..3d9c5d24 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/grantRoleWithExpiry.t.sol @@ -82,10 +82,4 @@ contract GrantRoleWithExpiry_AccessControlTemporalGrantFacet_Fuzz_Unit_Test is A vm.prank(caller); facet.grantRoleWithExpiry(role, account, block.timestamp + expiryOffset); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlTemporalGrantFacet.grantRoleWithExpiry.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..868b9b88 --- /dev/null +++ b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {AccessControlTemporalRevokeFacet} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; + +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlTemporalRevokeFacet_Unit_Test is Base_Test { + AccessControlTemporalRevokeFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new AccessControlTemporalRevokeFacet(); + vm.label(address(facet), "AccessControlTemporalRevokeFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlTemporalRevokeFacet.revokeTemporalRole.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol index c8987d7e..46f043dc 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/revokeTemporalRole.t.sol @@ -75,10 +75,4 @@ contract RevokeTemporalRole_AccessControlTemporalRevokeFacet_Fuzz_Unit_Test is A vm.prank(caller); facet.revokeTemporalRole(role, account); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(AccessControlTemporalRevokeFacet.revokeTemporalRole.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/Data/facet/fuzz/data.t.sol b/test/unit/access/Owner/Data/facet/fuzz/data.t.sol index f17f7b93..bd48ca10 100644 --- a/test/unit/access/Owner/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/Owner/Data/facet/fuzz/data.t.sol @@ -32,10 +32,4 @@ contract Data_OwnerDataFacet_Fuzz_Unit_Test is OwnerData_Base_Test { assertEq(facet.owner(), address(0), "owner"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(OwnerDataFacet.owner.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..efe31105 --- /dev/null +++ b/test/unit/access/Owner/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerDataFacet} from "src/access/Owner/Data/OwnerDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerDataFacet_Unit_Test is Base_Test { + OwnerDataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerDataFacet(); + vm.label(address(facet), "OwnerDataFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerDataFacet.owner.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/Renounce/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/Renounce/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..ce3bb4d6 --- /dev/null +++ b/test/unit/access/Owner/Renounce/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerRenounceFacet} from "src/access/Owner/Renounce/OwnerRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerRenounceFacet_Unit_Test is Base_Test { + OwnerRenounceFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerRenounceFacet(); + vm.label(address(facet), "OwnerRenounceFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerRenounceFacet.renounceOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol index 806e0e65..b679cfb1 100644 --- a/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol +++ b/test/unit/access/Owner/Renounce/facet/fuzz/renounceOwnership.t.sol @@ -46,10 +46,4 @@ contract RenounceOwnership_OwnerRenounceFacet_Fuzz_Unit_Test is OwnerRenounce_Ba vm.expectRevert(OwnerRenounceFacet.OwnerUnauthorizedAccount.selector); facet.renounceOwnership(); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(OwnerRenounceFacet.renounceOwnership.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..2c8198e1 --- /dev/null +++ b/test/unit/access/Owner/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerTransferFacet} from "src/access/Owner/Transfer/OwnerTransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerTransferFacet_Unit_Test is Base_Test { + OwnerTransferFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerTransferFacet(); + vm.label(address(facet), "OwnerTransferFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTransferFacet.transferOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol index 7c086b37..faadda4b 100644 --- a/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/Transfer/facet/fuzz/transferOwnership.t.sol @@ -63,10 +63,4 @@ contract TransferOwnership_OwnerTransferFacet_Fuzz_Unit_Test is OwnerTransfer_Ba vm.expectRevert(OwnerTransferFacet.OwnerUnauthorizedAccount.selector); facet.transferOwnership(newOwner); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(OwnerTransferFacet.transferOwnership.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol index 636040f3..67f52665 100644 --- a/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/data.t.sol @@ -32,10 +32,4 @@ contract Data_OwnerTwoStepDataFacet_Fuzz_Unit_Test is OwnerTwoStepData_Base_Test function testFuzz_ShouldReturnZero_PendingOwner_WhenNoPendingOwnerHasBeenSet() external view { assertEq(facet.pendingOwner(), address(0), "pendingOwner"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(OwnerTwoStepDataFacet.pendingOwner.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..ccd7e1e6 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerTwoStepDataFacet} from "src/access/Owner/TwoSteps/Data/OwnerTwoStepDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerTwoStepDataFacet_Unit_Test is Base_Test { + OwnerTwoStepDataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerTwoStepDataFacet(); + vm.label(address(facet), "OwnerTwoStepDataFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTwoStepDataFacet.pendingOwner.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..fa79c065 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerTwoStepRenounceFacet} from "src/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerTwoStepRenounceFacet_Unit_Test is Base_Test { + OwnerTwoStepRenounceFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerTwoStepRenounceFacet(); + vm.label(address(facet), "OwnerTwoStepRenounceFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(OwnerTwoStepRenounceFacet.renounceOwnership.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol index c4a96bf8..7f5dd4fa 100644 --- a/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Renounce/facet/fuzz/renounceOwnership.t.sol @@ -49,10 +49,4 @@ contract RenounceOwnership_OwnerTwoStepRenounceFacet_Fuzz_Unit_Test is OwnerTwoS vm.expectRevert(OwnerTwoStepRenounceFacet.OwnerUnauthorizedAccount.selector); facet.renounceOwnership(); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked(OwnerTwoStepRenounceFacet.renounceOwnership.selector); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..84b94091 --- /dev/null +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Base_Test} from "test/Base.t.sol"; +import {OwnerTwoStepTransferFacet} from "src/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/Owner.tree + */ +contract ExportSelectors_OwnerTwoStepTransferFacet_Unit_Test is Base_Test { + OwnerTwoStepTransferFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new OwnerTwoStepTransferFacet(); + vm.label(address(facet), "OwnerTwoStepTransferFacet"); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + OwnerTwoStepTransferFacet.transferOwnership.selector, + OwnerTwoStepTransferFacet.acceptOwnership.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol index 25cb6d86..82470d44 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol @@ -91,12 +91,4 @@ contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoS vm.expectRevert(OwnerTwoStepTransferFacet.OwnerUnauthorizedAccount.selector); facet.acceptOwnership(); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - OwnerTwoStepTransferFacet.transferOwnership.selector, OwnerTwoStepTransferFacet.acceptOwnership.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/token/ERC1155/Approve/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Approve/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..592107bd --- /dev/null +++ b/test/unit/token/ERC1155/Approve/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155ApproveFacet_Base_Test} from "test/unit/token/ERC1155/Approve/ERC1155ApproveFacetBase.t.sol"; +import {ERC1155ApproveFacet} from "src/token/ERC1155/Approve/ERC1155ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract ExportSelectors_ERC1155ApproveFacet_Unit_Test is ERC1155ApproveFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC1155ApproveFacet.setApprovalForAll.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol b/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol index 298fdde3..7ba49703 100644 --- a/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol +++ b/test/unit/token/ERC1155/Approve/facet/fuzz/setApprovalForAll.t.sol @@ -22,10 +22,9 @@ contract SetApprovalForAll_ERC1155ApproveFacet_Fuzz_Test is ERC1155ApproveFacet_ facet.setApprovalForAll(address(0), approved); } - function testFuzz_ShouldSetApprovalAndEmit_SetApprovalForAll_WhenOperatorNotZero( - address operator, - bool approved - ) external { + function testFuzz_ShouldSetApprovalAndEmit_SetApprovalForAll_WhenOperatorNotZero(address operator, bool approved) + external + { vm.assume(operator != address(0)); vm.stopPrank(); vm.prank(users.alice); @@ -34,9 +33,4 @@ contract SetApprovalForAll_ERC1155ApproveFacet_Fuzz_Test is ERC1155ApproveFacet_ facet.setApprovalForAll(operator, approved); assertEq(address(facet).isApprovedForAll(users.alice, operator), approved, "isApprovedForAll"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - assertEq(selectors, abi.encodePacked(ERC1155ApproveFacet.setApprovalForAll.selector), "exportSelectors"); - } } diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol index a1235ceb..f25b0d68 100644 --- a/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/burn.t.sol @@ -22,20 +22,14 @@ contract Burn_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { facet.burn(address(0), id, value); } - function testFuzz_ShouldRevert_Burn_WhenNotApprovedAndNotOwner( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_Burn_WhenNotApprovedAndNotOwner(address from, uint256 id, uint256 value) external { vm.assume(from != address(0)); vm.assume(from != users.bob); vm.assume(value != type(uint256).max); address(facet).setBalanceOf(id, from, value); vm.stopPrank(); vm.prank(users.bob); - vm.expectRevert( - abi.encodeWithSelector(ERC1155BurnFacet.ERC1155MissingApprovalForAll.selector, users.bob, from) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155BurnFacet.ERC1155MissingApprovalForAll.selector, users.bob, from)); facet.burn(from, id, value); } @@ -52,22 +46,14 @@ contract Burn_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { vm.stopPrank(); vm.prank(from); vm.expectRevert( - abi.encodeWithSelector( - ERC1155BurnFacet.ERC1155InsufficientBalance.selector, - from, - balance, - value, - id - ) + abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InsufficientBalance.selector, from, balance, value, id) ); facet.burn(from, id, value); } - function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold(address from, uint256 id, uint256 value) + external + { vm.assume(from != address(0)); vm.assume(value != type(uint256).max); address(facet).setBalanceOf(id, from, value); @@ -77,11 +63,9 @@ contract Burn_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { assertEq(address(facet).balanceOf(id, from), 0, "balance"); } - function testFuzz_ShouldDecrementBalance_Burn_WhenApprovedOperator( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldDecrementBalance_Burn_WhenApprovedOperator(address from, uint256 id, uint256 value) + external + { vm.assume(from != address(0)); vm.assume(value != type(uint256).max); address(facet).setBalanceOf(id, from, value); @@ -91,13 +75,4 @@ contract Burn_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { facet.burn(from, id, value); assertEq(address(facet).balanceOf(id, from), 0, "balance"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC1155BurnFacet.burn.selector, - ERC1155BurnFacet.burnBatch.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..1aa7d4d4 --- /dev/null +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155BurnFacet_Base_Test} from "test/unit/token/ERC1155/Burn/ERC1155BurnFacetBase.t.sol"; +import {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract ExportSelectors_ERC1155BurnFacet_Unit_Test is ERC1155BurnFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC1155BurnFacet.burn.selector, + ERC1155BurnFacet.burnBatch.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol index 35a71b8c..f4236062 100644 --- a/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol +++ b/test/unit/token/ERC1155/Data/facet/fuzz/data.t.sol @@ -15,11 +15,9 @@ import {ERC1155DataFacet} from "src/token/ERC1155/Data/ERC1155DataFacet.sol"; contract Data_ERC1155DataFacet_Fuzz_Test is ERC1155DataFacet_Base_Test { using ERC1155StorageUtils for address; - function testFuzz_ShouldReturnBalance_BalanceOf_WhenAccountAndIdQueried( - address account, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldReturnBalance_BalanceOf_WhenAccountAndIdQueried(address account, uint256 id, uint256 value) + external + { vm.assume(value != type(uint256).max); address(facet).setBalanceOf(id, account, value); @@ -40,8 +38,12 @@ contract Data_ERC1155DataFacet_Fuzz_Test is ERC1155DataFacet_Base_Test { address[] memory accounts = new address[](accountsLen); uint256[] memory ids = new uint256[](idsLen); - for (uint256 i = 0; i < accountsLen; i++) accounts[i] = makeAddr(string.concat("a", vm.toString(i))); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; + for (uint256 i = 0; i < accountsLen; i++) { + accounts[i] = makeAddr(string.concat("a", vm.toString(i))); + } + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } vm.expectRevert( abi.encodeWithSelector(ERC1155DataFacet.ERC1155InvalidArrayLength.selector, idsLen, accountsLen) @@ -81,28 +83,15 @@ contract Data_ERC1155DataFacet_Fuzz_Test is ERC1155DataFacet_Base_Test { assertEq(balances.length, 0, "empty"); } - function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotApproved( - address account, - address operator - ) external view { + function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotApproved(address account, address operator) + external + view + { assertEq(facet.isApprovedForAll(account, operator), false, "isApprovedForAll"); } - function testFuzz_ShouldReturnTrue_IsApprovedForAll_WhenApproved( - address account, - address operator - ) external { + function testFuzz_ShouldReturnTrue_IsApprovedForAll_WhenApproved(address account, address operator) external { address(facet).setApprovedForAll(account, operator, true); assertEq(facet.isApprovedForAll(account, operator), true, "isApprovedForAll"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC1155DataFacet.balanceOf.selector, - ERC1155DataFacet.balanceOfBatch.selector, - ERC1155DataFacet.isApprovedForAll.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/unit/token/ERC1155/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..9f0561da --- /dev/null +++ b/test/unit/token/ERC1155/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155DataFacet_Base_Test} from "test/unit/token/ERC1155/Data/ERC1155DataFacetBase.t.sol"; +import {ERC1155DataFacet} from "src/token/ERC1155/Data/ERC1155DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract ExportSelectors_ERC1155DataFacet_Unit_Test is ERC1155DataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC1155DataFacet.balanceOf.selector, + ERC1155DataFacet.balanceOfBatch.selector, + ERC1155DataFacet.isApprovedForAll.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Metadata/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..651a5519 --- /dev/null +++ b/test/unit/token/ERC1155/Metadata/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155MetadataFacet_Base_Test} from "test/unit/token/ERC1155/Metadata/ERC1155MetadataFacetBase.t.sol"; +import {ERC1155MetadataFacet} from "src/token/ERC1155/Metadata/ERC1155MetadataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract ExportSelectors_ERC1155MetadataFacet_Unit_Test is ERC1155MetadataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC1155MetadataFacet.uri.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol b/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol index 695d7fb3..0f0396ff 100644 --- a/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol +++ b/test/unit/token/ERC1155/Metadata/facet/fuzz/uri.t.sol @@ -21,9 +21,4 @@ contract Uri_ERC1155MetadataFacet_Fuzz_Test is ERC1155MetadataFacet_Base_Test { function testFuzz_ShouldReturnUri_Uri_WhenUninitialized(uint256 id) external view { assertEq(facet.uri(id), "", "uri uninitialized"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - assertEq(selectors, abi.encodePacked(ERC1155MetadataFacet.uri.selector), "exportSelectors"); - } } diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..60cd0d82 --- /dev/null +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC1155TransferFacet_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol"; +import {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC1155.tree + */ +contract ExportSelectors_ERC1155TransferFacet_Unit_Test is ERC1155TransferFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC1155TransferFacet.safeTransferFrom.selector, + ERC1155TransferFacet.safeBatchTransferFrom.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} diff --git a/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol index 3f096b02..f886290c 100644 --- a/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol +++ b/test/unit/token/ERC20/Data/facet/fuzz/data.t.sol @@ -43,12 +43,4 @@ contract Data_ERC20DataFacet_Fuzz_Unit_Test is ERC20DataFacet_Base_Test { assertEq(facet.allowance(owner, spender), amount, "allowance"); } - - function test_ShouldReturnSelectors_ExportSelectors() external view { - bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC20DataFacet.totalSupply.selector, ERC20DataFacet.balanceOf.selector, ERC20DataFacet.allowance.selector - ); - assertEq(selectors, expected, "exportSelectors"); - } } diff --git a/test/utils/storage/AccessControlStorageUtils.sol b/test/utils/storage/AccessControlStorageUtils.sol index 7b902841..585ceac2 100644 --- a/test/utils/storage/AccessControlStorageUtils.sol +++ b/test/utils/storage/AccessControlStorageUtils.sol @@ -19,6 +19,26 @@ library AccessControlStorageUtils { bytes32 internal constant PAUSABLE_STORAGE_POSITION = keccak256("compose.accesscontrol.pausable"); bytes32 internal constant TEMPORAL_STORAGE_POSITION = keccak256("compose.accesscontrol.temporal"); + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + + /* + * @notice AccessControl storage layout (ERC-8042 standard) + * @custom:storage-location erc8042:compose.accesscontrol + * + * Slot 0: mapping(address account => mapping(bytes32 role => bool)) hasRole + * Slot 1: mapping(bytes32 role => bytes32) adminRole + * + * @custom:storage-location erc8042:compose.accesscontrol.pausable + * + * Slot 0: mapping(bytes32 role => bool) isRolePaused + * + * @custom:storage-location erc8042:compose.accesscontrol.temporal + * + * Slot 0: mapping(address account => mapping(bytes32 role => uint256)) roleExpiry + */ + function hasRole(address target, address account, bytes32 role) internal view returns (bool) { bytes32 accountSlot = keccak256(abi.encode(account, uint256(ACCESS_CONTROL_STORAGE_POSITION))); bytes32 slot = keccak256(abi.encode(role, accountSlot)); diff --git a/test/utils/storage/OwnerStorageUtils.sol b/test/utils/storage/OwnerStorageUtils.sol index ef96d086..cb7244e1 100644 --- a/test/utils/storage/OwnerStorageUtils.sol +++ b/test/utils/storage/OwnerStorageUtils.sol @@ -19,6 +19,20 @@ library OwnerStorageUtils { bytes32 internal constant OWNER_STORAGE_POSITION = keccak256("erc173.owner"); bytes32 internal constant PENDING_OWNER_STORAGE_POSITION = keccak256("erc173.owner.pending"); + /*////////////////////////////////////////////////////////////// + GETTERS + //////////////////////////////////////////////////////////////*/ + /* + * @notice ERC-173 Owner storage layout (ERC-8042 standard) + * @custom:storage-location erc8042:erc173.owner + * + * Slot 0: address owner + * + * @custom:storage-location erc8042:erc173.owner.pending + * + * Slot 0: address pendingOwner + */ + function owner(address target) internal view returns (address) { return address(uint160(uint256(vm.load(target, OWNER_STORAGE_POSITION)))); } From 352fc53e938415809747f156c3b712bb716b3c58 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 23:16:36 -0400 Subject: [PATCH 08/25] format --- test/mocks/ERC1155ReceiverMock.sol | 21 +++------ .../Data/facet/fuzz/exportSelectors.t.sol | 4 +- .../Grant/facet/fuzz/exportSelectors.t.sol | 4 +- .../Revoke/facet/fuzz/exportSelectors.t.sol | 4 +- .../Transfer/facet/fuzz/exportSelectors.t.sol | 3 +- .../Approve/mod/fuzz/setApprovalForAll.t.sol | 5 +- .../ERC1155/Burn/facet/fuzz/burnBatch.t.sol | 25 ++++------ .../Burn/facet/fuzz/exportSelectors.t.sol | 5 +- .../token/ERC1155/Burn/mod/fuzz/burn.t.sol | 12 ++--- .../ERC1155/Burn/mod/fuzz/burnBatch.t.sol | 17 ++++--- .../token/ERC1155/Mint/mod/fuzz/mint.t.sol | 21 ++------- .../ERC1155/Mint/mod/fuzz/mintBatch.t.sol | 22 ++++----- .../Transfer/facet/fuzz/exportSelectors.t.sol | 3 +- .../facet/fuzz/safeBatchTransferFrom.t.sol | 47 +++++++------------ .../facet/fuzz/safeTransferFrom.t.sol | 47 +++++-------------- .../mod/fuzz/safeBatchTransferFrom.t.sol | 12 +++-- .../Transfer/mod/fuzz/safeTransferFrom.t.sol | 12 ++--- .../ERC20/Approve/facet/fuzz/approve.t.sol | 1 - .../Bridgeable/ERC20BridgeableFacetBase.t.sol | 4 +- .../facet/fuzz/checkTokenBridge.t.sol | 8 +--- .../facet/fuzz/crosschainBurn.t.sol | 17 ++----- .../facet/fuzz/crosschainMint.t.sol | 4 +- .../token/ERC20/Burn/facet/fuzz/burn.t.sol | 4 +- .../ERC20/Burn/facet/fuzz/burnFrom.t.sol | 16 ++----- .../Burn/facet/fuzz/exportSelectors.t.sol | 5 +- .../Data/facet/fuzz/exportSelectors.t.sol | 4 +- .../Metadata/facet/fuzz/exportSelectors.t.sol | 4 +- .../ERC20/Permit/ERC20PermitFacetBase.t.sol | 12 ++--- .../ERC20/Permit/facet/fuzz/permit.t.sol | 42 +++-------------- .../Transfer/facet/fuzz/exportSelectors.t.sol | 6 +-- .../ERC20/Transfer/mod/fuzz/transfer.t.sol | 6 +-- 31 files changed, 125 insertions(+), 272 deletions(-) diff --git a/test/mocks/ERC1155ReceiverMock.sol b/test/mocks/ERC1155ReceiverMock.sol index f0467ac7..c976456d 100644 --- a/test/mocks/ERC1155ReceiverMock.sol +++ b/test/mocks/ERC1155ReceiverMock.sol @@ -25,14 +25,7 @@ contract ERC1155ReceiverMock is IERC1155Receiver { error CustomError(bytes4); event Received(address operator, address from, uint256 id, uint256 value, bytes data, uint256 gas); - event BatchReceived( - address operator, - address from, - uint256[] ids, - uint256[] values, - bytes data, - uint256 gas - ); + event BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data, uint256 gas); bytes4 private immutable _singleRetval; bytes4 private immutable _batchRetval; @@ -44,13 +37,11 @@ contract ERC1155ReceiverMock is IERC1155Receiver { _revertType = revertType; } - function onERC1155Received( - address operator, - address from, - uint256 id, - uint256 value, - bytes calldata data - ) external override returns (bytes4) { + function onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes calldata data) + external + override + returns (bytes4) + { if (_revertType == RevertType.RevertWithoutMessage) { revert(); } diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol index fd44d16b..f7ae4765 100644 --- a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/exportSelectors.t.sol @@ -6,7 +6,9 @@ pragma solidity >=0.8.30; */ import {Base_Test} from "test/Base.t.sol"; -import {AccessControlTemporalDataFacet} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; +import { + AccessControlTemporalDataFacet +} from "src/access/AccessControl/Temporal/Data/AccessControlTemporalDataFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol index 37db08a6..888adc7c 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/facet/fuzz/exportSelectors.t.sol @@ -6,7 +6,9 @@ pragma solidity >=0.8.30; */ import {Base_Test} from "test/Base.t.sol"; -import {AccessControlTemporalGrantFacet} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; +import { + AccessControlTemporalGrantFacet +} from "src/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol index 868b9b88..7c761116 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/facet/fuzz/exportSelectors.t.sol @@ -6,7 +6,9 @@ pragma solidity >=0.8.30; */ import {Base_Test} from "test/Base.t.sol"; -import {AccessControlTemporalRevokeFacet} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; +import { + AccessControlTemporalRevokeFacet +} from "src/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeFacet.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol index 84b94091..d7649c8d 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/exportSelectors.t.sol @@ -23,8 +23,7 @@ contract ExportSelectors_OwnerTwoStepTransferFacet_Unit_Test is Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); bytes memory expected = abi.encodePacked( - OwnerTwoStepTransferFacet.transferOwnership.selector, - OwnerTwoStepTransferFacet.acceptOwnership.selector + OwnerTwoStepTransferFacet.transferOwnership.selector, OwnerTwoStepTransferFacet.acceptOwnership.selector ); assertEq(selectors, expected, "exportSelectors"); } diff --git a/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol b/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol index 41025f95..402d6f51 100644 --- a/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol +++ b/test/unit/token/ERC1155/Approve/mod/fuzz/setApprovalForAll.t.sol @@ -15,10 +15,7 @@ import "src/token/ERC1155/Approve/ERC1155ApproveMod.sol"; contract SetApprovalForAll_ERC1155ApproveMod_Fuzz_Test is ERC1155ApproveMod_Base_Test { using ERC1155StorageUtils for address; - function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress( - address user, - bool approved - ) external { + function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress(address user, bool approved) external { vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidOperator.selector, address(0))); harness.setApprovalForAll(user, address(0), approved); } diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol index 8cee821b..0c8669ca 100644 --- a/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/burnBatch.t.sol @@ -15,10 +15,7 @@ import {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; contract BurnBatch_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { using ERC1155StorageUtils for address; - function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress( - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress(uint256 id, uint256 value) external { vm.stopPrank(); vm.prank(users.alice); uint256[] memory ids = new uint256[](1); @@ -40,13 +37,15 @@ contract BurnBatch_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; uint256[] memory ids = new uint256[](idsLen); uint256[] memory values = new uint256[](valuesLen); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; - for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } + for (uint256 i = 0; i < valuesLen; i++) { + values[i] = 1; + } vm.stopPrank(); vm.prank(from); - vm.expectRevert( - abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InvalidArrayLength.selector, idsLen, valuesLen) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InvalidArrayLength.selector, idsLen, valuesLen)); facet.burnBatch(from, ids, values); } @@ -76,13 +75,7 @@ contract BurnBatch_ERC1155BurnFacet_Fuzz_Test is ERC1155BurnFacet_Base_Test { vm.stopPrank(); vm.prank(users.alice); vm.expectRevert( - abi.encodeWithSelector( - ERC1155BurnFacet.ERC1155InsufficientBalance.selector, - users.alice, - 0, - 1, - 2 - ) + abi.encodeWithSelector(ERC1155BurnFacet.ERC1155InsufficientBalance.selector, users.alice, 0, 1, 2) ); facet.burnBatch(users.alice, ids, values); } diff --git a/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol index 1aa7d4d4..a3d2a47e 100644 --- a/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC1155/Burn/facet/fuzz/exportSelectors.t.sol @@ -14,10 +14,7 @@ import {ERC1155BurnFacet} from "src/token/ERC1155/Burn/ERC1155BurnFacet.sol"; contract ExportSelectors_ERC1155BurnFacet_Unit_Test is ERC1155BurnFacet_Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC1155BurnFacet.burn.selector, - ERC1155BurnFacet.burnBatch.selector - ); + bytes memory expected = abi.encodePacked(ERC1155BurnFacet.burn.selector, ERC1155BurnFacet.burnBatch.selector); assertEq(selectors, expected, "exportSelectors"); } } diff --git a/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol index c41b9c51..bb5c9341 100644 --- a/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol +++ b/test/unit/token/ERC1155/Burn/mod/fuzz/burn.t.sol @@ -30,17 +30,13 @@ contract Burn_ERC1155BurnMod_Fuzz_Test is ERC1155BurnMod_Base_Test { vm.assume(balance < type(uint256).max); value = bound(value, balance + 1, type(uint256).max); address(harness).setBalanceOf(id, from, balance); - vm.expectRevert( - abi.encodeWithSelector(ERC1155InsufficientBalance.selector, from, balance, value, id) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155InsufficientBalance.selector, from, balance, value, id)); harness.burn(from, id, value); } - function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldDecrementBalance_Burn_WhenPreconditionsHold(address from, uint256 id, uint256 value) + external + { vm.assume(from != address(0)); vm.assume(value != type(uint256).max); address(harness).setBalanceOf(id, from, value); diff --git a/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol b/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol index e706c462..d5910a00 100644 --- a/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol +++ b/test/unit/token/ERC1155/Burn/mod/fuzz/burnBatch.t.sol @@ -15,10 +15,7 @@ import "src/token/ERC1155/Burn/ERC1155BurnMod.sol"; contract BurnBatch_ERC1155BurnMod_Fuzz_Test is ERC1155BurnMod_Base_Test { using ERC1155StorageUtils for address; - function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress( - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_BurnBatch_WhenFromIsZeroAddress(uint256 id, uint256 value) external { uint256[] memory ids = new uint256[](1); uint256[] memory values = new uint256[](1); ids[0] = id; @@ -38,11 +35,13 @@ contract BurnBatch_ERC1155BurnMod_Fuzz_Test is ERC1155BurnMod_Base_Test { if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; uint256[] memory ids = new uint256[](idsLen); uint256[] memory values = new uint256[](valuesLen); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; - for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; - vm.expectRevert( - abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) - ); + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } + for (uint256 i = 0; i < valuesLen; i++) { + values[i] = 1; + } + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen)); harness.burnBatch(from, ids, values); } diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol index 849a387c..e282cfb7 100644 --- a/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol @@ -21,11 +21,7 @@ contract Mint_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { harness.mint(address(0), id, value, ""); } - function testFuzz_ShouldIncrementBalanceAndEmit_Mint_WhenToNotZero( - address to, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldIncrementBalanceAndEmit_Mint_WhenToNotZero(address to, uint256 id, uint256 value) external { vm.assume(to != address(0)); vm.assume(to.code.length == 0); vm.assume(value != type(uint256).max); @@ -38,9 +34,7 @@ contract Mint_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { function testFuzz_ShouldIncrementBalance_Mint_WhenToIsReceiverContract(uint256 id, uint256 value) external { vm.assume(value != type(uint256).max); ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); harness.mint(address(receiver), id, value, ""); @@ -50,11 +44,8 @@ contract Mint_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { function testFuzz_ShouldRevert_Mint_WhenReceiverContractReturnsWrongValue(uint256 id, uint256 value) external { vm.assume(value != type(uint256).max); - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - bytes4(0), - bytes4(0), - ERC1155ReceiverMock.RevertType.None - ); + ERC1155ReceiverMock receiver = + new ERC1155ReceiverMock(bytes4(0), bytes4(0), ERC1155ReceiverMock.RevertType.None); vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidReceiver.selector, address(receiver))); harness.mint(address(receiver), id, value, ""); @@ -63,9 +54,7 @@ contract Mint_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { function test_ShouldRevert_Mint_WhenReceiverContractRevertsWithMessage(uint256 id, uint256 value) external { vm.assume(value != type(uint256).max); ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.RevertWithMessage + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.RevertWithMessage ); vm.expectRevert("ERC1155ReceiverMock: reverting on receive"); harness.mint(address(receiver), id, value, ""); diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol index 9238f588..82477a70 100644 --- a/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol @@ -41,12 +41,14 @@ contract MintBatch_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { uint256[] memory ids = new uint256[](idsLen); uint256[] memory values = new uint256[](valuesLen); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; - for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } + for (uint256 i = 0; i < valuesLen; i++) { + values[i] = 1; + } - vm.expectRevert( - abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen)); harness.mintBatch(to, ids, values, ""); } @@ -92,9 +94,7 @@ contract MintBatch_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { vm.assume(id0 != id1); vm.assume(v0 != type(uint256).max && v1 != type(uint256).max); ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); uint256[] memory ids = new uint256[](2); @@ -111,11 +111,9 @@ contract MintBatch_ERC1155MintMod_Fuzz_Test is ERC1155MintMod_Base_Test { } function test_ShouldRevert_MintBatch_WhenReceiverContractReturnsWrongValue() external { - // Receiver returns single magic for batch (wrong return for onERC1155BatchReceived) + /* Receiver returns single magic for batch (wrong return for onERC1155BatchReceived) */ ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_SINGLE_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_SINGLE_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); uint256[] memory ids = new uint256[](1); uint256[] memory values = new uint256[](1); diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol index 60cd0d82..6eb00642 100644 --- a/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/exportSelectors.t.sol @@ -15,8 +15,7 @@ contract ExportSelectors_ERC1155TransferFacet_Unit_Test is ERC1155TransferFacet_ function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); bytes memory expected = abi.encodePacked( - ERC1155TransferFacet.safeTransferFrom.selector, - ERC1155TransferFacet.safeBatchTransferFrom.selector + ERC1155TransferFacet.safeTransferFrom.selector, ERC1155TransferFacet.safeBatchTransferFrom.selector ); assertEq(selectors, expected, "exportSelectors"); } diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol index 8d5e133f..4e8a1263 100644 --- a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol @@ -31,8 +31,12 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; uint256[] memory ids = new uint256[](idsLen); uint256[] memory values = new uint256[](valuesLen); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; - for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } + for (uint256 i = 0; i < valuesLen; i++) { + values[i] = 1; + } vm.stopPrank(); vm.prank(from); vm.expectRevert( @@ -52,11 +56,9 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer facet.safeBatchTransferFrom(address(0), users.bob, ids, values, ""); } - function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenToIsZeroAddress( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_SafeBatchTransferFrom_WhenToIsZeroAddress(address from, uint256 id, uint256 value) + external + { vm.assume(from != address(0)); address(facet).setBalanceOf(id, from, value); uint256[] memory ids = new uint256[](1); @@ -90,13 +92,7 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer vm.stopPrank(); vm.prank(from); vm.expectRevert( - abi.encodeWithSelector( - ERC1155TransferFacet.ERC1155InsufficientBalance.selector, - from, - balance, - value, - id - ) + abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InsufficientBalance.selector, from, balance, value, id) ); facet.safeBatchTransferFrom(from, to, ids, values, ""); } @@ -134,9 +130,7 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer function test_ShouldUpdateBalances_SafeBatchTransferFrom_WhenToIsReceiverContractAccepting() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); address(facet).setBalanceOf(1, users.alice, 20); address(facet).setBalanceOf(2, users.alice, 30); @@ -154,11 +148,8 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer } function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverContractReturnsWrongValue() external { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - bytes4(0), - bytes4(0), - ERC1155ReceiverMock.RevertType.None - ); + ERC1155ReceiverMock receiver = + new ERC1155ReceiverMock(bytes4(0), bytes4(0), ERC1155ReceiverMock.RevertType.None); address(facet).setBalanceOf(1, users.alice, 10); uint256[] memory ids = new uint256[](1); uint256[] memory values = new uint256[](1); @@ -198,9 +189,7 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer function test_ShouldRevert_SafeBatchTransferFrom_WhenReceiverPanics() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.Panic + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic ); address(facet).setBalanceOf(1, users.alice, 10); uint256[] memory ids = new uint256[](1); @@ -226,17 +215,13 @@ contract SafeBatchTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155Transfer values[0] = 10; vm.stopPrank(); vm.prank(users.alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_BATCH_MAGIC_VALUE)); facet.safeBatchTransferFrom(users.alice, address(receiver), ids, values, ""); } function test_ShouldForwardData_SafeBatchTransferFrom_WhenToIsReceiverContract() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); address(facet).setBalanceOf(1, users.alice, 50); address(facet).setBalanceOf(2, users.alice, 100); diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol index ff5b2373..8d195831 100644 --- a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol @@ -48,11 +48,9 @@ contract RevertingReceiver is IERC1155Receiver { contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet_Base_Test { using ERC1155StorageUtils for address; - function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress( - address from, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress(address from, uint256 id, uint256 value) + external + { vm.assume(from != address(0)); address(facet).setBalanceOf(id, from, value); vm.stopPrank(); @@ -61,11 +59,9 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet facet.safeTransferFrom(from, address(0), id, value, ""); } - function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress( - address to, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress(address to, uint256 id, uint256 value) + external + { vm.assume(to != address(0)); vm.assume(to.code.length == 0); vm.stopPrank(); @@ -112,13 +108,7 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet vm.stopPrank(); vm.prank(from); vm.expectRevert( - abi.encodeWithSelector( - ERC1155TransferFacet.ERC1155InsufficientBalance.selector, - from, - balance, - value, - id - ) + abi.encodeWithSelector(ERC1155TransferFacet.ERC1155InsufficientBalance.selector, from, balance, value, id) ); facet.safeTransferFrom(from, to, id, value, ""); } @@ -174,9 +164,7 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet function test_ShouldUpdateBalances_SafeTransferFrom_WhenToIsReceiverContractAccepting() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); address(facet).setBalanceOf(1, users.alice, 50); vm.stopPrank(); @@ -187,11 +175,8 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet } function test_ShouldRevert_SafeTransferFrom_WhenReceiverContractReturnsWrongValue() external { - ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - bytes4(0), - bytes4(0), - ERC1155ReceiverMock.RevertType.None - ); + ERC1155ReceiverMock receiver = + new ERC1155ReceiverMock(bytes4(0), bytes4(0), ERC1155ReceiverMock.RevertType.None); address(facet).setBalanceOf(1, users.alice, 10); vm.stopPrank(); vm.prank(users.alice); @@ -219,9 +204,7 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet function test_ShouldRevert_SafeTransferFrom_WhenReceiverPanics() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.Panic + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.Panic ); address(facet).setBalanceOf(1, users.alice, 10); vm.stopPrank(); @@ -239,17 +222,13 @@ contract SafeTransferFrom_ERC1155TransferFacet_Fuzz_Test is ERC1155TransferFacet address(facet).setBalanceOf(1, users.alice, 10); vm.stopPrank(); vm.prank(users.alice); - vm.expectRevert( - abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155ReceiverMock.CustomError.selector, RECEIVER_SINGLE_MAGIC_VALUE)); facet.safeTransferFrom(users.alice, address(receiver), 1, 10, ""); } function test_ShouldForwardData_SafeTransferFrom_WhenToIsReceiverContract() external { ERC1155ReceiverMock receiver = new ERC1155ReceiverMock( - RECEIVER_SINGLE_MAGIC_VALUE, - RECEIVER_BATCH_MAGIC_VALUE, - ERC1155ReceiverMock.RevertType.None + RECEIVER_SINGLE_MAGIC_VALUE, RECEIVER_BATCH_MAGIC_VALUE, ERC1155ReceiverMock.RevertType.None ); address(facet).setBalanceOf(1, users.alice, 50); bytes memory data = hex"deadbeef"; diff --git a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol index c7097a7a..37aa7927 100644 --- a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeBatchTransferFrom.t.sol @@ -30,11 +30,13 @@ contract SafeBatchTransferFrom_ERC1155TransferMod_Fuzz_Test is ERC1155TransferMo if (idsLen == valuesLen) valuesLen = (valuesLen + 1) % 6; uint256[] memory ids = new uint256[](idsLen); uint256[] memory values = new uint256[](valuesLen); - for (uint256 i = 0; i < idsLen; i++) ids[i] = i; - for (uint256 i = 0; i < valuesLen; i++) values[i] = 1; - vm.expectRevert( - abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen) - ); + for (uint256 i = 0; i < idsLen; i++) { + ids[i] = i; + } + for (uint256 i = 0; i < valuesLen; i++) { + values[i] = 1; + } + vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidArrayLength.selector, idsLen, valuesLen)); harness.safeBatchTransferFrom(from, to, ids, values, operator); } diff --git a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol index 0d1b1396..42ab5651 100644 --- a/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/mod/fuzz/safeTransferFrom.t.sol @@ -28,11 +28,9 @@ contract SafeTransferFrom_ERC1155TransferMod_Fuzz_Test is ERC1155TransferMod_Bas harness.safeTransferFrom(from, address(0), id, value, operator); } - function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress( - address to, - uint256 id, - uint256 value - ) external { + function testFuzz_ShouldRevert_SafeTransferFrom_WhenFromIsZeroAddress(address to, uint256 id, uint256 value) + external + { vm.assume(to != address(0)); vm.assume(to.code.length == 0); vm.expectRevert(abi.encodeWithSelector(ERC1155InvalidSender.selector, address(0))); @@ -53,9 +51,7 @@ contract SafeTransferFrom_ERC1155TransferMod_Fuzz_Test is ERC1155TransferMod_Bas vm.assume(operator != from); vm.assume(value != type(uint256).max); address(harness).setBalanceOf(id, from, value); - vm.expectRevert( - abi.encodeWithSelector(ERC1155MissingApprovalForAll.selector, operator, from) - ); + vm.expectRevert(abi.encodeWithSelector(ERC1155MissingApprovalForAll.selector, operator, from)); harness.safeTransferFrom(from, to, id, value, operator); } diff --git a/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol b/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol index 5ae50a88..84b65f22 100644 --- a/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol +++ b/test/unit/token/ERC20/Approve/facet/fuzz/approve.t.sol @@ -40,6 +40,5 @@ contract Approve_ERC20ApproveFacet_Fuzz_Unit_Test is Base_Test { assertEq(result, true, "approve failed"); assertEq(address(facet).allowance(users.alice, spender), value); } - } diff --git a/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol b/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol index 9329fdc9..f7b5d992 100644 --- a/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol +++ b/test/unit/token/ERC20/Bridgeable/ERC20BridgeableFacetBase.t.sol @@ -11,7 +11,9 @@ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorage import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; abstract contract ERC20BridgeableFacet_Base_Test is Base_Test { - /// @dev Role identifier used by ERC20BridgeableFacet (literal "trusted-bridge" as bytes32). + /** + * @dev Role identifier used by ERC20BridgeableFacet (literal "trusted-bridge" as bytes32). + */ bytes32 internal constant ERC20_BRIDGE_ROLE = bytes32("trusted-bridge"); using AccessControlStorageUtils for address; using ERC20StorageUtils for address; diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol index 87f8fb4b..15a9fc20 100644 --- a/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/checkTokenBridge.t.sol @@ -13,17 +13,13 @@ import {ERC20BridgeableFacet} from "src/token/ERC20/Bridgeable/ERC20BridgeableFa */ contract CheckTokenBridge_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFacet_Base_Test { function testFuzz_ShouldRevert_WhenCallerIsZeroAddress() external { - vm.expectRevert( - abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, ADDRESS_ZERO) - ); + vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, ADDRESS_ZERO)); facet.checkTokenBridge(ADDRESS_ZERO); } function testFuzz_ShouldRevert_WhenCallerDoesNotHaveBridgeRole(address account) external { vm.assume(account != ADDRESS_ZERO); - vm.expectRevert( - abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, account) - ); + vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, account)); facet.checkTokenBridge(account); } diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol index 24d6de4b..aefefd01 100644 --- a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainBurn.t.sol @@ -25,9 +25,7 @@ contract CrosschainBurn_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFa vm.prank(from); vm.expectRevert( abi.encodeWithSelector( - ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, - from, - ERC20_BRIDGE_ROLE + ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, from, ERC20_BRIDGE_ROLE ) ); facet.crosschainBurn(from, value); @@ -41,11 +39,7 @@ contract CrosschainBurn_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFa facet.crosschainBurn(ADDRESS_ZERO, value); } - function testFuzz_ShouldRevert_WhenInsufficientBalance( - address from, - uint256 balance, - uint256 value - ) external { + function testFuzz_ShouldRevert_WhenInsufficientBalance(address from, uint256 balance, uint256 value) external { vm.assume(from != ADDRESS_ZERO); vm.assume(balance < MAX_UINT256); value = bound(value, balance + 1, MAX_UINT256); @@ -54,12 +48,7 @@ contract CrosschainBurn_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFa vm.stopPrank(); vm.prank(users.admin); vm.expectRevert( - abi.encodeWithSelector( - ERC20BridgeableFacet.ERC20InsufficientBalance.selector, - from, - balance, - value - ) + abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InsufficientBalance.selector, from, balance, value) ); facet.crosschainBurn(from, value); } diff --git a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol index 08807f3e..57916bff 100644 --- a/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol +++ b/test/unit/token/ERC20/Bridgeable/facet/fuzz/crosschainMint.t.sol @@ -24,9 +24,7 @@ contract CrosschainMint_ERC20BridgeableFacet_Fuzz_Unit_Test is ERC20BridgeableFa vm.prank(to); vm.expectRevert( abi.encodeWithSelector( - ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, - to, - ERC20_BRIDGE_ROLE + ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, to, ERC20_BRIDGE_ROLE ) ); facet.crosschainMint(to, value); diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol index 046b96eb..f90d7db3 100644 --- a/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol +++ b/test/unit/token/ERC20/Burn/facet/fuzz/burn.t.sol @@ -15,9 +15,7 @@ import {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; contract Burn_ERC20BurnFacet_Fuzz_Unit_Test is ERC20BurnFacet_Base_Test { using ERC20StorageUtils for address; - function testFuzz_ShouldRevert_CallerInsufficientBalance(address caller, uint256 balance, uint256 value) - external - { + function testFuzz_ShouldRevert_CallerInsufficientBalance(address caller, uint256 balance, uint256 value) external { vm.assume(balance < MAX_UINT256); value = bound(value, balance + 1, MAX_UINT256); diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol index e016e5ff..35fb8ee7 100644 --- a/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol +++ b/test/unit/token/ERC20/Burn/facet/fuzz/burnFrom.t.sol @@ -32,12 +32,7 @@ contract BurnFrom_ERC20BurnFacet_Fuzz_Unit_Test is ERC20BurnFacet_Base_Test { vm.stopPrank(); vm.prank(spender); vm.expectRevert( - abi.encodeWithSelector( - ERC20BurnFacet.ERC20InsufficientAllowance.selector, - spender, - allowance, - value - ) + abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientAllowance.selector, spender, allowance, value) ); facet.burnFrom(account, value); } @@ -95,12 +90,9 @@ contract BurnFrom_ERC20BurnFacet_Fuzz_Unit_Test is ERC20BurnFacet_Base_Test { assertEq(address(facet).allowance(account, spender), allowance - value, "allowance"); } - function testFuzz_BurnFrom_InfiniteApproval( - address account, - address spender, - uint256 value, - uint256 balance - ) external { + function testFuzz_BurnFrom_InfiniteApproval(address account, address spender, uint256 value, uint256 balance) + external + { vm.assume(account != ADDRESS_ZERO); vm.assume(spender != ADDRESS_ZERO); vm.assume(account != spender); diff --git a/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol index 44d1dcf1..3587913e 100644 --- a/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC20/Burn/facet/fuzz/exportSelectors.t.sol @@ -14,10 +14,7 @@ import {ERC20BurnFacet} from "src/token/ERC20/Burn/ERC20BurnFacet.sol"; contract ExportSelectors_ERC20BurnFacet_Unit_Test is ERC20BurnFacet_Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC20BurnFacet.burn.selector, - ERC20BurnFacet.burnFrom.selector - ); + bytes memory expected = abi.encodePacked(ERC20BurnFacet.burn.selector, ERC20BurnFacet.burnFrom.selector); assertEq(selectors, expected, "exportSelectors"); } } diff --git a/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol index 8b56b878..643059c8 100644 --- a/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC20/Data/facet/fuzz/exportSelectors.t.sol @@ -15,9 +15,7 @@ contract ExportSelectors_ERC20DataFacet_Unit_Test is ERC20DataFacet_Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); bytes memory expected = abi.encodePacked( - ERC20DataFacet.totalSupply.selector, - ERC20DataFacet.balanceOf.selector, - ERC20DataFacet.allowance.selector + ERC20DataFacet.totalSupply.selector, ERC20DataFacet.balanceOf.selector, ERC20DataFacet.allowance.selector ); assertEq(selectors, expected, "exportSelectors"); } diff --git a/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol index a77d850b..4dba3ecd 100644 --- a/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC20/Metadata/facet/fuzz/exportSelectors.t.sol @@ -15,9 +15,7 @@ contract ExportSelectors_ERC20MetadataFacet_Unit_Test is ERC20MetadataFacet_Base function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); bytes memory expected = abi.encodePacked( - ERC20MetadataFacet.name.selector, - ERC20MetadataFacet.symbol.selector, - ERC20MetadataFacet.decimals.selector + ERC20MetadataFacet.name.selector, ERC20MetadataFacet.symbol.selector, ERC20MetadataFacet.decimals.selector ); assertEq(selectors, expected, "exportSelectors"); } diff --git a/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol index 618e8f3c..b1906731 100644 --- a/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol +++ b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol @@ -19,14 +19,10 @@ abstract contract ERC20PermitFacet_Base_Test is Base_Test { vm.label(address(facet), "ERC20PermitFacetHarness"); } - /// @dev Computes EIP-712 digest for Permit(owner, spender, value, nonce, deadline) for the current facet. - function _getPermitDigest( - address owner, - address spender, - uint256 value, - uint256 nonce, - uint256 deadline - ) + /** + * @dev Computes EIP-712 digest for Permit(owner, spender, value, nonce, deadline) for the current facet. + */ + function _getPermitDigest(address owner, address spender, uint256 value, uint256 nonce, uint256 deadline) internal view returns (bytes32) diff --git a/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol b/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol index 837e8f62..4dabfd1c 100644 --- a/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol +++ b/test/unit/token/ERC20/Permit/facet/fuzz/permit.t.sol @@ -15,11 +15,7 @@ import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; contract Permit_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { using ERC20StorageUtils for address; - function testFuzz_ShouldRevert_WhenSpenderIsZeroAddress( - address owner, - uint256 value, - uint256 deadline - ) external { + function testFuzz_ShouldRevert_WhenSpenderIsZeroAddress(address owner, uint256 value, uint256 deadline) external { uint256 ownerPrivateKey = 0xA11CE; owner = vm.addr(ownerPrivateKey); deadline = bound(deadline, block.timestamp + 1, type(uint256).max); @@ -31,11 +27,7 @@ contract Permit_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { facet.permit(owner, ADDRESS_ZERO, value, deadline, v, r, s); } - function testFuzz_ShouldRevert_WhenDeadlineExpired( - address owner, - address spender, - uint256 value - ) external { + function testFuzz_ShouldRevert_WhenDeadlineExpired(address owner, address spender, uint256 value) external { vm.assume(spender != ADDRESS_ZERO); uint256 ownerPrivateKey = 0xB0B; owner = vm.addr(ownerPrivateKey); @@ -46,24 +38,13 @@ contract Permit_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { vm.expectRevert( abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, - owner, - spender, - value, - deadline, - v, - r, - s + ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, spender, value, deadline, v, r, s ) ); facet.permit(owner, spender, value, deadline, v, r, s); } - function testFuzz_ShouldRevert_WhenSignatureInvalid( - address spender, - uint256 value, - uint256 deadline - ) external { + function testFuzz_ShouldRevert_WhenSignatureInvalid(address spender, uint256 value, uint256 deadline) external { vm.assume(spender != ADDRESS_ZERO); uint256 ownerPrivateKey = 0xA11CE; address owner = vm.addr(ownerPrivateKey); @@ -75,24 +56,13 @@ contract Permit_ERC20PermitFacet_Fuzz_Unit_Test is ERC20PermitFacet_Base_Test { vm.expectRevert( abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, - owner, - spender, - value, - deadline, - v, - r, - s + ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, spender, value, deadline, v, r, s ) ); facet.permit(owner, spender, value, deadline, v, r, s); } - function testFuzz_Permit( - address spender, - uint256 value, - uint256 deadline - ) external { + function testFuzz_Permit(address spender, uint256 value, uint256 deadline) external { vm.assume(spender != ADDRESS_ZERO); uint256 ownerPrivateKey = 0xA11CE; address owner = vm.addr(ownerPrivateKey); diff --git a/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol index 096f7a45..8a1fa955 100644 --- a/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC20/Transfer/facet/fuzz/exportSelectors.t.sol @@ -14,10 +14,8 @@ import {ERC20TransferFacet} from "src/token/ERC20/Transfer/ERC20TransferFacet.so contract ExportSelectors_ERC20TransferFacet_Unit_Test is ERC20TransferFacet_Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); - bytes memory expected = abi.encodePacked( - ERC20TransferFacet.transfer.selector, - ERC20TransferFacet.transferFrom.selector - ); + bytes memory expected = + abi.encodePacked(ERC20TransferFacet.transfer.selector, ERC20TransferFacet.transferFrom.selector); assertEq(selectors, expected, "exportSelectors"); } } diff --git a/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol b/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol index 69b22150..8f03f134 100644 --- a/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol +++ b/test/unit/token/ERC20/Transfer/mod/fuzz/transfer.t.sol @@ -40,11 +40,7 @@ contract Transfer_ERC20TransferMod_Fuzz_Unit_Test is ERC20TransferMod_Base_Test address to, uint256 senderBalance, uint256 receiverBalance - ) - external - whenReceiverNotZeroAddress - givenWhenSenderBalanceGETransferAmount - { + ) external whenReceiverNotZeroAddress givenWhenSenderBalanceGETransferAmount { vm.assume(to != ADDRESS_ZERO); vm.assume(to != users.alice); From c036520905b941178c2d53b0d19abef2572ac9f4 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Mon, 9 Mar 2026 23:24:09 -0400 Subject: [PATCH 09/25] fix grant test --- test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol index 35687da5..1fd6f2a4 100644 --- a/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol +++ b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol @@ -64,6 +64,8 @@ contract GrantRole_AccessControlGrantMod_Fuzz_Unit_Test is AccessControlGrant_Ba } function testFuzz_ShouldReturnTrue_WhenRoleIsSelfAdmin(address caller, address account) external { + vm.assume(caller != account); + seedAdminRole(address(harness), MINTER_ROLE, MINTER_ROLE); seedRole(address(harness), MINTER_ROLE, caller); From 2480e61d9e8948e7e9b989ac0ec6aef20c59f176 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 14:12:30 -0400 Subject: [PATCH 10/25] add getter/setter comment in storage utils, move mocks in utils --- test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol | 2 +- .../token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol | 2 +- .../facet/fuzz/safeBatchTransferFrom.t.sol | 2 +- .../Transfer/facet/fuzz/safeTransferFrom.t.sol | 2 +- test/{ => utils}/mocks/ERC1155ReceiverMock.sol | 0 test/utils/storage/AccessControlStorageUtils.sol | 4 ++++ test/utils/storage/ERC1155StorageUtils.sol | 16 ++++++++++------ test/utils/storage/OwnerStorageUtils.sol | 4 ++++ 8 files changed, 22 insertions(+), 10 deletions(-) rename test/{ => utils}/mocks/ERC1155ReceiverMock.sol (100%) diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol index e282cfb7..4e1aea80 100644 --- a/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mint.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {ERC1155MintMod_Base_Test} from "test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; -import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {ERC1155ReceiverMock} from "test/utils/mocks/ERC1155ReceiverMock.sol"; import "src/token/ERC1155/Mint/ERC1155MintMod.sol"; /** diff --git a/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol index 82477a70..e4c5b004 100644 --- a/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol +++ b/test/unit/token/ERC1155/Mint/mod/fuzz/mintBatch.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {ERC1155MintMod_Base_Test} from "test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; -import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {ERC1155ReceiverMock} from "test/utils/mocks/ERC1155ReceiverMock.sol"; import "src/token/ERC1155/Mint/ERC1155MintMod.sol"; /** diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol index 4e8a1263..8272026d 100644 --- a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeBatchTransferFrom.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import {ERC1155TransferFacet_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; import {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; -import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {ERC1155ReceiverMock} from "test/utils/mocks/ERC1155ReceiverMock.sol"; import {RevertingReceiver} from "test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol"; /** diff --git a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol index 8d195831..53d1f674 100644 --- a/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol +++ b/test/unit/token/ERC1155/Transfer/facet/fuzz/safeTransferFrom.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import {ERC1155TransferFacet_Base_Test} from "test/unit/token/ERC1155/Transfer/ERC1155TransferFacetBase.t.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; import {ERC1155TransferFacet} from "src/token/ERC1155/Transfer/ERC1155TransferFacet.sol"; -import {ERC1155ReceiverMock} from "test/mocks/ERC1155ReceiverMock.sol"; +import {ERC1155ReceiverMock} from "test/utils/mocks/ERC1155ReceiverMock.sol"; import {IERC1155Receiver} from "src/interfaces/IERC1155Receiver.sol"; /** diff --git a/test/mocks/ERC1155ReceiverMock.sol b/test/utils/mocks/ERC1155ReceiverMock.sol similarity index 100% rename from test/mocks/ERC1155ReceiverMock.sol rename to test/utils/mocks/ERC1155ReceiverMock.sol diff --git a/test/utils/storage/AccessControlStorageUtils.sol b/test/utils/storage/AccessControlStorageUtils.sol index 585ceac2..dd8c427e 100644 --- a/test/utils/storage/AccessControlStorageUtils.sol +++ b/test/utils/storage/AccessControlStorageUtils.sol @@ -61,6 +61,10 @@ library AccessControlStorageUtils { return uint256(vm.load(target, slot)); } + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + function setHasRole(address target, address account, bytes32 role, bool value) internal { bytes32 accountSlot = keccak256(abi.encode(account, uint256(ACCESS_CONTROL_STORAGE_POSITION))); bytes32 slot = keccak256(abi.encode(role, accountSlot)); diff --git a/test/utils/storage/ERC1155StorageUtils.sol b/test/utils/storage/ERC1155StorageUtils.sol index ff7cc664..106c4fce 100644 --- a/test/utils/storage/ERC1155StorageUtils.sol +++ b/test/utils/storage/ERC1155StorageUtils.sol @@ -36,18 +36,22 @@ library ERC1155StorageUtils { return uint256(vm.load(target, slot)); } - function setBalanceOf(address target, uint256 id, address account, uint256 value) internal { - bytes32 idSlot = keccak256(abi.encode(id, uint256(ERC1155_STORAGE_POSITION))); - bytes32 slot = keccak256(abi.encode(account, idSlot)); - vm.store(target, slot, bytes32(value)); - } - function isApprovedForAll(address target, address account, address operator) internal view returns (bool) { bytes32 accountSlot = keccak256(abi.encode(account, uint256(ERC1155_STORAGE_POSITION) + 1)); bytes32 slot = keccak256(abi.encode(operator, accountSlot)); return uint256(vm.load(target, slot)) != 0; } + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + + function setBalanceOf(address target, uint256 id, address account, uint256 value) internal { + bytes32 idSlot = keccak256(abi.encode(id, uint256(ERC1155_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(account, idSlot)); + vm.store(target, slot, bytes32(value)); + } + function setApprovedForAll(address target, address account, address operator, bool value) internal { bytes32 accountSlot = keccak256(abi.encode(account, uint256(ERC1155_STORAGE_POSITION) + 1)); bytes32 slot = keccak256(abi.encode(operator, accountSlot)); diff --git a/test/utils/storage/OwnerStorageUtils.sol b/test/utils/storage/OwnerStorageUtils.sol index cb7244e1..636186e6 100644 --- a/test/utils/storage/OwnerStorageUtils.sol +++ b/test/utils/storage/OwnerStorageUtils.sol @@ -41,6 +41,10 @@ library OwnerStorageUtils { return address(uint160(uint256(vm.load(target, PENDING_OWNER_STORAGE_POSITION)))); } + /*////////////////////////////////////////////////////////////// + SETTERS + //////////////////////////////////////////////////////////////*/ + function setOwner(address target, address value) internal { vm.store(target, OWNER_STORAGE_POSITION, bytes32(uint256(uint160(value)))); } From 93017fd0200db9353f77284e22825c5b57508862 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 15:20:52 -0400 Subject: [PATCH 11/25] refactor ERC165 tests, move harnesses to utils, refactor nonreentrancy tests --- .../ERC165/Data/diamond/fuzz/data.t.sol | 22 + .../ERC165/ERC165DiamondBase.t.sol | 64 +++ .../diamond/fuzz/registerInterfaces.t.sol | 25 + test/interfaceDetection/ERC165/ERC165.t.sol | 502 ----------------- .../ERC165/ERC165Facet.t.sol | 381 ------------- .../ERC165/harnesses/ERC165FacetHarness.sol | 67 --- test/token/ERC20/ERC20/ERC20BurnFacet | 225 -------- test/token/ERC20/ERC20/ERC20PermitFacet | 532 ------------------ .../ERC20/harnesses/ERC20BurnFacetHarness | 56 -- .../ERC20/harnesses/ERC20PermitFacetHarness | 85 --- .../ERC20/ERC20Bridgeble/ERC20Bridgeable | 127 ----- .../ERC20/ERC20Bridgeble/ERC20BridgeableFacet | 132 ----- .../harnesses/ERC20BridgeableHarness | 57 -- test/trees/ERC165.tree | 91 +++ test/trees/NonReentrancy.tree | 13 + .../Admin/mod/fuzz/setRoleAdmin.t.sol | 2 +- .../Batch/Grant/mod/fuzz/grantRoleBatch.t.sol | 2 +- .../Revoke/mod/fuzz/revokeRoleBatch.t.sol | 2 +- .../AccessControl/Data/mod/fuzz/data.t.sol | 2 +- .../Grant/mod/fuzz/grantRole.t.sol | 2 +- .../Pausable/mod/fuzz/pausable.t.sol | 2 +- .../Renounce/mod/fuzz/renounceRole.t.sol | 2 +- .../Revoke/mod/fuzz/revokeRole.t.sol | 2 +- .../Temporal/Data/mod/fuzz/data.t.sol | 2 +- .../Grant/mod/fuzz/grantRoleWithExpiry.t.sol | 2 +- .../Revoke/mod/fuzz/revokeTemporalRole.t.sol | 2 +- .../access/Owner/Data/mod/fuzz/data.t.sol | 2 +- .../Renounce/mod/fuzz/renounceOwnership.t.sol | 2 +- .../Transfer/mod/fuzz/transferOwnership.t.sol | 2 +- .../Owner/TwoSteps/Data/mod/fuzz/data.t.sol | 2 +- .../Renounce/mod/fuzz/renounceOwnership.t.sol | 2 +- .../Transfer/mod/fuzz/transferOwnership.t.sol | 2 +- .../ERC165/Data/facet/fuzz/data.t.sol | 32 ++ .../ERC165/Data/mod/fuzz/data.t.sol | 38 ++ .../ERC165/ERC165FacetBase.t.sol | 30 + .../ERC165/ERC165ModBase.t.sol | 30 + .../Gas/facet/fuzz/supportsInterface.t.sol | 40 ++ .../ERC165/Gas/mod/fuzz/gas.t.sol | 55 ++ .../facet/fuzz/registerInterface.t.sol | 65 +++ .../Register/mod/fuzz/registerInterface.t.sol | 107 ++++ .../ERC165/Storage/facet/fuzz/storage.t.sol | 49 ++ .../ERC165/Storage/mod/fuzz/storage.t.sol | 58 ++ .../Unregister/facet/fuzz/operations.t.sol | 52 ++ .../facet/fuzz/unregisterInterface.t.sol | 53 ++ .../mod/fuzz/unregisterInterface.t.sol | 86 +++ .../NonReentrancy/NonReentrancyModBase.t.sol | 19 + .../mod/fuzz/nonReentrancy.t.sol} | 24 +- .../Approve/ERC1155ApproveModBase.t.sol | 2 +- .../ERC1155/Burn/ERC1155BurnModBase.t.sol | 2 +- .../Metadata/ERC1155MetadataModBase.t.sol | 2 +- .../ERC1155/Mint/ERC1155MintModBase.t.sol | 2 +- .../Transfer/ERC1155TransferModBase.t.sol | 2 +- .../ERC20/Approve/mod/fuzz/approve.t.sol | 2 +- .../unit/token/ERC20/Burn/mod/fuzz/burn.t.sol | 2 +- .../Metadata/ERC20MetadataFacetBase.t.sol | 2 +- .../unit/token/ERC20/Mint/mod/fuzz/mint.t.sol | 2 +- .../ERC20/Permit/ERC20PermitFacetBase.t.sol | 2 +- .../Transfer/mod/ERC20TransferModBase.t.sol | 2 +- .../AccessControlCoreModHarness.sol | 0 .../AccessControlPausableModHarness.sol | 0 .../AccessControlTemporalModHarness.sol | 0 .../access/Owner/OwnerCoreModHarness.sol | 0 .../access/Owner/OwnerTwoStepModHarness.sol | 0 .../ERC165/ERC165FacetHarness.sol | 37 ++ .../ERC165}/ERC165Harness.sol | 37 +- .../libraries}/NonReentrancyHarness.sol | 1 + .../ERC1155/ERC1155ApproveModHarness.sol | 0 .../token/ERC1155/ERC1155BurnModHarness.sol | 0 .../ERC1155/ERC1155MetadataModHarness.sol | 0 .../token/ERC1155/ERC1155MintModHarness.sol | 0 .../ERC1155/ERC1155TransferModHarness.sol | 0 .../token/ERC20/ERC20ApproveModHarness.sol | 0 .../token/ERC20/ERC20BurnModHarness.sol | 0 .../token/ERC20/ERC20MetadataModHarness.sol | 0 .../token/ERC20/ERC20MintModHarness.sol | 0 .../token/ERC20/ERC20PermitFacetHarness.sol | 0 .../token/ERC20/ERC20TransferModHarness.sol | 0 77 files changed, 1011 insertions(+), 2237 deletions(-) create mode 100644 test/integration/interfaceDetection/ERC165/Data/diamond/fuzz/data.t.sol create mode 100644 test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol create mode 100644 test/integration/interfaceDetection/ERC165/Register/diamond/fuzz/registerInterfaces.t.sol delete mode 100644 test/interfaceDetection/ERC165/ERC165.t.sol delete mode 100644 test/interfaceDetection/ERC165/ERC165Facet.t.sol delete mode 100644 test/interfaceDetection/ERC165/harnesses/ERC165FacetHarness.sol delete mode 100644 test/token/ERC20/ERC20/ERC20BurnFacet delete mode 100644 test/token/ERC20/ERC20/ERC20PermitFacet delete mode 100644 test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness delete mode 100644 test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness delete mode 100644 test/token/ERC20/ERC20Bridgeble/ERC20Bridgeable delete mode 100644 test/token/ERC20/ERC20Bridgeble/ERC20BridgeableFacet delete mode 100644 test/token/ERC20/ERC20Bridgeble/harnesses/ERC20BridgeableHarness create mode 100644 test/trees/ERC165.tree create mode 100644 test/trees/NonReentrancy.tree create mode 100644 test/unit/interfaceDetection/ERC165/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Data/mod/fuzz/data.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Gas/facet/fuzz/supportsInterface.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Gas/mod/fuzz/gas.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Register/facet/fuzz/registerInterface.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Register/mod/fuzz/registerInterface.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Storage/facet/fuzz/storage.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Storage/mod/fuzz/storage.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/operations.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/unregisterInterface.t.sol create mode 100644 test/unit/interfaceDetection/ERC165/Unregister/mod/fuzz/unregisterInterface.t.sol create mode 100644 test/unit/libraries/NonReentrancy/NonReentrancyModBase.t.sol rename test/{libraries/NonReentrancy.t.sol => unit/libraries/NonReentrancy/mod/fuzz/nonReentrancy.t.sol} (51%) rename test/{ => utils}/harnesses/access/AccessControl/AccessControlCoreModHarness.sol (100%) rename test/{ => utils}/harnesses/access/AccessControl/AccessControlPausableModHarness.sol (100%) rename test/{ => utils}/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol (100%) rename test/{ => utils}/harnesses/access/Owner/OwnerCoreModHarness.sol (100%) rename test/{ => utils}/harnesses/access/Owner/OwnerTwoStepModHarness.sol (100%) create mode 100644 test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol rename test/{interfaceDetection/ERC165/harnesses => utils/harnesses/interfaceDetection/ERC165}/ERC165Harness.sol (57%) rename test/{libraries/harnesses => utils/harnesses/libraries}/NonReentrancyHarness.sol (99%) rename test/{ => utils}/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC1155/ERC1155BurnModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC1155/ERC1155MintModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC1155/ERC1155TransferModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20ApproveModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20BurnModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20MetadataModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20MintModHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20PermitFacetHarness.sol (100%) rename test/{ => utils}/harnesses/token/ERC20/ERC20TransferModHarness.sol (100%) diff --git a/test/integration/interfaceDetection/ERC165/Data/diamond/fuzz/data.t.sol b/test/integration/interfaceDetection/ERC165/Data/diamond/fuzz/data.t.sol new file mode 100644 index 00000000..f1be1364 --- /dev/null +++ b/test/integration/interfaceDetection/ERC165/Data/diamond/fuzz/data.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Diamond_Base_Integration_Test} from "test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Data_ERC165Diamond_Fuzz_Integration_Test is ERC165Diamond_Base_Integration_Test { + function test_ShouldSupportIERC165_WhenDiamondIsDeployedWithERC165Facet() external view { + assertTrue(diamondERC165.supportsInterface(IERC165_INTERFACE_ID)); + } + + function test_ShouldReturnFalse_ForUnregisteredInterfaceBeforeRegistration() external view { + assertFalse(diamondERC165.supportsInterface(IERC1155_INTERFACE_ID)); + } +} + diff --git a/test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol b/test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol new file mode 100644 index 00000000..e4110d49 --- /dev/null +++ b/test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test} from "forge-std/Test.sol"; + +import {ExampleDiamond} from "src/diamond/example/ExampleDiamond.sol"; +import {ERC165Facet, IERC165} from "src/interfaceDetection/ERC165/ERC165Facet.sol"; +import "src/interfaceDetection/ERC165/ERC165Mod.sol" as ERC165Mod; +import {IERC20} from "src/interfaces/IERC20.sol"; +import {IERC1155} from "src/interfaces/IERC1155.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ + +contract ERC165RegisterFacet { + function registerInterfaces(bytes4[] calldata interfaceIds) external { + uint256 length = interfaceIds.length; + + for (uint256 i = 0; i < length; i++) { + ERC165Mod.registerInterface(interfaceIds[i]); + } + } + + function exportSelectors() external pure returns (bytes memory) { + return bytes.concat(this.registerInterfaces.selector); + } +} + +interface IDiamondERC165 is IERC165 {} + +interface IDiamondERC165Register { + function registerInterfaces(bytes4[] calldata interfaceIds) external; +} + +abstract contract ERC165Diamond_Base_Integration_Test is Test { + ExampleDiamond internal diamond; + + IDiamondERC165 internal diamondERC165; + IDiamondERC165Register internal diamondRegister; + + bytes4 internal constant IERC165_INTERFACE_ID = type(IERC165).interfaceId; + bytes4 internal constant IERC20_INTERFACE_ID = type(IERC20).interfaceId; + bytes4 internal constant IERC1155_INTERFACE_ID = type(IERC1155).interfaceId; + + function setUp() public virtual { + ERC165Facet erc165Facet = new ERC165Facet(); + ERC165RegisterFacet registerFacet = new ERC165RegisterFacet(); + + address[] memory facets = new address[](2); + facets[0] = address(erc165Facet); + facets[1] = address(registerFacet); + + diamond = new ExampleDiamond(facets, address(this)); + + diamondERC165 = IDiamondERC165(address(diamond)); + diamondRegister = IDiamondERC165Register(address(diamond)); + } +} + diff --git a/test/integration/interfaceDetection/ERC165/Register/diamond/fuzz/registerInterfaces.t.sol b/test/integration/interfaceDetection/ERC165/Register/diamond/fuzz/registerInterfaces.t.sol new file mode 100644 index 00000000..fdea5a68 --- /dev/null +++ b/test/integration/interfaceDetection/ERC165/Register/diamond/fuzz/registerInterfaces.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Diamond_Base_Integration_Test} from "test/integration/interfaceDetection/ERC165/ERC165DiamondBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract RegisterInterfaces_ERC165Diamond_Fuzz_Integration_Test is ERC165Diamond_Base_Integration_Test { + function test_ShouldReturnTrue_ForFeatureInterfacesAfterRegistration() external { + bytes4[] memory interfaceIds = new bytes4[](2); + interfaceIds[0] = IERC20_INTERFACE_ID; + interfaceIds[1] = IERC1155_INTERFACE_ID; + + diamondRegister.registerInterfaces(interfaceIds); + + assertTrue(diamondERC165.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(diamondERC165.supportsInterface(IERC1155_INTERFACE_ID)); + } +} + diff --git a/test/interfaceDetection/ERC165/ERC165.t.sol b/test/interfaceDetection/ERC165/ERC165.t.sol deleted file mode 100644 index 49db015f..00000000 --- a/test/interfaceDetection/ERC165/ERC165.t.sol +++ /dev/null @@ -1,502 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import "../../../src/interfaceDetection/ERC165/ERC165Mod.sol" as ERC165Mod; -import {ERC165Harness} from "./harnesses/ERC165Harness.sol"; - -contract LibERC165Test is Test { - ERC165Harness public harness; - - /** - * Test interface IDs - */ - bytes4 constant IERC721_INTERFACE_ID = 0x80ac58cd; - bytes4 constant IERC20_INTERFACE_ID = 0x36372b07; - bytes4 constant IERC1155_INTERFACE_ID = 0xd9b67a26; - bytes4 constant IERC165_INTERFACE_ID = 0x01ffc9a7; - bytes4 constant INVALID_INTERFACE_ID = 0xffffffff; - bytes4 constant CUSTOM_INTERFACE_ID = 0x12345678; - bytes4 constant ZERO_INTERFACE_ID = 0x00000000; - - function setUp() public { - harness = new ERC165Harness(); - harness.initialize(); - } - - /** - * Storage Tests - */ - - function test_GetStorage_ReturnsCorrectStoragePosition() public view { - bytes32 expectedSlot = keccak256("erc165"); - assertEq(harness.getStoragePosition(), expectedSlot); - } - - function test_StorageSlot_UsesCorrectPosition() public { - bytes32 expectedSlot = keccak256("erc165"); - - harness.registerInterface(IERC721_INTERFACE_ID); - - /** - * Read directly from storage - */ - /** - * The mapping slot is calculated as keccak256(abi.encode(key, slot)) - */ - bytes32 mappingSlot = keccak256(abi.encode(IERC721_INTERFACE_ID, expectedSlot)); - bytes32 storedValue = vm.load(address(harness), mappingSlot); - - /** - * Should be true (1) - */ - assertEq(uint256(storedValue), 1); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_GetStorageValue_MatchesSupportsInterface() public { - harness.registerInterface(IERC721_INTERFACE_ID); - - bool supportsResult = harness.supportsInterface(IERC721_INTERFACE_ID); - bool storageResult = harness.getStorageValue(IERC721_INTERFACE_ID); - - assertEq(supportsResult, storageResult); - } - - /** - * Register Interface Tests - */ - - function test_RegisterInterface_SingleInterface() public { - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_RegisterInterface_MultipleInterfaces() public { - harness.registerInterface(IERC721_INTERFACE_ID); - harness.registerInterface(IERC20_INTERFACE_ID); - harness.registerInterface(IERC1155_INTERFACE_ID); - - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); - } - - function test_RegisterInterface_Idempotent() public { - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Register again - */ - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_RegisterInterface_ZeroInterfaceId() public { - harness.registerInterface(ZERO_INTERFACE_ID); - assertTrue(harness.supportsInterface(ZERO_INTERFACE_ID)); - } - - function test_RegisterInterface_InvalidInterfaceId() public { - harness.registerInterface(INVALID_INTERFACE_ID); - assertTrue(harness.supportsInterface(INVALID_INTERFACE_ID)); - } - - function test_RegisterInterface_ERC165InterfaceId() public { - harness.registerInterface(IERC165_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_RegisterInterface_CustomInterfaceId() public { - harness.registerInterface(CUSTOM_INTERFACE_ID); - assertTrue(harness.supportsInterface(CUSTOM_INTERFACE_ID)); - } - - /** - * Multiple Interface Registration Tests - */ - - function test_RegisterMultipleInterfaces_Array() public { - bytes4[] memory interfaceIds = new bytes4[](3); - interfaceIds[0] = IERC721_INTERFACE_ID; - interfaceIds[1] = IERC20_INTERFACE_ID; - interfaceIds[2] = IERC1155_INTERFACE_ID; - - harness.registerMultipleInterfaces(interfaceIds); - - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); - } - - function test_RegisterMultipleInterfaces_EmptyArray() public { - bytes4[] memory interfaceIds = new bytes4[](0); - harness.registerMultipleInterfaces(interfaceIds); - /** - * Should not revert - */ - } - - function test_RegisterMultipleInterfaces_SingleElement() public { - bytes4[] memory interfaceIds = new bytes4[](1); - interfaceIds[0] = IERC721_INTERFACE_ID; - - harness.registerMultipleInterfaces(interfaceIds); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_RegisterMultipleInterfaces_WithDuplicates() public { - bytes4[] memory interfaceIds = new bytes4[](5); - interfaceIds[0] = IERC721_INTERFACE_ID; - interfaceIds[1] = IERC20_INTERFACE_ID; - interfaceIds[2] = IERC721_INTERFACE_ID; /** - * Duplicate - */ - interfaceIds[3] = IERC1155_INTERFACE_ID; - interfaceIds[4] = IERC20_INTERFACE_ID; /** - * Duplicate - */ - - harness.registerMultipleInterfaces(interfaceIds); - - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); - } - - /** - * Unregistered Interface Tests - */ - - function test_SupportsInterface_UnregisteredInterface() public view { - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertFalse(harness.supportsInterface(CUSTOM_INTERFACE_ID)); - } - - function test_SupportsInterface_AfterUnregistration() public { - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister using forceSetInterface - */ - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - /** - * Edge Cases - */ - - function test_RegisterAndUnregisterCycle() public { - /** - * Register - */ - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister - */ - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Register again - */ - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister again - */ - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_MixedOperations() public { - /** - * Register multiple - */ - harness.registerInterface(IERC721_INTERFACE_ID); - harness.registerInterface(IERC20_INTERFACE_ID); - harness.registerInterface(CUSTOM_INTERFACE_ID); - - /** - * Verify all registered - */ - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(CUSTOM_INTERFACE_ID)); - - /** - * Unregister one - */ - harness.forceSetInterface(IERC20_INTERFACE_ID, false); - - /** - * Verify state - */ - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(CUSTOM_INTERFACE_ID)); - - /** - * Register a new one - */ - harness.registerInterface(IERC1155_INTERFACE_ID); - - /** - * Verify final state - */ - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(harness.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(harness.supportsInterface(CUSTOM_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); - } - - function test_ForceSetInterface_CanSetToTrue() public { - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - - harness.forceSetInterface(IERC721_INTERFACE_ID, true); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_ForceSetInterface_CanSetToFalse() public { - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_ForceSetInterface_DoesNotAffectOtherInterfaces() public { - harness.registerInterface(IERC721_INTERFACE_ID); - harness.registerInterface(IERC20_INTERFACE_ID); - - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - - assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); - } - - /** - * Storage Consistency Tests - */ - - function test_StorageConsistency_AfterRegistration() public { - harness.registerInterface(IERC721_INTERFACE_ID); - - assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); - } - - function test_StorageConsistency_AfterMultipleRegistrations() public { - harness.registerInterface(IERC721_INTERFACE_ID); - harness.registerInterface(IERC20_INTERFACE_ID); - harness.registerInterface(IERC1155_INTERFACE_ID); - - assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); - assertEq(harness.supportsInterface(IERC20_INTERFACE_ID), harness.getStorageValue(IERC20_INTERFACE_ID)); - assertEq(harness.supportsInterface(IERC1155_INTERFACE_ID), harness.getStorageValue(IERC1155_INTERFACE_ID)); - } - - function test_StorageConsistency_AfterUnregistration() public { - harness.registerInterface(IERC721_INTERFACE_ID); - harness.forceSetInterface(IERC721_INTERFACE_ID, false); - - assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); - } - - /** - * Library Does Not Check Caller Tests - */ - - function test_LibraryDoesNotCheckMsgSender() public { - /** - * The library doesn't check msg.sender - that's the facet's responsibility - */ - /** - * This test verifies the library works regardless of caller - */ - /** - * (In production, the facet should check permissions before calling the library) - */ - - address alice = makeAddr("alice"); - - vm.prank(alice); /** - * Not any special address - */ - harness.registerInterface(IERC721_INTERFACE_ID); - assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * This shows the library itself doesn't enforce access control - */ - /** - * Access control should be implemented in the facet that uses the library - */ - } - - /** - * Fuzz Tests - */ - - function testFuzz_RegisterInterface(bytes4 interfaceId) public { - harness.registerInterface(interfaceId); - assertTrue(harness.supportsInterface(interfaceId)); - } - - function testFuzz_RegisterAndUnregister(bytes4 interfaceId) public { - harness.registerInterface(interfaceId); - assertTrue(harness.supportsInterface(interfaceId)); - - harness.forceSetInterface(interfaceId, false); - assertFalse(harness.supportsInterface(interfaceId)); - } - - function testFuzz_MultipleRegistrations(bytes4[] calldata interfaceIds) public { - vm.assume(interfaceIds.length > 0 && interfaceIds.length <= 20); - - /** - * Register all - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - harness.registerInterface(interfaceIds[i]); - } - - /** - * Verify all registered - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - assertTrue(harness.supportsInterface(interfaceIds[i])); - } - } - - function testFuzz_StorageConsistency(bytes4 interfaceId, bool shouldSupport) public { - harness.forceSetInterface(interfaceId, shouldSupport); - - assertEq(harness.supportsInterface(interfaceId), harness.getStorageValue(interfaceId)); - assertEq(harness.supportsInterface(interfaceId), shouldSupport); - } - - function testFuzz_RegisterMultipleInterfaces(bytes4[] calldata interfaceIds) public { - vm.assume(interfaceIds.length > 0 && interfaceIds.length <= 50); - - harness.registerMultipleInterfaces(interfaceIds); - - /** - * Verify all registered - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - assertTrue(harness.supportsInterface(interfaceIds[i])); - } - } - - function testFuzz_MixedOperations(bytes4 interfaceId1, bytes4 interfaceId2, bytes4 interfaceId3) public { - /** - * Ensure unique interface IDs for this test - */ - vm.assume(interfaceId1 != interfaceId2); - vm.assume(interfaceId1 != interfaceId3); - vm.assume(interfaceId2 != interfaceId3); - - /** - * Register first two - */ - harness.registerInterface(interfaceId1); - harness.registerInterface(interfaceId2); - - assertTrue(harness.supportsInterface(interfaceId1)); - assertTrue(harness.supportsInterface(interfaceId2)); - - /** - * Unregister first - */ - harness.forceSetInterface(interfaceId1, false); - - assertFalse(harness.supportsInterface(interfaceId1)); - assertTrue(harness.supportsInterface(interfaceId2)); - - /** - * Register third - */ - harness.registerInterface(interfaceId3); - - assertFalse(harness.supportsInterface(interfaceId1)); - assertTrue(harness.supportsInterface(interfaceId2)); - assertTrue(harness.supportsInterface(interfaceId3)); - } - - function testFuzz_IdempotentRegistration(bytes4 interfaceId, uint8 registrationCount) public { - vm.assume(registrationCount > 0 && registrationCount <= 10); - - /** - * Register multiple times - */ - for (uint256 i = 0; i < registrationCount; i++) { - harness.registerInterface(interfaceId); - } - - /** - * Should still be supported - */ - assertTrue(harness.supportsInterface(interfaceId)); - } - - function testFuzz_CallerIndependence(address caller, bytes4 interfaceId) public { - vm.prank(caller); - harness.registerInterface(interfaceId); - assertTrue(harness.supportsInterface(interfaceId)); - } - - /** - * Gas Optimization Tests - */ - - function test_Gas_RegisterInterface() public { - uint256 gasBefore = gasleft(); - harness.registerInterface(IERC721_INTERFACE_ID); - uint256 gasUsed = gasBefore - gasleft(); - - /** - * Log gas usage for reference - */ - console2.log("Gas used for registerInterface:", gasUsed); - } - - function test_Gas_SupportsInterface() public { - harness.registerInterface(IERC721_INTERFACE_ID); - - uint256 gasBefore = gasleft(); - harness.supportsInterface(IERC721_INTERFACE_ID); - uint256 gasUsed = gasBefore - gasleft(); - - /** - * Log gas usage for reference - */ - console2.log("Gas used for supportsInterface:", gasUsed); - } - - function test_Gas_RegisterMultipleInterfaces() public { - bytes4[] memory interfaceIds = new bytes4[](10); - for (uint256 i = 0; i < 10; i++) { - interfaceIds[i] = bytes4(uint32(i + 1)); - } - - uint256 gasBefore = gasleft(); - harness.registerMultipleInterfaces(interfaceIds); - uint256 gasUsed = gasBefore - gasleft(); - - /** - * Log gas usage for reference - */ - console2.log("Gas used for registerMultipleInterfaces (10 interfaces):", gasUsed); - } -} diff --git a/test/interfaceDetection/ERC165/ERC165Facet.t.sol b/test/interfaceDetection/ERC165/ERC165Facet.t.sol deleted file mode 100644 index c54a171f..00000000 --- a/test/interfaceDetection/ERC165/ERC165Facet.t.sol +++ /dev/null @@ -1,381 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test, console2} from "forge-std/Test.sol"; -import {ERC165Facet, IERC165} from "../../../src/interfaceDetection/ERC165/ERC165Facet.sol"; -import {ERC165FacetHarness} from "./harnesses/ERC165FacetHarness.sol"; - -contract ERC165FacetTest is Test { - ERC165FacetHarness public erc165Facet; - - /** - * Test interface IDs - */ - bytes4 constant IERC165_INTERFACE_ID = type(IERC165).interfaceId; - bytes4 constant IERC721_INTERFACE_ID = 0x80ac58cd; - bytes4 constant IERC20_INTERFACE_ID = 0x36372b07; - bytes4 constant INVALID_INTERFACE_ID = 0xffffffff; - bytes4 constant CUSTOM_INTERFACE_ID = 0x12345678; - bytes4 constant ZERO_INTERFACE_ID = 0x00000000; - - function setUp() public { - erc165Facet = new ERC165FacetHarness(); - erc165Facet.initialize(); - } - - /** - * ERC165 Interface Support Tests - */ - - function test_SupportsInterface_ERC165() public view { - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_SupportsInterface_ERC165_AlwaysReturnsTrue() public view { - /** - * Even without registration, ERC165 interface should be supported - */ - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_SupportsInterface_InvalidInterface() public view { - /** - * 0xffffffff should return false per ERC-165 spec - */ - assertFalse(erc165Facet.supportsInterface(INVALID_INTERFACE_ID)); - } - - function test_SupportsInterface_UnregisteredInterface() public view { - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertFalse(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); - } - - function test_SupportsInterface_ZeroInterfaceId() public view { - assertFalse(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); - } - - /** - * Interface Registration Tests - */ - - function test_RegisterInterface_SingleInterface() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_RegisterInterface_MultipleInterfaces() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - erc165Facet.registerInterface(IERC20_INTERFACE_ID); - erc165Facet.registerInterface(CUSTOM_INTERFACE_ID); - - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); - } - - function test_RegisterInterface_DoesNotAffectERC165() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - /** - * ERC165 should still be supported - */ - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_RegisterInterface_CanRegisterZeroInterfaceId() public { - erc165Facet.registerInterface(ZERO_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); - } - - function test_RegisterInterface_CanRegisterInvalidInterfaceId() public { - /** - * While 0xffffffff should return false per spec, we can still register it - */ - erc165Facet.registerInterface(INVALID_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(INVALID_INTERFACE_ID)); - } - - function test_RegisterInterface_Idempotent() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Register again - */ - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - /** - * Interface Unregistration Tests - */ - - function test_UnregisterInterface_RemovesSupport() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_UnregisterInterface_DoesNotAffectOtherInterfaces() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - erc165Facet.registerInterface(IERC20_INTERFACE_ID); - - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_UnregisterInterface_CannotUnregisterERC165() public { - /** - * Even if we try to unregister ERC165, it should still return true - */ - erc165Facet.unregisterInterface(IERC165_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function test_UnregisterInterface_Idempotent() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister again - */ - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - /** - * Storage Tests - */ - - function test_StorageSlot_UsesCorrectPosition() public view { - bytes32 expectedSlot = keccak256("erc165"); - assertEq(erc165Facet.exposedGetStorage(), expectedSlot); - } - - function test_StorageSlot_Consistency() public { - bytes32 expectedSlot = keccak256("erc165"); - - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - - /** - * Read directly from storage - */ - /** - * The mapping slot is calculated as keccak256(abi.encode(key, slot)) - */ - bytes32 mappingSlot = keccak256(abi.encode(IERC721_INTERFACE_ID, expectedSlot)); - bytes32 storedValue = vm.load(address(erc165Facet), mappingSlot); - - /** - * Should be true (1) - */ - assertEq(uint256(storedValue), 1); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_GetStorageValue_MatchesSupportsInterface() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - - bool supportsResult = erc165Facet.supportsInterface(IERC721_INTERFACE_ID); - bool storageResult = erc165Facet.getStorageValue(IERC721_INTERFACE_ID); - - assertEq(supportsResult, storageResult); - } - - /** - * Edge Cases - */ - - function test_MultipleRegistrationAndUnregistration() public { - /** - * Register - */ - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister - */ - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Register again - */ - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - - /** - * Unregister again - */ - erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); - assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - } - - function test_MixedInterfaceOperations() public { - /** - * Register multiple - */ - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - erc165Facet.registerInterface(IERC20_INTERFACE_ID); - erc165Facet.registerInterface(CUSTOM_INTERFACE_ID); - - /** - * Verify all registered - */ - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); - - /** - * Unregister one - */ - erc165Facet.unregisterInterface(IERC20_INTERFACE_ID); - - /** - * Verify state - */ - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); - - /** - * Register a new one - */ - erc165Facet.registerInterface(ZERO_INTERFACE_ID); - - /** - * Verify final state - */ - assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); - assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - /** - * Fuzz Tests - */ - - function testFuzz_SupportsInterface_ERC165AlwaysTrue(bytes4) public view { - /** - * ERC165 should always return true regardless of what else is registered - */ - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function testFuzz_RegisterInterface(bytes4 interfaceId) public { - erc165Facet.registerInterface(interfaceId); - - if (interfaceId == IERC165_INTERFACE_ID) { - /** - * ERC165 is always supported - */ - assertTrue(erc165Facet.supportsInterface(interfaceId)); - } else { - /** - * Other interfaces should be supported after registration - */ - assertTrue(erc165Facet.supportsInterface(interfaceId)); - } - } - - function testFuzz_UnregisterInterface(bytes4 interfaceId) public { - vm.assume(interfaceId != IERC165_INTERFACE_ID); - - erc165Facet.registerInterface(interfaceId); - assertTrue(erc165Facet.supportsInterface(interfaceId)); - - erc165Facet.unregisterInterface(interfaceId); - assertFalse(erc165Facet.supportsInterface(interfaceId)); - } - - function testFuzz_RegisterAndUnregisterMultiple(bytes4[] calldata interfaceIds) public { - vm.assume(interfaceIds.length > 0 && interfaceIds.length <= 20); - - /** - * Register all - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - erc165Facet.registerInterface(interfaceIds[i]); - } - - /** - * Verify all registered - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - assertTrue(erc165Facet.supportsInterface(interfaceIds[i])); - } - - /** - * Unregister all (except ERC165) - */ - for (uint256 i = 0; i < interfaceIds.length; i++) { - if (interfaceIds[i] != IERC165_INTERFACE_ID) { - erc165Facet.unregisterInterface(interfaceIds[i]); - assertFalse(erc165Facet.supportsInterface(interfaceIds[i])); - } - } - - /** - * ERC165 should still be supported - */ - assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); - } - - function testFuzz_StorageConsistency(bytes4 interfaceId, bool shouldSupport) public { - vm.assume(interfaceId != IERC165_INTERFACE_ID); - - if (shouldSupport) { - erc165Facet.registerInterface(interfaceId); - } else { - erc165Facet.unregisterInterface(interfaceId); - } - - assertEq(erc165Facet.supportsInterface(interfaceId), erc165Facet.getStorageValue(interfaceId)); - } - - /** - * Gas Optimization Tests - */ - - function test_Gas_SupportsInterface_ERC165() public view { - /** - * Should use less than 30,000 gas per ERC-165 spec - */ - uint256 gasBefore = gasleft(); - erc165Facet.supportsInterface(IERC165_INTERFACE_ID); - uint256 gasUsed = gasBefore - gasleft(); - - assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); - } - - function test_Gas_SupportsInterface_RegisteredInterface() public { - erc165Facet.registerInterface(IERC721_INTERFACE_ID); - - uint256 gasBefore = gasleft(); - erc165Facet.supportsInterface(IERC721_INTERFACE_ID); - uint256 gasUsed = gasBefore - gasleft(); - - assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); - } - - function test_Gas_SupportsInterface_UnregisteredInterface() public view { - uint256 gasBefore = gasleft(); - erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID); - uint256 gasUsed = gasBefore - gasleft(); - - assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); - } -} diff --git a/test/interfaceDetection/ERC165/harnesses/ERC165FacetHarness.sol b/test/interfaceDetection/ERC165/harnesses/ERC165FacetHarness.sol deleted file mode 100644 index c3f11649..00000000 --- a/test/interfaceDetection/ERC165/harnesses/ERC165FacetHarness.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC165Facet} from "../../../../src/interfaceDetection/ERC165/ERC165Facet.sol"; - -/** - * @title ERC165Facet Test Harness - */ -/** - * @notice Extends ERC165Facet with initialization and test-specific functions - */ -contract ERC165FacetHarness is ERC165Facet { - /** - * @notice Initialize the ERC165 storage for testing - */ - /** - * @dev This function is only for testing purposes - */ - function initialize() external { - /** - * No initialization needed for basic ERC165 - */ - /** - * Storage is automatically available - */ - } - - /** - * @notice Register an interface for testing - */ - /** - * @dev Exposes internal storage manipulation for testing - */ - function registerInterface(bytes4 _interfaceId) external { - ERC165Storage storage s = getStorage(); - s.supportedInterfaces[_interfaceId] = true; - } - - /** - * @notice Unregister an interface for testing - */ - /** - * @dev Exposes internal storage manipulation for testing - */ - function unregisterInterface(bytes4 _interfaceId) external { - ERC165Storage storage s = getStorage(); - s.supportedInterfaces[_interfaceId] = false; - } - - /** - * @notice Get the raw storage value for an interface (for testing storage consistency) - */ - function getStorageValue(bytes4 _interfaceId) external view returns (bool) { - return getStorage().supportedInterfaces[_interfaceId]; - } - - /** - * @notice Expose getStorage for testing - */ - function exposedGetStorage() external pure returns (bytes32) { - return STORAGE_POSITION; - } -} diff --git a/test/token/ERC20/ERC20/ERC20BurnFacet b/test/token/ERC20/ERC20/ERC20BurnFacet deleted file mode 100644 index c28ebb35..00000000 --- a/test/token/ERC20/ERC20/ERC20BurnFacet +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC20BurnFacet} from "src/token/ERC20/ERC20/ERC20BurnFacet.sol"; -import {ERC20BurnFacetHarness} from "./harnesses/ERC20BurnFacetHarness.sol"; - -contract ERC20BurnFacetTest is Test { - ERC20BurnFacetHarness public token; - - address public alice; - address public bob; - address public charlie; - - uint256 constant INITIAL_SUPPLY = 1000000e18; - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - token = new ERC20BurnFacetHarness(); - token.mint(alice, INITIAL_SUPPLY); - } - - function test_Burn() public { - uint256 amount = 100e18; - - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, address(0), amount); - token.burn(amount); - - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - amount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - amount); - } - - function test_Burn_EntireBalance() public { - vm.prank(alice); - token.burn(INITIAL_SUPPLY); - - assertEq(token.balanceOf(alice), 0); - assertEq(token.totalSupply(), 0); - } - - function testFuzz_Burn(uint256 amount) public { - vm.assume(amount <= INITIAL_SUPPLY); - - vm.prank(alice); - token.burn(amount); - - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - amount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - amount); - } - - function test_RevertWhen_BurnInsufficientBalance() public { - uint256 amount = INITIAL_SUPPLY + 1; - - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientBalance.selector, alice, INITIAL_SUPPLY, amount) - ); - token.burn(amount); - } - - function test_RevertWhen_BurnFromZeroBalance() public { - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientBalance.selector, bob, 0, 1)); - token.burn(1); - } - - function test_BurnFrom() public { - uint256 amount = 100e18; - - vm.prank(alice); - token.approve(bob, amount); - - vm.prank(bob); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, address(0), amount); - token.burnFrom(alice, amount); - - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - amount); - assertEq(token.allowance(alice, bob), 0); - assertEq(token.totalSupply(), INITIAL_SUPPLY - amount); - } - - function test_BurnFrom_PartialAllowance() public { - uint256 allowanceAmount = 200e18; - uint256 burnAmount = 100e18; - - vm.prank(alice); - token.approve(bob, allowanceAmount); - - vm.prank(bob); - token.burnFrom(alice, burnAmount); - - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - burnAmount); - assertEq(token.allowance(alice, bob), allowanceAmount - burnAmount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - burnAmount); - } - - function testFuzz_BurnFrom(uint256 approval, uint256 amount) public { - vm.assume(approval <= INITIAL_SUPPLY); - vm.assume(amount <= approval); - - vm.prank(alice); - token.approve(bob, approval); - - vm.prank(bob); - token.burnFrom(alice, amount); - - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - amount); - assertEq(token.allowance(alice, bob), approval - amount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - amount); - } - - function test_BurnFrom_UnlimitedAllowance() public { - uint256 amount = 100e18; - uint256 maxAllowance = type(uint256).max; - - /** - * Set unlimited allowance - */ - vm.prank(alice); - token.approve(bob, maxAllowance); - - /** - * Perform first burn - */ - vm.prank(bob); - token.burnFrom(alice, amount); - - /** - * Check that allowance remains unchanged (unlimited) - */ - assertEq(token.allowance(alice, bob), maxAllowance); - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - amount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - amount); - - /** - * Perform second burn to verify allowance is still unlimited - */ - vm.prank(bob); - token.burnFrom(alice, amount); - - /** - * Check that allowance is still unchanged (unlimited) - */ - assertEq(token.allowance(alice, bob), maxAllowance); - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - 2 * amount); - assertEq(token.totalSupply(), INITIAL_SUPPLY - 2 * amount); - } - - function test_BurnFrom_UnlimitedAllowance_MultipleBurns() public { - uint256 maxAllowance = type(uint256).max; - uint256 burnAmount = 50e18; - uint256 numBurns = 10; - - /** - * Set unlimited allowance - */ - vm.prank(alice); - token.approve(bob, maxAllowance); - - /** - * Perform multiple burns - */ - for (uint256 i = 0; i < numBurns; i++) { - vm.prank(bob); - token.burnFrom(alice, burnAmount); - - /** - * Verify allowance remains unlimited after each burn - */ - assertEq(token.allowance(alice, bob), maxAllowance); - } - - /** - * Verify final balances and total supply - */ - assertEq(token.balanceOf(alice), INITIAL_SUPPLY - (burnAmount * numBurns)); - assertEq(token.totalSupply(), INITIAL_SUPPLY - (burnAmount * numBurns)); - } - - function test_RevertWhen_BurnFromInsufficientAllowance() public { - uint256 allowanceAmount = 50e18; - uint256 burnAmount = 100e18; - - vm.prank(alice); - token.approve(bob, allowanceAmount); - - vm.prank(bob); - vm.expectRevert( - abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientAllowance.selector, bob, allowanceAmount, burnAmount) - ); - token.burnFrom(alice, burnAmount); - } - - function test_RevertWhen_BurnFromInsufficientBalance() public { - uint256 amount = INITIAL_SUPPLY + 1; - - vm.prank(alice); - token.approve(bob, amount); - - vm.prank(bob); - vm.expectRevert( - abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientBalance.selector, alice, INITIAL_SUPPLY, amount) - ); - token.burnFrom(alice, amount); - } - - function test_RevertWhen_BurnFromNoAllowance() public { - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC20BurnFacet.ERC20InsufficientAllowance.selector, bob, 0, 100e18)); - token.burnFrom(alice, 100e18); - } -} diff --git a/test/token/ERC20/ERC20/ERC20PermitFacet b/test/token/ERC20/ERC20/ERC20PermitFacet deleted file mode 100644 index cdf6e98e..00000000 --- a/test/token/ERC20/ERC20/ERC20PermitFacet +++ /dev/null @@ -1,532 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC20PermitFacet} from "src/token/ERC20/ERC20Permit/ERC20PermitFacet.sol"; -import {ERC20PermitFacetHarness} from "./harnesses/ERC20PermitFacetHarness.sol"; - -contract ERC20BurnFacetTest is Test { - ERC20PermitFacetHarness public token; - - address public alice; - address public bob; - address public charlie; - - string constant TOKEN_NAME = "Test Token"; - uint256 constant INITIAL_SUPPLY = 1000000e18; - - event Transfer(address indexed _from, address indexed _to, uint256 _value); - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - token = new ERC20PermitFacetHarness(); - token.initialize(TOKEN_NAME); - token.mint(alice, INITIAL_SUPPLY); - } - - function test_Nonces() public view { - assertEq(token.nonces(alice), 0); - assertEq(token.nonces(bob), 0); - } - - function test_DOMAIN_SEPARATOR() public view { - bytes32 expectedDomainSeparator = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(TOKEN_NAME)), - keccak256("1"), - block.chainid, - address(token) - ) - ); - assertEq(token.DOMAIN_SEPARATOR(), expectedDomainSeparator); - } - - function test_DOMAIN_SEPARATOR_ConsistentWithinSameChain() public view { - /** - * First call - computes domain separator - */ - bytes32 separator1 = token.DOMAIN_SEPARATOR(); - - /** - * Second call - recomputes and should return same value for same chain ID - */ - bytes32 separator2 = token.DOMAIN_SEPARATOR(); - - assertEq(separator1, separator2); - } - - function test_DOMAIN_SEPARATOR_RecalculatesAfterFork() public { - /** - * Get initial domain separator on chain 1 - */ - uint256 originalChainId = block.chainid; - bytes32 separator1 = token.DOMAIN_SEPARATOR(); - - /** - * Simulate chain fork (chain ID changes) - */ - vm.chainId(originalChainId + 1); - - /** - * Domain separator should recalculate with new chain ID - */ - bytes32 separator2 = token.DOMAIN_SEPARATOR(); - - /** - * Separators should be different - */ - assertTrue(separator1 != separator2); - - /** - * New separator should match expected value for new chain ID - */ - bytes32 expectedSeparator = keccak256( - abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(TOKEN_NAME)), - keccak256("1"), - originalChainId + 1, - address(token) - ) - ); - assertEq(separator2, expectedSeparator); - } - - function test_Permit() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - address spender = bob; - uint256 value = 100e18; - uint256 nonce = 0; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - spender, - value, - nonce, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - vm.expectEmit(true, true, true, true); - emit Approval(owner, spender, value); - token.permit(owner, spender, value, deadline, v, r, s); - - assertEq(token.allowance(owner, spender), value); - assertEq(token.nonces(owner), 1); - } - - function test_Permit_IncreasesNonce() public { - uint256 ownerPrivateKey = 0xB0B; - address owner = vm.addr(ownerPrivateKey); - uint256 deadline = block.timestamp + 1 hours; - - for (uint256 i = 0; i < 3; i++) { - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - 100e18, - i, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, 100e18, deadline, v, r, s); - assertEq(token.nonces(owner), i + 1); - } - } - - function test_RevertWhen_PermitExpired() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 100e18; - uint256 deadline = block.timestamp - 1; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, r, s - ) - ); - token.permit(owner, bob, value, deadline, v, r, s); - } - - function test_RevertWhen_PermitInvalidSignature() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 wrongPrivateKey = 0xBAD; - uint256 value = 100e18; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongPrivateKey, hash); - - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, r, s - ) - ); - token.permit(owner, bob, value, deadline, v, r, s); - } - - function test_RevertWhen_PermitReplay() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 100e18; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, value, deadline, v, r, s); - - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, r, s - ) - ); - token.permit(owner, bob, value, deadline, v, r, s); - } - - function test_RevertWhen_PermitZeroAddressSpender() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 100e18; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - address(0), - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - vm.expectRevert(abi.encodeWithSelector(ERC20PermitFacet.ERC20InvalidSpender.selector, address(0))); - token.permit(owner, address(0), value, deadline, v, r, s); - } - - function test_Permit_MaxValue() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = type(uint256).max; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, value, deadline, v, r, s); - - assertEq(token.allowance(owner, bob), type(uint256).max); - assertEq(token.nonces(owner), 1); - } - - function test_Permit_ThenTransferFrom() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 permitValue = 500e18; - uint256 transferAmount = 300e18; - uint256 deadline = block.timestamp + 1 hours; - - token.mint(owner, 1000e18); - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - permitValue, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, permitValue, deadline, v, r, s); - - uint256 ownerBalanceBefore = token.balanceOf(owner); - - vm.prank(bob); - token.transferFrom(owner, charlie, transferAmount); - - assertEq(token.balanceOf(owner), ownerBalanceBefore - transferAmount); - assertEq(token.balanceOf(charlie), transferAmount); - assertEq(token.allowance(owner, bob), permitValue - transferAmount); - } - - function test_RevertWhen_PermitWrongNonce() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 100e18; - uint256 wrongNonce = 99; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - wrongNonce, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, r, s - ) - ); - token.permit(owner, bob, value, deadline, v, r, s); - } - - function test_Permit_ZeroValue() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 0; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, value, deadline, v, r, s); - - assertEq(token.allowance(owner, bob), 0); - assertEq(token.nonces(owner), 1); - } - - function test_Permit_MultipleDifferentSpenders() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 deadline = block.timestamp + 1 hours; - - address[] memory spenders = new address[](3); - spenders[0] = bob; - spenders[1] = charlie; - spenders[2] = makeAddr("dave"); - - uint256[] memory values = new uint256[](3); - values[0] = 100e18; - values[1] = 200e18; - values[2] = 300e18; - - for (uint256 i = 0; i < spenders.length; i++) { - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - spenders[i], - values[i], - i, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, spenders[i], values[i], deadline, v, r, s); - assertEq(token.allowance(owner, spenders[i]), values[i]); - } - - assertEq(token.nonces(owner), 3); - } - - function test_Permit_OverwritesExistingAllowance() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 deadline = block.timestamp + 1 hours; - - token.mint(owner, 1000e18); - - vm.prank(owner); - token.approve(bob, 100e18); - assertEq(token.allowance(owner, bob), 100e18); - - uint256 newValue = 500e18; - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - newValue, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - token.permit(owner, bob, newValue, deadline, v, r, s); - - assertEq(token.allowance(owner, bob), newValue); - } - - function test_RevertWhen_PermitMalformedSignature() public { - uint256 ownerPrivateKey = 0xA11CE; - address owner = vm.addr(ownerPrivateKey); - uint256 value = 100e18; - uint256 deadline = block.timestamp + 1 hours; - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - bob, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, hash); - - /** - * Test with invalid v value (should be 27 or 28) - */ - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, 99, r, s - ) - ); - token.permit(owner, bob, value, deadline, 99, r, s); - - /** - * Test with zero r value - */ - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, bytes32(0), s - ) - ); - token.permit(owner, bob, value, deadline, v, bytes32(0), s); - - /** - * Test with zero s value - */ - vm.expectRevert( - abi.encodeWithSelector( - ERC20PermitFacet.ERC2612InvalidSignature.selector, owner, bob, value, deadline, v, r, bytes32(0) - ) - ); - token.permit(owner, bob, value, deadline, v, r, bytes32(0)); - } - - function testFuzz_Permit(uint256 ownerKey, address spender, uint256 value, uint256 deadline) public { - vm.assume(ownerKey != 0 && ownerKey < type(uint256).max / 2); - vm.assume(spender != address(0)); - vm.assume(deadline > block.timestamp); - - address owner = vm.addr(ownerKey); - - bytes32 structHash = keccak256( - abi.encode( - keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"), - owner, - spender, - value, - 0, - deadline - ) - ); - - bytes32 hash = keccak256(abi.encodePacked("\x19\x01", token.DOMAIN_SEPARATOR(), structHash)); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerKey, hash); - - token.permit(owner, spender, value, deadline, v, r, s); - - assertEq(token.allowance(owner, spender), value); - assertEq(token.nonces(owner), 1); - } -} diff --git a/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness b/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness deleted file mode 100644 index c117dae9..00000000 --- a/test/token/ERC20/ERC20/harnesses/ERC20BurnFacetHarness +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC20BurnFacet} from "src/token/ERC20/ERC20/ERC20BurnFacet.sol"; - -/** - * @title ERC20BurnFacetHarness - * @notice Test harness for ERC20BurnFacet that adds initialization and minting for testing - */ -contract ERC20BurnFacetHarness is ERC20BurnFacet { - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - - /** - * @notice ERC20 view helpers so tests can call the standard API - */ - function balanceOf(address _account) external view returns (uint256) { - return getStorage().balanceOf[_account]; - } - - function totalSupply() external view returns (uint256) { - return getStorage().totalSupply; - } - - function allowance(address _owner, address _spender) external view returns (uint256) { - return getStorage().allowance[_owner][_spender]; - } - - /** - * @notice Minimal approve implementation for tests (writes into the same storage used by burnFrom) - */ - function approve(address _spender, uint256 _value) external returns (bool) { - require(_spender != address(0), "ERC20: approve to zero address"); - ERC20TransferStorage storage s = getStorage(); - s.allowance[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - /** - * @notice Mint tokens to an address - * @dev Only used for testing - exposes internal mint functionality - */ - function mint(address _to, uint256 _value) external { - ERC20TransferStorage storage s = getStorage(); - require(_to != address(0), "ERC20: mint to zero address"); - unchecked { - s.totalSupply += _value; - s.balanceOf[_to] += _value; - } - emit Transfer(address(0), _to, _value); - } -} diff --git a/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness b/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness deleted file mode 100644 index e06ee93b..00000000 --- a/test/token/ERC20/ERC20/harnesses/ERC20PermitFacetHarness +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC20PermitFacet} from "src/token/ERC20/ERC20Permit/ERC20PermitFacet.sol"; - -/** - * @title ERC20PermitFacetHarness - * @notice Test harness for ERC20PermitFacet that adds initialization and minting for testing - */ -contract ERC20PermitFacetHarness is ERC20PermitFacet { - event Transfer(address indexed _from, address indexed _to, uint256 _value); - - /** - * @notice Initialize the ERC20 token storage - * @dev Only used for testing - production diamonds should initialize in constructor - */ - function initialize(string memory _name) external { - ERC20MetadataStorage storage s = getERC20MetadataStorage(); - s.name = _name; - } - - /** - * @notice Mint tokens to an address - * @dev Only used for testing - exposes internal mint functionality - */ - function mint(address _to, uint256 _value) external { - ERC20TransferStorage storage s = getERC20TransferStorage(); - require(_to != address(0), "ERC20: mint to zero address"); - unchecked { - s.totalSupply += _value; - s.balanceOf[_to] += _value; - } - emit Transfer(address(0), _to, _value); - } - - /** - * @notice ERC20 view helpers so tests can call the standard API - */ - function balanceOf(address _account) external view returns (uint256) { - return getERC20TransferStorage().balanceOf[_account]; - } - - function totalSupply() external view returns (uint256) { - return getERC20TransferStorage().totalSupply; - } - - function allowance(address _owner, address _spender) external view returns (uint256) { - return getERC20TransferStorage().allowance[_owner][_spender]; - } - - /** - * @notice Minimal approve implementation for tests - */ - function approve(address _spender, uint256 _value) external returns (bool) { - require(_spender != address(0), "ERC20: approve to zero address"); - ERC20TransferStorage storage s = getERC20TransferStorage(); - s.allowance[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } - - /** - * @notice TransferFrom implementation for tests (needed by test_Permit_ThenTransferFrom) - */ - function transferFrom(address _from, address _to, uint256 _value) external returns (bool) { - ERC20TransferStorage storage s = getERC20TransferStorage(); - require(_to != address(0), "ERC20: transfer to zero address"); - require(s.balanceOf[_from] >= _value, "ERC20: insufficient balance"); - - uint256 currentAllowance = s.allowance[_from][msg.sender]; - require(currentAllowance >= _value, "ERC20: insufficient allowance"); - - unchecked { - s.allowance[_from][msg.sender] = currentAllowance - _value; - s.balanceOf[_from] -= _value; - } - s.balanceOf[_to] += _value; - emit Transfer(_from, _to, _value); - return true; - } -} diff --git a/test/token/ERC20/ERC20Bridgeble/ERC20Bridgeable b/test/token/ERC20/ERC20Bridgeble/ERC20Bridgeable deleted file mode 100644 index d0564678..00000000 --- a/test/token/ERC20/ERC20Bridgeble/ERC20Bridgeable +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import "../../../../src/token/ERC20/ERC20Bridgeable/ERC20BridgeableMod.sol" as ERC20BridgeableMod; -import {ERC20BridgeableHarness} from "./harnesses/ERC20BridgeableHarness.sol"; - -contract LibERC20BridgeableTest is Test { - ERC20BridgeableHarness public token; - - address public alice; - address public bob; - - uint256 constant INITIAL_SUPPLY = 1000000e18; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - - token = new ERC20BridgeableHarness(); - token.setRole(alice, "trusted-bridge", true); - vm.prank(alice); - token.crosschainMint(alice, INITIAL_SUPPLY); - } - - /** - * ====================================== - * CrossChainMint Tests - * ====================================== - */ - - function test_CrossChainMintRevertsInvalidCaller(address to, uint256 amount, address invalidCaller) public { - vm.assume(to != address(0)); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.assume(invalidCaller != alice); - vm.prank(invalidCaller); - vm.expectRevert( - abi.encodeWithSelector( - ERC20BridgeableMod.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge") - ) - ); - token.crosschainMint(to, amount); - } - - function test_CrossChainMintRevertsInvalidReceiver(uint256 amount) public { - address to = address(0); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableMod.ERC20InvalidReciever.selector, to)); - vm.prank(alice); - token.crosschainMint(to, amount); - } - - function test_CrossChainMint() public { - vm.prank(alice); - token.crosschainMint(bob, 500e18); - assertEq(token.balanceOf(bob), 500e18); - } - - /** - * ====================================== - * CrossChainBurn Tests - * ====================================== - */ - - function test_CrossChainBurnRevertsInvalidCaller(address from, uint256 amount, address invalidCaller) public { - vm.assume(from != address(0)); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.assume(invalidCaller != alice); - vm.prank(alice); - token.crosschainMint(from, amount); - vm.prank(invalidCaller); - vm.expectRevert( - abi.encodeWithSelector( - ERC20BridgeableMod.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge") - ) - ); - token.crosschainBurn(from, amount); - } - - function test_CrossChainBurnRevertsInvalidFrom(uint256 amount) public { - address from = address(0); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableMod.ERC20InvalidReciever.selector, from)); - token.crosschainBurn(from, amount); - } - - function test_CrossChainBurn() public { - vm.prank(alice); - token.crosschainMint(bob, 500e18); - assertEq(token.balanceOf(bob), 500e18); - - vm.prank(alice); - token.crosschainBurn(bob, 200e18); - assertEq(token.balanceOf(bob), 300e18); - } - - /** - * ====================================== - * checkTokenBridge Tests - * ====================================== - */ - - function test_CheckTokenBridgeSucceedsValidCaller() public { - vm.prank(alice); - token.checkTokenBridge(alice); - } - - function test_CheckTokenBridgeRevertsInvalidCaller(address invalidCaller) public { - vm.assume(invalidCaller != alice); - vm.assume(invalidCaller != address(0)); - vm.prank(invalidCaller); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableMod.ERC20InvalidBridgeAccount.selector, invalidCaller)); - token.checkTokenBridge(invalidCaller); - } - - function test_CheckTokenBridgeRevertsZeroCaller() public { - address zeroAddress = address(0); - vm.prank(zeroAddress); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableMod.ERC20InvalidBridgeAccount.selector, zeroAddress)); - token.checkTokenBridge(zeroAddress); - } -} diff --git a/test/token/ERC20/ERC20Bridgeble/ERC20BridgeableFacet b/test/token/ERC20/ERC20Bridgeble/ERC20BridgeableFacet deleted file mode 100644 index d0ee2f94..00000000 --- a/test/token/ERC20/ERC20Bridgeble/ERC20BridgeableFacet +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC20BridgeableFacet} from "../../../../src/token/ERC20/ERC20Bridgeable/ERC20BridgeableFacet.sol"; -import {ERC20BridgeableHarness} from "./harnesses/ERC20BridgeableHarness.sol"; - -contract ERC20BridgeableFacetTest is Test { - ERC20BridgeableHarness public token; - - address public alice; - address public bob; - - uint256 constant INITIAL_SUPPLY = 1000000e18; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - - token = new ERC20BridgeableHarness(); - token.setRole(alice, "trusted-bridge", true); - vm.prank(alice); - token.crosschainMint(alice, INITIAL_SUPPLY); - } - - /** - * ====================================== - * CrossChainMint Tests - * ====================================== - */ - - function test_CrossChainMintRevertsInvalidCaller(address to, uint256 amount, address invalidCaller) public { - vm.assume(to != address(0)); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.assume(invalidCaller != alice); - vm.prank(invalidCaller); - vm.expectRevert( - abi.encodeWithSelector( - ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge") - ) - ); - token.crosschainMint(to, amount); - } - - function test_CrossChainMintRevertsInvalidReceiver(uint256 amount) public { - address to = address(0); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReciever.selector, to)); - vm.prank(alice); - token.crosschainMint(to, amount); - } - - function test_CrossChainMint() public { - vm.prank(alice); - token.crosschainMint(bob, 500e18); - assertEq(token.balanceOf(bob), 500e18); - } - - /** - * ====================================== - * CrossChainBurn Tests - * ====================================== - */ - - function test_CrossChainBurnRevertsInvalidCaller(address from, uint256 amount, address invalidCaller) public { - vm.assume(from != address(0)); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.assume(invalidCaller != alice); - vm.prank(alice); - token.crosschainMint(from, amount); - vm.prank(invalidCaller); - vm.expectRevert( - abi.encodeWithSelector( - ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge") - ) - ); - token.crosschainBurn(from, amount); - } - - function test_CrossChainBurnRevertsInvalidFrom(uint256 amount) public { - address from = address(0); - vm.assume(amount > 0 && amount < INITIAL_SUPPLY); - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReciever.selector, from)); - token.crosschainBurn(from, amount); - } - - function test_CrossChainBurn() public { - vm.prank(alice); - token.crosschainMint(bob, 500e18); - assertEq(token.balanceOf(bob), 500e18); - vm.prank(alice); - token.crosschainBurn(bob, 500e18); - assertEq(token.balanceOf(bob), 0); - } - - /** - * ====================================== - * checkTokenBridge Tests - * ====================================== - */ - - function test_CheckTokenBridgeSucceeds(address _caller) public { - vm.prank(alice); - token.checkTokenBridge(alice); - } - - function test_CheckTokenBridgeReverts(address invalidCaller) public { - vm.assume(invalidCaller != alice); - vm.prank(invalidCaller); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, invalidCaller)); - token.checkTokenBridge(invalidCaller); - } - - function test_CheckTokenBridgeRevertsZeroAddress() public { - address invalidCaller = address(0); - vm.prank(invalidCaller); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, invalidCaller)); - token.checkTokenBridge(invalidCaller); - } - - function test_CheckTokenBridgeSucceedsAfterRevokingRole() public { - token.setRole(alice, "trusted-bridge", false); - vm.prank(alice); - vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, alice)); - token.checkTokenBridge(alice); - } -} diff --git a/test/token/ERC20/ERC20Bridgeble/harnesses/ERC20BridgeableHarness b/test/token/ERC20/ERC20Bridgeble/harnesses/ERC20BridgeableHarness deleted file mode 100644 index 2a68ce97..00000000 --- a/test/token/ERC20/ERC20Bridgeble/harnesses/ERC20BridgeableHarness +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../../src/token/ERC20/ERC20Bridgeable/ERC20BridgeableMod.sol" as ERC20BridgeableMod; - -contract ERC20BridgeableHarness { - /** - * @notice Mints tokens to a specified address on behalf of the "trusted-bridge" role. - * @param _to The address receiving the minted tokens. [NATPSEC: Only trusted bridge callers] - * @param _amount The amount of tokens to mint. [NATPSEC: Only trusted bridge callers] - */ - function crosschainMint(address _to, uint256 _amount) external { - ERC20BridgeableMod.crosschainMint(_to, _amount); - } - - /** - * @notice Returns the token balance of a specified account. - * @param _account The account for which to query the balance. [NATPSEC: Read-only] - * @return The current balance of the account. - */ - function balanceOf(address _account) external view returns (uint256) { - ERC20BridgeableMod.ERC20TransferStorage storage s = ERC20BridgeableMod.getERC20TransferStorage(); - return s.balanceOf[_account]; - } - - /** - * @notice Burns tokens from a specified address on behalf of the "trusted-bridge" role. - * @param _from The address whose tokens will be burned. [NATPSEC: Only trusted bridge callers] - * @param _amount The amount of tokens to burn. [NATPSEC: Only trusted bridge callers] - */ - function crosschainBurn(address _from, uint256 _amount) external { - ERC20BridgeableMod.crosschainBurn(_from, _amount); - } - - /** - * @notice Validates whether the caller has the token bridge role. - * @param _caller The address to check for the "trusted-bridge" role. [NATPSEC: Internal access control] - */ - function checkTokenBridge(address _caller) external view { - ERC20BridgeableMod.checkTokenBridge(_caller); - } - - /** - * @notice Grants or revokes a role for a given account, for harness/testing only. - * @param account The account to grant or revoke the role for. [NATPSEC: Test utility only] - * @param role The bytes32 identifier of the role. [NATPSEC: Test utility only] - * @param value True to grant the role, false to revoke. [NATPSEC: Test utility only] - */ - function setRole(address account, bytes32 role, bool value) external { - ERC20BridgeableMod.AccessControlStorage storage acs = ERC20BridgeableMod.getAccessControlStorage(); - acs.hasRole[account][role] = value; - } -} diff --git a/test/trees/ERC165.tree b/test/trees/ERC165.tree new file mode 100644 index 00000000..9bcd323f --- /dev/null +++ b/test/trees/ERC165.tree @@ -0,0 +1,91 @@ +Data +β”œβ”€β”€ ERC165Interface +β”‚ β”œβ”€β”€ when querying IERC165.interfaceId +β”‚ β”‚ └── it should always return true +β”‚ └── when attempting to unregister IERC165.interfaceId +β”‚ └── it should still return true +β”œβ”€β”€ RegisteredInterfaces +β”‚ β”œβ”€β”€ when an interface has been registered +β”‚ β”‚ └── it should return true for that interfaceId +β”‚ β”œβ”€β”€ when multiple interfaces have been registered +β”‚ β”‚ └── it should return true for each registered interfaceId +β”‚ └── when the same interface is registered multiple times +β”‚ └── it should still return true (idempotent) +β”œβ”€β”€ UnregisteredInterfaces +β”‚ β”œβ”€β”€ when an interface has never been registered +β”‚ β”‚ └── it should return false +β”‚ └── when an interface has been unregistered +β”‚ └── it should return false +└── SpecialInterfaceIds + β”œβ”€β”€ when querying 0xffffffff and it has not been registered + β”‚ └── it should return false per ERC-165 + β”œβ”€β”€ when querying 0x00000000 and it has not been registered + β”‚ └── it should return false + β”œβ”€β”€ when 0xffffffff has been explicitly registered + β”‚ └── it should return true + └── when 0x00000000 has been explicitly registered + └── it should return true + +Register +β”œβ”€β”€ SingleInterface +β”‚ β”œβ”€β”€ when registering a standard interface (e.g. ERC721) +β”‚ β”‚ └── it should mark that interface as supported +β”‚ └── when registering IERC165.interfaceId +β”‚ └── it should remain supported (no regression) +β”œβ”€β”€ MultipleInterfaces +β”‚ β”œβ”€β”€ when registering a list of distinct interfaceIds +β”‚ β”‚ └── it should mark all as supported +β”‚ └── when registering a list containing duplicates +β”‚ └── it should still end up with all interfaces supported +└── CallerIndependence + └── when any caller invokes the library register function + └── it should update interface support without checking msg.sender + +Unregister +β”œβ”€β”€ SingleInterface +β”‚ β”œβ”€β”€ when unregistering a previously registered interface +β”‚ β”‚ └── it should mark that interface as unsupported +β”‚ └── when unregistering an interface twice +β”‚ └── it should remain unsupported (idempotent) +β”œβ”€β”€ EffectIsolation +β”‚ └── when unregistering one interface +β”‚ └── it should not affect support for other interfaces +└── ERC165Sticky + └── when attempting to unregister IERC165.interfaceId + └── it should remain supported + +Storage +β”œβ”€β”€ Slot +β”‚ β”œβ”€β”€ when reading the ERC165 storage slot +β”‚ β”‚ └── it should equal keccak256("erc165") +β”‚ └── when computing the mapping slot for a registered interfaceId +β”‚ └── it should contain 1 at keccak256(abi.encode(interfaceId, slot)) +└── Consistency + └── when comparing supportsInterface to the raw storage value + └── it should return the same boolean + +IntegrationWithDiamond +β”œβ”€β”€ BaseSetup +β”‚ β”œβ”€β”€ when the diamond is deployed with ERC165Facet installed +β”‚ β”‚ └── it should support IERC165.interfaceId via the diamond proxy +β”‚ └── when querying via the diamond before any facet registers extra interfaces +β”‚ └── it should return false for unregistered interfaceIds +β”œβ”€β”€ FeatureFacets +β”‚ β”œβ”€β”€ when an ERC20 facet has registered its interfaceId +β”‚ β”‚ └── the diamond supportsInterface should return true for ERC20 +β”‚ β”œβ”€β”€ when an ERC1155 facet has registered its interfaceId +β”‚ β”‚ └── the diamond supportsInterface should return true for ERC1155 +β”‚ └── when a facet is removed and its interfaceId unregistered +β”‚ └── the diamond supportsInterface should return false for that interfaceId +└── Upgrades + └── when upgrading facets that add or remove interfaceIds + └── supportsInterface should reflect the new set of interfaces + +Gas +β”œβ”€β”€ when calling supportsInterface for IERC165.interfaceId +β”‚ └── it should consume less than 30,000 gas +β”œβ”€β”€ when calling supportsInterface for a registered interfaceId +β”‚ └── it should consume less than 30,000 gas +└── when calling supportsInterface for an unregistered interfaceId + └── it should consume less than 30,000 gas + diff --git a/test/trees/NonReentrancy.tree b/test/trees/NonReentrancy.tree new file mode 100644 index 00000000..d8c56ea5 --- /dev/null +++ b/test/trees/NonReentrancy.tree @@ -0,0 +1,13 @@ +NonReentrancy +β”œβ”€β”€ GuardedIncrement +β”‚ β”œβ”€β”€ when called once +β”‚ β”‚ └── it should increment the counter by 1 +β”‚ └── when called twice sequentially +β”‚ └── it should increment the counter to 2 +β”œβ”€β”€ ReentrancyProtection +β”‚ └── when a guarded function attempts to reenter itself (direct or via external call) +β”‚ └── it should revert with Reentrancy +└── GuardResetOnRevert + └── when a guarded function reverts after enter and before exit + └── it should clear the guard so that future guarded calls succeed + diff --git a/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol b/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol index ad0626f7..914e022c 100644 --- a/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol +++ b/test/unit/access/AccessControl/Admin/mod/fuzz/setRoleAdmin.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {AccessControlAdmin_Base_Test} from "test/unit/access/AccessControl/Admin/AccessControlAdminBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol index 81b4aa38..d5b8f4cb 100644 --- a/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Grant/mod/fuzz/grantRoleBatch.t.sol @@ -11,7 +11,7 @@ import { AccessControlGrantBatch_Base_Test } from "test/unit/access/AccessControl/Batch/Grant/AccessControlGrantBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol index 0293f43f..af922578 100644 --- a/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol +++ b/test/unit/access/AccessControl/Batch/Revoke/mod/fuzz/revokeRoleBatch.t.sol @@ -11,7 +11,7 @@ import { AccessControlRevokeBatch_Base_Test } from "test/unit/access/AccessControl/Batch/Revoke/AccessControlRevokeBatchBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol index 75af5899..097deee3 100644 --- a/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {AccessControlData_Base_Test} from "test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol index 1fd6f2a4..005cf968 100644 --- a/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol +++ b/test/unit/access/AccessControl/Grant/mod/fuzz/grantRole.t.sol @@ -9,7 +9,7 @@ import {Vm} from "forge-std/Vm.sol"; import {AccessControlGrant_Base_Test} from "test/unit/access/AccessControl/Grant/AccessControlGrantBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol index 26654fdf..759b61f7 100644 --- a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {AccessControlPausable_Base_Test} from "test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlPausableModHarness} from "test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol"; +import {AccessControlPausableModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlPausableModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol index 8a8ebe55..bc9efef9 100644 --- a/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol +++ b/test/unit/access/AccessControl/Renounce/mod/fuzz/renounceRole.t.sol @@ -9,7 +9,7 @@ import {Vm} from "forge-std/Vm.sol"; import {AccessControlRenounce_Base_Test} from "test/unit/access/AccessControl/Renounce/AccessControlRenounceBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol b/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol index 46c1d116..bdc4fa0c 100644 --- a/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol +++ b/test/unit/access/AccessControl/Revoke/mod/fuzz/revokeRole.t.sol @@ -9,7 +9,7 @@ import {Vm} from "forge-std/Vm.sol"; import {AccessControlRevoke_Base_Test} from "test/unit/access/AccessControl/Revoke/AccessControlRevokeBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlCoreModHarness} from "test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; +import {AccessControlCoreModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol index 9e80cb63..3867840b 100644 --- a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import { AccessControlTemporalData_Base_Test } from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol index 0f2a32f3..f3668a9a 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol @@ -9,7 +9,7 @@ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorage import { AccessControlTemporalGrant_Base_Test } from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol index 2d601c04..4c738e83 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol @@ -11,7 +11,7 @@ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorage import { AccessControlTemporalRevoke_Base_Test } from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/Owner/Data/mod/fuzz/data.t.sol b/test/unit/access/Owner/Data/mod/fuzz/data.t.sol index b6618d8a..5872df47 100644 --- a/test/unit/access/Owner/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/Owner/Data/mod/fuzz/data.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerData_Base_Test} from "test/unit/access/Owner/Data/OwnerDataBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; +import {OwnerCoreModHarness} from "test/utils/harnesses/access/Owner/OwnerCoreModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol index 5f1ad4b0..7e4cc1d0 100644 --- a/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol +++ b/test/unit/access/Owner/Renounce/mod/fuzz/renounceOwnership.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerRenounce_Base_Test} from "test/unit/access/Owner/Renounce/OwnerRenounceBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; +import {OwnerCoreModHarness} from "test/utils/harnesses/access/Owner/OwnerCoreModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol index a153e900..dc117bd4 100644 --- a/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/Transfer/mod/fuzz/transferOwnership.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerTransfer_Base_Test} from "test/unit/access/Owner/Transfer/OwnerTransferBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerCoreModHarness} from "test/harnesses/access/Owner/OwnerCoreModHarness.sol"; +import {OwnerCoreModHarness} from "test/utils/harnesses/access/Owner/OwnerCoreModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol b/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol index 3936c0e4..4fe0fed4 100644 --- a/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/Owner/TwoSteps/Data/mod/fuzz/data.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerTwoStepData_Base_Test} from "test/unit/access/Owner/TwoSteps/Data/OwnerTwoStepDataBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; +import {OwnerTwoStepModHarness} from "test/utils/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol index 0a529b8d..5d7e8a8f 100644 --- a/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Renounce/mod/fuzz/renounceOwnership.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerTwoStepRenounce_Base_Test} from "test/unit/access/Owner/TwoSteps/Renounce/OwnerTwoStepRenounceBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; +import {OwnerTwoStepModHarness} from "test/utils/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol index 76a3c910..2013232a 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30; import {OwnerTwoStepTransfer_Base_Test} from "test/unit/access/Owner/TwoSteps/Transfer/OwnerTwoStepTransferBase.t.sol"; import {OwnerStorageUtils} from "test/utils/storage/OwnerStorageUtils.sol"; -import {OwnerTwoStepModHarness} from "test/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; +import {OwnerTwoStepModHarness} from "test/utils/harnesses/access/Owner/OwnerTwoStepModHarness.sol"; /** * @dev BTT spec: test/trees/Owner.tree diff --git a/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/data.t.sol b/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..cfd548bb --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/data.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Data_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function testFuzz_ShouldReturnTrue_IERC165Interface(bytes4) external view { + assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); + } + + function test_ShouldReturnFalse_InvalidInterfaceWhenUnregistered() external view { + assertFalse(erc165Facet.supportsInterface(INVALID_INTERFACE_ID)); + } + + function test_ShouldReturnFalse_ZeroInterfaceWhenUnregistered() external view { + assertFalse(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); + } + + function test_ShouldReturnFalse_UnregisteredInterfaces() external view { + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + assertFalse(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Data/mod/fuzz/data.t.sol b/test/unit/interfaceDetection/ERC165/Data/mod/fuzz/data.t.sol new file mode 100644 index 00000000..8218b1d0 --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Data/mod/fuzz/data.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Mod_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Data_ERC165Mod_Fuzz_Unit_Test is ERC165Mod_Base_Test { + function test_ShouldReturnFalse_ForUnregisteredInterfaces() external view { + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + assertFalse(harness.supportsInterface(IERC20_INTERFACE_ID)); + assertFalse(harness.supportsInterface(CUSTOM_INTERFACE_ID)); + } + + function test_ShouldReturnFalse_AfterUnregistration() external { + harness.registerInterface(IERC721_INTERFACE_ID); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldSupportSpecialIds_WhenRegistered() external { + harness.registerInterface(ZERO_INTERFACE_ID); + harness.registerInterface(INVALID_INTERFACE_ID); + harness.registerInterface(IERC165_INTERFACE_ID); + + assertTrue(harness.supportsInterface(ZERO_INTERFACE_ID)); + assertTrue(harness.supportsInterface(INVALID_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC165_INTERFACE_ID)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol b/test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol new file mode 100644 index 00000000..b8a2dc4e --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test} from "forge-std/Test.sol"; +import {IERC165} from "src/interfaceDetection/ERC165/ERC165Facet.sol"; +import {ERC165FacetHarness} from "test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +abstract contract ERC165Facet_Base_Test is Test { + ERC165FacetHarness internal erc165Facet; + + bytes4 internal constant IERC165_INTERFACE_ID = type(IERC165).interfaceId; + bytes4 internal constant IERC721_INTERFACE_ID = 0x80ac58cd; + bytes4 internal constant IERC20_INTERFACE_ID = 0x36372b07; + bytes4 internal constant INVALID_INTERFACE_ID = 0xffffffff; + bytes4 internal constant CUSTOM_INTERFACE_ID = 0x12345678; + bytes4 internal constant ZERO_INTERFACE_ID = 0x00000000; + + function setUp() public virtual { + erc165Facet = new ERC165FacetHarness(); + erc165Facet.initialize(); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol b/test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol new file mode 100644 index 00000000..caf6ff4d --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test} from "forge-std/Test.sol"; +import {ERC165Harness} from "test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +abstract contract ERC165Mod_Base_Test is Test { + ERC165Harness internal harness; + + bytes4 internal constant IERC721_INTERFACE_ID = 0x80ac58cd; + bytes4 internal constant IERC20_INTERFACE_ID = 0x36372b07; + bytes4 internal constant IERC1155_INTERFACE_ID = 0xd9b67a26; + bytes4 internal constant IERC165_INTERFACE_ID = 0x01ffc9a7; + bytes4 internal constant INVALID_INTERFACE_ID = 0xffffffff; + bytes4 internal constant CUSTOM_INTERFACE_ID = 0x12345678; + bytes4 internal constant ZERO_INTERFACE_ID = 0x00000000; + + function setUp() public virtual { + harness = new ERC165Harness(); + harness.initialize(); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Gas/facet/fuzz/supportsInterface.t.sol b/test/unit/interfaceDetection/ERC165/Gas/facet/fuzz/supportsInterface.t.sol new file mode 100644 index 00000000..4beaf1ae --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Gas/facet/fuzz/supportsInterface.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Gas_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldUseLessThan30kGas_WhenCheckingIERC165Support() external view { + uint256 gasBefore = gasleft(); + erc165Facet.supportsInterface(IERC165_INTERFACE_ID); + uint256 gasUsed = gasBefore - gasleft(); + + assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); + } + + function test_ShouldUseLessThan30kGas_WhenCheckingRegisteredInterfaceSupport() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + + uint256 gasBefore = gasleft(); + erc165Facet.supportsInterface(IERC721_INTERFACE_ID); + uint256 gasUsed = gasBefore - gasleft(); + + assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); + } + + function test_ShouldUseLessThan30kGas_WhenCheckingUnregisteredInterfaceSupport() external view { + uint256 gasBefore = gasleft(); + erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID); + uint256 gasUsed = gasBefore - gasleft(); + + assertLt(gasUsed, 30000, "supportsInterface should use less than 30,000 gas"); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Gas/mod/fuzz/gas.t.sol b/test/unit/interfaceDetection/ERC165/Gas/mod/fuzz/gas.t.sol new file mode 100644 index 00000000..fc825faf --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Gas/mod/fuzz/gas.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test, console2} from "forge-std/Test.sol"; +import {ERC165Harness} from "test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Gas_ERC165Mod_Fuzz_Unit_Test is Test { + ERC165Harness internal harness; + + bytes4 internal constant IERC721_INTERFACE_ID = 0x80ac58cd; + + function setUp() public { + harness = new ERC165Harness(); + harness.initialize(); + } + + function test_Gas_RegisterInterface() public { + uint256 gasBefore = gasleft(); + harness.registerInterface(IERC721_INTERFACE_ID); + uint256 gasUsed = gasBefore - gasleft(); + + console2.log("Gas used for registerInterface:", gasUsed); + } + + function test_Gas_SupportsInterface() public { + harness.registerInterface(IERC721_INTERFACE_ID); + + uint256 gasBefore = gasleft(); + harness.supportsInterface(IERC721_INTERFACE_ID); + uint256 gasUsed = gasBefore - gasleft(); + + console2.log("Gas used for supportsInterface:", gasUsed); + } + + function test_Gas_RegisterMultipleInterfaces() public { + bytes4[] memory interfaceIds = new bytes4[](10); + for (uint256 i = 0; i < 10; i++) { + interfaceIds[i] = bytes4(uint32(i + 1)); + } + + uint256 gasBefore = gasleft(); + harness.registerMultipleInterfaces(interfaceIds); + uint256 gasUsed = gasBefore - gasleft(); + + console2.log("Gas used for registerMultipleInterfaces (10 interfaces):", gasUsed); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Register/facet/fuzz/registerInterface.t.sol b/test/unit/interfaceDetection/ERC165/Register/facet/fuzz/registerInterface.t.sol new file mode 100644 index 00000000..acc046ca --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Register/facet/fuzz/registerInterface.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract RegisterInterface_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldMarkSupported_WhenRegisteringSingleInterface() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldMarkSupported_WhenRegisteringMultipleInterfaces() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.registerInterface(IERC20_INTERFACE_ID); + erc165Facet.registerInterface(CUSTOM_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); + } + + function test_ShouldRemainSupported_WhenRegisteringIERC165OrOtherInterfaces() external { + erc165Facet.registerInterface(IERC165_INTERFACE_ID); + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); + } + + function test_ShouldAllowRegisteringSpecialIds() external { + erc165Facet.registerInterface(ZERO_INTERFACE_ID); + erc165Facet.registerInterface(INVALID_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(INVALID_INTERFACE_ID)); + } + + function test_ShouldBeIdempotent_WhenRegisteringSameInterfaceTwice() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + } + + function testFuzz_ShouldMarkSupported_WhenRegisteringInterface(bytes4 interfaceId) external { + erc165Facet.registerInterface(interfaceId); + assertTrue(erc165Facet.supportsInterface(interfaceId)); + } + + function testFuzz_ShouldTrackAllInterfaces_WhenRegisteringMany(bytes4[] calldata interfaceIds) external { + vm.assume(interfaceIds.length > 0 && interfaceIds.length <= 20); + + for (uint256 i = 0; i < interfaceIds.length; i++) { + erc165Facet.registerInterface(interfaceIds[i]); + assertTrue(erc165Facet.supportsInterface(interfaceIds[i])); + } + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Register/mod/fuzz/registerInterface.t.sol b/test/unit/interfaceDetection/ERC165/Register/mod/fuzz/registerInterface.t.sol new file mode 100644 index 00000000..712667aa --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Register/mod/fuzz/registerInterface.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Mod_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract RegisterInterface_ERC165Mod_Fuzz_Unit_Test is ERC165Mod_Base_Test { + function test_ShouldMarkSupported_WhenRegisteringSingleInterface() external { + harness.registerInterface(IERC721_INTERFACE_ID); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldMarkSupported_WhenRegisteringMultipleInterfaces() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.registerInterface(IERC20_INTERFACE_ID); + harness.registerInterface(IERC1155_INTERFACE_ID); + + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); + } + + function test_ShouldBeIdempotent_WhenRegisteringSameInterfaceTwice() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.registerInterface(IERC721_INTERFACE_ID); + + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldSupportAll_WhenRegisteringArrayWithDuplicates() external { + bytes4[] memory interfaceIds = new bytes4[](5); + interfaceIds[0] = IERC721_INTERFACE_ID; + interfaceIds[1] = IERC20_INTERFACE_ID; + interfaceIds[2] = IERC721_INTERFACE_ID; + interfaceIds[3] = IERC1155_INTERFACE_ID; + interfaceIds[4] = IERC20_INTERFACE_ID; + + harness.registerMultipleInterfaces(interfaceIds); + + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC1155_INTERFACE_ID)); + } + + function test_ShouldNotRevert_WhenRegisteringEmptyArray() external { + bytes4[] memory interfaceIds = new bytes4[](0); + harness.registerMultipleInterfaces(interfaceIds); + } + + function test_ShouldRegister_WhenArrayHasSingleElement() external { + bytes4[] memory interfaceIds = new bytes4[](1); + interfaceIds[0] = IERC721_INTERFACE_ID; + + harness.registerMultipleInterfaces(interfaceIds); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldNotDependOnCaller_WhenRegistering() external { + address caller = makeAddr("caller"); + + vm.prank(caller); + harness.registerInterface(IERC721_INTERFACE_ID); + + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function testFuzz_ShouldMarkSupported_WhenRegistering(bytes4 interfaceId) external { + harness.registerInterface(interfaceId); + assertTrue(harness.supportsInterface(interfaceId)); + } + + function testFuzz_ShouldSupportAll_WhenRegisteringMany(bytes4[] calldata interfaceIds) external { + vm.assume(interfaceIds.length > 0 && interfaceIds.length <= 50); + + harness.registerMultipleInterfaces(interfaceIds); + + for (uint256 i = 0; i < interfaceIds.length; i++) { + assertTrue(harness.supportsInterface(interfaceIds[i])); + } + } + + function testFuzz_ShouldRemainSupported_WhenRegisteredManyTimes(bytes4 interfaceId, uint8 registrationCount) + external + { + vm.assume(registrationCount > 0 && registrationCount <= 10); + + for (uint256 i = 0; i < registrationCount; i++) { + harness.registerInterface(interfaceId); + } + + assertTrue(harness.supportsInterface(interfaceId)); + } + + function testFuzz_ShouldSupportIndependentCallers(address caller, bytes4 interfaceId) external { + vm.prank(caller); + harness.registerInterface(interfaceId); + + assertTrue(harness.supportsInterface(interfaceId)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Storage/facet/fuzz/storage.t.sol b/test/unit/interfaceDetection/ERC165/Storage/facet/fuzz/storage.t.sol new file mode 100644 index 00000000..2d9428cd --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Storage/facet/fuzz/storage.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Storage_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldUseExpectedSlot_WhenReadingStorageSlot() external view { + assertEq(erc165Facet.exposedGetStorage(), keccak256("erc165")); + } + + function test_ShouldWriteMappingValue_WhenRegisteringInterface() external { + bytes32 storageSlot = keccak256("erc165"); + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + + bytes32 mappingSlot = keccak256(abi.encode(IERC721_INTERFACE_ID, storageSlot)); + bytes32 storedValue = vm.load(address(erc165Facet), mappingSlot); + + assertEq(uint256(storedValue), 1); + } + + function test_ShouldMatchRawStorage_WhenReadingSupportStatus() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + + bool supportsResult = erc165Facet.supportsInterface(IERC721_INTERFACE_ID); + bool storageResult = erc165Facet.getStorageValue(IERC721_INTERFACE_ID); + + assertEq(supportsResult, storageResult); + } + + function testFuzz_ShouldMatchRawStorage_AfterFuzzedState(bytes4 interfaceId, bool shouldSupport) external { + vm.assume(interfaceId != IERC165_INTERFACE_ID); + + if (shouldSupport) { + erc165Facet.registerInterface(interfaceId); + } else { + erc165Facet.unregisterInterface(interfaceId); + } + + assertEq(erc165Facet.supportsInterface(interfaceId), erc165Facet.getStorageValue(interfaceId)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Storage/mod/fuzz/storage.t.sol b/test/unit/interfaceDetection/ERC165/Storage/mod/fuzz/storage.t.sol new file mode 100644 index 00000000..81efa511 --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Storage/mod/fuzz/storage.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Mod_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Storage_ERC165Mod_Fuzz_Unit_Test is ERC165Mod_Base_Test { + function test_ShouldUseExpectedSlot_WhenReadingStoragePosition() external view { + assertEq(harness.getStoragePosition(), keccak256("erc165")); + } + + function test_ShouldStoreMappingValue_WhenRegisteringInterface() external { + bytes32 storageSlot = keccak256("erc165"); + harness.registerInterface(IERC721_INTERFACE_ID); + + bytes32 mappingSlot = keccak256(abi.encode(IERC721_INTERFACE_ID, storageSlot)); + bytes32 storedValue = vm.load(address(harness), mappingSlot); + + assertEq(uint256(storedValue), 1); + } + + function test_ShouldMatchRawStorage_AfterRegistration() external { + harness.registerInterface(IERC721_INTERFACE_ID); + + assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); + } + + function test_ShouldMatchRawStorage_AfterMultipleRegistrations() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.registerInterface(IERC20_INTERFACE_ID); + harness.registerInterface(IERC1155_INTERFACE_ID); + + assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); + assertEq(harness.supportsInterface(IERC20_INTERFACE_ID), harness.getStorageValue(IERC20_INTERFACE_ID)); + assertEq(harness.supportsInterface(IERC1155_INTERFACE_ID), harness.getStorageValue(IERC1155_INTERFACE_ID)); + } + + function test_ShouldMatchRawStorage_AfterUnregistration() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + + assertEq(harness.supportsInterface(IERC721_INTERFACE_ID), harness.getStorageValue(IERC721_INTERFACE_ID)); + } + + function testFuzz_ShouldMatchRawStorage_AfterFuzzedState(bytes4 interfaceId, bool shouldSupport) external { + harness.forceSetInterface(interfaceId, shouldSupport); + + assertEq(harness.supportsInterface(interfaceId), harness.getStorageValue(interfaceId)); + assertEq(harness.supportsInterface(interfaceId), shouldSupport); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/operations.t.sol b/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/operations.t.sol new file mode 100644 index 00000000..70c96ec8 --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/operations.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract Operations_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldHandleRegisterUnregisterCycles() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldHandleMixedInterfaceOperations() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.registerInterface(IERC20_INTERFACE_ID); + erc165Facet.registerInterface(CUSTOM_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); + + erc165Facet.unregisterInterface(IERC20_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); + + erc165Facet.registerInterface(ZERO_INTERFACE_ID); + + assertTrue(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertFalse(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(CUSTOM_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(ZERO_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/unregisterInterface.t.sol b/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/unregisterInterface.t.sol new file mode 100644 index 00000000..1a6dbf3b --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Unregister/facet/fuzz/unregisterInterface.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract UnregisterInterface_ERC165Facet_Fuzz_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldUnsetSupport_WhenUnregisteringRegisteredInterface() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldNotAffectOtherInterfaces_WhenUnregisteringOne() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.registerInterface(IERC20_INTERFACE_ID); + + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(erc165Facet.supportsInterface(IERC20_INTERFACE_ID)); + } + + function test_ShouldRemainSupported_WhenAttemptingToUnregisterIERC165() external { + erc165Facet.unregisterInterface(IERC165_INTERFACE_ID); + assertTrue(erc165Facet.supportsInterface(IERC165_INTERFACE_ID)); + } + + function test_ShouldBeIdempotent_WhenUnregisteringSameInterfaceTwice() external { + erc165Facet.registerInterface(IERC721_INTERFACE_ID); + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + erc165Facet.unregisterInterface(IERC721_INTERFACE_ID); + + assertFalse(erc165Facet.supportsInterface(IERC721_INTERFACE_ID)); + } + + function testFuzz_ShouldUnsetSupport_WhenUnregisteringInterface(bytes4 interfaceId) external { + vm.assume(interfaceId != IERC165_INTERFACE_ID); + + erc165Facet.registerInterface(interfaceId); + erc165Facet.unregisterInterface(interfaceId); + + assertFalse(erc165Facet.supportsInterface(interfaceId)); + } +} + diff --git a/test/unit/interfaceDetection/ERC165/Unregister/mod/fuzz/unregisterInterface.t.sol b/test/unit/interfaceDetection/ERC165/Unregister/mod/fuzz/unregisterInterface.t.sol new file mode 100644 index 00000000..d88b99ac --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Unregister/mod/fuzz/unregisterInterface.t.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Mod_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165ModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract UnregisterInterface_ERC165Mod_Fuzz_Unit_Test is ERC165Mod_Base_Test { + function test_ShouldUnsetSupport_WhenUnregisteringRegisteredInterface() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldNotAffectOtherInterfaces_WhenUnregisteringOne() external { + harness.registerInterface(IERC721_INTERFACE_ID); + harness.registerInterface(IERC20_INTERFACE_ID); + + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + assertTrue(harness.supportsInterface(IERC20_INTERFACE_ID)); + } + + function test_ShouldTrackRegisterUnregisterCycle() external { + harness.registerInterface(IERC721_INTERFACE_ID); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + + harness.registerInterface(IERC721_INTERFACE_ID); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function test_ShouldForceSetTrueAndFalse() external { + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + + harness.forceSetInterface(IERC721_INTERFACE_ID, true); + assertTrue(harness.supportsInterface(IERC721_INTERFACE_ID)); + + harness.forceSetInterface(IERC721_INTERFACE_ID, false); + assertFalse(harness.supportsInterface(IERC721_INTERFACE_ID)); + } + + function testFuzz_ShouldUnsetSupport_WhenForceUnregistering(bytes4 interfaceId) external { + harness.registerInterface(interfaceId); + harness.forceSetInterface(interfaceId, false); + + assertFalse(harness.supportsInterface(interfaceId)); + } + + function testFuzz_ShouldKeepOtherInterfaceSupport_WhenUnregisteringOne( + bytes4 interfaceId1, + bytes4 interfaceId2, + bytes4 interfaceId3 + ) external { + vm.assume(interfaceId1 != interfaceId2); + vm.assume(interfaceId1 != interfaceId3); + vm.assume(interfaceId2 != interfaceId3); + + harness.registerInterface(interfaceId1); + harness.registerInterface(interfaceId2); + + assertTrue(harness.supportsInterface(interfaceId1)); + assertTrue(harness.supportsInterface(interfaceId2)); + + harness.forceSetInterface(interfaceId1, false); + + assertFalse(harness.supportsInterface(interfaceId1)); + assertTrue(harness.supportsInterface(interfaceId2)); + + harness.registerInterface(interfaceId3); + + assertFalse(harness.supportsInterface(interfaceId1)); + assertTrue(harness.supportsInterface(interfaceId2)); + assertTrue(harness.supportsInterface(interfaceId3)); + } +} + diff --git a/test/unit/libraries/NonReentrancy/NonReentrancyModBase.t.sol b/test/unit/libraries/NonReentrancy/NonReentrancyModBase.t.sol new file mode 100644 index 00000000..b03f398b --- /dev/null +++ b/test/unit/libraries/NonReentrancy/NonReentrancyModBase.t.sol @@ -0,0 +1,19 @@ +// 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 {NonReentrantHarness} from "test/utils/harnesses/libraries/NonReentrancyHarness.sol"; + +contract NonReentrancyMod_Base_Test is Base_Test { + NonReentrantHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new NonReentrantHarness(); + } +} + diff --git a/test/libraries/NonReentrancy.t.sol b/test/unit/libraries/NonReentrancy/mod/fuzz/nonReentrancy.t.sol similarity index 51% rename from test/libraries/NonReentrancy.t.sol rename to test/unit/libraries/NonReentrancy/mod/fuzz/nonReentrancy.t.sol index 61792f80..1bcc9a9f 100644 --- a/test/libraries/NonReentrancy.t.sol +++ b/test/unit/libraries/NonReentrancy/mod/fuzz/nonReentrancy.t.sol @@ -5,34 +5,31 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import {Test} from "forge-std/Test.sol"; +import {NonReentrancyMod_Base_Test} from "test/unit/libraries/NonReentrancy/NonReentrancyModBase.t.sol"; import "src/libraries/NonReentrancyMod.sol" as NonReentrancyMod; -import {NonReentrantHarness} from "test/libraries/harnesses/NonReentrancyHarness.sol"; +import {NonReentrantHarness} from "test/utils/harnesses/libraries/NonReentrancyHarness.sol"; -contract LibNonReentrancyTest is Test { - NonReentrantHarness internal harness; - - function setUp() public { - harness = new NonReentrantHarness(); - } - - function test_GuardedIncrement_IncrementsCounter() public { +/** + * @dev BTT spec: test/trees/NonReentrancy.tree + */ +contract NonReentrancyMod_Fuzz_Unit_Test is NonReentrancyMod_Base_Test { + function test_ShouldIncrementCounter_WhenGuardedIncrementCalledOnce() public { harness.guardedIncrement(); assertEq(harness.counter(), 1); } - function test_GuardedIncrement_AllowsSequentialCalls() public { + function test_ShouldIncrementCounter_WhenGuardedIncrementCalledTwiceSequentially() public { harness.guardedIncrement(); harness.guardedIncrement(); assertEq(harness.counter(), 2); } - function test_RevertWhen_ReenteringFunction() public { + function test_ShouldRevert_WhenReenteringGuardedFunction() public { vm.expectRevert(NonReentrancyMod.Reentrancy.selector); harness.guardedIncrementAndReenter(); } - function test_GuardResetsAfterRevert() public { + function test_ShouldAllowGuardedCallsAfterRevert() public { vm.expectRevert(NonReentrantHarness.ForcedFailure.selector); harness.guardedIncrementAndForceRevert(); @@ -40,3 +37,4 @@ contract LibNonReentrancyTest is Test { assertEq(harness.counter(), 1); } } + diff --git a/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol b/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol index 1d5f537b..7ed58300 100644 --- a/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol +++ b/test/unit/token/ERC1155/Approve/ERC1155ApproveModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC1155ApproveModHarness} from "test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol"; +import {ERC1155ApproveModHarness} from "test/utils/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; abstract contract ERC1155ApproveMod_Base_Test is Base_Test { diff --git a/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol b/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol index 50ae53b4..aa0f72e6 100644 --- a/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol +++ b/test/unit/token/ERC1155/Burn/ERC1155BurnModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC1155BurnModHarness} from "test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol"; +import {ERC1155BurnModHarness} from "test/utils/harnesses/token/ERC1155/ERC1155BurnModHarness.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; abstract contract ERC1155BurnMod_Base_Test is Base_Test { diff --git a/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol b/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol index 1d00b001..49491d0c 100644 --- a/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol +++ b/test/unit/token/ERC1155/Metadata/ERC1155MetadataModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC1155MetadataModHarness} from "test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol"; +import {ERC1155MetadataModHarness} from "test/utils/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol"; abstract contract ERC1155MetadataMod_Base_Test is Base_Test { ERC1155MetadataModHarness internal harness; diff --git a/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol b/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol index 2a7aa224..118ca044 100644 --- a/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol +++ b/test/unit/token/ERC1155/Mint/ERC1155MintModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC1155MintModHarness} from "test/harnesses/token/ERC1155/ERC1155MintModHarness.sol"; +import {ERC1155MintModHarness} from "test/utils/harnesses/token/ERC1155/ERC1155MintModHarness.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; abstract contract ERC1155MintMod_Base_Test is Base_Test { diff --git a/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol b/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol index 38d1709f..d89512d6 100644 --- a/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol +++ b/test/unit/token/ERC1155/Transfer/ERC1155TransferModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC1155TransferModHarness} from "test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol"; +import {ERC1155TransferModHarness} from "test/utils/harnesses/token/ERC1155/ERC1155TransferModHarness.sol"; import {ERC1155StorageUtils} from "test/utils/storage/ERC1155StorageUtils.sol"; abstract contract ERC1155TransferMod_Base_Test is Base_Test { diff --git a/test/unit/token/ERC20/Approve/mod/fuzz/approve.t.sol b/test/unit/token/ERC20/Approve/mod/fuzz/approve.t.sol index bf2394e4..9c8783c1 100644 --- a/test/unit/token/ERC20/Approve/mod/fuzz/approve.t.sol +++ b/test/unit/token/ERC20/Approve/mod/fuzz/approve.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; import {Base_Test} from "test/Base.t.sol"; import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; -import {ERC20ApproveModHarness} from "test/harnesses/token/ERC20/ERC20ApproveModHarness.sol"; +import {ERC20ApproveModHarness} from "test/utils/harnesses/token/ERC20/ERC20ApproveModHarness.sol"; import "src/token/ERC20/Approve/ERC20ApproveMod.sol"; diff --git a/test/unit/token/ERC20/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC20/Burn/mod/fuzz/burn.t.sol index b7bf369f..c63e31f1 100644 --- a/test/unit/token/ERC20/Burn/mod/fuzz/burn.t.sol +++ b/test/unit/token/ERC20/Burn/mod/fuzz/burn.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; import {Base_Test} from "test/Base.t.sol"; import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; -import {ERC20BurnModHarness} from "test/harnesses/token/ERC20/ERC20BurnModHarness.sol"; +import {ERC20BurnModHarness} from "test/utils/harnesses/token/ERC20/ERC20BurnModHarness.sol"; import "src/token/ERC20/Burn/ERC20BurnMod.sol"; diff --git a/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol b/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol index 025e82ac..c6d2359e 100644 --- a/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol +++ b/test/unit/token/ERC20/Metadata/ERC20MetadataFacetBase.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30 <0.9.0; import {Base_Test} from "test/Base.t.sol"; import {ERC20MetadataFacet} from "src/token/ERC20/Metadata/ERC20MetadataFacet.sol"; -import {ERC20MetadataModHarness} from "test/harnesses/token/ERC20/ERC20MetadataModHarness.sol"; +import {ERC20MetadataModHarness} from "test/utils/harnesses/token/ERC20/ERC20MetadataModHarness.sol"; abstract contract ERC20MetadataFacet_Base_Test is Base_Test { ERC20MetadataModHarness internal facet; diff --git a/test/unit/token/ERC20/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC20/Mint/mod/fuzz/mint.t.sol index ab53f7a2..fc572a80 100644 --- a/test/unit/token/ERC20/Mint/mod/fuzz/mint.t.sol +++ b/test/unit/token/ERC20/Mint/mod/fuzz/mint.t.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.30; import {stdError} from "forge-std/StdError.sol"; import {Base_Test} from "test/Base.t.sol"; import {ERC20StorageUtils} from "test/utils/storage/ERC20StorageUtils.sol"; -import {ERC20MintModHarness} from "test/harnesses/token/ERC20/ERC20MintModHarness.sol"; +import {ERC20MintModHarness} from "test/utils/harnesses/token/ERC20/ERC20MintModHarness.sol"; import "src/token/ERC20/Mint/ERC20MintMod.sol"; diff --git a/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol index b1906731..257ee166 100644 --- a/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol +++ b/test/unit/token/ERC20/Permit/ERC20PermitFacetBase.t.sol @@ -7,7 +7,7 @@ pragma solidity >=0.8.30 <0.9.0; import {Base_Test} from "test/Base.t.sol"; import {ERC20PermitFacet} from "src/token/ERC20/Permit/ERC20PermitFacet.sol"; -import {ERC20PermitFacetHarness} from "test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol"; +import {ERC20PermitFacetHarness} from "test/utils/harnesses/token/ERC20/ERC20PermitFacetHarness.sol"; abstract contract ERC20PermitFacet_Base_Test is Base_Test { ERC20PermitFacetHarness internal facet; diff --git a/test/unit/token/ERC20/Transfer/mod/ERC20TransferModBase.t.sol b/test/unit/token/ERC20/Transfer/mod/ERC20TransferModBase.t.sol index 26974e48..5166bf98 100644 --- a/test/unit/token/ERC20/Transfer/mod/ERC20TransferModBase.t.sol +++ b/test/unit/token/ERC20/Transfer/mod/ERC20TransferModBase.t.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.30 <0.9.0; */ import {Base_Test} from "test/Base.t.sol"; -import {ERC20TransferModHarness} from "test/harnesses/token/ERC20/ERC20TransferModHarness.sol"; +import {ERC20TransferModHarness} from "test/utils/harnesses/token/ERC20/ERC20TransferModHarness.sol"; contract ERC20TransferMod_Base_Test is Base_Test { ERC20TransferModHarness internal harness; diff --git a/test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol b/test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol similarity index 100% rename from test/harnesses/access/AccessControl/AccessControlCoreModHarness.sol rename to test/utils/harnesses/access/AccessControl/AccessControlCoreModHarness.sol diff --git a/test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol b/test/utils/harnesses/access/AccessControl/AccessControlPausableModHarness.sol similarity index 100% rename from test/harnesses/access/AccessControl/AccessControlPausableModHarness.sol rename to test/utils/harnesses/access/AccessControl/AccessControlPausableModHarness.sol diff --git a/test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol b/test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol similarity index 100% rename from test/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol rename to test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol diff --git a/test/harnesses/access/Owner/OwnerCoreModHarness.sol b/test/utils/harnesses/access/Owner/OwnerCoreModHarness.sol similarity index 100% rename from test/harnesses/access/Owner/OwnerCoreModHarness.sol rename to test/utils/harnesses/access/Owner/OwnerCoreModHarness.sol diff --git a/test/harnesses/access/Owner/OwnerTwoStepModHarness.sol b/test/utils/harnesses/access/Owner/OwnerTwoStepModHarness.sol similarity index 100% rename from test/harnesses/access/Owner/OwnerTwoStepModHarness.sol rename to test/utils/harnesses/access/Owner/OwnerTwoStepModHarness.sol diff --git a/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol b/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol new file mode 100644 index 00000000..9e3376c2 --- /dev/null +++ b/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet} from "src/interfaceDetection/ERC165/ERC165Facet.sol"; + +/** + * @title ERC165FacetHarness + * @notice Test harness that exposes ERC165Facet storage helpers as external + */ +contract ERC165FacetHarness is ERC165Facet { + function initialize() external { + // No-op; storage is implicitly available + } + + function registerInterface(bytes4 _interfaceId) external { + ERC165Storage storage s = getStorage(); + s.supportedInterfaces[_interfaceId] = true; + } + + function unregisterInterface(bytes4 _interfaceId) external { + ERC165Storage storage s = getStorage(); + s.supportedInterfaces[_interfaceId] = false; + } + + function getStorageValue(bytes4 _interfaceId) external view returns (bool) { + return getStorage().supportedInterfaces[_interfaceId]; + } + + function exposedGetStorage() external pure returns (bytes32) { + return STORAGE_POSITION; + } +} + diff --git a/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol b/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol similarity index 57% rename from test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol rename to test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol index dbd6c8b8..79db11d3 100644 --- a/test/interfaceDetection/ERC165/harnesses/ERC165Harness.sol +++ b/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol @@ -5,70 +5,43 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import "../../../../src/interfaceDetection/ERC165/ERC165Mod.sol" as ERC165Mod; +import "src/interfaceDetection/ERC165/ERC165Mod.sol" as ERC165Mod; /** - * @title LibERC165 Test Harness - */ -/** - * @notice Exposes internal LibERC165 functions as external for testing + * @title ERC165ModHarness + * @notice Test harness that exposes ERC165Mod storage and helpers as external */ contract ERC165Harness { - /** - * @notice Initialize the ERC165 storage (for testing) - */ function initialize() external { - /** - * No initialization needed for basic ERC165 - */ - /** - * Storage is automatically available - */ + // No-op; storage is implicitly available } - /** - * @notice Register an interface - */ function registerInterface(bytes4 _interfaceId) external { ERC165Mod.registerInterface(_interfaceId); } - /** - * @notice Check if an interface is supported - */ function supportsInterface(bytes4 _interfaceId) external view returns (bool) { ERC165Mod.ERC165Storage storage s = ERC165Mod.getStorage(); return s.supportedInterfaces[_interfaceId]; } - /** - * @notice Get storage directly (for testing storage consistency) - */ function getStorageValue(bytes4 _interfaceId) external view returns (bool) { return ERC165Mod.getStorage().supportedInterfaces[_interfaceId]; } - /** - * @notice Get the storage position (must match ERC165Mod.sol STORAGE_POSITION) - */ function getStoragePosition() external pure returns (bytes32) { return keccak256("erc165"); } - /** - * @notice Force set an interface support value (for testing edge cases) - */ function forceSetInterface(bytes4 _interfaceId, bool _supported) external { ERC165Mod.ERC165Storage storage s = ERC165Mod.getStorage(); s.supportedInterfaces[_interfaceId] = _supported; } - /** - * @notice Register multiple interfaces at once (for testing) - */ function registerMultipleInterfaces(bytes4[] calldata _interfaceIds) external { for (uint256 i = 0; i < _interfaceIds.length; i++) { ERC165Mod.registerInterface(_interfaceIds[i]); } } } + diff --git a/test/libraries/harnesses/NonReentrancyHarness.sol b/test/utils/harnesses/libraries/NonReentrancyHarness.sol similarity index 99% rename from test/libraries/harnesses/NonReentrancyHarness.sol rename to test/utils/harnesses/libraries/NonReentrancyHarness.sol index 6201389a..e82846bd 100644 --- a/test/libraries/harnesses/NonReentrancyHarness.sol +++ b/test/utils/harnesses/libraries/NonReentrancyHarness.sol @@ -33,3 +33,4 @@ contract NonReentrantHarness { revert ForcedFailure(); } } + diff --git a/test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol b/test/utils/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol similarity index 100% rename from test/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol rename to test/utils/harnesses/token/ERC1155/ERC1155ApproveModHarness.sol diff --git a/test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol b/test/utils/harnesses/token/ERC1155/ERC1155BurnModHarness.sol similarity index 100% rename from test/harnesses/token/ERC1155/ERC1155BurnModHarness.sol rename to test/utils/harnesses/token/ERC1155/ERC1155BurnModHarness.sol diff --git a/test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol b/test/utils/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol similarity index 100% rename from test/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol rename to test/utils/harnesses/token/ERC1155/ERC1155MetadataModHarness.sol diff --git a/test/harnesses/token/ERC1155/ERC1155MintModHarness.sol b/test/utils/harnesses/token/ERC1155/ERC1155MintModHarness.sol similarity index 100% rename from test/harnesses/token/ERC1155/ERC1155MintModHarness.sol rename to test/utils/harnesses/token/ERC1155/ERC1155MintModHarness.sol diff --git a/test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol b/test/utils/harnesses/token/ERC1155/ERC1155TransferModHarness.sol similarity index 100% rename from test/harnesses/token/ERC1155/ERC1155TransferModHarness.sol rename to test/utils/harnesses/token/ERC1155/ERC1155TransferModHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20ApproveModHarness.sol b/test/utils/harnesses/token/ERC20/ERC20ApproveModHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20ApproveModHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20ApproveModHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20BurnModHarness.sol b/test/utils/harnesses/token/ERC20/ERC20BurnModHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20BurnModHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20BurnModHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20MetadataModHarness.sol b/test/utils/harnesses/token/ERC20/ERC20MetadataModHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20MetadataModHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20MetadataModHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20MintModHarness.sol b/test/utils/harnesses/token/ERC20/ERC20MintModHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20MintModHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20MintModHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol b/test/utils/harnesses/token/ERC20/ERC20PermitFacetHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20PermitFacetHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20PermitFacetHarness.sol diff --git a/test/harnesses/token/ERC20/ERC20TransferModHarness.sol b/test/utils/harnesses/token/ERC20/ERC20TransferModHarness.sol similarity index 100% rename from test/harnesses/token/ERC20/ERC20TransferModHarness.sol rename to test/utils/harnesses/token/ERC20/ERC20TransferModHarness.sol From 905027c6f41e97f553ead9a2d2f2a05a111ee594 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 15:26:38 -0400 Subject: [PATCH 12/25] fmt, add ERC165 exportSelector test --- .../Pausable/mod/fuzz/pausable.t.sol | 4 +++- .../Temporal/Data/mod/fuzz/data.t.sol | 4 +++- .../Grant/mod/fuzz/grantRoleWithExpiry.t.sol | 4 +++- .../Revoke/mod/fuzz/revokeTemporalRole.t.sol | 4 +++- .../Data/facet/fuzz/exportSelectors.t.sol | 21 +++++++++++++++++++ .../ERC165/ERC165FacetHarness.sol | 2 +- .../ERC165/ERC165Harness.sol | 2 +- 7 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 test/unit/interfaceDetection/ERC165/Data/facet/fuzz/exportSelectors.t.sol diff --git a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol index 759b61f7..b95e9d3b 100644 --- a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol @@ -7,7 +7,9 @@ pragma solidity >=0.8.30; import {AccessControlPausable_Base_Test} from "test/unit/access/AccessControl/Pausable/AccessControlPausableBase.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; -import {AccessControlPausableModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlPausableModHarness.sol"; +import { + AccessControlPausableModHarness +} from "test/utils/harnesses/access/AccessControl/AccessControlPausableModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol index 3867840b..9e61c438 100644 --- a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol @@ -8,7 +8,9 @@ pragma solidity >=0.8.30; import { AccessControlTemporalData_Base_Test } from "test/unit/access/AccessControl/Temporal/Data/AccessControlTemporalDataBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import { + AccessControlTemporalModHarness +} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol index f3668a9a..f4aed8dc 100644 --- a/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol +++ b/test/unit/access/AccessControl/Temporal/Grant/mod/fuzz/grantRoleWithExpiry.t.sol @@ -9,7 +9,9 @@ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorage import { AccessControlTemporalGrant_Base_Test } from "test/unit/access/AccessControl/Temporal/Grant/AccessControlTemporalGrantBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import { + AccessControlTemporalModHarness +} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol index 4c738e83..36f0ad27 100644 --- a/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol +++ b/test/unit/access/AccessControl/Temporal/Revoke/mod/fuzz/revokeTemporalRole.t.sol @@ -11,7 +11,9 @@ import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorage import { AccessControlTemporalRevoke_Base_Test } from "test/unit/access/AccessControl/Temporal/Revoke/AccessControlTemporalRevokeBase.t.sol"; -import {AccessControlTemporalModHarness} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; +import { + AccessControlTemporalModHarness +} from "test/utils/harnesses/access/AccessControl/AccessControlTemporalModHarness.sol"; /** * @dev BTT spec: test/trees/AccessControl.tree diff --git a/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..bc461796 --- /dev/null +++ b/test/unit/interfaceDetection/ERC165/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC165Facet_Base_Test} from "test/unit/interfaceDetection/ERC165/ERC165FacetBase.t.sol"; +import {IERC165} from "src/interfaceDetection/ERC165/ERC165Facet.sol"; + +/** + * @dev BTT spec: test/trees/ERC165.tree + */ +contract ExportSelectors_ERC165Facet_Unit_Test is ERC165Facet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = erc165Facet.exportSelectors(); + bytes memory expected = abi.encodePacked(IERC165.supportsInterface.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol b/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol index 9e3376c2..d0792033 100644 --- a/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol +++ b/test/utils/harnesses/interfaceDetection/ERC165/ERC165FacetHarness.sol @@ -13,7 +13,7 @@ import {ERC165Facet} from "src/interfaceDetection/ERC165/ERC165Facet.sol"; */ contract ERC165FacetHarness is ERC165Facet { function initialize() external { - // No-op; storage is implicitly available + /* No-op; storage is implicitly available */ } function registerInterface(bytes4 _interfaceId) external { diff --git a/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol b/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol index 79db11d3..ded6c4a5 100644 --- a/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol +++ b/test/utils/harnesses/interfaceDetection/ERC165/ERC165Harness.sol @@ -13,7 +13,7 @@ import "src/interfaceDetection/ERC165/ERC165Mod.sol" as ERC165Mod; */ contract ERC165Harness { function initialize() external { - // No-op; storage is implicitly available + /* No-op; storage is implicitly available */ } function registerInterface(bytes4 _interfaceId) external { From 1f7d508588f1513006021a46f690711541dce7e7 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 16:50:27 -0400 Subject: [PATCH 13/25] add ERC721 BTT testsuites --- .../ERC721/Metadata/ERC721MetadataFacet.sol | 26 +- test/token/ERC721/ERC721/ERC721 | 177 ------- test/token/ERC721/ERC721/ERC721BurnFacet | 62 --- test/token/ERC721/ERC721/ERC721Facet | 265 ---------- .../ERC721/harnesses/ERC721BurnFacetHarness | 44 -- .../ERC721/harnesses/ERC721FacetHarness | 42 -- .../ERC721/ERC721/harnesses/ERC721Harness | 64 --- .../ERC721/ERC721Enumerable/ERC721Enumerable | 452 ----------------- .../ERC721EnumerableBurnFacet | 85 ---- .../ERC721Enumerable/ERC721EnumerableFacet | 459 ------------------ .../ERC721EnumerableBurnFacetHarness | 78 --- .../harnesses/ERC721EnumerableFacetHarness | 48 -- .../harnesses/ERC721EnumerableHarness | 125 ----- test/trees/ERC721.tree | 117 +++++ .../Approve/ERC721ApproveFacetBase.t.sol | 27 ++ .../ERC721/Approve/facet/fuzz/approve.t.sol | 111 +++++ .../Approve/facet/fuzz/exportSelectors.t.sol | 22 + .../ERC721/Burn/ERC721BurnFacetBase.t.sol | 27 ++ .../token/ERC721/Burn/facet/fuzz/burn.t.sol | 181 +++++++ .../Burn/facet/fuzz/exportSelectors.t.sol | 22 + .../ERC721/Data/ERC721DataFacetBase.t.sol | 27 ++ .../token/ERC721/Data/facet/fuzz/data.t.sol | 88 ++++ .../Data/facet/fuzz/exportSelectors.t.sol | 26 + .../Enumerable/Burn/facet/fuzz/burn.t.sol | 150 ++++++ .../Burn/facet/fuzz/exportSelectors.t.sol | 23 + .../Enumerable/Data/facet/fuzz/data.t.sol | 91 ++++ .../Data/facet/fuzz/exportSelectors.t.sol | 27 ++ .../ERC721EnumerableBurnFacetBase.t.sol | 23 + .../ERC721EnumerableDataFacetBase.t.sol | 23 + .../ERC721EnumerableTransferFacetBase.t.sol | 23 + .../Transfer/facet/fuzz/exportSelectors.t.sol | 29 ++ .../Transfer/facet/fuzz/transfer.t.sol | 314 ++++++++++++ .../Metadata/ERC721MetadataFacetBase.t.sol | 45 ++ .../ERC721/Metadata/facet/fuzz/metadata.t.sol | 114 +++++ .../Transfer/ERC721TransferFacetBase.t.sol | 27 ++ .../Transfer/facet/fuzz/exportSelectors.t.sol | 25 + .../ERC721/Transfer/facet/fuzz/transfer.t.sol | 388 +++++++++++++++ test/utils/storage/ERC721StorageUtils.sol | 177 +++++++ 38 files changed, 2140 insertions(+), 1914 deletions(-) delete mode 100644 test/token/ERC721/ERC721/ERC721 delete mode 100644 test/token/ERC721/ERC721/ERC721BurnFacet delete mode 100644 test/token/ERC721/ERC721/ERC721Facet delete mode 100644 test/token/ERC721/ERC721/harnesses/ERC721BurnFacetHarness delete mode 100644 test/token/ERC721/ERC721/harnesses/ERC721FacetHarness delete mode 100644 test/token/ERC721/ERC721/harnesses/ERC721Harness delete mode 100644 test/token/ERC721/ERC721Enumerable/ERC721Enumerable delete mode 100644 test/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet delete mode 100644 test/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet delete mode 100644 test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableBurnFacetHarness delete mode 100644 test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableFacetHarness delete mode 100644 test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableHarness create mode 100644 test/trees/ERC721.tree create mode 100644 test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Approve/facet/fuzz/approve.t.sol create mode 100644 test/unit/token/ERC721/Approve/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/token/ERC721/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Data/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol create mode 100644 test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol create mode 100644 test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol create mode 100644 test/unit/token/ERC721/Transfer/facet/fuzz/exportSelectors.t.sol create mode 100644 test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol create mode 100644 test/utils/storage/ERC721StorageUtils.sol diff --git a/src/token/ERC721/Metadata/ERC721MetadataFacet.sol b/src/token/ERC721/Metadata/ERC721MetadataFacet.sol index a618bf2c..b37a3355 100644 --- a/src/token/ERC721/Metadata/ERC721MetadataFacet.sol +++ b/src/token/ERC721/Metadata/ERC721MetadataFacet.sol @@ -28,18 +28,6 @@ contract ERC721MetadataFacet { string baseURI; } - /** - * @notice Returns a pointer to the ERC-721 storage struct. - * @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. - * @return s The ERC721Storage struct in storage. - */ - function getStorage() internal pure returns (ERC721MetadataStorage storage s) { - bytes32 position = STORAGE_POSITION; - assembly { - s.slot := position - } - } - bytes32 constant ERC721_STORAGE_POSITION = keccak256("erc721"); /** @@ -52,13 +40,25 @@ contract ERC721MetadataFacet { mapping(uint256 tokenId => address approved) approved; } + /** + * @notice Returns a pointer to the ERC-721 storage struct. + * @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. + * @return s The ERC721Storage struct in storage. + */ + function getStorage() internal pure returns (ERC721MetadataStorage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + /** * @notice Returns a pointer to the ERC-721 storage struct. * @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. * @return s The ERC721Storage struct in storage. */ function getERC721Storage() internal pure returns (ERC721Storage storage s) { - bytes32 position = STORAGE_POSITION; + bytes32 position = ERC721_STORAGE_POSITION; assembly { s.slot := position } diff --git a/test/token/ERC721/ERC721/ERC721 b/test/token/ERC721/ERC721/ERC721 deleted file mode 100644 index 2c9a6424..00000000 --- a/test/token/ERC721/ERC721/ERC721 +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC721Harness} from "./harnesses/ERC721Harness.sol"; -import "../../../../src/token/ERC721/ERC721/ERC721Mod.sol" as ERC721Mod; - -contract LibERC721Test is Test { - ERC721Harness public harness; - - address public alice; - address public bob; - address public charlie; - - string constant TOKEN_NAME = "Test Token"; - string constant TOKEN_SYMBOL = "TEST"; - string constant BASE_URI = "https://example.com/api/nft/"; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC721Harness(); - harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, BASE_URI); - } - - /** - * ============================================ - * Metadata Tests - * ============================================ - */ - - function test_Name() public view { - assertEq(harness.name(), TOKEN_NAME); - } - - function test_Symbol() public view { - assertEq(harness.symbol(), TOKEN_SYMBOL); - } - - function test_baseURI() public view { - assertEq(harness.baseURI(), BASE_URI); - } - - /** - * ============================================ - * TransferFrom Tests - * ============================================ - */ - - function test_TransferFrom() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_TransferToSelf() public { - uint256 tokenId = 2; - - harness.mint(charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - - vm.prank(charlie); - harness.transferFrom(charlie, charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_TransferFuzz(address from, address to, uint256 tokenId) public { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(from, tokenId); - assertEq(harness.ownerOf(tokenId), from); - - vm.prank(from); - harness.transferFrom(from, to, tokenId); - assertEq(harness.ownerOf(tokenId), to); - } - - function test_TransferRevertWhenTransferFromNonExistentToken() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721Mod.ERC721NonexistentToken.selector, tokenId)); - harness.transferFrom(alice, bob, tokenId); - } - - function test_TransferRevertWhenSenderIsNotOwnerOrApproved() public { - uint256 tokenId = 4; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - vm.prank(bob); - vm.expectRevert(abi.encodeWithSelector(ERC721Mod.ERC721InsufficientApproval.selector, bob, tokenId)); - harness.transferFrom(alice, charlie, tokenId); - } - - /** - * ============================================ - * Mint Tests - * ============================================ - */ - - function test_Mint() public { - uint256 tokenId = 5; - - harness.mint(bob, tokenId); - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_MintMultiple() public { - for (uint256 tokenId = 1; tokenId <= 10; tokenId++) { - harness.mint(charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - } - } - - function test_MintFuzz(address to, uint256 tokenId) public { - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(to, tokenId); - assertEq(harness.ownerOf(tokenId), to); - } - - function test_MintRevertWhenInvalidReceiver() public { - uint256 tokenId = 6; - - vm.expectRevert(abi.encodeWithSelector(ERC721Mod.ERC721InvalidReceiver.selector, address(0))); - harness.mint(address(0), tokenId); - } - - /** - * ============================================ - * Burn Tests - * ============================================ - */ - - function test_Burn() public { - uint256 tokenId = 7; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - harness.burn(tokenId); - assertEq(harness.ownerOf(tokenId), address(0)); - } - - function test_BurnFuzz(address to, uint256 tokenId) public { - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(to, tokenId); - assertEq(harness.ownerOf(tokenId), to); - - harness.burn(tokenId); - assertEq(harness.ownerOf(tokenId), address(0)); - } - - function test_BurnRevertWhenNonExistentToken() public { - uint256 tokenId = 888; - - vm.expectRevert(abi.encodeWithSelector(ERC721Mod.ERC721NonexistentToken.selector, tokenId)); - harness.burn(tokenId); - } -} diff --git a/test/token/ERC721/ERC721/ERC721BurnFacet b/test/token/ERC721/ERC721/ERC721BurnFacet deleted file mode 100644 index 084e6ac3..00000000 --- a/test/token/ERC721/ERC721/ERC721BurnFacet +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC721BurnFacet} from "../../../../src/token/ERC721/ERC721/ERC721BurnFacet.sol"; -import {ERC721BurnFacetHarness} from "./harnesses/ERC721BurnFacetHarness.sol"; - -contract ERC721BurnFacetTest is Test { - ERC721BurnFacetHarness public harness; - - address public alice; - address public bob; - address public charlie; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC721BurnFacetHarness(); - } - - /** - * ============================================ - * Burn Tests - * ============================================ - */ - - function test_Burn() public { - uint256 tokenId = 7; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - vm.prank(alice); - harness.burn(tokenId); - assertEq(harness.ownerOf(tokenId), address(0)); - } - - function test_BurnFuzz(address to, uint256 tokenId) public { - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(to, tokenId); - assertEq(harness.ownerOf(tokenId), to); - - vm.prank(to); - harness.burn(tokenId); - assertEq(harness.ownerOf(tokenId), address(0)); - } - - function test_BurnRevertWhenNonExistentToken() public { - uint256 tokenId = 888; - - vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, tokenId)); - harness.burn(tokenId); - } -} diff --git a/test/token/ERC721/ERC721/ERC721Facet b/test/token/ERC721/ERC721/ERC721Facet deleted file mode 100644 index 0a1587aa..00000000 --- a/test/token/ERC721/ERC721/ERC721Facet +++ /dev/null @@ -1,265 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC721FacetHarness} from "./harnesses/ERC721FacetHarness.sol"; -import {ERC721Facet} from "../../../../src/token/ERC721/ERC721/ERC721Facet.sol"; - -contract ERC721FacetTest is Test { - ERC721FacetHarness public harness; - - address public alice; - address public bob; - address public charlie; - - string constant TOKEN_NAME = "Test Token"; - string constant TOKEN_SYMBOL = "TEST"; - string constant BASE_URI = "https://example.com/api/nft/"; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC721FacetHarness(); - harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, BASE_URI); - } - - /** - * ============================================ - * Metadata Tests - * ============================================ - */ - - function test_name() public view { - assertEq(harness.name(), TOKEN_NAME); - } - - function test_symbol() public view { - assertEq(harness.symbol(), TOKEN_SYMBOL); - } - - function test_baseURI() public view { - assertEq(harness.baseURI(), BASE_URI); - } - - /** - * ============================================ - * TokenURI Tests - * ============================================ - */ - - function test_tokenURI() public { - uint256 tokenId = 1; - string memory expectedURI = string(abi.encodePacked(BASE_URI, "1")); - - harness.mint(alice, tokenId); - - string memory tokenURI = ERC721Facet(address(harness)).tokenURI(tokenId); - assertEq(tokenURI, expectedURI); - } - - function test_tokenOwner() public { - uint256 tokenId = 45; - - harness.mint(alice, tokenId); - - assertEq(harness.ownerOf(tokenId), alice); - } - - /** - * ============================================ - * Approve Tests - * ============================================ - */ - - function test_Approve() public { - uint256 tokenId = 4; - - harness.mint(alice, tokenId); - - vm.prank(alice); - ERC721Facet(address(harness)).approve(bob, tokenId); - - address approved = ERC721Facet(address(harness)).getApproved(tokenId); - assertEq(approved, bob); - } - - function test_ApproveSelfApproval() public { - uint256 tokenId = 6; - - harness.mint(bob, tokenId); - - vm.prank(bob); - ERC721Facet(address(harness)).approve(bob, tokenId); - - address approved = ERC721Facet(address(harness)).getApproved(tokenId); - assertEq(approved, bob); - } - - function test_ApproveClearsOnTransfer() public { - uint256 tokenId = 7; - - harness.mint(alice, tokenId); - - vm.prank(alice); - ERC721Facet(address(harness)).approve(bob, tokenId); - - vm.prank(alice); - ERC721Facet(address(harness)).transferFrom(alice, charlie, tokenId); - - address approved = ERC721Facet(address(harness)).getApproved(tokenId); - assertEq(approved, address(0)); - } - - function test_ApproveFuzz(address owner, address operator, uint256 tokenId) public { - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - vm.assume(owner != operator); - vm.assume(tokenId < type(uint256).max); - - harness.mint(owner, tokenId); - - vm.prank(owner); - ERC721Facet(address(harness)).approve(operator, tokenId); - - address approved = ERC721Facet(address(harness)).getApproved(tokenId); - assertEq(approved, operator); - } - - function test_getApproved() public { - uint256 tokenId = 4; - - harness.mint(alice, tokenId); - - vm.prank(alice); - ERC721Facet(address(harness)).approve(bob, tokenId); - - address approved = ERC721Facet(address(harness)).getApproved(tokenId); - assertEq(approved, bob); - - assertEq(harness.getApproved(tokenId), bob); - } - - /** - * =========================================== - * SetApprovalForAll Tests - * =========================================== - */ - - function test_SetApprovalForAll() public { - vm.prank(alice); - ERC721Facet(address(harness)).setApprovalForAll(bob, true); - - bool isApproved = ERC721Facet(address(harness)).isApprovedForAll(alice, bob); - assertTrue(isApproved); - } - - function test_SetApprovalForAllFuzz(address owner, address operator) public { - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - vm.assume(owner != operator); - - vm.prank(owner); - ERC721Facet(address(harness)).setApprovalForAll(operator, true); - - bool isApproved = ERC721Facet(address(harness)).isApprovedForAll(owner, operator); - assertTrue(isApproved); - } - - /** - * ============================================ - * transferFrom tests - * ============================================ - */ - - function test_transferFrom() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_transferFromToSelf() public { - uint256 tokenId = 2; - - harness.mint(charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - - vm.prank(charlie); - harness.transferFrom(charlie, charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_transferFromFuzz(address from, address to, uint256 tokenId) public { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(from, tokenId); - assertEq(harness.ownerOf(tokenId), from); - - vm.prank(from); - harness.transferFrom(from, to, tokenId); - assertEq(harness.ownerOf(tokenId), to); - } - - function test_transferFromRevertWhenTransferFromNonExistentToken() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721Facet.ERC721NonexistentToken.selector, tokenId)); - harness.transferFrom(alice, bob, tokenId); - } - - /** - * =========================================== - * safeTransferFrom Tests - * =========================================== - */ - - function test_safeTransferFrom() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - assertEq(harness.ownerOf(tokenId), alice); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, tokenId); - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_safeTransferFromToSelf() public { - uint256 tokenId = 2; - - harness.mint(charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - - vm.prank(charlie); - harness.safeTransferFrom(charlie, charlie, tokenId); - assertEq(harness.ownerOf(tokenId), charlie); - } - - /** - * ==================================== - * balanceOf Tests - * ==================================== - */ - - function test_BalanceOf() public { - uint256 tokenId1 = 32; - uint256 tokenId2 = 45; - - harness.mint(alice, tokenId1); - harness.mint(alice, tokenId2); - - assertEq(harness.balanceOf(alice), 2); - } -} diff --git a/test/token/ERC721/ERC721/harnesses/ERC721BurnFacetHarness b/test/token/ERC721/ERC721/harnesses/ERC721BurnFacetHarness deleted file mode 100644 index d121ed7b..00000000 --- a/test/token/ERC721/ERC721/harnesses/ERC721BurnFacetHarness +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC721BurnFacet} from "../../../../../src/token/ERC721/ERC721/ERC721BurnFacet.sol"; - -contract ERC721BurnFacetHarness is ERC721BurnFacet { - /** - * @notice Initialize the ERC721 token storage - * @dev Only used for testing - production diamonds should initialize in constructor - */ - function initialize() external { - ERC721Storage storage s = getStorage(); - } - - /** - * @notice Mints a new ERC-721 token to the specified address. - * @dev Reverts if the receiver address is zero or if the token already exists. - * @param _to The address that will own the newly minted token. - * @param _tokenId The ID of the token to mint. - */ - function mint(address _to, uint256 _tokenId) public { - ERC721Storage storage s = getStorage(); - require(_to != address(0), "ERC721InvalidReceiver"); - require(s.ownerOf[_tokenId] == address(0), "ERC721InvalidSender"); - s.ownerOf[_tokenId] = _to; - unchecked { - s.balanceOf[_to]++; - } - emit Transfer(address(0), _to, _tokenId); - } - - /** - * @notice Returns the owner of a given token ID (raw, does not revert for non-existent tokens). - * @param _tokenId The token ID to query. - * @return The address of the token owner (or address(0) if not minted). - */ - function ownerOf(uint256 _tokenId) public view returns (address) { - return getStorage().ownerOf[_tokenId]; - } -} diff --git a/test/token/ERC721/ERC721/harnesses/ERC721FacetHarness b/test/token/ERC721/ERC721/harnesses/ERC721FacetHarness deleted file mode 100644 index 3f9fcf36..00000000 --- a/test/token/ERC721/ERC721/harnesses/ERC721FacetHarness +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC721Facet} from "../../../../../src/token/ERC721/ERC721/ERC721Facet.sol"; - -contract ERC721FacetHarness is ERC721Facet { - /** - * @notice Initialize the ERC721 token storage - * @dev Only used for testing - production diamonds should initialize in constructor - */ - function initialize(string memory _name, string memory _symbol, string memory _baseURI) external { - ERC721Storage storage s = getStorage(); - s.name = _name; - s.symbol = _symbol; - s.baseURI = _baseURI; - } - - function baseURI() external view returns (string memory) { - return ERC721Facet.getStorage().baseURI; - } - - /** - * @notice Mints a new ERC-721 token to the specified address. - * @dev Reverts if the receiver address is zero or if the token already exists. - * @param _to The address that will own the newly minted token. - * @param _tokenId The ID of the token to mint. - */ - function mint(address _to, uint256 _tokenId) public { - ERC721Storage storage s = getStorage(); - require(_to != address(0), "ERC721InvalidReceiver"); - require(s.ownerOf[_tokenId] == address(0), "ERC721InvalidSender"); - s.ownerOf[_tokenId] = _to; - unchecked { - s.balanceOf[_to]++; - } - emit Transfer(address(0), _to, _tokenId); - } -} diff --git a/test/token/ERC721/ERC721/harnesses/ERC721Harness b/test/token/ERC721/ERC721/harnesses/ERC721Harness deleted file mode 100644 index 788adb94..00000000 --- a/test/token/ERC721/ERC721/harnesses/ERC721Harness +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../../src/token/ERC721/ERC721/ERC721Mod.sol" as ERC721Mod; - -contract ERC721Harness { - /** - * @notice Initialize the ERC721 token storage - * @dev Only used for testing - */ - function initialize(string memory _name, string memory _symbol, string memory _baseURI) external { - ERC721Mod.ERC721Storage storage s = ERC721Mod.getStorage(); - s.name = _name; - s.symbol = _symbol; - s.baseURI = _baseURI; - } - - /** - * @notice Exposes ERC721Mod.mint as an external function - */ - function mint(address _to, uint256 _tokenId) external { - ERC721Mod.mint(_to, _tokenId); - } - - /** - * @notice Exposes ERC721Mod.burn as an external function - */ - function burn(uint256 _tokenId) external { - ERC721Mod.burn(_tokenId); - } - - /** - * @notice Exposes ERC721Mod.transferFrom as an external function - */ - function transferFrom(address _from, address _to, uint256 _tokenId) external { - ERC721Mod.transferFrom(_from, _to, _tokenId); - } - - /** - * @notice Expose owner lookup for a given token id - */ - function ownerOf(uint256 _tokenId) external view returns (address) { - return ERC721Mod.getStorage().ownerOf[_tokenId]; - } - - /** - * @notice Get storage values for testing - */ - function name() external view returns (string memory) { - return ERC721Mod.getStorage().name; - } - - function symbol() external view returns (string memory) { - return ERC721Mod.getStorage().symbol; - } - - function baseURI() external view returns (string memory) { - return ERC721Mod.getStorage().baseURI; - } -} diff --git a/test/token/ERC721/ERC721Enumerable/ERC721Enumerable b/test/token/ERC721/ERC721Enumerable/ERC721Enumerable deleted file mode 100644 index 62e0cf1f..00000000 --- a/test/token/ERC721/ERC721Enumerable/ERC721Enumerable +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import "../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableMod.sol" as ERC721EnumerableMod; -import {ERC721EnumerableHarness} from "./harnesses/ERC721EnumerableHarness.sol"; - -contract LibERC721EnumerableTest is Test { - ERC721EnumerableHarness public harness; - - address public alice; - address public bob; - address public charlie; - - string constant TOKEN_NAME = "Test Token"; - string constant TOKEN_SYMBOL = "TEST"; - string constant BASE_URI = "https://example.com/api/nft/"; - - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC721EnumerableHarness(); - harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, BASE_URI); - } - - /** - * ============================================ - * Metadata Tests - * ============================================ - */ - - function test_Name() public view { - assertEq(harness.name(), TOKEN_NAME); - } - - function test_Symbol() public view { - assertEq(harness.symbol(), TOKEN_SYMBOL); - } - - function test_BaseURI() public view { - assertEq(harness.baseURI(), BASE_URI); - } - - /** - * ============================================ - * Mint Tests - * ============================================ - */ - - function test_Mint() public { - uint256 tokenId = 1; - - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), alice, tokenId); - harness.mint(alice, tokenId); - - assertEq(harness.ownerOf(tokenId), alice); - } - - function test_MintUpdatesOwnership() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - assertEq(harness.ownerOf(tokenId), alice); - } - - function test_MintUpdatesBalance() public { - harness.mint(alice, 1); - assertEq(harness.balanceOf(alice), 1); - - harness.mint(alice, 2); - assertEq(harness.balanceOf(alice), 2); - - harness.mint(bob, 3); - assertEq(harness.balanceOf(bob), 1); - } - - function test_MintUpdatesOwnerTokens() public { - harness.mint(alice, 10); - harness.mint(alice, 20); - harness.mint(alice, 30); - - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 10); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 20); - assertEq(harness.tokenOfOwnerByIndex(alice, 2), 30); - } - - function test_MintUpdatesAllTokens() public { - harness.mint(alice, 1); - harness.mint(bob, 2); - harness.mint(charlie, 3); - - assertEq(harness.totalSupply(), 3); - assertEq(harness.tokenByIndex(0), 1); - assertEq(harness.tokenByIndex(1), 2); - assertEq(harness.tokenByIndex(2), 3); - } - - function test_MintUpdatesIndices() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - - /** - * Verify tokens are at correct indices - */ - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 2); - } - - function test_MintMultipleTokens() public { - for (uint256 i = 1; i <= 10; i++) { - harness.mint(alice, i); - assertEq(harness.ownerOf(i), alice); - } - - assertEq(harness.balanceOf(alice), 10); - assertEq(harness.totalSupply(), 10); - } - - function test_MintToMultipleAddresses() public { - harness.mint(alice, 1); - harness.mint(bob, 2); - harness.mint(charlie, 3); - - assertEq(harness.ownerOf(1), alice); - assertEq(harness.ownerOf(2), bob); - assertEq(harness.ownerOf(3), charlie); - - assertEq(harness.balanceOf(alice), 1); - assertEq(harness.balanceOf(bob), 1); - assertEq(harness.balanceOf(charlie), 1); - } - - function test_MintEmitsTransferEvent() public { - uint256 tokenId = 1; - - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), alice, tokenId); - harness.mint(alice, tokenId); - } - - function test_MintRevertWhenZeroAddress() public { - uint256 tokenId = 1; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721InvalidReceiver.selector, address(0))); - harness.mint(address(0), tokenId); - } - - function test_MintRevertWhenTokenExists() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721InvalidSender.selector, address(0))); - harness.mint(bob, tokenId); - } - - /** - * ============================================ - * Transfer Tests - * ============================================ - */ - - function test_TransferFrom() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, tokenId); - harness.transferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_TransferFromByOwner() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_TransferFromByApproved() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.approve(bob, tokenId); - - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_TransferFromByOperator() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_TransferFromUpdatesOwnership() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_TransferFromUpdatesBalances() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - assertEq(harness.balanceOf(alice), 1); - assertEq(harness.balanceOf(bob), 0); - - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - - assertEq(harness.balanceOf(alice), 0); - assertEq(harness.balanceOf(bob), 1); - } - - function test_TransferFromUpdatesOwnerTokens() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(alice, 3); - - vm.prank(alice); - harness.transferFrom(alice, bob, 2); - - /** - * Alice should have tokens 1 and 3 - */ - assertEq(harness.balanceOf(alice), 2); - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 3); - - /** - * Bob should have token 2 - */ - assertEq(harness.balanceOf(bob), 1); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 2); - } - - function test_TransferFromUpdatesIndices() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(alice, 3); - - vm.prank(alice); - harness.transferFrom(alice, bob, 1); - - /** - * Verify indices are correct after transfer - */ - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 3); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 2); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 1); - } - - function test_TransferFromClearsApproval() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.approve(bob, tokenId); - - assertEq(harness.getApproved(tokenId), bob); - - vm.prank(alice); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.getApproved(tokenId), address(0)); - } - - function test_TransferFromEmitsTransferEvent() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, tokenId); - harness.transferFrom(alice, bob, tokenId); - } - - function test_TransferFromRevertWhenNonexistent() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721NonexistentToken.selector, tokenId)); - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - } - - function test_TransferFromRevertWhenZeroAddress() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721InvalidReceiver.selector, address(0))); - vm.prank(alice); - harness.transferFrom(alice, address(0), tokenId); - } - - function test_TransferFromRevertWhenIncorrectOwner() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721IncorrectOwner.selector, bob, tokenId, alice)); - vm.prank(alice); - harness.transferFrom(bob, charlie, tokenId); - } - - function test_TransferFromRevertWhenUnauthorized() public { - uint256 tokenId = 1; - - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableMod.ERC721InsufficientApproval.selector, bob, tokenId)); - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - } - - /** - * ============================================ - * Enumeration Tests - * ============================================ - */ - - function test_EnumerationAfterMultipleMints() public { - harness.mint(alice, 1); - harness.mint(bob, 2); - harness.mint(alice, 3); - harness.mint(charlie, 4); - harness.mint(bob, 5); - - assertEq(harness.totalSupply(), 5); - assertEq(harness.balanceOf(alice), 2); - assertEq(harness.balanceOf(bob), 2); - assertEq(harness.balanceOf(charlie), 1); - - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 3); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 2); - assertEq(harness.tokenOfOwnerByIndex(bob, 1), 5); - assertEq(harness.tokenOfOwnerByIndex(charlie, 0), 4); - } - - function test_EnumerationAfterTransfers() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(alice, 3); - - vm.prank(alice); - harness.transferFrom(alice, bob, 2); - - assertEq(harness.balanceOf(alice), 2); - assertEq(harness.balanceOf(bob), 1); - - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 3); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 2); - } - - function test_EnumerationComplexScenario() public { - /** - * Mint tokens - */ - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(bob, 3); - harness.mint(charlie, 4); - - assertEq(harness.totalSupply(), 4); - - /** - * Transfer token - */ - vm.prank(alice); - harness.transferFrom(alice, bob, 1); - - assertEq(harness.balanceOf(alice), 1); - assertEq(harness.balanceOf(bob), 2); - - /** - * Verify final state - */ - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 2); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 3); - assertEq(harness.tokenOfOwnerByIndex(bob, 1), 1); - assertEq(harness.tokenOfOwnerByIndex(charlie, 0), 4); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function test_MintFuzz(address to, uint256 tokenId) public { - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(to, tokenId); - - assertEq(harness.ownerOf(tokenId), to); - assertEq(harness.balanceOf(to), 1); - assertEq(harness.totalSupply(), 1); - } - - function test_TransferFromFuzz(address from, address to, uint256 tokenId) public { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(from, tokenId); - - vm.prank(from); - harness.transferFrom(from, to, tokenId); - - assertEq(harness.ownerOf(tokenId), to); - } -} diff --git a/test/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet b/test/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet deleted file mode 100644 index db515bd2..00000000 --- a/test/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC721EnumerableBurnFacet} from "../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet.sol"; -import {ERC721EnumerableBurnFacetHarness} from "./harnesses/ERC721EnumerableBurnFacetHarness.sol"; - -contract ERC721EnumerableBurnFacetTest is Test { - ERC721EnumerableBurnFacetHarness internal token; - - address internal alice; - address internal bob; - address internal charlie; - - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - - function setUp() public { - token = new ERC721EnumerableBurnFacetHarness(); - - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - token.mint(alice, 1); - token.mint(alice, 2); - token.mint(bob, 3); - } - - function test_Burn_RemovesTokenAndUpdatesSupply() public { - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, address(0), 1); - token.burn(1); - - assertEq(token.balanceOf(alice), 1); - assertEq(token.totalSupply(), 2); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableBurnFacet.ERC721NonexistentToken.selector, 1)); - token.ownerOf(1); - } - - function test_Burn_ByApprovedOperator() public { - vm.prank(alice); - token.approve(bob, 2); - - vm.prank(bob); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, address(0), 2); - token.burn(2); - - assertEq(token.balanceOf(alice), 1); - assertEq(token.totalSupply(), 2); - } - - function test_Burn_UpdatesEnumerationOrdering() public { - vm.prank(alice); - token.burn(1); - - uint256 remainingToken = token.tokenOfOwnerByIndex(alice, 0); - assertEq(remainingToken, 2); - - /** - * Ensure global enumeration shrinks - */ - assertEq(token.totalSupply(), 2); - } - - function test_RevertWhen_BurnWithoutApproval() public { - vm.expectRevert( - abi.encodeWithSelector(ERC721EnumerableBurnFacet.ERC721InsufficientApproval.selector, charlie, 2) - ); - vm.prank(charlie); - token.burn(2); - } - - function test_RevertWhen_BurnNonexistentToken() public { - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableBurnFacet.ERC721NonexistentToken.selector, 99)); - vm.prank(alice); - token.burn(99); - } -} diff --git a/test/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet b/test/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet deleted file mode 100644 index 08d4da21..00000000 --- a/test/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet +++ /dev/null @@ -1,459 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {ERC721EnumerableFacetHarness} from "./harnesses/ERC721EnumerableFacetHarness.sol"; -import {ERC721EnumerableFacet} from "../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol"; - -contract ERC721EnumerableFacetTest is Test { - ERC721EnumerableFacetHarness public harness; - - address public alice; - address public bob; - address public charlie; - - string constant TOKEN_NAME = "Test Token"; - string constant TOKEN_SYMBOL = "TEST"; - string constant BASE_URI = "https://example.com/api/nft/"; - - event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); - event Approval(address indexed _owner, address indexed _to, uint256 indexed _tokenId); - event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - - harness = new ERC721EnumerableFacetHarness(); - harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, BASE_URI); - } - - /** - * ============================================ - * Metadata Tests - * ============================================ - */ - - function test_Name() public view { - assertEq(harness.name(), TOKEN_NAME); - } - - function test_Symbol() public view { - assertEq(harness.symbol(), TOKEN_SYMBOL); - } - - function test_TokenURI() public { - uint256 tokenId = 1; - string memory expectedURI = string(abi.encodePacked(BASE_URI, "1")); - - harness.mint(alice, tokenId); - - string memory tokenURI = harness.tokenURI(tokenId); - assertEq(tokenURI, expectedURI); - } - - function test_TokenURIWithZeroTokenId() public { - uint256 tokenId = 0; - string memory expectedURI = string(abi.encodePacked(BASE_URI, "0")); - - harness.mint(alice, tokenId); - - string memory tokenURI = harness.tokenURI(tokenId); - assertEq(tokenURI, expectedURI); - } - - function test_TokenURIRevertWhenNonexistent() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721NonexistentToken.selector, tokenId)); - harness.tokenURI(tokenId); - } - - /** - * ============================================ - * Balance and Ownership Tests - * ============================================ - */ - - function test_BalanceOf() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(bob, 3); - - assertEq(harness.balanceOf(alice), 2); - assertEq(harness.balanceOf(bob), 1); - assertEq(harness.balanceOf(charlie), 0); - } - - function test_BalanceOfRevertWhenZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721InvalidOwner.selector, address(0))); - harness.balanceOf(address(0)); - } - - function test_OwnerOf() public { - uint256 tokenId = 42; - harness.mint(alice, tokenId); - - assertEq(harness.ownerOf(tokenId), alice); - } - - function test_OwnerOfRevertWhenNonexistent() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721NonexistentToken.selector, tokenId)); - harness.ownerOf(tokenId); - } - - /** - * ============================================ - * Enumeration Tests - * ============================================ - */ - - function test_TotalSupply() public { - assertEq(harness.totalSupply(), 0); - - harness.mint(alice, 1); - assertEq(harness.totalSupply(), 1); - - harness.mint(bob, 2); - assertEq(harness.totalSupply(), 2); - - harness.mint(charlie, 3); - assertEq(harness.totalSupply(), 3); - } - - function test_TokenOfOwnerByIndex() public { - harness.mint(alice, 10); - harness.mint(alice, 20); - harness.mint(alice, 30); - - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 10); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 20); - assertEq(harness.tokenOfOwnerByIndex(alice, 2), 30); - } - - function test_TokenOfOwnerByIndexMultipleTokens() public { - harness.mint(alice, 1); - harness.mint(bob, 2); - harness.mint(alice, 3); - harness.mint(bob, 4); - - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 3); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 2); - assertEq(harness.tokenOfOwnerByIndex(bob, 1), 4); - } - - function test_TokenOfOwnerByIndexRevertWhenOutOfBounds() public { - harness.mint(alice, 1); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721OutOfBoundsIndex.selector, alice, 1)); - harness.tokenOfOwnerByIndex(alice, 1); - } - - /** - * ============================================ - * Approval Tests - * ============================================ - */ - - function test_Approve() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Approval(alice, bob, tokenId); - harness.approve(bob, tokenId); - - assertEq(harness.getApproved(tokenId), bob); - } - - function test_ApproveByOperator() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.setApprovalForAll(charlie, true); - - vm.prank(charlie); - harness.approve(bob, tokenId); - - assertEq(harness.getApproved(tokenId), bob); - } - - function test_ApproveSelfApproval() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.approve(alice, tokenId); - - assertEq(harness.getApproved(tokenId), alice); - } - - function test_ApproveClearsOnTransfer() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.approve(bob, tokenId); - - vm.prank(alice); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.getApproved(tokenId), address(0)); - } - - function test_ApproveRevertWhenNonexistent() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721NonexistentToken.selector, tokenId)); - vm.prank(alice); - harness.approve(bob, tokenId); - } - - function test_ApproveRevertWhenUnauthorized() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721InvalidApprover.selector, bob)); - vm.prank(bob); - harness.approve(charlie, tokenId); - } - - function test_GetApproved() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - assertEq(harness.getApproved(tokenId), address(0)); - - vm.prank(alice); - harness.approve(bob, tokenId); - - assertEq(harness.getApproved(tokenId), bob); - } - - function test_SetApprovalForAll() public { - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit ApprovalForAll(alice, bob, true); - harness.setApprovalForAll(bob, true); - - assertTrue(harness.isApprovedForAll(alice, bob)); - - vm.prank(alice); - harness.setApprovalForAll(bob, false); - - assertFalse(harness.isApprovedForAll(alice, bob)); - } - - function test_SetApprovalForAllRevertWhenZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721InvalidOperator.selector, address(0))); - vm.prank(alice); - harness.setApprovalForAll(address(0), true); - } - - function test_IsApprovedForAll() public { - assertFalse(harness.isApprovedForAll(alice, bob)); - - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - assertTrue(harness.isApprovedForAll(alice, bob)); - } - - /** - * ============================================ - * Transfer Tests - * ============================================ - */ - - function test_TransferFrom() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - vm.expectEmit(true, true, true, true); - emit Transfer(alice, bob, tokenId); - harness.transferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - assertEq(harness.balanceOf(alice), 0); - assertEq(harness.balanceOf(bob), 1); - } - - function test_TransferFromByApproved() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.approve(bob, tokenId); - - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_TransferFromByOperator() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.setApprovalForAll(bob, true); - - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - - assertEq(harness.ownerOf(tokenId), charlie); - } - - function test_TransferFromToSelf() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.transferFrom(alice, alice, tokenId); - - assertEq(harness.ownerOf(tokenId), alice); - assertEq(harness.balanceOf(alice), 1); - } - - function test_TransferFromUpdatesEnumeration() public { - harness.mint(alice, 1); - harness.mint(alice, 2); - harness.mint(alice, 3); - - vm.prank(alice); - harness.transferFrom(alice, bob, 2); - - /** - * Alice should have tokens 1 and 3 - */ - assertEq(harness.balanceOf(alice), 2); - assertEq(harness.tokenOfOwnerByIndex(alice, 0), 1); - assertEq(harness.tokenOfOwnerByIndex(alice, 1), 3); - - /** - * Bob should have token 2 - */ - assertEq(harness.balanceOf(bob), 1); - assertEq(harness.tokenOfOwnerByIndex(bob, 0), 2); - } - - function test_TransferFromRevertWhenNonexistent() public { - uint256 tokenId = 999; - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721NonexistentToken.selector, tokenId)); - vm.prank(alice); - harness.transferFrom(alice, bob, tokenId); - } - - function test_TransferFromRevertWhenUnauthorized() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721InsufficientApproval.selector, bob, tokenId)); - vm.prank(bob); - harness.transferFrom(alice, charlie, tokenId); - } - - function test_TransferFromRevertWhenZeroAddress() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableFacet.ERC721InvalidReceiver.selector, address(0))); - vm.prank(alice); - harness.transferFrom(alice, address(0), tokenId); - } - - function test_TransferFromRevertWhenIncorrectOwner() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.expectRevert( - abi.encodeWithSelector(ERC721EnumerableFacet.ERC721IncorrectOwner.selector, bob, tokenId, alice) - ); - vm.prank(alice); - harness.transferFrom(bob, charlie, tokenId); - } - - function test_SafeTransferFrom() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_SafeTransferFromWithData() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, tokenId, "test data"); - - assertEq(harness.ownerOf(tokenId), bob); - } - - function test_SafeTransferFromToEOA() public { - uint256 tokenId = 1; - harness.mint(alice, tokenId); - - vm.prank(alice); - harness.safeTransferFrom(alice, bob, tokenId); - - assertEq(harness.ownerOf(tokenId), bob); - } - - /** - * ============================================ - * Fuzz Tests - * ============================================ - */ - - function test_ApproveFuzz(address owner, address operator, uint256 tokenId) public { - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(owner, tokenId); - - vm.prank(owner); - harness.approve(operator, tokenId); - - assertEq(harness.getApproved(tokenId), operator); - } - - function test_TransferFromFuzz(address from, address to, uint256 tokenId) public { - vm.assume(from != address(0)); - vm.assume(to != address(0)); - vm.assume(tokenId < type(uint256).max); - - harness.mint(from, tokenId); - - vm.prank(from); - harness.transferFrom(from, to, tokenId); - - assertEq(harness.ownerOf(tokenId), to); - } - - function test_SetApprovalForAllFuzz(address owner, address operator) public { - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - - vm.prank(owner); - harness.setApprovalForAll(operator, true); - - assertTrue(harness.isApprovedForAll(owner, operator)); - } -} diff --git a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableBurnFacetHarness b/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableBurnFacetHarness deleted file mode 100644 index 7e62c062..00000000 --- a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableBurnFacetHarness +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import { - ERC721EnumerableBurnFacet -} from "../../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableBurnFacet.sol"; -import "../../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableMod.sol" as ERC721EnumerableMod; - -/** - * @title ERC721EnumerableBurnFacetHarness - * @notice Lightweight harness combining read/transfer functionality with burn entrypoint for testing. - */ -contract ERC721EnumerableBurnFacetHarness is ERC721EnumerableBurnFacet { - /** - * @notice Initialize collection metadata for tests. - */ - /** - * function initialize(string memory _name, string memory _symbol, string memory _baseURI) external { - * ERC721EnumerableStorage storage s = getStorage(); - * s.name = _name; - * s.symbol = _symbol; - * s.baseURI = _baseURI; - * } - */ - /** - * @notice Mint helper for tests (not part of production facet surface). - */ - function mint(address _to, uint256 _tokenId) external { - ERC721EnumerableMod.mint(_to, _tokenId); - } - - function balanceOf(address _owner) external view returns (uint256) { - return getStorage().ownerTokens[_owner].length; - } - - function ownerOf(uint256 _tokenId) external view returns (address) { - address owner = getStorage().ownerOf[_tokenId]; - if (owner == address(0)) { - revert ERC721NonexistentToken(_tokenId); - } - return owner; - } - - function totalSupply() external view returns (uint256) { - return getStorage().allTokens.length; - } - - function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) { - ERC721EnumerableStorage storage s = getStorage(); - if (_index >= s.ownerTokens[_owner].length) { - /** - * We don't have the error defined in this facet, so we revert with generic or define it? - * The test expects ERC721OutOfBoundsIndex? - * Wait, the test `test_Burn_UpdatesEnumerationOrdering` calls `tokenOfOwnerByIndex`. - * If I don't implement it correctly, it fails. - * The test doesn't check for revert on this function, it checks return value. - */ - revert("Index out of bounds"); - } - return s.ownerTokens[_owner][_index]; - } - - function approve(address _to, uint256 _tokenId) external { - ERC721EnumerableStorage storage s = getStorage(); - address owner = s.ownerOf[_tokenId]; - if (owner == address(0)) { - revert ERC721NonexistentToken(_tokenId); - } - /** - * Simplified approve for testing burn - */ - s.approved[_tokenId] = _to; - } -} diff --git a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableFacetHarness b/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableFacetHarness deleted file mode 100644 index 46530e6a..00000000 --- a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableFacetHarness +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {ERC721EnumerableFacet} from "../../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableFacet.sol"; - -/** - * @title ERC721EnumerableFacetHarness - * @notice Test harness for ERC721EnumerableFacet that adds initialization and minting helpers - */ -contract ERC721EnumerableFacetHarness is ERC721EnumerableFacet { - /** - * @notice Initialize the ERC721 enumerable token storage - * @dev Only used for testing - production diamonds should initialize in constructor - */ - function initialize(string memory _name, string memory _symbol, string memory _baseURI) external { - ERC721EnumerableStorage storage s = getStorage(); - s.name = _name; - s.symbol = _symbol; - s.baseURI = _baseURI; - } - - /** - * @notice Mints a new ERC-721 token to the specified address. - * @dev Reverts if the receiver address is zero or if the token already exists. - * @param _to The address that will own the newly minted token. - * @param _tokenId The ID of the token to mint. - */ - function mint(address _to, uint256 _tokenId) external { - ERC721EnumerableStorage storage s = getStorage(); - if (_to == address(0)) { - revert ERC721InvalidReceiver(address(0)); - } - if (s.ownerOf[_tokenId] != address(0)) { - revert ERC721InvalidSender(address(0)); - } - - s.ownerOf[_tokenId] = _to; - s.ownerTokensIndex[_tokenId] = s.ownerTokens[_to].length; - s.ownerTokens[_to].push(_tokenId); - s.allTokensIndex[_tokenId] = s.allTokens.length; - s.allTokens.push(_tokenId); - emit Transfer(address(0), _to, _tokenId); - } -} diff --git a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableHarness b/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableHarness deleted file mode 100644 index be4a48d9..00000000 --- a/test/token/ERC721/ERC721Enumerable/harnesses/ERC721EnumerableHarness +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import "../../../../../src/token/ERC721/ERC721Enumerable/ERC721EnumerableMod.sol" as ERC721EnumerableMod; - -/** - * @title ERC721EnumerableHarness - * @notice Test harness that exposes LibERC721Enumerable library functions as external functions - */ -contract ERC721EnumerableHarness { - /** - * @notice Initialize the ERC721 enumerable token storage - * @dev Only used for testing - */ - function initialize(string memory _name, string memory _symbol, string memory _baseURI) external { - ERC721EnumerableMod.ERC721EnumerableStorage storage s = ERC721EnumerableMod.getStorage(); - s.name = _name; - s.symbol = _symbol; - s.baseURI = _baseURI; - } - - /** - * @notice Exposes ERC721EnumerableMod.mint as an external function - */ - function mint(address _to, uint256 _tokenId) external { - ERC721EnumerableMod.mint(_to, _tokenId); - } - - /** - * @notice Exposes ERC721EnumerableMod.burn as an external function - */ - function burn(uint256 _tokenId) external { - ERC721EnumerableMod.burn(_tokenId, msg.sender); - } - - /** - * @notice Exposes ERC721EnumerableMod.transferFrom as an external function - */ - function transferFrom(address _from, address _to, uint256 _tokenId) external { - ERC721EnumerableMod.transferFrom(_from, _to, _tokenId, msg.sender); - } - - /** - * @notice Expose owner lookup for a given token id - */ - function ownerOf(uint256 _tokenId) external view returns (address) { - return ERC721EnumerableMod.getStorage().ownerOf[_tokenId]; - } - - /** - * @notice Get balance of an address - */ - function balanceOf(address _owner) external view returns (uint256) { - return ERC721EnumerableMod.getStorage().ownerTokens[_owner].length; - } - - /** - * @notice Get total supply - */ - function totalSupply() external view returns (uint256) { - return ERC721EnumerableMod.getStorage().allTokens.length; - } - - /** - * @notice Get token by index in owner's list - */ - function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) { - return ERC721EnumerableMod.getStorage().ownerTokens[_owner][_index]; - } - - /** - * @notice Get token by global index - */ - function tokenByIndex(uint256 _index) external view returns (uint256) { - return ERC721EnumerableMod.getStorage().allTokens[_index]; - } - - /** - * @notice Get storage values for testing - */ - function name() external view returns (string memory) { - return ERC721EnumerableMod.getStorage().name; - } - - function symbol() external view returns (string memory) { - return ERC721EnumerableMod.getStorage().symbol; - } - - function baseURI() external view returns (string memory) { - return ERC721EnumerableMod.getStorage().baseURI; - } - - /** - * @notice Set approval for a token - */ - function approve(address _approved, uint256 _tokenId) external { - ERC721EnumerableMod.ERC721EnumerableStorage storage s = ERC721EnumerableMod.getStorage(); - s.approved[_tokenId] = _approved; - } - - /** - * @notice Get approved address for a token - */ - function getApproved(uint256 _tokenId) external view returns (address) { - return ERC721EnumerableMod.getStorage().approved[_tokenId]; - } - - /** - * @notice Set approval for all tokens - */ - function setApprovalForAll(address _operator, bool _approved) external { - ERC721EnumerableMod.getStorage().isApprovedForAll[msg.sender][_operator] = _approved; - } - - /** - * @notice Check if operator is approved for all - */ - function isApprovedForAll(address _owner, address _operator) external view returns (bool) { - return ERC721EnumerableMod.getStorage().isApprovedForAll[_owner][_operator]; - } -} diff --git a/test/trees/ERC721.tree b/test/trees/ERC721.tree new file mode 100644 index 00000000..62a0e332 --- /dev/null +++ b/test/trees/ERC721.tree @@ -0,0 +1,117 @@ +Data +β”œβ”€β”€ BalanceOf +β”‚ β”œβ”€β”€ when account is queried +β”‚ β”‚ └── it should return the balance for that account +β”‚ └── when account is the zero address +β”‚ └── it should revert with ERC721InvalidOwner +β”œβ”€β”€ OwnerOf +β”‚ β”œβ”€β”€ when token exists +β”‚ β”‚ └── it should return the owner address +β”‚ └── when token does not exist +β”‚ └── it should revert with ERC721NonexistentToken +β”œβ”€β”€ GetApproved +β”‚ β”œβ”€β”€ when token does not exist +β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ β”œβ”€β”€ when no account is approved +β”‚ β”‚ └── it should return the zero address +β”‚ └── when an account is approved +β”‚ └── it should return the approved account +β”œβ”€β”€ IsApprovedForAll +β”‚ β”œβ”€β”€ when operator has not been approved +β”‚ β”‚ └── it should return false +β”‚ └── when operator has been approved +β”‚ └── it should return true +└── TokenURI + β”œβ”€β”€ when token exists + β”‚ └── it should return baseURI concatenated with tokenId + └── when token does not exist + └── it should revert with ERC721NonexistentToken + +Approve +β”œβ”€β”€ Approve +β”‚ β”œβ”€β”€ when token does not exist +β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ β”œβ”€β”€ when caller is not owner, not approved, and not operator +β”‚ β”‚ └── it should revert with ERC721InvalidApprover or equivalent +β”‚ β”œβ”€β”€ when caller is owner +β”‚ β”‚ └── it should set approved[tokenId] to spender and emit Approval +β”‚ └── when caller is operator approved for owner +β”‚ └── it should set approved[tokenId] to spender and emit Approval +└── SetApprovalForAll + β”œβ”€β”€ when operator is the zero address + β”‚ └── it should revert with ERC721InvalidOperator or equivalent + └── when operator is a non-zero address + β”œβ”€β”€ it should set isApprovedForAll[owner][operator] to approved + └── it should emit ApprovalForAll + +Transfer +β”œβ”€β”€ TransferFrom +β”‚ β”œβ”€β”€ when token does not exist +β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ β”œβ”€β”€ when to is the zero address +β”‚ β”‚ └── it should revert with ERC721InvalidReceiver +β”‚ β”œβ”€β”€ when caller is not owner, not approved, and not operator +β”‚ β”‚ └── it should revert with ERC721InsufficientApproval or equivalent +β”‚ β”œβ”€β”€ when caller is owner +β”‚ β”‚ └── it should transfer ownership, update balances, clear approval, and emit Transfer +β”‚ β”œβ”€β”€ when caller is approved for the tokenId +β”‚ β”‚ └── it should transfer ownership, update balances, clear approval, and emit Transfer +β”‚ └── when caller is operator approved for owner +β”‚ └── it should transfer ownership, update balances, clear approval, and emit Transfer +└── SafeTransferFrom + β”œβ”€β”€ when token does not exist + β”‚ └── it should revert with ERC721NonexistentToken + β”œβ”€β”€ when to is the zero address + β”‚ └── it should revert with ERC721InvalidReceiver + β”œβ”€β”€ when to is a non-receiver contract + β”‚ └── it should revert + β”œβ”€β”€ when to is an ERC721Receiver contract and returns the correct selector + β”‚ └── it should transfer ownership, update balances, clear approval, emit Transfer, and not revert + └── when to is an ERC721Receiver contract and does not return the correct selector + └── it should revert + +Burn +β”œβ”€β”€ Burn +β”‚ β”œβ”€β”€ when token does not exist +β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ β”œβ”€β”€ when caller is not owner, not approved, and not operator +β”‚ β”‚ └── it should revert with ERC721InsufficientApproval or equivalent +β”‚ └── when caller is owner, approved, or operator +β”‚ β”œβ”€β”€ it should clear ownership and approval +β”‚ β”œβ”€β”€ it should decrement the owner's balance +β”‚ └── it should emit Transfer to the zero address +└── BurnBatch (if supported) + β”œβ”€β”€ when any token does not exist + β”‚ └── it should revert with ERC721NonexistentToken + └── when all tokens exist and caller is authorized + β”œβ”€β”€ it should burn all tokens and update balances + └── it should emit Transfer events to the zero address for each token + +Enumerable +β”œβ”€β”€ TotalSupply +β”‚ └── when queried +β”‚ └── it should return the number of existing tokens +β”œβ”€β”€ TokenOfOwnerByIndex +β”‚ β”œβ”€β”€ when index is out of bounds +β”‚ β”‚ └── it should revert with ERC721OutOfBoundsIndex or equivalent +β”‚ └── when index is within bounds +β”‚ └── it should return the tokenId at that index for the owner +└── TokenByIndex + β”œβ”€β”€ when index is out of bounds + β”‚ └── it should revert with ERC721OutOfBoundsIndex or equivalent + └── when index is within bounds + └── it should return the tokenId at that index in the global list + +Metadata +β”œβ”€β”€ Name +β”‚ └── when set (via Mod) +β”‚ └── it should return the token collection name +β”œβ”€β”€ Symbol +β”‚ └── when set (via Mod) +β”‚ └── it should return the token collection symbol +└── TokenURI + β”œβ”€β”€ when baseURI is set + β”‚ └── it should return baseURI concatenated with tokenId for existing tokens + └── when baseURI is empty + └── it should return an empty string or documented behavior + diff --git a/test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol b/test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol new file mode 100644 index 00000000..b80bdec8 --- /dev/null +++ b/test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol @@ -0,0 +1,27 @@ +// 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 {ERC721ApproveFacet} from "src/token/ERC721/Approve/ERC721ApproveFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721ApproveFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721ApproveFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721ApproveFacet(); + vm.label(address(facet), "ERC721ApproveFacet"); + } + + function _mint(address to, uint256 tokenId) internal { + address(facet).mint(to, tokenId); + } +} + diff --git a/test/unit/token/ERC721/Approve/facet/fuzz/approve.t.sol b/test/unit/token/ERC721/Approve/facet/fuzz/approve.t.sol new file mode 100644 index 00000000..dd82b235 --- /dev/null +++ b/test/unit/token/ERC721/Approve/facet/fuzz/approve.t.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721ApproveFacet_Base_Test} from "test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721ApproveFacet} from "src/token/ERC721/Approve/ERC721ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Approve_ERC721ApproveFacet_Fuzz_Unit_Test is ERC721ApproveFacet_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Approve_WhenTokenDoesNotExist(address to, uint256 tokenId) external { + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721ApproveFacet.ERC721NonexistentToken.selector, tokenId)); + facet.approve(to, tokenId); + } + + function testFuzz_ShouldRevert_Approve_WhenCallerIsNotOwnerOrOperator( + address owner, + address caller, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(caller != owner); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + vm.stopPrank(); + vm.prank(caller); + + vm.expectRevert(abi.encodeWithSelector(ERC721ApproveFacet.ERC721InvalidApprover.selector, caller)); + facet.approve(to, tokenId); + } + + function testFuzz_ShouldApprove_WhenCallerIsOwner(address owner, address to, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721ApproveFacet.Approval(owner, to, tokenId); + facet.approve(to, tokenId); + + assertEq(address(facet).getApproved(tokenId), to, "approved(tokenId)"); + } + + function testFuzz_ShouldApprove_WhenCallerIsOperator(address owner, address operator, address to, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(operator != owner); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + address(facet).setApprovedForAll(owner, operator, true); + + vm.stopPrank(); + vm.prank(operator); + + vm.expectEmit(address(facet)); + emit ERC721ApproveFacet.Approval(owner, to, tokenId); + facet.approve(to, tokenId); + + assertEq(address(facet).getApproved(tokenId), to, "approved(tokenId)"); + } + + function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress(bool approved) external { + vm.expectRevert(abi.encodeWithSelector(ERC721ApproveFacet.ERC721InvalidOperator.selector, address(0))); + facet.setApprovalForAll(address(0), approved); + } + + function testFuzz_ShouldSetApprovalForAll_WhenOperatorIsNotCaller(address operator, bool approved) external { + vm.assume(operator != address(0)); + + vm.expectEmit(address(facet)); + emit ERC721ApproveFacet.ApprovalForAll(users.alice, operator, approved); + facet.setApprovalForAll(operator, approved); + + bool result = address(facet).isApprovedForAll(users.alice, operator); + assertEq(result, approved, "isApprovedForAll(caller, operator)"); + } + + function testFuzz_ShouldSetApprovalForAll_WhenOperatorIsCaller(bool approved) external { + address caller = users.alice; + + vm.expectEmit(address(facet)); + emit ERC721ApproveFacet.ApprovalForAll(caller, caller, approved); + facet.setApprovalForAll(caller, approved); + + bool result = address(facet).isApprovedForAll(caller, caller); + assertEq(result, approved, "isApprovedForAll(caller, caller)"); + } +} + diff --git a/test/unit/token/ERC721/Approve/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Approve/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..320d986e --- /dev/null +++ b/test/unit/token/ERC721/Approve/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721ApproveFacet_Base_Test} from "test/unit/token/ERC721/Approve/ERC721ApproveFacetBase.t.sol"; +import {ERC721ApproveFacet} from "src/token/ERC721/Approve/ERC721ApproveFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721ApproveFacet_Unit_Test is ERC721ApproveFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = + abi.encodePacked(ERC721ApproveFacet.approve.selector, ERC721ApproveFacet.setApprovalForAll.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol b/test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol new file mode 100644 index 00000000..acb4c56b --- /dev/null +++ b/test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol @@ -0,0 +1,27 @@ +// 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 {ERC721BurnFacet} from "src/token/ERC721/Burn/ERC721BurnFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721BurnFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721BurnFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721BurnFacet(); + vm.label(address(facet), "ERC721BurnFacet"); + } + + function _mint(address to, uint256 tokenId) internal { + address(facet).mint(to, tokenId); + } +} + diff --git a/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol new file mode 100644 index 00000000..d7e29e67 --- /dev/null +++ b/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721BurnFacet_Base_Test} from "test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721BurnFacet} from "src/token/ERC721/Burn/ERC721BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Burn_ERC721BurnFacet_Fuzz_Unit_Test is ERC721BurnFacet_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Burn_WhenTokenDoesNotExist(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, tokenId)); + facet.burn(tokenId); + } + + function testFuzz_ShouldRevert_Burn_WhenCallerNotOwnerOrApproved(address owner, address caller, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(caller != owner); + vm.assume(caller != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(caller); + + vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721InsufficientApproval.selector, caller, tokenId)); + facet.burn(tokenId); + } + + function testFuzz_ShouldBurn_WhenCallerIsOwner(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721BurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, tokenId)); + facet.burn(tokenId); + } + + function testFuzz_ShouldBurn_WhenCallerIsApproved(address owner, address approved, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(approved != address(0)); + vm.assume(approved != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + address(facet).setApproved(tokenId, approved); + + vm.stopPrank(); + vm.prank(approved); + + vm.expectEmit(address(facet)); + emit ERC721BurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + } + + function testFuzz_ShouldBurn_WhenCallerIsOperator(address owner, address operator, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(operator != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + address(facet).setApprovedForAll(owner, operator, true); + + vm.stopPrank(); + vm.prank(operator); + + vm.expectEmit(address(facet)); + emit ERC721BurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + } + + function testFuzz_ShouldBurnBatch_WhenAllTokensExistAndCallerAuthorized( + address owner, + uint256 tokenId1, + uint256 tokenId2 + ) external { + vm.assume(owner != address(0)); + tokenId1 = bound(tokenId1, 1, type(uint128).max); + tokenId2 = bound(tokenId2, 2, type(uint128).max); + vm.assume(tokenId1 != tokenId2); + + address(facet).mint(owner, tokenId1); + address(facet).mint(owner, tokenId2); + + vm.stopPrank(); + vm.prank(owner); + + uint256[] memory ids = new uint256[](2); + ids[0] = tokenId1; + ids[1] = tokenId2; + + vm.expectEmit(address(facet)); + emit ERC721BurnFacet.Transfer(owner, address(0), tokenId1); + vm.expectEmit(address(facet)); + emit ERC721BurnFacet.Transfer(owner, address(0), tokenId2); + facet.burnBatch(ids); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + } + + function testFuzz_ShouldRevert_BurnBatch_WhenAnyTokenDoesNotExist( + address owner, + uint256 existingTokenId, + uint256 nonexistentTokenId + ) external { + vm.assume(owner != address(0)); + existingTokenId = bound(existingTokenId, 1, type(uint128).max); + nonexistentTokenId = bound(nonexistentTokenId, 2, type(uint128).max); + vm.assume(existingTokenId != nonexistentTokenId); + + address(facet).mint(owner, existingTokenId); + + vm.stopPrank(); + vm.prank(owner); + + uint256[] memory ids = new uint256[](2); + ids[0] = existingTokenId; + ids[1] = nonexistentTokenId; + + vm.expectRevert( + abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, nonexistentTokenId) + ); + facet.burnBatch(ids); + } + + function testFuzz_ShouldRevert_BurnBatch_WhenCallerNotOwnerOrApproved( + address owner, + address caller, + uint256 tokenId1, + uint256 tokenId2 + ) external { + vm.assume(owner != address(0)); + vm.assume(caller != address(0)); + vm.assume(caller != owner); + tokenId1 = bound(tokenId1, 1, type(uint128).max); + tokenId2 = bound(tokenId2, 2, type(uint128).max); + vm.assume(tokenId1 != tokenId2); + + address(facet).mint(owner, tokenId1); + address(facet).mint(owner, tokenId2); + + vm.stopPrank(); + vm.prank(caller); + + uint256[] memory ids = new uint256[](2); + ids[0] = tokenId1; + ids[1] = tokenId2; + + vm.expectRevert( + abi.encodeWithSelector(ERC721BurnFacet.ERC721InsufficientApproval.selector, caller, tokenId1) + ); + facet.burnBatch(ids); + } +} + diff --git a/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..662b277b --- /dev/null +++ b/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721BurnFacet_Base_Test} from "test/unit/token/ERC721/Burn/ERC721BurnFacetBase.t.sol"; +import {ERC721BurnFacet} from "src/token/ERC721/Burn/ERC721BurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721BurnFacet_Unit_Test is ERC721BurnFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = + abi.encodePacked(ERC721BurnFacet.burn.selector, ERC721BurnFacet.burnBatch.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol b/test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol new file mode 100644 index 00000000..6369144e --- /dev/null +++ b/test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol @@ -0,0 +1,27 @@ +// 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 {ERC721DataFacet} from "src/token/ERC721/Data/ERC721DataFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721DataFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721DataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721DataFacet(); + vm.label(address(facet), "ERC721DataFacet"); + } + + function _mint(address to, uint256 tokenId) internal { + address(facet).mint(to, tokenId); + } +} + diff --git a/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..e0ae67fa --- /dev/null +++ b/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721DataFacet_Base_Test} from "test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721DataFacet} from "src/token/ERC721/Data/ERC721DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Data_ERC721DataFacet_Fuzz_Unit_Test is ERC721DataFacet_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldReturnBalanceOf_WhenOwnerIsNonZero(address owner, uint256 count) external { + vm.assume(owner != address(0)); + count = bound(count, 0, 10); + + // Seed balances by minting sequential tokenIds to the same owner. + for (uint256 i; i < count; i++) { + address(facet).mint(owner, i + 1); + } + + uint256 balance = facet.balanceOf(owner); + assertEq(balance, count, "balanceOf(owner)"); + } + + function testFuzz_ShouldRevertBalanceOf_WhenOwnerIsZero() external { + vm.expectRevert(abi.encodeWithSelector(ERC721DataFacet.ERC721InvalidOwner.selector, address(0))); + facet.balanceOf(address(0)); + } + + function testFuzz_ShouldReturnOwnerOf_WhenTokenExists(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + address result = facet.ownerOf(tokenId); + assertEq(result, owner, "ownerOf(tokenId)"); + } + + function testFuzz_ShouldRevertOwnerOf_WhenTokenDoesNotExist(uint256 tokenId) external { + vm.expectRevert(abi.encodeWithSelector(ERC721DataFacet.ERC721NonexistentToken.selector, tokenId)); + facet.ownerOf(tokenId); + } + + function testFuzz_ShouldReturnZeroAddress_GetApproved_WhenNoApproval(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + address approved = facet.getApproved(tokenId); + assertEq(approved, address(0), "getApproved(tokenId)"); + } + + function testFuzz_ShouldRevert_GetApproved_WhenTokenDoesNotExist(uint256 tokenId) external { + vm.expectRevert(abi.encodeWithSelector(ERC721DataFacet.ERC721NonexistentToken.selector, tokenId)); + facet.getApproved(tokenId); + } + + function testFuzz_ShouldReturnIsApprovedForAll(address owner, address operator, bool approved) external { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + + address(facet).setApprovedForAll(owner, operator, approved); + + bool result = facet.isApprovedForAll(owner, operator); + assertEq(result, approved, "isApprovedForAll(owner, operator)"); + } + + function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotPreviouslyApproved( + address owner, + address operator + ) external { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(owner != operator); + + bool result = facet.isApprovedForAll(owner, operator); + assertEq(result, false, "default isApprovedForAll(owner, operator)"); + } +} + diff --git a/test/unit/token/ERC721/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..b389500d --- /dev/null +++ b/test/unit/token/ERC721/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721DataFacet_Base_Test} from "test/unit/token/ERC721/Data/ERC721DataFacetBase.t.sol"; +import {ERC721DataFacet} from "src/token/ERC721/Data/ERC721DataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721DataFacet_Unit_Test is ERC721DataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC721DataFacet.balanceOf.selector, + ERC721DataFacet.ownerOf.selector, + ERC721DataFacet.getApproved.selector, + ERC721DataFacet.isApprovedForAll.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol new file mode 100644 index 00000000..b889e1c8 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableBurnFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721EnumerableBurnFacet} from "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Burn_ERC721EnumerableBurnFacet_Fuzz_Unit_Test is ERC721EnumerableBurnFacet_Base_Test { + using ERC721StorageUtils for address; + + function _seedOwnerToken(address owner, uint256 tokenId) internal { + uint256 ownerIndex = address(facet).balanceOf(owner); + address(facet).setOwnerTokenByIndex(owner, ownerIndex, tokenId); + address(facet).setOwnerTokensIndex(tokenId, ownerIndex); + address(facet).setBalanceOf(owner, ownerIndex + 1); + + uint256 globalIndex = address(facet).allTokensLength(); + address(facet).pushAllToken(tokenId); + address(facet).setAllTokensIndex(tokenId, globalIndex); + address(facet).setOwnerOf(tokenId, owner); + } + + function testFuzz_ShouldRevert_Burn_WhenTokenDoesNotExist(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableBurnFacet.ERC721NonexistentToken.selector, tokenId)); + facet.burn(tokenId); + } + + function testFuzz_ShouldRevert_Burn_WhenCallerNotOwnerOrApproved(address owner, address caller, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(caller != address(0)); + vm.assume(caller != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(caller); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableBurnFacet.ERC721InsufficientApproval.selector, caller, tokenId) + ); + facet.burn(tokenId); + } + + function testFuzz_ShouldUpdateEnumerationOnBurn_WhenCallerIsOwner(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721EnumerableBurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + assertEq(address(facet).allTokensLength(), 0, "totalSupply"); + } + + function testFuzz_ShouldBurn_WhenCallerIsTokenApproved(address owner, address approved, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(approved != address(0)); + vm.assume(approved != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + address(facet).setApproved(tokenId, approved); + + vm.stopPrank(); + vm.prank(approved); + + vm.expectEmit(address(facet)); + emit ERC721EnumerableBurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + assertEq(address(facet).allTokensLength(), 0, "totalSupply"); + } + + function testFuzz_ShouldBurn_WhenCallerIsOperator(address owner, address operator, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(operator != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + address(facet).setApprovedForAll(owner, operator, true); + + vm.stopPrank(); + vm.prank(operator); + + vm.expectEmit(address(facet)); + emit ERC721EnumerableBurnFacet.Transfer(owner, address(0), tokenId); + facet.burn(tokenId); + + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + assertEq(address(facet).allTokensLength(), 0, "totalSupply"); + } + + function test_ShouldUpdateEnumerationOnBurn_WhenTokenNotLastInOwnerAndAllTokens() external { + address owner = users.alice; + + uint256 tokenId1 = 1; + uint256 tokenId2 = 2; + uint256 tokenId3 = 3; + + _seedOwnerToken(owner, tokenId1); + _seedOwnerToken(owner, tokenId2); + _seedOwnerToken(owner, tokenId3); + + vm.stopPrank(); + vm.prank(owner); + facet.burn(tokenId2); + + assertEq(address(facet).balanceOf(owner), 2, "owner balance after burn"); + assertEq(address(facet).ownerTokenByIndex(owner, 0), tokenId1, "owner token index 0"); + assertEq(address(facet).ownerTokenByIndex(owner, 1), tokenId3, "owner token index 1"); + assertEq(address(facet).ownerTokensIndex(tokenId1), 0, "ownerTokensIndex tokenId1"); + assertEq(address(facet).ownerTokensIndex(tokenId3), 1, "ownerTokensIndex tokenId3"); + + assertEq(address(facet).allTokensLength(), 2, "allTokens length"); + // Ensure indices are in range and consistent + uint256 idx1 = address(facet).allTokensIndex(tokenId1); + uint256 idx3 = address(facet).allTokensIndex(tokenId3); + assertLt(idx1, 2, "allTokensIndex tokenId1 in range"); + assertLt(idx3, 2, "allTokensIndex tokenId3 in range"); + } + + function test_ShouldExportSelectors_ERC721EnumerableBurnFacet() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = bytes.concat(ERC721EnumerableBurnFacet.burn.selector); + assertEq(selectors, expected, "exportSelectors enumerable burn facet"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..b515f9b6 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableBurnFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol"; +import {ERC721EnumerableBurnFacet} from "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721EnumerableBurnFacet_Unit_Test is ERC721EnumerableBurnFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(ERC721EnumerableBurnFacet.burn.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol new file mode 100644 index 00000000..308565e5 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableDataFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721EnumerableDataFacet} from "src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Data_ERC721EnumerableDataFacet_Fuzz_Unit_Test is ERC721EnumerableDataFacet_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldReturnTotalSupply_WhenAllTokensTracked(uint256 supply) external { + supply = bound(supply, 0, 10); + + for (uint256 i; i < supply; i++) { + address(facet).pushAllToken(i + 1); + } + + uint256 result = facet.totalSupply(); + assertEq(result, supply, "totalSupply"); + } + + function testFuzz_ShouldRevert_TokenOfOwnerByIndex_WhenIndexOutOfBounds( + address owner, + uint256 balance, + uint256 index + ) external { + vm.assume(owner != address(0)); + balance = bound(balance, 0, 10); + index = bound(index, balance, type(uint256).max); + + // Seed owner balance without ownerTokens to hit out-of-bounds branch + address(facet).setBalanceOf(owner, balance); + + vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableDataFacet.ERC721OutOfBoundsIndex.selector, owner, index)); + facet.tokenOfOwnerByIndex(owner, index); + } + + function testFuzz_ShouldReturnTokenOfOwnerByIndex_WhenIndexInBounds(address owner, uint256 balance) external { + vm.assume(owner != address(0)); + balance = bound(balance, 1, 10); + + for (uint256 i; i < balance; i++) { + uint256 tokenId = i + 1; + address(facet).setOwnerTokenByIndex(owner, i, tokenId); + address(facet).setOwnerTokensIndex(tokenId, i); + } + address(facet).setBalanceOf(owner, balance); + + for (uint256 i; i < balance; i++) { + uint256 tokenId = facet.tokenOfOwnerByIndex(owner, i); + assertEq(tokenId, i + 1, "tokenOfOwnerByIndex"); + } + } + + function testFuzz_ShouldRevert_TokenByIndex_WhenIndexOutOfBounds(uint256 length, uint256 index) external { + length = bound(length, 0, 10); + index = bound(index, length, type(uint256).max); + + for (uint256 i; i < length; i++) { + address(facet).pushAllToken(i + 1); + } + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableDataFacet.ERC721OutOfBoundsIndex.selector, address(0), index) + ); + facet.tokenByIndex(index); + } + + function testFuzz_ShouldReturnTokenByIndex_WhenIndexInBounds(uint256 length) external { + length = bound(length, 1, 10); + + for (uint256 i; i < length; i++) { + address(facet).pushAllToken(i + 1); + } + + for (uint256 i; i < length; i++) { + uint256 tokenId = facet.tokenByIndex(i); + assertEq(tokenId, i + 1, "tokenByIndex"); + } + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..1202cbee --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableDataFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol"; +import {ERC721EnumerableDataFacet} from "src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721EnumerableDataFacet_Unit_Test is ERC721EnumerableDataFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC721EnumerableDataFacet.totalSupply.selector, + ERC721EnumerableDataFacet.tokenOfOwnerByIndex.selector, + ERC721EnumerableDataFacet.tokenByIndex.selector + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol new file mode 100644 index 00000000..0daad1da --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnFacetBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721EnumerableBurnFacet} from "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableBurnFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableBurnFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721EnumerableBurnFacet(); + vm.label(address(facet), "ERC721EnumerableBurnFacet"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol new file mode 100644 index 00000000..834c97a4 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableDataFacetBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721EnumerableDataFacet} from "src/token/ERC721/Enumerable/Data/ERC721EnumerableDataFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableDataFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableDataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721EnumerableDataFacet(); + vm.label(address(facet), "ERC721EnumerableDataFacet"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol new file mode 100644 index 00000000..cb77f2b1 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721EnumerableTransferFacet} from "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableTransferFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableTransferFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721EnumerableTransferFacet(); + vm.label(address(facet), "ERC721EnumerableTransferFacet"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..34b59523 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableTransferFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol"; +import { + ERC721EnumerableTransferFacet +} from "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721EnumerableTransferFacet_Unit_Test is ERC721EnumerableTransferFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC721EnumerableTransferFacet.transferFrom.selector, + bytes4(keccak256("safeTransferFrom(address,address,uint256)")), + bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")) + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol new file mode 100644 index 00000000..4ee0ce48 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableTransferFacet_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import { + ERC721EnumerableTransferFacet, + IERC721Receiver +} from "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol"; + +contract ERC721Enumerable_ReceiverMock is IERC721Receiver { + enum RevertType { + None, + RevertWithMessage, + RevertWithoutMessage, + ReturnWrongSelector + } + + RevertType public revertType; + + event Received(address operator, address from, uint256 tokenId, bytes data); + + constructor(RevertType _revertType) { + revertType = _revertType; + } + + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) + external + override + returns (bytes4) + { + if (revertType == RevertType.RevertWithMessage) { + revert("ERC721EnumerableReceiver: revert"); + } else if (revertType == RevertType.RevertWithoutMessage) { + revert(); + } else if (revertType == RevertType.ReturnWrongSelector) { + emit Received(_operator, _from, _tokenId, _data); + return bytes4(0xDEADBEEF); + } + emit Received(_operator, _from, _tokenId, _data); + return this.onERC721Received.selector; + } +} + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721EnumerableTransferFacet_Base_Test { + using ERC721StorageUtils for address; + + function _seedOwnerToken(address owner, uint256 tokenId) internal { + // push into ownerTokens and allTokens arrays + uint256 ownerIndex = address(facet).balanceOf(owner); + address(facet).setOwnerTokenByIndex(owner, ownerIndex, tokenId); + address(facet).setOwnerTokensIndex(tokenId, ownerIndex); + address(facet).setBalanceOf(owner, ownerIndex + 1); + + uint256 globalIndex = address(facet).allTokensLength(); + address(facet).pushAllToken(tokenId); + address(facet).setAllTokensIndex(tokenId, globalIndex); + address(facet).setOwnerOf(tokenId, owner); + } + + function _seedTwoOwnerTokens(address owner, uint256 firstTokenId, uint256 secondTokenId) internal { + uint256 ownerIndex = address(facet).balanceOf(owner); + + // first token + address(facet).setOwnerTokenByIndex(owner, ownerIndex, firstTokenId); + address(facet).setOwnerTokensIndex(firstTokenId, ownerIndex); + address(facet).setBalanceOf(owner, ownerIndex + 1); + + // second token + address(facet).setOwnerTokenByIndex(owner, ownerIndex + 1, secondTokenId); + address(facet).setOwnerTokensIndex(secondTokenId, ownerIndex + 1); + address(facet).setBalanceOf(owner, ownerIndex + 2); + + uint256 globalIndex = address(facet).allTokensLength(); + address(facet).pushAllToken(firstTokenId); + address(facet).setAllTokensIndex(firstTokenId, globalIndex); + + address(facet).pushAllToken(secondTokenId); + address(facet).setAllTokensIndex(secondTokenId, globalIndex + 1); + + address(facet).setOwnerOf(firstTokenId, owner); + address(facet).setOwnerOf(secondTokenId, owner); + } + + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenCallerIsOwner(address owner, address to, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + } + + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenMovingMiddleToken(address owner, address to) + external + { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + + uint256 firstTokenId = 1; + uint256 secondTokenId = 2; + + _seedTwoOwnerTokens(owner, firstTokenId, secondTokenId); + + vm.stopPrank(); + vm.prank(owner); + facet.transferFrom(owner, to, firstTokenId); + + // original owner should now only own the second token at index 0 + assertEq(address(facet).balanceOf(owner), 1, "old owner balance"); + assertEq(address(facet).ownerTokenByIndex(owner, 0), secondTokenId, "remaining token"); + // new owner should own the first token + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + assertEq(address(facet).ownerOf(firstTokenId), to, "transferred token owner"); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenCallerNotOwnerOrApproved( + address owner, + address caller, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(caller != address(0)); + vm.assume(caller != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(caller); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InsufficientApproval.selector, caller, tokenId) + ); + facet.transferFrom(owner, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenTokenDoesNotExist(address from, address to, uint256 tokenId) + external + { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721NonexistentToken.selector, tokenId)); + facet.transferFrom(from, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenToIsZeroAddress(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(0))); + facet.transferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldCallOnERC721Received_SafeTransferFrom_WhenReceiverContractAccepts( + address owner, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + ERC721Enumerable_ReceiverMock receiver = + new ERC721Enumerable_ReceiverMock(ERC721Enumerable_ReceiverMock.RevertType.None); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(true, true, true, true); + emit ERC721Enumerable_ReceiverMock.Received(owner, owner, tokenId, data); + facet.safeTransferFrom(owner, address(receiver), tokenId, data); + + assertEq(address(facet).ownerOf(tokenId), address(receiver), "new owner"); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverIsNonCompliantContract(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + ERC721Enumerable_ReceiverMock receiver = + new ERC721Enumerable_ReceiverMock(ERC721Enumerable_ReceiverMock.RevertType.RevertWithoutMessage); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(receiver)) + ); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverRevertsWithMessage(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + ERC721Enumerable_ReceiverMock receiver = + new ERC721Enumerable_ReceiverMock(ERC721Enumerable_ReceiverMock.RevertType.RevertWithMessage); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert("ERC721EnumerableReceiver: revert"); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverReturnsWrongSelector(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + ERC721Enumerable_ReceiverMock receiver = + new ERC721Enumerable_ReceiverMock(ERC721Enumerable_ReceiverMock.RevertType.ReturnWrongSelector); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(receiver)) + ); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenCallerIsApproved( + address owner, + address approved, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(approved != address(0)); + vm.assume(approved != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != approved); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + address(facet).setApproved(tokenId, approved); + + vm.stopPrank(); + vm.prank(approved); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + } + + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenCallerIsOperator( + address owner, + address operator, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(operator != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != operator); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + address(facet).setApprovedForAll(owner, operator, true); + + vm.stopPrank(); + vm.prank(operator); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + } +} + diff --git a/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol b/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol new file mode 100644 index 00000000..05ba6ec3 --- /dev/null +++ b/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol @@ -0,0 +1,45 @@ +// 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 {ERC721MetadataFacet} from "src/token/ERC721/Metadata/ERC721MetadataFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +contract ERC721MetadataFacetHarness is ERC721MetadataFacet { + function setNameAndSymbol(string memory newName, string memory newSymbol) external { + ERC721MetadataStorage storage s = getStorage(); + s.name = newName; + s.symbol = newSymbol; + } + + function setBaseURI(string memory newBaseURI) external { + ERC721MetadataStorage storage s = getStorage(); + s.baseURI = newBaseURI; + } +} + +abstract contract ERC721MetadataFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721MetadataFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721MetadataFacetHarness(); + vm.label(address(facet), "ERC721MetadataFacet"); + } + + function _mint(address to, uint256 tokenId) internal { + address(facet).mint(to, tokenId); + } + + function _setBaseURI(string memory baseURI) internal { + ERC721MetadataFacetHarness(address(facet)).setBaseURI(baseURI); + } +} + + diff --git a/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol b/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol new file mode 100644 index 00000000..5220c5ae --- /dev/null +++ b/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721MetadataFacet_Base_Test, + ERC721MetadataFacetHarness +} from "test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721MetadataFacet} from "src/token/ERC721/Metadata/ERC721MetadataFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Metadata_ERC721MetadataFacet_Fuzz_Unit_Test is ERC721MetadataFacet_Base_Test { + using ERC721StorageUtils for address; + + function test_ShouldReturnNameAndSymbol_WhenSetViaMetadataStorage() external view { + string memory nameResult = facet.name(); + string memory symbolResult = facet.symbol(); + + assertEq(bytes(nameResult).length, 0, "default name"); + assertEq(bytes(symbolResult).length, 0, "default symbol"); + } + + function testFuzz_ShouldRevert_TokenURI_WhenTokenDoesNotExist(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721MetadataFacet.ERC721NonexistentToken.selector, tokenId)); + facet.tokenURI(tokenId); + } + + function testFuzz_ShouldReturnEmptyString_TokenURI_WhenBaseURIIsEmpty(uint256 tokenId, address owner) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + // seed ownership without touching metadata baseURI (remains empty) + _mint(owner, tokenId); + + string memory uri = facet.tokenURI(tokenId); + assertEq(bytes(uri).length, 0, "tokenURI when baseURI is empty"); + } + + function test_ShouldReturnTokenURI_ForTokenIdZero_WhenBaseURISet() external { + uint256 tokenId = 0; + address owner = users.alice; + + _mint(owner, tokenId); + string memory baseURI = "https://example.com/metadata/"; + _setBaseURI(baseURI); + + string memory uri = facet.tokenURI(tokenId); + assertEq(uri, string.concat(baseURI, "0"), "tokenURI for tokenId 0"); + } + + function testFuzz_ShouldReturnTokenURI_WhenBaseURISetAndTokenExists(uint256 tokenId, address owner) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _mint(owner, tokenId); + string memory baseURI = "ipfs://base/"; + _setBaseURI(baseURI); + + string memory expected = string.concat(baseURI, _toString(tokenId)); + string memory uri = facet.tokenURI(tokenId); + assertEq(uri, expected, "tokenURI with baseURI set"); + } + + function test_ShouldReturnConfiguredNameAndSymbol_WhenSetInMetadataStorage() external { + string memory expectedName = "Test Token"; + string memory expectedSymbol = "TEST"; + + ERC721MetadataFacetHarness(address(facet)).setNameAndSymbol(expectedName, expectedSymbol); + + string memory nameResult = facet.name(); + string memory symbolResult = facet.symbol(); + + assertEq(nameResult, expectedName, "configured name"); + assertEq(symbolResult, expectedSymbol, "configured symbol"); + } + + function test_ShouldExportSelectors_MetadataFacet() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = bytes.concat( + ERC721MetadataFacet.name.selector, + ERC721MetadataFacet.symbol.selector, + ERC721MetadataFacet.tokenURI.selector + ); + assertEq(selectors, expected, "exportSelectors metadata facet"); + } + + function _toString(uint256 value) internal pure returns (string memory) { + if (value == 0) { + return "0"; + } + uint256 temp = value; + uint256 digits; + while (temp != 0) { + digits++; + temp /= 10; + } + bytes memory buffer = new bytes(digits); + while (value != 0) { + digits -= 1; + buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); + value /= 10; + } + return string(buffer); + } +} + diff --git a/test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol b/test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol new file mode 100644 index 00000000..5c903a09 --- /dev/null +++ b/test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol @@ -0,0 +1,27 @@ +// 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 {ERC721TransferFacet} from "src/token/ERC721/Transfer/ERC721TransferFacet.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721TransferFacet_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721TransferFacet internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new ERC721TransferFacet(); + vm.label(address(facet), "ERC721TransferFacet"); + } + + function _mint(address to, uint256 tokenId) internal { + address(facet).mint(to, tokenId); + } +} + diff --git a/test/unit/token/ERC721/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Transfer/facet/fuzz/exportSelectors.t.sol new file mode 100644 index 00000000..45ee3a1b --- /dev/null +++ b/test/unit/token/ERC721/Transfer/facet/fuzz/exportSelectors.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721TransferFacet_Base_Test} from "test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol"; +import {ERC721TransferFacet} from "src/token/ERC721/Transfer/ERC721TransferFacet.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract ExportSelectors_ERC721TransferFacet_Unit_Test is ERC721TransferFacet_Base_Test { + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked( + ERC721TransferFacet.transferFrom.selector, + bytes4(keccak256("safeTransferFrom(address,address,uint256)")), + bytes4(keccak256("safeTransferFrom(address,address,uint256,bytes)")) + ); + assertEq(selectors, expected, "exportSelectors"); + } +} + diff --git a/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol new file mode 100644 index 00000000..5e28047d --- /dev/null +++ b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721TransferFacet_Base_Test} from "test/unit/token/ERC721/Transfer/ERC721TransferFacetBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; +import {ERC721TransferFacet, IERC721Receiver} from "src/token/ERC721/Transfer/ERC721TransferFacet.sol"; + +contract ERC721_ReceiverMock is IERC721Receiver { + enum RevertType { + None, + RevertWithMessage, + RevertWithoutMessage, + ReturnWrongSelector + } + + RevertType public revertType; + + event Received(address operator, address from, uint256 tokenId, bytes data); + + constructor(RevertType _revertType) { + revertType = _revertType; + } + + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) + external + override + returns (bytes4) + { + if (revertType == RevertType.RevertWithMessage) { + revert("ERC721Receiver: revert"); + } else if (revertType == RevertType.RevertWithoutMessage) { + revert(); + } else if (revertType == RevertType.ReturnWrongSelector) { + emit Received(_operator, _from, _tokenId, _data); + return bytes4(0xDEADBEEF); + } + emit Received(_operator, _from, _tokenId, _data); + return this.onERC721Received.selector; + } +} + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Transfer_ERC721TransferFacet_Fuzz_Unit_Test is ERC721TransferFacet_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_TransferFrom_WhenTokenDoesNotExist(address from, address to, uint256 tokenId) + external + { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721NonexistentToken.selector, tokenId)); + facet.transferFrom(from, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenToIsZeroAddress(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721InvalidReceiver.selector, address(0))); + facet.transferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenCallerIsNotOwnerOrApproved( + address owner, + address caller, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(caller != owner); + vm.assume(caller != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(caller); + + vm.expectRevert( + abi.encodeWithSelector(ERC721TransferFacet.ERC721InsufficientApproval.selector, caller, tokenId) + ); + facet.transferFrom(owner, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenFromIsNotOwner( + address owner, + address wrongFrom, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(wrongFrom != address(0)); + vm.assume(wrongFrom != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != wrongFrom); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721TransferFacet.ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner) + ); + facet.transferFrom(wrongFrom, to, tokenId); + } + + function testFuzz_ShouldTransfer_WhenCallerIsOwner(address owner, address to, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721TransferFacet.Transfer(owner, to, tokenId); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "owner balance"); + assertEq(address(facet).balanceOf(to), 1, "receiver balance"); + } + + function testFuzz_ShouldTransfer_WhenCallerIsApproved(address owner, address approved, address to, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(approved != address(0)); + vm.assume(approved != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + address(facet).setApproved(tokenId, approved); + + vm.stopPrank(); + vm.prank(approved); + + vm.expectEmit(address(facet)); + emit ERC721TransferFacet.Transfer(owner, to, tokenId); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).getApproved(tokenId), address(0), "approval cleared"); + } + + function testFuzz_ShouldTransfer_WhenCallerIsOperator(address owner, address operator, address to, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(operator != address(0)); + vm.assume(operator != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + address(facet).setApprovedForAll(owner, operator, true); + + vm.stopPrank(); + vm.prank(operator); + + vm.expectEmit(address(facet)); + emit ERC721TransferFacet.Transfer(owner, to, tokenId); + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + } + + function testFuzz_ShouldSafeTransfer_WhenReceiverIsEOA( + address owner, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != address(facet)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721TransferFacet.Transfer(owner, to, tokenId); + facet.safeTransferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + } + + function testFuzz_ShouldSafeTransferWithData_WhenReceiverIsEOA( + address owner, + address to, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != address(facet)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(address(facet)); + emit ERC721TransferFacet.Transfer(owner, to, tokenId); + facet.safeTransferFrom(owner, to, tokenId, data); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverIsNonCompliantContract(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + ERC721_ReceiverMock receiver = new ERC721_ReceiverMock(ERC721_ReceiverMock.RevertType.RevertWithoutMessage); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverReturnsWrongSelector(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + ERC721_ReceiverMock receiver = new ERC721_ReceiverMock(ERC721_ReceiverMock.RevertType.ReturnWrongSelector); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721InvalidReceiver.selector, address(receiver))); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenReceiverRevertsWithMessage(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + ERC721_ReceiverMock receiver = new ERC721_ReceiverMock(ERC721_ReceiverMock.RevertType.RevertWithMessage); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert("ERC721Receiver: revert"); + facet.safeTransferFrom(owner, address(receiver), tokenId); + } + + function testFuzz_ShouldCallOnERC721Received_SafeTransferFrom_WhenReceiverAccepts_NoData( + address owner, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + ERC721_ReceiverMock receiver = new ERC721_ReceiverMock(ERC721_ReceiverMock.RevertType.None); + + vm.stopPrank(); + vm.prank(owner); + + bytes memory data = ""; + vm.expectEmit(true, true, true, true); + emit ERC721_ReceiverMock.Received(owner, owner, tokenId, data); + facet.safeTransferFrom(owner, address(receiver), tokenId); + + assertEq(address(facet).ownerOf(tokenId), address(receiver), "new owner"); + } + + function testFuzz_ShouldCallOnERC721Received_SafeTransferFrom_WhenReceiverAccepts( + address owner, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + ERC721_ReceiverMock receiver = new ERC721_ReceiverMock(ERC721_ReceiverMock.RevertType.None); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(true, true, true, true); + emit ERC721_ReceiverMock.Received(owner, owner, tokenId, data); + facet.safeTransferFrom(owner, address(receiver), tokenId, data); + + assertEq(address(facet).ownerOf(tokenId), address(receiver), "new owner"); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenTokenDoesNotExist_NoData( + address from, + address to, + uint256 tokenId + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721NonexistentToken.selector, tokenId)); + facet.safeTransferFrom(from, to, tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenTokenDoesNotExist_WithData( + address from, + address to, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721NonexistentToken.selector, tokenId)); + facet.safeTransferFrom(from, to, tokenId, data); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress_NoData(address owner, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721InvalidReceiver.selector, address(0))); + facet.safeTransferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress_WithData( + address owner, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(facet).mint(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert(abi.encodeWithSelector(ERC721TransferFacet.ERC721InvalidReceiver.selector, address(0))); + facet.safeTransferFrom(owner, address(0), tokenId, data); + } +} + diff --git a/test/utils/storage/ERC721StorageUtils.sol b/test/utils/storage/ERC721StorageUtils.sol new file mode 100644 index 00000000..7684b12d --- /dev/null +++ b/test/utils/storage/ERC721StorageUtils.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Vm} from "forge-std/Vm.sol"; + +/** + * @title ERC721StorageUtils + * @notice Storage manipulation utilities for ERC-721-related testing. + * @dev Uses vm.load and vm.store to directly manipulate ERC-721 storage. + * Layout matches src/token/ERC721: + * - keccak256("erc721") for core ownership/approval data + * - keccak256("erc721.enumerable") for enumerable data + */ +library ERC721StorageUtils { + Vm private constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + bytes32 internal constant ERC721_STORAGE_POSITION = keccak256("erc721"); + bytes32 internal constant ERC721_ENUMERABLE_STORAGE_POSITION = keccak256("erc721.enumerable"); + + /*////////////////////////////////////////////////////////////// + CORE ERC-721 + //////////////////////////////////////////////////////////////*/ + + /* + * @notice ERC-721 core storage layout (matches ERC721DataFacet) + * @custom:storage-location erc8042:erc721 + * + * Slot 0: mapping(uint256 tokenId => address owner) ownerOf + * Slot 1: mapping(address owner => uint256 balance) balanceOf + * Slot 2: mapping(address owner => mapping(address operator => bool)) isApprovedForAll + * Slot 3: mapping(uint256 tokenId => address approved) approved + */ + + function ownerOf(address target, uint256 tokenId) internal view returns (address) { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_STORAGE_POSITION))); + return address(uint160(uint256(vm.load(target, slot)))); + } + + function balanceOf(address target, address owner) internal view returns (uint256) { + bytes32 slot = keccak256(abi.encode(owner, uint256(ERC721_STORAGE_POSITION) + 1)); + return uint256(vm.load(target, slot)); + } + + function isApprovedForAll(address target, address owner, address operator) internal view returns (bool) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC721_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(operator, ownerSlot)); + return uint256(vm.load(target, slot)) != 0; + } + + function getApproved(address target, uint256 tokenId) internal view returns (address) { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_STORAGE_POSITION) + 3)); + return address(uint160(uint256(vm.load(target, slot)))); + } + + function setOwnerOf(address target, uint256 tokenId, address owner) internal { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_STORAGE_POSITION))); + vm.store(target, slot, bytes32(uint256(uint160(owner)))); + } + + function setBalanceOf(address target, address owner, uint256 balance) internal { + bytes32 slot = keccak256(abi.encode(owner, uint256(ERC721_STORAGE_POSITION) + 1)); + vm.store(target, slot, bytes32(balance)); + } + + function setApprovedForAll(address target, address owner, address operator, bool approved) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC721_STORAGE_POSITION) + 2)); + bytes32 slot = keccak256(abi.encode(operator, ownerSlot)); + vm.store(target, slot, approved ? bytes32(uint256(1)) : bytes32(0)); + } + + function setApproved(address target, uint256 tokenId, address spender) internal { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_STORAGE_POSITION) + 3)); + vm.store(target, slot, bytes32(uint256(uint160(spender)))); + } + + /** + * @notice Helper to mint a token by updating owner and balances. + */ + function mint(address target, address to, uint256 tokenId) internal { + setOwnerOf(target, tokenId, to); + + uint256 currentBalance = balanceOf(target, to); + setBalanceOf(target, to, currentBalance + 1); + } + + /** + * @notice Helper to burn a token by clearing owner and updating balances. + */ + function burn(address target, uint256 tokenId) internal { + address owner = ownerOf(target, tokenId); + setOwnerOf(target, tokenId, address(0)); + + uint256 currentBalance = balanceOf(target, owner); + setBalanceOf(target, owner, currentBalance - 1); + + // Clear single-token approval + setApproved(target, tokenId, address(0)); + } + + /*////////////////////////////////////////////////////////////// + ENUMERABLE EXTENSION + //////////////////////////////////////////////////////////////*/ + + /* + * @notice ERC-721 enumerable storage layout (matches ERC721EnumerableDataFacet and mods) + * @custom:storage-location erc8042:erc721.enumerable + * + * Slot 0: mapping(address owner => mapping(uint256 index => uint256 tokenId)) ownerTokens + * Slot 1: mapping(uint256 tokenId => uint256 ownerTokensIndex) ownerTokensIndex + * Slot 2: uint256[] allTokens + * Slot 3: mapping(uint256 tokenId => uint256 allTokensIndex) allTokensIndex + * + * Note: ERC721EnumerableDataFacet currently only uses ownerTokens and allTokens length; + * transfer/mint/burn enumerable mods use the index mappings as well. + */ + + function ownerTokenByIndex(address target, address owner, uint256 index) internal view returns (uint256) { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC721_ENUMERABLE_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(index, ownerSlot)); + return uint256(vm.load(target, slot)); + } + + function ownerTokensIndex(address target, uint256 tokenId) internal view returns (uint256) { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 1)); + return uint256(vm.load(target, slot)); + } + + function allTokensLength(address target) internal view returns (uint256) { + // Dynamic array length is stored at the base slot + bytes32 slot = bytes32(uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 2); + return uint256(vm.load(target, slot)); + } + + function allTokensIndex(address target, uint256 tokenId) internal view returns (uint256) { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 3)); + return uint256(vm.load(target, slot)); + } + + function setOwnerTokenByIndex(address target, address owner, uint256 index, uint256 tokenId) internal { + bytes32 ownerSlot = keccak256(abi.encode(owner, uint256(ERC721_ENUMERABLE_STORAGE_POSITION))); + bytes32 slot = keccak256(abi.encode(index, ownerSlot)); + vm.store(target, slot, bytes32(tokenId)); + } + + function setOwnerTokensIndex(address target, uint256 tokenId, uint256 index) internal { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 1)); + vm.store(target, slot, bytes32(index)); + } + + function pushAllToken(address target, uint256 tokenId) internal { + uint256 length = allTokensLength(target); + + // Dynamic array layout: + // - length stored at slot `p` + // - elements stored starting at slot `keccak256(abi.encode(p)) + index` + bytes32 arraySlot = bytes32(uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 2); + bytes32 baseSlot = keccak256(abi.encode(arraySlot)); + bytes32 elementSlot = bytes32(uint256(baseSlot) + length); + + vm.store(target, elementSlot, bytes32(tokenId)); + vm.store(target, arraySlot, bytes32(length + 1)); + + // Track index + bytes32 indexSlot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 3)); + vm.store(target, indexSlot, bytes32(length)); + } + + function setAllTokensIndex(address target, uint256 tokenId, uint256 index) internal { + bytes32 slot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 3)); + vm.store(target, slot, bytes32(index)); + } +} + From a34390788e50cf0d3ae385bf3d272db51d7a169a Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 17:01:42 -0400 Subject: [PATCH 14/25] forge fmt, fix comments --- .../ERC721/Metadata/ERC721MetadataFacet.sol | 2 +- .../token/ERC721/Burn/facet/fuzz/burn.t.sol | 8 ++------ .../Burn/facet/fuzz/exportSelectors.t.sol | 3 +-- .../token/ERC721/Data/facet/fuzz/data.t.sol | 9 ++++----- .../Enumerable/Burn/facet/fuzz/burn.t.sol | 2 +- .../Enumerable/Data/facet/fuzz/data.t.sol | 2 +- .../Transfer/facet/fuzz/exportSelectors.t.sol | 4 +--- .../Transfer/facet/fuzz/transfer.t.sol | 18 +++++++++--------- .../Metadata/ERC721MetadataFacetBase.t.sol | 1 - .../ERC721/Metadata/facet/fuzz/metadata.t.sol | 2 +- .../ERC721/Transfer/facet/fuzz/transfer.t.sol | 6 +----- test/utils/storage/ERC721StorageUtils.sol | 13 +++++++------ 12 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/token/ERC721/Metadata/ERC721MetadataFacet.sol b/src/token/ERC721/Metadata/ERC721MetadataFacet.sol index b37a3355..0c79e43a 100644 --- a/src/token/ERC721/Metadata/ERC721MetadataFacet.sol +++ b/src/token/ERC721/Metadata/ERC721MetadataFacet.sol @@ -40,7 +40,7 @@ contract ERC721MetadataFacet { mapping(uint256 tokenId => address approved) approved; } - /** + /** * @notice Returns a pointer to the ERC-721 storage struct. * @dev Uses inline assembly to access the storage slot defined by STORAGE_POSITION. * @return s The ERC721Storage struct in storage. diff --git a/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol index d7e29e67..ab34fe18 100644 --- a/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol +++ b/test/unit/token/ERC721/Burn/facet/fuzz/burn.t.sol @@ -143,9 +143,7 @@ contract Burn_ERC721BurnFacet_Fuzz_Unit_Test is ERC721BurnFacet_Base_Test { ids[0] = existingTokenId; ids[1] = nonexistentTokenId; - vm.expectRevert( - abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, nonexistentTokenId) - ); + vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721NonexistentToken.selector, nonexistentTokenId)); facet.burnBatch(ids); } @@ -172,9 +170,7 @@ contract Burn_ERC721BurnFacet_Fuzz_Unit_Test is ERC721BurnFacet_Base_Test { ids[0] = tokenId1; ids[1] = tokenId2; - vm.expectRevert( - abi.encodeWithSelector(ERC721BurnFacet.ERC721InsufficientApproval.selector, caller, tokenId1) - ); + vm.expectRevert(abi.encodeWithSelector(ERC721BurnFacet.ERC721InsufficientApproval.selector, caller, tokenId1)); facet.burnBatch(ids); } } diff --git a/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol index 662b277b..cb070f6f 100644 --- a/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC721/Burn/facet/fuzz/exportSelectors.t.sol @@ -14,8 +14,7 @@ import {ERC721BurnFacet} from "src/token/ERC721/Burn/ERC721BurnFacet.sol"; contract ExportSelectors_ERC721BurnFacet_Unit_Test is ERC721BurnFacet_Base_Test { function test_ShouldReturnSelectors_ExportSelectors() external view { bytes memory selectors = facet.exportSelectors(); - bytes memory expected = - abi.encodePacked(ERC721BurnFacet.burn.selector, ERC721BurnFacet.burnBatch.selector); + bytes memory expected = abi.encodePacked(ERC721BurnFacet.burn.selector, ERC721BurnFacet.burnBatch.selector); assertEq(selectors, expected, "exportSelectors"); } } diff --git a/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol index e0ae67fa..9aebbe2e 100644 --- a/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol +++ b/test/unit/token/ERC721/Data/facet/fuzz/data.t.sol @@ -19,7 +19,7 @@ contract Data_ERC721DataFacet_Fuzz_Unit_Test is ERC721DataFacet_Base_Test { vm.assume(owner != address(0)); count = bound(count, 0, 10); - // Seed balances by minting sequential tokenIds to the same owner. + /* Seed balances by minting sequential tokenIds to the same owner. */ for (uint256 i; i < count; i++) { address(facet).mint(owner, i + 1); } @@ -73,10 +73,9 @@ contract Data_ERC721DataFacet_Fuzz_Unit_Test is ERC721DataFacet_Base_Test { assertEq(result, approved, "isApprovedForAll(owner, operator)"); } - function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotPreviouslyApproved( - address owner, - address operator - ) external { + function testFuzz_ShouldReturnFalse_IsApprovedForAll_WhenNotPreviouslyApproved(address owner, address operator) + external + { vm.assume(owner != address(0)); vm.assume(operator != address(0)); vm.assume(owner != operator); diff --git a/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol index b889e1c8..0fdcf242 100644 --- a/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol +++ b/test/unit/token/ERC721/Enumerable/Burn/facet/fuzz/burn.t.sol @@ -134,7 +134,7 @@ contract Burn_ERC721EnumerableBurnFacet_Fuzz_Unit_Test is ERC721EnumerableBurnFa assertEq(address(facet).ownerTokensIndex(tokenId3), 1, "ownerTokensIndex tokenId3"); assertEq(address(facet).allTokensLength(), 2, "allTokens length"); - // Ensure indices are in range and consistent + /* Ensure indices are in range and consistent */ uint256 idx1 = address(facet).allTokensIndex(tokenId1); uint256 idx3 = address(facet).allTokensIndex(tokenId3); assertLt(idx1, 2, "allTokensIndex tokenId1 in range"); diff --git a/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol index 308565e5..6aa5dd7a 100644 --- a/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol +++ b/test/unit/token/ERC721/Enumerable/Data/facet/fuzz/data.t.sol @@ -37,7 +37,7 @@ contract Data_ERC721EnumerableDataFacet_Fuzz_Unit_Test is ERC721EnumerableDataFa balance = bound(balance, 0, 10); index = bound(index, balance, type(uint256).max); - // Seed owner balance without ownerTokens to hit out-of-bounds branch + /* Seed owner balance without ownerTokens to hit out-of-bounds branch */ address(facet).setBalanceOf(owner, balance); vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableDataFacet.ERC721OutOfBoundsIndex.selector, owner, index)); diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol index 34b59523..e280662b 100644 --- a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/exportSelectors.t.sol @@ -8,9 +8,7 @@ pragma solidity >=0.8.30; import { ERC721EnumerableTransferFacet_Base_Test } from "test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferFacetBase.t.sol"; -import { - ERC721EnumerableTransferFacet -} from "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol"; +import {ERC721EnumerableTransferFacet} from "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferFacet.sol"; /** * @dev BTT spec: test/trees/ERC721.tree diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol index 4ee0ce48..141f2e85 100644 --- a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol @@ -55,7 +55,7 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab using ERC721StorageUtils for address; function _seedOwnerToken(address owner, uint256 tokenId) internal { - // push into ownerTokens and allTokens arrays + /* push into ownerTokens and allTokens arrays */ uint256 ownerIndex = address(facet).balanceOf(owner); address(facet).setOwnerTokenByIndex(owner, ownerIndex, tokenId); address(facet).setOwnerTokensIndex(tokenId, ownerIndex); @@ -70,12 +70,12 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab function _seedTwoOwnerTokens(address owner, uint256 firstTokenId, uint256 secondTokenId) internal { uint256 ownerIndex = address(facet).balanceOf(owner); - // first token + /* first token */ address(facet).setOwnerTokenByIndex(owner, ownerIndex, firstTokenId); address(facet).setOwnerTokensIndex(firstTokenId, ownerIndex); address(facet).setBalanceOf(owner, ownerIndex + 1); - // second token + /* second token */ address(facet).setOwnerTokenByIndex(owner, ownerIndex + 1, secondTokenId); address(facet).setOwnerTokensIndex(secondTokenId, ownerIndex + 1); address(facet).setBalanceOf(owner, ownerIndex + 2); @@ -110,9 +110,7 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab assertEq(address(facet).balanceOf(to), 1, "new owner balance"); } - function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenMovingMiddleToken(address owner, address to) - external - { + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenMovingMiddleToken(address owner, address to) external { vm.assume(owner != address(0)); vm.assume(to != address(0)); vm.assume(to != owner); @@ -126,10 +124,10 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab vm.prank(owner); facet.transferFrom(owner, to, firstTokenId); - // original owner should now only own the second token at index 0 + /* original owner should now only own the second token at index 0 */ assertEq(address(facet).balanceOf(owner), 1, "old owner balance"); assertEq(address(facet).ownerTokenByIndex(owner, 0), secondTokenId, "remaining token"); - // new owner should own the first token + /* new owner should own the first token */ assertEq(address(facet).balanceOf(to), 1, "new owner balance"); assertEq(address(facet).ownerOf(firstTokenId), to, "transferred token owner"); } @@ -178,7 +176,9 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab vm.stopPrank(); vm.prank(owner); - vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(0))); + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(0)) + ); facet.transferFrom(owner, address(0), tokenId); } diff --git a/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol b/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol index 05ba6ec3..f31607b8 100644 --- a/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol +++ b/test/unit/token/ERC721/Metadata/ERC721MetadataFacetBase.t.sol @@ -42,4 +42,3 @@ abstract contract ERC721MetadataFacet_Base_Test is Base_Test { } } - diff --git a/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol b/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol index 5220c5ae..2bcd6464 100644 --- a/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol +++ b/test/unit/token/ERC721/Metadata/facet/fuzz/metadata.t.sol @@ -37,7 +37,7 @@ contract Metadata_ERC721MetadataFacet_Fuzz_Unit_Test is ERC721MetadataFacet_Base vm.assume(owner != address(0)); tokenId = bound(tokenId, 1, type(uint128).max); - // seed ownership without touching metadata baseURI (remains empty) + /* seed ownership without touching metadata baseURI (remains empty) */ _mint(owner, tokenId); string memory uri = facet.tokenURI(tokenId); diff --git a/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol index 5e28047d..590b85a8 100644 --- a/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol +++ b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol @@ -189,11 +189,7 @@ contract Transfer_ERC721TransferFacet_Fuzz_Unit_Test is ERC721TransferFacet_Base assertEq(address(facet).ownerOf(tokenId), to, "new owner"); } - function testFuzz_ShouldSafeTransfer_WhenReceiverIsEOA( - address owner, - address to, - uint256 tokenId - ) external { + function testFuzz_ShouldSafeTransfer_WhenReceiverIsEOA(address owner, address to, uint256 tokenId) external { vm.assume(owner != address(0)); vm.assume(to != address(0)); vm.assume(to != owner); diff --git a/test/utils/storage/ERC721StorageUtils.sol b/test/utils/storage/ERC721StorageUtils.sol index 7684b12d..70ff0eea 100644 --- a/test/utils/storage/ERC721StorageUtils.sol +++ b/test/utils/storage/ERC721StorageUtils.sol @@ -97,7 +97,7 @@ library ERC721StorageUtils { uint256 currentBalance = balanceOf(target, owner); setBalanceOf(target, owner, currentBalance - 1); - // Clear single-token approval + /* Clear single-token approval */ setApproved(target, tokenId, address(0)); } @@ -130,7 +130,7 @@ library ERC721StorageUtils { } function allTokensLength(address target) internal view returns (uint256) { - // Dynamic array length is stored at the base slot + /* Dynamic array length is stored at the base slot */ bytes32 slot = bytes32(uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 2); return uint256(vm.load(target, slot)); } @@ -154,9 +154,10 @@ library ERC721StorageUtils { function pushAllToken(address target, uint256 tokenId) internal { uint256 length = allTokensLength(target); - // Dynamic array layout: - // - length stored at slot `p` - // - elements stored starting at slot `keccak256(abi.encode(p)) + index` + /* Dynamic array layout: + * - length stored at slot `p` + * - elements stored starting at slot `keccak256(abi.encode(p)) + index` + */ bytes32 arraySlot = bytes32(uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 2); bytes32 baseSlot = keccak256(abi.encode(arraySlot)); bytes32 elementSlot = bytes32(uint256(baseSlot) + length); @@ -164,7 +165,7 @@ library ERC721StorageUtils { vm.store(target, elementSlot, bytes32(tokenId)); vm.store(target, arraySlot, bytes32(length + 1)); - // Track index + /* Track index */ bytes32 indexSlot = keccak256(abi.encode(tokenId, uint256(ERC721_ENUMERABLE_STORAGE_POSITION) + 3)); vm.store(target, indexSlot, bytes32(length)); } From b3f66f6f507a68a28d2eaa0c3ba89bdefc9914b2 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 17:05:01 -0400 Subject: [PATCH 15/25] fix test --- test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol index 590b85a8..5d782cda 100644 --- a/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol +++ b/test/unit/token/ERC721/Transfer/facet/fuzz/transfer.t.sol @@ -194,6 +194,7 @@ contract Transfer_ERC721TransferFacet_Fuzz_Unit_Test is ERC721TransferFacet_Base vm.assume(to != address(0)); vm.assume(to != owner); vm.assume(to != address(facet)); + vm.assume(to.code.length == 0); tokenId = bound(tokenId, 1, type(uint128).max); address(facet).mint(owner, tokenId); @@ -218,6 +219,7 @@ contract Transfer_ERC721TransferFacet_Fuzz_Unit_Test is ERC721TransferFacet_Base vm.assume(to != address(0)); vm.assume(to != owner); vm.assume(to != address(facet)); + vm.assume(to.code.length == 0); tokenId = bound(tokenId, 1, type(uint128).max); address(facet).mint(owner, tokenId); From b4bc1192b6c6fcd3baa9bf1fa61274b00d22d85f Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 17:08:51 -0400 Subject: [PATCH 16/25] add transfer test --- test/trees/ERC721.tree | 4 + .../Transfer/facet/fuzz/transfer.t.sol | 198 ++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/test/trees/ERC721.tree b/test/trees/ERC721.tree index 62a0e332..0976ff4c 100644 --- a/test/trees/ERC721.tree +++ b/test/trees/ERC721.tree @@ -48,6 +48,8 @@ Transfer β”œβ”€β”€ TransferFrom β”‚ β”œβ”€β”€ when token does not exist β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ β”œβ”€β”€ when from is not the current owner +β”‚ β”‚ └── it should revert with ERC721IncorrectOwner or equivalent β”‚ β”œβ”€β”€ when to is the zero address β”‚ β”‚ └── it should revert with ERC721InvalidReceiver β”‚ β”œβ”€β”€ when caller is not owner, not approved, and not operator @@ -61,6 +63,8 @@ Transfer └── SafeTransferFrom β”œβ”€β”€ when token does not exist β”‚ └── it should revert with ERC721NonexistentToken + β”œβ”€β”€ when to is an externally owned account + β”‚ └── it should transfer ownership, update balances, clear approval, emit Transfer, and not revert β”œβ”€β”€ when to is the zero address β”‚ └── it should revert with ERC721InvalidReceiver β”œβ”€β”€ when to is a non-receiver contract diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol index 141f2e85..f5aee76b 100644 --- a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol @@ -310,5 +310,203 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); assertEq(address(facet).balanceOf(to), 1, "new owner balance"); } + + function testFuzz_ShouldRevert_TransferFrom_WhenFromIsNotOwner( + address owner, + address wrongFrom, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(wrongFrom != address(0)); + vm.assume(wrongFrom != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner) + ); + facet.transferFrom(wrongFrom, to, tokenId); + } + + function testFuzz_ShouldAppendEnumerationOnTransfer_WhenRecipientHasExistingTokens(address owner, address to) + external + { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + + uint256 existingTokenId = 1; + uint256 transferredTokenId = 2; + + _seedOwnerToken(to, existingTokenId); + _seedOwnerToken(owner, transferredTokenId); + + uint256 previousToBalance = address(facet).balanceOf(to); + + vm.stopPrank(); + vm.prank(owner); + facet.transferFrom(owner, to, transferredTokenId); + + assertEq(address(facet).balanceOf(to), previousToBalance + 1, "new owner balance"); + assertEq( + address(facet).ownerTokenByIndex(to, previousToBalance), transferredTokenId, "transferred token index for to" + ); + assertEq( + address(facet).ownerTokensIndex(transferredTokenId), + previousToBalance, + "ownerTokensIndex for transferred token" + ); + } + + function testFuzz_ShouldClearApprovalAndEmitTransfer_OnTransferFrom(address owner, address approved, address to, uint256 tokenId) + external + { + vm.assume(owner != address(0)); + vm.assume(approved != address(0)); + vm.assume(approved != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != approved); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + address(facet).setApproved(tokenId, approved); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(true, true, true, true); + emit ERC721EnumerableTransferFacet.Transfer(owner, to, tokenId); + + facet.transferFrom(owner, to, tokenId); + + assertEq(address(facet).getApproved(tokenId), address(0), "approval cleared"); + + vm.stopPrank(); + vm.prank(approved); + + vm.expectRevert( + abi.encodeWithSelector( + ERC721EnumerableTransferFacet.ERC721InsufficientApproval.selector, approved, tokenId + ) + ); + facet.transferFrom(to, owner, tokenId); + } + + function testFuzz_ShouldCallOnERC721Received_SafeTransferFromSimple_WhenReceiverContractAccepts( + address owner, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + ERC721Enumerable_ReceiverMock receiver = + new ERC721Enumerable_ReceiverMock(ERC721Enumerable_ReceiverMock.RevertType.None); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectEmit(true, true, true, true); + emit ERC721Enumerable_ReceiverMock.Received(owner, owner, tokenId, ""); + facet.safeTransferFrom(owner, address(receiver), tokenId); + + assertEq(address(facet).ownerOf(tokenId), address(receiver), "new owner"); + } + + function testFuzz_ShouldTransferToEOA_SafeTransferFromSimple(address owner, address to, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to.code.length == 0); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + facet.safeTransferFrom(owner, to, tokenId); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + } + + function testFuzz_ShouldTransferToEOA_SafeTransferFromWithData( + address owner, + address to, + uint256 tokenId, + bytes calldata data + ) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to.code.length == 0); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + facet.safeTransferFrom(owner, to, tokenId, data); + + assertEq(address(facet).ownerOf(tokenId), to, "new owner"); + assertEq(address(facet).balanceOf(owner), 0, "old owner balance"); + assertEq(address(facet).balanceOf(to), 1, "new owner balance"); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenToIsZeroAddress(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(0)) + ); + facet.safeTransferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldRevert_SafeTransferFromWithData_WhenToIsZeroAddress(address owner, uint256 tokenId, bytes calldata data) + external + { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.stopPrank(); + vm.prank(owner); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InvalidReceiver.selector, address(0)) + ); + facet.safeTransferFrom(owner, address(0), tokenId, data); + } + + function testFuzz_ShouldRevert_SafeTransferFrom_WhenTokenDoesNotExist(address from, address to, uint256 tokenId) + external + { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert( + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721NonexistentToken.selector, tokenId) + ); + facet.safeTransferFrom(from, to, tokenId); + } } From eb22e236d8f15fd3203586305e1b0163cde231a9 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 17:09:09 -0400 Subject: [PATCH 17/25] format --- .../Transfer/facet/fuzz/transfer.t.sol | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol index f5aee76b..8870897f 100644 --- a/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol +++ b/test/unit/token/ERC721/Enumerable/Transfer/facet/fuzz/transfer.t.sol @@ -330,7 +330,9 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab vm.prank(owner); vm.expectRevert( - abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner) + abi.encodeWithSelector( + ERC721EnumerableTransferFacet.ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner + ) ); facet.transferFrom(wrongFrom, to, tokenId); } @@ -356,7 +358,9 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab assertEq(address(facet).balanceOf(to), previousToBalance + 1, "new owner balance"); assertEq( - address(facet).ownerTokenByIndex(to, previousToBalance), transferredTokenId, "transferred token index for to" + address(facet).ownerTokenByIndex(to, previousToBalance), + transferredTokenId, + "transferred token index for to" ); assertEq( address(facet).ownerTokensIndex(transferredTokenId), @@ -365,9 +369,12 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab ); } - function testFuzz_ShouldClearApprovalAndEmitTransfer_OnTransferFrom(address owner, address approved, address to, uint256 tokenId) - external - { + function testFuzz_ShouldClearApprovalAndEmitTransfer_OnTransferFrom( + address owner, + address approved, + address to, + uint256 tokenId + ) external { vm.assume(owner != address(0)); vm.assume(approved != address(0)); vm.assume(approved != owner); @@ -393,9 +400,7 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab vm.prank(approved); vm.expectRevert( - abi.encodeWithSelector( - ERC721EnumerableTransferFacet.ERC721InsufficientApproval.selector, approved, tokenId - ) + abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721InsufficientApproval.selector, approved, tokenId) ); facet.transferFrom(to, owner, tokenId); } @@ -479,9 +484,11 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab facet.safeTransferFrom(owner, address(0), tokenId); } - function testFuzz_ShouldRevert_SafeTransferFromWithData_WhenToIsZeroAddress(address owner, uint256 tokenId, bytes calldata data) - external - { + function testFuzz_ShouldRevert_SafeTransferFromWithData_WhenToIsZeroAddress( + address owner, + uint256 tokenId, + bytes calldata data + ) external { vm.assume(owner != address(0)); tokenId = bound(tokenId, 1, type(uint128).max); @@ -503,9 +510,7 @@ contract Transfer_ERC721EnumerableTransferFacet_Fuzz_Unit_Test is ERC721Enumerab vm.assume(to != address(0)); tokenId = bound(tokenId, 1, type(uint128).max); - vm.expectRevert( - abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721NonexistentToken.selector, tokenId) - ); + vm.expectRevert(abi.encodeWithSelector(ERC721EnumerableTransferFacet.ERC721NonexistentToken.selector, tokenId)); facet.safeTransferFrom(from, to, tokenId); } } From 23a85d19ceba55cddfa822032c543421638eb9e8 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 18:00:15 -0400 Subject: [PATCH 18/25] refactor Royalty test to BTT --- .../Royalty/Facet/fuzz/integration.t.sol | 68 +++ .../token/Royalty/Mod/fuzz/integration.t.sol | 76 +++ test/token/Royalty/Royalty.t.sol | 555 ------------------ test/token/Royalty/RoyaltyFacet.t.sol | 468 --------------- test/trees/Royalty.tree | 73 +++ .../Royalty/Facet/fuzz/defaultRoyalty.t.sol | 51 ++ .../token/Royalty/Facet/fuzz/edgeCases.t.sol | 66 +++ .../token/Royalty/Facet/fuzz/royalty.t.sol | 13 + .../Royalty/Facet/fuzz/royaltyInfo.t.sol | 91 +++ .../Royalty/Facet/fuzz/tokenRoyalty.t.sol | 75 +++ .../Royalty/Mod/fuzz/defaultRoyalty.t.sol | 95 +++ .../token/Royalty/Mod/fuzz/edgeCases.t.sol | 66 +++ .../unit/token/Royalty/Mod/fuzz/royalty.t.sol | 14 + .../token/Royalty/Mod/fuzz/royaltyInfo.t.sol | 94 +++ .../token/Royalty/Mod/fuzz/tokenRoyalty.t.sol | 137 +++++ .../unit/token/Royalty/RoyaltyFacetBase.t.sol | 19 + test/unit/token/Royalty/RoyaltyModBase.t.sol | 19 + .../token/Royalty}/RoyaltyFacetHarness.sol | 3 +- .../token/Royalty}/RoyaltyHarness.sol | 7 +- 19 files changed, 963 insertions(+), 1027 deletions(-) create mode 100644 test/integration/token/Royalty/Facet/fuzz/integration.t.sol create mode 100644 test/integration/token/Royalty/Mod/fuzz/integration.t.sol delete mode 100644 test/token/Royalty/Royalty.t.sol delete mode 100644 test/token/Royalty/RoyaltyFacet.t.sol create mode 100644 test/trees/Royalty.tree create mode 100644 test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol create mode 100644 test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol create mode 100644 test/unit/token/Royalty/Facet/fuzz/royalty.t.sol create mode 100644 test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol create mode 100644 test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol create mode 100644 test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol create mode 100644 test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol create mode 100644 test/unit/token/Royalty/Mod/fuzz/royalty.t.sol create mode 100644 test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol create mode 100644 test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol create mode 100644 test/unit/token/Royalty/RoyaltyFacetBase.t.sol create mode 100644 test/unit/token/Royalty/RoyaltyModBase.t.sol rename test/{token/Royalty/harnesses => utils/harnesses/token/Royalty}/RoyaltyFacetHarness.sol (92%) rename test/{token/Royalty/harnesses => utils/harnesses/token/Royalty}/RoyaltyHarness.sol (92%) diff --git a/test/integration/token/Royalty/Facet/fuzz/integration.t.sol b/test/integration/token/Royalty/Facet/fuzz/integration.t.sol new file mode 100644 index 00000000..13f326e5 --- /dev/null +++ b/test/integration/token/Royalty/Facet/fuzz/integration.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * Integration (Facet) + */ +contract Integration_RoyaltyFacet_Integration_Test is RoyaltyFacet_Base_Test { + function test_DefaultThenTokenThenReset_ThroughRoyaltyInfo() external { + uint256 tokenId = 5; + uint256 salePrice = 100 ether; + + facet.setDefaultRoyalty(users.alice, 500); + + (address receiver1, uint256 royalty1) = facet.royaltyInfo(tokenId, salePrice); + assertEq(receiver1, users.alice, "receiver1"); + assertEq(royalty1, 5 ether, "royalty1"); + + facet.setTokenRoyalty(tokenId, users.bob, 1_000); + + (address receiver2, uint256 royalty2) = facet.royaltyInfo(tokenId, salePrice); + assertEq(receiver2, users.bob, "receiver2"); + assertEq(royalty2, 10 ether, "royalty2"); + + facet.setTokenRoyalty(tokenId, ADDRESS_ZERO, 0); + + (address receiver3, uint256 royalty3) = facet.royaltyInfo(tokenId, salePrice); + assertEq(receiver3, users.alice, "receiver3"); + assertEq(royalty3, 5 ether, "royalty3"); + } + + function test_DefaultAndMultipleTokensThenDeleteDefault_ThroughRoyaltyInfo() external { + uint256 token1 = 1; + uint256 token2 = 2; + uint256 token3 = 3; + uint256 salePrice = 100 ether; + + facet.setDefaultRoyalty(users.alice, 500); + facet.setTokenRoyalty(token1, users.bob, 1_000); + facet.setTokenRoyalty(token2, users.charlee, 250); + + (address receiver1, uint256 royalty1) = facet.royaltyInfo(token1, salePrice); + (address receiver2, uint256 royalty2) = facet.royaltyInfo(token2, salePrice); + (address receiver3, uint256 royalty3) = facet.royaltyInfo(token3, salePrice); + + assertEq(receiver1, users.bob, "receiver1"); + assertEq(royalty1, 10 ether, "royalty1"); + + assertEq(receiver2, users.charlee, "receiver2"); + assertEq(royalty2, 2.5 ether, "royalty2"); + + assertEq(receiver3, users.alice, "receiver3"); + assertEq(royalty3, 5 ether, "royalty3"); + + facet.setDefaultRoyalty(ADDRESS_ZERO, 0); + + (receiver3, royalty3) = facet.royaltyInfo(token3, salePrice); + assertEq(receiver3, ADDRESS_ZERO, "receiver3 after delete"); + assertEq(royalty3, 0, "royalty3 after delete"); + } +} diff --git a/test/integration/token/Royalty/Mod/fuzz/integration.t.sol b/test/integration/token/Royalty/Mod/fuzz/integration.t.sol new file mode 100644 index 00000000..60d0df56 --- /dev/null +++ b/test/integration/token/Royalty/Mod/fuzz/integration.t.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * Integration (Mod) + */ +contract Integration_RoyaltyMod_Integration_Test is RoyaltyMod_Base_Test { + function test_DefaultThenTokenThenReset_Sequence() external { + uint256 tokenId = 5; + uint256 salePrice = 100 ether; + + harness.setDefaultRoyalty(users.alice, 500); + + (address receiver1, uint256 royalty1) = harness.royaltyInfo(tokenId, salePrice); + assertEq(receiver1, users.alice, "receiver1"); + assertEq(royalty1, 5 ether, "royalty1"); + + harness.setTokenRoyalty(tokenId, users.bob, 1_000); + + (address receiver2, uint256 royalty2) = harness.royaltyInfo(tokenId, salePrice); + assertEq(receiver2, users.bob, "receiver2"); + assertEq(royalty2, 10 ether, "royalty2"); + + harness.resetTokenRoyalty(tokenId); + + (address receiver3, uint256 royalty3) = harness.royaltyInfo(tokenId, salePrice); + assertEq(receiver3, users.alice, "receiver3"); + assertEq(royalty3, 5 ether, "royalty3"); + } + + function test_DefaultAndMultipleTokensThenDeleteDefault_Behavior() external { + uint256 token1 = 1; + uint256 token2 = 2; + uint256 token3 = 3; + uint256 salePrice = 100 ether; + + harness.setDefaultRoyalty(users.alice, 500); + harness.setTokenRoyalty(token1, users.bob, 1_000); + harness.setTokenRoyalty(token2, users.charlee, 250); + + (address receiver1, uint256 royalty1) = harness.royaltyInfo(token1, salePrice); + (address receiver2, uint256 royalty2) = harness.royaltyInfo(token2, salePrice); + (address receiver3, uint256 royalty3) = harness.royaltyInfo(token3, salePrice); + + assertEq(receiver1, users.bob, "receiver1"); + assertEq(royalty1, 10 ether, "royalty1"); + + assertEq(receiver2, users.charlee, "receiver2"); + assertEq(royalty2, 2.5 ether, "royalty2"); + + assertEq(receiver3, users.alice, "receiver3"); + assertEq(royalty3, 5 ether, "royalty3"); + + harness.deleteDefaultRoyalty(); + + // token-specific royalties remain unchanged + (receiver1, royalty1) = harness.royaltyInfo(token1, salePrice); + (receiver2, royalty2) = harness.royaltyInfo(token2, salePrice); + assertEq(receiver1, users.bob, "receiver1 after delete"); + assertEq(royalty1, 10 ether, "royalty1 after delete"); + assertEq(receiver2, users.charlee, "receiver2 after delete"); + assertEq(royalty2, 2.5 ether, "royalty2 after delete"); + + (receiver3, royalty3) = harness.royaltyInfo(token3, salePrice); + assertEq(receiver3, ADDRESS_ZERO, "receiver3 after delete"); + assertEq(royalty3, 0, "royalty3 after delete"); + } +} diff --git a/test/token/Royalty/Royalty.t.sol b/test/token/Royalty/Royalty.t.sol deleted file mode 100644 index af9cb6d6..00000000 --- a/test/token/Royalty/Royalty.t.sol +++ /dev/null @@ -1,555 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {RoyaltyHarness} from "./harnesses/RoyaltyHarness.sol"; -import "../../../src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; - -contract LibRoyaltyTest is Test { - RoyaltyHarness public harness; - - address public alice; - address public bob; - address public charlie; - address public royaltyReceiver; - - uint256 constant FEE_DENOMINATOR = 10000; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - royaltyReceiver = makeAddr("royaltyReceiver"); - - harness = new RoyaltyHarness(); - } - - /** - * ============================================ - * royaltyInfo Tests - * ============================================ - */ - - function test_RoyaltyInfo_NoRoyaltySet() public view { - uint256 tokenId = 1; - uint256 salePrice = 1 ether; - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, address(0)); - assertEq(royaltyAmount, 0); - } - - function test_RoyaltyInfo_DefaultRoyaltyOnly() public { - uint256 tokenId = 1; - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, salePrice * feeNumerator / FEE_DENOMINATOR); - } - - function test_RoyaltyInfo_5PercentRoyalty() public { - uint256 tokenId = 1; - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - /** - * 500) / 10000 = 5 ether - */ - uint256 expectedRoyalty = 5 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_10PercentRoyalty() public { - uint96 feeNumerator = 1000; // 10% - uint256 salePrice = 50 ether; - /** - * 1000) / 10000 = 5 ether - */ - uint256 expectedRoyalty = 5 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_50PercentRoyalty() public { - uint96 feeNumerator = 5000; // 50% - uint256 salePrice = 10 ether; - /** - * 5000) / 10000 = 5 ether - */ - uint256 expectedRoyalty = 5 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_100PercentRoyalty() public { - uint96 feeNumerator = 10000; // 100% - uint256 salePrice = 1 ether; - /** - * 10000) / 10000 = 1 ether - */ - uint256 expectedRoyalty = 1 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_TokenSpecificRoyalty() public { - uint256 tokenId = 5; - uint96 defaultFeeNumerator = 500; // 5% - uint96 tokenFeeNumerator = 750; // 7.5% - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, defaultFeeNumerator); - harness.setTokenRoyalty(tokenId, bob, tokenFeeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - /** - * 750) / 10000 = 7.5 ether - */ - assertEq(royaltyAmount, 7.5 ether); - } - - function test_RoyaltyInfo_TokenSpecificOverridesDefault() public { - uint256 tokenId = 10; - uint96 defaultFeeNumerator = 1000; // 10% - uint96 tokenFeeNumerator = 250; // 2.5% - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, defaultFeeNumerator); - harness.setTokenRoyalty(tokenId, bob, tokenFeeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - /** - * 250) / 10000 = 2.5 ether - */ - assertEq(royaltyAmount, 2.5 ether); - } - - function test_RoyaltyInfo_ZeroSalePrice() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 0; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 0); - } - - function test_RoyaltyInfo_ZeroRoyaltyPercentage() public { - uint96 feeNumerator = 0; // 0% - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 0); - } - - function testFuzz_RoyaltyInfo_WithValidFee(uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(salePrice <= 1000000 ether); // Prevent overflow with reasonable maximum - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR); - } - - function testFuzz_RoyaltyInfo_WithTokenRoyalty(uint256 tokenId, uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(salePrice <= 1000000 ether); // Prevent overflow with reasonable maximum - - harness.setTokenRoyalty(tokenId, bob, feeNumerator); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR); - } - - function test_RoyaltyInfo_MultipleTokensDifferentRoyalties() public { - uint256 token1 = 1; - uint256 token2 = 2; - uint256 token3 = 3; - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, 500); // 5% - harness.setTokenRoyalty(token1, bob, 1000); // 10% - harness.setTokenRoyalty(token2, charlie, 250); // 2.5% - - (address receiver1, uint256 royalty1) = harness.royaltyInfo(token1, salePrice); - (address receiver2, uint256 royalty2) = harness.royaltyInfo(token2, salePrice); - (address receiver3, uint256 royalty3) = harness.royaltyInfo(token3, salePrice); - - assertEq(receiver1, bob); - assertEq(royalty1, 10 ether); - - assertEq(receiver2, charlie); - assertEq(royalty2, 2.5 ether); - - assertEq(receiver3, alice); - assertEq(royalty3, 5 ether); - } - - function test_RoyaltyInfo_AfterResetTokenRoyalty() public { - uint256 tokenId = 1; - uint96 defaultFee = 500; // 5% - uint96 tokenFee = 1000; // 10% - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, defaultFee); - harness.setTokenRoyalty(tokenId, bob, tokenFee); - - (address receiver1, uint256 royalty1) = harness.royaltyInfo(tokenId, salePrice); - assertEq(receiver1, bob); - assertEq(royalty1, 10 ether); - - harness.resetTokenRoyalty(tokenId); - - (address receiver2, uint256 royalty2) = harness.royaltyInfo(tokenId, salePrice); - assertEq(receiver2, alice); - assertEq(royalty2, 5 ether); - } - - /** - * ============================================ - * setDefaultRoyalty Tests - * ============================================ - */ - - function test_SetDefaultRoyalty() public { - uint96 feeNumerator = 500; // 5% - - harness.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - assertEq(harness.getDefaultRoyaltyReceiver(), royaltyReceiver); - assertEq(harness.getDefaultRoyaltyFraction(), feeNumerator); - } - - function test_SetDefaultRoyalty_UpdatesExisting() public { - harness.setDefaultRoyalty(alice, 500); - harness.setDefaultRoyalty(bob, 1000); - - assertEq(harness.getDefaultRoyaltyReceiver(), bob); - assertEq(harness.getDefaultRoyaltyFraction(), 1000); - } - - function test_SetDefaultRoyalty_ZeroPercentage() public { - harness.setDefaultRoyalty(royaltyReceiver, 0); - - assertEq(harness.getDefaultRoyaltyReceiver(), royaltyReceiver); - assertEq(harness.getDefaultRoyaltyFraction(), 0); - } - - function test_SetDefaultRoyalty_MaxPercentage() public { - uint96 maxFee = 10000; // 100% - - harness.setDefaultRoyalty(royaltyReceiver, maxFee); - - assertEq(harness.getDefaultRoyaltyReceiver(), royaltyReceiver); - assertEq(harness.getDefaultRoyaltyFraction(), maxFee); - } - - function testFuzz_SetDefaultRoyalty(address receiver, uint96 feeNumerator) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(receiver != address(0)); - - harness.setDefaultRoyalty(receiver, feeNumerator); - - assertEq(harness.getDefaultRoyaltyReceiver(), receiver); - assertEq(harness.getDefaultRoyaltyFraction(), feeNumerator); - } - - function test_RevertWhen_SetDefaultRoyalty_InvalidFee() public { - uint96 invalidFee = 10001; // More than 100% - - vm.expectRevert( - abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyalty.selector, invalidFee, FEE_DENOMINATOR) - ); - harness.setDefaultRoyalty(royaltyReceiver, invalidFee); - } - - function test_RevertWhen_SetDefaultRoyalty_ZeroReceiver() public { - vm.expectRevert(abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyaltyReceiver.selector, address(0))); - harness.setDefaultRoyalty(address(0), 500); - } - - /** - * ============================================ - * deleteDefaultRoyalty Tests - * ============================================ - */ - - function test_DeleteDefaultRoyalty() public { - harness.setDefaultRoyalty(royaltyReceiver, 500); - harness.deleteDefaultRoyalty(); - - assertEq(harness.getDefaultRoyaltyReceiver(), address(0)); - assertEq(harness.getDefaultRoyaltyFraction(), 0); - } - - function test_DeleteDefaultRoyalty_NoEffectOnTokenRoyalty() public { - uint256 tokenId = 1; - - harness.setDefaultRoyalty(alice, 500); - harness.setTokenRoyalty(tokenId, bob, 1000); - harness.deleteDefaultRoyalty(); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), bob); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 1000); - } - - function test_DeleteDefaultRoyalty_RoyaltyInfoReturnsZero() public { - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(royaltyReceiver, 500); - harness.deleteDefaultRoyalty(); - - (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); - - assertEq(receiver, address(0)); - assertEq(royaltyAmount, 0); - } - - /** - * ============================================ - * setTokenRoyalty Tests - * ============================================ - */ - - function test_SetTokenRoyalty() public { - uint256 tokenId = 1; - uint96 feeNumerator = 500; // 5% - - harness.setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), royaltyReceiver); - assertEq(harness.getTokenRoyaltyFraction(tokenId), feeNumerator); - } - - function test_SetTokenRoyalty_MultipleTokens() public { - harness.setTokenRoyalty(1, alice, 500); - harness.setTokenRoyalty(2, bob, 1000); - harness.setTokenRoyalty(3, charlie, 250); - - assertEq(harness.getTokenRoyaltyReceiver(1), alice); - assertEq(harness.getTokenRoyaltyFraction(1), 500); - - assertEq(harness.getTokenRoyaltyReceiver(2), bob); - assertEq(harness.getTokenRoyaltyFraction(2), 1000); - - assertEq(harness.getTokenRoyaltyReceiver(3), charlie); - assertEq(harness.getTokenRoyaltyFraction(3), 250); - } - - function test_SetTokenRoyalty_UpdatesExisting() public { - uint256 tokenId = 1; - - harness.setTokenRoyalty(tokenId, alice, 500); - harness.setTokenRoyalty(tokenId, bob, 1000); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), bob); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 1000); - } - - function test_SetTokenRoyalty_ZeroPercentage() public { - uint256 tokenId = 1; - - harness.setTokenRoyalty(tokenId, royaltyReceiver, 0); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), royaltyReceiver); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 0); - } - - function test_SetTokenRoyalty_MaxPercentage() public { - uint256 tokenId = 1; - uint96 maxFee = 10000; // 100% - - harness.setTokenRoyalty(tokenId, royaltyReceiver, maxFee); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), royaltyReceiver); - assertEq(harness.getTokenRoyaltyFraction(tokenId), maxFee); - } - - function testFuzz_SetTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(receiver != address(0)); - - harness.setTokenRoyalty(tokenId, receiver, feeNumerator); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), receiver); - assertEq(harness.getTokenRoyaltyFraction(tokenId), feeNumerator); - } - - function test_RevertWhen_SetTokenRoyalty_InvalidFee() public { - uint256 tokenId = 1; - uint96 invalidFee = 10001; // More than 100% - - vm.expectRevert( - abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidTokenRoyalty.selector, tokenId, invalidFee, FEE_DENOMINATOR) - ); - harness.setTokenRoyalty(tokenId, royaltyReceiver, invalidFee); - } - - function test_RevertWhen_SetTokenRoyalty_ZeroReceiver() public { - uint256 tokenId = 1; - - vm.expectRevert( - abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidTokenRoyaltyReceiver.selector, tokenId, address(0)) - ); - harness.setTokenRoyalty(tokenId, address(0), 500); - } - - /** - * ============================================ - * resetTokenRoyalty Tests - * ============================================ - */ - - function test_ResetTokenRoyalty() public { - uint256 tokenId = 1; - - harness.setTokenRoyalty(tokenId, royaltyReceiver, 500); - harness.resetTokenRoyalty(tokenId); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), address(0)); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 0); - } - - function test_ResetTokenRoyalty_FallsBackToDefault() public { - uint256 tokenId = 1; - - harness.setDefaultRoyalty(alice, 500); - harness.setTokenRoyalty(tokenId, bob, 1000); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), bob); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 1000); - - harness.resetTokenRoyalty(tokenId); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), address(0)); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 0); - } - - function test_ResetTokenRoyalty_MultipleTokens() public { - harness.setTokenRoyalty(1, alice, 500); - harness.setTokenRoyalty(2, bob, 1000); - harness.setTokenRoyalty(3, charlie, 250); - - harness.resetTokenRoyalty(1); - harness.resetTokenRoyalty(3); - - assertEq(harness.getTokenRoyaltyReceiver(1), address(0)); - assertEq(harness.getTokenRoyaltyReceiver(2), bob); - assertEq(harness.getTokenRoyaltyReceiver(3), address(0)); - } - - function testFuzz_ResetTokenRoyalty(uint256 tokenId, uint96 feeNumerator) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - - harness.setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); - harness.resetTokenRoyalty(tokenId); - - assertEq(harness.getTokenRoyaltyReceiver(tokenId), address(0)); - assertEq(harness.getTokenRoyaltyFraction(tokenId), 0); - } - - /** - * ============================================ - * Integration Tests - * ============================================ - */ - - function test_SetDefaultThenTokenThenReset() public { - uint256 tokenId = 5; - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, 500); // 5% - - (address receiver1, uint256 royalty1) = harness.royaltyInfo(tokenId, salePrice); - assertEq(receiver1, alice); - assertEq(royalty1, 5 ether); - - harness.setTokenRoyalty(tokenId, bob, 1000); // 10% - - (address receiver2, uint256 royalty2) = harness.royaltyInfo(tokenId, salePrice); - assertEq(receiver2, bob); - assertEq(royalty2, 10 ether); - - harness.resetTokenRoyalty(tokenId); - - (address receiver3, uint256 royalty3) = harness.royaltyInfo(tokenId, salePrice); - assertEq(receiver3, alice); - assertEq(royalty3, 5 ether); - } - - function test_ComplexRoyaltyFlow() public { - uint256 token1 = 1; - uint256 token2 = 2; - uint256 token3 = 3; - uint256 salePrice = 100 ether; - - harness.setDefaultRoyalty(alice, 500); // 5% default - - harness.setTokenRoyalty(token1, bob, 1000); // 10% - harness.setTokenRoyalty(token2, charlie, 250); // 2.5% - - (address receiver1, uint256 royalty1) = harness.royaltyInfo(token1, salePrice); - (address receiver2, uint256 royalty2) = harness.royaltyInfo(token2, salePrice); - (address receiver3, uint256 royalty3) = harness.royaltyInfo(token3, salePrice); - - assertEq(receiver1, bob); - assertEq(royalty1, 10 ether); - - assertEq(receiver2, charlie); - assertEq(royalty2, 2.5 ether); - - assertEq(receiver3, alice); - assertEq(royalty3, 5 ether); - - harness.deleteDefaultRoyalty(); - - (receiver3, royalty3) = harness.royaltyInfo(token3, salePrice); - assertEq(receiver3, address(0)); - assertEq(royalty3, 0); - } -} diff --git a/test/token/Royalty/RoyaltyFacet.t.sol b/test/token/Royalty/RoyaltyFacet.t.sol deleted file mode 100644 index 5c808243..00000000 --- a/test/token/Royalty/RoyaltyFacet.t.sol +++ /dev/null @@ -1,468 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.30; - -/* Compose - * https://compose.diamonds - */ - -import {Test} from "forge-std/Test.sol"; -import {RoyaltyFacet} from "../../../src/token/Royalty/RoyaltyFacet.sol"; -import {RoyaltyFacetHarness} from "./harnesses/RoyaltyFacetHarness.sol"; - -contract RoyaltyFacetTest is Test { - RoyaltyFacetHarness public facet; - - address public alice; - address public bob; - address public charlie; - address public royaltyReceiver; - - uint256 constant FEE_DENOMINATOR = 10000; - - function setUp() public { - alice = makeAddr("alice"); - bob = makeAddr("bob"); - charlie = makeAddr("charlie"); - royaltyReceiver = makeAddr("royaltyReceiver"); - - facet = new RoyaltyFacetHarness(); - } - - /** - * ============================================ - * Helper Functions - * ============================================ - */ - - /** - * @notice Helper to set default royalty - */ - function _setDefaultRoyalty(address _receiver, uint96 _feeNumerator) internal { - facet.setDefaultRoyalty(_receiver, _feeNumerator); - } - - /** - * @notice Helper to set token royalty - */ - function _setTokenRoyalty(uint256 _tokenId, address _receiver, uint96 _feeNumerator) internal { - facet.setTokenRoyalty(_tokenId, _receiver, _feeNumerator); - } - - /** - * ============================================ - * royaltyInfo Tests - * ============================================ - */ - - function test_RoyaltyInfo_NoRoyaltySet() public view { - uint256 tokenId = 1; - uint256 salePrice = 1 ether; - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, address(0)); - assertEq(royaltyAmount, 0); - } - - function test_RoyaltyInfo_DefaultRoyaltyOnly() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, salePrice * feeNumerator / FEE_DENOMINATOR); - } - - function test_RoyaltyInfo_5PercentRoyalty() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - uint256 expectedRoyalty = 5 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_10PercentRoyalty() public { - uint96 feeNumerator = 1000; // 10% - uint256 salePrice = 50 ether; - uint256 expectedRoyalty = 5 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_50PercentRoyalty() public { - uint96 feeNumerator = 5000; // 50% - uint256 salePrice = 10 ether; - uint256 expectedRoyalty = 5 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_100PercentRoyalty() public { - uint96 feeNumerator = 10000; // 100% - uint256 salePrice = 1 ether; - uint256 expectedRoyalty = 1 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_TokenSpecificRoyalty() public { - uint256 tokenId = 5; - uint96 tokenFeeNumerator = 750; // 7.5% - uint256 salePrice = 100 ether; - - _setTokenRoyalty(tokenId, bob, tokenFeeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - assertEq(royaltyAmount, 7.5 ether); - } - - function test_RoyaltyInfo_TokenSpecificOverridesDefault() public { - uint256 tokenId = 10; - uint96 defaultFeeNumerator = 1000; // 10% - uint96 tokenFeeNumerator = 250; // 2.5% - uint256 salePrice = 100 ether; - - _setDefaultRoyalty(alice, defaultFeeNumerator); - _setTokenRoyalty(tokenId, bob, tokenFeeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - assertEq(royaltyAmount, 2.5 ether); - } - - function test_RoyaltyInfo_ZeroSalePrice() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 0; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 0); - } - - function test_RoyaltyInfo_ZeroRoyaltyPercentage() public { - uint96 feeNumerator = 0; // 0% - uint256 salePrice = 100 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 0); - } - - function testFuzz_RoyaltyInfo_WithValidFee(uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(salePrice <= 1000000 ether); // Prevent overflow with reasonable maximum - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR); - } - - function testFuzz_RoyaltyInfo_WithTokenRoyalty(uint256 tokenId, uint96 feeNumerator, uint256 salePrice) public { - vm.assume(feeNumerator <= FEE_DENOMINATOR); - vm.assume(salePrice <= 1000000 ether); // Prevent overflow with reasonable maximum - - _setTokenRoyalty(tokenId, bob, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, bob); - assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR); - } - - function test_RoyaltyInfo_MultipleTokensDifferentRoyalties() public { - uint256 token1 = 1; - uint256 token2 = 2; - uint256 token3 = 3; - uint256 salePrice = 100 ether; - - _setDefaultRoyalty(alice, 500); // 5% - _setTokenRoyalty(token1, bob, 1000); // 10% - _setTokenRoyalty(token2, charlie, 250); // 2.5% - - (address receiver1, uint256 royalty1) = facet.royaltyInfo(token1, salePrice); - (address receiver2, uint256 royalty2) = facet.royaltyInfo(token2, salePrice); - (address receiver3, uint256 royalty3) = facet.royaltyInfo(token3, salePrice); - - assertEq(receiver1, bob); - assertEq(royalty1, 10 ether); - - assertEq(receiver2, charlie); - assertEq(royalty2, 2.5 ether); - - assertEq(receiver3, alice); - assertEq(royalty3, 5 ether); - } - - function test_RoyaltyInfo_FractionalRoyalty() public { - uint96 feeNumerator = 1; // 0.01% - uint256 salePrice = 100000 ether; - uint256 expectedRoyalty = 10 ether; - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_RoyaltyInfo_LargeSalePrice() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 1000000000 ether; // Large but safe value - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, salePrice * feeNumerator / FEE_DENOMINATOR); - } - - function test_RoyaltyInfo_VariousFeePercentages() public { - uint256 salePrice = 1 ether; - - uint96[] memory fees = new uint96[](6); - fees[0] = 1; // 0.01% - fees[1] = 25; // 0.25% - fees[2] = 100; // 1% - fees[3] = 250; // 2.5% - fees[4] = 500; // 5% - fees[5] = 750; // 7.5% - - uint256[] memory expectedRoyalties = new uint256[](6); - expectedRoyalties[0] = 0.0001 ether; - expectedRoyalties[1] = 0.0025 ether; - expectedRoyalties[2] = 0.01 ether; - expectedRoyalties[3] = 0.025 ether; - expectedRoyalties[4] = 0.05 ether; - expectedRoyalties[5] = 0.075 ether; - - for (uint256 i = 0; i < fees.length; i++) { - _setDefaultRoyalty(royaltyReceiver, fees[i]); - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalties[i]); - } - } - - function test_RoyaltyInfo_SmallTokenId() public { - uint256 tokenId = 0; - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - - _setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 5 ether); - } - - function test_RoyaltyInfo_VeryLargeTokenId() public { - uint256 tokenId = type(uint256).max; - uint96 feeNumerator = 1000; // 10% - uint256 salePrice = 50 ether; - - _setTokenRoyalty(tokenId, royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 5 ether); - } - - function test_RoyaltyInfo_FallsBackWhenTokenRoyaltyNotSet() public { - uint256 tokenId = 999; - uint96 defaultFee = 750; // 7.5% - uint256 salePrice = 100 ether; - - _setDefaultRoyalty(royaltyReceiver, defaultFee); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 7.5 ether); - } - - function test_RoyaltyInfo_DefaultRoyaltyAddress() public { - uint256 salePrice = 100 ether; - uint96 feeNumerator = 500; // 5% - - _setDefaultRoyalty(alice, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, alice); - assertEq(royaltyAmount, 5 ether); - } - - function test_RoyaltyInfo_ZeroAddressReceiverReturnsZero() public view { - /** - * Set up a token royalty with non-zero fee but zero address - * This simulates the edge case - */ - uint256 tokenId = 5; - uint256 salePrice = 100 ether; - - /** - * No royalty set - should return zero - */ - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); - assertEq(receiver, address(0)); - assertEq(royaltyAmount, 0); - } - - /** - * ============================================ - * Storage Consistency Tests - * ============================================ - */ - - function test_StorageSlot_Consistency() public { - uint96 feeNumerator = 500; // 5% - - facet.setDefaultRoyalty(royaltyReceiver, feeNumerator); - - /** - * Read back through facet function to verify - */ - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(999, 100 ether); - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, 5 ether); - } - - function test_StorageSlot_TokenRoyaltyConsistency() public { - uint256 tokenId = 42; - uint96 feeNumerator = 1000; // 10% - - facet.setTokenRoyalty(tokenId, bob, feeNumerator); - - /** - * Read back through facet function to verify - */ - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, 100 ether); - assertEq(receiver, bob); - assertEq(royaltyAmount, 10 ether); - } - - /** - * ============================================ - * Edge Cases - * ============================================ - */ - - function test_RoyaltyInfo_WithMaximumValues() public { - uint96 maxFee = 10000; // 100% - uint256 maxSalePrice = 1000000000 ether; // Large but safe value - - _setDefaultRoyalty(royaltyReceiver, maxFee); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, maxSalePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, maxSalePrice); - } - - function test_RoyaltyInfo_VariousTokenIds() public { - uint96 feeNumerator = 500; // 5% - uint256 salePrice = 100 ether; - - _setTokenRoyalty(1, alice, feeNumerator); - _setTokenRoyalty(100, bob, feeNumerator); - _setTokenRoyalty(999999, charlie, feeNumerator); - - (address receiver1, uint256 royalty1) = facet.royaltyInfo(1, salePrice); - (address receiver2, uint256 royalty2) = facet.royaltyInfo(100, salePrice); - (address receiver3, uint256 royalty3) = facet.royaltyInfo(999999, salePrice); - - assertEq(receiver1, alice); - assertEq(royalty1, 5 ether); - - assertEq(receiver2, bob); - assertEq(royalty2, 5 ether); - - assertEq(receiver3, charlie); - assertEq(royalty3, 5 ether); - } - - function test_RoyaltyInfo_MinimalRoyaltyFee() public { - uint96 feeNumerator = 1; // 0.01% - uint256 salePrice = 1 ether; - uint256 expectedRoyalty = 0.0001 ether; // 0.0001 ETH - - _setDefaultRoyalty(royaltyReceiver, feeNumerator); - - (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); - - assertEq(receiver, royaltyReceiver); - assertEq(royaltyAmount, expectedRoyalty); - } - - function test_ComplexScenario_MultipleTokensAndDefaults() public { - /** - * Set up complex royalty structure - */ - _setDefaultRoyalty(alice, 500); // 5% default - - _setTokenRoyalty(1, bob, 1000); // Token 1: 10% - _setTokenRoyalty(2, charlie, 250); // Token 2: 2.5% - - uint256 salePrice = 100 ether; - - /** - * Token 1 should use token-specific - */ - (address receiver1, uint256 royalty1) = facet.royaltyInfo(1, salePrice); - assertEq(receiver1, bob); - assertEq(royalty1, 10 ether); - - /** - * Token 2 should use token-specific - */ - (address receiver2, uint256 royalty2) = facet.royaltyInfo(2, salePrice); - assertEq(receiver2, charlie); - assertEq(royalty2, 2.5 ether); - - /** - * Token 999 should use default - */ - (address receiver3, uint256 royalty3) = facet.royaltyInfo(999, salePrice); - assertEq(receiver3, alice); - assertEq(royalty3, 5 ether); - } -} diff --git a/test/trees/Royalty.tree b/test/trees/Royalty.tree new file mode 100644 index 00000000..49e5af46 --- /dev/null +++ b/test/trees/Royalty.tree @@ -0,0 +1,73 @@ +RoyaltyInfo +β”œβ”€β”€ when no default or token royalty is set +β”‚ └── it should return zero address and zero royalty amount +β”œβ”€β”€ when only default royalty is set +β”‚ └── it should return the default receiver and salePrice * defaultFee / FEE_DENOMINATOR +β”œβ”€β”€ when only token-specific royalty is set +β”‚ └── it should return the token-specific receiver and salePrice * tokenFee / FEE_DENOMINATOR +β”œβ”€β”€ when both default and token-specific royalty are set +β”‚ └── it should prefer the token-specific royalty over the default +β”œβ”€β”€ when sale price is zero +β”‚ └── it should return zero royalty amount while preserving the configured receiver +└── when royalty fee is zero + └── it should return zero royalty amount while preserving the configured receiver + +DefaultRoyalty +β”œβ”€β”€ SetDefaultRoyalty +β”‚ β”œβ”€β”€ when called with a valid receiver and fee <= FEE_DENOMINATOR +β”‚ β”‚ └── it should set the default royalty receiver and fraction +β”‚ β”œβ”€β”€ when called multiple times with different values +β”‚ β”‚ └── it should overwrite the previous default royalty receiver and fraction +β”‚ β”œβ”€β”€ when called with fee equal to zero +β”‚ β”‚ └── it should set default royalty with zero fraction but keep the receiver +β”‚ β”œβ”€β”€ when called with fee greater than FEE_DENOMINATOR +β”‚ β”‚ └── it should revert with ERC2981InvalidDefaultRoyalty +β”‚ └── when called with the zero address as receiver +β”‚ └── it should revert with ERC2981InvalidDefaultRoyaltyReceiver +└── DeleteDefaultRoyalty + β”œβ”€β”€ when a default royalty is set and then deleted + β”‚ └── it should clear the default royalty receiver and fraction in storage + β”œβ”€β”€ when a default royalty is deleted and no token-specific royalty exists + β”‚ └── royaltyInfo should return zero address and zero royalty amount + └── when a default royalty is deleted but token-specific royalty exists + └── royaltyInfo for tokens with token-specific royalty should remain unchanged + +TokenRoyalty +β”œβ”€β”€ SetTokenRoyalty +β”‚ β”œβ”€β”€ when called with a valid receiver and fee <= FEE_DENOMINATOR +β”‚ β”‚ └── it should set the token-specific royalty receiver and fraction for that tokenId +β”‚ β”œβ”€β”€ when called for multiple tokenIds +β”‚ β”‚ └── it should maintain independent royalty settings per tokenId +β”‚ β”œβ”€β”€ when called again for the same tokenId +β”‚ β”‚ └── it should overwrite the previous token-specific royalty receiver and fraction +β”‚ β”œβ”€β”€ when called with fee equal to zero +β”‚ β”‚ └── it should set token-specific royalty with zero fraction but keep the receiver +β”‚ β”œβ”€β”€ when called with fee greater than FEE_DENOMINATOR +β”‚ β”‚ └── it should revert with ERC2981InvalidTokenRoyalty +β”‚ └── when called with the zero address as receiver +β”‚ └── it should revert with ERC2981InvalidTokenRoyaltyReceiver +└── ResetTokenRoyalty + β”œβ”€β”€ when token-specific royalty exists and default royalty is set + β”‚ └── it should clear token-specific royalty and cause royaltyInfo to fall back to the default royalty + β”œβ”€β”€ when token-specific royalty exists and no default royalty is set + β”‚ └── it should clear token-specific royalty and cause royaltyInfo to return zero address and zero royalty amount + └── when multiple tokenIds have token-specific royalty and some are reset + └── it should only clear royalty for the reset tokenIds and leave the others unchanged + +Integration +β”œβ”€β”€ when default royalty is set, then token-specific royalty is set, then token-specific royalty is reset +β”‚ └── royaltyInfo should first use the default, then the token-specific, then fall back to the default again +└── when default royalty is set, multiple tokens have different token-specific royalties, and default royalty is deleted + β”œβ”€β”€ royaltyInfo for tokens with token-specific royalty should continue using their token-specific configuration + └── royaltyInfo for tokens without token-specific royalty should return zero address and zero royalty amount + +EdgeCases +β”œβ”€β”€ when sale price and fee create fractional royalty amounts +β”‚ └── it should compute the royalty amount correctly without overflow +β”œβ”€β”€ when sale price is very large but below overflow thresholds +β”‚ └── it should compute the royalty amount without reverting +β”œβ”€β”€ when tokenId is zero +β”‚ └── it should behave the same as for other valid tokenIds +└── when tokenId is the maximum uint256 value + └── it should behave the same as for other valid tokenIds + diff --git a/test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol b/test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol new file mode 100644 index 00000000..512da2bc --- /dev/null +++ b/test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * DefaultRoyalty + */ +contract DefaultRoyalty_RoyaltyFacet_Fuzz_Unit_Test is RoyaltyFacet_Base_Test { + function testFuzz_SetDefaultRoyalty_VisibleThroughRoyaltyInfo( + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) external { + vm.assume(receiver != ADDRESS_ZERO); + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + facet.setDefaultRoyalty(receiver, feeNumerator); + + (address royaltyReceiverResult, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); + + assertEq(royaltyReceiverResult, receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function test_SetDefaultRoyalty_UpdatesExisting_ThroughRoyaltyInfo() external { + facet.setDefaultRoyalty(users.alice, 500); + facet.setDefaultRoyalty(users.bob, 1_000); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, 100 ether); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, 10 ether, "royaltyAmount"); + } + + function test_SetDefaultRoyalty_ZeroFee_KeepsReceiver_ThroughRoyaltyInfo() external { + facet.setDefaultRoyalty(users.receiver, 0); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, 100 ether); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } +} diff --git a/test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol b/test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol new file mode 100644 index 00000000..c9ac1874 --- /dev/null +++ b/test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * EdgeCases + */ +contract EdgeCases_RoyaltyFacet_Unit_Test is RoyaltyFacet_Base_Test { + function test_RoyaltyInfo_FractionalRoyalty_ThroughRoyaltyInfo() external { + uint96 feeNumerator = 1; // 0.01% + uint256 salePrice = 100_000 ether; + uint256 expectedRoyalty = 10 ether; + + facet.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, expectedRoyalty, "royaltyAmount"); + } + + function test_RoyaltyInfo_LargeSalePrice_ThroughRoyaltyInfo() external { + uint96 feeNumerator = 500; // 5% + uint256 salePrice = 1_000_000_000 ether; + + facet.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function test_RoyaltyInfo_TokenIdZero_ThroughRoyaltyInfo() external { + uint256 tokenId = 0; + uint96 feeNumerator = 500; // 5% + uint256 salePrice = 100 ether; + + facet.setTokenRoyalty(tokenId, users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 5 ether, "royaltyAmount"); + } + + function test_RoyaltyInfo_TokenIdMax_ThroughRoyaltyInfo() external { + uint256 tokenId = type(uint256).max; + uint96 feeNumerator = 1_000; // 10% + uint256 salePrice = 50 ether; + + facet.setTokenRoyalty(tokenId, users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 5 ether, "royaltyAmount"); + } +} diff --git a/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol b/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol new file mode 100644 index 00000000..5d4038db --- /dev/null +++ b/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test} from "forge-std/Test.sol"; +import {RoyaltyFacetHarness} from "test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol"; + +// Intentionally left empty; Royalty facet BTT tests are split +// into dedicated suites per Royalty.tree section. + diff --git a/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol new file mode 100644 index 00000000..3a422e6b --- /dev/null +++ b/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * RoyaltyInfo + */ +contract RoyaltyInfo_RoyaltyFacet_Fuzz_Unit_Test is RoyaltyFacet_Base_Test { + function testFuzz_RoyaltyInfo_NoRoyaltySet(uint256 tokenId, uint256 salePrice) external view { + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, ADDRESS_ZERO, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_OnlyDefaultRoyalty(uint96 feeNumerator, uint256 salePrice) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + facet.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_OnlyTokenRoyalty( + uint256 tokenId, + uint96 feeNumerator, + uint256 salePrice + ) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + facet.setTokenRoyalty(tokenId, users.bob, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_DefaultAndTokenRoyalty_TokenOverridesDefault( + uint256 tokenId, + uint96 defaultFee, + uint96 tokenFee, + uint256 salePrice + ) external { + vm.assume(defaultFee <= FEE_DENOMINATOR); + vm.assume(tokenFee <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + facet.setDefaultRoyalty(users.alice, defaultFee); + facet.setTokenRoyalty(tokenId, users.bob, tokenFee); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, (salePrice * tokenFee) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_ZeroSalePrice_PreservesReceiver(uint96 feeNumerator, uint256 tokenId) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + facet.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, 0); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_ZeroFee_PreservesReceiver(uint256 salePrice) external { + vm.assume(salePrice <= 1_000_000 ether); + + facet.setDefaultRoyalty(users.receiver, 0); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } +} diff --git a/test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol b/test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol new file mode 100644 index 00000000..24770322 --- /dev/null +++ b/test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * TokenRoyalty + */ +contract TokenRoyalty_RoyaltyFacet_Fuzz_Unit_Test is RoyaltyFacet_Base_Test { + function testFuzz_SetTokenRoyalty_VisibleThroughRoyaltyInfo( + uint256 tokenId, + address receiver, + uint96 feeNumerator, + uint256 salePrice + ) external { + vm.assume(receiver != ADDRESS_ZERO); + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + facet.setTokenRoyalty(tokenId, receiver, feeNumerator); + + (address royaltyReceiverResult, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, salePrice); + + assertEq(royaltyReceiverResult, receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function test_SetTokenRoyalty_MultipleTokens_IndependentState_ThroughRoyaltyInfo() external { + facet.setTokenRoyalty(1, users.alice, 500); + facet.setTokenRoyalty(2, users.bob, 1_000); + facet.setTokenRoyalty(3, users.charlee, 250); + + (address receiver1, uint256 royalty1) = facet.royaltyInfo(1, 100 ether); + (address receiver2, uint256 royalty2) = facet.royaltyInfo(2, 100 ether); + (address receiver3, uint256 royalty3) = facet.royaltyInfo(3, 100 ether); + + assertEq(receiver1, users.alice, "receiver1"); + assertEq(royalty1, 5 ether, "royalty1"); + + assertEq(receiver2, users.bob, "receiver2"); + assertEq(royalty2, 10 ether, "royalty2"); + + assertEq(receiver3, users.charlee, "receiver3"); + assertEq(royalty3, 2.5 ether, "royalty3"); + } + + function test_SetTokenRoyalty_UpdatesExisting_ThroughRoyaltyInfo() external { + uint256 tokenId = 1; + + facet.setTokenRoyalty(tokenId, users.alice, 500); + facet.setTokenRoyalty(tokenId, users.bob, 1_000); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, 100 ether); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, 10 ether, "royaltyAmount"); + } + + function test_SetTokenRoyalty_ZeroFee_KeepsReceiver_ThroughRoyaltyInfo() external { + uint256 tokenId = 1; + + facet.setTokenRoyalty(tokenId, users.receiver, 0); + + (address receiver, uint256 royaltyAmount) = facet.royaltyInfo(tokenId, 100 ether); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } +} diff --git a/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol new file mode 100644 index 00000000..cc1669ac --- /dev/null +++ b/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol"; + +import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * DefaultRoyalty + */ +contract DefaultRoyalty_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { + /* SetDefaultRoyalty */ + + function testFuzz_SetDefaultRoyalty_SetsReceiverAndFraction(address receiver, uint96 feeNumerator) external { + vm.assume(receiver != ADDRESS_ZERO); + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + harness.setDefaultRoyalty(receiver, feeNumerator); + + assertEq(harness.getDefaultRoyaltyReceiver(), receiver, "default receiver"); + assertEq(harness.getDefaultRoyaltyFraction(), feeNumerator, "default fraction"); + } + + function test_SetDefaultRoyalty_UpdatesExisting() external { + harness.setDefaultRoyalty(users.alice, 500); + harness.setDefaultRoyalty(users.bob, 1_000); + + assertEq(harness.getDefaultRoyaltyReceiver(), users.bob, "default receiver"); + assertEq(harness.getDefaultRoyaltyFraction(), 1_000, "default fraction"); + } + + function test_SetDefaultRoyalty_ZeroFee_KeepsReceiver() external { + harness.setDefaultRoyalty(users.receiver, 0); + + assertEq(harness.getDefaultRoyaltyReceiver(), users.receiver, "default receiver"); + assertEq(harness.getDefaultRoyaltyFraction(), 0, "default fraction"); + } + + function test_RevertWhen_SetDefaultRoyalty_FeeAboveDenominator() external { + uint96 invalidFee = uint96(FEE_DENOMINATOR + 1); + + vm.expectRevert( + abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyalty.selector, invalidFee, FEE_DENOMINATOR) + ); + harness.setDefaultRoyalty(users.receiver, invalidFee); + } + + function test_RevertWhen_SetDefaultRoyalty_ZeroReceiver() external { + vm.expectRevert( + abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyaltyReceiver.selector, ADDRESS_ZERO) + ); + harness.setDefaultRoyalty(ADDRESS_ZERO, 500); + } + + /* DeleteDefaultRoyalty */ + + function testFuzz_DeleteDefaultRoyalty_ClearsStorage(uint96 feeNumerator) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + harness.setDefaultRoyalty(users.receiver, feeNumerator); + harness.deleteDefaultRoyalty(); + + assertEq(harness.getDefaultRoyaltyReceiver(), ADDRESS_ZERO, "default receiver"); + assertEq(harness.getDefaultRoyaltyFraction(), 0, "default fraction"); + } + + function test_DeleteDefaultRoyalty_NoTokenRoyalty_RoyaltyInfoZero() external { + uint256 salePrice = 100 ether; + + harness.setDefaultRoyalty(users.receiver, 500); + harness.deleteDefaultRoyalty(); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); + + assertEq(receiver, ADDRESS_ZERO, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } + + function test_DeleteDefaultRoyalty_WithTokenRoyalty_TokenRoyaltyUnchanged() external { + uint256 tokenId = 1; + + harness.setDefaultRoyalty(users.alice, 500); + harness.setTokenRoyalty(tokenId, users.bob, 1_000); + harness.deleteDefaultRoyalty(); + + assertEq(harness.getTokenRoyaltyReceiver(tokenId), users.bob, "token receiver"); + assertEq(harness.getTokenRoyaltyFraction(tokenId), 1_000, "token fraction"); + } +} diff --git a/test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol b/test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol new file mode 100644 index 00000000..62c9dc1e --- /dev/null +++ b/test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol"; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * EdgeCases + */ +contract EdgeCases_RoyaltyMod_Unit_Test is RoyaltyMod_Base_Test { + function test_RoyaltyInfo_FractionalRoyalty() external { + uint96 feeNumerator = 1; // 0.01% + uint256 salePrice = 100_000 ether; + uint256 expectedRoyalty = 10 ether; + + harness.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, expectedRoyalty, "royaltyAmount"); + } + + function test_RoyaltyInfo_LargeSalePrice() external { + uint96 feeNumerator = 500; // 5% + uint256 salePrice = 1_000_000_000 ether; + + harness.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function test_RoyaltyInfo_TokenIdZero() external { + uint256 tokenId = 0; + uint96 feeNumerator = 500; // 5% + uint256 salePrice = 100 ether; + + harness.setTokenRoyalty(tokenId, users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 5 ether, "royaltyAmount"); + } + + function test_RoyaltyInfo_TokenIdMax() external { + uint256 tokenId = type(uint256).max; + uint96 feeNumerator = 1_000; // 10% + uint256 salePrice = 50 ether; + + harness.setTokenRoyalty(tokenId, users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 5 ether, "royaltyAmount"); + } +} diff --git a/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol new file mode 100644 index 00000000..f14bd146 --- /dev/null +++ b/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {Test} from "forge-std/Test.sol"; +import {RoyaltyHarness} from "test/utils/harnesses/token/Royalty/RoyaltyHarness.sol"; +import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; + +// Intentionally left empty; Royalty mod BTT tests are split +// into dedicated suites per Royalty.tree section. + diff --git a/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol new file mode 100644 index 00000000..d0b253ae --- /dev/null +++ b/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol"; + +import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * RoyaltyInfo + */ +contract RoyaltyInfo_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { + function testFuzz_RoyaltyInfo_NoRoyaltySet(uint256 tokenId, uint256 salePrice) external view { + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, address(0), "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_OnlyDefaultRoyalty(uint96 feeNumerator, uint256 salePrice) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + harness.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_OnlyTokenRoyalty( + uint256 tokenId, + uint96 feeNumerator, + uint256 salePrice + ) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + harness.setTokenRoyalty(tokenId, users.bob, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_DefaultAndTokenRoyalty_TokenOverridesDefault( + uint256 tokenId, + uint96 defaultFee, + uint96 tokenFee, + uint256 salePrice + ) external { + vm.assume(defaultFee <= FEE_DENOMINATOR); + vm.assume(tokenFee <= FEE_DENOMINATOR); + vm.assume(salePrice <= 1_000_000 ether); + + harness.setDefaultRoyalty(users.alice, defaultFee); + harness.setTokenRoyalty(tokenId, users.bob, tokenFee); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, salePrice); + + assertEq(receiver, users.bob, "receiver"); + assertEq(royaltyAmount, (salePrice * tokenFee) / FEE_DENOMINATOR, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_ZeroSalePrice_PreservesReceiver(uint96 feeNumerator, uint256 tokenId) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + harness.setDefaultRoyalty(users.receiver, feeNumerator); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(tokenId, 0); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } + + function testFuzz_RoyaltyInfo_ZeroFee_PreservesReceiver(uint256 salePrice) external { + vm.assume(salePrice <= 1_000_000 ether); + + harness.setDefaultRoyalty(users.receiver, 0); + + (address receiver, uint256 royaltyAmount) = harness.royaltyInfo(1, salePrice); + + assertEq(receiver, users.receiver, "receiver"); + assertEq(royaltyAmount, 0, "royaltyAmount"); + } +} + diff --git a/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol new file mode 100644 index 00000000..2e700ef5 --- /dev/null +++ b/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol"; + +import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; + +/** + * @dev BTT spec: test/trees/Royalty.tree + * + * TokenRoyalty + */ +contract TokenRoyalty_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { + /* SetTokenRoyalty */ + + function testFuzz_SetTokenRoyalty_SetsReceiverAndFraction( + uint256 tokenId, + address receiver, + uint96 feeNumerator + ) external { + vm.assume(receiver != ADDRESS_ZERO); + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + harness.setTokenRoyalty(tokenId, receiver, feeNumerator); + + assertEq(harness.getTokenRoyaltyReceiver(tokenId), receiver, "token receiver"); + assertEq(harness.getTokenRoyaltyFraction(tokenId), feeNumerator, "token fraction"); + } + + function test_SetTokenRoyalty_MultipleTokens_IndependentState() external { + harness.setTokenRoyalty(1, users.alice, 500); + harness.setTokenRoyalty(2, users.bob, 1_000); + harness.setTokenRoyalty(3, users.charlee, 250); + + assertEq(harness.getTokenRoyaltyReceiver(1), users.alice, "token1 receiver"); + assertEq(harness.getTokenRoyaltyFraction(1), 500, "token1 fraction"); + + assertEq(harness.getTokenRoyaltyReceiver(2), users.bob, "token2 receiver"); + assertEq(harness.getTokenRoyaltyFraction(2), 1_000, "token2 fraction"); + + assertEq(harness.getTokenRoyaltyReceiver(3), users.charlee, "token3 receiver"); + assertEq(harness.getTokenRoyaltyFraction(3), 250, "token3 fraction"); + } + + function test_SetTokenRoyalty_UpdatesExisting() external { + uint256 tokenId = 1; + + harness.setTokenRoyalty(tokenId, users.alice, 500); + harness.setTokenRoyalty(tokenId, users.bob, 1_000); + + assertEq(harness.getTokenRoyaltyReceiver(tokenId), users.bob, "token receiver"); + assertEq(harness.getTokenRoyaltyFraction(tokenId), 1_000, "token fraction"); + } + + function test_SetTokenRoyalty_ZeroFee_KeepsReceiver() external { + uint256 tokenId = 1; + + harness.setTokenRoyalty(tokenId, users.receiver, 0); + + assertEq(harness.getTokenRoyaltyReceiver(tokenId), users.receiver, "token receiver"); + assertEq(harness.getTokenRoyaltyFraction(tokenId), 0, "token fraction"); + } + + function test_RevertWhen_SetTokenRoyalty_FeeAboveDenominator() external { + uint256 tokenId = 1; + uint96 invalidFee = uint96(FEE_DENOMINATOR + 1); + + vm.expectRevert( + abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidTokenRoyalty.selector, tokenId, invalidFee, FEE_DENOMINATOR) + ); + harness.setTokenRoyalty(tokenId, users.receiver, invalidFee); + } + + function test_RevertWhen_SetTokenRoyalty_ZeroReceiver() external { + uint256 tokenId = 1; + + vm.expectRevert( + abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidTokenRoyaltyReceiver.selector, tokenId, ADDRESS_ZERO) + ); + harness.setTokenRoyalty(tokenId, ADDRESS_ZERO, 500); + } + + /* ResetTokenRoyalty */ + + function testFuzz_ResetTokenRoyalty_NoDefault_ClearsTokenRoyalty( + uint256 tokenId, + uint96 feeNumerator + ) external { + vm.assume(feeNumerator <= FEE_DENOMINATOR); + + harness.setTokenRoyalty(tokenId, users.receiver, feeNumerator); + harness.resetTokenRoyalty(tokenId); + + assertEq(harness.getTokenRoyaltyReceiver(tokenId), ADDRESS_ZERO, "token receiver"); + assertEq(harness.getTokenRoyaltyFraction(tokenId), 0, "token fraction"); + } + + function test_ResetTokenRoyalty_WithDefault_FallsBackToDefault() external { + uint256 tokenId = 1; + uint256 salePrice = 100 ether; + + harness.setDefaultRoyalty(users.alice, 500); + harness.setTokenRoyalty(tokenId, users.bob, 1_000); + + (address receiver1, uint256 royalty1) = harness.royaltyInfo(tokenId, salePrice); + assertEq(receiver1, users.bob, "receiver1"); + assertEq(royalty1, 10 ether, "royalty1"); + + harness.resetTokenRoyalty(tokenId); + + (address receiver2, uint256 royalty2) = harness.royaltyInfo(tokenId, salePrice); + assertEq(receiver2, users.alice, "receiver2"); + assertEq(royalty2, 5 ether, "royalty2"); + } + + function test_ResetTokenRoyalty_MultipleTokens_PartialReset() external { + harness.setTokenRoyalty(1, users.alice, 500); + harness.setTokenRoyalty(2, users.bob, 1_000); + harness.setTokenRoyalty(3, users.charlee, 250); + + harness.resetTokenRoyalty(1); + harness.resetTokenRoyalty(3); + + assertEq(harness.getTokenRoyaltyReceiver(1), ADDRESS_ZERO, "token1 receiver"); + assertEq(harness.getTokenRoyaltyFraction(1), 0, "token1 fraction"); + + assertEq(harness.getTokenRoyaltyReceiver(2), users.bob, "token2 receiver"); + assertEq(harness.getTokenRoyaltyFraction(2), 1_000, "token2 fraction"); + + assertEq(harness.getTokenRoyaltyReceiver(3), ADDRESS_ZERO, "token3 receiver"); + assertEq(harness.getTokenRoyaltyFraction(3), 0, "token3 fraction"); + } +} diff --git a/test/unit/token/Royalty/RoyaltyFacetBase.t.sol b/test/unit/token/Royalty/RoyaltyFacetBase.t.sol new file mode 100644 index 00000000..2453d5bd --- /dev/null +++ b/test/unit/token/Royalty/RoyaltyFacetBase.t.sol @@ -0,0 +1,19 @@ +// 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 {RoyaltyFacetHarness} from "test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol"; + +abstract contract RoyaltyFacet_Base_Test is Base_Test { + RoyaltyFacetHarness internal facet; + + function setUp() public virtual override { + Base_Test.setUp(); + facet = new RoyaltyFacetHarness(); + vm.label(address(facet), "RoyaltyFacetHarness"); + } +} diff --git a/test/unit/token/Royalty/RoyaltyModBase.t.sol b/test/unit/token/Royalty/RoyaltyModBase.t.sol new file mode 100644 index 00000000..6e657cff --- /dev/null +++ b/test/unit/token/Royalty/RoyaltyModBase.t.sol @@ -0,0 +1,19 @@ +// 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 {RoyaltyHarness} from "test/utils/harnesses/token/Royalty/RoyaltyHarness.sol"; + +abstract contract RoyaltyMod_Base_Test is Base_Test { + RoyaltyHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new RoyaltyHarness(); + vm.label(address(harness), "RoyaltyHarness"); + } +} diff --git a/test/token/Royalty/harnesses/RoyaltyFacetHarness.sol b/test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol similarity index 92% rename from test/token/Royalty/harnesses/RoyaltyFacetHarness.sol rename to test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol index 4968681e..7e0fc345 100644 --- a/test/token/Royalty/harnesses/RoyaltyFacetHarness.sol +++ b/test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import {RoyaltyFacet} from "../../../../src/token/Royalty/RoyaltyFacet.sol"; +import {RoyaltyFacet} from "src/token/Royalty/RoyaltyFacet.sol"; /** * @title RoyaltyFacetHarness @@ -31,3 +31,4 @@ contract RoyaltyFacetHarness is RoyaltyFacet { s.tokenRoyaltyInfo[_tokenId] = RoyaltyInfo(_receiver, _feeNumerator); } } + diff --git a/test/token/Royalty/harnesses/RoyaltyHarness.sol b/test/utils/harnesses/token/Royalty/RoyaltyHarness.sol similarity index 92% rename from test/token/Royalty/harnesses/RoyaltyHarness.sol rename to test/utils/harnesses/token/Royalty/RoyaltyHarness.sol index 715f3cbe..f79aa599 100644 --- a/test/token/Royalty/harnesses/RoyaltyHarness.sol +++ b/test/utils/harnesses/token/Royalty/RoyaltyHarness.sol @@ -5,12 +5,12 @@ pragma solidity >=0.8.30; * https://compose.diamonds */ -import "../../../../src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; +import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; /** * @title RoyaltyHarness - * @notice Test harness that exposes LibRoyalty's internal functions as external - * @dev Required for testing since LibRoyalty only has internal functions + * @notice Test harness that exposes RoyaltyMod's internal functions as external + * @dev Required for testing since RoyaltyMod only has internal functions */ contract RoyaltyHarness { /** @@ -80,3 +80,4 @@ contract RoyaltyHarness { return RoyaltyMod.getStorage().tokenRoyaltyInfo[_tokenId].royaltyFraction; } } + From b05fcd60a4269aa601247b70e7ca86458b03b833 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 18:01:59 -0400 Subject: [PATCH 19/25] format --- .../token/Royalty/Mod/fuzz/integration.t.sol | 2 +- test/unit/token/Royalty/Facet/fuzz/royalty.t.sol | 5 +++-- .../unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol | 6 +----- .../token/Royalty/Mod/fuzz/defaultRoyalty.t.sol | 4 +--- test/unit/token/Royalty/Mod/fuzz/royalty.t.sol | 5 +++-- test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol | 6 +----- test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol | 13 ++++--------- 7 files changed, 14 insertions(+), 27 deletions(-) diff --git a/test/integration/token/Royalty/Mod/fuzz/integration.t.sol b/test/integration/token/Royalty/Mod/fuzz/integration.t.sol index 60d0df56..cb22a560 100644 --- a/test/integration/token/Royalty/Mod/fuzz/integration.t.sol +++ b/test/integration/token/Royalty/Mod/fuzz/integration.t.sol @@ -61,7 +61,7 @@ contract Integration_RoyaltyMod_Integration_Test is RoyaltyMod_Base_Test { harness.deleteDefaultRoyalty(); - // token-specific royalties remain unchanged + /* token-specific royalties remain unchanged */ (receiver1, royalty1) = harness.royaltyInfo(token1, salePrice); (receiver2, royalty2) = harness.royaltyInfo(token2, salePrice); assertEq(receiver1, users.bob, "receiver1 after delete"); diff --git a/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol b/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol index 5d4038db..f7ee98d0 100644 --- a/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol +++ b/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol @@ -8,6 +8,7 @@ pragma solidity >=0.8.30; import {Test} from "forge-std/Test.sol"; import {RoyaltyFacetHarness} from "test/utils/harnesses/token/Royalty/RoyaltyFacetHarness.sol"; -// Intentionally left empty; Royalty facet BTT tests are split -// into dedicated suites per Royalty.tree section. +/* Intentionally left empty; Royalty facet BTT tests are split + * into dedicated suites per Royalty.tree section. + */ diff --git a/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol index 3a422e6b..6b8b16f9 100644 --- a/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol +++ b/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol @@ -32,11 +32,7 @@ contract RoyaltyInfo_RoyaltyFacet_Fuzz_Unit_Test is RoyaltyFacet_Base_Test { assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); } - function testFuzz_RoyaltyInfo_OnlyTokenRoyalty( - uint256 tokenId, - uint96 feeNumerator, - uint256 salePrice - ) external { + function testFuzz_RoyaltyInfo_OnlyTokenRoyalty(uint256 tokenId, uint96 feeNumerator, uint256 salePrice) external { vm.assume(feeNumerator <= FEE_DENOMINATOR); vm.assume(salePrice <= 1_000_000 ether); diff --git a/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol index cc1669ac..c845d451 100644 --- a/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol +++ b/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol @@ -52,9 +52,7 @@ contract DefaultRoyalty_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { } function test_RevertWhen_SetDefaultRoyalty_ZeroReceiver() external { - vm.expectRevert( - abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyaltyReceiver.selector, ADDRESS_ZERO) - ); + vm.expectRevert(abi.encodeWithSelector(RoyaltyMod.ERC2981InvalidDefaultRoyaltyReceiver.selector, ADDRESS_ZERO)); harness.setDefaultRoyalty(ADDRESS_ZERO, 500); } diff --git a/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol index f14bd146..92590fa8 100644 --- a/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol +++ b/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol @@ -9,6 +9,7 @@ import {Test} from "forge-std/Test.sol"; import {RoyaltyHarness} from "test/utils/harnesses/token/Royalty/RoyaltyHarness.sol"; import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; -// Intentionally left empty; Royalty mod BTT tests are split -// into dedicated suites per Royalty.tree section. +/* Intentionally left empty; Royalty mod BTT tests are split + * into dedicated suites per Royalty.tree section. + */ diff --git a/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol index d0b253ae..f96275f9 100644 --- a/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol +++ b/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol @@ -34,11 +34,7 @@ contract RoyaltyInfo_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { assertEq(royaltyAmount, (salePrice * feeNumerator) / FEE_DENOMINATOR, "royaltyAmount"); } - function testFuzz_RoyaltyInfo_OnlyTokenRoyalty( - uint256 tokenId, - uint96 feeNumerator, - uint256 salePrice - ) external { + function testFuzz_RoyaltyInfo_OnlyTokenRoyalty(uint256 tokenId, uint96 feeNumerator, uint256 salePrice) external { vm.assume(feeNumerator <= FEE_DENOMINATOR); vm.assume(salePrice <= 1_000_000 ether); diff --git a/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol b/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol index 2e700ef5..a8128c18 100644 --- a/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol +++ b/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol @@ -17,11 +17,9 @@ import "src/token/Royalty/RoyaltyMod.sol" as RoyaltyMod; contract TokenRoyalty_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { /* SetTokenRoyalty */ - function testFuzz_SetTokenRoyalty_SetsReceiverAndFraction( - uint256 tokenId, - address receiver, - uint96 feeNumerator - ) external { + function testFuzz_SetTokenRoyalty_SetsReceiverAndFraction(uint256 tokenId, address receiver, uint96 feeNumerator) + external + { vm.assume(receiver != ADDRESS_ZERO); vm.assume(feeNumerator <= FEE_DENOMINATOR); @@ -86,10 +84,7 @@ contract TokenRoyalty_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { /* ResetTokenRoyalty */ - function testFuzz_ResetTokenRoyalty_NoDefault_ClearsTokenRoyalty( - uint256 tokenId, - uint96 feeNumerator - ) external { + function testFuzz_ResetTokenRoyalty_NoDefault_ClearsTokenRoyalty(uint256 tokenId, uint96 feeNumerator) external { vm.assume(feeNumerator <= FEE_DENOMINATOR); harness.setTokenRoyalty(tokenId, users.receiver, feeNumerator); From f8db23144132467242921021de690d0d04f9827b Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 18:28:45 -0400 Subject: [PATCH 20/25] add access tests --- test/trees/AccessControl.tree | 2 ++ test/trees/Owner.tree | 8 +++-- .../Data/AccessControlDataBase.t.sol | 12 +++++++ .../AccessControl/Data/facet/fuzz/data.t.sol | 9 +++++ .../AccessControl/Data/mod/fuzz/data.t.sol | 9 +++++ .../Pausable/facet/fuzz/pausable.t.sol | 31 +++++++++++++++++ .../Pausable/mod/fuzz/pausable.t.sol | 31 +++++++++++++++++ .../Temporal/Data/facet/fuzz/data.t.sol | 15 +++++++++ .../Temporal/Data/mod/fuzz/data.t.sol | 13 ++++++++ .../facet/fuzz/transferOwnership.t.sol | 33 +++++++++++++++++++ .../Transfer/mod/fuzz/transferOwnership.t.sol | 33 +++++++++++++++++++ 11 files changed, 194 insertions(+), 2 deletions(-) diff --git a/test/trees/AccessControl.tree b/test/trees/AccessControl.tree index 877086f2..0dddc266 100644 --- a/test/trees/AccessControl.tree +++ b/test/trees/AccessControl.tree @@ -4,6 +4,8 @@ Data β”‚ β”‚ └── it should return true β”‚ β”œβ”€β”€ when the account has not been granted the role β”‚ β”‚ └── it should return false +β”‚ β”œβ”€β”€ when the role has never been granted to any account +β”‚ β”‚ └── it should return false β”‚ └── when the account is the zero address β”‚ └── it should return false └── RequireRole diff --git a/test/trees/Owner.tree b/test/trees/Owner.tree index 1c9218be..5a4dab64 100644 --- a/test/trees/Owner.tree +++ b/test/trees/Owner.tree @@ -42,7 +42,9 @@ TwoSteps β”‚ β”‚ β”œβ”€β”€ when the caller is the owner β”‚ β”‚ β”‚ β”œβ”€β”€ it should set pendingOwner to the new address β”‚ β”‚ β”‚ β”œβ”€β”€ it should emit an {OwnershipTransferStarted} event -β”‚ β”‚ β”‚ └── it should leave the current owner unchanged until acceptance +β”‚ β”‚ β”‚ β”œβ”€β”€ it should leave the current owner unchanged until acceptance +β”‚ β”‚ β”‚ └── when a pending transfer already exists +β”‚ β”‚ β”‚ └── it should overwrite pendingOwner and emit {OwnershipTransferStarted} β”‚ β”‚ └── when the caller is not the owner β”‚ β”‚ └── it should revert with {OwnerUnauthorizedAccount} β”‚ └── AcceptOwnership @@ -50,7 +52,9 @@ TwoSteps β”‚ β”‚ β”œβ”€β”€ it should set the owner to the pending owner β”‚ β”‚ β”œβ”€β”€ it should clear the pending owner to zero β”‚ β”‚ └── it should emit an {OwnershipTransferred} event -β”‚ └── when the caller is not the pending owner +β”‚ β”œβ”€β”€ when the caller is not the pending owner +β”‚ β”‚ └── it should revert with {OwnerUnauthorizedAccount} +β”‚ └── when no transfer is pending β”‚ └── it should revert with {OwnerUnauthorizedAccount} └── Renounce └── RenounceOwnership diff --git a/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol b/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol index 5fa2f457..3210a20c 100644 --- a/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol +++ b/test/unit/access/AccessControl/Data/AccessControlDataBase.t.sol @@ -4,14 +4,26 @@ pragma solidity >=0.8.30 <0.9.0; import {Base_Test} from "test/Base.t.sol"; import {AccessControlStorageUtils} from "test/utils/storage/AccessControlStorageUtils.sol"; +interface IAccessControlDataView { + function hasRole(bytes32 role, address account) external view returns (bool); +} + abstract contract AccessControlData_Base_Test is Base_Test { using AccessControlStorageUtils for address; + function _getDataTarget() internal virtual returns (address); + function setUp() public virtual override { Base_Test.setUp(); vm.stopPrank(); } + function test_HasRole_ReturnsFalseForUnknownRole() public { + address target = _getDataTarget(); + bytes32 unknownRole = bytes32(uint256(1)); + assertEq(IAccessControlDataView(target).hasRole(unknownRole, users.alice), false, "hasRole"); + } + function seedDefaultAdmin(address target) internal { target.setHasRole(users.admin, DEFAULT_ADMIN_ROLE, true); } diff --git a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol index eb8d7c99..c4be926b 100644 --- a/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Data/facet/fuzz/data.t.sol @@ -24,6 +24,10 @@ contract Data_AccessControlDataFacet_Fuzz_Unit_Test is AccessControlData_Base_Te seedDefaultAdmin(address(facet)); } + function _getDataTarget() internal view override returns (address) { + return address(facet); + } + function testFuzz_ShouldReturnTrue_HasRole_WhenAccountHasRole(address account, bytes32 role) external { address(facet).setHasRole(account, role, true); @@ -50,6 +54,11 @@ contract Data_AccessControlDataFacet_Fuzz_Unit_Test is AccessControlData_Base_Te facet.requireRole(role, account); } + function test_GetRoleAdmin_ReturnsDefaultAdminForNewRole() external view { + bytes32 newRole = bytes32(uint256(1)); + assertEq(facet.getRoleAdmin(newRole), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); + } + function testFuzz_ShouldReturnDefaultAdminRole_GetRoleAdmin_WhenAdminNotSet(bytes32 role) external view { assertEq(facet.getRoleAdmin(role), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); } diff --git a/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol index 097deee3..7a27340c 100644 --- a/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Data/mod/fuzz/data.t.sol @@ -24,6 +24,10 @@ contract Data_AccessControlMod_Fuzz_Unit_Test is AccessControlData_Base_Test { seedDefaultAdmin(address(harness)); } + function _getDataTarget() internal view override returns (address) { + return address(harness); + } + function testFuzz_ShouldReturnTrue_HasRole_WhenAccountHasRole(address account, bytes32 role) external { address(harness).setHasRole(account, role, true); @@ -48,6 +52,11 @@ contract Data_AccessControlMod_Fuzz_Unit_Test is AccessControlData_Base_Test { harness.requireRole(role, account); } + function test_GetRoleAdmin_ReturnsDefaultAdminForNewRole() external view { + bytes32 newRole = bytes32(uint256(1)); + assertEq(address(harness).adminRole(newRole), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); + } + function testFuzz_ShouldReturnDefaultAdminRole_GetRoleAdmin_WhenAdminNotSet(bytes32 role) external view { assertEq(address(harness).adminRole(role), DEFAULT_ADMIN_ROLE, "getRoleAdmin"); } diff --git a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol index 8efe92b2..bdbd75e4 100644 --- a/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/facet/fuzz/pausable.t.sol @@ -108,4 +108,35 @@ contract Pausable_AccessControlPausableFacet_Fuzz_Unit_Test is AccessControlPaus vm.expectRevert(abi.encodeWithSelector(AccessControlPausableFacet.AccessControlRolePaused.selector, role)); facet.requireRoleNotPaused(role, account); } + + function testFuzz_ShouldEndUnpaused_WhenMultiplePauseUnpauseCycles(bytes32 role) external { + vm.startPrank(users.admin); + facet.pauseRole(role); + facet.unpauseRole(role); + facet.pauseRole(role); + facet.unpauseRole(role); + vm.stopPrank(); + + assertEq(facet.isRolePaused(role), false, "role unpaused after cycles"); + } + + function testFuzz_ShouldRemainPaused_PauseRole_WhenCalledTwice(bytes32 role) external { + vm.prank(users.admin); + facet.pauseRole(role); + vm.prank(users.admin); + facet.pauseRole(role); + + assertEq(facet.isRolePaused(role), true, "role paused"); + } + + function testFuzz_ShouldRemainUnpaused_UnpauseRole_WhenCalledTwice(bytes32 role) external { + seedPausedRole(address(facet), role, true); + + vm.prank(users.admin); + facet.unpauseRole(role); + vm.prank(users.admin); + facet.unpauseRole(role); + + assertEq(facet.isRolePaused(role), false, "role unpaused"); + } } diff --git a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol index b95e9d3b..f8d484d2 100644 --- a/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol +++ b/test/unit/access/AccessControl/Pausable/mod/fuzz/pausable.t.sol @@ -83,4 +83,35 @@ contract Pausable_AccessControlPausableMod_Fuzz_Unit_Test is AccessControlPausab vm.expectRevert(abi.encodeWithSignature("AccessControlRolePaused(bytes32)", role)); harness.requireRoleNotPaused(role, account); } + + function testFuzz_ShouldEndUnpaused_WhenMultiplePauseUnpauseCycles(bytes32 role) external { + vm.startPrank(users.admin); + harness.pauseRole(role); + harness.unpauseRole(role); + harness.pauseRole(role); + harness.unpauseRole(role); + vm.stopPrank(); + + assertEq(harness.isRolePaused(role), false, "role unpaused after cycles"); + } + + function testFuzz_ShouldRemainPaused_PauseRole_WhenCalledTwice(bytes32 role) external { + vm.prank(users.admin); + harness.pauseRole(role); + vm.prank(users.admin); + harness.pauseRole(role); + + assertEq(harness.isRolePaused(role), true, "role paused"); + } + + function testFuzz_ShouldRemainUnpaused_UnpauseRole_WhenCalledTwice(bytes32 role) external { + seedPausedRole(address(harness), role, true); + + vm.prank(users.admin); + harness.unpauseRole(role); + vm.prank(users.admin); + harness.unpauseRole(role); + + assertEq(harness.isRolePaused(role), false, "role unpaused"); + } } diff --git a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol index c922a73a..2802500e 100644 --- a/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/facet/fuzz/data.t.sol @@ -24,6 +24,21 @@ contract Data_AccessControlTemporalDataFacet_Fuzz_Unit_Test is AccessControlTemp vm.label(address(facet), "AccessControlTemporalDataFacet"); } + function test_TemporalRole_IsActive_JustAfterExpiryIsFalse() public { + bytes32 role = bytes32(uint256(1)); + address account = users.alice; + uint256 expiry = block.timestamp + 1; + seedRole(address(facet), role, account); + seedRoleExpiry(address(facet), role, account, expiry); + + vm.warp(expiry + 1); + assertTrue(facet.isRoleExpired(role, account), "isRoleExpired"); + vm.expectRevert( + abi.encodeWithSelector(AccessControlTemporalDataFacet.AccessControlRoleExpired.selector, role, account) + ); + facet.requireValidRole(role, account); + } + function testFuzz_ShouldReturnZero_GetRoleExpiry_WhenExpiryNotSet(bytes32 role, address account) external view { assertEq(facet.getRoleExpiry(role, account), 0, "getRoleExpiry"); } diff --git a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol index 9e61c438..be643c38 100644 --- a/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol +++ b/test/unit/access/AccessControl/Temporal/Data/mod/fuzz/data.t.sol @@ -24,6 +24,19 @@ contract Data_AccessControlTemporalDataMod_Fuzz_Unit_Test is AccessControlTempor vm.label(address(harness), "AccessControlTemporalModHarness"); } + function test_TemporalRole_IsActive_JustAfterExpiryIsFalse() public { + bytes32 role = bytes32(uint256(1)); + address account = users.alice; + uint256 expiry = block.timestamp + 1; + seedRole(address(harness), role, account); + seedRoleExpiry(address(harness), role, account, expiry); + + vm.warp(expiry + 1); + assertTrue(harness.isRoleExpired(role, account), "isRoleExpired"); + vm.expectRevert(abi.encodeWithSignature("AccessControlRoleExpired(bytes32,address)", role, account)); + harness.requireValidRole(role, account); + } + function testFuzz_ShouldReturnZero_GetRoleExpiry_WhenExpiryNotSet(bytes32 role, address account) external view { assertEq(harness.getRoleExpiry(role, account), 0, "getRoleExpiry"); } diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol index 82470d44..c5df48a2 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol @@ -91,4 +91,37 @@ contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoS vm.expectRevert(OwnerTwoStepTransferFacet.OwnerUnauthorizedAccount.selector); facet.acceptOwnership(); } + + function testFuzz_ShouldRevert_AcceptOwnership_WhenNoPendingTransfer(address caller) external { + vm.assume(caller != address(0)); + seedOwner(address(facet), users.admin); + /* no pending owner set */ + + vm.prank(caller); + vm.expectRevert(OwnerTwoStepTransferFacet.OwnerUnauthorizedAccount.selector); + facet.acceptOwnership(); + } + + function testFuzz_ShouldOverwritePendingOwner_TransferOwnership_WhenCalledAgainByOwner( + address firstPending, + address secondPending + ) + external + { + vm.assume(firstPending != address(0)); + vm.assume(secondPending != address(0)); + vm.assume(secondPending != firstPending); + + vm.prank(users.admin); + facet.transferOwnership(firstPending); + assertEq(OwnerStorageUtils.pendingOwner(address(facet)), firstPending, "pending after first"); + + vm.expectEmit(address(facet)); + emit OwnershipTransferStarted(users.admin, secondPending); + vm.prank(users.admin); + facet.transferOwnership(secondPending); + + assertEq(OwnerStorageUtils.pendingOwner(address(facet)), secondPending, "pending overwritten"); + assertEq(OwnerStorageUtils.owner(address(facet)), users.admin, "owner unchanged"); + } } diff --git a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol index 2013232a..caa9de3c 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol @@ -91,4 +91,37 @@ contract TransferOwnership_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepTransfe vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); harness.acceptOwnership(); } + + function testFuzz_ShouldRevert_AcceptOwnership_WhenNoPendingTransfer(address caller) external { + vm.assume(caller != address(0)); + seedOwner(address(harness), users.admin); + /* no pending owner set */ + + vm.prank(caller); + vm.expectRevert(abi.encodeWithSignature("OwnerUnauthorizedAccount()")); + harness.acceptOwnership(); + } + + function testFuzz_ShouldOverwritePendingOwner_TransferOwnership_WhenCalledAgainByOwner( + address firstPending, + address secondPending + ) + external + { + vm.assume(firstPending != address(0)); + vm.assume(secondPending != address(0)); + vm.assume(secondPending != firstPending); + + vm.prank(users.admin); + harness.transferOwnership(firstPending); + assertEq(OwnerStorageUtils.pendingOwner(address(harness)), firstPending, "pending after first"); + + vm.expectEmit(address(harness)); + emit OwnershipTransferStarted(users.admin, secondPending); + vm.prank(users.admin); + harness.transferOwnership(secondPending); + + assertEq(OwnerStorageUtils.pendingOwner(address(harness)), secondPending, "pending overwritten"); + assertEq(harness.owner(), users.admin, "owner unchanged"); + } } From cdabc2e0258bb909e524743e77c336f48bd2fd5d Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 18:29:22 -0400 Subject: [PATCH 21/25] format --- .../TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol | 4 +--- .../Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol index c5df48a2..12681568 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/facet/fuzz/transferOwnership.t.sol @@ -105,9 +105,7 @@ contract TransferOwnership_OwnerTwoStepTransferFacet_Fuzz_Unit_Test is OwnerTwoS function testFuzz_ShouldOverwritePendingOwner_TransferOwnership_WhenCalledAgainByOwner( address firstPending, address secondPending - ) - external - { + ) external { vm.assume(firstPending != address(0)); vm.assume(secondPending != address(0)); vm.assume(secondPending != firstPending); diff --git a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol index caa9de3c..9014099c 100644 --- a/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol +++ b/test/unit/access/Owner/TwoSteps/Transfer/mod/fuzz/transferOwnership.t.sol @@ -105,9 +105,7 @@ contract TransferOwnership_OwnerTwoStepMod_Fuzz_Unit_Test is OwnerTwoStepTransfe function testFuzz_ShouldOverwritePendingOwner_TransferOwnership_WhenCalledAgainByOwner( address firstPending, address secondPending - ) - external - { + ) external { vm.assume(firstPending != address(0)); vm.assume(secondPending != address(0)); vm.assume(secondPending != firstPending); From 7772252e1b651947b7f8d3ad104f2047eccb36d3 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Tue, 10 Mar 2026 19:51:41 -0400 Subject: [PATCH 22/25] update tree --- test/trees/AccessControl.tree | 25 ++++++++++++++++--------- test/trees/ERC721.tree | 26 +++++++++++++++++++++----- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/test/trees/AccessControl.tree b/test/trees/AccessControl.tree index 0dddc266..f253cbee 100644 --- a/test/trees/AccessControl.tree +++ b/test/trees/AccessControl.tree @@ -112,24 +112,31 @@ Pausable β”œβ”€β”€ PauseRole β”‚ β”œβ”€β”€ when the caller has the admin role for the target role β”‚ β”‚ β”œβ”€β”€ it should set pausedRoles[role] to true -β”‚ β”‚ └── it should emit a {RolePaused} event +β”‚ β”‚ β”œβ”€β”€ it should emit a {RolePaused} event +β”‚ β”‚ └── when the role is already paused and PauseRole is called again +β”‚ β”‚ └── it should remain paused (idempotent) β”‚ └── when the caller does not have the admin role for the target role β”‚ β”œβ”€β”€ on the facet surface it should revert with {AccessControlUnauthorizedAccount} β”‚ └── on the module surface it should follow the current split implementation semantics β”œβ”€β”€ UnpauseRole β”‚ β”œβ”€β”€ when the caller has the admin role for the target role β”‚ β”‚ β”œβ”€β”€ it should set pausedRoles[role] to false -β”‚ β”‚ └── it should emit a {RoleUnpaused} event +β”‚ β”‚ β”œβ”€β”€ it should emit a {RoleUnpaused} event +β”‚ β”‚ └── when the role is already unpaused and UnpauseRole is called again +β”‚ β”‚ └── it should remain unpaused (idempotent) β”‚ └── when the caller does not have the admin role for the target role β”‚ β”œβ”€β”€ on the facet surface it should revert with {AccessControlUnauthorizedAccount} β”‚ └── on the module surface it should follow the current split implementation semantics -└── RequireRoleNotPaused - β”œβ”€β”€ when the account does not have the role - β”‚ └── it should revert with {AccessControlUnauthorizedAccount} - β”œβ”€β”€ when the account has the role and the role is paused - β”‚ └── it should revert with {AccessControlRolePaused} - └── when the account has the role and the role is not paused - └── it should not revert +β”œβ”€β”€ RequireRoleNotPaused +β”‚ β”œβ”€β”€ when the account does not have the role +β”‚ β”‚ └── it should revert with {AccessControlUnauthorizedAccount} +β”‚ β”œβ”€β”€ when the account has the role and the role is paused +β”‚ β”‚ └── it should revert with {AccessControlRolePaused} +β”‚ └── when the account has the role and the role is not paused +β”‚ └── it should not revert +└── PauseUnpauseCycles + └── when multiple pause and unpause cycles are performed for the same role + └── it should end in the unpaused state when last action is unpause Temporal β”œβ”€β”€ Data diff --git a/test/trees/ERC721.tree b/test/trees/ERC721.tree index 0976ff4c..fe8df3f0 100644 --- a/test/trees/ERC721.tree +++ b/test/trees/ERC721.tree @@ -100,11 +100,27 @@ Enumerable β”‚ β”‚ └── it should revert with ERC721OutOfBoundsIndex or equivalent β”‚ └── when index is within bounds β”‚ └── it should return the tokenId at that index for the owner -└── TokenByIndex - β”œβ”€β”€ when index is out of bounds - β”‚ └── it should revert with ERC721OutOfBoundsIndex or equivalent - └── when index is within bounds - └── it should return the tokenId at that index in the global list +β”œβ”€β”€ TokenByIndex +β”‚ β”œβ”€β”€ when index is out of bounds +β”‚ β”‚ └── it should revert with ERC721OutOfBoundsIndex or equivalent +β”‚ └── when index is within bounds +β”‚ └── it should return the tokenId at that index in the global list +β”œβ”€β”€ TransferFrom +β”‚ β”œβ”€β”€ when transfer occurs (owner, approved, or operator) +β”‚ β”‚ └── it should update enumeration (owner's token list and global list) +β”‚ β”œβ”€β”€ when to is zero address +β”‚ β”‚ └── it should revert with ERC721InvalidReceiver +β”‚ β”œβ”€β”€ when token does not exist +β”‚ β”‚ └── it should revert with ERC721NonexistentToken +β”‚ └── when to is a contract +β”‚ └── it should call onERC721Received and respect return value or revert +└── Burn + β”œβ”€β”€ when token does not exist + β”‚ └── it should revert with ERC721NonexistentToken + β”œβ”€β”€ when caller is not owner, not approved, and not operator + β”‚ └── it should revert with ERC721InsufficientApproval or equivalent + └── when caller is owner, approved, or operator + └── it should update enumeration (remove from owner list and global list) and burn Metadata β”œβ”€β”€ Name From 38273508e5478d9222c291fa2685491a8de850c0 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Thu, 12 Mar 2026 12:12:10 -0400 Subject: [PATCH 23/25] update tree, update readme --- test/README.md | 362 +++++++++++++++------------------- test/trees/AccessControl.tree | 3 + test/trees/ERC1155.tree | 4 + test/trees/ERC165.tree | 6 +- test/trees/ERC20.tree | 6 +- test/trees/ERC721.tree | 4 + test/trees/Owner.tree | 4 + 7 files changed, 181 insertions(+), 208 deletions(-) diff --git a/test/README.md b/test/README.md index 3fcfa5ae..3d5a666b 100644 --- a/test/README.md +++ b/test/README.md @@ -1,272 +1,222 @@ # Compose Test Suite -This directory contains comprehensive tests for the Compose smart contract library. +This directory contains the tests for the Compose library. The goal of this document is to give contributors a reference for how tests are organised, how we specify behaviour, and how to add or extend tests safely. ## Overview -Compose follows strict design principles that ban certain Solidity features (inheritance, modifiers, public/private storage, external library functions, etc.). These constraints require specialized testing patterns to ensure code quality without violating the project's architectural rules. +- **Behaviour first (BTT)**: tests are driven from behaviour trees in `test/trees/*.tree`, not from implementation details. +- **Facet/mod oriented**: each facet or modifier module has a focused set of base fixtures and fuzz tests. +- **No test‑only code in `src`**: all test helpers live under `test/` (base contracts, defaults, storage utils). +- **Design‑constrained testing**: Compose bans some common Solidity patterns (e.g. inheritance‑heavy hierarchies, test‑only hooks in production code), so the suite relies on behaviour trees, shared fixtures, and storage utilities to keep `src/` clean while still achieving thorough coverage. +- **Consistent naming**: similar behaviours across standards (ERC‑20, ERC‑721, AccessControl, etc.) share naming and structure. -## Testing Architecture +--- -### The Challenge +## Layout -Compose's design constraints create unique testing challenges: - -1. **No external functions in libraries** - Libraries like `LibERC20` only expose `internal` functions, which cannot be called directly from tests -2. **No initialization functions** - Facets like `ERC20Facet` have no built-in way to initialize storage (name, symbol, decimals) -3. **No constructors in facets** - Only diamond contracts can have constructors - -### The Solution: Test Harnesses - -Test harnesses are wrapper contracts that make production code testable without modifying it. This is a standard pattern used by OpenZeppelin and other production-grade smart contract projects. - -## Directory Structure - -``` +```text test/ -β”œβ”€β”€ README.md (this file) -β”‚ -β”œβ”€β”€ ERC20/ -β”‚ β”œβ”€β”€ ERC20Facet.t.sol # Tests for ERC20Facet (44 tests) -β”‚ β”œβ”€β”€ LibERC20.t.sol # Tests for LibERC20 library (34 tests) -β”‚ β”‚ -β”‚ └── harnesses/ -β”‚ β”œβ”€β”€ ERC20FacetHarness.sol # Test harness for ERC20Facet -β”‚ └── LibERC20Harness.sol # Test harness for LibERC20 +β”œβ”€β”€ README.md # this file +β”œβ”€β”€ trees/ # behaviour trees (specs) +β”‚ β”œβ”€β”€ ERC20.tree +β”‚ β”œβ”€β”€ ERC721.tree +β”‚ β”œβ”€β”€ ERC1155.tree +β”‚ β”œβ”€β”€ ERC165.tree +β”‚ β”œβ”€β”€ AccessControl.tree +β”‚ β”œβ”€β”€ Owner.tree +β”‚ β”œβ”€β”€ Royalty.tree +β”‚ └── NonReentrancy.tree +└── unit/ # unit tests by domain + β”œβ”€β”€ token/ # ERC-20/721/1155 + royalty facets/mods + β”œβ”€β”€ access/ # Owner, AccessControl facets/mods + └── interfaceDetection/ # ERC165 facets/mods ``` -## Test Harnesses Explained +Within `unit/` you will generally see: -### ERC20FacetHarness +- **`*FacetBase.t.sol` / `*ModBase.t.sol`**: base fixtures for a facet or modifier (deploys contract, common setup). +- **`facet/fuzz/*.t.sol`**: fuzz tests that exercise a facet’s external API. +- **`mod/fuzz/*.t.sol`**: fuzz tests that exercise modifier‑style modules. -**Purpose:** Extends `ERC20Facet` with test-only utilities +All tests share a small set of common utilities, described next. -**Why it's needed:** +--- -- `ERC20Facet` has no way to initialize storage (set token name, symbol, decimals) -- In production, diamonds handle initialization via constructors or init facets -- For testing, we need a way to set up initial state +## Common test utilities -**What it adds:** +- **`Base_Test`** (`test/Base.t.sol`): + - Inherits `forge-std` utilities and internal helpers. + - Creates labelled test users (`Users`) and funds them. + - Instantiates `Defaults` and wires users into it. + - Exposes helpers like `setMsgSender` (via `Utils`) to manage `msg.sender` during tests. -```solidity -function initialize(string memory _name, string memory _symbol, uint8 _decimals) -function mint(address _to, uint256 _value) -``` +- **`Users`** (`test/utils/Types.sol`): + - Typed bundle of common accounts: `alice`, `bob`, `charlee`, `admin`, `receiver`, `sender`. -**Usage in tests:** +- **`Constants`** (`test/utils/Constants.sol`): + - Shared values (interface IDs, common roles, token metadata, URIs, fee denominators, etc.). -```solidity -ERC20FacetHarness token = new ERC20FacetHarness(); -token.initialize("Test Token", "TEST", 18); -token.mint(alice, 1000e18); -// Now test transfer, approve, etc. -``` +- **`Defaults`** (`test/utils/Defaults.sol`): + - Holds default configuration for tests; receives `Users` from `Base_Test` and can be extended with more defaults over time. -### LibERC20Harness +- **`Modifiers`** (`test/utils/Modifiers.sol`): + - Houses β€œgiven/when” style modifiers that mirror the behaviour tree wording. + - `Base_Test` wires `Defaults` and `Users` into this layer so tests can depend on shared state. -**Purpose:** Exposes `LibERC20`'s internal functions as external for testing +- **Harnesses** (`test/utils/harnesses/**`): + - Test-only contracts used to expose internal hooks or lifecycle entry points needed to exercise behaviour trees (e.g. ERC165, NonReentrancy). + - Keep test-only logic out of `src/` while still allowing precise control in tests. -**Why it's needed:** +- **Storage utilities** (e.g. `test/utils/storage/AccessControlStorageUtils.sol`): + - Libraries that use `vm.load` / `vm.store` to directly inspect and manipulate layout‑sensitive storage in tests (e.g. split AccessControl storage). + - Allow verifying low‑level invariants without adding test‑only code to `src`. -- `LibERC20` only has `internal` functions (per Compose's rules) -- Internal functions cannot be called from external test contracts -- We need a way to test library functionality in isolation +Most unit tests import `Base_Test` and then add only the minimal extra setup they need, while a few low-level base tests (such as ERC165) extend `forge-std/Test` directly but follow the same patterns. -**What it does:** +--- -```solidity -// Wraps each internal library function: -function mint(address _account, uint256 _value) external { - LibERC20.mint(_account, _value); // Calls internal function -} -``` +## Behaviour trees (`test/trees/*.tree`) -**Usage in tests:** +Each `.tree` file is a **behaviour tree specification** for a standard or module. For example, `test/trees/ERC20.tree` contains behaviours such as: -```solidity -LibERC20Harness harness = new LibERC20Harness(); -harness.mint(alice, 1000e18); -// Test library behavior +```text +Transfer +└── when the receiver is not the zero address + └── given when the sender's balance is greater than, or equal to, the transfer amount + └── when the amount is > 0 + β”œβ”€β”€ it should decrement the sender's balance by the transfer amount + β”œβ”€β”€ it should increment the receiver's balance by the transfer amount + β”œβ”€β”€ it should emit a {Transfer} event + └── it should return true ``` -## Running Tests +These trees drive test design: -### Run all tests +- **Each β€œit should …” leaf maps to one or more test functions** in the relevant facet or mod fuzz file. +- **Intermediate β€œgiven/when …” nodes** often map to shared modifiers or setup helpers. +- Tests should be easy to trace back to the tree: + - Files usually carry a short note like `@dev BTT spec: test/trees/ERC20.tree`. + - Function names and test groupings follow the wording of the tree as closely as practical. -```bash -forge test -``` - -### Run ERC20 tests only +When you add new behaviour, update the corresponding `.tree` file first, then add or extend tests to cover the new leaves. -```bash -forge test --match-path "test/ERC20/*.t.sol" -``` +--- -### Run with verbose output +## Writing tests for a facet or module -```bash -forge test --match-path "test/ERC20/*.t.sol" -vv -``` +The typical pattern for a new facet test looks like: -### Run specific test file +1. **Extend a domain-specific `*Base_Test` and override `setUp` there**: + - Each facet or mod usually has a base test (e.g. `ERC20TransferFacet_Base_Test`, `AccessControlAdmin_Base_Test`) that itself extends `Base_Test`. + - In that base, call `Base_Test.setUp()` (or `super.setUp()` if there are multiple layers) first. + - Deploy the facet or module under test in the base and label it with `vm.label` for clear traces and logs. +2. **Align with the behaviour tree**: + - Identify which `.tree` file the facet belongs to (e.g. `ERC20.tree`, `AccessControl.tree`). + - Group tests by the same high‑level branches (e.g. `Transfer`, `Approve`, `ExportSelectors`). +3. **Write fuzz tests in the appropriate folder**: + - Use `facet/fuzz/*.t.sol` for facet APIs. + - Use `mod/fuzz/*.t.sol` for modifier‑style modules. -```bash -forge test --match-path "test/ERC20/ERC20Facet.t.sol" -``` +As an example, a simple unit test following this pattern is (taken from the AccessControl admin facet and its base test): -### Run specific test function - -```bash -forge test --match-test "test_Transfer" -``` - -### Generate gas report - -```bash -forge test --gas-report +```solidity +/** + * @dev BTT spec: test/trees/AccessControl.tree + */ +contract ExportSelectors_AccessControlAdminFacet_Unit_Test is AccessControlAdmin_Base_Test { + using AccessControlStorageUtils for address; + + AccessControlAdminFacet internal facet; + + function setUp() public override { + super.setUp(); + facet = new AccessControlAdminFacet(); + vm.label(address(facet), "AccessControlAdminFacet"); + seedDefaultAdmin(address(facet)); + } + + function test_ShouldReturnSelectors_ExportSelectors() external view { + bytes memory selectors = facet.exportSelectors(); + bytes memory expected = abi.encodePacked(AccessControlAdminFacet.setRoleAdmin.selector); + assertEq(selectors, expected, "exportSelectors"); + } +} ``` -## Writing New Tests - -### For New Facets - -1. Create a test harness if needed: - - ```solidity - contract MyFacetHarness is MyFacet { - function initialize(...) external { /* setup storage */ } - function testHelper(...) external { /* test utilities */ } - } - ``` - -2. Create test file: - - ```solidity - import {Test} from "forge-std/Test.sol"; - import {MyFacet} from "../../src/MyFacet.sol"; - import {MyFacetHarness} from "./harnesses/MyFacetHarness.sol"; - - contract MyFacetTest is Test { - MyFacetHarness public facet; +This contract: - function setUp() public { - facet = new MyFacetHarness(); - facet.initialize(...); - } +- Reuses the common user/defaults setup from `Base_Test`. +- Targets one leaf in the `AccessControl.tree` (the `ExportSelectors` behaviour). +- Uses clear, behaviour‑oriented naming for the test function. - function test_Functionality() public { /* ... */ } - } - ``` +--- -### For New Libraries +## Naming and structure conventions -1. Create a harness to expose internal functions: +- **Behaviour‑oriented naming**: + - `test_FunctionName_Scenario()` for happy‑path and scenario tests. + - `test_RevertWhen_Condition()` for revert paths. + - `testFuzz_FunctionName_Scenario()` for fuzz/property tests. +- **Arrange–Act–Assert**: + - Make the phases obvious with whitespace and, if helpful, short comments. +- **Mirror the trees**: + - Use the same terminology as the `.tree` files where possible (e.g. β€œgiven when balance < amount”). +- **Scope and isolation**: + - Prefer small, focused test contracts rather than monolithic files. + - Use storage utilities instead of adding debug getters to `src`. - ```solidity - contract MyLibHarness { - function myInternalFunction(...) external { - MyLib.myInternalFunction(...); - } +--- - // Add view functions to read storage - function getStorageValue() external view returns (...) { - return MyLib.getStorage().value; - } - } - ``` +## Running tests -2. Create test file following the same pattern as `LibERC20.t.sol` - -## Testing Best Practices - -1. **Test behavior, not implementation** - Focus on what the contract does, not how -2. **Use descriptive test names** - Follow the pattern `test_FunctionName_Scenario` -3. **Test error conditions** - Use `test_RevertWhen_Condition` naming -4. **Use fuzz testing** - Prefix with `testFuzz_` for property-based tests -5. **Test events** - Use `vm.expectEmit()` to verify event emission -6. **Arrange-Act-Assert** - Structure tests clearly with setup, action, and verification -7. **Keep harnesses minimal** - Only add what's necessary for testing - -## Test Naming Conventions - -- `test_FunctionName()` - Basic happy path test -- `test_FunctionName_Scenario()` - Specific scenario test -- `test_RevertWhen_Condition()` - Tests that verify reverts -- `testFuzz_FunctionName()` - Fuzz tests (property-based) - -## Example Test Pattern - -```solidity -function test_Transfer() public { - // Arrange - uint256 amount = 100e18; +- **Run all tests**: - // Act - vm.prank(alice); - token.transfer(bob, amount); +```bash +forge test +``` - // Assert - assertEq(token.balanceOf(bob), amount); -} +- **Run tests for a specific area** (example: ERC‑20 token module): -function test_RevertWhen_TransferInsufficientBalance() public { - vm.prank(alice); - vm.expectRevert( - abi.encodeWithSelector( - ERC20Facet.ERC20InsufficientBalance.selector, - alice, - balance, - amount - ) - ); - token.transfer(bob, tooMuchAmount); -} +```bash +forge test --match-path "test/unit/token/ERC20/**" ``` -## Understanding Test Output +- **Run a specific fuzz file**: -When tests pass, you'll see: - -``` -Ran 2 test suites: 78 tests passed, 0 failed, 0 skipped (78 total tests) +```bash +forge test --match-path "test/unit/token/ERC20/Transfer/facet/fuzz/transfer.t.sol" ``` -Each test shows gas usage: +- **Run a specific test by name**: -``` -[PASS] test_Transfer() (gas: 46819) +```bash +forge test --match-test "test_RevertWhen_TransferInsufficientBalance" ``` -Fuzz tests show number of runs: +- **Generate a gas report**: +```bash +forge test --gas-report ``` -[PASS] testFuzz_Transfer(address,uint256) (runs: 256, ΞΌ: 42444, ~: 43179) -``` - -## Contributing -When adding new features to Compose: +You can combine `--match-path` and `--match-test` to narrow the scope as needed. -1. Write test harnesses if the contract needs initialization or has internal functions -2. Follow existing test patterns and naming conventions -3. Aim for comprehensive coverage including error cases -4. Add fuzz tests for functions with numeric parameters -5. Verify events are emitted correctly -6. Run tests before submitting PRs: `forge test` +--- -## Why This Approach? +## Contributing to tests -This testing architecture: +When you add or change behaviour in Compose: -- βœ… Respects Compose's design constraints -- βœ… Keeps production code clean (no test-only modifications) -- βœ… Provides comprehensive coverage -- βœ… Follows industry best practices (OpenZeppelin pattern) -- βœ… Makes internal code testable -- βœ… Enables isolated unit testing +- **Update or add the relevant behaviour tree** in `test/trees/*.tree` to reflect the new or changed behaviour. +- **Add or extend unit tests** under `test/unit/**` that: + - Use `Base_Test` and the shared utilities. + - Clearly map back to specific leaves in the tree. + - Include both success paths and revert paths. +- **Prefer fuzz tests** for anything with numeric parameters or interesting state spaces. +- **Run `forge test` locally** before opening a PR and ensure the new tests are stable (no flakiness). -## Questions? +If you are unsure how to structure tests for a new module, start by: -If you're unsure about testing patterns or need help writing tests for a new feature, refer to the existing test files in `test/ERC20/` as examples, or ask in the Discord community. +- Reading the relevant `.tree` file in `test/trees/`. +- Looking at existing tests for a similar standard (e.g. ERC‑20 vs ERC‑721 transfer behaviour). +- Following the same patterns for layout, naming, and use of shared utilities. diff --git a/test/trees/AccessControl.tree b/test/trees/AccessControl.tree index f253cbee..4194e942 100644 --- a/test/trees/AccessControl.tree +++ b/test/trees/AccessControl.tree @@ -186,3 +186,6 @@ Temporal └── when the caller does not have the admin role for the target role └── it should revert with {AccessControlUnauthorizedAccount} +ExportSelectors +└── for each AccessControl facet (data, admin, grant, revoke, renounce, batch, pausable, temporal) + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order diff --git a/test/trees/ERC1155.tree b/test/trees/ERC1155.tree index 0363de21..303bab37 100644 --- a/test/trees/ERC1155.tree +++ b/test/trees/ERC1155.tree @@ -100,3 +100,7 @@ Metadata β”‚ └── it should return baseURI + tokenURIs[id] or documented behavior └── SetBaseURI_SetTokenURI └── it should update storage so uri(id) returns the expected value + +ExportSelectors +└── for each ERC1155 facet (core, metadata, etc.) + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order diff --git a/test/trees/ERC165.tree b/test/trees/ERC165.tree index 9bcd323f..f6a25084 100644 --- a/test/trees/ERC165.tree +++ b/test/trees/ERC165.tree @@ -64,7 +64,7 @@ Storage └── when comparing supportsInterface to the raw storage value └── it should return the same boolean -IntegrationWithDiamond +Integration β”œβ”€β”€ BaseSetup β”‚ β”œβ”€β”€ when the diamond is deployed with ERC165Facet installed β”‚ β”‚ └── it should support IERC165.interfaceId via the diamond proxy @@ -81,6 +81,10 @@ IntegrationWithDiamond └── when upgrading facets that add or remove interfaceIds └── supportsInterface should reflect the new set of interfaces +ExportSelectors +└── for the ERC165 data facet and related interface-registration facets + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order + Gas β”œβ”€β”€ when calling supportsInterface for IERC165.interfaceId β”‚ └── it should consume less than 30,000 gas diff --git a/test/trees/ERC20.tree b/test/trees/ERC20.tree index 58f2d79e..a58cfdb5 100644 --- a/test/trees/ERC20.tree +++ b/test/trees/ERC20.tree @@ -145,4 +145,8 @@ Permit └── when signature is valid and deadline not passed β”œβ”€β”€ it should set allowance(owner, spender) to value β”œβ”€β”€ it should increment nonces(owner) - └── it should emit {Approval} \ No newline at end of file + └── it should emit {Approval} + +ExportSelectors +└── for each ERC20 facet + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order \ No newline at end of file diff --git a/test/trees/ERC721.tree b/test/trees/ERC721.tree index fe8df3f0..237e97df 100644 --- a/test/trees/ERC721.tree +++ b/test/trees/ERC721.tree @@ -135,3 +135,7 @@ Metadata └── when baseURI is empty └── it should return an empty string or documented behavior +ExportSelectors +└── for each ERC721 facet (core, enumerable, metadata, etc.) + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order + diff --git a/test/trees/Owner.tree b/test/trees/Owner.tree index 5a4dab64..49614e1b 100644 --- a/test/trees/Owner.tree +++ b/test/trees/Owner.tree @@ -64,3 +64,7 @@ TwoSteps β”‚ └── it should emit an {OwnershipTransferred} event └── when the caller is not the owner └── it should revert with {OwnerUnauthorizedAccount} + +ExportSelectors +└── for each Owner facet (data, transfer, renounce, two-steps) + └── it should return an abi.encodePacked list of that facet's external function selectors in the expected order From 07a9f4fbccc584eb9c807b7f51a536300aa1d5ed Mon Sep 17 00:00:00 2001 From: maxnorm Date: Thu, 12 Mar 2026 12:38:11 -0400 Subject: [PATCH 24/25] add ERC72 --- .../ERC721/Approve/ERC721ApproveModBase.t.sol | 23 +++++ .../ERC721/Approve/mod/fuzz/approve.t.sol | 61 ++++++++++++ .../token/ERC721/Burn/ERC721BurnModBase.t.sol | 23 +++++ .../token/ERC721/Burn/mod/fuzz/burn.t.sol | 39 ++++++++ .../Enumerable/Burn/mod/fuzz/burn.t.sol | 52 ++++++++++ .../ERC721EnumerableBurnModBase.t.sol | 23 +++++ .../ERC721EnumerableMintModBase.t.sol | 23 +++++ .../ERC721EnumerableTransferModBase.t.sol | 25 +++++ .../Enumerable/Mint/mod/fuzz/mint.t.sol | 49 ++++++++++ .../Transfer/mod/fuzz/transfer.t.sol | 95 +++++++++++++++++++ .../token/ERC721/Mint/ERC721MintModBase.t.sol | 23 +++++ .../token/ERC721/Mint/mod/fuzz/mint.t.sol | 48 ++++++++++ .../Transfer/ERC721TransferModBase.t.sol | 23 +++++ .../ERC721/Transfer/mod/fuzz/transfer.t.sol | 77 +++++++++++++++ .../fuzz/defaultRoyalty.t.sol | 0 .../{Facet => facet}/fuzz/edgeCases.t.sol | 0 .../{Facet => facet}/fuzz/royalty.t.sol | 0 .../{Facet => facet}/fuzz/royaltyInfo.t.sol | 0 .../{Facet => facet}/fuzz/tokenRoyalty.t.sol | 0 .../{Mod => mod}/fuzz/defaultRoyalty.t.sol | 0 .../Royalty/{Mod => mod}/fuzz/edgeCases.t.sol | 0 .../Royalty/{Mod => mod}/fuzz/royalty.t.sol | 0 .../{Mod => mod}/fuzz/royaltyInfo.t.sol | 0 .../{Mod => mod}/fuzz/tokenRoyalty.t.sol | 0 .../token/ERC721/ERC721ApproveModHarness.sol | 29 ++++++ .../token/ERC721/ERC721BurnModHarness.sol | 19 ++++ .../ERC721/ERC721EnumerableBurnModHarness.sol | 19 ++++ .../ERC721/ERC721EnumerableMintModHarness.sol | 19 ++++ .../ERC721EnumerableTransferModHarness.sol | 27 ++++++ .../token/ERC721/ERC721MintModHarness.sol | 19 ++++ .../token/ERC721/ERC721TransferModHarness.sol | 19 ++++ 31 files changed, 735 insertions(+) create mode 100644 test/unit/token/ERC721/Approve/ERC721ApproveModBase.t.sol create mode 100644 test/unit/token/ERC721/Approve/mod/fuzz/approve.t.sol create mode 100644 test/unit/token/ERC721/Burn/ERC721BurnModBase.t.sol create mode 100644 test/unit/token/ERC721/Burn/mod/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Burn/mod/fuzz/burn.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnModBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableMintModBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferModBase.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Mint/mod/fuzz/mint.t.sol create mode 100644 test/unit/token/ERC721/Enumerable/Transfer/mod/fuzz/transfer.t.sol create mode 100644 test/unit/token/ERC721/Mint/ERC721MintModBase.t.sol create mode 100644 test/unit/token/ERC721/Mint/mod/fuzz/mint.t.sol create mode 100644 test/unit/token/ERC721/Transfer/ERC721TransferModBase.t.sol create mode 100644 test/unit/token/ERC721/Transfer/mod/fuzz/transfer.t.sol rename test/unit/token/Royalty/{Facet => facet}/fuzz/defaultRoyalty.t.sol (100%) rename test/unit/token/Royalty/{Facet => facet}/fuzz/edgeCases.t.sol (100%) rename test/unit/token/Royalty/{Facet => facet}/fuzz/royalty.t.sol (100%) rename test/unit/token/Royalty/{Facet => facet}/fuzz/royaltyInfo.t.sol (100%) rename test/unit/token/Royalty/{Facet => facet}/fuzz/tokenRoyalty.t.sol (100%) rename test/unit/token/Royalty/{Mod => mod}/fuzz/defaultRoyalty.t.sol (100%) rename test/unit/token/Royalty/{Mod => mod}/fuzz/edgeCases.t.sol (100%) rename test/unit/token/Royalty/{Mod => mod}/fuzz/royalty.t.sol (100%) rename test/unit/token/Royalty/{Mod => mod}/fuzz/royaltyInfo.t.sol (100%) rename test/unit/token/Royalty/{Mod => mod}/fuzz/tokenRoyalty.t.sol (100%) create mode 100644 test/utils/harnesses/token/ERC721/ERC721ApproveModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721BurnModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721EnumerableBurnModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721EnumerableMintModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721EnumerableTransferModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721MintModHarness.sol create mode 100644 test/utils/harnesses/token/ERC721/ERC721TransferModHarness.sol diff --git a/test/unit/token/ERC721/Approve/ERC721ApproveModBase.t.sol b/test/unit/token/ERC721/Approve/ERC721ApproveModBase.t.sol new file mode 100644 index 00000000..aecdc9a6 --- /dev/null +++ b/test/unit/token/ERC721/Approve/ERC721ApproveModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721ApproveModHarness} from "test/utils/harnesses/token/ERC721/ERC721ApproveModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721ApproveMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721ApproveModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721ApproveModHarness(); + vm.label(address(harness), "ERC721ApproveModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Approve/mod/fuzz/approve.t.sol b/test/unit/token/ERC721/Approve/mod/fuzz/approve.t.sol new file mode 100644 index 00000000..f9b29321 --- /dev/null +++ b/test/unit/token/ERC721/Approve/mod/fuzz/approve.t.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721ApproveMod_Base_Test} from "test/unit/token/ERC721/Approve/ERC721ApproveModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Approve/ERC721ApproveMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Approve_ERC721ApproveMod_Fuzz_Unit_Test is ERC721ApproveMod_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Approve_WhenTokenDoesNotExist(address to, uint256 tokenId) external { + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + harness.approve(to, tokenId); + } + + function testFuzz_ShouldApprove_WhenTokenExists(address owner, address to, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectEmit(address(harness)); + emit Approval(owner, to, tokenId); + harness.approve(to, tokenId); + + assertEq(address(harness).getApproved(tokenId), to, "approved(tokenId)"); + } + + function testFuzz_ShouldRevert_SetApprovalForAll_WhenOperatorIsZeroAddress(address user, bool approved) external { + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidOperator.selector, address(0))); + harness.setApprovalForAll(user, address(0), approved); + } + + function testFuzz_ShouldSetApprovalForAll_WhenOperatorIsNotZeroAddress( + address user, + address operator, + bool approved + ) external { + vm.assume(operator != address(0)); + + vm.expectEmit(address(harness)); + emit ApprovalForAll(user, operator, approved); + harness.setApprovalForAll(user, operator, approved); + + bool result = address(harness).isApprovedForAll(user, operator); + assertEq(result, approved, "isApprovedForAll(user, operator)"); + } +} + diff --git a/test/unit/token/ERC721/Burn/ERC721BurnModBase.t.sol b/test/unit/token/ERC721/Burn/ERC721BurnModBase.t.sol new file mode 100644 index 00000000..1a216686 --- /dev/null +++ b/test/unit/token/ERC721/Burn/ERC721BurnModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721BurnModHarness} from "test/utils/harnesses/token/ERC721/ERC721BurnModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721BurnMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721BurnModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721BurnModHarness(); + vm.label(address(harness), "ERC721BurnModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC721/Burn/mod/fuzz/burn.t.sol new file mode 100644 index 00000000..c46be3c2 --- /dev/null +++ b/test/unit/token/ERC721/Burn/mod/fuzz/burn.t.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721BurnMod_Base_Test} from "test/unit/token/ERC721/Burn/ERC721BurnModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Burn/ERC721BurnMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Burn_ERC721BurnMod_Fuzz_Unit_Test is ERC721BurnMod_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Burn_WhenTokenDoesNotExist(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + harness.burn(tokenId); + } + + function testFuzz_ShouldBurn_WhenTokenExists(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectEmit(address(harness)); + emit Transfer(owner, address(0), tokenId); + harness.burn(tokenId); + + assertEq(address(harness).balanceOf(owner), 0, "owner balance"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Burn/mod/fuzz/burn.t.sol b/test/unit/token/ERC721/Enumerable/Burn/mod/fuzz/burn.t.sol new file mode 100644 index 00000000..97948332 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Burn/mod/fuzz/burn.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721EnumerableBurnMod_Base_Test} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Burn_ERC721EnumerableBurnMod_Fuzz_Unit_Test is ERC721EnumerableBurnMod_Base_Test { + using ERC721StorageUtils for address; + + function _seedOwnerToken(address owner, uint256 tokenId) internal { + uint256 ownerIndex = address(harness).balanceOf(owner); + address(harness).setOwnerTokenByIndex(owner, ownerIndex, tokenId); + address(harness).setOwnerTokensIndex(tokenId, ownerIndex); + address(harness).setBalanceOf(owner, ownerIndex + 1); + + uint256 globalIndex = address(harness).allTokensLength(); + address(harness).pushAllToken(tokenId); + address(harness).setAllTokensIndex(tokenId, globalIndex); + address(harness).setOwnerOf(tokenId, owner); + } + + function testFuzz_ShouldRevert_Burn_WhenTokenDoesNotExist(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + harness.burn(tokenId); + } + + function testFuzz_ShouldBurnAndUpdateEnumeration_WhenTokenExists(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.expectEmit(address(harness)); + emit Transfer(owner, address(0), tokenId); + harness.burn(tokenId); + + assertEq(address(harness).balanceOf(owner), 0, "owner balance"); + assertEq(address(harness).allTokensLength(), 0, "allTokens length"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnModBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnModBase.t.sol new file mode 100644 index 00000000..2744d2f0 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableBurnModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721EnumerableBurnModHarness} from "test/utils/harnesses/token/ERC721/ERC721EnumerableBurnModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableBurnMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableBurnModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721EnumerableBurnModHarness(); + vm.label(address(harness), "ERC721EnumerableBurnModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableMintModBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableMintModBase.t.sol new file mode 100644 index 00000000..11cb5098 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableMintModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721EnumerableMintModHarness} from "test/utils/harnesses/token/ERC721/ERC721EnumerableMintModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableMintMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableMintModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721EnumerableMintModHarness(); + vm.label(address(harness), "ERC721EnumerableMintModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferModBase.t.sol b/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferModBase.t.sol new file mode 100644 index 00000000..579f098e --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferModBase.t.sol @@ -0,0 +1,25 @@ +// 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 { + ERC721EnumerableTransferModHarness +} from "test/utils/harnesses/token/ERC721/ERC721EnumerableTransferModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721EnumerableTransferMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721EnumerableTransferModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721EnumerableTransferModHarness(); + vm.label(address(harness), "ERC721EnumerableTransferModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC721/Enumerable/Mint/mod/fuzz/mint.t.sol new file mode 100644 index 00000000..e5d526f8 --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Mint/mod/fuzz/mint.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721EnumerableMintMod_Base_Test} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableMintModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Mint_ERC721EnumerableMintMod_Fuzz_Unit_Test is ERC721EnumerableMintMod_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Mint_WhenToIsZeroAddress(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); + harness.mint(address(0), tokenId); + } + + function testFuzz_ShouldRevert_Mint_WhenTokenAlreadyExists(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + harness.mint(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidSender.selector, address(0))); + harness.mint(owner, tokenId); + } + + function testFuzz_ShouldMintAndEnumerate_WhenValidInputs(address to, uint256 tokenId) external { + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectEmit(address(harness)); + emit Transfer(address(0), to, tokenId); + harness.mint(to, tokenId); + + assertEq(address(harness).ownerOf(tokenId), to, "ownerOf"); + assertEq(address(harness).balanceOf(to), 1, "balanceOf(to)"); + assertEq(address(harness).allTokensLength(), 1, "allTokens length"); + } +} + diff --git a/test/unit/token/ERC721/Enumerable/Transfer/mod/fuzz/transfer.t.sol b/test/unit/token/ERC721/Enumerable/Transfer/mod/fuzz/transfer.t.sol new file mode 100644 index 00000000..ef3260cd --- /dev/null +++ b/test/unit/token/ERC721/Enumerable/Transfer/mod/fuzz/transfer.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import { + ERC721EnumerableTransferMod_Base_Test +} from "test/unit/token/ERC721/Enumerable/ERC721EnumerableTransferModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Transfer_ERC721EnumerableTransferMod_Fuzz_Unit_Test is ERC721EnumerableTransferMod_Base_Test { + using ERC721StorageUtils for address; + + function _seedOwnerToken(address owner, uint256 tokenId) internal { + uint256 ownerIndex = address(harness).balanceOf(owner); + address(harness).setOwnerTokenByIndex(owner, ownerIndex, tokenId); + address(harness).setOwnerTokensIndex(tokenId, ownerIndex); + address(harness).setBalanceOf(owner, ownerIndex + 1); + + uint256 globalIndex = address(harness).allTokensLength(); + address(harness).pushAllToken(tokenId); + address(harness).setAllTokensIndex(tokenId, globalIndex); + address(harness).setOwnerOf(tokenId, owner); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenTokenDoesNotExist(address from, address to, uint256 tokenId) + external + { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + harness.transferFrom(from, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenToIsZeroAddress(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); + harness.transferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenFromIsNotOwner( + address owner, + address wrongFrom, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(wrongFrom != address(0)); + vm.assume(wrongFrom != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != wrongFrom); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner)); + harness.transferFrom(wrongFrom, to, tokenId); + } + + function testFuzz_ShouldUpdateEnumerationOnTransfer_WhenCalledWithCorrectOwner( + address owner, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + _seedOwnerToken(owner, tokenId); + + vm.expectEmit(address(harness)); + emit Transfer(owner, to, tokenId); + harness.transferFrom(owner, to, tokenId); + + assertEq(address(harness).ownerOf(tokenId), to, "new owner"); + assertEq(address(harness).balanceOf(owner), 0, "old owner balance"); + assertEq(address(harness).balanceOf(to), 1, "new owner balance"); + } +} + diff --git a/test/unit/token/ERC721/Mint/ERC721MintModBase.t.sol b/test/unit/token/ERC721/Mint/ERC721MintModBase.t.sol new file mode 100644 index 00000000..45d29d04 --- /dev/null +++ b/test/unit/token/ERC721/Mint/ERC721MintModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721MintModHarness} from "test/utils/harnesses/token/ERC721/ERC721MintModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721MintMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721MintModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721MintModHarness(); + vm.label(address(harness), "ERC721MintModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Mint/mod/fuzz/mint.t.sol b/test/unit/token/ERC721/Mint/mod/fuzz/mint.t.sol new file mode 100644 index 00000000..431d95fd --- /dev/null +++ b/test/unit/token/ERC721/Mint/mod/fuzz/mint.t.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721MintMod_Base_Test} from "test/unit/token/ERC721/Mint/ERC721MintModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Mint/ERC721MintMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Mint_ERC721MintMod_Fuzz_Unit_Test is ERC721MintMod_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_Mint_WhenToIsZeroAddress(uint256 tokenId) external { + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); + harness.mint(address(0), tokenId); + } + + function testFuzz_ShouldRevert_Mint_WhenTokenAlreadyExists(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidSender.selector, address(0))); + harness.mint(owner, tokenId); + } + + function testFuzz_ShouldMint_WhenValidInputs(address to, uint256 tokenId) external { + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectEmit(address(harness)); + emit Transfer(address(0), to, tokenId); + harness.mint(to, tokenId); + + assertEq(address(harness).ownerOf(tokenId), to, "ownerOf"); + assertEq(address(harness).balanceOf(to), 1, "balanceOf(to)"); + } +} + diff --git a/test/unit/token/ERC721/Transfer/ERC721TransferModBase.t.sol b/test/unit/token/ERC721/Transfer/ERC721TransferModBase.t.sol new file mode 100644 index 00000000..4db5dc5a --- /dev/null +++ b/test/unit/token/ERC721/Transfer/ERC721TransferModBase.t.sol @@ -0,0 +1,23 @@ +// 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 {ERC721TransferModHarness} from "test/utils/harnesses/token/ERC721/ERC721TransferModHarness.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +abstract contract ERC721TransferMod_Base_Test is Base_Test { + using ERC721StorageUtils for address; + + ERC721TransferModHarness internal harness; + + function setUp() public virtual override { + Base_Test.setUp(); + harness = new ERC721TransferModHarness(); + vm.label(address(harness), "ERC721TransferModHarness"); + } +} + diff --git a/test/unit/token/ERC721/Transfer/mod/fuzz/transfer.t.sol b/test/unit/token/ERC721/Transfer/mod/fuzz/transfer.t.sol new file mode 100644 index 00000000..0a10e659 --- /dev/null +++ b/test/unit/token/ERC721/Transfer/mod/fuzz/transfer.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import {ERC721TransferMod_Base_Test} from "test/unit/token/ERC721/Transfer/ERC721TransferModBase.t.sol"; +import {ERC721StorageUtils} from "test/utils/storage/ERC721StorageUtils.sol"; + +import "src/token/ERC721/Transfer/ERC721TransferMod.sol"; + +/** + * @dev BTT spec: test/trees/ERC721.tree + */ +contract Transfer_ERC721TransferMod_Fuzz_Unit_Test is ERC721TransferMod_Base_Test { + using ERC721StorageUtils for address; + + function testFuzz_ShouldRevert_TransferFrom_WhenTokenDoesNotExist(address from, address to, uint256 tokenId) + external + { + vm.assume(from != address(0)); + vm.assume(to != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, tokenId)); + harness.transferFrom(from, to, tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenToIsZeroAddress(address owner, uint256 tokenId) external { + vm.assume(owner != address(0)); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721InvalidReceiver.selector, address(0))); + harness.transferFrom(owner, address(0), tokenId); + } + + function testFuzz_ShouldRevert_TransferFrom_WhenFromIsNotOwner( + address owner, + address wrongFrom, + address to, + uint256 tokenId + ) external { + vm.assume(owner != address(0)); + vm.assume(wrongFrom != address(0)); + vm.assume(wrongFrom != owner); + vm.assume(to != address(0)); + vm.assume(to != owner); + vm.assume(to != wrongFrom); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectRevert(abi.encodeWithSelector(ERC721IncorrectOwner.selector, wrongFrom, tokenId, owner)); + harness.transferFrom(wrongFrom, to, tokenId); + } + + function testFuzz_ShouldTransfer_WhenCalledWithCorrectOwner(address owner, address to, uint256 tokenId) external { + vm.assume(owner != address(0)); + vm.assume(to != address(0)); + vm.assume(to != owner); + tokenId = bound(tokenId, 1, type(uint128).max); + + address(harness).mint(owner, tokenId); + + vm.expectEmit(address(harness)); + emit Transfer(owner, to, tokenId); + harness.transferFrom(owner, to, tokenId); + + assertEq(address(harness).ownerOf(tokenId), to, "new owner"); + assertEq(address(harness).balanceOf(owner), 0, "owner balance"); + assertEq(address(harness).balanceOf(to), 1, "receiver balance"); + } +} + diff --git a/test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol b/test/unit/token/Royalty/facet/fuzz/defaultRoyalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Facet/fuzz/defaultRoyalty.t.sol rename to test/unit/token/Royalty/facet/fuzz/defaultRoyalty.t.sol diff --git a/test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol b/test/unit/token/Royalty/facet/fuzz/edgeCases.t.sol similarity index 100% rename from test/unit/token/Royalty/Facet/fuzz/edgeCases.t.sol rename to test/unit/token/Royalty/facet/fuzz/edgeCases.t.sol diff --git a/test/unit/token/Royalty/Facet/fuzz/royalty.t.sol b/test/unit/token/Royalty/facet/fuzz/royalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Facet/fuzz/royalty.t.sol rename to test/unit/token/Royalty/facet/fuzz/royalty.t.sol diff --git a/test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/facet/fuzz/royaltyInfo.t.sol similarity index 100% rename from test/unit/token/Royalty/Facet/fuzz/royaltyInfo.t.sol rename to test/unit/token/Royalty/facet/fuzz/royaltyInfo.t.sol diff --git a/test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol b/test/unit/token/Royalty/facet/fuzz/tokenRoyalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Facet/fuzz/tokenRoyalty.t.sol rename to test/unit/token/Royalty/facet/fuzz/tokenRoyalty.t.sol diff --git a/test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol b/test/unit/token/Royalty/mod/fuzz/defaultRoyalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Mod/fuzz/defaultRoyalty.t.sol rename to test/unit/token/Royalty/mod/fuzz/defaultRoyalty.t.sol diff --git a/test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol b/test/unit/token/Royalty/mod/fuzz/edgeCases.t.sol similarity index 100% rename from test/unit/token/Royalty/Mod/fuzz/edgeCases.t.sol rename to test/unit/token/Royalty/mod/fuzz/edgeCases.t.sol diff --git a/test/unit/token/Royalty/Mod/fuzz/royalty.t.sol b/test/unit/token/Royalty/mod/fuzz/royalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Mod/fuzz/royalty.t.sol rename to test/unit/token/Royalty/mod/fuzz/royalty.t.sol diff --git a/test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol b/test/unit/token/Royalty/mod/fuzz/royaltyInfo.t.sol similarity index 100% rename from test/unit/token/Royalty/Mod/fuzz/royaltyInfo.t.sol rename to test/unit/token/Royalty/mod/fuzz/royaltyInfo.t.sol diff --git a/test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol b/test/unit/token/Royalty/mod/fuzz/tokenRoyalty.t.sol similarity index 100% rename from test/unit/token/Royalty/Mod/fuzz/tokenRoyalty.t.sol rename to test/unit/token/Royalty/mod/fuzz/tokenRoyalty.t.sol diff --git a/test/utils/harnesses/token/ERC721/ERC721ApproveModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721ApproveModHarness.sol new file mode 100644 index 00000000..a2b02054 --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721ApproveModHarness.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Approve/ERC721ApproveMod.sol" as ERC721ApproveMod; + +/** + * @title ERC721ApproveModHarness + * @notice Test harness that exposes ERC721ApproveMod functions as external + */ +contract ERC721ApproveModHarness { + /** + * @notice Exposes ERC721ApproveMod.approve as an external function + */ + function approve(address _to, uint256 _tokenId) external { + ERC721ApproveMod.approve(_to, _tokenId); + } + + /** + * @notice Exposes ERC721ApproveMod.setApprovalForAll as an external function + */ + function setApprovalForAll(address _user, address _operator, bool _approved) external { + ERC721ApproveMod.setApprovalForAll(_user, _operator, _approved); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721BurnModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721BurnModHarness.sol new file mode 100644 index 00000000..2245b0df --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721BurnModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Burn/ERC721BurnMod.sol" as ERC721BurnMod; + +/** + * @title ERC721BurnModHarness + * @notice Test harness that exposes ERC721BurnMod functions as external + */ +contract ERC721BurnModHarness { + function burn(uint256 _tokenId) external { + ERC721BurnMod.burn(_tokenId); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721EnumerableBurnModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721EnumerableBurnModHarness.sol new file mode 100644 index 00000000..c93e44f4 --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721EnumerableBurnModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Enumerable/Burn/ERC721EnumerableBurnMod.sol" as ERC721EnumerableBurnMod; + +/** + * @title ERC721EnumerableBurnModHarness + * @notice Test harness that exposes ERC721EnumerableBurnMod functions as external + */ +contract ERC721EnumerableBurnModHarness { + function burn(uint256 _tokenId) external { + ERC721EnumerableBurnMod.burn(_tokenId); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721EnumerableMintModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721EnumerableMintModHarness.sol new file mode 100644 index 00000000..f8a8878e --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721EnumerableMintModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Enumerable/Mint/ERC721EnumerableMintMod.sol" as ERC721EnumerableMintMod; + +/** + * @title ERC721EnumerableMintModHarness + * @notice Test harness that exposes ERC721EnumerableMintMod functions as external + */ +contract ERC721EnumerableMintModHarness { + function mint(address _to, uint256 _tokenId) external { + ERC721EnumerableMintMod.mint(_to, _tokenId); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721EnumerableTransferModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721EnumerableTransferModHarness.sol new file mode 100644 index 00000000..57da40b6 --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721EnumerableTransferModHarness.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Enumerable/Transfer/ERC721EnumerableTransferMod.sol" as ERC721EnumerableTransferMod; + +/** + * @title ERC721EnumerableTransferModHarness + * @notice Test harness that exposes ERC721EnumerableTransferMod functions as external + */ +contract ERC721EnumerableTransferModHarness { + function transferFrom(address _from, address _to, uint256 _tokenId) external { + ERC721EnumerableTransferMod.transferFrom(_from, _to, _tokenId); + } + + function safeTransferFrom(address _from, address _to, uint256 _tokenId) external { + ERC721EnumerableTransferMod.safeTransferFrom(_from, _to, _tokenId); + } + + function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata _data) external { + ERC721EnumerableTransferMod.safeTransferFrom(_from, _to, _tokenId, _data); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721MintModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721MintModHarness.sol new file mode 100644 index 00000000..970f38c4 --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721MintModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Mint/ERC721MintMod.sol" as ERC721MintMod; + +/** + * @title ERC721MintModHarness + * @notice Test harness that exposes ERC721MintMod functions as external + */ +contract ERC721MintModHarness { + function mint(address _to, uint256 _tokenId) external { + ERC721MintMod.mint(_to, _tokenId); + } +} + diff --git a/test/utils/harnesses/token/ERC721/ERC721TransferModHarness.sol b/test/utils/harnesses/token/ERC721/ERC721TransferModHarness.sol new file mode 100644 index 00000000..3148c3e3 --- /dev/null +++ b/test/utils/harnesses/token/ERC721/ERC721TransferModHarness.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +/* Compose + * https://compose.diamonds + */ + +import "src/token/ERC721/Transfer/ERC721TransferMod.sol" as ERC721TransferMod; + +/** + * @title ERC721TransferModHarness + * @notice Test harness that exposes ERC721TransferMod functions as external + */ +contract ERC721TransferModHarness { + function transferFrom(address _from, address _to, uint256 _tokenId) external { + ERC721TransferMod.transferFrom(_from, _to, _tokenId); + } +} + From 480df6e4375b8bd93280bbefd968bfc6fb809541 Mon Sep 17 00:00:00 2001 From: maxnorm Date: Thu, 12 Mar 2026 12:56:28 -0400 Subject: [PATCH 25/25] move royalty integration test to sequence unit tests --- test/trees/Royalty.tree | 2 +- .../token/Royalty/facet/fuzz/sequences.t.sol} | 5 +++-- .../token/Royalty/mod/fuzz/sequences.t.sol} | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) rename test/{integration/token/Royalty/Facet/fuzz/integration.t.sol => unit/token/Royalty/facet/fuzz/sequences.t.sol} (95%) rename test/{integration/token/Royalty/Mod/fuzz/integration.t.sol => unit/token/Royalty/mod/fuzz/sequences.t.sol} (96%) diff --git a/test/trees/Royalty.tree b/test/trees/Royalty.tree index 49e5af46..64220028 100644 --- a/test/trees/Royalty.tree +++ b/test/trees/Royalty.tree @@ -54,7 +54,7 @@ TokenRoyalty └── when multiple tokenIds have token-specific royalty and some are reset └── it should only clear royalty for the reset tokenIds and leave the others unchanged -Integration +Sequences β”œβ”€β”€ when default royalty is set, then token-specific royalty is set, then token-specific royalty is reset β”‚ └── royaltyInfo should first use the default, then the token-specific, then fall back to the default again └── when default royalty is set, multiple tokens have different token-specific royalties, and default royalty is deleted diff --git a/test/integration/token/Royalty/Facet/fuzz/integration.t.sol b/test/unit/token/Royalty/facet/fuzz/sequences.t.sol similarity index 95% rename from test/integration/token/Royalty/Facet/fuzz/integration.t.sol rename to test/unit/token/Royalty/facet/fuzz/sequences.t.sol index 13f326e5..0696b5df 100644 --- a/test/integration/token/Royalty/Facet/fuzz/integration.t.sol +++ b/test/unit/token/Royalty/facet/fuzz/sequences.t.sol @@ -10,9 +10,9 @@ import {RoyaltyFacet_Base_Test} from "test/unit/token/Royalty/RoyaltyFacetBase.t /** * @dev BTT spec: test/trees/Royalty.tree * - * Integration (Facet) + * Sequences (Facet) */ -contract Integration_RoyaltyFacet_Integration_Test is RoyaltyFacet_Base_Test { +contract Sequences_RoyaltyFacet_Fuzz_Unit_Test is RoyaltyFacet_Base_Test { function test_DefaultThenTokenThenReset_ThroughRoyaltyInfo() external { uint256 tokenId = 5; uint256 salePrice = 100 ether; @@ -66,3 +66,4 @@ contract Integration_RoyaltyFacet_Integration_Test is RoyaltyFacet_Base_Test { assertEq(royalty3, 0, "royalty3 after delete"); } } + diff --git a/test/integration/token/Royalty/Mod/fuzz/integration.t.sol b/test/unit/token/Royalty/mod/fuzz/sequences.t.sol similarity index 96% rename from test/integration/token/Royalty/Mod/fuzz/integration.t.sol rename to test/unit/token/Royalty/mod/fuzz/sequences.t.sol index cb22a560..041f4b42 100644 --- a/test/integration/token/Royalty/Mod/fuzz/integration.t.sol +++ b/test/unit/token/Royalty/mod/fuzz/sequences.t.sol @@ -10,9 +10,9 @@ import {RoyaltyMod_Base_Test} from "test/unit/token/Royalty/RoyaltyModBase.t.sol /** * @dev BTT spec: test/trees/Royalty.tree * - * Integration (Mod) + * Sequences (Mod) */ -contract Integration_RoyaltyMod_Integration_Test is RoyaltyMod_Base_Test { +contract Sequences_RoyaltyMod_Fuzz_Unit_Test is RoyaltyMod_Base_Test { function test_DefaultThenTokenThenReset_Sequence() external { uint256 tokenId = 5; uint256 salePrice = 100 ether; @@ -74,3 +74,4 @@ contract Integration_RoyaltyMod_Integration_Test is RoyaltyMod_Base_Test { assertEq(royalty3, 0, "royalty3 after delete"); } } +