2
2
pragma solidity >= 0.6.2 < 0.9.0 ;
3
3
pragma experimental ABIEncoderV2;
4
4
5
+ import { Math } from "../../dependencies/@openzeppelin-4.9.3/contracts/utils/math/Math.sol " ;
5
6
import { EnumerableSet } from "../../dependencies/@openzeppelin-4.9.3/contracts/utils/structs/EnumerableSet.sol " ;
6
7
import { JSONParserLib } from "../../dependencies/@solady-0.0.228/src/utils/JSONParserLib.sol " ;
7
8
import { LibString } from "../../dependencies/@solady-0.0.228/src/utils/LibString.sol " ;
@@ -34,13 +35,12 @@ interface IERC1967 {
34
35
*/
35
36
library LibInitializeGuard {
36
37
using StdStyle for * ;
38
+ using LibString for string ;
37
39
using JSONParserLib for string ;
38
40
using JSONParserLib for JSONParserLib.Item;
39
- using LibString for string ;
40
41
using EnumerableSet for EnumerableSet.AddressSet;
41
42
42
- struct InitializedSlot {
43
- bool found;
43
+ struct InitLocation {
44
44
bytes32 slot;
45
45
uint256 bitOffset;
46
46
uint256 nBit;
@@ -51,7 +51,7 @@ library LibInitializeGuard {
51
51
EnumerableSet.AddressSet _proxies;
52
52
mapping (address addr = > uint256 ) _lastInitVer;
53
53
mapping (address addr = > Vm.ChainInfo) _chainInfo;
54
- mapping (address proxy = > InitializedSlot ) _initSlot;
54
+ mapping (address proxy = > InitLocation ) _initSlot;
55
55
mapping (address logic = > address proxy ) _logic2Proxy;
56
56
}
57
57
@@ -81,23 +81,24 @@ library LibInitializeGuard {
81
81
for (uint256 i; i < stateDiffs.length ; ++ i) {
82
82
address addr = stateDiffs[i].account;
83
83
84
- if ($._proxies.contains (addr) && ! $._initSlot[addr].found ) {
84
+ if ($._proxies.contains (addr) && $._initSlot[addr].nBit != 0 ) {
85
85
// Record the chain info and initialized slot of the `addr`.
86
86
$._chainInfo[addr] = stateDiffs[i].chainInfo;
87
87
$._initSlot[addr] = _getInitializedSlot ($, addr);
88
88
}
89
89
90
90
if ($._logics.contains (addr) && stateDiffs[i].kind == VmSafe.AccountAccessKind.DelegateCall) {
91
91
address proxy = $._logic2Proxy[addr];
92
+ InitLocation memory initLoc = $._initSlot[proxy];
92
93
Vm.StorageAccess[] memory accs = stateDiffs[i].storageAccesses;
93
94
94
95
for (uint256 j; j < accs.length ; ++ j) {
95
96
// Skip if changes does not made changes to `initSlot` by `proxy` to `logic`
96
- if (! (accs[j].isWrite && accs[j].account == proxy && accs[j].slot == $._initSlot[proxy] .slot)) {
97
+ if (! (accs[j].isWrite && accs[j].account == proxy && accs[j].slot == initLoc .slot)) {
97
98
continue ;
98
99
}
99
100
100
- bool shouldSkip = _validateInitChanges (accs[j], $._initSlot[proxy] );
101
+ bool shouldSkip = _validateInitChanges (accs[j], initLoc );
101
102
if (shouldSkip) continue ;
102
103
}
103
104
}
@@ -117,11 +118,10 @@ library LibInitializeGuard {
117
118
118
119
for (uint256 i; i < length; ++ i) {
119
120
uint256 lastInitVer = $cache._lastInitVer[logics[i]];
120
- address proxy = $cache._logic2Proxy[logics[i]];
121
+
121
122
require (
122
- (lastInitVer == MAX_VER_V4 && $cache._initSlot[proxy].nBit == N_BIT_INIT_V4)
123
- || (lastInitVer == MAX_VER_V5 && $cache._initSlot[proxy].nBit == N_BIT_INIT_V5),
124
- string .concat ("LibInitializeGuard: Logic " , vm.getLabel (logics[i]), " does not disable initialized version! " )
123
+ lastInitVer == MAX_VER_V4 || lastInitVer == MAX_VER_V5,
124
+ string .concat ("LibInitializeGuard: Logic " , vm.getLabel (logics[i]), " did not disable initialized version! " )
125
125
);
126
126
}
127
127
}
@@ -137,22 +137,29 @@ library LibInitializeGuard {
137
137
138
138
for (uint256 i; i < length; ++ i) {
139
139
address proxy = proxies[i];
140
- InitializedSlot memory slot = $cache._initSlot[proxy];
140
+ InitLocation memory initLoc = $cache._initSlot[proxy];
141
141
142
142
require (
143
- slot.found ,
143
+ initLoc.nBit != 0 ,
144
144
string .concat ("LibInitializeGuard: Proxy " , vm.getLabel (proxies[i]), " does not have `_initialized` slot! " )
145
145
);
146
146
147
147
uint256 lastInitVer = $cache._lastInitVer[proxy];
148
+ // ToDo(TuDo1403): handle multi-chain
149
+ uint256 actualInitVer = _getVersionFromSlotValue (vm.load (proxy, initLoc.slot), initLoc.bitOffset, initLoc.nBit);
148
150
149
151
require (
150
- lastInitVer != 0 , string .concat ("LibInitializeGuard: Proxy " , vm.getLabel (proxy), " does not initialize! " )
152
+ lastInitVer != 0 || actualInitVer != 0 ,
153
+ string .concat ("LibInitializeGuard: Proxy " , vm.getLabel (proxy), " does not initialize! " .red ())
151
154
);
155
+ // Allow upgrade without initialization
156
+ require (actualInitVer >= lastInitVer, "LibInitializeGuard: `lastInitVer` > `actualInitVer`! " );
157
+
158
+ actualInitVer = Math.max (lastInitVer, actualInitVer);
152
159
153
160
if (
154
- (lastInitVer == MAX_VER_V4 && slot .nBit == N_BIT_INIT_V4)
155
- || (lastInitVer == MAX_VER_V5 && slot .nBit == N_BIT_INIT_V5)
161
+ (actualInitVer == MAX_VER_V4 && initLoc .nBit == N_BIT_INIT_V4)
162
+ || (actualInitVer == MAX_VER_V5 && initLoc .nBit == N_BIT_INIT_V5)
156
163
) {
157
164
string memory ret = vm.prompt (
158
165
string .concat (
@@ -165,20 +172,23 @@ library LibInitializeGuard {
165
172
"to continue... "
166
173
)
167
174
);
168
- require (keccak256 (bytes (vm.toLowercase (ret))) == keccak256 ("yes " ), "LibInitializeGuard: User aborted! " );
175
+ require (
176
+ keccak256 (bytes (vm.toLowercase (ret))) == keccak256 ("yes " ),
177
+ "LibInitializeGuard: Aborted due to unintended disable initialization! "
178
+ );
169
179
170
180
continue ;
171
181
}
172
182
173
183
uint256 initFnCount = _getInitializeFnCount ($cache, proxy);
174
184
require (
175
- lastInitVer == initFnCount,
185
+ actualInitVer == initFnCount,
176
186
string .concat (
177
187
"LibInitializeGuard: Invalid initialized version! " ,
178
188
" Expected: " ,
179
189
vm.toString (initFnCount),
180
190
" Got: " ,
181
- vm.toString (lastInitVer )
191
+ vm.toString (actualInitVer )
182
192
)
183
193
);
184
194
}
@@ -188,24 +198,24 @@ library LibInitializeGuard {
188
198
* @dev Validate the intermediate changes of the `_initialized` slot of the given `access` storage.
189
199
*
190
200
* @param acc The storage access of data.
191
- * @param slot The initialized slot of the proxy.
201
+ * @param initLoc The initialized location data of the proxy.
192
202
* @return shouldSkip Whether to skip the validation.
193
203
*/
194
- function _validateInitChanges (Vm.StorageAccess memory acc , InitializedSlot memory slot )
204
+ function _validateInitChanges (Vm.StorageAccess memory acc , InitLocation memory initLoc )
195
205
private
196
206
view
197
207
returns (bool shouldSkip )
198
208
{
199
- uint256 mask = (1 << slot.nBit) - 1 ;
200
-
201
- uint256 prvVer = (uint256 (acc.previousValue) >> slot.bitOffset) & mask;
202
- uint256 newVer = (uint256 (acc.newValue) >> slot.bitOffset) & mask;
209
+ uint256 prvVer = _getVersionFromSlotValue (acc.previousValue, initLoc.bitOffset, initLoc.nBit);
210
+ uint256 newVer = _getVersionFromSlotValue (acc.newValue, initLoc.bitOffset, initLoc.nBit);
203
211
204
212
// Skip if `_initialized` bytes location in `slot` does not change
205
213
// Assume other data in given slot is not related to initialized version
206
214
if (prvVer == newVer) return true ;
215
+
216
+ uint256 initBit = initLoc.nBit;
207
217
// Skip if the proxy disable initialized version
208
- if ((newVer == MAX_VER_V4 && slot.nBit == N_BIT_INIT_V4) || (newVer == MAX_VER_V5 && slot.nBit == N_BIT_INIT_V5)) {
218
+ if ((newVer == MAX_VER_V4 && initBit == N_BIT_INIT_V4) || (newVer == MAX_VER_V5 && initBit == N_BIT_INIT_V5)) {
209
219
console.log ("[INIT] %s: Disabled initialized version " , vm.getLabel (acc.account));
210
220
return true ;
211
221
}
@@ -219,19 +229,21 @@ library LibInitializeGuard {
219
229
* @dev Record the upgrades and initializations of proxies and logics.
220
230
*/
221
231
function _recordUpgradesAndInitializations (Cache storage $cache, Vm.Log[] memory logs ) private {
222
- for (uint256 i; i < logs.length ; ++ i) {
232
+ uint256 length = logs.length ;
233
+
234
+ for (uint256 i; i < length; ++ i) {
223
235
address emitter = logs[i].emitter;
224
- bytes32 eventSig = logs[i].topics[0 ];
236
+ bytes32 eventTopic = logs[i].topics[0 ];
225
237
226
- if (eventSig == InitializableOZV4.Initialized.selector ) {
238
+ if (eventTopic == InitializableOZV4.Initialized.selector ) {
227
239
$cache._lastInitVer[emitter] = abi.decode (logs[i].data, (uint8 ));
228
240
}
229
241
230
- if (eventSig == InitializableOZV5.Initialized.selector ) {
242
+ if (eventTopic == InitializableOZV5.Initialized.selector ) {
231
243
$cache._lastInitVer[emitter] = abi.decode (logs[i].data, (uint64 ));
232
244
}
233
245
234
- if (eventSig == IERC1967 .Upgraded.selector ) {
246
+ if (eventTopic == IERC1967 .Upgraded.selector ) {
235
247
address logic = address (uint160 (uint256 (logs[i].topics[1 ])));
236
248
$cache._logics.add (logic);
237
249
$cache._proxies.add (emitter);
@@ -240,6 +252,14 @@ library LibInitializeGuard {
240
252
}
241
253
}
242
254
255
+ /**
256
+ * @dev Get the version from the given `value` at the `bitOffset` and `nBit`.
257
+ */
258
+ function _getVersionFromSlotValue (bytes32 value , uint256 bitOffset , uint256 nBit ) private pure returns (uint256 ) {
259
+ uint256 mask = (1 << nBit) - 1 ;
260
+ return (uint256 (value) >> bitOffset) & mask;
261
+ }
262
+
243
263
/**
244
264
* @dev Get the number of `initialize` functions of the given `proxy` by inspecting its storage layout using `forge inspect <contract_name> methodIdentifiers`.
245
265
*/
@@ -261,9 +281,13 @@ library LibInitializeGuard {
261
281
262
282
/**
263
283
* @dev Get `_initialized` slot of the given `proxy` by inspecting its storage layout using `forge inspect <contract_name> storage`.
264
- * If the slot is not found, infer it used OpenZeppelin v5 `Initializable` extension and see if the custom storage slot has value .
284
+ * If the slot is not found, infer it used OpenZeppelin v5 `Initializable` extension.
265
285
*/
266
- function _getInitializedSlot (Cache storage $cache, address proxy ) private returns (InitializedSlot memory initSlot ) {
286
+ function _getInitializedSlot (Cache storage $cache, address proxy ) private returns (InitLocation memory initSlot ) {
287
+ // Assume the proxy uses OpenZeppelin v5 `Initializable` extension
288
+ initSlot.nBit = N_BIT_INIT_V5;
289
+ initSlot.slot = INITIALIZABLE_STORAGE_OZV5;
290
+
267
291
string [] memory inputs = new string [](4 );
268
292
inputs[0 ] = "forge " ;
269
293
inputs[1 ] = "inspect " ;
@@ -278,26 +302,13 @@ library LibInitializeGuard {
278
302
JSONParserLib.Item memory storageSlot = layout.at (i);
279
303
280
304
if (keccak256 (bytes (storageSlot.at ('"label" ' ).value ().decodeString ())) == keccak256 ("_initialized " )) {
281
- initSlot.found = true ;
282
305
initSlot.bitOffset = storageSlot.at ('"offset" ' ).value ().parseUint () * 8 ;
283
306
initSlot.nBit = N_BIT_INIT_V4;
284
307
initSlot.slot = bytes32 (vm.parseUint (storageSlot.at ('"slot" ' ).value ().decodeString ()));
285
308
286
309
return initSlot;
287
310
}
288
311
}
289
-
290
- if ($cache._lastInitVer[proxy] != 0 ) {
291
- // assume given proxy use `Initializable` from OpenZeppelin v5
292
- // ToDo(TuDo1403): switch to `forkId` if working multichain
293
- bytes32 slotValue = vm.load (proxy, INITIALIZABLE_STORAGE_OZV5);
294
- if (slotValue != 0 ) {
295
- initSlot.found = true ;
296
- initSlot.bitOffset = 0 ;
297
- initSlot.nBit = N_BIT_INIT_V5;
298
- initSlot.slot = INITIALIZABLE_STORAGE_OZV5;
299
- }
300
- }
301
312
}
302
313
303
314
/**
0 commit comments