From 325e552dbd2bf9c2c6ca76b36b7abbf557982494 Mon Sep 17 00:00:00 2001 From: David Dada Date: Sat, 30 Aug 2025 15:32:24 +0100 Subject: [PATCH 1/9] chore: switch "$" to "" for gas savings --- .gas-snapshot | 2 +- test/helpers/HelperContract.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 1f08d3d..d88bd13 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -2,7 +2,7 @@ DiamondTester:testDiamondDeployed() (gas: 5289) DiamondTester:testDiamondOwner() (gas: 17516) DiamondTester:testFacetAddressToSelectorsMappingIsCorrect() (gas: 140449) DiamondTester:testSelectorToFacetMappingIsCorrect() (gas: 138419) -DiamondTester:testSelectorsAreComplete() (gas: 144484) +DiamondTester:testSelectorsAreComplete() (gas: 144433) DiamondTester:testSelectorsAreUnique() (gas: 183338) DiamondTester:testStandardFacetsDeployed() (gas: 13886) DiamondTester:testSupportsERC165() (gas: 15452) diff --git a/test/helpers/HelperContract.sol b/test/helpers/HelperContract.sol index 5bfb808..6d7c297 100644 --- a/test/helpers/HelperContract.sol +++ b/test/helpers/HelperContract.sol @@ -26,7 +26,7 @@ abstract contract HelperContract is Test { bytes memory res = vm.ffi(cmd); string memory output = string(res); - string[] memory keys = vm.parseJsonKeys(output, "$"); + string[] memory keys = vm.parseJsonKeys(output, ""); uint256 keysLength = keys.length; // Initialize the selectors array with the selectorCount From f051eac144284ca437446d75ac2a6671f411af32 Mon Sep 17 00:00:00 2001 From: David Dada Date: Sat, 30 Aug 2025 15:32:40 +0100 Subject: [PATCH 2/9] feat: add foundry.lock --- foundry.lock | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 foundry.lock diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..eef3d68 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,5 @@ +{ + "lib/forge-std": { + "rev": "3b20d60d14b343ee4f908cb8079495c07f5e8981" + } +} \ No newline at end of file From bb586eb3acd628a5973c407f0d7e52d7354172ae Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:19:36 +0100 Subject: [PATCH 3/9] chore: trim foundry.toml --- foundry.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/foundry.toml b/foundry.toml index 47e6806..50a5022 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,10 +10,5 @@ remappings = [ '@diamond-logs/=src/libraries/logs/', '@diamond-errors/=src/libraries/errors/' ] -auto_detect_remappings = true -ffi = true optimizer_runs = 20_000 -evm_version = "prague" -allow_internal_expect_revert = false -names = true -sizes = true +ffi = true From ea19fc5fd4869433dc07bdf63b27c0eaa1f3e5d5 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:33:42 +0100 Subject: [PATCH 4/9] chore: 100% modified from Nick's --- src/libraries/LibDiamond.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/LibDiamond.sol b/src/libraries/LibDiamond.sol index 0b0ae77..36061b9 100644 --- a/src/libraries/LibDiamond.sol +++ b/src/libraries/LibDiamond.sol @@ -10,7 +10,6 @@ import "@diamond-errors/DiamondErrors.sol"; /// @notice Internal library providing core functionality for EIP-2535 Diamond proxy management. /// @author David Dada /// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/libraries/LibDiamond.sol) -/// @author Modified from Timo (https://github.com/FydeTreasury/Diamond-Foundry/blob/main/src/libraries/LibDiamond.sol) /// /// @dev Defines the diamond storage layout and implements the `_diamondCut` operation and storage accessors library LibDiamond { From 0bc6a3020c14bebffa106c7fe975187971a56be5 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:37:31 +0100 Subject: [PATCH 5/9] chore(refactor): extract get selector function and rename helper contract to utils lib --- test/helpers/GetSelectors.sol | 31 +++++++++++++++++++ .../helpers/{HelperContract.sol => Utils.sol} | 29 +---------------- 2 files changed, 32 insertions(+), 28 deletions(-) create mode 100644 test/helpers/GetSelectors.sol rename test/helpers/{HelperContract.sol => Utils.sol} (79%) diff --git a/test/helpers/GetSelectors.sol b/test/helpers/GetSelectors.sol new file mode 100644 index 0000000..9ffdc5b --- /dev/null +++ b/test/helpers/GetSelectors.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +abstract contract GetSelectors is Test { + /// @notice Generates function selectors for a given facet using Foundry's `forge inspect`. + /// @dev Uses `vm.ffi` to execute a shell command that retrieves method identifiers. + /// @param _facet The name of the facet contract to inspect. + /// @return selectors_ An array of function selectors extracted from the facet. + function _getSelectors(string memory _facet) internal returns (bytes4[] memory selectors_) { + string[] memory cmd = new string[](5); + cmd[0] = "forge"; + cmd[1] = "inspect"; + cmd[2] = _facet; + cmd[3] = "methodIdentifiers"; + cmd[4] = "--json"; + + bytes memory res = vm.ffi(cmd); + string memory output = string(res); + + string[] memory keys = vm.parseJsonKeys(output, ""); + uint256 keysLength = keys.length; + + selectors_ = new bytes4[](keysLength); + + for (uint256 i; i < keysLength; ++i) { + selectors_[i] = bytes4(bytes32(keccak256(bytes(keys[i])))); + } + } +} diff --git a/test/helpers/HelperContract.sol b/test/helpers/Utils.sol similarity index 79% rename from test/helpers/HelperContract.sol rename to test/helpers/Utils.sol index 6d7c297..4e54449 100644 --- a/test/helpers/HelperContract.sol +++ b/test/helpers/Utils.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {Test} from "forge-std/Test.sol"; import {IDiamondLoupe} from "@diamond/interfaces/IDiamondLoupe.sol"; import {Facet} from "@diamond-storage/DiamondStorage.sol"; @@ -10,33 +9,7 @@ import {Facet} from "@diamond-storage/DiamondStorage.sol"; /// @author Modified from Timo (https://github.com/FydeTreasury/Diamond-Foundry/blob/main/test/HelperContract.sol) /// /// @dev Includes support for generating selectors using Foundry FFI, array manipulation, and facet inspection. -abstract contract HelperContract is Test { - /// @notice Generates function selectors for a given facet using Foundry's `forge inspect`. - /// @dev Uses `vm.ffi` to execute a shell command that retrieves method identifiers. - /// @param _facet The name of the facet contract to inspect. - /// @return selectors_ An array of function selectors extracted from the facet. - function _generateSelectors(string memory _facet) internal returns (bytes4[] memory selectors_) { - string[] memory cmd = new string[](5); - cmd[0] = "forge"; - cmd[1] = "inspect"; - cmd[2] = _facet; - cmd[3] = "methodIdentifiers"; - cmd[4] = "--json"; - - bytes memory res = vm.ffi(cmd); - string memory output = string(res); - - string[] memory keys = vm.parseJsonKeys(output, ""); - uint256 keysLength = keys.length; - - // Initialize the selectors array with the selectorCount - selectors_ = new bytes4[](keysLength); - - for (uint256 i; i < keysLength; ++i) { - selectors_[i] = bytes4(bytes32(keccak256(bytes(keys[i])))); - } - } - +library Utils { /// @notice Removes an element from a bytes4 array at the given index. /// @param _index The index of the element to remove. /// @param _array The original array. From 25a9091bb5ff71eb42cf4e848a3d2a1f7218dbc4 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:39:14 +0100 Subject: [PATCH 6/9] chore(refactor): move diamond deployment to script --- script/DeployDiamond.s.sol | 56 ++++++++++++++++++++++++++-- test/helpers/DeployDiamondHelper.sol | 54 --------------------------- 2 files changed, 52 insertions(+), 58 deletions(-) delete mode 100644 test/helpers/DeployDiamondHelper.sol diff --git a/script/DeployDiamond.s.sol b/script/DeployDiamond.s.sol index 7abc7e3..fc4ab90 100644 --- a/script/DeployDiamond.s.sol +++ b/script/DeployDiamond.s.sol @@ -2,20 +2,68 @@ pragma solidity ^0.8.4; import {Script} from "forge-std/Script.sol"; -import {DeployDiamondHelper} from "@diamond-test/helpers/DeployDiamondHelper.sol"; +import {Diamond} from "@diamond/Diamond.sol"; +import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol"; +import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol"; +import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.sol"; +import {ERC165Init} from "@diamond/initializers/ERC165Init.sol"; +import {FacetCutAction, FacetCut, DiamondArgs} from "@diamond-storage/DiamondStorage.sol"; +import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol"; /// @title DeployDiamond /// @notice Deployment script for an EIP-2535 Diamond proxy contract with core facets and ERC165 initialization /// @author David Dada /// /// @dev Uses Foundry's `Script` and a helper contract to deploy and wire up DiamondCutFacet, DiamondLoupeFacet, and OwnableRolesFacet -contract DeployDiamond is Script, DeployDiamondHelper { +contract DeployDiamond is Script, GetSelectors { /// @notice Executes the deployment of the Diamond contract with the initial facets and ERC165 interface setup /// @dev Broadcasts transactions using Foundry's scripting environment (`vm.startBroadcast()` and `vm.stopBroadcast()`). /// Deploys three core facets, sets up DiamondArgs, encodes an initializer call, and constructs the Diamond. /// @return diamond_ The address of the deployed Diamond proxy contract function run() external returns (address diamond_) { - vm.broadcast(); - diamond_ = _deployDiamond(msg.sender); + vm.startBroadcast(); + // Deploy core facet contracts + DiamondCutFacet diamondCutFacet = new DiamondCutFacet(); + DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet(); + OwnableRolesFacet ownableRolesFacet = new OwnableRolesFacet(); + + // Deploy ERC165 initializer contract + ERC165Init erc165Init = new ERC165Init(); + + // Prepare DiamondArgs: owner and init data + DiamondArgs memory args = DiamondArgs({ + owner: msg.sender, + init: address(erc165Init), + initData: abi.encodeWithSignature("initErc165()") + }); + + // Create an array of FacetCut entries for standard facets + FacetCut[] memory cut = new FacetCut[](3); + + // Add DiamondCutFacet to the cut list + cut[0] = FacetCut({ + facetAddress: address(diamondCutFacet), + action: FacetCutAction.Add, + functionSelectors: _getSelectors("DiamondCutFacet") + }); + + // Add DiamondLoupeFacet to the cut list + cut[1] = FacetCut({ + facetAddress: address(diamondLoupeFacet), + action: FacetCutAction.Add, + functionSelectors: _getSelectors("DiamondLoupeFacet") + }); + + // Add OwnableRolesFacet to the cut list + cut[2] = FacetCut({ + facetAddress: address(ownableRolesFacet), + action: FacetCutAction.Add, + functionSelectors: _getSelectors("OwnableRolesFacet") + }); + + // Deploy the Diamond contract with the facets and initialization args + Diamond diamond = new Diamond(cut, args); + diamond_ = address(diamond); + vm.stopBroadcast(); } } diff --git a/test/helpers/DeployDiamondHelper.sol b/test/helpers/DeployDiamondHelper.sol deleted file mode 100644 index 7e9ffb8..0000000 --- a/test/helpers/DeployDiamondHelper.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {Diamond} from "@diamond/Diamond.sol"; -import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol"; -import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol"; -import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.sol"; -import {ERC165Init} from "@diamond/initializers/ERC165Init.sol"; -import {FacetCutAction, FacetCut, DiamondArgs} from "@diamond-storage/DiamondStorage.sol"; -import {HelperContract} from "@diamond-test/helpers/HelperContract.sol"; - -abstract contract DeployDiamondHelper is HelperContract { - function _deployDiamond(address _owner) internal returns (address payable diamond_) { - // Deploy core facet contracts - DiamondCutFacet diamondCutFacet = new DiamondCutFacet(); - DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet(); - OwnableRolesFacet ownableRolesFacet = new OwnableRolesFacet(); - - // Deploy ERC165 initializer contract - ERC165Init erc165Init = new ERC165Init(); - - // Prepare DiamondArgs: owner and init data - DiamondArgs memory args = - DiamondArgs({owner: _owner, init: address(erc165Init), initData: abi.encodeWithSignature("initErc165()")}); - - // Create an array of FacetCut entries for standard facets - FacetCut[] memory cut = new FacetCut[](3); - - // Add DiamondCutFacet to the cut list - cut[0] = FacetCut({ - facetAddress: address(diamondCutFacet), - action: FacetCutAction.Add, - functionSelectors: _generateSelectors("DiamondCutFacet") - }); - - // Add DiamondLoupeFacet to the cut list - cut[1] = FacetCut({ - facetAddress: address(diamondLoupeFacet), - action: FacetCutAction.Add, - functionSelectors: _generateSelectors("DiamondLoupeFacet") - }); - - // Add OwnableRolesFacet to the cut list - cut[2] = FacetCut({ - facetAddress: address(ownableRolesFacet), - action: FacetCutAction.Add, - functionSelectors: _generateSelectors("OwnableRolesFacet") - }); - - // Deploy the Diamond contract with the facets and initialization args - Diamond diamond = new Diamond(cut, args); - diamond_ = payable(address(diamond)); - } -} From 46cd27d2494fffa76941f86c7e49be1b54e9aff1 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:41:57 +0100 Subject: [PATCH 7/9] chore(refactor): move test states to states folder --- .../DeployedDiamondState.sol} | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) rename test/{helpers/TestStates.sol => states/DeployedDiamondState.sol} (74%) diff --git a/test/helpers/TestStates.sol b/test/states/DeployedDiamondState.sol similarity index 74% rename from test/helpers/TestStates.sol rename to test/states/DeployedDiamondState.sol index 95a96bd..0d46b28 100644 --- a/test/helpers/TestStates.sol +++ b/test/states/DeployedDiamondState.sol @@ -1,14 +1,16 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; -import {DeployDiamondHelper} from "@diamond-test/helpers/DeployDiamondHelper.sol"; +import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol"; +import {DeployDiamond} from "@diamond-script/DeployDiamond.s.sol"; import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol"; import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol"; import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.sol"; /// @notice Provides shared state for tests involving a freshly deployed Diamond contract. /// @dev Sets up references to deployed facets, interfaces, and the diamond itself for testing. -abstract contract DeployedDiamondState is DeployDiamondHelper { +abstract contract DeployedDiamondState is GetSelectors { + DeployDiamond deployDiamond; /// @notice Instance of the deployed Diamond contract. address public diamond; @@ -27,17 +29,17 @@ abstract contract DeployedDiamondState is DeployDiamondHelper { /// @notice List of facet contract names used in deployment. string[3] public facetNames = ["DiamondCutFacet", "DiamondLoupeFacet", "OwnableRolesFacet"]; - address public diamondOwner = makeAddr("Owner"); + // address public diamondOwner = makeAddr("Owner"); /// @notice Deploys the Diamond contract and initializes interface references and facet addresses. /// @dev This function is intended to be called in a test setup phase (e.g., `setUp()` in Foundry). function setUp() public { - vm.startPrank(diamondOwner); - diamond = _deployDiamond(diamondOwner); + deployDiamond = new DeployDiamond(); + diamond = deployDiamond.run(); - diamondCut = DiamondCutFacet(address(diamond)); - diamondLoupe = DiamondLoupeFacet(address(diamond)); - ownableRoles = OwnableRolesFacet(address(diamond)); + diamondCut = DiamondCutFacet(diamond); + diamondLoupe = DiamondLoupeFacet(diamond); + ownableRoles = OwnableRolesFacet(diamond); facetAddresses = diamondLoupe.facetAddresses(); } From 3bb512b92146d926d3381bb387b3b1d8d78a8371 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:42:32 +0100 Subject: [PATCH 8/9] chore: update test --- test/DiamondTester.t.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/DiamondTester.t.sol b/test/DiamondTester.t.sol index 7a26683..ff852f6 100644 --- a/test/DiamondTester.t.sol +++ b/test/DiamondTester.t.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.4; import {Facet} from "@diamond-storage/DiamondStorage.sol"; -import {DeployedDiamondState} from "@diamond-test/helpers/TestStates.sol"; +import {DeployedDiamondState} from "@diamond-test/states/DeployedDiamondState.sol"; +import {Utils} from "@diamond-test/helpers/Utils.sol"; /// @title DiamondTester /// @notice Contains test cases to validate the deployment and structure of the Diamond contract. @@ -15,7 +16,7 @@ contract DiamondTester is DeployedDiamondState { /// @notice Verifies the diamond owner is set correctly. function testDiamondOwner() public view { - assertEq(ownableRoles.owner(), diamondOwner); + assertEq(ownableRoles.owner(), address(this)); } /// @notice Checks that the standard facets are deployed and have valid addresses. @@ -31,7 +32,7 @@ contract DiamondTester is DeployedDiamondState { /// @dev Compares generated selectors with those registered in the diamond via facetAddress(). function testSelectorsAreComplete() public { for (uint256 i; i < facetAddresses.length; ++i) { - bytes4[] memory fromGenSelectors = _generateSelectors(facetNames[i]); + bytes4[] memory fromGenSelectors = _getSelectors(facetNames[i]); for (uint256 j; j < fromGenSelectors.length; ++j) { assertEq(facetAddresses[i], diamondLoupe.facetAddress(fromGenSelectors[j])); } @@ -40,7 +41,7 @@ contract DiamondTester is DeployedDiamondState { /// @notice Asserts that all function selectors across all facets are unique. function testSelectorsAreUnique() public view { - bytes4[] memory allSelectors = getAllSelectors(address(diamond)); + bytes4[] memory allSelectors = Utils.getAllSelectors(address(diamond)); for (uint256 i; i < allSelectors.length; ++i) { for (uint256 j = i + 1; j < allSelectors.length; ++j) { assertNotEq(allSelectors[i], allSelectors[j]); From eeee1e61eb2f117662e1912b461f3902155139f9 Mon Sep 17 00:00:00 2001 From: David Dada Date: Mon, 1 Sep 2025 13:43:32 +0100 Subject: [PATCH 9/9] chore: update gas snapshot --- .gas-snapshot | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index d88bd13..3fce107 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,11 +1,11 @@ -DiamondTester:testDiamondDeployed() (gas: 5289) -DiamondTester:testDiamondOwner() (gas: 17516) -DiamondTester:testFacetAddressToSelectorsMappingIsCorrect() (gas: 140449) -DiamondTester:testSelectorToFacetMappingIsCorrect() (gas: 138419) -DiamondTester:testSelectorsAreComplete() (gas: 144433) -DiamondTester:testSelectorsAreUnique() (gas: 183338) -DiamondTester:testStandardFacetsDeployed() (gas: 13886) -DiamondTester:testSupportsERC165() (gas: 15452) -DiamondTester:testSupportsERC173() (gas: 15497) +DiamondTester:testDiamondDeployed() (gas: 5278) +DiamondTester:testDiamondOwner() (gas: 15392) +DiamondTester:testFacetAddressToSelectorsMappingIsCorrect() (gas: 139943) +DiamondTester:testSelectorToFacetMappingIsCorrect() (gas: 137976) +DiamondTester:testSelectorsAreComplete() (gas: 144581) +DiamondTester:testSelectorsAreUnique() (gas: 191268) +DiamondTester:testStandardFacetsDeployed() (gas: 13908) +DiamondTester:testSupportsERC165() (gas: 15496) +DiamondTester:testSupportsERC173() (gas: 15475) DiamondTester:testSupportsIDiamondCut() (gas: 15465) -DiamondTester:testSupportsIDiamondLoupe() (gas: 15474) \ No newline at end of file +DiamondTester:testSupportsIDiamondLoupe() (gas: 15496) \ No newline at end of file