Skip to content

Commit df59407

Browse files
authored
Merge pull request #20 from dadadave80/feat/initializable-diamond
Add initializable Diamond pattern
2 parents de52d9e + 64ccb7a commit df59407

13 files changed

Lines changed: 663 additions & 174 deletions

script/DeployDiamond.s.sol

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {MockDiamond} from "@diamond-test/mocks/MockDiamond.sol";
66
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
77
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
88
import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.sol";
9-
import {DiamondInit} from "@diamond/initializers/DiamondInit.sol";
9+
import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
10+
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
11+
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
1012
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
1113
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
1214
import {Script} from "forge-std/Script.sol";
@@ -28,8 +30,10 @@ contract DeployDiamond is Script, GetSelectors {
2830
DiamondLoupeFacet diamondLoupeFacet = new DiamondLoupeFacet();
2931
OwnableRolesFacet ownableRolesFacet = new OwnableRolesFacet();
3032

31-
// Deploy ERC165 initializer contract
32-
address diamondInit = address(new DiamondInit());
33+
// Deploy initializer contracts
34+
address multiInit = address(new MultiInit());
35+
address ownableInit = address(new OwnableInit());
36+
address erc165Init = address(new ERC165Init());
3337

3438
// Create an array of FacetCut entries for standard facets
3539
FacetCut[] memory cut = new FacetCut[](3);
@@ -55,9 +59,21 @@ contract DeployDiamond is Script, GetSelectors {
5559
functionSelectors: _getSelectors("OwnableRolesFacet")
5660
});
5761

62+
// Build MultiInit arrays for granular initialization
63+
address[] memory initAddresses = new address[](2);
64+
bytes[] memory initData = new bytes[](2);
65+
66+
initAddresses[0] = ownableInit;
67+
initData[0] = abi.encodeWithSignature("initOwner(address)", ContextLib.msgSender());
68+
69+
initAddresses[1] = erc165Init;
70+
initData[1] = abi.encodeWithSignature("initERC165()");
71+
5872
// Deploy the Diamond contract with the facets and initialization args
59-
MockDiamond diamond =
60-
new MockDiamond(cut, diamondInit, abi.encodeWithSignature("initDiamond(address)", ContextLib.msgSender()));
73+
MockDiamond diamond = new MockDiamond();
74+
diamond.initialize(
75+
cut, multiInit, abi.encodeWithSignature("multiInit(address[],bytes[])", initAddresses, initData)
76+
);
6177
diamond_ = address(diamond);
6278
vm.stopBroadcast();
6379
}

src/Diamond.sol

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity ^0.8.20;
33

44
import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
5+
import {InitializableLib} from "@diamond/libraries/InitializableLib.sol";
56

