Skip to content

Commit

Permalink
temp
Browse files Browse the repository at this point in the history
  • Loading branch information
Do Tu authored and Do Tu committed Aug 12, 2024
1 parent ab9eed2 commit 38f2226
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 12 deletions.
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ localhost = "http://localhost:8545"
[dependencies]
"@openzeppelin" = { version = "4.9.3", url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v4.9.3.zip" }
"@forge-std" = { version = "1.9.1", url = "https://github.com/foundry-rs/forge-std/archive/refs/tags/v1.9.1.zip" }
"@solady" = { version = "0.0.228", url = "https://github.com/Vectorized/solady/archive/refs/tags/v0.0.228.zip" }
"@solady" = { version = "0.0.228", url = "https://github.com/Vectorized/solady/archive/refs/tags/v0.0.228.zip" }
"@openzeppelin-v5" = { version = "5.0.2", url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip" }
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@fdk/=script/
@forge-std-1.9.1=dependencies/@forge-std-1.9.1/src/
@openzeppelin-4.9.3=dependencies/@openzeppelin-4.9.3
@solady-0.0.228=dependencies/@solady-0.0.228
@solady-0.0.228=dependencies/@solady-0.0.228
@openzeppelin-v5-5.0.2=dependencies/@openzeppelin-v5-5.0.2
9 changes: 9 additions & 0 deletions script/BaseMigration.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ScriptExtended, IScriptExtended } from "./extensions/ScriptExtended.s.s
import { OnchainExecutor } from "./OnchainExecutor.s.sol"; // cheat to load artifact to parent `out` directory
import { IMigrationScript } from "./interfaces/IMigrationScript.sol";
import { LibProxy } from "./libraries/LibProxy.sol";
import { LibInitializeGuard } from "./libraries/LibInitializeGuard.sol";
import { DefaultContract } from "./utils/DefaultContract.sol";
import { ProxyInterface, LibDeploy, DeployInfo, UpgradeInfo } from "./libraries/LibDeploy.sol";
import { cheatBroadcast } from "./utils/Helpers.sol";
Expand Down Expand Up @@ -38,6 +39,14 @@ abstract contract BaseMigration is ScriptExtended {
ProxyInterface /* proxyInterface */
) internal virtual { }

function _beforeRunningScript() internal virtual override {
LibInitializeGuard.start();
}

function _afterRunningScript() internal virtual override {
LibInitializeGuard.end();
}

function _sharedArguments() internal virtual returns (bytes memory rawSharedArgs);

function _injectDependencies() internal virtual { }
Expand Down
6 changes: 6 additions & 0 deletions script/configs/NetworkConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,18 @@ abstract contract NetworkConfig is INetworkConfig {

function setForkMode(bool shouldEnable) public virtual {
_isForkModeEnabled = shouldEnable;
emit ForkModeUpdated(shouldEnable);
}

function getNetworkData(TNetwork network) public view virtual returns (NetworkData memory) {
return _networkDataMap[network];
}

function getNetworkTypeByForkId(uint256 forkId) public view virtual returns (TNetwork network) {
network = _forkId2Network[forkId];
if (network == TNetwork.wrap(0x0)) network = DefaultNetwork.LocalHost.key();
}

function getDeploymentDirectory(TNetwork network) public view virtual returns (string memory dirPath) {
string memory dirName = network.dir();
require(bytes(dirName).length != 0, "NetworkConfig: Deployment directory not found");
Expand Down
10 changes: 9 additions & 1 deletion script/extensions/ScriptExtended.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ abstract contract ScriptExtended is BaseScriptExtended, Script, StdAssertions, I
console.log("ScriptExtended:".blue(), "Prechecking completed in", vm.toString(end - start), "milliseconds.\n");
}

_beforeRunningScript();

(bool success, bytes memory data) = address(this).delegatecall(callData);
success.handleRevert(msg.sig, data);

_afterRunningScript();

if (vme.getRuntimeConfig().disablePostcheck) {
console.log("\nPostchecking is disabled.".yellow());
} else {
Expand All @@ -99,6 +103,10 @@ abstract contract ScriptExtended is BaseScriptExtended, Script, StdAssertions, I
}
}

function _beforeRunningScript() internal virtual { }

function _afterRunningScript() internal virtual { }

function _requireOn(TNetwork networkType) private view {
require(network() == networkType, string.concat("ScriptExtended: Only allowed on ", vme.getAlias(networkType)));
}
Expand Down Expand Up @@ -137,7 +145,7 @@ abstract contract ScriptExtended is BaseScriptExtended, Script, StdAssertions, I
}

