Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/token/ERC1155/ERC1155Facet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -257,9 +257,8 @@ contract ERC1155Facet {
emit TransferSingle(msg.sender, _from, _to, _id, _value);

if (_to.code.length > 0) {
try IERC1155Receiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) returns (
bytes4 response
) {
try IERC1155Receiver(_to).onERC1155Received(msg.sender, _from, _id, _value, _data) returns (bytes4 response)
{
if (response != IERC1155Receiver.onERC1155Received.selector) {
revert ERC1155InvalidReceiver(_to);
}
Expand Down
42 changes: 42 additions & 0 deletions test/benchmark/Base.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {Utils} from "./Utils.sol";

import {MinimalDiamond} from "./MinimalDiamond.sol";
import {LibDiamond} from "../../src/diamond/LibDiamond.sol";
import {DiamondLoupeFacet} from "../../src/diamond/DiamondLoupeFacet.sol";

abstract contract BaseBenchmark is Utils {
MinimalDiamond internal diamond;
address internal loupe;

function setUp() public {
diamond = new MinimalDiamond();
loupe = _deployLoupe();

// Initialize minimal diamond with DiamondLoupeFacet address and selectors.
bytes4[] memory loupeSelectors = new bytes4[](NUM_LOUPE_SELECTORS);
loupeSelectors[0] = SELECTOR_FACETS;
loupeSelectors[1] = SELECTOR_FACET_FUNCTION_SELECTORS;
loupeSelectors[2] = SELECTOR_FACET_ADDRESSES;
loupeSelectors[3] = SELECTOR_FACET_ADDRESS;

LibDiamond.FacetCut[] memory dc = new LibDiamond.FacetCut[](1);

dc[0] = LibDiamond.FacetCut({
facetAddress: loupe,
action: LibDiamond.FacetCutAction.Add,
functionSelectors: loupeSelectors
});

MinimalDiamond.DiamondArgs memory args = MinimalDiamond.DiamondArgs({init: address(0), initCalldata: ""});

diamond.initialize(dc, args);

// Initiatlise complex storage for minimal diamond
_buildDiamond(address(diamond), NUM_FACETS, SELECTORS_PER_FACET);
}

function _deployLoupe() internal virtual returns (address);
}
58 changes: 58 additions & 0 deletions test/benchmark/Benchmark.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {BaseBenchmark} from "./Base.t.sol";
import {DiamondLoupeFacet} from "../../src/diamond/DiamondLoupeFacet.sol";

contract LoupeGasBenchmarkTest is BaseBenchmark {
/*//////////////////////////////////////////////////////////////
OVERRIDES
//////////////////////////////////////////////////////////////*/

function _deployLoupe() internal override returns (address) {
return address(new DiamondLoupeFacet());
}

/*//////////////////////////////////////////////////////////////
GAS BENCHMARKS
//////////////////////////////////////////////////////////////*/

// Estimated gas: 370_049_638
function testGas_Loupe_Facets() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) = address(diamond).call(abi.encodeWithSelector(SELECTOR_FACETS));
emit log_uint(startGas - gasleft());

DiamondLoupeFacet.Facet[] memory allFacets = abi.decode(data, (DiamondLoupeFacet.Facet[]));
assertEq(allFacets.length, NUM_FACETS + 1); // plus Loupe
}

// Estimated gas: 5_889_500
function testGas_Loupe_FacetFunctionSelectors() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) =
address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_FUNCTION_SELECTORS, loupe));
emit log_uint(startGas - gasleft());

bytes4[] memory facetSelectors = abi.decode(data, (bytes4[]));
assertEq(facetSelectors.length, NUM_LOUPE_SELECTORS);
}

// Estimated gas: 31_606_629
function testGas_Loupe_FacetAddresses() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) = address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_ADDRESSES));
emit log_uint(startGas - gasleft());

address[] memory allFacets = abi.decode(data, (address[]));
assertEq(allFacets.length, NUM_FACETS + 1); // plus Loupe
}

// Estimated gas: 12_672
function testGas_Loupe_FacetAddress() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) =
address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_ADDRESS, SELECTOR_FACET_ADDRESSES));
emit log_uint(startGas - gasleft());
}
}
36 changes: 36 additions & 0 deletions test/benchmark/MinimalDiamond.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {LibDiamond} from "../../src/diamond/LibDiamond.sol";

// Adapted from: https://github.com/mudgen/diamond-1-hardhat/blob/main/contracts/Diamond.sol

contract MinimalDiamond {
error FunctionNotFound(bytes4 selector);

struct DiamondArgs {
address init;
bytes initCalldata;
}

function initialize(LibDiamond.FacetCut[] calldata _diamondCut, DiamondArgs calldata _args) public payable {
LibDiamond.diamondCut(_diamondCut, _args.init, _args.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 {}
}
61 changes: 61 additions & 0 deletions test/benchmark/Optimised.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {BaseBenchmark} from "./Base.t.sol";
import {DiamondLoupeFacet} from "../../src/diamond/DiamondLoupeFacet.sol";

/// @dev Please override `_deployLoupe` with the deployment of your optimised contract.
contract OptimisedLoupeBenchmarkTest is BaseBenchmark {
/*//////////////////////////////////////////////////////////////
OVERRIDES
//////////////////////////////////////////////////////////////*/

/// @dev Override with a deployment of your optimised diamond loupe contract.
function _deployLoupe() internal override returns (address) {
return address(new DiamondLoupeFacet());
}

/*//////////////////////////////////////////////////////////////
GAS BENCHMARKS
//////////////////////////////////////////////////////////////*/

struct Facet {
address facet;
bytes4[] functionSelectors;
}

function testGas_Loupe_Facets() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) = address(diamond).call(abi.encodeWithSelector(SELECTOR_FACETS));
emit log_uint(startGas - gasleft());

Facet[] memory allFacets = abi.decode(data, (Facet[]));
assertEq(allFacets.length, NUM_FACETS + 1); // plus Loupe
}

function testGas_Loupe_FacetFunctionSelectors() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) =
address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_FUNCTION_SELECTORS, loupe));
emit log_uint(startGas - gasleft());

