Skip to content

Commit 43e943f

Browse files
authored
Merge pull request #33 from dadadave80/dev
refactor!: make Diamond an abstract fallback-only base
2 parents fa70a12 + 523bf38 commit 43e943f

12 files changed

Lines changed: 185 additions & 213 deletions

DEVELOPER_GUIDE.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,11 @@ bytes32 constant STORAGE_LOCATION_2 = keccak256(abi.encode("1"));
237237

238238
```solidity
239239
contract DiamondTest is Test {
240-
Diamond diamond;
240+
MyDiamond diamond;
241241
242242
function setUp() public {
243-
// Deploy and initialize
244-
diamond = new Diamond();
245-
// ... initialization code ...
243+
// Constructor applies the cuts and runs the init contract
244+
diamond = new MyDiamond(cuts, address(diamondInit), initData);
246245
}
247246
248247
function test_DiamondInitialized() public {

GLOSSARY.md

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ Process of setting up the Diamond when it's first deployed.
144144
- Must be atomic (all succeed or all fail)
145145

146146
**Process**:
147-
1. Deploy Diamond (empty, no functions yet)
148-
2. Call `initialize(cuts, initContract, initData)`
147+
1. Deploy facets and the initializer contract
148+
2. Deploy your concrete diamond — its constructor calls `diamondCut(cuts, initContract, initData)`
149149
3. Facets are added via diamond cuts
150150
4. Initialization contract runs (sets owner, etc.)
151151
5. Diamond is ready
@@ -165,27 +165,6 @@ function init() external {
165165

166166
**Execution**: Runs via delegatecall in Diamond's context
167167

168-
### Reinitialization
169-
Updating initialization state without resetting everything.
170-
171-
**Use case**: Upgrade with new state variables
172-
173-
**Protection**: Version tracking prevents accidental reinitialization
174-
- v1 → v2 (success)
175-
- v2 → v2 (failure: already at this version)
176-
- v2 → v1 (failure: cannot go backward)
177-
178-
### Initialization Guard
179-
Mechanism preventing reentrancy during initialization.
180-
181-
**Implementation**:
182-
```solidity
183-
// Flag: 0 = not init, 1 = currently initializing, 2 = done
184-
if (isInitializing) revert InvalidInitialization();
185-
```
186-
187-
**Prevents**: Accidentally calling initialization again during initialization
188-
189168
---
190169

191170
## Access Control
@@ -422,13 +401,6 @@ Function selector doesn't route to any facet.
422401

423402
**Fix**: Use loupe to verify `facetAddress(selector)` returns non-zero
424403

425-
### "Invalid initialization" Error
426-
Attempt to reinitialize to same or lower version.
427-
428-
**Cause**: Already initialized and version unchanged
429-
430-
**Fix**: Increment version number for reinitializations
431-
432404
### "Cannot add function to diamond that already exists" Error
433405
Selector already has a mapping.
434406

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ src/
6060
├── libraries/
6161
│ ├── DiamondLib.sol # Core Diamond logic
6262
│ ├── OwnableLib.sol # Ownership primitives
63-
│ ├── InitializableLib.sol # Initialization guards
6463
│ └── ERC165Lib.sol # Interface registration
6564
└── script/
6665
└── DeployDiamond.s.sol # Foundry deployment script

SPECIFICATION.md

Lines changed: 10 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ graph TB
3939
4040
DiamondLib["DiamondLib<br/>Storage & cuts"]
4141
OwnableLib["OwnableLib<br/>Owner state"]
42-
InitLib["InitializableLib<br/>Init guards"]
4342
ERC165Lib["ERC165Lib<br/>Interface registry"]
4443
4544
SharedStorage["Shared Storage<br/>Diamond + Libraries"]
@@ -57,11 +56,9 @@ graph TB
5756
Own --> OwnableLib
5857
Custom --> DiamondLib
5958
Custom --> OwnableLib
60-
Custom --> InitLib
6159
6260
DiamondLib --> SharedStorage
6361
OwnableLib --> SharedStorage
64-
InitLib --> SharedStorage
6562
ERC165Lib --> SharedStorage
6663
```
6764

@@ -182,15 +179,15 @@ Initialization differs from constructors because Diamond is a proxy:
182179
**Solution: Delegatecall-based initialization:**
183180

184181
```solidity
185-
// Initialize during Diamond deployment
186-
Diamond.initialize(
187-
[facetCuts...],
188-
address(diamondInit),
189-
abi.encodeCall(DiamondInit.init, ())
190-
);
182+
// Concrete diamond: cut facets and run the init contract atomically at deployment
183+
contract MyDiamond is Diamond {
184+
constructor(FacetCut[] memory _facetCuts, address _init, bytes memory _calldata) {
185+
DiamondLib.diamondCut(_facetCuts, _init, _calldata);
186+
}
187+
}
191188
```
192189