67
/*
78
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
@@ -32,23 +33,39 @@ import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
3233

3334
/// @title Diamond
3435
/// @notice Implements ERC-2535 Diamond proxy pattern, allowing dynamic addition, replacement, and removal of facets
35-
/// @author Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
36-
/// @author Modified by David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
36+
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
37+
/// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
3738
abstract contract Diamond {
3839
/// @notice Initializes the Diamond proxy with the provided facets and initialization parameters
3940
/// @param _init Address of the initialization contract
4041
/// @param _calldata Calldata to be passed to the initialization contract
41-
constructor(FacetCut[] memory _facetCuts, address _init, bytes memory _calldata) payable {
42-
_diamondCut(_facetCuts, _init, _calldata);
42+
function initialize(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata)
43+
external
44+
payable
45+
virtual
46+
{
47+
bytes32 s = InitializableLib.initializableSlot();
48+
InitializableLib.preInitializer(s);
49+
50+
DiamondLib.diamondCutCalldata(_facetCuts, _init, _calldata);
51+
52+
InitializableLib.postInitializer(s);
4353
}
4454

4555
/// @notice Fallback function that delegates calls to the appropriate facet based on function selector
4656
/// @dev Reads the facet address from diamond storage and performs a delegatecall; reverts if selector is not found
4757
fallback() external payable virtual {
48-
_beforeDelegate();
4958
_delegate(_facet());
5059
}
5160

61+
receive() external payable virtual {}
62+
63+
/// @notice Retrieves the implementation address for the current function call
64+
/// @dev A Facet is one of many implementations in a Diamond Proxy
65+
function _facet() internal view virtual returns (address) {
66+
return DiamondLib.selectorToFacet(msg.sig);
67+
}
68+
5269
/// @notice Internal function to perform a delegatecall to an implementation
5370
/// @param _implementation Address of the implementation to delegate to
5471
function _delegate(address _implementation) internal virtual {
@@ -72,19 +89,4 @@ abstract contract Diamond {
7289
}
7390
}
7491
}
75-
76-
/// @notice Internal function to perform a diamond cut
77-
function _diamondCut(FacetCut[] memory _facetCuts, address _init, bytes memory _calldata) internal virtual {
78-
DiamondLib.diamondCut(_facetCuts, _init, _calldata);
79-
}
80-
81-
/// @notice Internal hook function to run before a delegatecall to the facet
82-
/// @dev This function can be replaced to perform additional logic before the delegatecall
83-
function _beforeDelegate() internal virtual {}
84-
85-
/// @notice Retrieves the implementation address for the current function call
86-
/// @dev A Facet is one of many implementations in a Diamond Proxy
87-
function _facet() internal view virtual returns (address) {
88-
return DiamondLib.selectorToFacet(msg.sig);
89-
}
9092
}

src/facets/DiamondCutFacet.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ contract DiamondCutFacet is IDiamondCut {
2222
/// _calldata is executed with delegatecall on _init
2323
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external payable {
2424
// Check that the caller is the owner
25-
OwnableRolesLib._checkOwner();
25+
OwnableRolesLib.checkOwner();
2626
// Call the diamond cut function from the library
2727
DiamondLib.diamondCutCalldata(_diamondCut, _init, _calldata);
2828
}

src/facets/OwnableRolesFacet.sol

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,47 +24,47 @@ contract OwnableRolesFacet {
2424

2525
/// @dev Allows the owner to transfer the ownership to `newOwner`.
2626
function transferOwnership(address _newOwner) public onlyOwner {
27-
_newOwner._transferOwnership();
27+
_newOwner.transferOwnership();
2828
}
2929

3030
/// @dev Allows the owner to renounce their ownership.
3131
function renounceOwnership() public onlyOwner {
32-
OwnableRolesLib._renounceOwnership();
32+
OwnableRolesLib.renounceOwnership();
3333
}
3434

3535
/// @dev Request a two-step ownership handover to the caller.
3636
/// The request will automatically expire in 48 hours (172800 seconds) by default.
3737
function requestOwnershipHandover() public {
38-
OwnableRolesLib._requestOwnershipHandover();
38+
OwnableRolesLib.requestOwnershipHandover();
3939
}
4040

4141
/// @dev Cancels the two-step ownership handover to the caller, if any.
4242
function cancelOwnershipHandover() public {
43-
OwnableRolesLib._cancelOwnershipHandover();
43+
OwnableRolesLib.cancelOwnershipHandover();
4444
}
4545

4646
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
4747
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
4848
function completeOwnershipHandover(address _pendingOwner) public onlyOwner {
49-
_pendingOwner._completeOwnershipHandover();
49+
_pendingOwner.completeOwnershipHandover();
5050
}
5151

5252
/// @dev Allows the owner to grant `user` `roles`.
5353
/// If the `user` already has a role, then it will be an no-op for the role.
5454
function grantRoles(address _user, uint256 _roles) public onlyOwner {
55-
_user._grantRoles(_roles);
55+
_user.grantRoles(_roles);
5656
}
5757

5858
/// @dev Allows the owner to remove `user` `roles`.
5959
/// If the `user` does not have a role, then it will be an no-op for the role.
6060
function revokeRoles(address _user, uint256 _roles) public onlyOwner {
61-
_user._removeRoles(_roles);
61+
_user.removeRoles(_roles);
6262
}
6363

6464
/// @dev Allow the caller to remove their own roles.
6565
/// If the caller does not have a role, then it will be an no-op for the role.
6666
function renounceRoles(uint256 _roles) public {
67-
msg.sender._removeRoles(_roles);
67+
msg.sender.removeRoles(_roles);
6868
}
6969

7070
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -73,41 +73,41 @@ contract OwnableRolesFacet {
7373

7474
/// @dev Returns the owner of the contract.
7575
function owner() public view returns (address) {
76-
return OwnableRolesLib._owner();
76+
return OwnableRolesLib.owner();
7777
}
7878

7979
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
8080
function ownershipHandoverExpiresAt(address _pendingOwner) public view returns (uint256) {
81-
return _pendingOwner._ownershipHandoverExpiresAt();
81+
return _pendingOwner.ownershipHandoverExpiresAt();
8282
}
8383

8484
/// @dev Returns the roles of `user`.
8585
function rolesOf(address _user) public view returns (uint256) {
86-
return _user._rolesOf();
86+
return _user.rolesOf();
8787
}
8888

8989
/// @dev Returns whether `user` has any of `roles`.
9090
function hasAnyRole(address _user, uint256 _roles) public view returns (bool) {
91-
return _user._rolesOf() & _roles != 0;
91+
return _user.rolesOf() & _roles != 0;
9292
}
9393

9494
/// @dev Returns whether `user` has all of `roles`.
9595
function hasAllRoles(address _user, uint256 _roles) public view returns (bool) {
96-
return _user._rolesOf() & _roles == _roles;
96+
return _user.rolesOf() & _roles == _roles;
9797
}
9898

9999
/// @dev Convenience function to return a `roles` bitmap from an array of `ordinals`.
100100
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
101101
/// Not recommended to be called on-chain.
102102
function rolesFromOrdinals(uint8[] memory ordinals) external pure returns (uint256) {
103-
return ordinals._rolesFromOrdinals();
103+
return ordinals.rolesFromOrdinals();
104104
}
105105

106106
/// @dev Convenience function to return an array of `ordinals` from the `roles` bitmap.
107107
/// This is meant for frontends like Etherscan, and is therefore not fully optimized.
108108
/// Not recommended to be called on-chain.
109109
function ordinalsFromRoles(uint256 roles) external pure returns (uint8[] memory) {
110-
return roles._ordinalsFromRoles();
110+
return roles.ordinalsFromRoles();
111111
}
112112

113113
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
@@ -116,27 +116,27 @@ contract OwnableRolesFacet {
116116

117117
/// @dev Marks a function as only callable by the owner.
118118
modifier onlyOwner() {
119-
OwnableRolesLib._checkOwner();
119+
OwnableRolesLib.checkOwner();
120120
_;
121121
}
122122

123123
/// @dev Marks a function as only callable by an account with `roles`.
124124
modifier onlyRoles(uint256 _roles) {
125-
OwnableRolesLib._checkRoles(_roles);
125+
OwnableRolesLib.checkRoles(_roles);
126126
_;
127127
}
128128

129129
/// @dev Marks a function as only callable by the owner or by an account
130130
/// with `roles`. Checks for ownership first, then lazily checks for roles.
131131
modifier onlyOwnerOrRoles(uint256 _roles) {
132-
OwnableRolesLib._checkOwnerOrRoles(_roles);
132+
OwnableRolesLib.checkOwnerOrRoles(_roles);
133133
_;
134134
}
135135

136136
/// @dev Marks a function as only callable by an account with `roles` or the owner.
137137
/// Checks for roles first, then lazily checks for ownership.
138138
modifier onlyRolesOrOwner(uint256 _roles) {
139-
OwnableRolesLib._checkRolesOrOwner(_roles);
139+
OwnableRolesLib.checkRolesOrOwner(_roles);
140140
_;
141141
}
142142
}

src/initializers/DiamondInit.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ import {DiamondLib, DiamondStorage} from "@diamond/libraries/DiamondLib.sol";
55
import {OwnableRolesLib} from "@diamond/libraries/OwnableRolesLib.sol";
66

77
/// @title DiamondInit
8-
/// @notice Provides an initializer to register standard interface support (ERC-165, ERC-173, IDiamondCut, IDiamondLoupe)
8+
/// @notice Provides a combined initializer to set up ownership and register standard interface support
99
/// @author Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
1010
/// @author Modified by David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
1111
///
12-
/// @dev Intended to be called as the `initDiamond` function in a diamond cut to set up ERC-165 interface IDs
12+
/// @dev Intended to be called as the `initDiamond` function in a diamond cut.
13+
/// For granular initialization, use OwnableInit and ERC165Init separately via MultiInit.
1314
contract DiamondInit {
14-
/// @notice Initialize the contract with the ERC165 interface support.
15+
/// @notice Initialize the contract owner and register standard diamond ERC-165 interface IDs.
1516
/// @dev This function is called during the diamond cut process to set up
1617
/// the initial state of the contract.
18+
/// @param _owner The address to set as the contract owner.
1719
function initDiamond(address _owner) public {
1820
// Initialize the owner
19-
OwnableRolesLib._initializeOwner(_owner);
21+
OwnableRolesLib.initializeOwner(_owner);
2022

2123
DiamondStorage storage ds = DiamondLib.diamondStorage();
2224
/// @dev type(ERC165).interfaceId

src/initializers/ERC165Init.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {DiamondLib, DiamondStorage} from "@diamond/libraries/DiamondLib.sol";
5+
6+
/// @title ERC165Init
7+
/// @notice Provides an initializer to register standard diamond interface support (ERC-165, ERC-173, IDiamondCut, IDiamondLoupe)
8+
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
9+
///
10+
/// @dev Intended to be called as a standalone initializer via diamond cut or through MultiInit
11+
contract ERC165Init {
12+
/// @notice Register standard diamond ERC-165 interface IDs.
13+
/// @dev Sets support for ERC-165, ERC-173, IDiamondCut, and IDiamondLoupe.
14+
function initERC165() public {
15+
DiamondStorage storage ds = DiamondLib.diamondStorage();
16+
/// @dev type(ERC165).interfaceId
17+
ds.supportedInterfaces[0x01ffc9a7] = true;
18+
/// @dev type(IERC173).interfaceId
19+
ds.supportedInterfaces[0x7f5828d0] = true;
20+
/// @dev type(IDiamondCut).interfaceId
21+
ds.supportedInterfaces[0x1f931c1c] = true;
22+
/// @dev type(IDiamondLoupe).interfaceId
23+
ds.supportedInterfaces[0x48e2b093] = true;
24+
}
25+
}

src/initializers/MultiInit.sol

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.20;
33

4-
import {DiamondLib} from "@diamond/libraries/DiamondLib.sol";
5-
64
/// @notice Thrown when the length of the address array does not match the length of the calldata array.
75
/// @dev Used in initializer logic to ensure one-to-one mapping between addresses and initialization calldata.
86
error AddressAndCalldataLengthMismatch();
7+
error NoBytecodeAtAddress(address initAddress);
8+
error InitializeReverted(address initAddress, bytes initCalldata);
99

1010
contract MultiInit {
1111
function multiInit(address[] calldata _initAddresses, bytes[] calldata _initData) public {
@@ -15,7 +15,21 @@ contract MultiInit {
1515
}
1616

1717
for (uint256 i; i < initAddressesLength; ++i) {
18-
DiamondLib.initializeDiamondCut(_initAddresses[i], _initData[i]);
18+
address initAddress = _initAddresses[i];
19+
if (initAddress == address(0)) return;
20+
if (initAddress.code.length == 0) revert NoBytecodeAtAddress(initAddress);
21+
(bool success, bytes memory err) = initAddress.delegatecall(_initData[i]);
22+
if (!success) {
23+
if (err.length > 0) {
24+
// bubble up error
25+
assembly ("memory-safe") {
26+
let returndata_size := mload(err)
27+
revert(add(32, err), returndata_size)
28+
}
29+
} else {
30+
revert InitializeReverted(initAddress, _initData[i]);
31+
}
32+
}
1933
}
2034
}
2135
}

src/initializers/OwnableInit.sol

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {OwnableRolesLib} from "@diamond/libraries/OwnableRolesLib.sol";
5+
6+
/// @title OwnableInit
7+
/// @notice Provides an initializer to set the contract owner
8+
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
9+
///
10+
/// @dev Intended to be called as a standalone initializer via diamond cut or through MultiInit
11+
contract OwnableInit {
12+
/// @notice Initialize the contract owner.
13+
/// @dev This function is called during the diamond cut process to set up the owner.
14+
/// @param _owner The address to set as the contract owner.
15+
function initOwner(address _owner) public {
16+
OwnableRolesLib.initializeOwner(_owner);
17+
}
18+
}

0 commit comments

Comments
 (0)