function prankOrBroadcast(address by) internal virtual {
if (vme.isPostChecking()) {
if (vme.isPostChecking() || vme.isPreChecking()) {
vm.prank(by);
} else {
vm.broadcast(by);
Expand Down
4 changes: 4 additions & 0 deletions script/interfaces/configs/INetworkConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface INetworkConfig {
string explorer;
}

event ForkModeUpdated(bool enabled);

function setNetworkInfo(NetworkData memory networkData) external;

function setForkMode(bool shouldEnable) external;
Expand All @@ -25,6 +27,8 @@ interface INetworkConfig {

function getNetworkData(TNetwork network) external view returns (NetworkData memory);

function getNetworkTypeByForkId(uint256 forkId) external view returns (TNetwork network);

function getForkId(TNetwork network) external view returns (uint256 forkId);

function getForkId(TNetwork, uint256 forkBlockNumber) external view returns (uint256 forkId);
Expand Down
6 changes: 3 additions & 3 deletions script/libraries/LibErrorHandler.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;

library LibErrorHandler {
/// @dev Reserves error definition to upload to signature database.
error ExternalCallFailed(bytes4 msgSig, bytes4 callSig);

/// @notice handle low level call revert if call failed,
/// If external call return empty bytes, reverts with custom error.
Expand Down
184 changes: 184 additions & 0 deletions script/libraries/LibInitializeGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.2 <0.9.0;
pragma experimental ABIEncoderV2;

import { EnumerableSet } from "../../dependencies/@openzeppelin-4.9.3/contracts/utils/structs/EnumerableSet.sol";
import { JSONParserLib } from "../../dependencies/@solady-0.0.228/src/utils/JSONParserLib.sol";
import { Vm } from "../../dependencies/@forge-std-1.9.1/src/Vm.sol";
import { StdStorage, stdStorage } from "../../dependencies/@forge-std-1.9.1/src/StdStorage.sol";
import { console, vm, vme } from "../utils/Helpers.sol";
import { TNetwork } from "../types/TNetwork.sol";
import { TContract } from "../types/TContract.sol";

interface InitializableOZV4 {
event Initialized(uint8);
}

interface InitializableOZV5 {
event Initialized(uint64);
}

interface IERC1967 {
event Upgraded(address indexed);
}

library LibInitializeGuard {
using JSONParserLib for *;
using stdStorage for StdStorage;
using EnumerableSet for EnumerableSet.AddressSet;

struct InitializedSlot {
bool found;
bytes32 slot;
uint256 offset;
}

struct Cache {
EnumerableSet.AddressSet _proxies;
mapping(address addr => uint256) _initVersion;
mapping(address addr => Vm.ChainInfo) _chainInfo;
mapping(address proxy => InitializedSlot) _initSlot;
mapping(address proxy => address logic) _proxy2Logic;
}

uint256 internal constant MAX_INIT_VERSION_OZV4 = type(uint8).max;
uint256 internal constant MAX_INIT_VERSION_OZV5 = type(uint64).max;

// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE_OZV5 =
0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

function start() internal {
bool started;
try vme.getUserDefinedConfig("LibInitializeGuard") returns (bytes memory raw) {
if (raw.length != 0) {
started = abi.decode(raw, (bool));
}
} catch { }

require(!started, "LibInitializeGuard: already initialized");
vme.setUserDefinedConfig("LibInitializeGuard", abi.encode(true));

vm.recordLogs();
vm.startStateDiffRecording();
}

function end() internal {
bool started;
try vme.getUserDefinedConfig("LibInitializeGuard") returns (bytes memory raw) {
if (raw.length != 0) {
started = abi.decode(raw, (bool));
}
} catch { }

require(started, "LibInitializeGuard: not initialized");
vme.setUserDefinedConfig("LibInitializeGuard", abi.encode(false));

Vm.Log[] memory logs = vm.getRecordedLogs();
Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff();
Cache storage $ = _getCacheStorage();

for (uint256 i; i < logs.length; ++i) {
address emitter = logs[i].emitter;
bytes32 eventSig = logs[i].topics[0];

if (eventSig == InitializableOZV4.Initialized.selector) {
$._initVersion[emitter] = abi.decode(logs[i].data, (uint8));
}
if (eventSig == InitializableOZV5.Initialized.selector) {
$._initVersion[emitter] = abi.decode(logs[i].data, (uint64));
}
if (eventSig == IERC1967.Upgraded.selector) {
$._proxies.add(emitter);
$._proxy2Logic[emitter] = address(uint160(uint256(logs[i].topics[1])));
}
}

for (uint256 i; i < stateDiffs.length; ++i) {
if ($._proxies.contains(stateDiffs[i].account) && !$._initSlot[stateDiffs[i].account].found) {
address proxy = stateDiffs[i].account;

$._chainInfo[proxy] = stateDiffs[i].chainInfo;
$._initSlot[proxy] = _getInitializedSlot($, proxy);
}
}

address[] memory proxies = $._proxies.values();
for (uint256 i; i < proxies.length; ++i) {
uint256 proxyVersion = $._initVersion[proxies[i]];
uint256 logicVersion = $._initVersion[$._proxy2Logic[proxies[i]]];

require(
$._initSlot[proxies[i]].found,
string.concat("LibInitializeGuard: Proxy ", vm.getLabel(proxies[i]), " does not have `_initialized` slot!")
);

require(
logicVersion == MAX_INIT_VERSION_OZV4 || logicVersion == MAX_INIT_VERSION_OZV5,
string.concat(
"LibInitializeGuard: Logic of proxy ", vm.getLabel(proxies[i]), " does not disable initialized version!"
)
);

require(
proxyVersion != 0, string.concat("LibInitializeGuard: Proxy ", vm.getLabel(proxies[i]), " does not initialize!")
);
}
}

function _getInitializedSlot(Cache storage $cache, address proxy) internal returns (InitializedSlot memory initSlot) {
Vm.ChainInfo memory chainInfo = $cache._chainInfo[proxy];
TNetwork networkType = vme.getNetworkTypeByForkId(chainInfo.forkId);
TContract contractType = vme.getContractTypeByRawData(networkType, proxy);
string memory contractName = vme.getContractName(contractType);

string[] memory inputs = new string[](4);
inputs[0] = "forge";
inputs[1] = "inspect";
inputs[2] = contractName;
inputs[3] = "storage";

string memory ret = string(vm.ffi(inputs));
JSONParserLib.Item memory layout = ret.parse().at('"storage"');
uint256 layoutSize = layout.size();

for (uint256 i; i < layoutSize; ++i) {
JSONParserLib.Item memory storageSlot = layout.at(i);

if (keccak256(bytes(storageSlot.at('"label"').value().decodeString())) == keccak256("_initialized")) {
initSlot.found = true;
initSlot.slot = vm.parseBytes32(storageSlot.at('"slot"').value().decodeString());
initSlot.offset = storageSlot.at('"offset"').value().parseUint();

return initSlot;
}
}

uint256 initVersion = $cache._initVersion[proxy];
if (!initSlot.found && initVersion != 0) {
// assume proxy use `Initializable` from OpenZeppelin v5
bytes32 slotValue = vm.load(proxy, INITIALIZABLE_STORAGE_OZV5);
if (slotValue != 0) {
initSlot.found = true;
initSlot.slot = INITIALIZABLE_STORAGE_OZV5;
initSlot.offset = 0;
}
}
}

function _getCacheStorage() internal pure returns (Cache storage $) {
bytes32 slot = keccak256("LibInitializeGuard.Cache.storage.slot");

assembly ("memory-safe") {
$.slot := slot
}
}

function _getStdStorage() internal pure returns (StdStorage storage $) {
bytes32 slot = keccak256("StdStorage.storage.slot");

assembly ("memory-safe") {
$.slot := slot
}
}
}
6 changes: 1 addition & 5 deletions script/utils/Helpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,6 @@ function decodeData(bytes memory data) returns (string memory decodedData) {
decodedData = string(vm.ffi(commandInputs));
}

function loadContract(TContract contractType) view returns (address payable contractAddr) {
return loadContract({ contractType: contractType, shouldRevert: true });
}

function loadContract(TContract contractType, bool shouldRevert) view returns (address payable contractAddr) {
try vme.getAddressFromCurrentNetwork(contractType) returns (address payable res) {
contractAddr = res;
Expand All @@ -132,7 +128,7 @@ function loadContract(TContract contractType, bool shouldRevert) view returns (a
}

function prankOrBroadcast(address by) {
if (vme.isPostChecking()) {
if (vme.isPostChecking() || vme.isPreChecking()) {
vm.prank(by);
} else {
vm.broadcast(by);
Expand Down
24 changes: 24 additions & 0 deletions soldeer.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,27 @@ name = "@solady"
version = "0.0.228"
source = "https://github.com/Vectorized/solady/archive/refs/tags/v0.0.228.zip"
checksum = "90c6c562403b67338e3fa5fbad36a80b129df333d89db6759c5fd3ed15d4f0da"

[[dependencies]]
name = "@openzeppelin-5.0.2"
version = "5.0.2"
source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip"
checksum = "ca49e0776066328da0087977864dfaef0d5e54a0ea9859236a7cd9ad02abb9e5"

[[dependencies]]
name = "@openzeppelin"
version = "5.0.2"
source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip"
checksum = "ca49e0776066328da0087977864dfaef0d5e54a0ea9859236a7cd9ad02abb9e5"

[[dependencies]]
name = "@openzeppelinV5"
version = "5.0.2"
source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip"
checksum = "ca49e0776066328da0087977864dfaef0d5e54a0ea9859236a7cd9ad02abb9e5"

[[dependencies]]
name = "@openzeppelin-v5"
version = "5.0.2"
source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip"
checksum = "ca49e0776066328da0087977864dfaef0d5e54a0ea9859236a7cd9ad02abb9e5"
22 changes: 22 additions & 0 deletions src/mocks/MockInitialize.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import { Initializable } from "@openzeppelin-4.9.3/contracts/proxy/utils/Initializable.sol";

contract MockInitialize is Initializable {
address private _addr;

constructor() {
_disableInitializers();
}

function initialize() public initializer { }

function initializeV2() external reinitializer(2) { }

function initializeV3(address a) external reinitializer(3) {
_addr = a;
}

function initializeV4() external reinitializer(4) { }
}
5 changes: 4 additions & 1 deletion src/mocks/SampleProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
pragma solidity ^0.8.19;

import { Initializable } from "../../dependencies/@openzeppelin-4.9.3/contracts/proxy/utils/Initializable.sol";
import { Initializable as InitializableV5 } from
"../../dependencies/@openzeppelin-v5-5.0.2/contracts/proxy/utils/Initializable.sol";
import { Ownable } from "../../dependencies/@openzeppelin-4.9.3/contracts/access/Ownable.sol";

contract SampleProxy is Initializable {
contract SampleProxy is Ownable, InitializableV5 {
uint256[50] private __gap;

string internal _message;
Expand Down
Loading

0 comments on commit 38f2226

Please sign in to comment.