193-
Functions run in Diamond's delegatecall context, so initialization happens in the right storage.
190+
The init contract runs in the Diamond's delegatecall context, so initialization happens in the right storage. For factory/CREATE2/clone deployments where a constructor cannot run per-instance, expose an initializer-guarded `initialize` function in your concrete diamond instead (see `test/mocks/MockDiamond.sol` for a reference).
194191

195192
---
196193

@@ -256,27 +253,6 @@ Single 32-byte slot:
256253
- Atomic state transitions (single SSTORE)
257254
- Storage efficiency
258255

259-
### Initialization Storage Location
260-
261-
**Namespace**: Initialization state
262-
**Location**: `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffbf601132`
263-
264-
```
265-
Bit-packed single slot:
266-
Bit 0: initializing flag (1 = currently initializing)
267-
Bits 1-64: initialized version number
268-
```
269-
270-
**Example values:**
271-
- `0x0` = never initialized
272-
- `0x3` = initializing version 1 (bits: 1 | (1<<1))
273-
- `0x2` = initialized v1, not initializing
274-
275-
**Why bit packing:**
276-
- Two values in one storage slot
277-
- Atomic updates (single SSTORE)
278-
- Fast assembly checks
279-
280256
### ERC165 Interface Support
281257

282258
**Namespace**: `"diamond.lib.storage.ERC165"`
@@ -302,46 +278,22 @@ struct ERC165Storage {
302278

303279
```mermaid
304280
sequenceDiagram
305-
participant User
281+
participant Deployer
306282
participant Diamond
307-
participant InitLib
308283
participant DiamondLib
309284
participant InitContract
310285
311-
User->>Diamond: initialize(cuts, initAddr, initData)
312-
Diamond->>InitLib: preInitializer()
313-
InitLib->>InitLib: set initializing=true, version=1
314-
InitLib->>Diamond: continue
315-
286+
Deployer->>Diamond: constructor(cuts, initAddr, initData)
316287
Diamond->>DiamondLib: diamondCut(cuts)
317288
DiamondLib->>DiamondLib: validate & apply all cuts
318289
DiamondLib->>Diamond: emit DiamondCut event
319290
320291
Diamond->>InitContract: delegatecall(initData)
321292
InitContract->>Diamond: write state (owner, etc)
322293
InitContract-->>Diamond: return
323-
324-
Diamond->>InitLib: postInitializer()
325-
InitLib->>InitLib: set initializing=false, version++
326-
InitLib->>Diamond: emit Initialized event
327-
```
328-
329-
### Reinitialization Prevention
330-
331-
**Problem**: Without guards, anyone could reset state by calling init again.
332-
333-
**Solution: Version tracking**
334-
335-
```
336-
First init: version 0 → 1 (success)
337-
Retry same: version 1 → 1 (fails: InvalidInitialization)
338-
Upgrade init: version 1 → 2 (success: reinitializer)
339294
```
340295

341-
**Guarantees:**
342-
- Cannot reinitialize to same or lower version
343-
- Can upgrade to higher version (supports versioned upgrades)
344-
- Initialization flag prevents reentrancy during init
296+
Running the cut in the constructor is atomic: the diamond is never observable in a facetless, ownerless state, and there is no separate `initialize` transaction to front-run.
345297

346298
### MultiInit for Complex Setup
347299

script/DeployDiamond.s.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
1212
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
1313
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
1414
import {IFacet} from "@diamond/interfaces/IFacet.sol";
15-
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
1615
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
1716
import {Script} from "forge-std/Script.sol";
1817

@@ -70,7 +69,7 @@ contract DeployDiamond is Script {
7069

7170
// Deploy the Diamond contract with the facets and initialization args
7271
MockDiamond diamond = new MockDiamond();
73-
diamond.initialize(cut, diamondInit, abi.encodeWithSignature("init(address)", ContextLib.msgSender()));
72+
diamond.initialize(cut, diamondInit, abi.encodeWithSignature("init(address)", msg.sender));
7473
diamond_ = address(diamond);
7574
vm.stopBroadcast();
7675
}

src/Diamond.sol

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

4-
import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
5-
import {InitializableLib} from "@diamond/libraries/InitializableLib.sol";
4+
import {DiamondLib} from "@diamond/libraries/DiamondLib.sol";
65

76
/*
87
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀
@@ -35,23 +34,7 @@ import {InitializableLib} from "@diamond/libraries/InitializableLib.sol";
3534
/// @notice Implements ERC-2535 Diamond proxy pattern, allowing dynamic addition, replacement, and removal of facets
3635
/// @author David Dada <daveproxy80@gmail.com> (https://github.com/dadadave80)
3736
/// @author Modified from Nick Mudge (https://github.com/mudgen/diamond-3-hardhat/blob/main/contracts/Diamond.sol)
38-
contract Diamond {
39-
/// @notice Initializes the Diamond proxy with the provided facets and initialization parameters
40-
/// @param _init Address of the initialization contract
41-
/// @param _calldata Calldata to be passed to the initialization contract
42-
function initialize(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata)
43-
public
44-
payable
45-
virtual
46-
{
47-
bytes32 s = InitializableLib.initializableSlot();
48-
InitializableLib.preInitializer(s);
49-
50-
DiamondLib.diamondCut(_facetCuts, _init, _calldata);
51-
52-
InitializableLib.postInitializer(s);
53-
}
54-
37+
abstract contract Diamond {
5538
/// @notice Fallback function that delegates calls to the appropriate facet based on function selector
5639
/// @dev Reads the facet address from diamond storage and performs a delegatecall; reverts if selector is not found
5740
fallback() external payable virtual {
@@ -76,6 +59,4 @@ contract Diamond {
7659
}
7760
}
7861
}
79-
80-
receive() external payable virtual {}
8162
}

src/libraries/ContextLib.sol

Lines changed: 0 additions & 27 deletions
This file was deleted.

test/InitializableTester.t.sol

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ pragma solidity ^0.8.20;
33

44
import {Selectors} from "@diamond-test/helpers/Selectors.sol";
55
import {ReinitializableDiamond} from "@diamond-test/mocks/ReinitializableDiamond.sol";
6+
import {Initializable} from "@diamond-test/utils/Initializable.sol";
7+
import {Initialized, InvalidInitialization} from "@diamond-test/utils/InitializableLib.sol";
68
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
79
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
810
import {ERC165Facet} from "@diamond/facets/ERC165Facet.sol";
@@ -12,10 +14,24 @@ import {ERC165Init} from "@diamond/initializers/ERC165Init.sol";
1214
import {MultiInit} from "@diamond/initializers/MultiInit.sol";
1315
import {OwnableInit} from "@diamond/initializers/OwnableInit.sol";
1416
import {IFacet} from "@diamond/interfaces/IFacet.sol";
15-
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
1617
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
17-
import {Initialized, InvalidInitialization} from "@diamond/libraries/InitializableLib.sol";
18-
import {Test} from "forge-std/Test.sol";
18+
import {Test, Vm} from "forge-std/Test.sol";
19+
20+
/// @dev Exercises nested `initializer` calls within a constructor.
21+
contract NestedInitMock is Initializable {
22+
bool public wasInitializingAfterNested;
23+
24+
constructor() {
25+
outer();
26+
}
27+
28+
function outer() public initializer {
29+
inner();
30+
wasInitializingAfterNested = _isInitializing();
31+
}
32+
33+
function inner() public initializer {}
34+
}
1935

2036
/// @title InitializableTester
2137
/// @notice Tests for the initializable Diamond pattern
@@ -106,6 +122,22 @@ contract InitializableTester is Test {
106122
assertFalse(diamond.isInitializing());
107123
}
108124

125+
/// @notice A nested initializer inside a constructor finalizes exactly once:
126+
/// the inner call must not clear the initializing flag or emit its own event
127+
function testNestedConstructorInitializerFinalizesOnce() public {
128+
vm.recordLogs();
129+
NestedInitMock mock = new NestedInitMock();
130+
131+
uint256 count;
132+
Vm.Log[] memory logs = vm.getRecordedLogs();
133+
for (uint256 i; i < logs.length; ++i) {
134+
if (logs[i].topics[0] == keccak256("Initialized(uint64)")) ++count;
135+
}
136+
137+
assertEq(count, 1);
138+
assertTrue(mock.wasInitializingAfterNested());
139+
}
140+
109141
/// @notice Second call to initialize reverts with InvalidInitialization
110142
function testCannotInitializeTwice() public {
111143
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));

test/mocks/MockDiamond.sol

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

4-
import {Diamond, FacetCut} from "@diamond/Diamond.sol";
4+
import {Initializable} from "@diamond-test/utils/Initializable.sol";
5+
import {Diamond} from "@diamond/Diamond.sol";
6+
import {DiamondLib, FacetCut} from "@diamond/libraries/DiamondLib.sol";
57

6-
contract MockDiamond is Diamond {}
8+
/// @notice Reference initializable diamond: the abstract `Diamond` base composed
9+
/// with `Initializable` for factory/CREATE2 deployments where a constructor cut
10+
/// cannot run per-instance.
11+
contract MockDiamond is Diamond, Initializable {
12+
/// @notice Adds the provided facets and runs the initialization contract, at most once
13+
/// @param _init Address of the initialization contract
14+
/// @param _calldata Calldata to be passed to the initialization contract
15+
function initialize(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata)
16+
public
17+
payable
18+
virtual
19+
initializer
20+
{
21+
DiamondLib.diamondCut(_facetCuts, _init, _calldata);
22+
}
23+
24+
receive() external payable virtual {}
25+
}

0 commit comments

Comments
 (0)