diff --git a/.github/workflows/create-PR-implement.yml b/.github/workflows/create-PR-implement.yml index 33d00a5..85cbbd6 100644 --- a/.github/workflows/create-PR-implement.yml +++ b/.github/workflows/create-PR-implement.yml @@ -41,7 +41,7 @@ jobs: toBranch: ${{ env.FEATURE_BRANCH }} - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6.0.1 with: branch: ${{ env.HEAD_BRANCH }} base: ${{env.FEATURE_BRANCH}} diff --git a/.github/workflows/create-PR-release.yml b/.github/workflows/create-PR-release.yml index 87944c6..b9dec20 100644 --- a/.github/workflows/create-PR-release.yml +++ b/.github/workflows/create-PR-release.yml @@ -75,7 +75,7 @@ jobs: toBranch: ${{matrix.branch_name}} - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 + uses: peter-evans/create-pull-request@v6.0.1 with: labels: automated PR delete-branch: true diff --git a/.gitmodules b/.gitmodules index ca8e125..a2ad802 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,16 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std + shallow = true [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts + shallow = true [submodule "lib/solady"] path = lib/solady - url = https://github.com/Vectorized/solady + url = https://github.com/vectorized/solady + shallow = true [submodule "lib/contract-libs"] path = lib/contract-libs url = https://github.com/axieinfinity/contract-libs + shallow = true diff --git a/broadcast.sh b/broadcast.sh index e7c12c3..e9b951e 100755 --- a/broadcast.sh +++ b/broadcast.sh @@ -5,7 +5,6 @@ else echo "Error: .broadcast.env file not found." fi -verify_arg="" extra_argument="" for arg in "$@"; do @@ -20,6 +19,15 @@ done # Remove the @ character from the end of extra_argument extra_argument="${extra_argument%%@}" +op_command="" +## Check if the private key is stored in the .env file +if [[ ! $extra_argument == *"sender"* ]] && [[ ! $extra_argument == *"trezor"* ]]; then + source .env + if [[ $MAINNET_PK == op* ]] || [[ $TESTNET_PK == op* ]] || [[ $LOCAL_PK == op* ]]; then + op_command="op run --env-file="./.env" --" + fi +fi + echo Broadcast Tx... echo From: ${FROM} echo To: ${TO} @@ -28,4 +36,4 @@ echo GasAmount: ${GAS} echo Calldata: cast pretty-calldata ${CALLDATA} calldata=$(cast calldata 'broadcast(address,address,uint256,uint256,bytes)' ${FROM} ${TO} ${GAS} ${VALUE} ${CALLDATA}) -forge script ${verify_arg} ${@} -g 200 OnchainExecutor --sig 'run(bytes,string)' ${calldata} "${extra_argument}" \ No newline at end of file +${op_command} forge script ${verify_arg} ${@} -g 200 OnchainExecutor --sig 'run(bytes,string)' ${calldata} "${extra_argument}" \ No newline at end of file diff --git a/debug.sh b/debug.sh index dcaeab7..e2659e1 100755 --- a/debug.sh +++ b/debug.sh @@ -5,7 +5,6 @@ else echo "Error: .debug.env file not found." fi -verify_arg="" extra_argument="" for arg in "$@"; do diff --git a/lib/forge-std b/lib/forge-std index 87a2a0a..b6a506d 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 87a2a0afc5fafd6297538a45a52ac19e71a84562 +Subproject commit b6a506db2262cad5ff982a87789ee6d1558ec861 diff --git a/run.sh b/run.sh index 0de149a..9ed4253 100755 --- a/run.sh +++ b/run.sh @@ -6,9 +6,17 @@ for arg in "$@"; do --trezor) extra_argument+=trezor@ ;; - --log) - set -- "${@/#--log/}" - extra_argument+=log@ + --no-postcheck) + set -- "${@/#--no-postcheck/}" + extra_argument+=no-postcheck@ + ;; + --generate-artifacts) + set -- "${@/#--generate-artifacts/}" + extra_argument+=generate-artifact@ + ;; + -atf) + set -- "${@/#-atf/}" + extra_argument+=generate-artifact@ ;; *) ;; esac @@ -17,5 +25,14 @@ done # Remove the @ character from the end of extra_argument extra_argument="${extra_argument%%@}" +op_command="" +## Check if the private key is stored in the .env file +if [[ ! $extra_argument == *"sender"* ]] && [[ ! $extra_argument == *"trezor"* ]]; then + source .env + if [[ $MAINNET_PK == op* ]] || [[ $TESTNET_PK == op* ]] || [[ $LOCAL_PK == op* ]]; then + op_command="op run --env-file="./.env" --" + fi +fi + calldata=$(cast calldata 'run()') -forge script ${verify_arg} ${@} -g 200 --sig 'run(bytes,string)' ${calldata} "${extra_argument}" +${op_command} forge script ${verify_arg} ${@} -g 200 --sig 'run(bytes,string)' ${calldata} "${extra_argument}" \ No newline at end of file diff --git a/script/ArtifactFactory.sol b/script/ArtifactFactory.sol index b0d2ebc..2628800 100644 --- a/script/ArtifactFactory.sol +++ b/script/ArtifactFactory.sol @@ -24,7 +24,7 @@ contract ArtifactFactory is IArtifactFactory { address deployer, address contractAddr, string memory contractAbsolutePath, - string calldata fileName, + string memory fileName, bytes calldata args, uint256 nonce ) external { @@ -38,11 +38,16 @@ contract ArtifactFactory is IArtifactFactory { ).green(), string.concat("(nonce: ", nonce.toString(), ")") ); - if (!CONFIG.getRuntimeConfig().log) { + if (!CONFIG.getRuntimeConfig().generateArtifact || CONFIG.isPostChecking()) { console.log("Skipping artifact generation for:", vm.getLabel(contractAddr), "\n"); return; } string memory dirPath = CONFIG.getDeploymentDirectory(CONFIG.getCurrentNetwork()); + if (!vm.exists(dirPath)) { + console.log("\n", string.concat(dirPath, " not existed, making one...").yellow()); + vm.createDir(dirPath, true); + vm.writeFile(string.concat(dirPath, ".chainId"), vm.toString(block.chainid)); + } string memory filePath = string.concat(dirPath, fileName, ".json"); string memory json; @@ -67,10 +72,14 @@ contract ArtifactFactory is IArtifactFactory { json.serialize("contractAbsolutePath", contractAbsolutePath); json.serialize("numDeployments", numDeployments); - string[] memory s = contractAbsolutePath.split(":"); - string memory artifactPath = s.length == 2 - ? string.concat("./out/", s[0], "/", s[1], ".json") - : string.concat("./out/", contractAbsolutePath, "/", contractAbsolutePath.replace(".sol", ".json")); + string memory artifactPath = contractAbsolutePath; + if (!artifactPath.endsWith(".json")) { + string[] memory s = contractAbsolutePath.split(":"); + artifactPath = s.length == 2 + ? string.concat("./out/", s[0], "/", s[1], ".json") + : string.concat("./out/", contractAbsolutePath, "/", contractAbsolutePath.replace(".sol", ".json")); + } + string memory artifact = vm.readFile(artifactPath); JSONParserLib.Item memory item = artifact.parse(); diff --git a/script/BaseGeneralConfig.sol b/script/BaseGeneralConfig.sol index 662abae..8b5687e 100644 --- a/script/BaseGeneralConfig.sol +++ b/script/BaseGeneralConfig.sol @@ -76,12 +76,19 @@ contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, Netwo function _setUpDefaultContracts() private { _contractNameMap[DefaultContract.ProxyAdmin.key()] = DefaultContract.ProxyAdmin.name(); + _contractNameMap[DefaultContract.Multicall3.key()] = DefaultContract.Multicall3.name(); setAddress( DefaultNetwork.RoninTestnet.key(), DefaultContract.ProxyAdmin.key(), 0x505d91E8fd2091794b45b27f86C045529fa92CD7 ); setAddress( DefaultNetwork.RoninMainnet.key(), DefaultContract.ProxyAdmin.key(), 0xA3e7d085E65CB0B916f6717da876b7bE5cC92f03 ); + setAddress( + DefaultNetwork.RoninMainnet.key(), DefaultContract.Multicall3.key(), 0xcA11bde05977b3631167028862bE2a173976CA11 + ); + setAddress( + DefaultNetwork.RoninTestnet.key(), DefaultContract.Multicall3.key(), 0xcA11bde05977b3631167028862bE2a173976CA11 + ); _setUpContracts(); } @@ -92,6 +99,7 @@ contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, Netwo function getSender() public view virtual override returns (address payable sender) { sender = _option.trezor ? payable(_trezorSender) : payable(_envSender); + if (sender == address(0x0) && getCurrentNetwork() == DefaultNetwork.Local.key()) sender = payable(DEFAULT_SENDER); require(sender != address(0x0), "GeneralConfig: Sender is address(0x0)"); } diff --git a/script/BaseMigration.s.sol b/script/BaseMigration.s.sol index 0df9558..d0fb3d2 100644 --- a/script/BaseMigration.s.sol +++ b/script/BaseMigration.s.sol @@ -2,22 +2,29 @@ pragma solidity ^0.8.19; import { ProxyAdmin } from "../lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import { - ITransparentUpgradeableProxy, - TransparentUpgradeableProxy -} from "../lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ITransparentUpgradeableProxy, Proxy } from "../src/Proxy.sol"; import { LibString } from "../lib/solady/src/utils/LibString.sol"; -import { console, LibSharedAddress, StdStyle, IScriptExtended, ScriptExtended } from "./extensions/ScriptExtended.s.sol"; +import { + console, + StdStyle, + stdStorage, + StdStorage, + ScriptExtended, + IScriptExtended, + LibSharedAddress +} from "./extensions/ScriptExtended.s.sol"; import { IArtifactFactory, ArtifactFactory } from "./ArtifactFactory.sol"; 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 { DefaultContract } from "./utils/DefaultContract.sol"; import { TContract } from "./types/Types.sol"; +import { LibErrorHandler } from "../lib/contract-libs/src/LibErrorHandler.sol"; abstract contract BaseMigration is ScriptExtended { using StdStyle for *; using LibString for bytes32; + using LibErrorHandler for bool; using LibProxy for address payable; IArtifactFactory public constant ARTIFACT_FACTORY = IArtifactFactory(LibSharedAddress.ARTIFACT_FACTORY); @@ -27,13 +34,13 @@ abstract contract BaseMigration is ScriptExtended { function setUp() public virtual override { super.setUp(); - vm.label(address(ARTIFACT_FACTORY), "ArtifactFactory"); - deploySharedAddress(address(ARTIFACT_FACTORY), type(ArtifactFactory).creationCode); - _injectDependencies(); _storeRawSharedArguments(); + _injectDependencies(); + deploySharedAddress(address(ARTIFACT_FACTORY), type(ArtifactFactory).creationCode, "ArtifactFactory"); } function _storeRawSharedArguments() internal virtual { + if (CONFIG.areSharedArgumentsStored()) return; CONFIG.setRawSharedArguments(_sharedArguments()); } @@ -66,7 +73,7 @@ abstract contract BaseMigration is ScriptExtended { args = _overriddenArgs.length == 0 ? _defaultArguments() : _overriddenArgs; } - function _getProxyAdmin() internal view virtual returns (address payable proxyAdmin) { + function _getProxyAdmin() internal virtual returns (address payable proxyAdmin) { proxyAdmin = loadContract(DefaultContract.ProxyAdmin.key()); } @@ -93,15 +100,24 @@ abstract contract BaseMigration is ScriptExtended { virtual logFn(string.concat("_deployLogic ", TContract.unwrap(contractType).unpackOne())) returns (address payable logic) + { + logic = _deployLogic(contractType, EMPTY_ARGS); + } + + function _deployLogic(TContract contractType, bytes memory args) + internal + virtual + logFn(string.concat("_deployLogic ", TContract.unwrap(contractType).unpackOne())) + returns (address payable logic) { string memory contractName = CONFIG.getContractName(contractType); string memory contractAbsolutePath = CONFIG.getContractAbsolutePath(contractType); uint256 logicNonce; - (logic, logicNonce) = _deployRaw(contractAbsolutePath, EMPTY_ARGS); + (logic, logicNonce) = _deployRaw(contractAbsolutePath, args); CONFIG.label(block.chainid, logic, string.concat(contractName, "::Logic")); ARTIFACT_FACTORY.generateArtifact( - sender(), logic, contractAbsolutePath, string.concat(contractName, "Logic"), EMPTY_ARGS, logicNonce + sender(), logic, contractAbsolutePath, string.concat(contractName, "Logic"), args, logicNonce ); } @@ -109,7 +125,11 @@ abstract contract BaseMigration is ScriptExtended { deployed = _deployProxy(contractType, arguments()); } - function _deployProxy(TContract contractType, bytes memory args) + function _deployProxy(TContract contractType, bytes memory args) internal virtual returns (address payable deployed) { + deployed = _deployProxy(contractType, args, EMPTY_ARGS); + } + + function _deployProxy(TContract contractType, bytes memory args, bytes memory argsLogicConstructor) internal virtual logFn(string.concat("_deployProxy ", TContract.unwrap(contractType).unpackOne())) @@ -117,13 +137,14 @@ abstract contract BaseMigration is ScriptExtended { { string memory contractName = CONFIG.getContractName(contractType); - address logic = _deployLogic(contractType); - string memory proxyAbsolutePath = "TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy"; - uint256 proxyNonce; + address logic = _deployLogic(contractType, argsLogicConstructor); + string memory proxyAbsolutePath = "Proxy.sol:Proxy"; + uint256 proxyNonce = vm.getNonce(sender()); address proxyAdmin = _getProxyAdmin(); assertTrue(proxyAdmin != address(0x0), "BaseMigration: Null ProxyAdmin"); - (deployed, proxyNonce) = _deployRaw(proxyAbsolutePath, abi.encode(logic, proxyAdmin, args)); + _prankOrBroadcast(sender()); + deployed = payable(address(new Proxy(logic, proxyAdmin, args))); // validate proxy admin address actualProxyAdmin = deployed.getProxyAdmin(); @@ -151,7 +172,7 @@ abstract contract BaseMigration is ScriptExtended { returns (address payable deployed, uint256 nonce) { nonce = vm.getNonce(sender()); - vm.broadcast(sender()); + _prankOrBroadcast(sender()); deployed = payable(deployCode(filename, args)); } @@ -179,13 +200,17 @@ abstract contract BaseMigration is ScriptExtended { proxy = _upgradeProxy(contractType, arguments()); } - function _upgradeProxy(TContract contractType, bytes memory args) + function _upgradeProxy(TContract contractType, bytes memory args) internal virtual returns (address payable proxy) { + proxy = _upgradeProxy(contractType, args, EMPTY_ARGS); + } + + function _upgradeProxy(TContract contractType, bytes memory args, bytes memory argsLogicConstructor) internal virtual logFn(string.concat("_upgradeProxy ", TContract.unwrap(contractType).unpackOne())) returns (address payable proxy) { - address logic = _deployLogic(contractType); + address logic = _deployLogic(contractType, argsLogicConstructor); proxy = CONFIG.getAddress(network(), contractType); _upgradeRaw(proxy.getProxyAdmin(), proxy, logic, args); } @@ -230,12 +255,17 @@ abstract contract BaseMigration is ScriptExtended { } function _upgradeRaw(address proxyAdmin, address payable proxy, address logic, bytes memory args) internal virtual { + if (logic.codehash == payable(proxy).getProxyImplementation({ nullCheck: true }).codehash) { + console.log("BaseMigration: Logic is already upgraded!".yellow()); + return; + } + ITransparentUpgradeableProxy iProxy = ITransparentUpgradeableProxy(proxy); ProxyAdmin wProxyAdmin = ProxyAdmin(proxyAdmin); // if proxyAdmin is External Owned Wallet if (proxyAdmin.code.length == 0) { - vm.broadcast(proxyAdmin); + _prankOrBroadcast(proxyAdmin); if (args.length == 0) iProxy.upgradeTo(logic); else iProxy.upgradeToAndCall(logic, args); } else { @@ -248,7 +278,7 @@ abstract contract BaseMigration is ScriptExtended { if (owner.code.length != 0) { _cheatUpgrade(owner, wProxyAdmin, iProxy, logic); } else { - vm.broadcast(owner); + _prankOrBroadcast(owner); wProxyAdmin.upgrade(iProxy, logic); } } else { @@ -258,7 +288,7 @@ abstract contract BaseMigration is ScriptExtended { if (owner.code.length != 0) { _cheatUpgradeAndCall(owner, wProxyAdmin, iProxy, logic, args); } else { - vm.broadcast(owner); + _prankOrBroadcast(owner); wProxyAdmin.upgradeAndCall(iProxy, logic, args); } } @@ -266,7 +296,7 @@ abstract contract BaseMigration is ScriptExtended { if (owner.code.length != 0) { _cheatUpgradeAndCall(owner, wProxyAdmin, iProxy, logic, args); } else { - vm.broadcast(owner); + _prankOrBroadcast(owner); wProxyAdmin.upgradeAndCall(iProxy, logic, args); } } @@ -276,20 +306,52 @@ abstract contract BaseMigration is ScriptExtended { } } + function _cheatBroadcast(address from, address to, bytes memory callData) internal virtual { + string[] memory commandInputs = new string[](3); + commandInputs[0] = "cast"; + commandInputs[1] = "4byte-decode"; + commandInputs[2] = vm.toString(callData); + string memory decodedCallData = string(vm.ffi(commandInputs)); + + console.log("\n"); + console.log("--------------------------- Call Detail ---------------------------"); + console.log("To:".cyan(), vm.getLabel(to)); + console.log( + "Raw Calldata Data (Please double check using `cast pretty-calldata {raw_bytes}`):\n".cyan(), + string.concat(" - ", vm.toString(callData)) + ); + console.log("Cast Decoded Call Data:".cyan(), decodedCallData); + console.log("--------------------------------------------------------------------"); + + vm.prank(from); + (bool success, bytes memory returnOrRevertData) = to.call(callData); + success.handleRevert(bytes4(callData), returnOrRevertData); + } + function _cheatUpgrade(address owner, ProxyAdmin wProxyAdmin, ITransparentUpgradeableProxy iProxy, address logic) internal virtual { bytes memory callData = abi.encodeCall(ProxyAdmin.upgrade, (iProxy, logic)); + string[] memory commandInputs = new string[](3); + commandInputs[0] = "cast"; + commandInputs[1] = "4byte-decode"; + commandInputs[2] = vm.toString(callData); + string memory decodedCallData = string(vm.ffi(commandInputs)); + console.log( "------------------------------------------------------------------------------- Multi-Sig Proposal -------------------------------------------------------------------------------" ); console.log("To:".cyan(), vm.getLabel(address(wProxyAdmin))); + console.log( + "Raw Calldata Data (Please double check using `cast 4byte-decode {raw_bytes}`):\n".cyan(), + string.concat(" - ", vm.toString(callData)) + ); console.log( "Method:\n".cyan(), string.concat(" - upgrade(address,address)\n - ", vm.getLabel(address(iProxy)), "\n - ", vm.getLabel(logic)) ); - console.log("Raw Calldata Data:\n".cyan(), string.concat(" - ", vm.toString(callData))); + console.log("Cast Decoded Call Data:".cyan(), decodedCallData); console.log( "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------" ); @@ -307,18 +369,32 @@ abstract contract BaseMigration is ScriptExtended { bytes memory args ) internal virtual { bytes memory callData = abi.encodeCall(ProxyAdmin.upgradeAndCall, (iProxy, logic, args)); + string[] memory commandInputs = new string[](3); + commandInputs[0] = "cast"; + commandInputs[1] = "4byte-decode"; + commandInputs[2] = vm.toString(callData); + string memory decodedCallData = string(vm.ffi(commandInputs)); + commandInputs[2] = vm.toString(args); + string memory decodedInnerCall = string(vm.ffi(commandInputs)); + console.log( "------------------------------------------------------------------------------- Multi-Sig Proposal -------------------------------------------------------------------------------" ); console.log("To:".cyan(), vm.getLabel(address(wProxyAdmin))); + console.log( + "Raw Call Data (Please double check using `cast 4byte-decode {raw_bytes}`):\n".cyan(), + " - ", + vm.toString(callData) + ); console.log( "Method:\n".cyan(), " - upgradeAndCall(address,address,bytes)\n", string.concat(" - ", vm.getLabel(address(iProxy)), "\n - ", vm.getLabel(logic), "\n - ", vm.toString(args)) ); - console.log("Raw Call Data:\n".cyan(), " - ", vm.toString(callData)); + console.log("Cast Decoded Call Data:".cyan(), decodedCallData); + console.log("Cast Decoded Inner Method:".cyan(), decodedInnerCall); console.log( - "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------" + "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n" ); // cheat prank to update `implementation slot` for next call @@ -326,6 +402,14 @@ abstract contract BaseMigration is ScriptExtended { wProxyAdmin.upgradeAndCall(iProxy, logic, args); } + function _prankOrBroadcast(address to) internal virtual { + if (CONFIG.isPostChecking()) { + vm.prank(to); + } else { + vm.broadcast(to); + } + } + function _setDependencyDeployScript(TContract contractType, IScriptExtended deployScript) internal virtual { _deployScript[contractType] = IMigrationScript(address(deployScript)); } diff --git a/script/configs/ContractConfig.sol b/script/configs/ContractConfig.sol index b624ccb..d38b3f4 100644 --- a/script/configs/ContractConfig.sol +++ b/script/configs/ContractConfig.sol @@ -73,9 +73,7 @@ abstract contract ContractConfig is IContractConfig { function getContractAbsolutePath(TContract contractType) public view virtual returns (string memory name) { if (bytes(_contractAbsolutePathMap[contractType]).length != 0) { - name = string.concat( - _contractAbsolutePathMap[contractType], _contractNameMap[contractType], ".sol:", _contractNameMap[contractType] - ); + name = _contractAbsolutePathMap[contractType]; } else if (bytes(_absolutePath).length != 0) { name = string.concat(_absolutePath, _contractNameMap[contractType], ".sol:", _contractNameMap[contractType]); } else { diff --git a/script/configs/MigrationConfig.sol b/script/configs/MigrationConfig.sol index 319ef34..2bea430 100644 --- a/script/configs/MigrationConfig.sol +++ b/script/configs/MigrationConfig.sol @@ -7,10 +7,14 @@ abstract contract MigrationConfig is IMigrationConfig { bytes internal _migrationConfig; function setRawSharedArguments(bytes memory config) public virtual { - if (_migrationConfig.length != 0) return; + if (areSharedArgumentsStored()) return; _migrationConfig = config; } + function areSharedArgumentsStored() public view virtual returns (bool) { + return _migrationConfig.length != 0; + } + function getRawSharedArguments() public view virtual returns (bytes memory) { return _migrationConfig; } diff --git a/script/configs/NetworkConfig.sol b/script/configs/NetworkConfig.sol index 2628898..cf444f9 100644 --- a/script/configs/NetworkConfig.sol +++ b/script/configs/NetworkConfig.sol @@ -30,6 +30,10 @@ abstract contract NetworkConfig is INetworkConfig { _isForkModeEnabled = shouldEnable; } + function getNetworkData(TNetwork network) public view virtual returns (NetworkData memory) { + return _networkDataMap[network]; + } + function getDeploymentDirectory(TNetwork network) public view virtual returns (string memory dirPath) { string memory dirName = _networkDataMap[network].deploymentDir; require(bytes(dirName).length != 0, "NetworkConfig: Deployment directory not found"); @@ -76,7 +80,7 @@ abstract contract NetworkConfig is INetworkConfig { currentFork = forkId; } catch { console.log(StdStyle.yellow("NetworkConfig: fork mode disabled, no active fork")); - return NULL_FORK_ID; + currentFork = NULL_FORK_ID; } if (chainId == block.chainid) return currentFork; diff --git a/script/configs/RuntimeConfig.sol b/script/configs/RuntimeConfig.sol index 6a4c54e..2025a1a 100644 --- a/script/configs/RuntimeConfig.sol +++ b/script/configs/RuntimeConfig.sol @@ -12,11 +12,20 @@ abstract contract RuntimeConfig is IRuntimeConfig { bool internal _resolved; Option internal _option; string internal _rawCommand; + bool internal _isPostChecking; function getCommand() public view virtual returns (string memory) { return _rawCommand; } + function isPostChecking() public view virtual returns (bool) { + return _isPostChecking; + } + + function setPostCheckingStatus(bool status) public virtual { + _isPostChecking = status; + } + function resolveCommand(string calldata command) external virtual { if (_resolved) return; if (bytes(command).length != 0) { @@ -24,8 +33,9 @@ abstract contract RuntimeConfig is IRuntimeConfig { uint256 length = args.length; for (uint256 i; i < length;) { - if (args[i].eq("log")) _option.log = true; + if (args[i].eq("generate-artifact")) _option.generateArtifact = true; else if (args[i].eq("trezor")) _option.trezor = true; + else if (args[i].eq("no-postcheck")) _option.disablePostcheck = true; else console.log(StdStyle.yellow("Unsupported command: "), args[i]); unchecked { diff --git a/script/configs/WalletConfig.sol b/script/configs/WalletConfig.sol index f3f28e9..e3a420e 100644 --- a/script/configs/WalletConfig.sol +++ b/script/configs/WalletConfig.sol @@ -41,6 +41,10 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { public returns (bytes memory sig) { + sig = ethSignMessage(by, message, _loadENVPrivateKey(envLabel)); + } + + function ethSignMessage(address by, string memory message, uint256 privateKey) public returns (bytes memory sig) { string[] memory commandInput = new string[](8); commandInput[0] = "cast"; commandInput[1] = "wallet"; @@ -48,7 +52,7 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { commandInput[3] = "--from"; commandInput[4] = vm.toString(by); commandInput[5] = "--private-key"; - commandInput[6] = LibString.toHexString(_loadENVPrivateKey(envLabel)); + commandInput[6] = LibString.toHexString(privateKey); commandInput[7] = message; sig = vm.ffi(commandInput); @@ -86,6 +90,10 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { public returns (bytes memory sig) { + sig = signTypedDataV4(by, filePath, _loadENVPrivateKey(envLabel)); + } + + function signTypedDataV4(address by, string memory filePath, uint256 privateKey) public returns (bytes memory sig) { string[] memory commandInput = new string[](10); commandInput[0] = "cast"; commandInput[1] = "wallet"; @@ -93,7 +101,7 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { commandInput[3] = "--from"; commandInput[4] = vm.toString(by); commandInput[5] = "--private-key"; - commandInput[6] = LibString.toHexString(_loadENVPrivateKey(envLabel)); + commandInput[6] = LibString.toHexString(privateKey); commandInput[7] = "--data"; commandInput[8] = "--from-file"; commandInput[9] = filePath; @@ -143,28 +151,18 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { _envSender = vm.rememberKey(_loadENVPrivateKey(envLabel)); } - function _loadENVPrivateKey(string memory envLabel) private returns (uint256) { + function _loadENVPrivateKey(string memory envLabel) private view returns (uint256) { try vm.envUint(envLabel) returns (uint256 pk) { return pk; } catch { - string[] memory commandInput = new string[](3); - - try vm.envString(envLabel) returns (string memory data) { - commandInput[2] = data; - } catch { - revert( - string.concat( - "\nGeneralConfig: Error finding env address!\n- Please make `.env` file and create field `", - envLabel, - "=", - "{op_secret_reference_or_your_private_key}`" - ) - ); - } - commandInput[0] = "op"; - commandInput[1] = "read"; - - return vm.parseUint(vm.toString(vm.ffi(commandInput))); + revert( + string.concat( + "\nGeneralConfig: Error finding env address!\n- Please make `.env` file and create field `", + envLabel, + "=", + "{op_secret_reference_or_your_private_key}`" + ) + ); } } } diff --git a/script/extensions/ScriptExtended.s.sol b/script/extensions/ScriptExtended.s.sol index 09cf1c4..a9faa95 100644 --- a/script/extensions/ScriptExtended.s.sol +++ b/script/extensions/ScriptExtended.s.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.19; import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; import { console, Script } from "../../lib/forge-std/src/Script.sol"; +import { stdStorage, StdStorage } from "../../lib/forge-std/src/StdStorage.sol"; import { StdAssertions } from "../../lib/forge-std/src/StdAssertions.sol"; import { IGeneralConfig } from "../interfaces/IGeneralConfig.sol"; import { TNetwork, IScriptExtended } from "../interfaces/IScriptExtended.sol"; @@ -11,6 +12,7 @@ import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; import { TContract } from "../types/Types.sol"; abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { + using StdStyle for *; using LibErrorHandler for bool; bytes public constant EMPTY_ARGS = ""; @@ -29,12 +31,15 @@ abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { modifier onNetwork(TNetwork networkType) { TNetwork currentNetwork = _switchTo(networkType); _; - _swichBack(currentNetwork); + _switchBack(currentNetwork); + } + + constructor() { + setUp(); } function setUp() public virtual { - vm.label(address(CONFIG), "GeneralConfig"); - deploySharedAddress(address(CONFIG), _configByteCode()); + deploySharedAddress(address(CONFIG), _configByteCode(), "GeneralConfig"); } function _configByteCode() internal virtual returns (bytes memory); @@ -45,7 +50,16 @@ abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { CONFIG.resolveCommand(command); (bool success, bytes memory data) = address(this).delegatecall(callData); success.handleRevert(msg.sig, data); + + if (CONFIG.getRuntimeConfig().disablePostcheck) { + console.log("\nPostchecking is disabled.".yellow()); + return; + } + + console.log("\n>> Postchecking...".yellow()); + CONFIG.setPostCheckingStatus({ status: true }); _postCheck(); + CONFIG.setPostCheckingStatus({ status: false }); } function network() public view virtual returns (TNetwork) { @@ -65,17 +79,18 @@ abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { revert("ScriptExtended: Got failed assertion"); } - function deploySharedAddress(address where, bytes memory bytecode) public { + function deploySharedAddress(address where, bytes memory bytecode, string memory label) public { if (where.code.length == 0) { vm.makePersistent(where); vm.allowCheatcodes(where); deployCodeTo(bytecode, where); + if (bytes(label).length != 0) vm.label(where, label); } } function deploySharedMigration(TContract contractType, bytes memory bytecode) public returns (address where) { where = address(ripemd160(abi.encode(contractType))); - deploySharedAddress(where, bytecode); + deploySharedAddress(where, bytecode, string.concat(contractType.contractName(), "Deploy")); } function deployCodeTo(bytes memory creationCode, address where) internal { @@ -107,7 +122,7 @@ abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { CONFIG.switchTo(networkType); } - function _swichBack(TNetwork currentNetwork) private { + function _switchBack(TNetwork currentNetwork) private { CONFIG.switchTo(currentNetwork); } } diff --git a/script/interfaces/configs/IMigrationConfig.sol b/script/interfaces/configs/IMigrationConfig.sol index 1496f92..0858f6f 100644 --- a/script/interfaces/configs/IMigrationConfig.sol +++ b/script/interfaces/configs/IMigrationConfig.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.19; interface IMigrationConfig { + function areSharedArgumentsStored() external view returns (bool); + function setRawSharedArguments(bytes calldata migrationConfig) external; function getRawSharedArguments() external view returns (bytes memory); diff --git a/script/interfaces/configs/INetworkConfig.sol b/script/interfaces/configs/INetworkConfig.sol index 940e603..2729760 100644 --- a/script/interfaces/configs/INetworkConfig.sol +++ b/script/interfaces/configs/INetworkConfig.sol @@ -28,6 +28,8 @@ interface INetworkConfig { function getExplorer(TNetwork network) external view returns (string memory link); + function getNetworkData(TNetwork network) external view returns (NetworkData memory); + function getForkId(TNetwork network) external view returns (uint256 forkId); function getAlias(TNetwork network) external view returns (string memory networkAlias); diff --git a/script/interfaces/configs/IRuntimeConfig.sol b/script/interfaces/configs/IRuntimeConfig.sol index 28dae4d..b3ec019 100644 --- a/script/interfaces/configs/IRuntimeConfig.sol +++ b/script/interfaces/configs/IRuntimeConfig.sol @@ -3,10 +3,15 @@ pragma solidity ^0.8.19; interface IRuntimeConfig { struct Option { - bool log; + bool generateArtifact; bool trezor; + bool disablePostcheck; } + function isPostChecking() external view returns (bool); + + function setPostCheckingStatus(bool status) external; + function getCommand() external view returns (string memory); function resolveCommand(string calldata command) external; diff --git a/script/interfaces/configs/IWalletConfig.sol b/script/interfaces/configs/IWalletConfig.sol index 93c1109..b07573a 100644 --- a/script/interfaces/configs/IWalletConfig.sol +++ b/script/interfaces/configs/IWalletConfig.sol @@ -19,6 +19,8 @@ interface IWalletConfig { function ethSignMessage(string memory message) external returns (bytes memory sig); + function ethSignMessage(address by, string memory message, uint256 privateKey) external returns (bytes memory sig); + function envEthSignMessage(address by, string memory message, string memory envLabel) external returns (bytes memory sig); @@ -31,6 +33,8 @@ interface IWalletConfig { function trezorSignTypedDataV4(address by, string memory filePath) external returns (bytes memory sig); + function signTypedDataV4(address by, string memory filePath, uint256 privateKey) external returns (bytes memory sig); + function signTypedDataV4(address by, string memory filePath, WalletOption walletOption) external returns (bytes memory sig); diff --git a/script/libraries/LibProxy.sol b/script/libraries/LibProxy.sol index 77198c1..046e088 100644 --- a/script/libraries/LibProxy.sol +++ b/script/libraries/LibProxy.sol @@ -9,7 +9,7 @@ library LibProxy { bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - function getProxyAdmin(address payable proxy, bool nullCheck) internal returns (address payable proxyAdmin) { + function getProxyAdmin(address payable proxy, bool nullCheck) internal view returns (address payable proxyAdmin) { proxyAdmin = payable(address(uint160(uint256(vm.load(address(proxy), ADMIN_SLOT))))); if (!nullCheck) return proxyAdmin; require( @@ -18,11 +18,11 @@ library LibProxy { ); } - function getProxyAdmin(address payable proxy) internal returns (address payable proxyAdmin) { + function getProxyAdmin(address payable proxy) internal view returns (address payable proxyAdmin) { proxyAdmin = getProxyAdmin({ proxy: proxy, nullCheck: true }); } - function getProxyImplementation(address payable proxy, bool nullCheck) internal returns (address payable impl) { + function getProxyImplementation(address payable proxy, bool nullCheck) internal view returns (address payable impl) { impl = payable(address(uint160(uint256(vm.load(address(proxy), IMPLEMENTATION_SLOT))))); if (!nullCheck) return impl; require( @@ -31,7 +31,7 @@ library LibProxy { ); } - function getProxyImplementation(address payable proxy) internal returns (address payable impl) { + function getProxyImplementation(address payable proxy) internal view returns (address payable impl) { impl = getProxyImplementation({ proxy: proxy, nullCheck: true }); } } diff --git a/script/utils/DefaultContract.sol b/script/utils/DefaultContract.sol index d5af465..1e5b605 100644 --- a/script/utils/DefaultContract.sol +++ b/script/utils/DefaultContract.sol @@ -5,7 +5,8 @@ import { LibString } from "../../lib/solady/src/utils/LibString.sol"; import { TContract } from "../types/Types.sol"; enum DefaultContract { - ProxyAdmin + ProxyAdmin, + Multicall3 } using { key, name } for DefaultContract global; @@ -16,5 +17,6 @@ function key(DefaultContract defaultContract) pure returns (TContract) { function name(DefaultContract defaultContract) pure returns (string memory) { if (defaultContract == DefaultContract.ProxyAdmin) return "ProxyAdmin"; + if (defaultContract == DefaultContract.Multicall3) return "Multicall3"; revert("DefaultContract: Unknown contract"); } diff --git a/src/Proxy.sol b/src/Proxy.sol new file mode 100644 index 0000000..2e3280a --- /dev/null +++ b/src/Proxy.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "../lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +/** + * @title Proxy + * @dev A contract that acts as a proxy for transparent upgrades. + */ +contract Proxy is TransparentUpgradeableProxy { + /** + * @dev Initializes the Proxy contract. + * @param _logic The address of the logic contract. + * @param _admin The address of the admin contract. + * @param _data The initialization data. + */ + constructor(address _logic, address _admin, bytes memory _data) TransparentUpgradeableProxy(_logic, _admin, _data) { } +}