Skip to content

Commit da90f6d

Browse files
committed
Extract supportsInterface into dedicated ERC165Facet
Move supportsInterface from DiamondLoupeFacet into a new ERC165Facet for cleaner separation of concerns. Update deploy script, test state, and all test files to register and reference the new facet.
1 parent 7ea4417 commit da90f6d

7 files changed

Lines changed: 84 additions & 76 deletions

File tree

script/DeployDiamond.s.sol

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
55
import {MockDiamond} from "@diamond-test/mocks/MockDiamond.sol";
66
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
77
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
8+
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
89
import {OwnableFacet} from "@diamond/facets/OwnableFacet.sol";
9-
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
1010
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
1111
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
1212
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
@@ -28,15 +28,15 @@ contract DeployDiamond is Script, GetSelectors {
2828
// Deploy core facet contracts
2929
DiamondCutFacet diamondCutFacet = new DiamondCutFacet();
3030
DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet();
31+
ERC165Facet erc165Facet = new ERC165Facet();
3132
OwnableFacet ownableFacet = new OwnableFacet();
3233

3334
// Deploy initializer contracts
3435
address multiInit = address(new MultiInit());
3536
address ownableInit = address(new OwnableInit());
36-
address erc165Init = address(new ERC165Init());
3737

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

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

55-
// Add OwnableFacet to the cut list
55+
// Add ERC165Facet to the cut list
5656
cut[2] = FacetCut({
57+
facetAddress: address(erc165Facet),
58+
action: FacetCutAction.Add,
59+
functionSelectors: _getSelectors("ERC165Facet")
60+
});
61+
62+
// Add OwnableFacet to the cut list
63+
cut[3] = FacetCut({
5764
facetAddress: address(ownableFacet),
5865
action: FacetCutAction.Add,
5966
functionSelectors: _getSelectors("OwnableFacet")
6067
});
6168

6269
// Build MultiInit arrays for granular initialization
63-
address[] memory initAddresses = new address[](2);
64-
bytes[] memory initData = new bytes[](2);
70+
address[] memory initAddresses = new address[](1);
71+
bytes[] memory initData = new bytes[](1);
6572

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

69-
initAddresses[1] = erc165Init;
70-
initData[1] = abi.encodeWithSignature("initERC165()");
71-
7276
// Deploy the Diamond contract with the facets and initialization args
7377
MockDiamond diamond = new MockDiamond();
7478
diamond.initialize(

src/facets/DiamondLoupeFacet.sol

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,22 @@ contract DiamondLoupeFacet is IDiamondLoupe {
2727

2828
/// @notice Gets all the function selectors provided by a facet.
2929
/// @param _facet The facet address.
30-
/// @return facetFunctionSelectors_
31-
function facetFunctionSelectors(address _facet)
32-
external
33-
view
34-
override
35-
returns (bytes4[] memory facetFunctionSelectors_)
36-
{
37-
facetFunctionSelectors_ = DiamondLib.diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
30+
/// @return The function selectors for the specified facet.
31+
function facetFunctionSelectors(address _facet) external view override returns (bytes4[] memory) {
32+
return DiamondLib.diamondStorage().facetToSelectorsAndPosition[_facet].functionSelectors;
3833
}
3934

4035
/// @notice Get all the facet addresses used by a diamond.
41-
/// @return facetAddresses_
42-
function facetAddresses() external view override returns (address[] memory facetAddresses_) {
43-
facetAddresses_ = DiamondLib.diamondStorage().facetAddresses;
36+
/// @return Facet addresses.
37+
function facetAddresses() external view override returns (address[] memory) {
38+
return DiamondLib.diamondStorage().facetAddresses;
4439
}
4540

4641
/// @notice Gets the facet that supports the given selector.
4742
/// @dev If facet is not found return address(0).
4843
/// @param _functionSelector The function selector.
49-
/// @return facetAddress_ The facet address.
50-
function facetAddress(bytes4 _functionSelector) external view override returns (address facetAddress_) {
51-
facetAddress_ = DiamondLib.diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
52-
}
53-
54-
/// @notice Query if a contract implements an interface
55-
/// @param _interfaceId The interface identifier, as specified in ERC-165
56-
/// @dev Interface identification is specified in ERC-165. This function
57-
/// uses less than 30,000 gas.
58-
/// @return `true` if the contract implements `interfaceID` and
59-
/// `interfaceID` is not 0xffffffff, `false` otherwise
60-
function supportsInterface(bytes4 _interfaceId) external view returns (bool) {
61-
return DiamondLib.diamondStorage().supportedInterfaces[_interfaceId];
44+
/// @return The address of the facet that supports the given selector.
45+
function facetAddress(bytes4 _functionSelector) external view override returns (address) {
46+
return DiamondLib.diamondStorage().selectorToFacetAndPosition[_functionSelector].facetAddress;
6247
}
6348
}

src/facets/ERC165Facet.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {ERC165Lib} from "@diamond/libraries/ERC165Lib.sol";
5+
6+
/// @title ERC165Facet
7+
/// @notice Diamond facet that implements the ERC-165 standard interface detection
8+
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
9+
/// @dev Delegates to ERC165Lib which hardcodes support for ERC-165, ERC-173, IDiamondCut, and IDiamondLoupe
10+
contract ERC165Facet {
11+
/// @notice Query if a contract implements an interface
12+
/// @param _interfaceId The interface identifier, as specified in ERC-165
13+
/// @dev Interface identification is specified in ERC-165. This function
14+
/// uses less than 30,000 gas.
15+
/// @return `true` if the contract implements `_interfaceId` and
16+
/// `_interfaceId` is not 0xffffffff, `false` otherwise
17+
function supportsInterface(bytes4 _interfaceId) external pure returns (bool) {
18+
return ERC165Lib.supportsInterface(_interfaceId);
19+
}
20+
}

test/DiamondCutTester.t.sol

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {MockFacetB} from "@diamond-test/mocks/MockFacetB.sol";
77
import {MockRevertInit} from "@diamond-test/mocks/MockRevertInit.sol";
88
import {DeployedDiamondState} from "@diamond-test/states/DeployedDiamondState.sol";
99
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
10-
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
1110
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
1211
import {
1312
AddressAndCalldataLengthMismatch,
@@ -69,7 +68,7 @@ contract DiamondCutTester is DeployedDiamondState {
6968
_addMockFacetA();
7069

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

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

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

89-
assertEq(diamondLoupe.facetAddresses().length, 5); // 3 core + A + B
88+
assertEq(diamondLoupe.facetAddresses().length, 6); // 4 core + A + B
9089
}
9190

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

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

191-
assertEq(diamondLoupe.facetAddresses().length, 3);
190+
assertEq(diamondLoupe.facetAddresses().length, 4);
192191
assertEq(diamondLoupe.facetFunctionSelectors(address(mockFacetA)).length, 0);
193192
}
194193

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

206-
assertEq(diamondLoupe.facetAddresses().length, 4); // Facet still present
205+
assertEq(diamondLoupe.facetAddresses().length, 5); // Facet still present
207206
assertEq(diamondLoupe.facetFunctionSelectors(address(mockFacetA)).length, 1);
208207
assertEq(MockFacetA(address(diamond)).funcA3(), 3);
209208
}
@@ -453,14 +452,14 @@ contract DiamondCutTester is DeployedDiamondState {
453452
/// @notice MultiInit stops processing on address(0) — the sentinel stone
454453
function testSetting_MultiInitStopsOnZeroAddress() public {
455454
MultiInit multiInit = new MultiInit();
456-
ERC165Init erc165Init = new ERC165Init();
455+
OwnableInit ownableInit = new OwnableInit();
457456

458457
address[] memory addrs = new address[](2);
459458
bytes[] memory data = new bytes[](2);
460459
addrs[0] = address(0); // sentinel — stops here
461-
addrs[1] = address(erc165Init); // should not execute
460+
addrs[1] = address(ownableInit); // should not execute
462461
data[0] = "";
463-
data[1] = abi.encodeWithSignature("initERC165()");
462+
data[1] = abi.encodeWithSignature("initOwner(address)", address(this));
464463

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

test/DiamondTester.t.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ contract DiamondTester is DeployedDiamondState {
2424
assertEq(ownable.owner(), address(this));
2525
}
2626

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

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

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

106106
/// @notice Certified: supports IDiamondCut
107107
function testCertified_SupportsIDiamondCut() public view {
108-
assertTrue(diamondLoupe.supportsInterface(type(IDiamondCut).interfaceId));
108+
assertTrue(erc165.supportsInterface(type(IDiamondCut).interfaceId));
109109
}
110110

111111
/// @notice Certified: supports IDiamondLoupe
112112
function testCertified_SupportsIDiamondLoupe() public view {
113-
assertTrue(diamondLoupe.supportsInterface(type(IDiamondLoupe).interfaceId));
113+
assertTrue(erc165.supportsInterface(type(IDiamondLoupe).interfaceId));
114114
}
115115

116116
/// @notice Unregistered interface returns false
117117
function testCertified_UnsupportedInterfaceReturnsFalse() public view {
118-
assertFalse(diamondLoupe.supportsInterface(0xffffffff));
119-
assertFalse(diamondLoupe.supportsInterface(bytes4(0xdeadbeef)));
118+
assertFalse(erc165.supportsInterface(0xffffffff));
119+
assertFalse(erc165.supportsInterface(bytes4(0xdeadbeef)));
120120
}
121121
}

test/InitializableTester.t.sol

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
55
import {ReinitializableDiamond} from "@diamond-test/mocks/ReinitializableDiamond.sol";
66
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
77
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
8+
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
89
import {OwnableFacet} from "@diamond/facets/OwnableFacet.sol";
9-
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
10-
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
1110
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
1211
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
1312
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
@@ -19,23 +18,21 @@ contract InitializableTester is GetSelectors {
1918
ReinitializableDiamond diamond;
2019
DiamondCutFacet diamondCutFacet;
2120
DiamondLoupeFacet diamondLoupeFacet;
21+
ERC165Facet erc165Facet;
2222
OwnableFacet ownableFacet;
23-
MultiInit multiInit;
2423
OwnableInit ownableInit;
25-
ERC165Init erc165Init;
2624

2725
FacetCut[] cuts;
2826

2927
function setUp() public {
3028
// Deploy facets
3129
diamondCutFacet = new DiamondCutFacet();
3230
diamondLoupeFacet = new DiamondLoupeFacet();
31+
erc165Facet = new ERC165Facet();
3332
ownableFacet = new OwnableFacet();
3433

35-
// Deploy initializers
36-
multiInit = new MultiInit();
34+
// Deploy initializer
3735
ownableInit = new OwnableInit();
38-
erc165Init = new ERC165Init();
3936

4037
// Build facet cuts
4138
cuts.push(
@@ -52,6 +49,13 @@ contract InitializableTester is GetSelectors {
5249
functionSelectors: _getSelectors("DiamondLoupeFacet")
5350
})
5451
);
52+
cuts.push(
53+
FacetCut({
54+
facetAddress: address(erc165Facet),
55+
action: FacetCutAction.Add,
56+
functionSelectors: _getSelectors("ERC165Facet")
57+
})
58+
);
5559
cuts.push(
5660
FacetCut({
5761
facetAddress: address(ownableFacet),
@@ -200,7 +204,7 @@ contract InitializableTester is GetSelectors {
200204
/* MULTI-INIT TESTS */
201205
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
202206

203-
/// @notice MultiInit correctly initializes owner and ERC165 through initialize
207+
/// @notice Initialize correctly sets owner and ERC165 interfaces
204208
function testMultiInitSetsOwnerAndInterfaces() public {
205209
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
206210
diamond.initialize(facetCuts, init, initCalldata);
@@ -209,12 +213,12 @@ contract InitializableTester is GetSelectors {
209213
OwnableFacet ownable = OwnableFacet(address(diamond));
210214
assertEq(ownable.owner(), address(this));
211215

212-
// Verify ERC165 interfaces were registered
213-
DiamondLoupeFacet loupe = DiamondLoupeFacet(address(diamond));
214-
assertTrue(loupe.supportsInterface(0x01ffc9a7)); // ERC165
215-
assertTrue(loupe.supportsInterface(0x7f5828d0)); // ERC173
216-
assertTrue(loupe.supportsInterface(0x1f931c1c)); // IDiamondCut
217-
assertTrue(loupe.supportsInterface(0x48e2b093)); // IDiamondLoupe
216+
// Verify ERC165 interfaces are supported
217+
ERC165Facet erc165 = ERC165Facet(address(diamond));
218+
assertTrue(erc165.supportsInterface(0x01ffc9a7)); // ERC165
219+
assertTrue(erc165.supportsInterface(0x7f5828d0)); // ERC173
220+
assertTrue(erc165.supportsInterface(0x1f931c1c)); // IDiamondCut
221+
assertTrue(erc165.supportsInterface(0x48e2b093)); // IDiamondLoupe
218222
}
219223

220224
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -231,16 +235,7 @@ contract InitializableTester is GetSelectors {
231235
facetCuts_[i] = cuts[i];
232236
}
233237

234-
address[] memory initAddresses = new address[](2);
235-
bytes[] memory initData = new bytes[](2);
236-
237-
initAddresses[0] = address(ownableInit);
238-
initData[0] = abi.encodeWithSignature("initOwner(address)", _owner);
239-
240-
initAddresses[1] = address(erc165Init);
241-
initData[1] = abi.encodeWithSignature("initERC165()");
242-
243-
init_ = address(multiInit);
244-
initCalldata_ = abi.encodeWithSignature("multiInit(address[],bytes[])", initAddresses, initData);
238+
init_ = address(ownableInit);
239+
initCalldata_ = abi.encodeWithSignature("initOwner(address)", _owner);
245240
}
246241
}

test/states/DeployedDiamondState.sol

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity ^0.8.20;
44
import {DeployDiamond} from "@diamond-script/DeployDiamond.s.sol";
55
import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
66
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
7+
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
78
import {OwnableFacet} from "@diamond/facets/OwnableFacet.sol";
89
import {IDiamondCut} from "@diamond/interfaces/IDiamondCut.sol";
910

@@ -20,14 +21,17 @@ abstract contract DeployedDiamondState is GetSelectors {
2021
/// @notice Interface for the DiamondLoupe functionality of the deployed diamond.
2122
DiamondLoupeFacet public diamondLoupe;
2223

24+
/// @notice Interface for the ERC165 functionality of the deployed diamond.
25+
ERC165Facet public erc165;
26+
2327
/// @notice Interface for the OwnableRoles functionality of the deployed diamond.
2428
OwnableFacet public ownable;
2529

2630
/// @notice Stores the facet addresses returned from the diamond loupe.
2731
address[] public facetAddresses;
2832

2933
/// @notice List of facet contract names used in deployment.
30-
string[3] public facetNames = ["DiamondCutFacet", "DiamondLoupeFacet", "OwnableFacet"];
34+
string[4] public facetNames = ["DiamondCutFacet", "DiamondLoupeFacet", "ERC165Facet", "OwnableFacet"];
3135

3236
address public diamondOwner = address(this);
3337

@@ -39,6 +43,7 @@ abstract contract DeployedDiamondState is GetSelectors {
3943

4044
diamondCut = IDiamondCut(diamond);
4145
diamondLoupe = DiamondLoupeFacet(diamond);
46+
erc165 = ERC165Facet(diamond);
4247
ownable = OwnableFacet(diamond);
4348

4449
facetAddresses = diamondLoupe.facetAddresses();

0 commit comments

Comments
 (0)