Skip to content

Commit 64ccb7a

Browse files
committed
Add initializable pattern test coverage
Test initialization, re-initialization prevention, version tracking, sequential reinitializations, disableInitializers, and MultiInit composition through the Diamond.initialize() entrypoint.
1 parent 392fb29 commit 64ccb7a

2 files changed

Lines changed: 278 additions & 0 deletions

File tree

test/InitializableTester.t.sol

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {GetSelectors} from "@diamond-test/helpers/GetSelectors.sol";
5+
import {ReinitializableDiamond} from "@diamond-test/mocks/ReinitializableDiamond.sol";
6+
import {DiamondCutFacet} from "@diamond/facets/DiamondCutFacet.sol";
7+
import {DiamondLoupeFacet} from "@diamond/facets/DiamondLoupeFacet.sol";
8+
import {OwnableRolesFacet} from "@diamond/facets/OwnableRolesFacet.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";
12+
import {ContextLib} from "@diamond/libraries/ContextLib.sol";
13+
import {FacetCut, FacetCutAction} from "@diamond/libraries/DiamondLib.sol";
14+
import {Initialized, InvalidInitialization} from "@diamond/libraries/InitializableLib.sol";
15+
16+
/// @title InitializableTester
17+
/// @notice Tests for the initializable Diamond pattern
18+
contract InitializableTester is GetSelectors {
19+
ReinitializableDiamond diamond;
20+
DiamondCutFacet diamondCutFacet;
21+
DiamondLoupeFacet diamondLoupeFacet;
22+
OwnableRolesFacet ownableRolesFacet;
23+
MultiInit multiInit;
24+
OwnableInit ownableInit;
25+
ERC165Init erc165Init;
26+
27+
FacetCut[] cuts;
28+
29+
function setUp() public {
30+
// Deploy facets
31+
diamondCutFacet = new DiamondCutFacet();
32+
diamondLoupeFacet = new DiamondLoupeFacet();
33+
ownableRolesFacet = new OwnableRolesFacet();
34+
35+
// Deploy initializers
36+
multiInit = new MultiInit();
37+
ownableInit = new OwnableInit();
38+
erc165Init = new ERC165Init();
39+
40+
// Build facet cuts
41+
cuts.push(
42+
FacetCut({
43+
facetAddress: address(diamondCutFacet),
44+
action: FacetCutAction.Add,
45+
functionSelectors: _getSelectors("DiamondCutFacet")
46+
})
47+
);
48+
cuts.push(
49+
FacetCut({
50+
facetAddress: address(diamondLoupeFacet),
51+
action: FacetCutAction.Add,
52+
functionSelectors: _getSelectors("DiamondLoupeFacet")
53+
})
54+
);
55+
cuts.push(
56+
FacetCut({
57+
facetAddress: address(ownableRolesFacet),
58+
action: FacetCutAction.Add,
59+
functionSelectors: _getSelectors("OwnableRolesFacet")
60+
})
61+
);
62+
63+
// Deploy diamond (uninitialized)
64+
diamond = new ReinitializableDiamond();
65+
}
66+
67+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
68+
/* INITIALIZATION TESTS */
69+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
70+
71+
/// @notice Diamond starts uninitialized with version 0
72+
function testVersionIsZeroBeforeInit() public view {
73+
assertEq(diamond.getInitializedVersion(), 0);
74+
}
75+
76+
/// @notice Diamond is not in initializing state before init
77+
function testNotInitializingBeforeInit() public view {
78+
assertFalse(diamond.isInitializing());
79+
}
80+
81+
/// @notice Initialize sets version to 1 and emits Initialized event
82+
function testInitializeSetsVersionAndEmitsEvent() public {
83+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
84+
85+
vm.expectEmit(false, false, false, true);
86+
emit Initialized(1);
87+
88+
diamond.initialize(facetCuts, init, initCalldata);
89+
90+
assertEq(diamond.getInitializedVersion(), 1);
91+
assertFalse(diamond.isInitializing());
92+
}
93+
94+
/// @notice Second call to initialize reverts with InvalidInitialization
95+
function testCannotInitializeTwice() public {
96+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
97+
diamond.initialize(facetCuts, init, initCalldata);
98+
99+
// Second initialization should revert
100+
FacetCut[] memory emptyCuts = new FacetCut[](0);
101+
vm.expectRevert(InvalidInitialization.selector);
102+
diamond.initialize(emptyCuts, address(0), "");
103+
}
104+
105+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
106+
/* REINITIALIZATION TESTS */
107+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
108+
109+
/// @notice Reinitialize to version 2 after initial initialization
110+
function testReinitializeToVersion2() public {
111+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
112+
diamond.initialize(facetCuts, init, initCalldata);
113+
114+
FacetCut[] memory emptyCuts = new FacetCut[](0);
115+
116+
vm.expectEmit(false, false, false, true);
117+
emit Initialized(2);
118+
119+
diamond.reinitialize(emptyCuts, address(0), "", 2);
120+
121+
assertEq(diamond.getInitializedVersion(), 2);
122+
}
123+
124+
/// @notice Cannot reinitialize with same version
125+
function testCannotReinitializeWithSameVersion() public {
126+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
127+
diamond.initialize(facetCuts, init, initCalldata);
128+
129+
FacetCut[] memory emptyCuts = new FacetCut[](0);
130+
vm.expectRevert(InvalidInitialization.selector);
131+
diamond.reinitialize(emptyCuts, address(0), "", 1);
132+
}
133+
134+
/// @notice Cannot reinitialize with lower version
135+
function testCannotReinitializeWithLowerVersion() public {
136+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
137+
diamond.initialize(facetCuts, init, initCalldata);
138+
139+
FacetCut[] memory emptyCuts = new FacetCut[](0);
140+
diamond.reinitialize(emptyCuts, address(0), "", 2);
141+
142+
vm.expectRevert(InvalidInitialization.selector);
143+
diamond.reinitialize(emptyCuts, address(0), "", 1);
144+
}
145+
146+
/// @notice Version increments correctly through multiple reinitializations
147+
function testSequentialReinitializations() public {
148+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
149+
diamond.initialize(facetCuts, init, initCalldata);
150+
assertEq(diamond.getInitializedVersion(), 1);
151+
152+
FacetCut[] memory emptyCuts = new FacetCut[](0);
153+
diamond.reinitialize(emptyCuts, address(0), "", 2);
154+
assertEq(diamond.getInitializedVersion(), 2);
155+
156+
diamond.reinitialize(emptyCuts, address(0), "", 5);
157+
assertEq(diamond.getInitializedVersion(), 5);
158+
}
159+
160+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
161+
/* DISABLE INITIALIZERS TESTS */
162+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
163+
164+
/// @notice disableInitializers prevents future initialization
165+
function testDisableInitializersPreventsInit() public {
166+
diamond.disableInitializers();
167+
168+
FacetCut[] memory emptyCuts = new FacetCut[](0);
169+
vm.expectRevert(InvalidInitialization.selector);
170+
diamond.initialize(emptyCuts, address(0), "");
171+
}
172+
173+
/// @notice disableInitializers prevents future reinitialization
174+
function testDisableInitializersPreventsReinit() public {
175+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
176+
diamond.initialize(facetCuts, init, initCalldata);
177+
178+
diamond.disableInitializers();
179+
180+
FacetCut[] memory emptyCuts = new FacetCut[](0);
181+
vm.expectRevert(InvalidInitialization.selector);
182+
diamond.reinitialize(emptyCuts, address(0), "", 2);
183+
}
184+
185+
/// @notice disableInitializers sets version to max uint64
186+
function testDisableInitializersSetsMaxVersion() public {
187+
diamond.disableInitializers();
188+
assertEq(diamond.getInitializedVersion(), type(uint64).max);
189+
}
190+
191+
/// @notice disableInitializers emits Initialized with max uint64
192+
function testDisableInitializersEmitsEvent() public {
193+
vm.expectEmit(false, false, false, true);
194+
emit Initialized(type(uint64).max);
195+
196+
diamond.disableInitializers();
197+
}
198+
199+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
200+
/* MULTI-INIT TESTS */
201+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
202+
203+
/// @notice MultiInit correctly initializes owner and ERC165 through initialize
204+
function testMultiInitSetsOwnerAndInterfaces() public {
205+
(FacetCut[] memory facetCuts, address init, bytes memory initCalldata) = _buildInitArgs(address(this));
206+
diamond.initialize(facetCuts, init, initCalldata);
207+
208+
// Verify owner was set
209+
OwnableRolesFacet ownable = OwnableRolesFacet(address(diamond));
210+
assertEq(ownable.owner(), address(this));
211+
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
218+
}
219+
220+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
221+
/* HELPERS */
222+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
223+
224+
function _buildInitArgs(address _owner)
225+
internal
226+
view
227+
returns (FacetCut[] memory facetCuts_, address init_, bytes memory initCalldata_)
228+
{
229+
facetCuts_ = new FacetCut[](cuts.length);
230+
for (uint256 i; i < cuts.length; ++i) {
231+
facetCuts_[i] = cuts[i];
232+
}
233+
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);
245+
}
246+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import {Diamond, FacetCut} from "@diamond/Diamond.sol";
5+
import {DiamondLib} from "@diamond/libraries/DiamondLib.sol";
6+
import {InitializableLib} from "@diamond/libraries/InitializableLib.sol";
7+
8+
contract ReinitializableDiamond is Diamond {
9+
function reinitialize(FacetCut[] calldata _facetCuts, address _init, bytes calldata _calldata, uint64 _version)
10+
external
11+
payable
12+
{
13+
bytes32 s = InitializableLib.initializableSlot();
14+
InitializableLib.preReinitializer(s, _version);
15+
16+
DiamondLib.diamondCutCalldata(_facetCuts, _init, _calldata);
17+
18+
InitializableLib.postReinitializer(s, _version);
19+
}
20+
21+
function getInitializedVersion() external view returns (uint64) {
22+
return InitializableLib.getInitializedVersion(InitializableLib.initializableSlot());
23+
}
24+
25+
function isInitializing() external view returns (bool) {
26+
return InitializableLib.isInitializing(InitializableLib.initializableSlot());
27+
}
28+
29+
function disableInitializers() external {
30+
InitializableLib.disableInitializers(InitializableLib.initializableSlot());
31+
}
32+
}

0 commit comments

Comments
 (0)