bytes4[] memory facetSelectors = abi.decode(data, (bytes4[]));
assertEq(facetSelectors.length, NUM_LOUPE_SELECTORS);
}

function testGas_Loupe_FacetAddresses() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) = address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_ADDRESSES));
emit log_uint(startGas - gasleft());

address[] memory allFacets = abi.decode(data, (address[]));
assertEq(allFacets.length, NUM_FACETS + 1); // plus Loupe
}

function testGas_Loupe_FacetAddress() external {
uint256 startGas = gasleft();
(bool success, bytes memory data) =
address(diamond).call(abi.encodeWithSelector(SELECTOR_FACET_ADDRESS, SELECTOR_FACET_ADDRESSES));
emit log_uint(startGas - gasleft());
}
}
90 changes: 90 additions & 0 deletions test/benchmark/Utils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {Test} from "forge-std/Test.sol";

contract Utils is Test {
bytes32 internal constant DIAMOND_STORAGE_POSITION = keccak256("compose.diamond");
bytes4 constant SELECTOR_FACETS = bytes4(keccak256("facets()"));
bytes4 constant SELECTOR_FACET_FUNCTION_SELECTORS = bytes4(keccak256("facetFunctionSelectors(address)"));
bytes4 constant SELECTOR_FACET_ADDRESSES = bytes4(keccak256("facetAddresses()"));
bytes4 constant SELECTOR_FACET_ADDRESS = bytes4(keccak256("facetAddress(bytes4)"));

uint256 internal constant NUM_FACETS = 200;
uint256 internal constant SELECTORS_PER_FACET = 10;
uint256 internal constant NUM_LOUPE_SELECTORS = 4;
uint256 internal constant TOTAL_SELECTORS = NUM_FACETS * SELECTORS_PER_FACET + NUM_LOUPE_SELECTORS;

/*//////////////////////////////////////////////////////////////
STORAGE HELPERS
//////////////////////////////////////////////////////////////*/

function _facetAndPositionsSlot(bytes4 selector) internal pure returns (bytes32) {
return keccak256(abi.encode(selector, DIAMOND_STORAGE_POSITION));
}

function _packFacetAndPosition(address facet, uint16 position) internal pure returns (bytes32) {
return bytes32((uint256(uint160(facet))) | (uint256(position) << 160));
}

function _storeFacetAndPosition(address account, bytes4 selector, address facet, uint16 position) internal {
vm.store(account, _facetAndPositionsSlot(selector), _packFacetAndPosition(facet, position));
}

function _selectorsLengthSlot() internal pure returns (bytes32) {
return bytes32(uint256(DIAMOND_STORAGE_POSITION) + 1);
}

function _selectorsDataBase() internal pure returns (bytes32) {
return keccak256(abi.encode(uint256(DIAMOND_STORAGE_POSITION) + 1));
}

function _storeSelectorAtIndex(address account, bytes4 selector, uint256 index) internal {
bytes32 base = _selectorsDataBase();
uint256 packedWordIndex = index / 8;
uint256 laneIndex = index % 8;
bytes32 packedWordSlot = bytes32(uint256(base) + packedWordIndex);

bytes32 oldPackedWord = vm.load(account, packedWordSlot);

uint256 laneShiftBits = laneIndex * 32;
uint256 clearLaneMask = ~(uint256(0xffffffff) << laneShiftBits);
uint256 laneInsertBits = (uint256(uint32(selector)) << laneShiftBits);
uint256 newPackedWord = (uint256(oldPackedWord) & clearLaneMask) | laneInsertBits;

vm.store(account, packedWordSlot, bytes32(newPackedWord));
}

/*//////////////////////////////////////////////////////////////
DATA HELPERS
//////////////////////////////////////////////////////////////*/

function _buildDiamond(address account, uint256 nFacets, uint256 perFacet) internal {
uint256 total = nFacets * perFacet;
vm.store(account, _selectorsLengthSlot(), bytes32(total));

uint256 globalIndex = 4;
for (uint256 f = 0; f < nFacets; f++) {
address facet = _facetAddr(f);
for (uint16 j = 0; j < perFacet; j++) {
bytes4 selector = _selectorFor(f, j);

_storeSelectorAtIndex(account, selector, globalIndex);

_storeFacetAndPosition(account, selector, facet, j);

unchecked {
++globalIndex;
}
}
}
}

function _facetAddr(uint256 f) internal returns (address) {
return makeAddr(string.concat("facet ", vm.toString(f)));
}

function _selectorFor(uint256 f, uint16 j) internal pure returns (bytes4) {
return bytes4(keccak256(abi.encodePacked("fn_f_", vm.toString(f), "_idx_", vm.toString(j), "()")));
}
}
Loading