From d0884d931523cbf5e4e058e2d30b86d32a582e59 Mon Sep 17 00:00:00 2001 From: AnInsaneJimJam Date: Tue, 9 Dec 2025 00:08:31 +0530 Subject: [PATCH 1/4] Added file for benchmarking gas --- test/benchmark/DiamondDeploymentMethods.t.sol | 362 ++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 test/benchmark/DiamondDeploymentMethods.t.sol diff --git a/test/benchmark/DiamondDeploymentMethods.t.sol b/test/benchmark/DiamondDeploymentMethods.t.sol new file mode 100644 index 00000000..8e2a2554 --- /dev/null +++ b/test/benchmark/DiamondDeploymentMethods.t.sol @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +import {Test} from "forge-std/Test.sol"; +import {LibDiamond} from "../../src/diamond/LibDiamond.sol"; + +/*////////////////////////////////////////////////////////////// + DIAMOND IMPLEMENTATIONS +//////////////////////////////////////////////////////////////*/ + +// Constructor approach - uses FacetCut[] memory in constructor +contract ConstructorDiamond { + error FunctionNotFound(bytes4 selector); + error NoSelectorsProvidedForFacet(address _facet); + error NoBytecodeAtAddress(address _contractAddress, string _message); + error InitializationFunctionReverted(address _initializationContractAddress, bytes _calldata); + error IncorrectFacetCutAction(uint8 _action); + + event DiamondCut(LibDiamond.FacetCut[] _diamondCut, address _init, bytes _calldata); + + constructor(LibDiamond.FacetCut[] memory _facets, address _init, bytes memory _initCalldata) payable { + _diamondCutMemory(_facets, _init, _initCalldata); + } + + function _diamondCutMemory(LibDiamond.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata) + internal + { + for (uint256 facetIndex; facetIndex < _diamondCut.length; facetIndex++) { + bytes4[] memory functionSelectors = _diamondCut[facetIndex].functionSelectors; + address facetAddress = _diamondCut[facetIndex].facetAddress; + if (functionSelectors.length == 0) { + revert NoSelectorsProvidedForFacet(facetAddress); + } + LibDiamond.FacetCutAction action = _diamondCut[facetIndex].action; + if (action == LibDiamond.FacetCutAction.Add) { + _addFunctionsMemory(facetAddress, functionSelectors); + } else if (action == LibDiamond.FacetCutAction.Replace) { + _replaceFunctionsMemory(facetAddress, functionSelectors); + } else if (action == LibDiamond.FacetCutAction.Remove) { + _removeFunctionsMemory(facetAddress, functionSelectors); + } else { + revert IncorrectFacetCutAction(uint8(action)); + } + } + emit DiamondCut(_diamondCut, _init, _calldata); + + if (_init == address(0)) { + return; + } + if (_init.code.length == 0) { + revert NoBytecodeAtAddress(_init, "ConstructorDiamond: _init address no code"); + } + (bool success, bytes memory error) = _init.delegatecall(_calldata); + if (!success) { + if (error.length > 0) { + assembly ("memory-safe") { + revert(add(error, 0x20), mload(error)) + } + } else { + revert InitializationFunctionReverted(_init, _calldata); + } + } + } + + function _addFunctionsMemory(address _facet, bytes4[] memory _functionSelectors) internal { + LibDiamond.DiamondStorage storage s = LibDiamond.getStorage(); + if (_facet.code.length == 0) { + revert NoBytecodeAtAddress(_facet, "ConstructorDiamond: Add facet has no code"); + } + uint16 selectorPosition = uint16(s.selectors.length); + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacet = s.facetAndPosition[selector].facet; + if (oldFacet != address(0)) { + revert LibDiamond.CannotAddFunctionToDiamondThatAlreadyExists(selector); + } + s.facetAndPosition[selector] = LibDiamond.FacetAndPosition(_facet, selectorPosition); + s.selectors.push(selector); + selectorPosition++; + } + } + + function _replaceFunctionsMemory(address _facet, bytes4[] memory _functionSelectors) internal { + LibDiamond.DiamondStorage storage s = LibDiamond.getStorage(); + if (_facet.code.length == 0) { + revert NoBytecodeAtAddress(_facet, "ConstructorDiamond: Replace facet has no code"); + } + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + address oldFacet = s.facetAndPosition[selector].facet; + if (oldFacet == address(this)) { + revert LibDiamond.CannotReplaceImmutableFunction(selector); + } + if (oldFacet == _facet) { + revert LibDiamond.CannotReplaceFunctionWithTheSameFunctionFromTheSameFacet(selector); + } + if (oldFacet == address(0)) { + revert LibDiamond.CannotReplaceFunctionThatDoesNotExists(selector); + } + s.facetAndPosition[selector].facet = _facet; + } + } + + function _removeFunctionsMemory(address _facet, bytes4[] memory _functionSelectors) internal { + LibDiamond.DiamondStorage storage s = LibDiamond.getStorage(); + uint256 selectorCount = s.selectors.length; + if (_facet != address(0)) { + revert LibDiamond.RemoveFacetAddressMustBeZeroAddress(_facet); + } + for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) { + bytes4 selector = _functionSelectors[selectorIndex]; + LibDiamond.FacetAndPosition memory oldFacetAndPosition = s.facetAndPosition[selector]; + if (oldFacetAndPosition.facet == address(0)) { + revert LibDiamond.CannotRemoveFunctionThatDoesNotExist(selector); + } + if (oldFacetAndPosition.facet == address(this)) { + revert LibDiamond.CannotRemoveImmutableFunction(selector); + } + selectorCount--; + if (oldFacetAndPosition.position != selectorCount) { + bytes4 lastSelector = s.selectors[selectorCount]; + s.selectors[oldFacetAndPosition.position] = lastSelector; + s.facetAndPosition[lastSelector].position = oldFacetAndPosition.position; + } + s.selectors.pop(); + delete s.facetAndPosition[selector]; + } + } + + fallback() external payable { + LibDiamond.DiamondStorage storage s = LibDiamond.getStorage(); + address facet = s.facetAndPosition[msg.sig].facet; + if (facet == address(0)) revert FunctionNotFound(msg.sig); + + assembly { + calldatacopy(0, 0, calldatasize()) + let ok := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch ok + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + receive() external payable {} +} + +// Initialize approach - uses FacetCut[] calldata in external function +contract InitializeDiamond { + error FunctionNotFound(bytes4 selector); + error AlreadyInitialized(); + + bool private _initialized; + + constructor() payable {} + + function initialize(LibDiamond.FacetCut[] calldata _facets, address _init, bytes calldata _initCalldata) + external + payable + { + if (_initialized) revert AlreadyInitialized(); + _initialized = true; + LibDiamond.diamondCut(_facets, _init, _initCalldata); + } + + fallback() external payable { + LibDiamond.DiamondStorage storage s = LibDiamond.getStorage(); + address facet = s.facetAndPosition[msg.sig].facet; + if (facet == address(0)) revert FunctionNotFound(msg.sig); + + assembly { + calldatacopy(0, 0, calldatasize()) + let ok := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch ok + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + receive() external payable {} +} + +// Mock facet with 12 functions for testing +contract MockFacet { + function function01() external pure returns (uint256) { + return 1; + } + + function function02() external pure returns (uint256) { + return 2; + } + + function function03() external pure returns (uint256) { + return 3; + } + + function function04() external pure returns (uint256) { + return 4; + } + + function function05() external pure returns (uint256) { + return 5; + } + + function function06() external pure returns (uint256) { + return 6; + } + + function function07() external pure returns (uint256) { + return 7; + } + + function function08() external pure returns (uint256) { + return 8; + } + + function function09() external pure returns (uint256) { + return 9; + } + + function function10() external pure returns (uint256) { + return 10; + } + + function function11() external pure returns (uint256) { + return 11; + } + + function function12() external pure returns (uint256) { + return 12; + } +} + +/*////////////////////////////////////////////////////////////// + TEST CONTRACT +//////////////////////////////////////////////////////////////*/ + +// Gas benchmark comparing constructor vs initialize approaches for diamond deployment +// Tests scenarios: 10, 50, and 100 facets with 12 selectors each +contract DiamondDeploymentGasBenchmarkTest is Test { + uint256 constant SELECTORS_PER_FACET = 12; + + /*////////////////////////////////////////////////////////////// + HELPER FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _deployFacets(uint256 count) internal returns (address[] memory facets) { + facets = new address[](count); + for (uint256 i = 0; i < count; i++) { + facets[i] = address(new MockFacet()); + } + } + + function _generateSelectorsForFacet(uint256 facetIndex) internal pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](SELECTORS_PER_FACET); + for (uint256 i = 0; i < SELECTORS_PER_FACET; i++) { + selectors[i] = bytes4(keccak256(abi.encodePacked("facet", facetIndex, "func", i))); + } + } + + function _buildFacetCuts(address[] memory facets) internal pure returns (LibDiamond.FacetCut[] memory cuts) { + cuts = new LibDiamond.FacetCut[](facets.length); + for (uint256 i = 0; i < facets.length; i++) { + bytes4[] memory selectors = _generateSelectorsForFacet(i); + cuts[i] = LibDiamond.FacetCut({ + facetAddress: facets[i], action: LibDiamond.FacetCutAction.Add, functionSelectors: selectors + }); + } + } + + /*////////////////////////////////////////////////////////////// + GAS BENCHMARKS + //////////////////////////////////////////////////////////////*/ + + // Estimated gas: 3_373_293 + function testGas_Constructor_10Facets() external { + address[] memory facets = _deployFacets(10); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); + emit log_uint(startGas - gasleft()); + + assertTrue(address(diamond) != address(0)); + } + + // Estimated gas: 16_424_695 + function testGas_Constructor_50Facets() external { + address[] memory facets = _deployFacets(50); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); + emit log_uint(startGas - gasleft()); + + assertTrue(address(diamond) != address(0)); + } + + // Estimated gas: 32_808_971 + function testGas_Constructor_100Facets() external { + address[] memory facets = _deployFacets(100); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); + emit log_uint(startGas - gasleft()); + + assertTrue(address(diamond) != address(0)); + } + + // Estimated gas: 4_434_331 (deploy: 1_136_494 + init: 3_297_837) + function testGas_Initialize_10Facets() external { + address[] memory facets = _deployFacets(10); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + InitializeDiamond diamond = new InitializeDiamond(); + uint256 deployGas = startGas - gasleft(); + + startGas = gasleft(); + diamond.initialize(cuts, address(0), ""); + uint256 initGas = startGas - gasleft(); + + emit log_uint(deployGas + initGas); + assertTrue(address(diamond) != address(0)); + } + + // Estimated gas: 17_453_569 (deploy: 1_138_126 + init: 16_315_443) + function testGas_Initialize_50Facets() external { + address[] memory facets = _deployFacets(50); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + InitializeDiamond diamond = new InitializeDiamond(); + uint256 deployGas = startGas - gasleft(); + + startGas = gasleft(); + diamond.initialize(cuts, address(0), ""); + uint256 initGas = startGas - gasleft(); + + emit log_uint(deployGas + initGas); + assertTrue(address(diamond) != address(0)); + } + + // Estimated gas: 33_762_974 (deploy: 1_140_166 + init: 32_622_808) + function testGas_Initialize_100Facets() external { + address[] memory facets = _deployFacets(100); + LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); + + uint256 startGas = gasleft(); + InitializeDiamond diamond = new InitializeDiamond(); + uint256 deployGas = startGas - gasleft(); + + startGas = gasleft(); + diamond.initialize(cuts, address(0), ""); + uint256 initGas = startGas - gasleft(); + + emit log_uint(deployGas + initGas); + assertTrue(address(diamond) != address(0)); + } +} From d78c160ddc5a6f87c607d5c625b7e88bf721986d Mon Sep 17 00:00:00 2001 From: AnInsaneJimJam Date: Tue, 9 Dec 2025 00:25:03 +0530 Subject: [PATCH 2/4] Fixed the order and better logging --- test/benchmark/DiamondDeploymentMethods.t.sol | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/test/benchmark/DiamondDeploymentMethods.t.sol b/test/benchmark/DiamondDeploymentMethods.t.sol index 8e2a2554..61959189 100644 --- a/test/benchmark/DiamondDeploymentMethods.t.sol +++ b/test/benchmark/DiamondDeploymentMethods.t.sol @@ -273,44 +273,46 @@ contract DiamondDeploymentGasBenchmarkTest is Test { GAS BENCHMARKS //////////////////////////////////////////////////////////////*/ - // Estimated gas: 3_373_293 - function testGas_Constructor_10Facets() external { + function testGas_Constructor_010Facets() external { address[] memory facets = _deployFacets(10); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); uint256 startGas = gasleft(); ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); - emit log_uint(startGas - gasleft()); + uint256 gasUsed = startGas - gasleft(); + + emit log_named_uint("Constructor (10 facets, 120 selectors) - Total Gas", gasUsed); assertTrue(address(diamond) != address(0)); } - // Estimated gas: 16_424_695 - function testGas_Constructor_50Facets() external { + function testGas_Constructor_050Facets() external { address[] memory facets = _deployFacets(50); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); uint256 startGas = gasleft(); ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); - emit log_uint(startGas - gasleft()); + uint256 gasUsed = startGas - gasleft(); + + emit log_named_uint("Constructor (50 facets, 600 selectors) - Total Gas", gasUsed); assertTrue(address(diamond) != address(0)); } - // Estimated gas: 32_808_971 function testGas_Constructor_100Facets() external { address[] memory facets = _deployFacets(100); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); uint256 startGas = gasleft(); ConstructorDiamond diamond = new ConstructorDiamond(cuts, address(0), ""); - emit log_uint(startGas - gasleft()); + uint256 gasUsed = startGas - gasleft(); + + emit log_named_uint("Constructor (100 facets, 1200 selectors) - Total Gas", gasUsed); assertTrue(address(diamond) != address(0)); } - // Estimated gas: 4_434_331 (deploy: 1_136_494 + init: 3_297_837) - function testGas_Initialize_10Facets() external { + function testGas_Initialize_010Facets() external { address[] memory facets = _deployFacets(10); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); @@ -322,12 +324,16 @@ contract DiamondDeploymentGasBenchmarkTest is Test { diamond.initialize(cuts, address(0), ""); uint256 initGas = startGas - gasleft(); - emit log_uint(deployGas + initGas); + uint256 totalGas = deployGas + initGas; + + emit log_named_uint("Initialize (10 facets, 120 selectors) - Deploy Gas", deployGas); + emit log_named_uint("Initialize (10 facets, 120 selectors) - Init Gas", initGas); + emit log_named_uint("Initialize (10 facets, 120 selectors) - Total Gas", totalGas); + assertTrue(address(diamond) != address(0)); } - // Estimated gas: 17_453_569 (deploy: 1_138_126 + init: 16_315_443) - function testGas_Initialize_50Facets() external { + function testGas_Initialize_050Facets() external { address[] memory facets = _deployFacets(50); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); @@ -339,11 +345,15 @@ contract DiamondDeploymentGasBenchmarkTest is Test { diamond.initialize(cuts, address(0), ""); uint256 initGas = startGas - gasleft(); - emit log_uint(deployGas + initGas); + uint256 totalGas = deployGas + initGas; + + emit log_named_uint("Initialize (50 facets, 600 selectors) - Deploy Gas", deployGas); + emit log_named_uint("Initialize (50 facets, 600 selectors) - Init Gas", initGas); + emit log_named_uint("Initialize (50 facets, 600 selectors) - Total Gas", totalGas); + assertTrue(address(diamond) != address(0)); } - // Estimated gas: 33_762_974 (deploy: 1_140_166 + init: 32_622_808) function testGas_Initialize_100Facets() external { address[] memory facets = _deployFacets(100); LibDiamond.FacetCut[] memory cuts = _buildFacetCuts(facets); @@ -356,7 +366,12 @@ contract DiamondDeploymentGasBenchmarkTest is Test { diamond.initialize(cuts, address(0), ""); uint256 initGas = startGas - gasleft(); - emit log_uint(deployGas + initGas); + uint256 totalGas = deployGas + initGas; + + emit log_named_uint("Initialize (100 facets, 1200 selectors) - Deploy Gas", deployGas); + emit log_named_uint("Initialize (100 facets, 1200 selectors) - Init Gas", initGas); + emit log_named_uint("Initialize (100 facets, 1200 selectors) - Total Gas", totalGas); + assertTrue(address(diamond) != address(0)); } } From 0bcf9683094b7e6fda7567081ad7254732132e19 Mon Sep 17 00:00:00 2001 From: AnInsaneJimJam Date: Tue, 9 Dec 2025 00:49:31 +0530 Subject: [PATCH 3/4] Fixed Comments --- test/benchmark/DiamondDeploymentMethods.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/benchmark/DiamondDeploymentMethods.t.sol b/test/benchmark/DiamondDeploymentMethods.t.sol index 61959189..3464896f 100644 --- a/test/benchmark/DiamondDeploymentMethods.t.sol +++ b/test/benchmark/DiamondDeploymentMethods.t.sol @@ -8,7 +8,7 @@ import {LibDiamond} from "../../src/diamond/LibDiamond.sol"; DIAMOND IMPLEMENTATIONS //////////////////////////////////////////////////////////////*/ -// Constructor approach - uses FacetCut[] memory in constructor +/* Constructor approach - uses FacetCut[] memory in constructor */ contract ConstructorDiamond { error FunctionNotFound(bytes4 selector); error NoSelectorsProvidedForFacet(address _facet); @@ -145,7 +145,7 @@ contract ConstructorDiamond { receive() external payable {} } -// Initialize approach - uses FacetCut[] calldata in external function +/* Initialize approach - uses FacetCut[] calldata in external function */ contract InitializeDiamond { error FunctionNotFound(bytes4 selector); error AlreadyInitialized(); @@ -181,7 +181,7 @@ contract InitializeDiamond { receive() external payable {} } -// Mock facet with 12 functions for testing +/* Mock facet with 12 functions for testing */ contract MockFacet { function function01() external pure returns (uint256) { return 1; @@ -236,8 +236,8 @@ contract MockFacet { TEST CONTRACT //////////////////////////////////////////////////////////////*/ -// Gas benchmark comparing constructor vs initialize approaches for diamond deployment -// Tests scenarios: 10, 50, and 100 facets with 12 selectors each +/** Gas benchmark comparing constructor vs initialize approaches for diamond deployment + Tests scenarios: 10, 50, and 100 facets with 12 selectors each */ contract DiamondDeploymentGasBenchmarkTest is Test { uint256 constant SELECTORS_PER_FACET = 12; From 3ce9ed896f5e8679efb7fe47a7db85d5a9911eb9 Mon Sep 17 00:00:00 2001 From: AnInsaneJimJam Date: Tue, 9 Dec 2025 00:50:37 +0530 Subject: [PATCH 4/4] Formatted --- test/benchmark/DiamondDeploymentMethods.t.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/benchmark/DiamondDeploymentMethods.t.sol b/test/benchmark/DiamondDeploymentMethods.t.sol index 3464896f..e384d29f 100644 --- a/test/benchmark/DiamondDeploymentMethods.t.sol +++ b/test/benchmark/DiamondDeploymentMethods.t.sol @@ -236,8 +236,10 @@ contract MockFacet { TEST CONTRACT //////////////////////////////////////////////////////////////*/ -/** Gas benchmark comparing constructor vs initialize approaches for diamond deployment - Tests scenarios: 10, 50, and 100 facets with 12 selectors each */ +/** + * Gas benchmark comparing constructor vs initialize approaches for diamond deployment + * Tests scenarios: 10, 50, and 100 facets with 12 selectors each + */ contract DiamondDeploymentGasBenchmarkTest is Test { uint256 constant SELECTORS_PER_FACET = 12;