Skip to content

Commit 99ec3ea

Browse files
committed
feat(LibInitializeGuard): allow migration without initialization
1 parent a0fa1b4 commit 99ec3ea

File tree

1 file changed

+57
-46
lines changed

1 file changed

+57
-46
lines changed

script/libraries/LibInitializeGuard.sol

Lines changed: 57 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity >=0.6.2 <0.9.0;
33
pragma experimental ABIEncoderV2;
44

5+
import { Math } from "../../dependencies/@openzeppelin-4.9.3/contracts/utils/math/Math.sol";
56
import { EnumerableSet } from "../../dependencies/@openzeppelin-4.9.3/contracts/utils/structs/EnumerableSet.sol";
67
import { JSONParserLib } from "../../dependencies/@solady-0.0.228/src/utils/JSONParserLib.sol";
78
import { LibString } from "../../dependencies/@solady-0.0.228/src/utils/LibString.sol";
@@ -34,13 +35,12 @@ interface IERC1967 {
3435
*/
3536
library LibInitializeGuard {
3637
using StdStyle for *;
38+
using LibString for string;
3739
using JSONParserLib for string;
3840
using JSONParserLib for JSONParserLib.Item;
39-
using LibString for string;
4041
using EnumerableSet for EnumerableSet.AddressSet;
4142

42-
struct InitializedSlot {
43-
bool found;
43+
struct InitLocation {
4444
bytes32 slot;
4545
uint256 bitOffset;
4646
uint256 nBit;
@@ -51,7 +51,7 @@ library LibInitializeGuard {
5151
EnumerableSet.AddressSet _proxies;
5252
mapping(address addr => uint256) _lastInitVer;
5353
mapping(address addr => Vm.ChainInfo) _chainInfo;
54-
mapping(address proxy => InitializedSlot) _initSlot;
54+
mapping(address proxy => InitLocation) _initSlot;
5555
mapping(address logic => address proxy) _logic2Proxy;
5656
}
5757

@@ -81,23 +81,24 @@ library LibInitializeGuard {
8181
for (uint256 i; i < stateDiffs.length; ++i) {
8282
address addr = stateDiffs[i].account;
8383

84-
if ($._proxies.contains(addr) && !$._initSlot[addr].found) {
84+
if ($._proxies.contains(addr) && $._initSlot[addr].nBit != 0) {
8585
// Record the chain info and initialized slot of the `addr`.
8686
$._chainInfo[addr] = stateDiffs[i].chainInfo;
8787
$._initSlot[addr] = _getInitializedSlot($, addr);
8888
}
8989

9090
if ($._logics.contains(addr) && stateDiffs[i].kind == VmSafe.AccountAccessKind.DelegateCall) {
9191
address proxy = $._logic2Proxy[addr];
92+
InitLocation memory initLoc = $._initSlot[proxy];
9293
Vm.StorageAccess[] memory accs = stateDiffs[i].storageAccesses;
9394

9495
for (uint256 j; j < accs.length; ++j) {
9596
// 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)) {
9798
continue;
9899
}
99100

100-
bool shouldSkip = _validateInitChanges(accs[j], $._initSlot[proxy]);
101+
bool shouldSkip = _validateInitChanges(accs[j], initLoc);
101102
if (shouldSkip) continue;
102103
}
103104
}
@@ -117,11 +118,10 @@ library LibInitializeGuard {
117118

118119
for (uint256 i; i < length; ++i) {
119120
uint256 lastInitVer = $cache._lastInitVer[logics[i]];
120-
address proxy = $cache._logic2Proxy[logics[i]];
121+
121122
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!")
125125
);
126126
}
127127
}
@@ -137,22 +137,29 @@ library LibInitializeGuard {
137137

138138
for (uint256 i; i < length; ++i) {
139139
address proxy = proxies[i];
140-
InitializedSlot memory slot = $cache._initSlot[proxy];
140+
InitLocation memory initLoc = $cache._initSlot[proxy];
141141

142142
require(
143-
slot.found,
143+
initLoc.nBit != 0,
144144
string.concat("LibInitializeGuard: Proxy ", vm.getLabel(proxies[i]), " does not have `_initialized` slot!")
145145
);
146146

147147
uint256 lastInitVer = $cache._lastInitVer[proxy];
148+
// ToDo(TuDo1403): handle multi-chain
149+
uint256 actualInitVer = _getVersionFromSlotValue(vm.load(proxy, initLoc.slot), initLoc.bitOffset, initLoc.nBit);
148150

149151
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())
151154
);
155+
// Allow upgrade without initialization
156+
require(actualInitVer >= lastInitVer, "LibInitializeGuard: `lastInitVer` > `actualInitVer`!");
157+
158+
actualInitVer = Math.max(lastInitVer, actualInitVer);
152159

153160
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)
156163
) {
157164
string memory ret = vm.prompt(
158165
string.concat(
@@ -165,20 +172,23 @@ library LibInitializeGuard {
165172
"to continue..."
166173
)
167174
);
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+
);
169179

170180
continue;
171181
}
172182

173183
uint256 initFnCount = _getInitializeFnCount($cache, proxy);
174184
require(
175-
lastInitVer == initFnCount,
185+
actualInitVer == initFnCount,
176186
string.concat(
177187
"LibInitializeGuard: Invalid initialized version!",
178188
" Expected: ",
179189
vm.toString(initFnCount),
180190
" Got: ",
181-
vm.toString(lastInitVer)
191+
vm.toString(actualInitVer)
182192
)
183193
);
184194
}
@@ -188,24 +198,24 @@ library LibInitializeGuard {
188198
* @dev Validate the intermediate changes of the `_initialized` slot of the given `access` storage.
189199
*
190200
* @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.
192202
* @return shouldSkip Whether to skip the validation.
193203
*/
194-
function _validateInitChanges(Vm.StorageAccess memory acc, InitializedSlot memory slot)
204+
function _validateInitChanges(Vm.StorageAccess memory acc, InitLocation memory initLoc)
195205
private
196206
view
197207
returns (bool shouldSkip)
198208
{
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);
203211

204212
// Skip if `_initialized` bytes location in `slot` does not change
205213
// Assume other data in given slot is not related to initialized version
206214
if (prvVer == newVer) return true;
215+
216+
uint256 initBit = initLoc.nBit;
207217
// 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)) {
209219
console.log("[INIT] %s: Disabled initialized version", vm.getLabel(acc.account));
210220
return true;
211221
}
@@ -219,19 +229,21 @@ library LibInitializeGuard {
219229
* @dev Record the upgrades and initializations of proxies and logics.
220230
*/
221231
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) {
223235
address emitter = logs[i].emitter;
224-
bytes32 eventSig = logs[i].topics[0];
236+
bytes32 eventTopic = logs[i].topics[0];
225237

226-
if (eventSig == InitializableOZV4.Initialized.selector) {
238+
if (eventTopic == InitializableOZV4.Initialized.selector) {
227239
$cache._lastInitVer[emitter] = abi.decode(logs[i].data, (uint8));
228240
}
229241

230-
if (eventSig == InitializableOZV5.Initialized.selector) {
242+
if (eventTopic == InitializableOZV5.Initialized.selector) {
231243
$cache._lastInitVer[emitter] = abi.decode(logs[i].data, (uint64));
232244
}
233245

234-
if (eventSig == IERC1967.Upgraded.selector) {
246+
if (eventTopic == IERC1967.Upgraded.selector) {
235247
address logic = address(uint160(uint256(logs[i].topics[1])));
236248
$cache._logics.add(logic);
237249
$cache._proxies.add(emitter);
@@ -240,6 +252,14 @@ library LibInitializeGuard {
240252
}
241253
}
242254

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+
243263
/**
244264
* @dev Get the number of `initialize` functions of the given `proxy` by inspecting its storage layout using `forge inspect <contract_name> methodIdentifiers`.
245265
*/
@@ -261,9 +281,13 @@ library LibInitializeGuard {
261281

262282
/**
263283
* @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.
265285
*/
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+
267291
string[] memory inputs = new string[](4);
268292
inputs[0] = "forge";
269293
inputs[1] = "inspect";
@@ -278,26 +302,13 @@ library LibInitializeGuard {
278302
JSONParserLib.Item memory storageSlot = layout.at(i);
279303

280304
if (keccak256(bytes(storageSlot.at('"label"').value().decodeString())) == keccak256("_initialized")) {
281-
initSlot.found = true;
282305
initSlot.bitOffset = storageSlot.at('"offset"').value().parseUint() * 8;
283306
initSlot.nBit = N_BIT_INIT_V4;
284307
initSlot.slot = bytes32(vm.parseUint(storageSlot.at('"slot"').value().decodeString()));
285308

286309
return initSlot;
287310
}
288311
}
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-
}
301312
}
302313

303314
/**

0 commit comments

Comments
 (0)