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
22 changes: 13 additions & 9 deletions script/DeployDiamond.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
import {MockDiamond} from "@diamond-test/mocks/MockDiamond.sol";
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
import {OwnableFacet} from "@diamond/facets/OwnableFacet.sol";
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
Expand All @@ -28,15 +28,15 @@ contract DeployDiamond is Script, GetSelectors {
// Deploy core facet contracts
DiamondCutFacet diamondCutFacet = new DiamondCutFacet();
DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet();
ERC165Facet erc165Facet = new ERC165Facet();
OwnableFacet ownableFacet = new OwnableFacet();

// Deploy initializer contracts
address multiInit = address(new MultiInit());
address ownableInit = address(new OwnableInit());
address erc165Init = address(new ERC165Init());

// Create an array of FacetCut entries for standard facets
FacetCut[] memory cut = new FacetCut[](3);
FacetCut[] memory cut = new FacetCut[](4);

// Add DiamondCutFacet to the cut list
cut[0] = FacetCut({
Expand All @@ -52,23 +52,27 @@ contract DeployDiamond is Script, GetSelectors {
functionSelectors: _getSelectors("DiamondLoupeFacet")
});

// Add OwnableFacet to the cut list
// Add ERC165Facet to the cut list
cut[2] = FacetCut({
facetAddress: address(erc165Facet),
action: FacetCutAction.Add,
functionSelectors: _getSelectors("ERC165Facet")
});

// Add OwnableFacet to the cut list
cut[3] = FacetCut({
facetAddress: address(ownableFacet),
action: FacetCutAction.Add,
functionSelectors: _getSelectors("OwnableFacet")
});

// Build MultiInit arrays for granular initialization
address[] memory initAddresses = new address[](2);
bytes[] memory initData = new bytes[](2);
address[] memory initAddresses = new address[](1);
bytes[] memory initData = new bytes[](1);

initAddresses[0] = ownableInit;
initData[0] = abi.encodeWithSignature("initOwner(address)", ContextLib.msgSender());

initAddresses[1] = erc165Init;
initData[1] = abi.encodeWithSignature("initERC165()");

// Deploy the Diamond contract with the facets and initialization args
MockDiamond diamond = new MockDiamond();
diamond.initialize(
Expand Down
33 changes: 9 additions & 24 deletions src/facets/DiamondLoupeFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,37 +27,22 @@ contract DiamondLoupeFacet is IDiamondLoupe {

/// @notice Gets all the function selectors provided by a facet.
/// @param _facet The facet address.
/// @return facetFunctionSelectors_
function facetFunctionSelectors(address _facet)
external
view
override
returns (bytes4[] memory facetFunctionSelectors_)
{
facetFunctionSelectors_ = DiamondLib.diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
/// @return The function selectors for the specified facet.
function facetFunctionSelectors(address _facet) external view override returns (bytes4[] memory) {
return DiamondLib.diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
}

/// @notice Get all the facet addresses used by a diamond.
/// @return facetAddresses_
function facetAddresses() external view override returns (address[] memory facetAddresses_) {
facetAddresses_ = DiamondLib.diamondStorage().facetAddresses;
/// @return Facet addresses.
function facetAddresses() external view override returns (address[] memory) {
return DiamondLib.diamondStorage().facetAddresses;
}

/// @notice Gets the facet that supports the given selector.
/// @dev If facet is not found return address(0).
/// @param _functionSelector The function selector.
/// @return facetAddress_ The facet address.
function facetAddress(bytes4 _functionSelector) external view override returns (address facetAddress_) {
facetAddress_ = DiamondLib.diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
}

/// @notice Query if a contract implements an interface
/// @param _interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
return DiamondLib.diamondStorage().supportedInterfaces[_interfaceId];
/// @return The address of the facet that supports the given selector.
function facetAddress(bytes4 _functionSelector) external view override returns (address) {
return DiamondLib.diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
}
}
20 changes: 20 additions & 0 deletions src/facets/ERC165Facet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC165Lib} from "@diamond/libraries/ERC165Lib.sol";

/// @title ERC165Facet
/// @notice Diamond facet that implements the ERC-165 standard interface detection
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
/// @dev Delegates to ERC165Lib which hardcodes support for ERC-165, ERC-173, IDiamondCut, and IDiamondLoupe
contract ERC165Facet {
/// @notice Query if a contract implements an interface
/// @param _interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `_interfaceId` and
/// `_interfaceId` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
return ERC165Lib.supportsInterface(_interfaceId);
}
}
25 changes: 0 additions & 25 deletions src/initializers/ERC165Init.sol

This file was deleted.

2 changes: 0 additions & 2 deletions src/libraries/DiamondLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ struct DiamondStorage {
mapping(address => FacetFunctionSelectorsAndPosition) facetToSelectorsAndPosition;
/// @notice Array of all facet addresses registered in the diamond
address[] facetAddresses;
/// @notice Tracks which interface IDs (ERC-165) are supported by the diamond
mapping(bytes4 => bool) supportedInterfaces;
}

/// @title DiamondLib
Expand Down
44 changes: 44 additions & 0 deletions src/libraries/ERC165Lib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title ERC165Lib
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
/// @notice Library for checking ERC165 interface support
library ERC165Lib {
/// @notice Query if a contract implements an interface
/// @param _interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return supported_ `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 _interfaceId) internal pure returns (bool supported_) {
assembly ("memory-safe") {
// ERC165 interface IDs are 4 bytes, so we can shift right by 224 bits to get the first byte
switch shr(224, _interfaceId)
/// @dev type(ERC165).interfaceId
case 0x01ffc9a7 {
supported_ := true
}
/// @dev type(IERC173).interfaceId
case 0x7f5828d0 {
supported_ := true
}
/// @dev type(IDiamondCut).interfaceId
case 0x1f931c1c {
supported_ := true
}
/// @dev type(IDiamondLoupe).interfaceId
case 0x48e2b093 {
supported_ := true
}
/// @dev Invalid interface ID (ERC165 specifies that 0xffffffff is not a valid interface ID and must return false)
case 0xffffffff {
supported_ := false
}
/// @dev Invalid interface ID
default {
supported_ := false
}
}
}
}
17 changes: 8 additions & 9 deletions test/DiamondCutTester.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {MockFacetB} from "@diamond-test/mocks/MockFacetB.sol";
import {MockRevertInit} from "@diamond-test/mocks/MockRevertInit.sol";
import {DeployedDiamondState} from "@diamond-test/states/DeployedDiamondState.sol";
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
import {
AddressAndCalldataLengthMismatch,
Expand Down Expand Up @@ -69,7 +68,7 @@ contract DiamondCutTester is DeployedDiamondState {
_addMockFacetA();

address[] memory addresses = diamondLoupe.facetAddresses();
assertEq(addresses.length, 4); // 3 core + MockFacetA
assertEq(addresses.length, 5); // 4 core + MockFacetA

bytes4[] memory selectors = diamondLoupe.facetFunctionSelectors(address(mockFacetA));
assertEq(selectors.length, 3);
Expand All @@ -86,7 +85,7 @@ contract DiamondCutTester is DeployedDiamondState {

diamondCut.diamondCut(cuts, address(0), "");

assertEq(diamondLoupe.facetAddresses().length, 5); // 3 core + A + B
assertEq(diamondLoupe.facetAddresses().length, 6); // 4 core + A + B
}

/// @notice Diamond cut emits the DiamondCut event — the fire of the forge
Expand Down Expand Up @@ -182,13 +181,13 @@ contract DiamondCutTester is DeployedDiamondState {
/// @notice Removing all selectors removes the facet entirely
function testCleaving_RemoveAllSelectorsRemovesFacet() public {
_addMockFacetA();
assertEq(diamondLoupe.facetAddresses().length, 4);
assertEq(diamondLoupe.facetAddresses().length, 5);

FacetCut[] memory cuts = new FacetCut[](1);
cuts[0] = FacetCut(address(0), FacetCutAction.Remove, _mockFacetASelectors());
diamondCut.diamondCut(cuts, address(0), "");

assertEq(diamondLoupe.facetAddresses().length, 3);
assertEq(diamondLoupe.facetAddresses().length, 4);
assertEq(diamondLoupe.facetFunctionSelectors(address(mockFacetA)).length, 0);
}

Expand All @@ -203,7 +202,7 @@ contract DiamondCutTester is DeployedDiamondState {
cuts[0] = FacetCut(address(0), FacetCutAction.Remove, selectors);
diamondCut.diamondCut(cuts, address(0), "");

assertEq(diamondLoupe.facetAddresses().length, 4); // Facet still present
assertEq(diamondLoupe.facetAddresses().length, 5); // Facet still present
assertEq(diamondLoupe.facetFunctionSelectors(address(mockFacetA)).length, 1);
assertEq(MockFacetA(address(diamond)).funcA3(), 3);
}
Expand Down Expand Up @@ -453,14 +452,14 @@ contract DiamondCutTester is DeployedDiamondState {
/// @notice MultiInit stops processing on address(0) — the sentinel stone
function testSetting_MultiInitStopsOnZeroAddress() public {
MultiInit multiInit = new MultiInit();
ERC165Init erc165Init = new ERC165Init();
OwnableInit ownableInit = new OwnableInit();

address[] memory addrs = new address[](2);
bytes[] memory data = new bytes[](2);
addrs[0] = address(0); // sentinel — stops here
addrs[1] = address(erc165Init); // should not execute
addrs[1] = address(ownableInit); // should not execute
data[0] = "";
data[1] = abi.encodeWithSignature("initERC165()");
data[1] = abi.encodeWithSignature("initOwner(address)", address(this));

bytes memory callData = abi.encodeWithSignature("multiInit(address[],bytes[])", addrs, data);

Expand Down
16 changes: 8 additions & 8 deletions test/DiamondTester.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ contract DiamondTester is DeployedDiamondState {
assertEq(ownable.owner(), address(this));
}

/// @notice Exactly 3 standard facets are cut into the rough
/// @notice Exactly 4 standard facets are cut into the rough
function testRough_StandardFacetsDeployed() public view {
assertEq(facetAddresses.length, 3);
assertEq(facetAddresses.length, 4);
for (uint256 i; i < facetAddresses.length; ++i) {
assertNotEq(address(facetAddresses[i]), address(0));
}
Expand Down Expand Up @@ -95,27 +95,27 @@ contract DiamondTester is DeployedDiamondState {

/// @notice Certified: supports ERC-165 introspection
function testCertified_SupportsERC165() public view {
assertTrue(diamondLoupe.supportsInterface(0x01ffc9a7));
assertTrue(erc165.supportsInterface(0x01ffc9a7));
}

/// @notice Certified: supports ERC-173 ownership
function testCertified_SupportsERC173() public view {
assertTrue(diamondLoupe.supportsInterface(0x7f5828d0));
assertTrue(erc165.supportsInterface(0x7f5828d0));
}

/// @notice Certified: supports IDiamondCut
function testCertified_SupportsIDiamondCut() public view {
assertTrue(diamondLoupe.supportsInterface(type(IDiamondCut).interfaceId));
assertTrue(erc165.supportsInterface(type(IDiamondCut).interfaceId));
}

/// @notice Certified: supports IDiamondLoupe
function testCertified_SupportsIDiamondLoupe() public view {
assertTrue(diamondLoupe.supportsInterface(type(IDiamondLoupe).interfaceId));
assertTrue(erc165.supportsInterface(type(IDiamondLoupe).interfaceId));
}

/// @notice Unregistered interface returns false
function testCertified_UnsupportedInterfaceReturnsFalse() public view {
assertFalse(diamondLoupe.supportsInterface(0xffffffff));
assertFalse(diamondLoupe.supportsInterface(bytes4(0xdeadbeef)));
assertFalse(erc165.supportsInterface(0xffffffff));
assertFalse(erc165.supportsInterface(bytes4(0xdeadbeef)));
}
}
Loading
Loading