diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e8d435..0bb4642 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,10 +3,10 @@ name: test on: push: branches: - - mainnet - - testnet - 'feature/*' - 'features/*' + - 'implement-feature/**' + - 'implement-feature/**/**' pull_request: branches: - mainnet @@ -30,14 +30,17 @@ jobs: submodules: recursive - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 + uses: foundry-rs/foundry-toolchain@v1 with: - version: nightly + version: nightly-2b1f8d6dd90f9790faf0528e05e60e573a7569ce + + - name: Install soldeer + run: forge soldeer install - name: Run Forge build run: | forge --version - forge build --sizes + forge build id: build - name: Run Forge tests diff --git a/.gitignore b/.gitignore index 89bcfbd..caa3197 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,8 @@ docs/ *.env .vscode -node_modules/ -yarn-error.log -.yarn -.yarnrc.yml \ No newline at end of file +deployments/**/exported_address +dependencies +deployments +logs/deployed-contracts +broadcast \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a2ad802..0000000 --- a/.gitmodules +++ /dev/null @@ -1,16 +0,0 @@ -[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 - shallow = true -[submodule "lib/contract-libs"] - path = lib/contract-libs - url = https://github.com/axieinfinity/contract-libs - shallow = true diff --git a/foundry.toml b/foundry.toml index 726a500..6fd6265 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,13 +1,13 @@ [profile.default] src = "src" out = "out" -libs = ["lib"] +libs = ["dependencies"] +test = "test" ffi = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -solc = '0.8.23' extra_output = ["devdoc", "userdoc", "storagelayout"] -evm_version = 'istanbul' +evm_version = 'london' use_literal_content = true fs_permissions = [{ access = "read-write", path = "./" }] @@ -15,10 +15,41 @@ fs_permissions = [{ access = "read-write", path = "./" }] line_length = 120 tab_width = 2 bracket_spacing = true +multiline_func_header = 'params_first' +single_line_statement_blocks = 'single' +sort_imports = true +number_underscore = "thousands" +wrap_comments = true [rpc_endpoints] ethereum = "https://eth.llamarpc.com" goerli = "https://ethereum-goerli.publicnode.com" ronin-mainnet = "https://api-partner.roninchain.com/rpc" ronin-testnet = "https://saigon-archive.roninchain.com/rpc" -localhost = "http://localhost:8545" \ No newline at end of file +localhost = "http://localhost:8545" + +[dependencies] +"forge-std" = { version = "1.9.3", url = "https://github.com/foundry-rs/forge-std/archive/refs/tags/v1.9.3.zip" } +"solady" = { version = "0.0.228", url = "https://github.com/Vectorized/solady/archive/refs/tags/v0.0.228.zip" } +"openzeppelin" = { version = "5.0.2", url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip" } +"openzeppelin-v4" = { version = "4.9.5", url = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v4.9.5.zip" } + +[soldeer] +# whether soldeer manages remappings +remappings_generate = false + +# whether soldeer re-generates all remappings when installing, updating or uninstalling deps +remappings_regenerate = false + +# whether to suffix the remapping with the version: `name-a.b.c` +remappings_version = true + +# a prefix to add to the remappings ("@" would give `@name`) +remappings_prefix = "" + +# where to store the remappings ("txt" for `remappings.txt` or "config" for `foundry.toml`) +# ignored when `soldeer.toml` is used as config (uses `remappings.txt`) +remappings_location = "txt" + +# whether to install sub-dependencies or not. If true this wil install the dependencies of dependencies 1 level down. +recursive_deps = true diff --git a/generate-artifact.sh b/generate-artifact.sh new file mode 100755 index 0000000..12b9a41 --- /dev/null +++ b/generate-artifact.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +# Parse the command-line arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --name) + name="$2" + shift + ;; + --args) + args="$2" + shift + ;; + --value) + value="$2" + shift + ;; + --nonce) + nonce="$2" + shift + ;; + --address) + address="$2" + shift + ;; + --deployer) + deployer="$2" + shift + ;; + --chainid) + chainid="$2" + shift + ;; + --block-number) + block_number="$2" + shift + ;; + --timestamp) + timestamp="$2" + shift + ;; + --absolute-path) + absolute_path="$2" + shift + ;; + --path) + path="$2" + shift + ;; + --artifact-name) + artifact_name="$2" + shift + ;; + *) + echo "Unknown parameter passed: $1" + exit 1 + ;; + esac + shift +done + +if [[ -z "$name" || -z "$args" || -z "$value" || -z "$nonce" || -z "$deployer" || -z "$chainid" || -z "$block-number" || -z "$timestamp" || -z "$absolute_path" || -z "$path" ]]; then + echo "Error: Missing required arguments." + echo "Usage: ./generate-artifact.sh --name --value --nonce --address
--deployer --chainid --block-number --timestamp --absolute-path --args --path --artifact-name " + exit 1 +fi + +# Generate the artifact +abi=$(forge inspect $name abi) +devdoc=$(forge inspect $name devdoc) +userdoc=$(forge inspect $name userdoc) +metadata=$(forge inspect $name metadata) +storage_layout=$(forge inspect $name storageLayout) +bytecode=$(forge inspect $name bytecode) +deployed_bytecode=$(forge inspect $name deployedBytecode) + +# Create the JSON object +json_content=$( + jq -n \ + --arg name "$name" \ + --arg address "$address" \ + --arg args "$args" \ + --arg value "$value" \ + --arg nonce "$nonce" \ + --arg deployer "$deployer" \ + --arg chainid "$chainid" \ + --arg block_number "$block_number" \ + --arg timestamp "$timestamp" \ + --argjson abi "$abi" \ + --argjson devdoc "$devdoc" \ + --argjson userdoc "$userdoc" \ + --argjson metadata "$metadata" \ + --argjson storage_layout "$storage_layout" \ + --arg bytecode "$bytecode" \ + --arg deployed_bytecode "$deployed_bytecode" \ + '{ + name: $name, + address: $address, + args: $args, + value: $value, + nonce: $nonce, + deployer: $deployer, + chainid: $chainid, + block_number: $block_number, + timestamp: $timestamp, + abi: $abi, + devdoc: $devdoc, + userdoc: $userdoc, + metadata: $metadata, + storage_layout: $storage_layout, + bytecode: $bytecode, + deployed_bytecode: $deployed_bytecode + }' +) + +# Write the JSON object to the specified path +echo "$json_content" >"$path/$artifact_name.json" + +# Write the JSON object to the specified path +echo "$json_content" >"$path/$artifact_name.json" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..ad495ef --- /dev/null +++ b/install.sh @@ -0,0 +1,28 @@ +# Check if foundry is installed +if ! command -v $HOME/.foundry/bin/forge &>/dev/null; then + # Install foundryup + curl -L https://foundry.paradigm.xyz | bash + # Install foundry + $HOME/.foundry/bin/foundryup -v nightly-de33b6af53005037b463318d2628b5cfcaf39916 # Stable version +fi + +# Check if rustup is installed +if ! command -v rustup &>/dev/null; then + # Install rustup + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +fi + +# Update rustup +$HOME/.cargo/bin/rustup update stable +# Install soldeer +$HOME/.cargo/bin/cargo install soldeer +# Update dependencies with soldeer +$HOME/.cargo/bin/soldeer update +# Run forge build +$HOME/.foundry/bin/forge build + +# Check if rustup is installed +if ! command -v jq &>/dev/null; then + # Install jq + brew install jq +fi diff --git a/lib/contract-libs b/lib/contract-libs deleted file mode 160000 index 2388600..0000000 --- a/lib/contract-libs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2388600fbc1874ac588c5f779c498beb52213ca3 diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index b6a506d..0000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6a506db2262cad5ff982a87789ee6d1558ec861 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts deleted file mode 160000 index fd81a96..0000000 --- a/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fd81a96f01cc42ef1c9a5399364968d0e07e9e90 diff --git a/lib/solady b/lib/solady deleted file mode 160000 index d457831..0000000 --- a/lib/solady +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d457831578c0714d648ef19b599f9d7172113816 diff --git a/package.json b/package.json deleted file mode 100644 index e27edee..0000000 --- a/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "foundry-deployment-kit", - "version": "1.0.0", - "description": "Deployment kit for Foundry", - "author": "Axie Infinity Engineering ", - "license": "MIT", - "repository": "https://github.com/axieinfinity/foundry-deployment-kit.git", - "files": [ - "src/**/*.sol" - ], - "devDependencies": { - "husky": "^8.0.3", - "lint-staged": "^14.0.1" - }, - "lint-staged": { - "{src,script,test}/**/*.sol": [ - "forge fmt" - ] - }, - "scripts": { - "prepare": "husky install" - } -} diff --git a/remappings.txt b/remappings.txt index 952eae5..52fee5d 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,4 @@ -foundry-deployment-kit/=script/ \ No newline at end of file +@fdk/=script/ +forge-std=dependencies/forge-std-1.9.3/src/ +@solady-0.0.228=dependencies/solady-0.0.228 +@openzeppelin=dependencies/openzeppelin-5.0.2 \ No newline at end of file diff --git a/run.sh b/run.sh index 9ed4253..ed3f3a6 100755 --- a/run.sh +++ b/run.sh @@ -1,38 +1,210 @@ +# Function to display script usage +usage() { + forge script --help + + echo "" + echo "\033[33mFoundry Script Usage:\033[0m" + echo "Usage: $0 [forge_options] --no-postcheck|--npo --no-precheck|--npr --sender {sender_address} --force-generate-artifact" + echo "Options:" + echo " --no-postcheck: Disable post-check" + echo " --no-precheck: Disable pre-check" + echo " --sender: Specify the default sender address" + echo " --force-generate-artifact: Force generate artifact" + + exit 1 +} + +# Check if command-line arguments are provided +if [ "$#" -eq 0 ]; then + usage +fi + verify_arg="" extra_argument="" +index=0 +op_command="" +network_name="" +is_broadcast=false +should_verify=false +force_generate_artifact=false +# Define the deployments folder by concatenating it with the child folder +root="deployments/" + +export_address() { + index=0 + + start_time=$(date +%s) + + for folder in "$root"/*; do + # If exported_address.toml exists, delete it + if [ -f "$folder"/exported_address ]; then + rm "$folder"/exported_address + fi + + # Create a new exported_address file + touch "$folder"/exported_address + + for file in "$folder"/*.json; do + + # Check if the file exists and is a regular file + if [ -f "$file" ] && [ "$(basename "$file")" != ".chainId" ] && [ "$(basename "$file")" != "exported_address" ]; then + ((index++)) + ( + # Extract address from the JSON file + contractAddress=$(jq -r '.address' "$file") + # Extract contractName from file name without the extension + contractName=$(basename "$file" .json) + + # Check if contractName and address are not empty + if [ -n "$contractName" ]; then + # Write to file the contractName and address + echo "$contractName.json@$contractAddress" >>"$folder"/exported_address + else + echo "Error: Missing contractName or address in $file" + fi + ) & + fi + + # Check if index is a multiple of 10, then wait + if [ $((index % 10)) -eq 0 ]; then + wait + fi + done + done + + wait + + end_time=$(date +%s) + echo "Export address in deployment folder: $((end_time - start_time)) seconds" +} + +index=0 + for arg in "$@"; do case $arg in - --trezor) + -t | --trezor) extra_argument+=trezor@ ;; - --no-postcheck) + --np | --no-postcheck) set -- "${@/#--no-postcheck/}" extra_argument+=no-postcheck@ ;; - --generate-artifacts) - set -- "${@/#--generate-artifacts/}" - extra_argument+=generate-artifact@ + --npr | --no-precheck) + set -- "${@/#--no-precheck/}" + extra_argument+=no-precheck@ + ;; + --verify) + should_verify=true + set -- "${@/#--verify/}" + ;; + -f | --fork-url) + network_name=${@:index+2:1} + # skip if network_name is localhost + if [[ $network_name != "localhost" ]]; then + extra_argument+="network.${network_name}@" + + set -- "${@/#-f/}" + set -- "${@/#--fork-url/}" + set -- "${@/#$network_name/}" + fi + + ;; + --fork-block-number) + fork_block_number=${@:index+2:1} + extra_argument+="fork-block-number.${fork_block_number}@" + + set -- "${@/#--fork-block-number/}" + set -- "${@/#$fork_block_number/}" + ;; + --broadcast) + is_broadcast=true + ;; + --sender) + sender=${@:index+2:1} + extra_argument+="sender.${sender}@" + ;; + --force-generate-artifact) + force_generate_artifact=true + + set -- "${@/#--force-generate-artifact/}" ;; - -atf) - set -- "${@/#-atf/}" - extra_argument+=generate-artifact@ + -h | --help) + usage + exist 1 ;; *) ;; esac + index=$((index + 1)) done +export_address + +echo "\033[33mTrying to compile contracts ...\033[0m" +forge build --offline # Ensure the contracts are compiled before running the script + +should_verify=$([[ $should_verify == true && $is_broadcast == true ]] && echo true || echo false) + +if [[ $force_generate_artifact == true ]]; then + extra_argument+=generate-artifact@ +fi + +if [[ $should_verify == true ]] && [[ $force_generate_artifact == false ]]; then + extra_argument+=generate-artifact@ +fi + +if [[ $should_verify == true ]]; then + if [[ $network_name == "ronin-mainnet" ]] || [[ $network_name == "ronin-testnet" ]]; then + verify_arg="--verify --retries 5 --verifier sourcify --verifier-url https://sourcify.roninchain.com/server/" + else + verify_arg="--verify --retries 5" + fi +fi + +echo "Should Verify Contract: $should_verify" + +# Get the directory of the current script +script_root="$(dirname "$(realpath "$0")")" +extra_argument+="script-root.${script_root}@" + # 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" --" + # Check if the .env file exists + if [ -f .env ]; then + source .env + # If network_name is empty, set it to localhost + network_name=${network_name:-localhost} + # Convert network name to uppercase + account_label=$(echo $network_name | tr '[:lower:]' '[:upper:]') + # Replace "-" with "_" + account_label=$(echo $account_label | tr '-' '_') + # Add "_PK" prefix + account_label="${account_label}_PK" + + # Check if the private key is stored in the .env file + if [[ $(eval "echo \$$account_label") == *"op://"* ]]; then + echo "\033[32mFound 'op://' in ${account_label}\033[0m" + op_command="op run --env-file="./.env" --" + elif [[ $(eval "echo \$$account_label") == *""* ]]; then + echo "\033[33mWARNING: Not found private key in ${account_label}\033[0m" + fi + else + echo "\033[33mWARNING: .env file not found\033[0m" fi + +fi + +start_time=$(date +%s) + +${op_command} forge script --offline ${verify_arg} ${@} -g 200 --sig 'run(bytes,string)' $(cast calldata 'run()') "${extra_argument}" + +if [ $? -ne 0 ]; then + exit 1 fi -calldata=$(cast calldata 'run()') -${op_command} forge script ${verify_arg} ${@} -g 200 --sig 'run(bytes,string)' ${calldata} "${extra_argument}" \ No newline at end of file +end_time=$(date +%s) + +echo "Execution time: $((end_time - start_time))s" diff --git a/script/ArtifactFactory.sol b/script/ArtifactFactory.sol deleted file mode 100644 index 2628800..0000000 --- a/script/ArtifactFactory.sol +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { Vm } from "../lib/forge-std/src/Vm.sol"; -import { stdJson } from "../lib/forge-std/src/StdJson.sol"; -import { StdStyle } from "../lib/forge-std/src/StdStyle.sol"; -import { console2 as console } from "../lib/forge-std/src/console2.sol"; -import { LibString } from "../lib/solady/src/utils/LibString.sol"; -import { JSONParserLib } from "../lib/solady/src/utils/JSONParserLib.sol"; -import { IArtifactFactory } from "./interfaces/IArtifactFactory.sol"; -import { IGeneralConfig } from "./interfaces/IGeneralConfig.sol"; -import { LibSharedAddress } from "./libraries/LibSharedAddress.sol"; - -contract ArtifactFactory is IArtifactFactory { - using stdJson for *; - using StdStyle for *; - using LibString for *; - using JSONParserLib for *; - - Vm internal constant vm = Vm(LibSharedAddress.VM); - IGeneralConfig public constant CONFIG = IGeneralConfig(LibSharedAddress.CONFIG); - - function generateArtifact( - address deployer, - address contractAddr, - string memory contractAbsolutePath, - string memory fileName, - bytes calldata args, - uint256 nonce - ) external { - console.log( - string.concat( - fileName, - " will be deployed at: ", - CONFIG.getExplorer(CONFIG.getCurrentNetwork()), - "address/", - contractAddr.toHexString() - ).green(), - string.concat("(nonce: ", nonce.toString(), ")") - ); - 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; - uint256 numDeployments = 1; - - if (vm.exists(filePath)) { - string memory existedJson = vm.readFile(filePath); - if (vm.keyExists(existedJson, ".numDeployments")) { - numDeployments = vm.parseJsonUint(vm.readFile(filePath), ".numDeployments"); - numDeployments += 1; - } - } - - json.serialize("args", args); - json.serialize("nonce", nonce); - json.serialize("isFoundry", true); - json.serialize("deployer", deployer); - json.serialize("chainId", block.chainid); - json.serialize("address", contractAddr); - json.serialize("blockNumber", block.number); - json.serialize("timestamp", block.timestamp); - json.serialize("contractAbsolutePath", contractAbsolutePath); - json.serialize("numDeployments", numDeployments); - - 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(); - - json.serialize("abi", item.at('"abi"').value()); - json.serialize("ast", item.at('"ast"').value()); - json.serialize("devdoc", item.at('"devdoc"').value()); - json.serialize("userdoc", item.at('"userdoc"').value()); - json.serialize("metadata", item.at('"rawMetadata"').value()); - json.serialize("storageLayout", item.at('"storageLayout"').value()); - json.serialize("bytecode", item.at('"bytecode"').at('"object"').value()); - json = json.serialize("deployedBytecode", item.at('"deployedBytecode"').at('"object"').value()); - - json.write(filePath); - } -} diff --git a/script/BaseGeneralConfig.sol b/script/BaseGeneralConfig.sol index 8b5687e..8ac2495 100644 --- a/script/BaseGeneralConfig.sol +++ b/script/BaseGeneralConfig.sol @@ -1,21 +1,33 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { Vm, VmSafe } from "../lib/forge-std/src/Vm.sol"; -import { StdStyle } from "../lib/forge-std/src/StdStyle.sol"; -import { console2 as console } from "../lib/forge-std/src/console2.sol"; -import { WalletConfig } from "./configs/WalletConfig.sol"; -import { RuntimeConfig } from "./configs/RuntimeConfig.sol"; +import { StdStyle } from "../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm, VmSafe } from "../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; + +import { ContractConfig, EnumerableSet, TContract } from "./configs/ContractConfig.sol"; import { MigrationConfig } from "./configs/MigrationConfig.sol"; -import { TNetwork, NetworkConfig } from "./configs/NetworkConfig.sol"; -import { EnumerableSet, TContract, ContractConfig } from "./configs/ContractConfig.sol"; +import { NetworkConfig, TNetwork } from "./configs/NetworkConfig.sol"; +import { RuntimeConfig } from "./configs/RuntimeConfig.sol"; +import { UserDefinedConfig } from "./configs/UserDefinedConfig.sol"; +import { WalletConfig } from "./configs/WalletConfig.sol"; + import { ISharedParameter } from "./interfaces/configs/ISharedParameter.sol"; -import { DefaultNetwork } from "./utils/DefaultNetwork.sol"; -import { DefaultContract } from "./utils/DefaultContract.sol"; + import { LibSharedAddress } from "./libraries/LibSharedAddress.sol"; +import { DefaultContract } from "./utils/DefaultContract.sol"; +import { DefaultNetwork } from "./utils/DefaultNetwork.sol"; -contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, NetworkConfig, MigrationConfig { - using StdStyle for string; +contract BaseGeneralConfig is + RuntimeConfig, + WalletConfig, + ContractConfig, + NetworkConfig, + MigrationConfig, + UserDefinedConfig +{ + using StdStyle for *; using EnumerableSet for EnumerableSet.AddressSet; fallback() external { @@ -29,16 +41,21 @@ contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, Netwo } } - constructor(string memory absolutePath, string memory deploymentRoot) - NetworkConfig(deploymentRoot) - ContractConfig(absolutePath, deploymentRoot) - { + constructor( + string memory absolutePath, + string memory deploymentRoot + ) NetworkConfig(deploymentRoot) ContractConfig(absolutePath, deploymentRoot) { + console.log("GeneralConfig: ", absolutePath); _setUpDefaultNetworks(); _setUpDefaultContracts(); _setUpDefaultSender(); _storeDeploymentData(deploymentRoot); } + function setUpDefaultContracts() public { + _setUpDefaultContracts(); + } + function _setUpNetworks() internal virtual { } function _setUpContracts() internal virtual { } @@ -46,30 +63,9 @@ contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, Netwo function _setUpSender() internal virtual { } function _setUpDefaultNetworks() private { - setNetworkInfo( - DefaultNetwork.Local.chainId(), - DefaultNetwork.Local.key(), - DefaultNetwork.Local.chainAlias(), - DefaultNetwork.Local.deploymentDir(), - DefaultNetwork.Local.envLabel(), - DefaultNetwork.Local.explorer() - ); - setNetworkInfo( - DefaultNetwork.RoninTestnet.chainId(), - DefaultNetwork.RoninTestnet.key(), - DefaultNetwork.RoninTestnet.chainAlias(), - DefaultNetwork.RoninTestnet.deploymentDir(), - DefaultNetwork.RoninTestnet.envLabel(), - DefaultNetwork.RoninTestnet.explorer() - ); - setNetworkInfo( - DefaultNetwork.RoninMainnet.chainId(), - DefaultNetwork.RoninMainnet.key(), - DefaultNetwork.RoninMainnet.chainAlias(), - DefaultNetwork.RoninMainnet.deploymentDir(), - DefaultNetwork.RoninMainnet.envLabel(), - DefaultNetwork.RoninMainnet.explorer() - ); + setNetworkInfo(DefaultNetwork.LocalHost.data()); + setNetworkInfo(DefaultNetwork.RoninTestnet.data()); + setNetworkInfo(DefaultNetwork.RoninMainnet.data()); _setUpNetworks(); } @@ -77,18 +73,141 @@ 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 - ); + + // ------------------------- Localhost ------------------------- + TNetwork localhost = DefaultNetwork.LocalHost.key(); + setAddress(localhost, DefaultContract.ProxyAdmin.key(), address(0xdead)); + + // ------------------------- Ronin Testnet ------------------------- + TNetwork roninTestnet = DefaultNetwork.RoninTestnet.key(); + + // Double check source: https://saigon-app.roninchain.com/address/0x505d91E8fd2091794b45b27f86C045529fa92CD7 + setAddress(roninTestnet, DefaultContract.ProxyAdmin.key(), 0x505d91E8fd2091794b45b27f86C045529fa92CD7); + + // Double check source: https://saigon-app.roninchain.com/address/0xcA11bde05977b3631167028862bE2a173976CA11 + setAddress(roninTestnet, DefaultContract.Multicall3.key(), 0xcA11bde05977b3631167028862bE2a173976CA11); + + // Double check source: https://saigon-app.roninchain.com/address/0xA959726154953bAe111746E265E6d754F48570E6 + setAddress(roninTestnet, DefaultContract.WRON.key(), 0xA959726154953bAe111746E265E6d754F48570E6); + + // Double check source: https://saigon-app.roninchain.com/address/0x2D3Aa3503B4EB3EEea370e2e089E3DEe43D5091C + setAddress(roninTestnet, DefaultContract.WRONHelper.key(), 0x2D3Aa3503B4EB3EEea370e2e089E3DEe43D5091C); + + // Double check source: https://saigon-app.roninchain.com/address/0x29C6F8349A028E1bdfC68BFa08BDee7bC5D47E16 + setAddress(roninTestnet, DefaultContract.WETH.key(), 0x29C6F8349A028E1bdfC68BFa08BDee7bC5D47E16); + + // Double check source: https://saigon-app.roninchain.com/address/0x3C4e17b9056272Ce1b49F6900d8cFD6171a1869d + setAddress(roninTestnet, DefaultContract.AXS.key(), 0x3C4e17b9056272Ce1b49F6900d8cFD6171a1869d); + + // Double check source: https://saigon-app.roninchain.com/address/0xFc4090C0A3c07155484Da061B9d9cB8650e6A8cC + setAddress(roninTestnet, DefaultContract.Scatter.key(), 0xFc4090C0A3c07155484Da061B9d9cB8650e6A8cC); + + // Double check source: https://saigon-app.roninchain.com/address/0xDa44546C0715ae78D454fE8B84f0235081584Fe0 + setAddress(roninTestnet, DefaultContract.KatanaRouter.key(), 0xDa44546C0715ae78D454fE8B84f0235081584Fe0); + + // Double check source: https://saigon-app.roninchain.com/address/0x86587380C4c815Ba0066c90aDB2B45CC9C15E72c + setAddress(roninTestnet, DefaultContract.KatanaFactory.key(), 0x86587380C4c815Ba0066c90aDB2B45CC9C15E72c); + + // Double check source: https://saigon-app.roninchain.com/address/0x247F12836A421CDC5e22B93Bf5A9AAa0f521f986 + setAddress(roninTestnet, DefaultContract.KatanaGovernance.key(), 0x247F12836A421CDC5e22B93Bf5A9AAa0f521f986); + + // Double check source: https://saigon-app.roninchain.com/address/0x4a913d50E618Ee9F61FfA288D8f8040D489d2360 + setAddress(roninTestnet, DefaultContract.AffiliateRouter.key(), 0x4a913d50E618Ee9F61FfA288D8f8040D489d2360); + + // Double check source: https://saigon-app.roninchain.com/address/0x3BD36748D17e322cFB63417B059Bcc1059012D83 + setAddress(roninTestnet, DefaultContract.PermissionedRouter.key(), 0x3BD36748D17e322cFB63417B059Bcc1059012D83); + + // Double check source: https://saigon-app.roninchain.com/address/0x88Ae5Ff2D22018542C842b68D4F924387f48215d + setAddress(roninTestnet, DefaultContract.USDC.key(), 0x88Ae5Ff2D22018542C842b68D4F924387f48215d); + + // Double check source: https://saigon-app.roninchain.com/address/0xcaCA1c072D26E46686d932686015207FbE08FdB8 + setAddress(roninTestnet, DefaultContract.Axie.key(), 0xcaCA1c072D26E46686d932686015207FbE08FdB8); + + // Double check source: https://saigon-app.roninchain.com/address/0xA2aa501b19aff244D90cc15a4Cf739D2725B5729 + setAddress(roninTestnet, DefaultContract.Pyth.key(), 0xA2aa501b19aff244D90cc15a4Cf739D2725B5729); + + // Double check source: https://saigon-app.roninchain.com/address/0x2E889348bD37f192063Bfec8Ff39bD3635949e20 + setAddress(roninTestnet, DefaultContract.ERC721BatchTransfer.key(), 0x2E889348bD37f192063Bfec8Ff39bD3635949e20); + + // Double check source: https://saigon-app.roninchain.com/address/0x53Ea388CB72081A3a397114a43741e7987815896 + setAddress(roninTestnet, DefaultContract.RoninGovernanceAdmin.key(), 0x53Ea388CB72081A3a397114a43741e7987815896); + + // Double check source: https://saigon-app.roninchain.com/address/0x54B3AC74a90E64E8dDE60671b6fE8F8DDf18eC9d + setAddress(roninTestnet, DefaultContract.RoninValidatorSet.key(), 0x54B3AC74a90E64E8dDE60671b6fE8F8DDf18eC9d); + + // Double check source: https://saigon-app.roninchain.com/address/0x3b67c8D22a91572a6AB18acC9F70787Af04A4043 + setAddress(roninTestnet, DefaultContract.Profile.key(), 0x3b67c8D22a91572a6AB18acC9F70787Af04A4043); + + // Double check source: https://saigon-app.roninchain.com/address/0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312 + setAddress(roninTestnet, DefaultContract.RoninVRFCoordinator.key(), 0xA60c1e07fa030E4B49Eb54950ADb298Ab94dD312); + + // ------------------------- Ronin Mainnet ------------------------- + TNetwork roninMainnet = DefaultNetwork.RoninMainnet.key(); + // Double check source: https://app.roninchain.com/address/0xA3e7d085E65CB0B916f6717da876b7bE5cC92f03 + setAddress(roninMainnet, DefaultContract.ProxyAdmin.key(), 0xA3e7d085E65CB0B916f6717da876b7bE5cC92f03); + + // Double check source: https://app.roninchain.com/address/0xC76d0d0D3Aa608190f78db02Bf2f5AeF374fC0b9 + setAddress(roninMainnet, DefaultContract.Multicall2.key(), 0xC76d0d0D3Aa608190f78db02Bf2f5AeF374fC0b9); + + // Double check source: https://app.roninchain.com/address/0xcA11bde05977b3631167028862bE2a173976CA11 + setAddress(roninMainnet, DefaultContract.Multicall3.key(), 0xcA11bde05977b3631167028862bE2a173976CA11); + + // Double check source: https://app.roninchain.com/address/0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4 + setAddress(roninMainnet, DefaultContract.WRON.key(), 0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4); + + // Double check source: https://app.roninchain.com/address/0xCAF3E62b27a3dF0766721d1959d22b066E1a57F1 + setAddress(roninMainnet, DefaultContract.WRONHelper.key(), 0xCAF3E62b27a3dF0766721d1959d22b066E1a57F1); + + // Double check source: https://app.roninchain.com/address/0xc99a6A985eD2Cac1ef41640596C5A5f9F4E19Ef5 + setAddress(roninMainnet, DefaultContract.WETH.key(), 0xc99a6A985eD2Cac1ef41640596C5A5f9F4E19Ef5); + + // Double check source: https://app.roninchain.com/address/0x97a9107C1793BC407d6F527b77e7fff4D812bece + setAddress(roninMainnet, DefaultContract.AXS.key(), 0x97a9107C1793BC407d6F527b77e7fff4D812bece); + + // Double check source: https://app.roninchain.com/address/0x5d518933351a0bC14B24B329b33b813565608769 + setAddress(roninMainnet, DefaultContract.Scatter.key(), 0x5d518933351a0bC14B24B329b33b813565608769); + + // Double check source: https://app.roninchain.com/address/0x7D0556D55ca1a92708681e2e231733EBd922597D + setAddress(roninMainnet, DefaultContract.KatanaRouter.key(), 0x7D0556D55ca1a92708681e2e231733EBd922597D); + + // Double check source: https://app.roninchain.com/address/0xB255D6A720BB7c39fee173cE22113397119cB930 + setAddress(roninMainnet, DefaultContract.KatanaFactory.key(), 0xB255D6A720BB7c39fee173cE22113397119cB930); + + // Double check source: https://app.roninchain.com/address/0x2C1726346d83cBF848bD3C2B208ec70d32a9E44a + setAddress(roninMainnet, DefaultContract.KatanaGovernance.key(), 0x2C1726346d83cBF848bD3C2B208ec70d32a9E44a); + + // Double check source: https://app.roninchain.com/address/0x77F96cF7b98B963fB8A9b84787806D396d953b2b + setAddress(roninMainnet, DefaultContract.AffiliateRouter.key(), 0x77F96cF7b98B963fB8A9b84787806D396d953b2b); + + // Double check source: https://app.roninchain.com/address/0xC05AFC8c9353c1dd5f872EcCFaCD60fd5A2a9aC7 + setAddress(roninMainnet, DefaultContract.PermissionedRouter.key(), 0xC05AFC8c9353c1dd5f872EcCFaCD60fd5A2a9aC7); + + // Double check source: https://app.roninchain.com/address/0x9D05D1F5b0424F8fDE534BC196FFB6Dd211D902a + setAddress(roninMainnet, DefaultContract.SCMultisig.key(), 0x9D05D1F5b0424F8fDE534BC196FFB6Dd211D902a); + + // Double check source: https://app.roninchain.com/address/0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc + setAddress(roninMainnet, DefaultContract.USDC.key(), 0x0B7007c13325C48911F73A2daD5FA5dCBf808aDc); + + // Double check source: https://app.roninchain.com/address/0x32950db2a7164aE833121501C797D79E7B79d74C + setAddress(roninMainnet, DefaultContract.Axie.key(), 0x32950db2a7164aE833121501C797D79E7B79d74C); + + // Double check source: https://app.roninchain.com/address/0x2880aB155794e7179c9eE2e38200202908C17B43 + setAddress(roninMainnet, DefaultContract.Pyth.key(), 0x2880aB155794e7179c9eE2e38200202908C17B43); + + // Double check source: https://app.roninchain.com/address/0x2368dfED532842dB89b470fdE9Fd584d48D4F644 + setAddress(roninMainnet, DefaultContract.ERC721BatchTransfer.key(), 0x2368dfED532842dB89b470fdE9Fd584d48D4F644); + + // Double check source: https://app.roninchain.com/address/0x946397deDFd2f79b75a72B322944a21C3240c9c3 + setAddress(roninMainnet, DefaultContract.RoninGovernanceAdmin.key(), 0x946397deDFd2f79b75a72B322944a21C3240c9c3); + + // Double check source: https://app.roninchain.com/address/0x617c5d73662282EA7FfD231E020eCa6D2B0D552f + setAddress(roninMainnet, DefaultContract.RoninValidatorSet.key(), 0x617c5d73662282EA7FfD231E020eCa6D2B0D552f); + + // Double check source: https://app.roninchain.com/address/0x840EBf1CA767CB690029E91856A357a43B85d035 + setAddress(roninMainnet, DefaultContract.Profile.key(), 0x840EBf1CA767CB690029E91856A357a43B85d035); + + // Double check source: https://app.roninchain.com/address/0x16A62a921e7fEC5Bf867fF5c805b662Db757B778 + setAddress(roninMainnet, DefaultContract.RoninVRFCoordinator.key(), 0x16A62a921e7fEC5Bf867fF5c805b662Db757B778); _setUpContracts(); } @@ -99,39 +218,75 @@ 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); + if (sender == address(0x0) && getCurrentNetwork() == DefaultNetwork.LocalHost.key()) { + sender = payable(DEFAULT_SENDER); + } require(sender != address(0x0), "GeneralConfig: Sender is address(0x0)"); } function setAddress(TNetwork network, TContract contractType, address contractAddr) public virtual { - uint256 chainId = _networkDataMap[network].chainId; string memory contractName = getContractName(contractType); - require(chainId != 0 && bytes(contractName).length != 0, "GeneralConfig: Network or Contract Key not found"); + require( + network != TNetwork.wrap(0x0) && bytes(contractName).length != 0, + string.concat( + "GeneralConfig: Network or Contract Key not found. Network: ", network.chainAlias(), " Contract: ", contractName + ) + ); - label(chainId, contractAddr, contractName); - _contractAddrSet[chainId].add(contractAddr); - _contractTypeMap[chainId][contractAddr] = contractType; - _contractAddrMap[chainId][contractName] = contractAddr; + label(network, contractAddr, contractName); + _contractAddrSet[network].add(contractAddr); + _contractTypeMap[network][contractAddr] = contractType; + _contractAddrMap[network][contractName] = contractAddr; } function getAddress(TNetwork network, TContract contractType) public view virtual returns (address payable) { - return getAddressByRawData(_networkDataMap[network].chainId, getContractName(contractType)); + return getAddressByRawData(network, getContractName(contractType)); } - function getAllAddresses(TNetwork network) public view virtual returns (address payable[] memory) { - return getAllAddressesByRawData(_networkDataMap[network].chainId); + function getAllAddresses( + TNetwork network + ) public view virtual returns (address payable[] memory) { + return getAllAddressesByRawData(network); } - function _handleRuntimeConfig() internal virtual override { + function logSenderInfo() public view { + console.log( + vm.getLabel(getSender()), + string.concat("| Balance: ".magenta(), vm.toString(getSender().balance / 1 ether), " ETHER") + ); + } + + function buildRuntimeConfig() public virtual override { + TNetwork currNetwork = getCurrentNetwork(); + if (_option.trezor) { _loadTrezorAccount(); - label(block.chainid, _trezorSender, "TrezorSender"); - console.log("GeneralConfig:", vm.getLabel(_trezorSender), "Enabled!"); - } else { - string memory envLabel = getPrivateKeyEnvLabel(getCurrentNetwork()); - _loadENVAccount(envLabel); - label(block.chainid, _envSender, "ENVSender"); - console.log("GeneralConfig:", vm.getLabel(_envSender), "Enabled!"); + label(currNetwork, _trezorSender, "trezor-sender"); + + return; + } + + if (_option.sender == address(0x0)) { + string memory env = currNetwork.env(); + try this.loadENVAccount(env) { + label(currNetwork, _envSender, "env-sender"); + return; + } catch { } + + if (currNetwork == DefaultNetwork.LocalHost.key() || currNetwork == TNetwork.wrap(0x0)) { + _envSender = DEFAULT_SENDER; + label(currNetwork, _envSender, "default-local-sender"); + + return; + } + + _envSender = address(0xdead); + label(currNetwork, _envSender, "mock-sender"); + + return; } + + _envSender = _option.sender; + label(currNetwork, _option.sender, "override-sender"); } } diff --git a/script/BaseMigration.s.sol b/script/BaseMigration.s.sol index d0fb3d2..5866a68 100644 --- a/script/BaseMigration.s.sol +++ b/script/BaseMigration.s.sol @@ -1,34 +1,32 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { ProxyAdmin } from "../lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; -import { ITransparentUpgradeableProxy, Proxy } from "../src/Proxy.sol"; -import { LibString } from "../lib/solady/src/utils/LibString.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 +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm } from "../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; +import { LibString } from "../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { RoninTransparentProxy } from "../src/RoninTransparentProxy.sol"; +import { TransparentProxyOZv4_9_5 } from "../src/TransparentProxyOZv4_9_5.sol"; + +import { OnchainExecutor } from "./OnchainExecutor.s.sol"; +import { IScriptExtended, ScriptExtended } from "./extensions/ScriptExtended.s.sol"; // cheat to load artifact to parent + // `out` directory import { IMigrationScript } from "./interfaces/IMigrationScript.sol"; + +import { DeployInfo, LibDeploy, ProxyInterface, UpgradeInfo } from "./libraries/LibDeploy.sol"; +import { LibInitializeGuard } from "./libraries/LibInitializeGuard.sol"; import { LibProxy } from "./libraries/LibProxy.sol"; + +import { TContract, TNetwork } from "./types/Types.sol"; import { DefaultContract } from "./utils/DefaultContract.sol"; -import { TContract } from "./types/Types.sol"; -import { LibErrorHandler } from "../lib/contract-libs/src/LibErrorHandler.sol"; +import { cheatBroadcast } from "./utils/Helpers.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); - bytes internal _overriddenArgs; mapping(TContract contractType => IMigrationScript deployScript) internal _deployScript; @@ -36,12 +34,26 @@ abstract contract BaseMigration is ScriptExtended { super.setUp(); _storeRawSharedArguments(); _injectDependencies(); - deploySharedAddress(address(ARTIFACT_FACTORY), type(ArtifactFactory).creationCode, "ArtifactFactory"); } - function _storeRawSharedArguments() internal virtual { - if (CONFIG.areSharedArgumentsStored()) return; - CONFIG.setRawSharedArguments(_sharedArguments()); + function _upgradeCallback( + address, /* proxy */ + address, /* logic */ + uint256, /* callValue */ + bytes memory, /* callData */ + ProxyInterface /* proxyInterface */ + ) internal virtual { } + + function _beforeRunningScript() internal virtual override { + vm.recordLogs(); + vm.startStateDiffRecording(); + } + + function _afterRunningScript() internal virtual override { + Vm.Log[] memory recordedLogs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + LibInitializeGuard.validate({ logs: recordedLogs, stateDiffs: stateDiffs }); } function _sharedArguments() internal virtual returns (bytes memory rawSharedArgs); @@ -50,7 +62,24 @@ abstract contract BaseMigration is ScriptExtended { function _defaultArguments() internal virtual returns (bytes memory) { } - function loadContractOrDeploy(TContract contractType) public virtual returns (address payable contractAddr) { + function switchTo( + TNetwork networkType, + uint256 forkBlockNumber + ) public virtual override returns (TNetwork currNetwork, uint256 currForkId) { + (currNetwork, currForkId) = super.switchTo(networkType, forkBlockNumber); + // Should rebuild the shared arguments since different chain may have different shared arguments + _storeRawSharedArguments(); + // Should rebuild runtime config since different chain may have different runtime config + vme.buildRuntimeConfig(); + // Should rebuild the contract data since different chain may have different contract data + vme.setUpDefaultContracts(); + // Log Sender Info of current network + vme.logSenderInfo(); + } + + function loadContractOrDeploy( + TContract contractType + ) public virtual returns (address payable contractAddr) { string memory contractName = CONFIG.getContractName(contractType); try this.loadContract(contractType) returns (address payable addr) { contractAddr = addr; @@ -60,11 +89,13 @@ abstract contract BaseMigration is ScriptExtended { } } - function loadContract(TContract contractType) public view virtual returns (address payable contractAddr) { - return CONFIG.getAddressFromCurrentNetwork(contractType); + function _storeRawSharedArguments() internal virtual { + vme.setRawSharedArguments(_sharedArguments()); } - function overrideArgs(bytes memory args) public virtual returns (IMigrationScript) { + function overrideArgs( + bytes memory args + ) public virtual returns (IMigrationScript) { _overriddenArgs = args; return IMigrationScript(address(this)); } @@ -73,130 +104,181 @@ abstract contract BaseMigration is ScriptExtended { args = _overriddenArgs.length == 0 ? _defaultArguments() : _overriddenArgs; } + function _cheatBroadcast(address from, address to, bytes memory callData) internal virtual { + cheatBroadcast(from, to, 0, callData); + } + function _getProxyAdmin() internal virtual returns (address payable proxyAdmin) { proxyAdmin = loadContract(DefaultContract.ProxyAdmin.key()); } - function _deployImmutable(TContract contractType) internal virtual returns (address payable deployed) { - deployed = _deployImmutable(contractType, arguments()); + function _deployImmutable( + TContract contractType + ) internal virtual returns (address payable deployed) { + deployed = _deployImmutable({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + by: sender(), + value: 0, + args: arguments() + }); } - function _deployImmutable(TContract contractType, bytes memory args) + function _deployImmutable( + TContract contractType, + bytes memory args + ) internal virtual returns (address payable deployed) { + deployed = _deployImmutable({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + by: sender(), + value: 0, + args: args + }); + } + + function _deployImmutable( + TContract contractType, + string memory artifactName, + address by, + uint256 value, + bytes memory args + ) internal virtual logFn(string.concat("_deployImmutable ", TContract.unwrap(contractType).unpackOne())) returns (address payable deployed) { - string memory contractName = CONFIG.getContractName(contractType); - string memory contractAbsolutePath = CONFIG.getContractAbsolutePath(contractType); - uint256 nonce; - (deployed, nonce) = _deployRaw(contractAbsolutePath, args); - CONFIG.setAddress(network(), contractType, deployed); - ARTIFACT_FACTORY.generateArtifact(sender(), deployed, contractAbsolutePath, contractName, args, nonce); + string memory contractName = vme.getContractName(contractType); + + deployed = DeployInfo({ + callValue: value, + by: by, + contractName: contractName, + absolutePath: vme.getContractAbsolutePath(contractType), + artifactName: bytes(artifactName).length == 0 ? contractName : artifactName, + constructorArgs: args + }).deployFromArtifact(); + + vme.setAddress(network(), contractType, deployed); } - function _deployLogic(TContract contractType) - internal - virtual - logFn(string.concat("_deployLogic ", TContract.unwrap(contractType).unpackOne())) - returns (address payable logic) - { - logic = _deployLogic(contractType, EMPTY_ARGS); + function _deployLogic( + TContract contractType + ) internal virtual returns (address payable logic) { + logic = _deployLogic({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + by: sender(), + constructorArgs: arguments() + }); } - function _deployLogic(TContract contractType, bytes memory args) + function _deployLogic(TContract contractType, bytes memory args) internal virtual returns (address payable logic) { + logic = _deployLogic({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + by: sender(), + constructorArgs: args + }); + } + + function _deployLogic( + TContract contractType, + string memory artifactName, + address by, + bytes memory constructorArgs + ) 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, args); - CONFIG.label(block.chainid, logic, string.concat(contractName, "::Logic")); - ARTIFACT_FACTORY.generateArtifact( - sender(), logic, contractAbsolutePath, string.concat(contractName, "Logic"), args, logicNonce - ); + string memory contractName = vme.getContractName(contractType); + + logic = DeployInfo({ + callValue: 0, + by: by, + contractName: contractName, + absolutePath: vme.getContractAbsolutePath(contractType), + artifactName: bytes(artifactName).length == 0 ? contractName : artifactName, + constructorArgs: constructorArgs + }).deployImplementation(); } - function _deployProxy(TContract contractType) internal virtual returns (address payable deployed) { + function _deployProxy( + TContract contractType + ) internal virtual returns (address payable deployed) { deployed = _deployProxy(contractType, arguments()); } - 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 callData + ) internal virtual returns (address payable deployed) { + deployed = _deployProxy({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + proxyAdmin: _getProxyAdmin(), + callValue: 0, + by: sender(), + callData: callData, + logicConstructorArgs: EMPTY_ARGS + }); } - function _deployProxy(TContract contractType, bytes memory args, bytes memory argsLogicConstructor) - internal - virtual - logFn(string.concat("_deployProxy ", TContract.unwrap(contractType).unpackOne())) - returns (address payable deployed) - { - string memory contractName = CONFIG.getContractName(contractType); - - 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"); - - _prankOrBroadcast(sender()); - deployed = payable(address(new Proxy(logic, proxyAdmin, args))); - - // validate proxy admin - address actualProxyAdmin = deployed.getProxyAdmin(); - assertEq( - actualProxyAdmin, - proxyAdmin, - string.concat( - "BaseMigration: Invalid proxy admin\n", - "Actual: ", - vm.toString(actualProxyAdmin), - "\nExpected: ", - vm.toString(proxyAdmin) - ) - ); - - CONFIG.setAddress(network(), contractType, deployed); - ARTIFACT_FACTORY.generateArtifact( - sender(), deployed, proxyAbsolutePath, string.concat(contractName, "Proxy"), args, proxyNonce - ); + function _deployProxy( + TContract contractType, + bytes memory callData, + bytes memory logicConstructorArgs + ) internal virtual returns (address payable deployed) { + deployed = _deployProxy({ + contractType: contractType, + artifactName: vme.getContractName(contractType), + proxyAdmin: _getProxyAdmin(), + callValue: 0, + by: sender(), + callData: callData, + logicConstructorArgs: logicConstructorArgs + }); } - function _deployRaw(string memory filename, bytes memory args) + function _deployProxy( + TContract contractType, + string memory artifactName, + address proxyAdmin, + uint256 callValue, + address by, + bytes memory callData, + bytes memory logicConstructorArgs + ) internal virtual - returns (address payable deployed, uint256 nonce) - { - nonce = vm.getNonce(sender()); - _prankOrBroadcast(sender()); - deployed = payable(deployCode(filename, args)); - } - - function _mockUpgradeProxy(TContract contractType) - internal - virtual - logFn(string.concat("_mockUpgradeProxy ", TContract.unwrap(contractType).unpackOne())) - returns (address payable proxy) - { - proxy = _mockUpgradeProxy(contractType, arguments()); - } - - function _mockUpgradeProxy(TContract contractType, bytes memory args) - internal - virtual - logFn(string.concat("_mockUpgradeProxy ", TContract.unwrap(contractType).unpackOne())) - returns (address payable proxy) + logFn(string.concat("_deployProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable deployed) { - address logic = _deployLogic(contractType); - proxy = CONFIG.getAddress(network(), contractType); - _mockUpgradeRaw(proxy.getProxyAdmin(), proxy, logic, args); + string memory contractName = vme.getContractName(contractType); + + deployed = LibDeploy.deployTransparentProxy({ + implInfo: DeployInfo({ + callValue: 0, + by: by, + contractName: contractName, + absolutePath: vme.getContractAbsolutePath(contractType), + artifactName: bytes(artifactName).length == 0 ? contractName : artifactName, + constructorArgs: logicConstructorArgs + }), + callValue: callValue, + proxyAdmin: proxyAdmin, + callData: callData + }); + + vme.setAddress(network(), contractType, deployed); } - function _upgradeProxy(TContract contractType) internal virtual returns (address payable proxy) { + function _upgradeProxy( + TContract contractType + ) internal virtual returns (address payable proxy) { proxy = _upgradeProxy(contractType, arguments()); } @@ -204,217 +286,50 @@ abstract contract BaseMigration is ScriptExtended { proxy = _upgradeProxy(contractType, args, EMPTY_ARGS); } - function _upgradeProxy(TContract contractType, bytes memory args, bytes memory argsLogicConstructor) + function _upgradeProxy( + TContract contractType, + bytes memory args, + bytes memory argsLogicConstructor + ) internal virtual logFn(string.concat("_upgradeProxy ", TContract.unwrap(contractType).unpackOne())) returns (address payable proxy) { + proxy = loadContract(contractType); address logic = _deployLogic(contractType, argsLogicConstructor); - proxy = CONFIG.getAddress(network(), contractType); - _upgradeRaw(proxy.getProxyAdmin(), proxy, logic, args); - } - - function _mockUpgradeRaw(address proxyAdmin, address payable proxy, address logic, bytes memory args) - internal - virtual - { - ITransparentUpgradeableProxy iProxy = ITransparentUpgradeableProxy(proxy); - ProxyAdmin wProxyAdmin = ProxyAdmin(proxyAdmin); - // if proxyAdmin is External Owned Wallet - if (proxyAdmin.code.length == 0) { - vm.prank(proxyAdmin); - if (args.length == 0) iProxy.upgradeTo(logic); - else iProxy.upgradeToAndCall(logic, args); - } else { - try wProxyAdmin.owner() returns (address owner) { - if (args.length == 0) { - // try `upgrade` function - vm.prank(owner); - (bool success,) = proxyAdmin.call(abi.encodeCall(ProxyAdmin.upgrade, (iProxy, logic))); - if (success) { - vm.prank(owner); - wProxyAdmin.upgrade(iProxy, logic); - } else { - console.log( - StdStyle.yellow( - "`ProxyAdmin:upgrade` failed!. Retrying with `ProxyAdmin:upgradeAndCall` with emty args..." - ) - ); - vm.prank(owner); - wProxyAdmin.upgradeAndCall(iProxy, logic, args); - } - } else { - vm.prank(owner); - wProxyAdmin.upgradeAndCall(iProxy, logic, args); - } - } catch { - revert("BaseMigration: Unknown ProxyAdmin contract!"); - } - } - } - - 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) { - _prankOrBroadcast(proxyAdmin); - if (args.length == 0) iProxy.upgradeTo(logic); - else iProxy.upgradeToAndCall(logic, args); - } else { - try wProxyAdmin.owner() returns (address owner) { - if (args.length == 0) { - // try `upgrade(address,address)` function - vm.prank(owner); - (bool success,) = proxyAdmin.call(abi.encodeCall(ProxyAdmin.upgrade, (iProxy, logic))); - if (success) { - if (owner.code.length != 0) { - _cheatUpgrade(owner, wProxyAdmin, iProxy, logic); - } else { - _prankOrBroadcast(owner); - wProxyAdmin.upgrade(iProxy, logic); - } - } else { - console.log( - "`ProxyAdmin:upgrade` failed!. Retrying with `ProxyAdmin:upgradeAndCall` with emty args...".yellow() - ); - if (owner.code.length != 0) { - _cheatUpgradeAndCall(owner, wProxyAdmin, iProxy, logic, args); - } else { - _prankOrBroadcast(owner); - wProxyAdmin.upgradeAndCall(iProxy, logic, args); - } - } - } else { - if (owner.code.length != 0) { - _cheatUpgradeAndCall(owner, wProxyAdmin, iProxy, logic, args); - } else { - _prankOrBroadcast(owner); - wProxyAdmin.upgradeAndCall(iProxy, logic, args); - } - } - } catch { - revert("BaseMigration: Unknown ProxyAdmin contract!"); - } - } - } - - 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("Cast Decoded Call Data:".cyan(), decodedCallData); - console.log( - "----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------" - ); - - // cheat prank to update `implementation slot` for next call - vm.prank(owner); - wProxyAdmin.upgrade(iProxy, logic); - } - - function _cheatUpgradeAndCall( - address owner, - ProxyAdmin wProxyAdmin, - ITransparentUpgradeableProxy iProxy, - address logic, - 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("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 - vm.prank(owner); - wProxyAdmin.upgradeAndCall(iProxy, logic, args); - } - - function _prankOrBroadcast(address to) internal virtual { - if (CONFIG.isPostChecking()) { - vm.prank(to); - } else { - vm.broadcast(to); - } + UpgradeInfo({ + proxy: proxy, + logic: logic, + callValue: 0, + callData: args, + shouldPrompt: true, + proxyInterface: ProxyInterface.Transparent, + upgradeCallback: _upgradeCallback, + shouldUseCallback: false + }).upgrade(); } function _setDependencyDeployScript(TContract contractType, IScriptExtended deployScript) internal virtual { - _deployScript[contractType] = IMigrationScript(address(deployScript)); + _setDependencyDeployScript(contractType, address(deployScript)); } function _setDependencyDeployScript(TContract contractType, address deployScript) internal virtual { _deployScript[contractType] = IMigrationScript(deployScript); + + vm.makePersistent(deployScript); + vm.allowCheatcodes(deployScript); + } + + /** + * @dev Cheat to force build artifact for customized TransparentProxy contract + * + * Can be disabled in child contract by overriding this function + */ + function _precompileProxyContracts() internal pure virtual { + bytes memory dummy; + dummy = type(RoninTransparentProxy).creationCode; + dummy = type(TransparentProxyOZv4_9_5).creationCode; } } diff --git a/script/OnchainExecutor.s.sol b/script/OnchainExecutor.s.sol index 44997fa..aa096bd 100644 --- a/script/OnchainExecutor.s.sol +++ b/script/OnchainExecutor.s.sol @@ -1,16 +1,22 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; -import { StdStyle } from "../lib/forge-std/src/StdStyle.sol"; -import { console2 as console } from "../lib/forge-std/src/console2.sol"; -import { ScriptExtended } from "./extensions/ScriptExtended.s.sol"; import { BaseGeneralConfig } from "./BaseGeneralConfig.sol"; -import { LibErrorHandler } from "../lib/contract-libs/src/LibErrorHandler.sol"; +import { ScriptExtended } from "./extensions/ScriptExtended.s.sol"; + +import { LibErrorHandler } from "./libraries/LibErrorHandler.sol"; +import { sendRawTransaction } from "./utils/Helpers.sol"; contract OnchainExecutor is ScriptExtended { using LibErrorHandler for bool; - modifier rollFork(uint256 forkBlock) { + modifier rollFork( + uint256 forkBlock + ) { if (forkBlock != 0) { vm.rollFork(forkBlock); console.log("OnchainExecutor: Rolling to fork block number:", forkBlock); @@ -22,50 +28,20 @@ contract OnchainExecutor is ScriptExtended { return abi.encodePacked(type(BaseGeneralConfig).creationCode, abi.encode("", "deployments/")); } - function trace(uint256 forkBlock, address from, address to, uint256 gas, uint256 value, bytes calldata callData) - public - rollFork(forkBlock) - { - vm.prank(from); - _sendRawTransaction(to, gas, value, callData); + function trace( + uint256 forkBlock, + address from, + address to, + uint256 gas, + uint256 value, + bytes calldata callData + ) public rollFork(forkBlock) { + vme.setPostCheckingStatus(true); + sendRawTransaction(from, to, gas, value, callData); + vme.setPostCheckingStatus(false); } function broadcast(address from, address to, uint256 gas, uint256 value, bytes calldata callData) public { - vm.broadcast(from); - _sendRawTransaction(to, gas, value, callData); - } - - function _sendRawTransaction(address to, uint256 gas, uint256 value, bytes calldata callData) internal { - bool success; - bytes memory returnOrRevertData; - - (success, returnOrRevertData) = - gas == 0 ? to.call{ value: value }(callData) : to.call{ value: value, gas: gas }(callData); - - if (!success) { - if (returnOrRevertData.length != 0) { - string[] memory commandInput = new string[](3); - commandInput[0] = "cast"; - commandInput[1] = returnOrRevertData.length > 4 ? "4byte-decode" : "4byte"; - commandInput[2] = vm.toString(returnOrRevertData); - bytes memory decodedError = vm.ffi(commandInput); - console.log(StdStyle.red(string.concat("Decoded Error: ", string(decodedError)))); - } else { - console.log(StdStyle.red("Evm Error!")); - } - } else { - console.log(StdStyle.green("OnchainExecutor: Call Executed Successfully!")); - } - } - - function _logDecodedError(bytes memory returnOrRevertData) internal { - if (returnOrRevertData.length != 0) { - string[] memory commandInput = new string[](3); - commandInput[0] = "cast"; - commandInput[1] = returnOrRevertData.length > 4 ? "4byte-decode" : "4byte"; - commandInput[2] = vm.toString(returnOrRevertData); - bytes memory decodedError = vm.ffi(commandInput); - console.log(StdStyle.red(string.concat("Decoded Error: ", string(decodedError)))); - } + sendRawTransaction(from, to, gas, value, callData); } } diff --git a/script/configs/ContractConfig.sol b/script/configs/ContractConfig.sol index d38b3f4..0460865 100644 --- a/script/configs/ContractConfig.sol +++ b/script/configs/ContractConfig.sol @@ -1,18 +1,23 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { EnumerableSet } from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; -import { Vm, VmSafe } from "../../lib/forge-std/src/Vm.sol"; -import { console2 as console } from "../../lib/forge-std/src/console2.sol"; -import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm, VmSafe } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; +import { EnumerableSet } from "../../dependencies/openzeppelin-5.0.2/contracts/utils/structs/EnumerableSet.sol"; + +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; import { IContractConfig } from "../interfaces/configs/IContractConfig.sol"; import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; -import { TContract } from "../types/Types.sol"; + +import { TContract } from "../types/TContract.sol"; +import { TNetwork } from "../types/TNetwork.sol"; +import { vme } from "../utils/Constants.sol"; abstract contract ContractConfig is IContractConfig { using LibString for *; - using StdStyle for string; + using StdStyle for *; using EnumerableSet for EnumerableSet.AddressSet; Vm private constant vm = Vm(LibSharedAddress.VM); @@ -23,40 +28,40 @@ abstract contract ContractConfig is IContractConfig { mapping(TContract contractType => string contractName) internal _contractNameMap; mapping(TContract contractType => string absolutePath) internal _contractAbsolutePathMap; - mapping(uint256 chainId => EnumerableSet.AddressSet) internal _contractAddrSet; - mapping(uint256 chainId => mapping(string name => address addr)) internal _contractAddrMap; - mapping(uint256 chainId => mapping(address addr => TContract contractType)) internal _contractTypeMap; + mapping(TNetwork networkType => EnumerableSet.AddressSet) internal _contractAddrSet; + mapping(TNetwork networkType => mapping(string name => address addr)) internal _contractAddrMap; + mapping(TNetwork networkType => mapping(address addr => TContract contractType)) internal _contractTypeMap; constructor(string memory absolutePath, string memory deploymentRoot) { _absolutePath = absolutePath; _deploymentRoot = deploymentRoot; } - function getContractTypeByRawData(uint256 chainId, address contractAddr) - public - view - virtual - returns (TContract contractType) - { - contractType = _contractTypeMap[chainId][contractAddr]; + function getContractTypeByRawData( + TNetwork network, + address contractAddr + ) public view virtual returns (TContract contractType) { + contractType = _contractTypeMap[network][contractAddr]; require( - TContract.unwrap(contractType) != bytes32(0x0), - string.concat( - "ContractConfig(getContractTypeByRawData): ContractType not found (", contractType.contractName(), ")" - ) + TContract.unwrap(contractType) != 0x0, + string.concat("ContractConfig(getContractTypeByRawData): ContractType not found (", contractType.name(), ")") ); } - function getContractTypeFromCurrentNetwok(address contractAddr) public view virtual returns (TContract contractType) { - return getContractTypeByRawData(block.chainid, contractAddr); + function getContractTypeFromCurrentNetwork( + address contractAddr + ) public view virtual returns (TContract contractType) { + return getContractTypeByRawData(vme.getCurrentNetwork(), contractAddr); } function setContractAbsolutePathMap(TContract contractType, string memory absolutePath) public virtual { _contractAbsolutePathMap[contractType] = absolutePath; } - function getContractName(TContract contractType) public view virtual returns (string memory name) { - string memory contractTypeName = contractType.contractName(); + function getContractName( + TContract contractType + ) public view virtual returns (string memory name) { + string memory contractTypeName = contractType.name(); name = _contractNameMap[contractType]; name = keccak256(bytes(contractTypeName)) == keccak256(bytes(name)) ? name : contractTypeName; require( @@ -71,7 +76,9 @@ abstract contract ContractConfig is IContractConfig { ); } - function getContractAbsolutePath(TContract contractType) public view virtual returns (string memory name) { + function getContractAbsolutePath( + TContract contractType + ) public view virtual returns (string memory name) { if (bytes(_contractAbsolutePathMap[contractType]).length != 0) { name = _contractAbsolutePathMap[contractType]; } else if (bytes(_absolutePath).length != 0) { @@ -81,48 +88,62 @@ abstract contract ContractConfig is IContractConfig { } } - function getAddressFromCurrentNetwork(TContract contractType) public view virtual returns (address payable) { + function getAddressFromCurrentNetwork( + TContract contractType + ) public view virtual returns (address payable) { string memory contractName = getContractName(contractType); require( bytes(contractName).length != 0, - string.concat( - "ContractConfig(getAddressFromCurrentNetwork): Contract Type not found (", contractType.contractName(), ")" - ) + string.concat("ContractConfig(getAddressFromCurrentNetwork): Contract Type not found (", contractType.name(), ")") ); - return getAddressByRawData(block.chainid, contractName); + return getAddressByRawData(vme.getCurrentNetwork(), contractName); } - function getAddressByString(string calldata contractName) public view virtual returns (address payable) { - return getAddressByRawData(block.chainid, contractName); + function getAddressByString( + string calldata contractName + ) public view virtual returns (address payable) { + return getAddressByRawData(vme.getCurrentNetwork(), contractName); } - function getAddressByRawData(uint256 chainId, string memory contractName) - public - view - virtual - returns (address payable addr) - { - addr = payable(_contractAddrMap[chainId][contractName]); + function getAddressByRawData( + TNetwork network, + string memory contractName + ) public view virtual returns (address payable addr) { + addr = payable(_contractAddrMap[network][contractName]); require( addr != address(0x0), string.concat("ContractConfig(getAddressByRawData): Address not found: ", contractName) ); } - function getAllAddressesByRawData(uint256 chainId) public view virtual returns (address payable[] memory addrs) { - address[] memory v = _contractAddrSet[chainId].values(); + function getAllAddressesByRawData( + TNetwork network + ) public view virtual returns (address payable[] memory addrs) { + address[] memory v = _contractAddrSet[network].values(); assembly ("memory-safe") { addrs := v } } - function label(uint256 chainId, address contractAddr, string memory contractName) public virtual { + function label(TNetwork network, address contractAddr, string memory contractName) public virtual { vm.label( contractAddr, - string.concat("(", vm.toString(chainId).blue(), ")", contractName.yellow(), "[", vm.toString(contractAddr), "]") + string.concat( + "(", + bytes32(TNetwork.unwrap(network)).unpackOne().blue(), + ")", + contractName.yellow(), + "[", + vm.toString(contractAddr), + "]" + ) ); } - function _storeDeploymentData(string memory deploymentRoot) internal virtual { + function _storeDeploymentData( + string memory deploymentRoot + ) internal virtual { + uint256 start = vm.unixTime(); + VmSafe.DirEntry[] memory deployments; try vm.exists(deploymentRoot) returns (bool exists) { if (!exists) { @@ -145,39 +166,42 @@ abstract contract ContractConfig is IContractConfig { return; } - for (uint256 i; i < deployments.length;) { - VmSafe.DirEntry[] memory entries = vm.readDir(deployments[i].path); - uint256 chainId = vm.parseUint(vm.readFile(string.concat(deployments[i].path, "/.chainId"))); - - for (uint256 j; j < entries.length;) { - string memory path = entries[j].path; - - if (path.endsWith(".json")) { - string[] memory splitteds = path.split("/"); - string memory contractName = splitteds[splitteds.length - 1]; - string memory suffix = path.endsWith("Proxy.json") ? "Proxy.json" : ".json"; - // remove suffix - contractName = contractName.replace(suffix, ""); - string memory json = vm.readFile(path); - address contractAddr = vm.parseJsonAddress(json, ".address"); - label(chainId, contractAddr, contractName); - - // filter out logic deployments - if (!path.endsWith("Logic.json")) { - _contractAddrSet[chainId].add(contractAddr); - _contractAddrMap[chainId][contractName] = contractAddr; - _contractTypeMap[chainId][contractAddr] = TContract.wrap(contractName.packOne()); - } - } - - unchecked { - ++j; - } + for (uint256 i; i < deployments.length; ++i) { + string[] memory s = vm.split(deployments[i].path, "/"); + TNetwork network = TNetwork.wrap(LibString.packOne(s[s.length - 1])); + + string memory exportedAddress; + try vm.readFile(string.concat(deployments[i].path, "/exported_address")) returns (string memory data) { + exportedAddress = data; + if (bytes(exportedAddress).length == 0) continue; + } catch { + console.log("ContractConfig:", "No exported_address file found for folder", deployments[i].path, "skip loading"); + continue; } - unchecked { - ++i; + string[] memory entries = exportedAddress.split("\n"); + + for (uint256 j; j < entries.length; ++j) { + string[] memory data = entries[j].split("@"); + if (data.length != 2) continue; + + string memory contractName = data[0]; + address contractAddr = vm.parseAddress(data[1]); + + string memory suffix = contractName.endsWith("Proxy.json") ? "Proxy.json" : ".json"; + + // remove suffix + contractName = vm.replace(contractName, suffix, ""); + + label(network, contractAddr, contractName); + + _contractAddrSet[network].add(contractAddr); + _contractAddrMap[network][contractName] = contractAddr; + _contractTypeMap[network][contractAddr] = TContract.wrap(contractName.packOne()); } } + + uint256 end = vm.unixTime(); + console.log("ContractConfig:".blue(), "Deployment data loaded in", vm.toString(end - start), "milliseconds"); } } diff --git a/script/configs/MigrationConfig.sol b/script/configs/MigrationConfig.sol index 2bea430..0fe4917 100644 --- a/script/configs/MigrationConfig.sol +++ b/script/configs/MigrationConfig.sol @@ -1,20 +1,18 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; import { IMigrationConfig } from "../interfaces/configs/IMigrationConfig.sol"; abstract contract MigrationConfig is IMigrationConfig { bytes internal _migrationConfig; - function setRawSharedArguments(bytes memory config) public virtual { - if (areSharedArgumentsStored()) return; + function setRawSharedArguments( + bytes memory config + ) public virtual { _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 cf444f9..9e3f26f 100644 --- a/script/configs/NetworkConfig.sol +++ b/script/configs/NetworkConfig.sol @@ -1,143 +1,272 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { Vm } from "../../lib/forge-std/src/Vm.sol"; -import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; -import { console2 as console } from "../../lib/forge-std/src/console2.sol"; +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; + +import { IGeneralConfig } from "../interfaces/IGeneralConfig.sol"; import { INetworkConfig } from "../interfaces/configs/INetworkConfig.sol"; import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; import { TNetwork } from "../types/Types.sol"; +import { DefaultNetwork } from "../utils/DefaultNetwork.sol"; abstract contract NetworkConfig is INetworkConfig { + using StdStyle for *; + Vm private constant vm = Vm(LibSharedAddress.VM); + IGeneralConfig private constant vme = IGeneralConfig(LibSharedAddress.VME); + uint256 private constant NULL_FORK_ID = uint256(keccak256("NULL_FORK_ID")); - string internal _deploymentRoot; - bool internal _isForkModeEnabled; + string private _deploymentRoot; + bool private _isForkModeEnabled; + TNetwork private _currentNetwork; + mapping(uint256 forkId => TNetwork) internal _forkId2Network; mapping(TNetwork network => NetworkData) internal _networkDataMap; - mapping(uint256 chainId => TNetwork network) internal _networkMap; + mapping(TNetwork network => mapping(uint256 forkBlockNumber => uint256 forkId)) internal _forkMap; - constructor(string memory deploymentRoot) { + constructor( + string memory deploymentRoot + ) { _deploymentRoot = deploymentRoot; - _logCurrentForkInfo(); } - function getDeploymentRoot() public virtual returns (string memory) { - return _deploymentRoot; + function roll( + uint256 numBlock + ) public virtual { + uint256 blockTime = _networkDataMap[getCurrentNetwork()].blockTime; + vm.roll(numBlock); + vm.warp(blockTime * numBlock); + } + + function warp( + uint256 numSecond + ) public virtual { + uint256 blockTime = _networkDataMap[getCurrentNetwork()].blockTime; + vm.warp(numSecond); + vm.roll(numSecond / blockTime); + } + + function rollUpTo( + uint256 untilBlockNumber + ) public virtual { + uint256 blockTime = _networkDataMap[getCurrentNetwork()].blockTime; + uint256 currBlockNumber = vm.getBlockNumber(); + uint256 newBlockTime; + + if (untilBlockNumber <= currBlockNumber) { + newBlockTime = vm.getBlockTimestamp() - blockTime * (currBlockNumber - untilBlockNumber); + } else { + newBlockTime = vm.getBlockTimestamp() + blockTime * (untilBlockNumber - currBlockNumber); + } + + vm.roll(untilBlockNumber); + vm.warp(newBlockTime); } - function setForkMode(bool shouldEnable) public virtual { + function warpUpTo( + uint256 untilTimestamp + ) public virtual { + uint256 blockTime = _networkDataMap[getCurrentNetwork()].blockTime; + uint256 currTimestamp = vm.getBlockTimestamp(); + uint256 newBlock; + + if (untilTimestamp <= currTimestamp) newBlock = vm.getBlockNumber() - (currTimestamp - untilTimestamp) / blockTime; + else newBlock = vm.getBlockNumber() + (untilTimestamp - currTimestamp) / blockTime; + + vm.roll(newBlock); + vm.warp(untilTimestamp); + } + + function setForkMode( + bool shouldEnable + ) public virtual { _isForkModeEnabled = shouldEnable; + emit ForkModeUpdated(shouldEnable); } - function getNetworkData(TNetwork network) public view virtual returns (NetworkData memory) { + 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; + 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"); dirPath = string.concat(_deploymentRoot, dirName); } function setNetworkInfo( - uint256 chainId, - TNetwork network, - string memory chainAlias, - string memory deploymentDir, - string memory privateKeyEnvLabel, - string memory explorer + NetworkData memory networkData ) public virtual { - _networkMap[chainId] = network; - _networkDataMap[network] = - NetworkData(tryCreateFork(chainAlias, chainId), chainId, chainAlias, deploymentDir, privateKeyEnvLabel, explorer); + _forkMap[networkData.network][0] = tryCreateFork(networkData.chainAlias, networkData.network, 0); + _networkDataMap[networkData.network] = networkData; } - function getExplorer(TNetwork network) public view virtual returns (string memory link) { + function getExplorer( + TNetwork network + ) public view virtual returns (string memory link) { link = _networkDataMap[network].explorer; } - function getAlias(TNetwork network) public view virtual returns (string memory networkAlias) { + function getAlias( + TNetwork network + ) public view virtual returns (string memory networkAlias) { networkAlias = _networkDataMap[network].chainAlias; require(bytes(networkAlias).length != 0, "NetworkConfig: Network alias not found"); } - function getForkId(TNetwork network) public view virtual returns (uint256 forkId) { - forkId = _networkDataMap[network].forkId; - require(forkId != NULL_FORK_ID, "NetworkConfig: Network fork is not created"); + function getForkId( + TNetwork network + ) public view virtual returns (uint256 forkId) { + forkId = getForkId({ network: network, forkBlockNumber: 0 }); } - function createFork(TNetwork network) public returns (uint256 forkId) { - NetworkData memory networkData = _networkDataMap[network]; + function getForkId(TNetwork network, uint256 forkBlockNumber) public view virtual returns (uint256 forkId) { + forkId = _forkMap[network][forkBlockNumber]; + } + + function createFork( + TNetwork network + ) public returns (uint256 forkId) { + return createFork({ network: network, forkBlockNumber: 0 }); + } + + function createFork(TNetwork network, uint256 forkBlockNumber) public returns (uint256 forkId) { setForkMode({ shouldEnable: true }); - forkId = tryCreateFork(networkData.chainAlias, networkData.chainId); - _networkDataMap[network].forkId = forkId; + + NetworkData memory networkData = _networkDataMap[network]; + forkId = + _forkMap[network][forkBlockNumber] = tryCreateFork(networkData.chainAlias, networkData.network, forkBlockNumber); + _forkId2Network[forkId] = network; } - function tryCreateFork(string memory chainAlias, uint256 chainId) public virtual returns (uint256) { - uint256 currentFork; + function tryCreateFork( + string memory chainAlias, + TNetwork network, + uint256 forkBlockNumber + ) public virtual returns (uint256) { + uint256 currentFork = NULL_FORK_ID; + try vm.activeFork() returns (uint256 forkId) { currentFork = forkId; - } catch { - console.log(StdStyle.yellow("NetworkConfig: fork mode disabled, no active fork")); - currentFork = NULL_FORK_ID; - } - if (chainId == block.chainid) return currentFork; + // return current fork if current network is the same as the given `network` + if (getCurrentNetwork() == network) return currentFork; + } catch { } + + // return NULL_FORK_ID if fork mode is not enabled if (!_isForkModeEnabled) return NULL_FORK_ID; - uint256 id = _networkDataMap[_networkMap[chainId]].forkId; - if (id != NULL_FORK_ID) return id; + + uint256 id = _forkMap[network][forkBlockNumber]; + + if (id != NULL_FORK_ID) { + // return if fork id is not NULL_FORK_ID and fork id != 0 + if (id != 0) return id; + + // if id is not NULL_FORK_ID, and fork id == 0 then try select the fork to see if it exists + try vm.selectFork(id) { + vm.selectFork(currentFork); + return id; + } catch { } + } string memory rpcUrl = vm.rpcUrl(chainAlias); - try vm.createFork(rpcUrl) returns (uint256 forkId) { - console.log(StdStyle.blue(string.concat("NetworkConfig: ", chainAlias, " fork created with forkId:")), forkId); - return forkId; - } catch { - console.log(StdStyle.red("NetworkConfig: Cannot create fork with url:"), rpcUrl); - return NULL_FORK_ID; + + if (forkBlockNumber == 0) { + try vm.createFork(rpcUrl) returns (uint256 forkId) { + console.log(string.concat("NetworkConfig: ".blue(), chainAlias, " fork created with forkId:"), forkId); + return forkId; + } catch { + console.log(StdStyle.red("NetworkConfig: Cannot create fork"), chainAlias, "with url:", rpcUrl); + return NULL_FORK_ID; + } + } else { + try vm.createFork(rpcUrl, forkBlockNumber) returns (uint256 forkId) { + console.log( + string.concat("NetworkConfig: ".blue(), chainAlias, " fork created with forkId:").blue(), + forkId, + "Fork Block Number:", + forkBlockNumber + ); + + return forkId; + } catch { + console.log(StdStyle.red("NetworkConfig: Cannot create fork"), chainAlias, "with url:", rpcUrl); + return NULL_FORK_ID; + } } } - function switchTo(TNetwork network) public virtual { - console.log(StdStyle.blue("\n>>"), "Switching to:", StdStyle.yellow(_networkDataMap[network].chainAlias), "\n"); - uint256 forkId = _networkDataMap[network].forkId; + function switchTo( + TNetwork network + ) public virtual { + switchTo({ network: network, forkBlockNumber: 0 }); + } + + function switchTo(TNetwork network, uint256 forkBlockNumber) public virtual { + uint256 forkId = _forkMap[network][forkBlockNumber]; require(forkId != NULL_FORK_ID, "Network Config: Unexists fork!"); + vm.selectFork(forkId); - require(_networkDataMap[network].chainId == block.chainid, "NetworkConfig: Switch chain failed"); - _logCurrentForkInfo(); + _currentNetwork = network; + require(network == _forkId2Network[forkId], "Network Config: Invalid fork network"); + + _logCurrentForkInfo(_networkDataMap[network].chainAlias); } - function getPrivateKeyEnvLabel(TNetwork network) public view virtual returns (string memory privateKeyEnvLabel) { - privateKeyEnvLabel = _networkDataMap[network].privateKeyEnvLabel; - require(bytes(privateKeyEnvLabel).length != 0, "Network Config: ENV label not found"); + function switchTo( + uint256 forkId + ) public virtual { + vm.selectFork(forkId); + _currentNetwork = _forkId2Network[forkId]; + this.logCurrentForkInfo(); } function getCurrentNetwork() public view virtual returns (TNetwork network) { - network = _networkMap[block.chainid]; - } - - function getNetworkByChainId(uint256 chainId) public view virtual returns (TNetwork network) { - network = _networkMap[chainId]; - } - - function _logCurrentForkInfo() internal view { - console.log( - StdStyle.yellow( - string.concat( - "Block Number: ", - vm.toString(block.number), - " | ", - "Timestamp: ", - vm.toString(block.timestamp), - " | ", - "Gas Price: ", - vm.toString(tx.gasprice), - " | ", - "Block Gas Limit: ", - vm.toString(block.gaslimit), - "\n" - ) - ) + network = _currentNetwork; + if (network == TNetwork.wrap(0x0)) network = DefaultNetwork.LocalHost.key(); + } + + function logCurrentForkInfo() public view virtual { + _logCurrentForkInfo(_networkDataMap[getCurrentNetwork()].chainAlias); + } + + function _logCurrentForkInfo( + string memory chainAlias + ) internal view { + string memory logA = string.concat( + "Network: ".blue(), + chainAlias.yellow(), + " - Block Number ".blue(), + vm.toString(vm.getBlockNumber()), + " - Timestamp ".blue(), + vm.toString(vm.getBlockTimestamp()), + " - Period ".blue(), + vm.toString(vm.getBlockTimestamp() / 1 days) + ); + string memory logB = string.concat( + " - Chain ID ".blue(), + vm.toString(block.chainid), + " - Gas Price ".blue(), + vm.toString(tx.gasprice / 1 gwei), + " GWEI", + " - Explorer ".blue(), + _networkDataMap[getCurrentNetwork()].explorer ); + string memory log = string.concat(logA, logB); + console.log(log); } } diff --git a/script/configs/RuntimeConfig.sol b/script/configs/RuntimeConfig.sol index 2025a1a..3966bc0 100644 --- a/script/configs/RuntimeConfig.sol +++ b/script/configs/RuntimeConfig.sol @@ -1,18 +1,27 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; -import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; -import { console2 as console } from "../../lib/forge-std/src/console2.sol"; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; import { IRuntimeConfig } from "../interfaces/configs/IRuntimeConfig.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; +import { TNetwork } from "../types/Types.sol"; +import { DefaultNetwork } from "../utils/DefaultNetwork.sol"; abstract contract RuntimeConfig is IRuntimeConfig { using LibString for string; + Vm private constant vm = Vm(LibSharedAddress.VM); + bool internal _resolved; Option internal _option; string internal _rawCommand; bool internal _isPostChecking; + bool internal _isPreChecking; function getCommand() public view virtual returns (string memory) { return _rawCommand; @@ -22,24 +31,56 @@ abstract contract RuntimeConfig is IRuntimeConfig { return _isPostChecking; } - function setPostCheckingStatus(bool status) public virtual { + function setPostCheckingStatus( + bool status + ) public virtual { _isPostChecking = status; } - function resolveCommand(string calldata command) external virtual { + function isPreChecking() public view virtual returns (bool) { + return _isPreChecking; + } + + function setPreCheckingStatus( + bool status + ) public virtual { + _isPreChecking = status; + } + + function resolveCommand( + string calldata command + ) external virtual { if (_resolved) return; + + _option.network = DefaultNetwork.LocalHost.key(); + if (bytes(command).length != 0) { string[] memory args = command.split("@"); uint256 length = args.length; - for (uint256 i; i < length;) { - 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 { - ++i; + for (uint256 i; i < length; ++i) { + 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 if (args[i].eq("no-precheck")) { + _option.disablePrecheck = true; + } else if (args[i].startsWith("network")) { + string memory network = vm.split(args[i], ".")[1]; + _option.network = TNetwork.wrap(LibString.packOne(network)); + } else if (args[i].startsWith("fork-block-number")) { + string memory blockNumber = vm.split(args[i], ".")[1]; + _option.forkBlockNumber = vm.parseUint(blockNumber); + } else if (args[i].startsWith("sender")) { + string memory sender = vm.split(args[i], ".")[1]; + _option.sender = vm.parseAddress(sender); + } else if (args[i].startsWith("script-root")) { + string memory scriptRoot = vm.split(args[i], ".")[1]; + _option.scriptRoot = scriptRoot; + } else { + console.log("Invalid command: %s", args[i]); } } } @@ -47,12 +88,12 @@ abstract contract RuntimeConfig is IRuntimeConfig { _rawCommand = command; _resolved = true; - _handleRuntimeConfig(); + buildRuntimeConfig(); } function getRuntimeConfig() public view returns (Option memory option) { option = _option; } - function _handleRuntimeConfig() internal virtual; + function buildRuntimeConfig() public virtual; } diff --git a/script/configs/UserDefinedConfig.sol b/script/configs/UserDefinedConfig.sol new file mode 100644 index 0000000..dd9e198 --- /dev/null +++ b/script/configs/UserDefinedConfig.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { IUserDefinedConfig } from "../interfaces/configs/IUserDefinedConfig.sol"; + +abstract contract UserDefinedConfig is IUserDefinedConfig { + bytes32 private constant $$_UserDefinedDataStorageLocation = keccak256("@fdk.UserDefinedConfig.UserDefinedData"); + + string[] private _userDefinedKeys; + mapping(bytes32 hashKey => bool registered) private _registry; + + function setUserDefinedConfig(string calldata key, bytes calldata value) external { + UserDefinedData storage $ = _getUserDefinedData(key); + $._value = value; + + bytes32 hashKey = keccak256(bytes(key)); + + if (!_registry[hashKey]) { + _userDefinedKeys.push(key); + _registry[hashKey] = true; + } + } + + function getUserDefinedConfig( + string calldata key + ) external view returns (bytes memory value) { + UserDefinedData storage $ = _getUserDefinedData(key); + return $._value; + } + + function getAllKeys() external view returns (string[] memory) { + return _userDefinedKeys; + } + + function _getUserDefinedData( + string calldata key + ) private pure returns (UserDefinedData storage $) { + bytes32 location = keccak256(abi.encode($$_UserDefinedDataStorageLocation, keccak256(bytes(key)))); + + assembly ("memory-safe") { + $.slot := location + } + } +} diff --git a/script/configs/WalletConfig.sol b/script/configs/WalletConfig.sol index e3a420e..7c06393 100644 --- a/script/configs/WalletConfig.sol +++ b/script/configs/WalletConfig.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { CommonBase } from "../../lib/forge-std/src/Base.sol"; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { CommonBase } from "../../dependencies/forge-std-1.9.3/src/Base.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; import { IWalletConfig } from "../interfaces/configs/IWalletConfig.sol"; abstract contract WalletConfig is CommonBase, IWalletConfig { @@ -23,24 +24,28 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { return "DEPLOYER"; } - function ethSignMessage(address by, string memory message, WalletOption walletOption) - public - returns (bytes memory sig) - { + function ethSignMessage( + address by, + string memory message, + WalletOption walletOption + ) public returns (bytes memory sig) { sig = walletOption == WalletOption.Env ? envEthSignMessage(by, message, _envLabel) : trezorEthSignMessage(by, message); } - function ethSignMessage(string memory message) public returns (bytes memory sig) { + function ethSignMessage( + string memory message + ) public returns (bytes memory sig) { sig = _walletOption == WalletOption.Env ? envEthSignMessage(_envSender, message, _envLabel) : trezorEthSignMessage(_trezorSender, message); } - function envEthSignMessage(address by, string memory message, string memory envLabel) - public - returns (bytes memory sig) - { + function envEthSignMessage( + address by, + string memory message, + string memory envLabel + ) public returns (bytes memory sig) { sig = ethSignMessage(by, message, _loadENVPrivateKey(envLabel)); } @@ -71,25 +76,29 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { sig = vm.ffi(commandInput); } - function signTypedDataV4(address by, string memory filePath, WalletOption walletOption) - public - returns (bytes memory sig) - { + function signTypedDataV4( + address by, + string memory filePath, + WalletOption walletOption + ) public returns (bytes memory sig) { sig = walletOption == WalletOption.Env ? envSignTypedDataV4(by, filePath, _envLabel) : trezorSignTypedDataV4(by, filePath); } - function signTypedDataV4(string memory filePath) public returns (bytes memory sig) { + function signTypedDataV4( + string memory filePath + ) public returns (bytes memory sig) { sig = _walletOption == WalletOption.Env ? envSignTypedDataV4(_envSender, filePath, _envLabel) : trezorSignTypedDataV4(_trezorSender, filePath); } - function envSignTypedDataV4(address by, string memory filePath, string memory envLabel) - public - returns (bytes memory sig) - { + function envSignTypedDataV4( + address by, + string memory filePath, + string memory envLabel + ) public returns (bytes memory sig) { sig = signTypedDataV4(by, filePath, _loadENVPrivateKey(envLabel)); } @@ -124,6 +133,16 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { sig = vm.ffi(commandInput); } + function loadTrezorAccount() external { + _loadTrezorAccount(); + } + + function loadENVAccount( + string calldata envLabel + ) external { + _loadENVAccount(envLabel); + } + function _loadTrezorAccount() internal { if (tx.origin != DEFAULT_SENDER) { _trezorSender = tx.origin; @@ -145,13 +164,17 @@ abstract contract WalletConfig is CommonBase, IWalletConfig { _walletOption = WalletOption.Trezor; } - function _loadENVAccount(string memory envLabel) internal { + function _loadENVAccount( + string memory envLabel + ) internal { _envLabel = envLabel; _walletOption = WalletOption.Env; _envSender = vm.rememberKey(_loadENVPrivateKey(envLabel)); } - function _loadENVPrivateKey(string memory envLabel) private view returns (uint256) { + function _loadENVPrivateKey( + string memory envLabel + ) private view returns (uint256) { try vm.envUint(envLabel) returns (uint256 pk) { return pk; } catch { diff --git a/script/extensions/BaseScriptExtended.s.sol b/script/extensions/BaseScriptExtended.s.sol new file mode 100644 index 0000000..2aac589 --- /dev/null +++ b/script/extensions/BaseScriptExtended.s.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { IVme } from "../interfaces/IVme.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; + +import { TContract } from "../types/TContract.sol"; +import { TNetwork } from "../types/TNetwork.sol"; + +abstract contract BaseScriptExtended { + bytes public constant EMPTY_ARGS = ""; + + IVme public constant vme = IVme(LibSharedAddress.VME); + // Backward compatibility + IVme public constant CONFIG = vme; + + function network() public view virtual returns (TNetwork) { + return vme.getCurrentNetwork(); + } + + function forkId() public view virtual returns (uint256) { + return vme.getForkId(network()); + } + + function forkId( + uint256 forkBlockNumber + ) public view virtual returns (uint256) { + return vme.getForkId(network(), forkBlockNumber); + } + + function sender() public view virtual returns (address payable) { + return vme.getSender(); + } + + function loadContract( + TContract contractType + ) public view virtual returns (address payable contractAddr) { + return vme.getAddressFromCurrentNetwork(contractType); + } +} diff --git a/script/extensions/ScriptExtended.s.sol b/script/extensions/ScriptExtended.s.sol index a9faa95..dae6ded 100644 --- a/script/extensions/ScriptExtended.s.sol +++ b/script/extensions/ScriptExtended.s.sol @@ -1,128 +1,172 @@ -// SPDX-License-Identifier: MIT -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"; -import { LibErrorHandler } from "../../lib/contract-libs/src/LibErrorHandler.sol"; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { Script, console } from "../../dependencies/forge-std-1.9.3/src/Script.sol"; +import { StdAssertions } from "../../dependencies/forge-std-1.9.3/src/StdAssertions.sol"; +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { VmSafe } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; + +import { IScriptExtended } from "../interfaces/IScriptExtended.sol"; +import { IVme } from "../interfaces/IVme.sol"; +import { IRuntimeConfig } from "../interfaces/configs/IRuntimeConfig.sol"; +import { LibErrorHandler } from "../libraries/LibErrorHandler.sol"; import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; -import { TContract } from "../types/Types.sol"; +import { TContract } from "../types/TContract.sol"; +import { TNetwork } from "../types/TNetwork.sol"; +import { deploySharedAddress, logInnerCall } from "../utils/Helpers.sol"; +import { BaseScriptExtended } from "./BaseScriptExtended.s.sol"; -abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { +abstract contract ScriptExtended is BaseScriptExtended, Script, StdAssertions, IScriptExtended { using StdStyle for *; using LibErrorHandler for bool; - bytes public constant EMPTY_ARGS = ""; - IGeneralConfig public constant CONFIG = IGeneralConfig(LibSharedAddress.CONFIG); + uint256 internal _originForkBlockNumber; - modifier logFn(string memory fnName) { - _logFn(fnName); + modifier logFn( + string memory fnName + ) { + logInnerCall(fnName); _; } - modifier onlyOn(TNetwork networkType) { + modifier onlyOn( + TNetwork networkType + ) { _requireOn(networkType); _; } - modifier onNetwork(TNetwork networkType) { - TNetwork currentNetwork = _switchTo(networkType); + modifier onNetwork( + TNetwork networkType + ) { + (TNetwork prevNetwork, uint256 prevForkId) = switchTo(networkType); _; - _switchBack(currentNetwork); + switchBack(prevNetwork, prevForkId); } constructor() { - setUp(); + try vm.isContext(VmSafe.ForgeContext.Test) { + setUp(); + } catch { + // Do nothing + } } function setUp() public virtual { - deploySharedAddress(address(CONFIG), _configByteCode(), "GeneralConfig"); + deploySharedAddress(address(vme), _configByteCode(), "VME"); } - function _configByteCode() internal virtual returns (bytes memory); + function run(bytes calldata callData, string calldata command) public virtual { + vm.pauseTracing(); + vme.resolveCommand(command); + + IRuntimeConfig.Option memory runtimeConfig = vme.getRuntimeConfig(); + _originForkBlockNumber = runtimeConfig.forkBlockNumber; + + if (runtimeConfig.network != network()) { + switchTo(runtimeConfig.network, runtimeConfig.forkBlockNumber); + } else { + uint256 currUnixTimestamp = vm.unixTime() / 1000; + if (vm.getBlockTimestamp() < currUnixTimestamp) vm.warp(currUnixTimestamp); + if (runtimeConfig.forkBlockNumber != 0) vme.rollUpTo(runtimeConfig.forkBlockNumber); + + vme.logSenderInfo(); + vme.setUpDefaultContracts(); + vme.logCurrentForkInfo(); + } - function _postCheck() internal virtual { } + uint256 start; + uint256 end; + + vm.resumeTracing(); + + if (runtimeConfig.disablePrecheck) { + console.log("\nPrechecking is disabled.".yellow()); + } else { + console.log("\n>> Prechecking...".yellow()); + vme.setPreCheckingStatus({ status: true }); + start = vm.unixTime(); + _preCheck(); + end = vm.unixTime(); + vme.setPreCheckingStatus({ status: false }); + console.log("ScriptExtended:".blue(), "Prechecking completed in", vm.toString(end - start), "milliseconds.\n"); + } + + _beforeRunningScript(); - function run(bytes calldata callData, string calldata command) public virtual { - CONFIG.resolveCommand(command); (bool success, bytes memory data) = address(this).delegatecall(callData); success.handleRevert(msg.sig, data); - if (CONFIG.getRuntimeConfig().disablePostcheck) { + _afterRunningScript(); + + if (vme.getRuntimeConfig().disablePostcheck) { console.log("\nPostchecking is disabled.".yellow()); - return; + } else { + console.log("\n>> Postchecking...".yellow()); + vme.setPostCheckingStatus({ status: true }); + start = vm.unixTime(); + _postCheck(); + end = vm.unixTime(); + vme.setPostCheckingStatus({ status: false }); + console.log("ScriptExtended:".blue(), "Postchecking completed in", vm.toString(end - start), "milliseconds."); } - - console.log("\n>> Postchecking...".yellow()); - CONFIG.setPostCheckingStatus({ status: true }); - _postCheck(); - CONFIG.setPostCheckingStatus({ status: false }); - } - - function network() public view virtual returns (TNetwork) { - return CONFIG.getCurrentNetwork(); } - function forkId() public view virtual returns (uint256) { - return CONFIG.getForkId(network()); - } - - function sender() public view virtual returns (address payable) { - return CONFIG.getSender(); - } + function _beforeRunningScript() internal virtual { } - function fail() internal override { - super.fail(); - revert("ScriptExtended: Got failed assertion"); - } + function _afterRunningScript() internal virtual { } - 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 _requireOn( + TNetwork networkType + ) private view { + require(network() == networkType, string.concat("ScriptExtended: Only allowed on ", vme.getAlias(networkType))); } function deploySharedMigration(TContract contractType, bytes memory bytecode) public returns (address where) { where = address(ripemd160(abi.encode(contractType))); - deploySharedAddress(where, bytecode, string.concat(contractType.contractName(), "Deploy")); + deploySharedAddress(where, bytecode, string.concat(contractType.name(), "Deploy")); } - function deployCodeTo(bytes memory creationCode, address where) internal { - deployCodeTo(EMPTY_ARGS, creationCode, 0, where); + function switchTo( + TNetwork networkType + ) public virtual returns (TNetwork currNetwork, uint256 currForkId) { + (currNetwork, currForkId) = switchTo({ networkType: networkType, forkBlockNumber: 0 }); } - function deployCodeTo(bytes memory creationCode, uint256 value, address where) internal { - deployCodeTo(EMPTY_ARGS, creationCode, value, where); - } + function switchTo( + TNetwork networkType, + uint256 forkBlockNumber + ) public virtual returns (TNetwork prevNetwork, uint256 prevForkId) { + prevForkId = forkId(_originForkBlockNumber); + prevNetwork = network(); - function deployCodeTo(bytes memory args, bytes memory creationCode, uint256 value, address where) internal { - vm.etch(where, abi.encodePacked(creationCode, args)); - (bool success, bytes memory runtimeBytecode) = where.call{ value: value }(""); - assertTrue(success, "ScriptExtended: Failed to create runtime bytecode."); - vm.etch(where, runtimeBytecode); + vme.createFork(networkType, forkBlockNumber); + vme.switchTo(networkType, forkBlockNumber); } - function _logFn(string memory fnName) private view { - console.log("> ", StdStyle.blue(fnName), "..."); + function switchBack(TNetwork prevNetwork, uint256 prevForkId) public virtual { + try vme.switchTo(prevForkId) { } + catch { + vme.switchTo(prevNetwork); + } } - function _requireOn(TNetwork networkType) private view { - require(network() == networkType, string.concat("ScriptExtended: Only allowed on ", CONFIG.getAlias(networkType))); + function fail() internal override { + super.fail(); + revert("ScriptExtended: Got failed assertion"); } - function _switchTo(TNetwork networkType) private returns (TNetwork currentNetwork) { - currentNetwork = network(); - CONFIG.createFork(networkType); - CONFIG.switchTo(networkType); + function prankOrBroadcast( + address by + ) internal virtual { + if (vme.isPostChecking() || vme.isPreChecking()) vm.prank(by); + else vm.broadcast(by); } - function _switchBack(TNetwork currentNetwork) private { - CONFIG.switchTo(currentNetwork); - } + function _configByteCode() internal virtual returns (bytes memory); + + function _postCheck() internal virtual { } + + function _preCheck() internal virtual { } } diff --git a/script/interfaces/IArtifactFactory.sol b/script/interfaces/IArtifactFactory.sol deleted file mode 100644 index b621725..0000000 --- a/script/interfaces/IArtifactFactory.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -interface IArtifactFactory { - function generateArtifact( - address deployer, - address contractAddr, - string calldata contractAbsolutePath, - string calldata fileName, - bytes calldata args, - uint256 nonce - ) external; -} diff --git a/script/interfaces/IEIP173.sol b/script/interfaces/IEIP173.sol new file mode 100644 index 0000000..1b9ce06 --- /dev/null +++ b/script/interfaces/IEIP173.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +interface IEIP173 { + function owner() external view returns (address); + function transferOwnership( + address newOwner + ) external; +} diff --git a/script/interfaces/IGeneralConfig.sol b/script/interfaces/IGeneralConfig.sol index dd5cb74..f798d09 100644 --- a/script/interfaces/IGeneralConfig.sol +++ b/script/interfaces/IGeneralConfig.sol @@ -1,16 +1,21 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { IWalletConfig } from "./configs/IWalletConfig.sol"; -import { IRuntimeConfig } from "./configs/IRuntimeConfig.sol"; +import { IContractConfig } from "./configs/IContractConfig.sol"; import { IMigrationConfig } from "./configs/IMigrationConfig.sol"; -import { TNetwork, INetworkConfig } from "./configs/INetworkConfig.sol"; -import { TContract, IContractConfig } from "./configs/IContractConfig.sol"; - -interface IGeneralConfig is IWalletConfig, IRuntimeConfig, INetworkConfig, IContractConfig, IMigrationConfig { - function setAddress(TNetwork network, TContract contractType, address contractAddr) external; - - function getAddress(TNetwork network, TContract contractType) external view returns (address payable); +import { INetworkConfig } from "./configs/INetworkConfig.sol"; +import { IRuntimeConfig } from "./configs/IRuntimeConfig.sol"; +import { IUserDefinedConfig } from "./configs/IUserDefinedConfig.sol"; +import { IWalletConfig } from "./configs/IWalletConfig.sol"; - function getAllAddresses(TNetwork network) external view returns (address payable[] memory); +interface IGeneralConfig is + IWalletConfig, + IRuntimeConfig, + INetworkConfig, + IContractConfig, + IMigrationConfig, + IUserDefinedConfig +{ + function logSenderInfo() external view; } diff --git a/script/interfaces/IMigrationScript.sol b/script/interfaces/IMigrationScript.sol index 6a820f6..522faf2 100644 --- a/script/interfaces/IMigrationScript.sol +++ b/script/interfaces/IMigrationScript.sol @@ -1,8 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface IMigrationScript { function run() external returns (address payable); - function overrideArgs(bytes calldata args) external returns (IMigrationScript); + function overrideArgs( + bytes calldata args + ) external returns (IMigrationScript); } diff --git a/script/interfaces/IScriptExtended.sol b/script/interfaces/IScriptExtended.sol index a45fa55..cc50f17 100644 --- a/script/interfaces/IScriptExtended.sol +++ b/script/interfaces/IScriptExtended.sol @@ -1,14 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; import { TNetwork } from "../types/Types.sol"; interface IScriptExtended { function run(bytes calldata callData, string calldata command) external; - - function network() external view returns (TNetwork); - - function forkId() external view returns (uint256); - - function sender() external view returns (address payable); } diff --git a/script/interfaces/IVme.sol b/script/interfaces/IVme.sol new file mode 100644 index 0000000..d712d75 --- /dev/null +++ b/script/interfaces/IVme.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { IGeneralConfig } from "./IGeneralConfig.sol"; + +interface IVme is IGeneralConfig { } diff --git a/script/interfaces/configs/IContractConfig.sol b/script/interfaces/configs/IContractConfig.sol index 4d91aa7..6806752 100644 --- a/script/interfaces/configs/IContractConfig.sol +++ b/script/interfaces/configs/IContractConfig.sol @@ -1,30 +1,54 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { TContract } from "../../types/Types.sol"; +import { TContract } from "../../types/TContract.sol"; +import { TNetwork } from "../../types/TNetwork.sol"; interface IContractConfig { - function getContractTypeByRawData(uint256 chainId, address contractAddr) - external - view - returns (TContract contractType); + function setUpDefaultContracts() external; - function label(uint256 chainId, address contractAddr, string memory contractName) external; + function setAddress(TNetwork network, TContract contractType, address contractAddr) external; - function getContractTypeFromCurrentNetwok(address contractAddr) external view returns (TContract contractType); + function getAddress(TNetwork network, TContract contractType) external view returns (address payable); - function getContractName(TContract contractType) external view returns (string memory name); + function getAllAddresses( + TNetwork network + ) external view returns (address payable[] memory); - function getContractAbsolutePath(TContract contractType) external view returns (string memory name); + function getContractTypeByRawData( + TNetwork network, + address contractAddr + ) external view returns (TContract contractType); - function getAddressFromCurrentNetwork(TContract contractType) external view returns (address payable); + function label(TNetwork network, address contractAddr, string memory contractName) external; - function getAddressByString(string calldata contractName) external view returns (address payable); + function getContractTypeFromCurrentNetwork( + address contractAddr + ) external view returns (TContract contractType); - function getAddressByRawData(uint256 chainId, string calldata contractName) - external - view - returns (address payable addr); + function getContractName( + TContract contractType + ) external view returns (string memory name); - function getAllAddressesByRawData(uint256 chainId) external view returns (address payable[] memory addrs); + function getContractAbsolutePath( + TContract contractType + ) external view returns (string memory name); + + function getAddressFromCurrentNetwork( + TContract contractType + ) external view returns (address payable); + + function getAddressByString( + string calldata contractName + ) external view returns (address payable); + + function getAddressByRawData( + TNetwork network, + string calldata contractName + ) external view returns (address payable addr); + + function getAllAddressesByRawData( + TNetwork network + ) external view returns (address payable[] memory addrs); } diff --git a/script/interfaces/configs/IMigrationConfig.sol b/script/interfaces/configs/IMigrationConfig.sol index 0858f6f..3756e6c 100644 --- a/script/interfaces/configs/IMigrationConfig.sol +++ b/script/interfaces/configs/IMigrationConfig.sol @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface IMigrationConfig { - function areSharedArgumentsStored() external view returns (bool); - - function setRawSharedArguments(bytes calldata migrationConfig) external; + 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 2729760..32596e7 100644 --- a/script/interfaces/configs/INetworkConfig.sol +++ b/script/interfaces/configs/INetworkConfig.sol @@ -1,50 +1,93 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { TNetwork } from "../../types/Types.sol"; +import { TNetwork } from "../../types/TNetwork.sol"; interface INetworkConfig { struct NetworkData { - uint256 forkId; + TNetwork network; + uint256 blockTime; uint256 chainId; string chainAlias; - string deploymentDir; - string privateKeyEnvLabel; string explorer; } + event ForkModeUpdated(bool enabled); + function setNetworkInfo( - uint256 chainId, - TNetwork network, - string calldata chainAlias, - string calldata deploymentDir, - string calldata privateKeyEnvLabel, - string calldata explorer + NetworkData memory networkData ) external; - function setForkMode(bool shouldEnable) external; + function setForkMode( + bool shouldEnable + ) external; - function createFork(TNetwork network) external returns (uint256 forkId); + function createFork( + TNetwork network + ) external returns (uint256 forkId); - function getExplorer(TNetwork network) external view returns (string memory link); + function createFork(TNetwork network, uint256 forkBlockNumber) external returns (uint256 forkId); - function getNetworkData(TNetwork network) external view returns (NetworkData memory); + function getExplorer( + TNetwork network + ) external view returns (string memory link); - function getForkId(TNetwork network) external view returns (uint256 forkId); + function getNetworkData( + TNetwork network + ) external view returns (NetworkData memory); - function getAlias(TNetwork network) external view returns (string memory networkAlias); + function getNetworkTypeByForkId( + uint256 forkId + ) external view returns (TNetwork network); - function switchTo(TNetwork network) external; + function getForkId( + TNetwork network + ) external view returns (uint256 forkId); - function tryCreateFork(string calldata chainAlias, uint256 chainId) external returns (uint256); + function getForkId(TNetwork, uint256 forkBlockNumber) external view returns (uint256 forkId); - function getDeploymentDirectory(TNetwork network) external view returns (string memory dirPath); + function getAlias( + TNetwork network + ) external view returns (string memory networkAlias); - function getDeploymentRoot() external returns (string memory); + function switchTo( + TNetwork network + ) external; - function getCurrentNetwork() external view returns (TNetwork network); + function switchTo(TNetwork network, uint256 forkBlockNumber) external; + + function tryCreateFork( + string calldata chainAlias, + TNetwork network, + uint256 forkBlockNumber + ) external returns (uint256); + + function switchTo( + uint256 forkId + ) external; - function getPrivateKeyEnvLabel(TNetwork network) external view returns (string memory privateKeyEnvLabel); + function logCurrentForkInfo() external view; - function getNetworkByChainId(uint256 chainId) external view returns (TNetwork network); + function rollUpTo( + uint256 untilBlockNumber + ) external; + + function roll( + uint256 numBlock + ) external; + + function warp( + uint256 numSecond + ) external; + + function warpUpTo( + uint256 untilTimestamp + ) external; + + function getDeploymentDirectory( + TNetwork network + ) external view returns (string memory dirPath); + + function getCurrentNetwork() external view returns (TNetwork network); } diff --git a/script/interfaces/configs/IRuntimeConfig.sol b/script/interfaces/configs/IRuntimeConfig.sol index b3ec019..ca7a340 100644 --- a/script/interfaces/configs/IRuntimeConfig.sol +++ b/script/interfaces/configs/IRuntimeConfig.sol @@ -1,20 +1,40 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { TNetwork } from "../../types/TNetwork.sol"; interface IRuntimeConfig { struct Option { - bool generateArtifact; bool trezor; + TNetwork network; + address sender; + bool generateArtifact; + bool disablePrecheck; bool disablePostcheck; + uint256 forkBlockNumber; + string scriptRoot; } function isPostChecking() external view returns (bool); - function setPostCheckingStatus(bool status) external; + function isPreChecking() external view returns (bool); + + function setPostCheckingStatus( + bool status + ) external; + + function setPreCheckingStatus( + bool status + ) external; function getCommand() external view returns (string memory); - function resolveCommand(string calldata command) external; + function resolveCommand( + string calldata command + ) external; + + function buildRuntimeConfig() external; function getRuntimeConfig() external view returns (Option memory options); } diff --git a/script/interfaces/configs/ISharedParameter.sol b/script/interfaces/configs/ISharedParameter.sol index 93f5523..a6394d7 100644 --- a/script/interfaces/configs/ISharedParameter.sol +++ b/script/interfaces/configs/ISharedParameter.sol @@ -1,5 +1,6 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface ISharedParameter { function sharedArguments() external; diff --git a/script/interfaces/configs/IUserDefinedConfig.sol b/script/interfaces/configs/IUserDefinedConfig.sol new file mode 100644 index 0000000..b5e3736 --- /dev/null +++ b/script/interfaces/configs/IUserDefinedConfig.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +interface IUserDefinedConfig { + struct UserDefinedData { + bytes _value; + } + + function setUserDefinedConfig(string calldata key, bytes calldata value) external; + + function getUserDefinedConfig( + string calldata key + ) external view returns (bytes memory value); + + function getAllKeys() external view returns (string[] memory); +} diff --git a/script/interfaces/configs/IWalletConfig.sol b/script/interfaces/configs/IWalletConfig.sol index b07573a..3fa442a 100644 --- a/script/interfaces/configs/IWalletConfig.sol +++ b/script/interfaces/configs/IWalletConfig.sol @@ -1,5 +1,6 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; interface IWalletConfig { enum WalletOption { @@ -7,27 +8,41 @@ interface IWalletConfig { Trezor } + function loadTrezorAccount() external; + + function loadENVAccount( + string calldata envLabel + ) external; + function getSender() external view returns (address payable sender); function trezorPrefix() external view returns (string memory); function deployerEnvLabel() external view returns (string memory); - function ethSignMessage(address by, string memory message, WalletOption walletOption) - external - returns (bytes memory sig); + function ethSignMessage( + address by, + string memory message, + WalletOption walletOption + ) external returns (bytes memory sig); - function ethSignMessage(string memory message) external returns (bytes memory sig); + 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); + function envEthSignMessage( + address by, + string memory message, + string memory envLabel + ) external returns (bytes memory sig); - function envSignTypedDataV4(address by, string memory filePath, string memory envLabel) - external - returns (bytes memory sig); + function envSignTypedDataV4( + address by, + string memory filePath, + string memory envLabel + ) external returns (bytes memory sig); function trezorEthSignMessage(address by, string memory message) external returns (bytes memory sig); @@ -35,9 +50,13 @@ interface IWalletConfig { 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); + function signTypedDataV4( + address by, + string memory filePath, + WalletOption walletOption + ) external returns (bytes memory sig); - function signTypedDataV4(string memory filePath) external returns (bytes memory sig); + function signTypedDataV4( + string memory filePath + ) external returns (bytes memory sig); } diff --git a/script/libraries/LibArtifact.sol b/script/libraries/LibArtifact.sol new file mode 100644 index 0000000..e937dc0 --- /dev/null +++ b/script/libraries/LibArtifact.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { stdJson } from "../../dependencies/forge-std-1.9.3/src/StdJson.sol"; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; + +import { JSONParserLib } from "../../dependencies/solady-0.0.228/src/utils/JSONParserLib.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { IGeneralConfig } from "../interfaces/IGeneralConfig.sol"; +import { IRuntimeConfig } from "../interfaces/configs/IRuntimeConfig.sol"; +import { LibSharedAddress } from "./LibSharedAddress.sol"; + +struct ArtifactInfo { + address deployer; + address addr; + string absolutePath; + string contractName; + string artifactName; + bytes constructorArgs; + uint256 nonce; + uint256 callValue; +} + +using LibArtifact for ArtifactInfo global; + +library LibArtifact { + using StdStyle for *; + using stdJson for string; + using LibString for string; + using LibString for address; + using JSONParserLib for string; + using JSONParserLib for JSONParserLib.Item; + + Vm private constant vm = Vm(LibSharedAddress.VM); + IGeneralConfig private constant vme = IGeneralConfig(LibSharedAddress.VME); + + function generateArtifact( + ArtifactInfo memory info + ) internal { + _logDeployment(info); + + if (!vme.getRuntimeConfig().generateArtifact || vme.isPostChecking()) { + console.log("Skipping artifact generation for:", vm.getLabel(info.addr), "\n"); + return; + } + + console.log(string.concat("By: ", vm.getLabel(info.deployer), ", nonce: ", vm.toString(info.nonce), "\n")); + + vm.pauseTracing(); + + string memory dirPath = vme.getDeploymentDirectory(vme.getCurrentNetwork()); + + _tryCreateDir(dirPath); + + _serializeArtifact(dirPath, info); + + vm.resumeTracing(); + } + + function _serializeArtifact(string memory dirPath, ArtifactInfo memory info) internal { + string[] memory inputs = new string[](25); + inputs[0] = "./generate-artifact.sh"; + inputs[1] = "--name"; + inputs[2] = info.contractName; + inputs[3] = "--args"; + inputs[4] = vm.toString(info.constructorArgs); + inputs[5] = "--value"; + inputs[6] = vm.toString(info.callValue); + inputs[7] = "--nonce"; + inputs[8] = vm.toString(info.nonce); + inputs[9] = "--deployer"; + inputs[10] = vm.toString(info.deployer); + inputs[11] = "--chainid"; + inputs[12] = vm.toString(block.chainid); + inputs[13] = "--block-number"; + inputs[14] = vm.toString(vm.getBlockNumber()); + inputs[15] = "--timestamp"; + inputs[16] = vm.toString(vm.getBlockTimestamp()); + inputs[17] = "--absolute-path"; + inputs[18] = info.absolutePath; + inputs[19] = "--path"; + inputs[20] = dirPath; + inputs[21] = "--artifact-name"; + inputs[22] = info.artifactName; + inputs[23] = "--address"; + inputs[24] = vm.toString(info.addr); + + // Write deployment info + vm.ffi(inputs); + } + + function _logDeployment( + ArtifactInfo memory info + ) internal view { + console.log( + string.concat( + vm.getLabel(info.addr), + " at: ", + vme.getExplorer(vme.getCurrentNetwork()).cyan(), + "address/".cyan(), + info.addr.toHexString().cyan() + ).green() + ); + } + + function _tryCreateDir( + string memory dirPath + ) private { + 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)); + } + } + + function _getArtifactPath( + string memory absolutePath + ) private pure returns (string memory artifactPath) { + artifactPath = absolutePath; + + if (!artifactPath.endsWith(".json")) { + string[] memory s = absolutePath.split(":"); + artifactPath = s.length == 2 + ? string.concat("./out/", s[0], "/", s[1], ".json") + : string.concat("./out/", absolutePath, "/", vm.replace(absolutePath, ".sol", ".json")); + } + } +} diff --git a/script/libraries/LibDeploy.sol b/script/libraries/LibDeploy.sol new file mode 100644 index 0000000..1a528d6 --- /dev/null +++ b/script/libraries/LibDeploy.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; + +import { IEIP173 } from "../interfaces/IEIP173.sol"; +import { vm, vme } from "../utils/Constants.sol"; +import { cheatBroadcast, decodeData, sendRawTransaction } from "../utils/Helpers.sol"; + +import { ArtifactInfo } from "./LibArtifact.sol"; +import { LibProxy } from "./LibProxy.sol"; +import { LibSharedAddress } from "./LibSharedAddress.sol"; + +enum ProxyInterface { + Transparent, + Beacon, + UUPS +} + +struct DeployInfo { + string absolutePath; + string contractName; + string artifactName; + bytes constructorArgs; + uint256 callValue; + address by; +} + +struct UpgradeInfo { + address proxy; + address logic; + uint256 callValue; + bytes callData; + ProxyInterface proxyInterface; + function(address,address,uint256,bytes memory,ProxyInterface) internal upgradeCallback; + bool shouldUseCallback; + bool shouldPrompt; +} + +using LibDeploy for DeployInfo global; +using LibDeploy for UpgradeInfo global; + +library LibDeploy { + using StdStyle for string; + using LibProxy for address; + using LibProxy for address payable; + + modifier prankOrBroadcast( + address by + ) { + if (vme.isPostChecking()) { + vm.startPrank(by); + _; + vm.stopPrank(); + } else { + vm.startBroadcast(by); + _; + vm.stopBroadcast(); + } + } + + modifier validateUpgrade(address proxy, address newImpl, bool shouldPrompt) { + require(newImpl != address(0x0), "LibDeploy: Logic address is 0x0."); + + address prevProxyAdmin = proxy.getProxyAdmin(); + address prevImpl = proxy.getProxyImplementation(); + + if (prevImpl.codehash == newImpl.codehash && shouldPrompt && !vme.isPostChecking()) { + try vm.prompt( + string.concat( + "Proxy: ", + vm.getLabel(proxy), + "\nCurrent implementation ", + vm.toString(prevImpl), + " is same as new implementation ", + vm.toString(newImpl), + "\nDo you want to continue? (y/n)" + ) + ) returns (string memory answer) { + if (keccak256(bytes(answer)) != keccak256("y")) { + console.log(string.concat("Cancel upgrade for ", vm.getLabel(proxy)).yellow()); + return; + } + } catch { + console.log( + string.concat( + "WARNING: Re-upgrading contract with similar logic as current implementation ", vm.getLabel(proxy) + ).yellow() + ); + } + } + + _; + + address currProxyAdmin = proxy.getProxyAdmin(); + address currImpl = proxy.getProxyImplementation(); + + require(currImpl != address(0x0), "LibDeploy: Null Implementation"); + require(currProxyAdmin != address(0x0), "LibDeploy: Null ProxyAdmin"); + require(currProxyAdmin == prevProxyAdmin, "LibDeploy: ProxyAdmin changed"); + } + + function upgrade( + UpgradeInfo memory info + ) internal { + if (info.proxyInterface == ProxyInterface.Transparent) { + upgradeTransparentProxy( + info.proxy, + info.logic, + info.callValue, + info.callData, + info.shouldPrompt, + info.upgradeCallback, + info.shouldUseCallback + ); + } else { + revert("LibDeploy: Unsupported proxy interface for now."); + } + } + + function upgradeTransparentProxy( + address proxy, + address logic, + uint256 callValue, + bytes memory callData, + bool shouldPrompt, + function(address,address,uint256,bytes memory,ProxyInterface) internal upgradeCallback, + bool shouldUseCallback + ) internal validateUpgrade(proxy, logic, shouldPrompt) { + if (shouldUseCallback) upgradeCallback(proxy, logic, callValue, callData, ProxyInterface.Transparent); + else _tryUpgradeTransparentProxy(proxy, logic, callValue, callData); + } + + function _tryUpgradeTransparentProxy(address proxy, address logic, uint256 callValue, bytes memory callData) private { + (address auth, address interactTo) = findHierarchyAdminOfProxy(proxy); + bool isViaAuxiliary = interactTo != proxy; + + if (isViaAuxiliary) { + callData = callData.length == 0 + ? abi.encodeWithSignature("upgrade(address,address)", proxy, logic) + : abi.encodeWithSignature("upgradeAndCall(address,address,bytes)", proxy, logic, callData); + } else { + callData = callData.length == 0 + ? abi.encodeWithSignature("upgradeTo(address)", logic) + : abi.encodeWithSignature("upgradeToAndCall(address,bytes)", logic, callData); + } + + bool shouldCheatCall = auth.code.length != 0; + if (shouldCheatCall) { + console.log( + string.concat( + "LibDeploy: upgradeTransparentProxy(address,address,uint256,bytes): ", + "Cannot upgrade proxy ", + vm.getLabel(proxy), + " because it is managed by an admin contract." + ) + ); + + cheatBroadcast({ from: auth, to: interactTo, callValue: callValue, callData: callData }); + } else { + sendRawTransaction({ from: auth, to: interactTo, callValue: callValue, callData: callData, gas: 0 }); + } + } + + function findHierarchyAdminOfProxy( + address proxy + ) internal view returns (address auth, address interactTo) { + interactTo = proxy; + auth = proxy.getProxyAdmin(); + + while (true) { + if (auth.code.length == 0) return (auth, interactTo); + + try IEIP173(auth).owner() returns (address owner) { + if (owner == address(0x0)) return (auth, interactTo); + + interactTo = auth; + auth = owner; + } catch { + return (auth, interactTo); + } + } + } + + function deployImplementation( + DeployInfo memory implInfo + ) internal returns (address payable impl) { + require(implInfo.callValue == 0, "LibDeploy: deployImplementation(DeployInfo): Value must be 0."); + implInfo.artifactName = string.concat(implInfo.artifactName, "Logic"); + return deployFromArtifact(implInfo); + } + + function deployTransparentProxy( + DeployInfo memory implInfo, + uint256 callValue, + address proxyAdmin, + bytes memory callData + ) internal returns (address payable proxy) { + require(proxyAdmin != address(0x0), "BaseMigration: Null ProxyAdmin"); + + address impl = deployImplementation(implInfo); + + DeployInfo memory proxyInfo; + proxyInfo.callValue = callValue; + proxyInfo.by = implInfo.by; + proxyInfo.contractName = "TransparentProxyOZv4_9_5"; + proxyInfo.absolutePath = string.concat(proxyInfo.contractName, ".sol:", proxyInfo.contractName); + proxyInfo.artifactName = string.concat(vm.replace(implInfo.artifactName, "Logic", ""), "Proxy"); + proxyInfo.constructorArgs = abi.encode(impl, proxyAdmin, callData); + + proxy = deployFromArtifact(proxyInfo); + + // validate proxy admin + address actualProxyAdmin = proxy.getProxyAdmin(); + require( + actualProxyAdmin == proxyAdmin, + string.concat( + "LibDeploy: Invalid proxy admin\n", + "Actual: ", + vm.toString(actualProxyAdmin), + "\nExpected: ", + vm.toString(proxyAdmin) + ) + ); + } + + function deployFromArtifact( + DeployInfo memory info + ) internal returns (address payable deployed) { + vm.pauseTracing(); + deployed = deployFromBytecode( + info.absolutePath, + info.contractName, + info.artifactName, + vm.getCode(info.absolutePath), + info.constructorArgs, + info.callValue, + info.by + ); + vm.resumeTracing(); + } + + function deployFromBytecode( + string memory absolutePath, + string memory contractName, + string memory artifactName, + bytes memory bytecode, + bytes memory constructorArgs, + uint256 callValue, + address by + ) internal returns (address payable deployed) { + uint256 nonce = vm.getNonce(by); + + bytecode = abi.encodePacked(bytecode, constructorArgs); + + deployed = _deployRaw(callValue, bytecode, by); + + require(deployed != address(0x0), "LibDeploy: deployFromBytecode(bytes,bytes,uint256,address): Deployment failed."); + require(deployed.code.length > 0, "LibDeploy: deployFromBytecode(bytes,bytes,uint256,address): Empty code."); + + vme.label(vme.getCurrentNetwork(), deployed, artifactName); + + ArtifactInfo({ + deployer: by, + addr: deployed, + callValue: callValue, + nonce: nonce, + absolutePath: absolutePath, + artifactName: artifactName, + contractName: contractName, + constructorArgs: constructorArgs + }).generateArtifact(); + } + + function _deployRaw( + uint256 callValue, + bytes memory bytecode, + address by + ) private prankOrBroadcast(by) returns (address payable deployed) { + assembly ("memory-safe") { + deployed := create(callValue, add(bytecode, 0x20), mload(bytecode)) + } + } +} diff --git a/script/libraries/LibErrorHandler.sol b/script/libraries/LibErrorHandler.sol new file mode 100644 index 0000000..aad3c3d --- /dev/null +++ b/script/libraries/LibErrorHandler.sol @@ -0,0 +1,40 @@ +// 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. + + /// @notice handle low level call revert if call failed, + /// If external call return empty bytes, reverts with custom error. + /// @param status Status of external call + /// @param callSig function signature of the calldata + /// @param returnOrRevertData bytes result from external call + function handleRevert(bool status, bytes4 callSig, bytes memory returnOrRevertData) internal pure { + // Get the function signature of current context + bytes4 msgSig = msg.sig; + assembly ("memory-safe") { + if iszero(status) { + // Load the length of bytes array + let revertLength := mload(returnOrRevertData) + // Check if length != 0 => revert following reason from external call + if iszero(iszero(revertLength)) { + // Start of revert data bytes. The 0x20 offset is always the same. + revert(add(returnOrRevertData, 0x20), revertLength) + } + + // Load free memory pointer + let ptr := mload(0x40) + // Store 4 bytes the function selector of ExternalCallFailed(msg.sig, callSig) + // Equivalent to revert ExternalCallFailed(bytes4,bytes4) + mstore(ptr, 0x49bf4104) + // Store 4 bytes of msgSig parameter in the next slot + mstore(add(ptr, 0x20), msgSig) + // Store 4 bytes of callSig parameter in the next slot + mstore(add(ptr, 0x40), callSig) + // Revert 68 bytes of error starting from 0x1c + revert(add(ptr, 0x1c), 0x44) + } + } + } +} diff --git a/script/libraries/LibInitializeGuard.sol b/script/libraries/LibInitializeGuard.sol new file mode 100644 index 0000000..aa70acc --- /dev/null +++ b/script/libraries/LibInitializeGuard.sol @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm, VmSafe } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { Math } from "../../dependencies/openzeppelin-5.0.2/contracts/utils/math/Math.sol"; +import { EnumerableSet } from "../../dependencies/openzeppelin-5.0.2/contracts/utils/structs/EnumerableSet.sol"; +import { JSONParserLib } from "../../dependencies/solady-0.0.228/src/utils/JSONParserLib.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; + +import { TContract } from "../types/TContract.sol"; +import { TNetwork } from "../types/TNetwork.sol"; +import { console, vm, vme } from "../utils/Helpers.sol"; + +interface InitializableOZV4 { + event Initialized(uint8); +} + +interface InitializableOZV5 { + event Initialized(uint64); +} + +interface IERC1967 { + event Upgraded(address indexed); +} + +/** + * @dev Library to guard the initialization of the proxies and logics. + * - Proxy: + * + The proxy MUST have `_initialized` slot. + * + `_initialized` value MUST increment by 1 after each `initialize` function call. + * + The last initialized version MUST equal to the number of `initialize` functions. + * - Logic: + * + The logic MUST disable the initialized version. + */ +library LibInitializeGuard { + using StdStyle for *; + using LibString for string; + using JSONParserLib for string; + using JSONParserLib for JSONParserLib.Item; + using EnumerableSet for EnumerableSet.AddressSet; + + struct InitLocation { + bytes32 slot; + uint256 bitOffset; + uint256 nBit; + } + + struct Cache { + EnumerableSet.AddressSet _logics; + EnumerableSet.AddressSet _proxies; + mapping(address addr => uint256) _lastInitVer; + mapping(address addr => Vm.ChainInfo) _chainInfo; + mapping(address proxy => InitLocation) _initSlot; + mapping(address logic => address proxy) _logic2Proxy; + } + + /// @dev Number of bits used to store initialized version in `_initialized` slot of OpenZeppelin v4 + uint256 private constant N_BIT_INIT_V4 = 8; + /// @dev Maximum value of initialized version in `_initialized` slot of OpenZeppelin v4 + uint256 private constant MAX_VER_V4 = type(uint8).max; + /// @dev Number of bits used to store initialized version in `_initialized` slot of OpenZeppelin v5 + uint256 private constant N_BIT_INIT_V5 = 64; + /// @dev Maximum value of initialized version in `_initialized` slot of OpenZeppelin v5 + uint256 private constant MAX_VER_V5 = type(uint64).max; + + /// @dev See: + /// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.0.2/contracts/proxy/utils/Initializable.sol#L77 + bytes32 private constant INITIALIZABLE_STORAGE_OZV5 = + 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00; + + /// @dev Custom storage slot of the `Cache` struct + bytes32 private constant $$_CacheStorageLocation = keccak256("LibInitializeGuard.Cache.storage.slot"); + /// @dev Custom storage slot of the `StdStorage` struct + bytes32 private constant $$_StdStorageLocation = keccak256("LibInitializeGuard.StdStorage.storage.slot"); + + /** + * @dev Validate the initialization of the proxies and logics. + * + * Requirements: + * - Must record `logs` via `vm.recordLogs()` before calling this function. + * - Must record `stateDiffs` via `vm.startStateDiffRecording()` before calling this function. + * + * @param logs The logs of the transactions. + * @param stateDiffs The state diffs of the transactions. + */ + function validate(Vm.Log[] memory logs, Vm.AccountAccess[] memory stateDiffs) internal { + Cache storage $ = _getCacheStorage(); + + _recordUpgradesAndInitializations({ $cache: $, logs: logs }); + + for (uint256 i; i < stateDiffs.length; ++i) { + address addr = stateDiffs[i].account; + + if ($._proxies.contains(addr) && $._initSlot[addr].nBit == 0) { + // Record the chain info and initialized slot of the `addr`. + $._chainInfo[addr] = stateDiffs[i].chainInfo; + $._initSlot[addr] = _getInitializedSlot($, addr); + } + + if ($._logics.contains(addr) && stateDiffs[i].kind == VmSafe.AccountAccessKind.DelegateCall) { + address proxy = $._logic2Proxy[addr]; + InitLocation memory initLoc = $._initSlot[proxy]; + Vm.StorageAccess[] memory accs = stateDiffs[i].storageAccesses; + + for (uint256 j; j < accs.length; ++j) { + // Skip if changes does not made changes to `initSlot` by `proxy` to `logic` + if (!(accs[j].isWrite && accs[j].account == proxy && accs[j].slot == initLoc.slot)) continue; + + bool shouldSkip = _validateInitChanges(accs[j], initLoc); + if (shouldSkip) continue; + } + } + } + + _validateLogicsVersion({ $cache: $ }); + _validateProxiesVersion({ $cache: $ }); + } + + /** + * @dev Validate the initialized version of the logics. + * - Check if the logic disable initialized version. + */ + function _validateLogicsVersion( + Cache storage $cache + ) private view { + address[] memory logics = $cache._logics.values(); + uint256 length = logics.length; + + for (uint256 i; i < length; ++i) { + uint256 lastInitVer = $cache._lastInitVer[logics[i]]; + + require( + lastInitVer == MAX_VER_V4 || lastInitVer == MAX_VER_V5, + string.concat("LibInitializeGuard: Logic ", vm.getLabel(logics[i]), " did not disable initialized version!") + ); + } + } + + /** + * @dev Validate the initialized version of the proxies. + * - Check if `_initialized` slot is found. + * - Check if the last initialized version is equal to the number of `initialize` functions. + */ + function _validateProxiesVersion( + Cache storage $cache + ) private { + address[] memory proxies = $cache._proxies.values(); + uint256 length = proxies.length; + + for (uint256 i; i < length; ++i) { + address proxy = proxies[i]; + InitLocation memory initLoc = $cache._initSlot[proxy]; + + require( + initLoc.nBit != 0, + string.concat("LibInitializeGuard: Proxy ", vm.getLabel(proxies[i]), " does not have `_initialized` slot!") + ); + + uint256 lastInitVer = $cache._lastInitVer[proxy]; + // ToDo(TuDo1403): handle multi-chain + uint256 actualInitVer = _getVersionFromSlotValue(vm.load(proxy, initLoc.slot), initLoc.bitOffset, initLoc.nBit); + + require( + lastInitVer != 0 || actualInitVer != 0, + string.concat("LibInitializeGuard: Proxy ", vm.getLabel(proxy), " does not initialize!".red()) + ); + // Allow upgrade without initialization + require(actualInitVer >= lastInitVer, "LibInitializeGuard: `lastInitVer` > `actualInitVer`!"); + + actualInitVer = Math.max(lastInitVer, actualInitVer); + + if ( + (actualInitVer == MAX_VER_V4 && initLoc.nBit == N_BIT_INIT_V4) + || (actualInitVer == MAX_VER_V5 && initLoc.nBit == N_BIT_INIT_V5) + ) { + string memory ret = vm.prompt( + string.concat( + "[WARNING] ".yellow(), + vm.getLabel(proxy), + " disabled initialized version.".yellow(), + " Is it intentional?\n".yellow(), + "Press ", + "yes ".blue(), + "to continue..." + ) + ); + require( + keccak256(bytes(vm.toLowercase(ret))) == keccak256("yes"), + "LibInitializeGuard: Aborted due to unintended disable initialization!" + ); + + continue; + } + + uint256 initFnCount = _getInitializeFnCount($cache, proxy); + require( + actualInitVer == initFnCount, + string.concat( + "LibInitializeGuard: Invalid initialized version!", + " Expected: ", + vm.toString(initFnCount), + " Got: ", + vm.toString(actualInitVer) + ) + ); + } + } + + /** + * @dev Validate the intermediate changes of the `_initialized` slot of the given `access` storage. + * + * @param acc The storage access of data. + * @param initLoc The initialized location data of the proxy. + * @return shouldSkip Whether to skip the validation. + */ + function _validateInitChanges( + Vm.StorageAccess memory acc, + InitLocation memory initLoc + ) private view returns (bool shouldSkip) { + uint256 prvVer = _getVersionFromSlotValue(acc.previousValue, initLoc.bitOffset, initLoc.nBit); + uint256 newVer = _getVersionFromSlotValue(acc.newValue, initLoc.bitOffset, initLoc.nBit); + + // Skip if `_initialized` bytes location in `slot` does not change + // Assume other data in given slot is not related to initialized version + if (prvVer == newVer) return true; + + uint256 initBit = initLoc.nBit; + // Skip if the proxy disable initialized version + if ((newVer == MAX_VER_V4 && initBit == N_BIT_INIT_V4) || (newVer == MAX_VER_V5 && initBit == N_BIT_INIT_V5)) { + console.log("[INIT] %s: Disabled initialized version", vm.getLabel(acc.account)); + return true; + } + + console.log(unicode"[INIT] %s: v%d → v%d", vm.getLabel(acc.account), prvVer, newVer); + + require(newVer == prvVer + 1, "LibInitializeGuard: Version does not correctly increment!"); + } + + /** + * @dev Record the upgrades and initializations of proxies and logics. + */ + function _recordUpgradesAndInitializations(Cache storage $cache, Vm.Log[] memory logs) private { + uint256 length = logs.length; + + for (uint256 i; i < length; ++i) { + address emitter = logs[i].emitter; + bytes32 eventTopic = logs[i].topics[0]; + + if (eventTopic == InitializableOZV4.Initialized.selector) { + $cache._lastInitVer[emitter] = abi.decode(logs[i].data, (uint8)); + } + + if (eventTopic == InitializableOZV5.Initialized.selector) { + $cache._lastInitVer[emitter] = abi.decode(logs[i].data, (uint64)); + } + + if (eventTopic == IERC1967.Upgraded.selector) { + address logic = address(uint160(uint256(logs[i].topics[1]))); + $cache._logics.add(logic); + $cache._proxies.add(emitter); + $cache._logic2Proxy[logic] = emitter; + } + } + } + + /** + * @dev Get the version from the given `value` at the `bitOffset` and `nBit`. + */ + function _getVersionFromSlotValue(bytes32 value, uint256 bitOffset, uint256 nBit) private pure returns (uint256) { + uint256 mask = (1 << nBit) - 1; + return (uint256(value) >> bitOffset) & mask; + } + + /** + * @dev Get the number of `initialize` functions of the given `proxy` by inspecting its storage layout using `forge + * inspect methodIdentifiers`. + */ + function _getInitializeFnCount(Cache storage $cache, address proxy) private returns (uint256 count) { + string[] memory inputs = new string[](4); + inputs[0] = "forge"; + inputs[1] = "inspect"; + inputs[2] = _getContractAbsolutePath($cache._chainInfo[proxy].forkId, proxy); + inputs[3] = "methodIdentifiers"; + + string memory ret = vm.toLowercase(string(vm.ffi(inputs))); + string[] memory allFns = vm.parseJsonKeys(ret, "."); + uint256 length = allFns.length; + + for (uint256 i; i < length; ++i) { + if (allFns[i].startsWith("initialize")) count++; + } + } + + /** + * @dev Get `_initialized` slot of the given `proxy` by inspecting its storage layout using `forge inspect + * storage`. + * If the slot is not found, infer it used OpenZeppelin v5 `Initializable` extension. + */ + function _getInitializedSlot(Cache storage $cache, address proxy) private returns (InitLocation memory initSlot) { + // Assume the proxy uses OpenZeppelin v5 `Initializable` extension + initSlot.nBit = N_BIT_INIT_V5; + initSlot.slot = INITIALIZABLE_STORAGE_OZV5; + + string[] memory inputs = new string[](4); + inputs[0] = "forge"; + inputs[1] = "inspect"; + inputs[2] = _getContractAbsolutePath($cache._chainInfo[proxy].forkId, proxy); + 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.bitOffset = storageSlot.at('"offset"').value().parseUint() * 8; + initSlot.nBit = N_BIT_INIT_V4; + initSlot.slot = bytes32(vm.parseUint(storageSlot.at('"slot"').value().decodeString())); + + return initSlot; + } + } + } + + /** + * @dev Get the contract absolute path by the given `addr` and `forkId`. + */ + function _getContractAbsolutePath(uint256 forkId, address addr) private view returns (string memory contractName) { + TNetwork networkType = vme.getNetworkTypeByForkId(forkId); + TContract contractType = vme.getContractTypeByRawData(networkType, addr); + string memory contractNameMap = _getContractNameFromAbsolutePath(vme.getContractAbsolutePath(contractType)); + contractName = contractNameMap; + } + + function _getContractNameFromAbsolutePath( + string memory path + ) internal pure returns (string memory contractName) { + uint256 length = bytes(path).length; + contractName = path; + if (path.endsWith(".sol")) contractName = path.slice(0, length - 4); + string[] memory parts = contractName.split(":"); + if (parts.length != 0) contractName = parts[parts.length - 1]; + parts = contractName.split("/"); + if (parts.length != 0) contractName = parts[parts.length - 1]; + } + + /** + * @dev Get the storage slot of the `Cache` struct. + */ + function _getCacheStorage() private pure returns (Cache storage $) { + bytes32 slot = $$_CacheStorageLocation; + + assembly ("memory-safe") { + $.slot := slot + } + } +} diff --git a/script/libraries/LibProxy.sol b/script/libraries/LibProxy.sol index 046e088..8f1fded 100644 --- a/script/libraries/LibProxy.sol +++ b/script/libraries/LibProxy.sol @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { Vm } from "../../lib/forge-std/src/Vm.sol"; +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; import { LibSharedAddress } from "./LibSharedAddress.sol"; library LibProxy { @@ -9,29 +10,37 @@ library LibProxy { bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; - function getProxyAdmin(address payable proxy, bool nullCheck) internal view returns (address payable proxyAdmin) { + function getProxyAdmin(address 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( proxyAdmin != address(0x0), string.concat("LibProxy: Null ProxyAdmin, Provided address: ", vm.getLabel(proxy), " is not EIP1967 Proxy") ); } - function getProxyAdmin(address payable proxy) internal view returns (address payable proxyAdmin) { + function getProxyAdmin( + address proxy + ) internal view returns (address payable proxyAdmin) { proxyAdmin = getProxyAdmin({ proxy: proxy, nullCheck: true }); } - function getProxyImplementation(address payable proxy, bool nullCheck) internal view returns (address payable impl) { + function getProxyImplementation(address 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( impl != address(0x0), string.concat("LibProxy: Null Implementation, Provided address: ", vm.getLabel(proxy), " is not EIP1967 Proxy") ); } - function getProxyImplementation(address payable proxy) internal view returns (address payable impl) { + function getProxyImplementation( + address proxy + ) internal view returns (address payable impl) { impl = getProxyImplementation({ proxy: proxy, nullCheck: true }); } } diff --git a/script/libraries/LibSharedAddress.sol b/script/libraries/LibSharedAddress.sol index 4504619..63452f4 100644 --- a/script/libraries/LibSharedAddress.sol +++ b/script/libraries/LibSharedAddress.sol @@ -1,8 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; library LibSharedAddress { - address internal constant CONFIG = address(uint160(uint256(keccak256("config")))); + address internal constant VME = address(uint160(uint256(keccak256("vme")))); + /// @dev Preserve constant for backwards compatibility + address internal constant CONFIG = address(uint160(uint256(keccak256("vme")))); address internal constant VM = address(uint160(uint256(keccak256("hevm cheat code")))); address internal constant ARTIFACT_FACTORY = address(uint160(uint256(keccak256("logger")))); } diff --git a/script/libraries/LibSig.sol b/script/libraries/LibSig.sol index 4b7e6f8..b8e8554 100644 --- a/script/libraries/LibSig.sol +++ b/script/libraries/LibSig.sol @@ -1,5 +1,6 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; library LibSig { /** @@ -23,7 +24,9 @@ library LibSig { * @param signature Signature bytes to split * @return r s v Tuple of ECDSA values */ - function split(bytes calldata signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) { + function split( + bytes calldata signature + ) internal pure returns (bytes32 r, bytes32 s, uint8 v) { assembly ("memory-safe") { r := calldataload(signature.offset) s := calldataload(add(signature.offset, 0x20)) diff --git a/script/sample/SampleGeneralConfig.sol b/script/sample/SampleGeneralConfig.sol index 3466e4f..0760146 100644 --- a/script/sample/SampleGeneralConfig.sol +++ b/script/sample/SampleGeneralConfig.sol @@ -1,19 +1,20 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { BaseGeneralConfig } from "foundry-deployment-kit/BaseGeneralConfig.sol"; import { Contract } from "./utils/Contract.sol"; +import { BaseGeneralConfig } from "@fdk/BaseGeneralConfig.sol"; contract SampleGeneralConfig is BaseGeneralConfig { constructor() BaseGeneralConfig("", "deployments/") { } function _setUpContracts() internal virtual override { _contractNameMap[Contract.Sample.key()] = Contract.Sample.name(); - // {SamepleClone} share same logic as {Sample} + // {SampleClone} share same logic as {Sample} _contractNameMap[Contract.SampleClone.key()] = Contract.Sample.name(); _contractNameMap[Contract.SampleProxy.key()] = Contract.SampleProxy.name(); - // allow diffrent contracts to share same logic + // allow different contracts to share same logic _contractNameMap[Contract.tSLP.key()] = "Token"; _contractNameMap[Contract.tAXS.key()] = "Token"; _contractNameMap[Contract.tWETH.key()] = "Token"; diff --git a/script/sample/SampleMigration.s.sol b/script/sample/SampleMigration.s.sol index 6817bcc..50040e8 100644 --- a/script/sample/SampleMigration.s.sol +++ b/script/sample/SampleMigration.s.sol @@ -1,13 +1,14 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; -import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; import { SampleGeneralConfig } from "./SampleGeneralConfig.sol"; import { ISharedArgument } from "./interfaces/ISharedArgument.sol"; +import { BaseMigration } from "@fdk/BaseMigration.s.sol"; +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; contract SampleMigration is BaseMigration { - ISharedArgument public constant config = ISharedArgument(address(CONFIG)); + ISharedArgument public constant config = ISharedArgument(address(vme)); function _configByteCode() internal virtual override returns (bytes memory) { return abi.encodePacked(type(SampleGeneralConfig).creationCode); @@ -24,7 +25,7 @@ contract SampleMigration is BaseMigration { param.message = "Sample Ronin Mainnet"; param.proxyMessage = "Sample Proxy Ronin Mainnet"; } - if (network() == DefaultNetwork.Local.key()) { + if (network() == DefaultNetwork.LocalHost.key()) { param.message = "Sample Anvil"; param.proxyMessage = "Sample Proxy Anvil"; } diff --git a/script/sample/contracts/SampleCloneDeploy.s.sol b/script/sample/contracts/SampleCloneDeploy.s.sol index 5a73e6f..4af4959 100644 --- a/script/sample/contracts/SampleCloneDeploy.s.sol +++ b/script/sample/contracts/SampleCloneDeploy.s.sol @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { Sample } from "src/Sample.sol"; -import { Contract } from "../utils/Contract.sol"; import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; +import { Contract } from "../utils/Contract.sol"; import { SampleDeploy } from "./SampleDeploy.s.sol"; +import { Sample } from "src/mocks/Sample.sol"; contract SampleCloneDeploy is SampleMigration { function _injectDependencies() internal virtual override { diff --git a/script/sample/contracts/SampleDeploy.s.sol b/script/sample/contracts/SampleDeploy.s.sol index 7784c74..c46706d 100644 --- a/script/sample/contracts/SampleDeploy.s.sol +++ b/script/sample/contracts/SampleDeploy.s.sol @@ -1,18 +1,13 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { Sample } from "src/Sample.sol"; -import { Contract } from "../utils/Contract.sol"; import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; +import { Contract } from "../utils/Contract.sol"; +import { Sample } from "src/mocks/Sample.sol"; contract SampleDeploy is SampleMigration { - function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.SharedParameter memory param = config.sharedArguments(); - args = abi.encode(param.message); - } - function run() public virtual returns (Sample instance) { instance = Sample(_deployImmutable(Contract.Sample.key())); - assertEq(instance.getMessage(), config.sharedArguments().message); } } diff --git a/script/sample/contracts/SampleProxyDeploy.s.sol b/script/sample/contracts/SampleProxyDeploy.s.sol index 5bf56c5..36fba41 100644 --- a/script/sample/contracts/SampleProxyDeploy.s.sol +++ b/script/sample/contracts/SampleProxyDeploy.s.sol @@ -1,18 +1,22 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { SampleProxy } from "src/SampleProxy.sol"; -import { Contract } from "../utils/Contract.sol"; import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; +import { Contract } from "../utils/Contract.sol"; +import { SampleProxy } from "src/mocks/SampleProxy.sol"; contract SampleProxyDeploy is SampleMigration { function _defaultArguments() internal virtual override returns (bytes memory args) { - ISharedArgument.SharedParameter memory param = ISharedArgument(address(CONFIG)).sharedArguments(); + ISharedArgument.SharedParameter memory param = ISharedArgument(address(vme)).sharedArguments(); args = abi.encodeCall(SampleProxy.initialize, (param.proxyMessage)); } function run() public virtual returns (SampleProxy instance) { instance = SampleProxy(_deployProxy(Contract.SampleProxy.key())); - assertEq(instance.getMessage(), ISharedArgument(address(CONFIG)).sharedArguments().proxyMessage); + assertEq(instance.getMessage(), ISharedArgument(address(vme)).sharedArguments().proxyMessage); + + // vm.prank(sender()); + // instance.initializeV4(); } } diff --git a/script/sample/interfaces/ISharedArgument.sol b/script/sample/interfaces/ISharedArgument.sol index 5d12e63..e0bcf82 100644 --- a/script/sample/interfaces/ISharedArgument.sol +++ b/script/sample/interfaces/ISharedArgument.sol @@ -1,7 +1,8 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { IGeneralConfig } from "foundry-deployment-kit/interfaces/IGeneralConfig.sol"; +import { IGeneralConfig } from "@fdk/interfaces/IGeneralConfig.sol"; interface ISharedArgument is IGeneralConfig { struct SharedParameter { @@ -10,7 +11,7 @@ interface ISharedArgument is IGeneralConfig { address mFactory; address testnetFactory; bytes32 mPairCodeHash; - bytes32 testnetpairCodeHash; + bytes32 testnetPairCodeHash; address mWRON; address mSLP; address mAXS; diff --git a/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol b/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol index 23564e8..959a284 100644 --- a/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol +++ b/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; import { Sample, SampleDeploy } from "../../contracts/SampleDeploy.s.sol"; import { SampleProxy, SampleProxyDeploy } from "../../contracts/SampleProxyDeploy.s.sol"; +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; contract Migration__XXXXYYZZ_DeploySample is SampleMigration { function _sharedArguments() internal virtual override returns (bytes memory args) { diff --git a/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol b/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol index b3a1fd7..72fa3af 100644 --- a/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol +++ b/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol @@ -1,12 +1,14 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { console2 as console } from "forge-std/console2.sol"; -import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; -import { Contract } from "../../utils/Contract.sol"; +import { console } from "../../../../dependencies/forge-std-1.9.3/src/console.sol"; + +import { Token } from "../../../../src/mocks/Token.sol"; +import { WNT } from "../../../../src/mocks/WNT.sol"; import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; -import { Token } from "../../../../src/Token.sol"; -import { WNT } from "../../../../src/WNT.sol"; +import { Contract } from "../../utils/Contract.sol"; +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; contract Migration__20231204_DeployMockERC20 is SampleMigration { function _sharedArguments() internal virtual override returns (bytes memory args) { @@ -20,11 +22,11 @@ contract Migration__20231204_DeployMockERC20 is SampleMigration { param.mWRON = 0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4; param.mBERRY = 0x1B918543B518E34902e1E8dd76052BeE43C762Ff; - config.label(2021, param.mAXS, "AXS"); - config.label(2021, param.mSLP, "SLP"); - config.label(2021, param.mWETH, "WETH"); - config.label(2021, param.mWRON, "WRON"); - config.label(2021, param.mBERRY, "BERRY"); + config.label(DefaultNetwork.RoninMainnet.key(), param.mAXS, "AXS"); + config.label(DefaultNetwork.RoninMainnet.key(), param.mSLP, "SLP"); + config.label(DefaultNetwork.RoninMainnet.key(), param.mWETH, "WETH"); + config.label(DefaultNetwork.RoninMainnet.key(), param.mWRON, "WRON"); + config.label(DefaultNetwork.RoninMainnet.key(), param.mBERRY, "BERRY"); args = abi.encode(param); } diff --git a/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol b/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol index 035afc6..1765c07 100644 --- a/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol +++ b/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol @@ -1,10 +1,11 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; import { Sample, SampleDeploy } from "../../contracts/SampleDeploy.s.sol"; import { SampleProxy, SampleProxyDeploy } from "../../contracts/SampleProxyDeploy.s.sol"; +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; contract Migration__XXXXYYZZ_DeploySample is SampleMigration { function _sharedArguments() internal virtual override returns (bytes memory args) { diff --git a/script/sample/ronin-testnet/xxxxyyzz-UpgradeSampleProxy/xxxxyyzz_UpgradeSampleProxy.s.sol b/script/sample/ronin-testnet/xxxxyyzz-UpgradeSampleProxy/xxxxyyzz_UpgradeSampleProxy.s.sol new file mode 100644 index 0000000..63b596e --- /dev/null +++ b/script/sample/ronin-testnet/xxxxyyzz-UpgradeSampleProxy/xxxxyyzz_UpgradeSampleProxy.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { SampleMigration } from "../../SampleMigration.s.sol"; + +import { Contract } from "../../utils/Contract.sol"; +import { DefaultNetwork } from "@fdk/utils/DefaultNetwork.sol"; +import { SampleProxy } from "src/mocks/SampleProxy.sol"; + +contract Migration__XXXXYYZZ_UpgradeSampleProxy is SampleMigration { + function run() public onlyOn(DefaultNetwork.RoninTestnet.key()) { + _upgradeProxy(Contract.Sample.key()); + } +} diff --git a/script/sample/utils/Contract.sol b/script/sample/utils/Contract.sol index 9a0de1a..55690f0 100644 --- a/script/sample/utils/Contract.sol +++ b/script/sample/utils/Contract.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { LibString } from "lib/solady/src/utils/LibString.sol"; -import { TContract } from "foundry-deployment-kit/types/Types.sol"; +import { LibString } from "../../../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { TContract } from "@fdk/types/Types.sol"; enum Contract { tSLP, @@ -12,16 +13,21 @@ enum Contract { tWETH, Sample, SampleClone, - SampleProxy + SampleProxy, + SampleProxyForTestingPurpose } using { key, name } for Contract global; -function key(Contract contractEnum) pure returns (TContract) { +function key( + Contract contractEnum +) pure returns (TContract) { return TContract.wrap(LibString.packOne(name(contractEnum))); } -function name(Contract contractEnum) pure returns (string memory) { +function name( + Contract contractEnum +) pure returns (string memory) { if (contractEnum == Contract.Sample) return "Sample"; if (contractEnum == Contract.tBERRY) return "tBERRY"; if (contractEnum == Contract.tWETH) return "tWETH"; @@ -30,5 +36,6 @@ function name(Contract contractEnum) pure returns (string memory) { if (contractEnum == Contract.tWRON) return "tWRON"; if (contractEnum == Contract.SampleClone) return "SampleClone"; if (contractEnum == Contract.SampleProxy) return "SampleProxy"; + if (contractEnum == Contract.SampleProxyForTestingPurpose) return "SampleProxyForTestingPurpose"; revert("Contract: Unknown contract"); } diff --git a/script/types/TContract.sol b/script/types/TContract.sol new file mode 100644 index 0000000..3a658e1 --- /dev/null +++ b/script/types/TContract.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; + +type TContract is bytes32; + +using LibString for bytes32; + +using { name, eq as ==, neq as != } for TContract global; + +function name( + TContract contractType +) pure returns (string memory) { + return TContract.unwrap(contractType).unpackOne(); +} + +function eq(TContract a, TContract b) pure returns (bool) { + return TContract.unwrap(a) == TContract.unwrap(b); +} + +function neq(TContract a, TContract b) pure returns (bool) { + return TContract.unwrap(a) != TContract.unwrap(b); +} diff --git a/script/types/TNetwork.sol b/script/types/TNetwork.sol new file mode 100644 index 0000000..8735bf9 --- /dev/null +++ b/script/types/TNetwork.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; + +type TNetwork is bytes32; + +using LibString for bytes32; + +using { chainAlias, eq as ==, neq as !=, env, dir } for TNetwork global; + +function chainAlias( + TNetwork network +) pure returns (string memory) { + return TNetwork.unwrap(network).unpackOne(); +} + +function env( + TNetwork network +) pure returns (string memory) { + Vm vm = Vm(LibSharedAddress.VM); + return string.concat(vm.toUppercase(vm.replace(chainAlias(network), "-", "_")), "_PK"); +} + +function dir( + TNetwork network +) pure returns (string memory) { + return string.concat(chainAlias(network), "/"); +} + +function eq(TNetwork a, TNetwork b) pure returns (bool) { + return TNetwork.unwrap(a) == TNetwork.unwrap(b); +} + +function neq(TNetwork a, TNetwork b) pure returns (bool) { + return TNetwork.unwrap(a) != TNetwork.unwrap(b); +} diff --git a/script/types/Types.sol b/script/types/Types.sol index 36f0193..2bec312 100644 --- a/script/types/Types.sol +++ b/script/types/Types.sol @@ -1,36 +1,6 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; - -type TNetwork is bytes32; - -type TContract is bytes32; - -using LibString for bytes32; -using { networkName, networkEq as ==, networkNeq as != } for TNetwork global; -using { contractName, contractEq as ==, contractNeq as != } for TContract global; - -function networkName(TNetwork network) pure returns (string memory) { - return TNetwork.unwrap(network).unpackOne(); -} - -function contractName(TContract contractType) pure returns (string memory) { - return TContract.unwrap(contractType).unpackOne(); -} - -function networkEq(TNetwork a, TNetwork b) pure returns (bool) { - return TNetwork.unwrap(a) == TNetwork.unwrap(b); -} - -function networkNeq(TNetwork a, TNetwork b) pure returns (bool) { - return TNetwork.unwrap(a) != TNetwork.unwrap(b); -} - -function contractEq(TContract a, TContract b) pure returns (bool) { - return TContract.unwrap(a) == TContract.unwrap(b); -} - -function contractNeq(TContract a, TContract b) pure returns (bool) { - return TContract.unwrap(a) != TContract.unwrap(b); -} +import { TContract } from "./TContract.sol"; +import { TNetwork } from "./TNetwork.sol"; diff --git a/script/utils/Constants.sol b/script/utils/Constants.sol new file mode 100644 index 0000000..60dc14b --- /dev/null +++ b/script/utils/Constants.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { Vm } from "../../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { IGeneralConfig } from "../interfaces/IGeneralConfig.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; + +bytes constant EMPTY_ARGS = ""; +Vm constant vm = Vm(LibSharedAddress.VM); +IGeneralConfig constant vme = IGeneralConfig(LibSharedAddress.VME); diff --git a/script/utils/DefaultContract.sol b/script/utils/DefaultContract.sol index 1e5b605..0da9c06 100644 --- a/script/utils/DefaultContract.sol +++ b/script/utils/DefaultContract.sol @@ -1,22 +1,67 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; import { TContract } from "../types/Types.sol"; enum DefaultContract { ProxyAdmin, - Multicall3 + Multicall2, + Multicall3, + WRON, + WRONHelper, + WETH, + AXS, + Scatter, + KatanaRouter, + KatanaFactory, + KatanaGovernance, + AffiliateRouter, + PermissionedRouter, + SCMultisig, + USDC, + Axie, + Pyth, + ERC721BatchTransfer, + RoninGovernanceAdmin, + RoninValidatorSet, + Profile, + RoninVRFCoordinator } using { key, name } for DefaultContract global; -function key(DefaultContract defaultContract) pure returns (TContract) { +function key( + DefaultContract defaultContract +) pure returns (TContract) { return TContract.wrap(LibString.packOne(name(defaultContract))); } -function name(DefaultContract defaultContract) pure returns (string memory) { +function name( + DefaultContract defaultContract +) pure returns (string memory) { if (defaultContract == DefaultContract.ProxyAdmin) return "ProxyAdmin"; + if (defaultContract == DefaultContract.Multicall2) return "Multicall2"; if (defaultContract == DefaultContract.Multicall3) return "Multicall3"; + if (defaultContract == DefaultContract.WRON) return "WRON"; + if (defaultContract == DefaultContract.WRONHelper) return "WRONHelper"; + if (defaultContract == DefaultContract.WETH) return "WETH"; + if (defaultContract == DefaultContract.AXS) return "AXS"; + if (defaultContract == DefaultContract.Scatter) return "Scatter"; + if (defaultContract == DefaultContract.KatanaRouter) return "KatanaRouter"; + if (defaultContract == DefaultContract.KatanaFactory) return "KatanaFactory"; + if (defaultContract == DefaultContract.KatanaGovernance) return "KatanaGovernance"; + if (defaultContract == DefaultContract.AffiliateRouter) return "AffiliateRouter"; + if (defaultContract == DefaultContract.PermissionedRouter) return "PermissionedRouter"; + if (defaultContract == DefaultContract.SCMultisig) return "SCMultisig"; + if (defaultContract == DefaultContract.USDC) return "USDC"; + if (defaultContract == DefaultContract.Axie) return "Axie"; + if (defaultContract == DefaultContract.Pyth) return "Pyth"; + if (defaultContract == DefaultContract.ERC721BatchTransfer) return "ERC721BatchTransfer"; + if (defaultContract == DefaultContract.RoninGovernanceAdmin) return "RoninGovernanceAdmin"; + if (defaultContract == DefaultContract.RoninValidatorSet) return "RoninValidatorSet"; + if (defaultContract == DefaultContract.RoninVRFCoordinator) return "RoninVRFCoordinator"; + if (defaultContract == DefaultContract.Profile) return "Profile"; revert("DefaultContract: Unknown contract"); } diff --git a/script/utils/DefaultNetwork.sol b/script/utils/DefaultNetwork.sol index 7a5a9eb..0386fa1 100644 --- a/script/utils/DefaultNetwork.sol +++ b/script/utils/DefaultNetwork.sol @@ -1,58 +1,69 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; -import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; + +import { INetworkConfig } from "../interfaces/configs/INetworkConfig.sol"; import { TNetwork } from "../types/Types.sol"; enum DefaultNetwork { - Local, + LocalHost, RoninTestnet, RoninMainnet } -using { key, name, chainId, chainAlias, envLabel, deploymentDir, explorer } for DefaultNetwork global; +using { key, chainId, chainAlias, explorer, data } for DefaultNetwork global; -function chainId(DefaultNetwork defaultNetwork) pure returns (uint256) { - if (defaultNetwork == DefaultNetwork.Local) return 31337; - if (defaultNetwork == DefaultNetwork.RoninMainnet) return 2020; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return 2021; - revert("DefaultNetwork: Unknown chain id"); +function data( + DefaultNetwork network +) pure returns (INetworkConfig.NetworkData memory) { + return INetworkConfig.NetworkData({ + network: key(network), + blockTime: blockTime(network), + chainAlias: chainAlias(network), + explorer: explorer(network), + chainId: chainId(network) + }); } -function explorer(DefaultNetwork defaultNetwork) pure returns (string memory link) { - if (defaultNetwork == DefaultNetwork.RoninMainnet) return "https://app.roninchain.com/"; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return "https://saigon-app.roninchain.com/"; - return ""; -} - -function key(DefaultNetwork defaultNetwork) pure returns (TNetwork) { - return TNetwork.wrap(LibString.packOne(name(defaultNetwork))); +function chainId( + DefaultNetwork network +) pure returns (uint256) { + if (network == DefaultNetwork.LocalHost) return 31_337; + if (network == DefaultNetwork.RoninMainnet) return 2020; + if (network == DefaultNetwork.RoninTestnet) return 2021; + revert("DefaultNetwork: Unknown chain id"); } -function name(DefaultNetwork defaultNetwork) pure returns (string memory) { - if (defaultNetwork == DefaultNetwork.Local) return "Local"; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return "RoninTestnet"; - if (defaultNetwork == DefaultNetwork.RoninMainnet) return "RoninMainnet"; - revert("DefaultNetwork: Unknown network name"); +function blockTime( + DefaultNetwork network +) pure returns (uint256) { + if (network == DefaultNetwork.LocalHost) return 3; + if (network == DefaultNetwork.RoninMainnet) return 3; + if (network == DefaultNetwork.RoninTestnet) return 3; + revert("DefaultNetwork: Unknown block time"); } -function deploymentDir(DefaultNetwork defaultNetwork) pure returns (string memory) { - if (defaultNetwork == DefaultNetwork.Local) return "local/"; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return "ronin-testnet/"; - if (defaultNetwork == DefaultNetwork.RoninMainnet) return "ronin-mainnet/"; - revert("DefaultNetwork: Unknown network deployment directory"); +function explorer( + DefaultNetwork network +) pure returns (string memory link) { + if (network == DefaultNetwork.RoninMainnet) return "https://app.roninchain.com/"; + if (network == DefaultNetwork.RoninTestnet) return "https://saigon-app.roninchain.com/"; + return "https://unknown-explorer.com/"; } -function envLabel(DefaultNetwork defaultNetwork) pure returns (string memory) { - if (defaultNetwork == DefaultNetwork.Local) return "LOCAL_PK"; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return "TESTNET_PK"; - if (defaultNetwork == DefaultNetwork.RoninMainnet) return "MAINNET_PK"; - revert("DefaultNetwork: Unknown private key env label"); +function key( + DefaultNetwork network +) pure returns (TNetwork) { + return TNetwork.wrap(LibString.packOne(chainAlias(network))); } -function chainAlias(DefaultNetwork defaultNetwork) pure returns (string memory) { - if (defaultNetwork == DefaultNetwork.Local) return "local"; - if (defaultNetwork == DefaultNetwork.RoninTestnet) return "ronin-testnet"; - if (defaultNetwork == DefaultNetwork.RoninMainnet) return "ronin-mainnet"; +function chainAlias( + DefaultNetwork network +) pure returns (string memory) { + if (network == DefaultNetwork.LocalHost) return "localhost"; + if (network == DefaultNetwork.RoninTestnet) return "ronin-testnet"; + if (network == DefaultNetwork.RoninMainnet) return "ronin-mainnet"; revert("DefaultNetwork: Unknown network alias"); } diff --git a/script/utils/Helpers.sol b/script/utils/Helpers.sol new file mode 100644 index 0000000..0acb444 --- /dev/null +++ b/script/utils/Helpers.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { stdJson } from "../../dependencies/forge-std-1.9.3/src/StdJson.sol"; +import { StdStorage, stdStorage } from "../../dependencies/forge-std-1.9.3/src/StdStorage.sol"; + +import { StdStyle } from "../../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { console } from "../../dependencies/forge-std-1.9.3/src/console.sol"; + +import { JSONParserLib } from "../../dependencies/solady-0.0.228/src/utils/JSONParserLib.sol"; +import { LibString } from "../../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { LibErrorHandler } from "../libraries/LibErrorHandler.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; + +import { TContract } from "../types/TContract.sol"; + +import { EMPTY_ARGS, vm, vme } from "./Constants.sol"; + +using StdStyle for string; +using LibString for address; +using LibString for string; +using stdJson for string; +using LibErrorHandler for bool; +using JSONParserLib for string; +using JSONParserLib for JSONParserLib.Item; +using stdStorage for StdStorage; + +// // Set the balance of an account for any ERC20 token +// // Use the alternative signature to update `totalSupply` +// function deal(address token, address to, uint256 give) { +// deal(token, to, give, false); +// } + +// function deal(address token, address to, uint256 give, bool adjust) { +// // get current balance +// (, bytes memory balData) = token.staticcall(abi.encodeWithSelector(0x70a08231, to)); +// uint256 prevBal = abi.decode(balData, (uint256)); + +// // update balance +// stdstore.target(token).sig(0x70a08231).with_key(to).checked_write(give); + +// // update total supply +// if (adjust) { +// (, bytes memory totSupData) = token.staticcall(abi.encodeWithSelector(0x18160ddd)); +// uint256 totSup = abi.decode(totSupData, (uint256)); +// if (give < prevBal) { +// totSup -= (prevBal - give); +// } else { +// totSup += (give - prevBal); +// } +// stdstore.target(token).sig(0x18160ddd).checked_write(totSup); +// } +// } + +function logDecodedError( + bytes memory returnOrRevertData +) { + if (returnOrRevertData.length != 0) { + string[] memory commandInput = new string[](3); + commandInput[0] = "cast"; + commandInput[1] = returnOrRevertData.length > 4 ? "4byte-decode" : "4byte"; + commandInput[2] = vm.toString(returnOrRevertData); + bytes memory decodedError = vm.ffi(commandInput); + console.log(StdStyle.red(string.concat("Decoded Error: ", string(decodedError)))); + } +} + +function sendRawTransaction(address from, address to, uint256 gas, uint256 callValue, bytes memory callData) { + bool success; + bytes memory returnOrRevertData; + + prankOrBroadcast(from); + + (success, returnOrRevertData) = + gas == 0 ? to.call{ value: callValue }(callData) : to.call{ value: callValue, gas: gas }(callData); + + if (!success) { + if (returnOrRevertData.length != 0) logDecodedError(returnOrRevertData); + else console.log(StdStyle.red("Evm Error!")); + } +} + +function logInnerCall( + string memory fnName +) pure { + console.log("> ", fnName.blue(), "..."); +} + +function cheatBroadcast(address from, address to, uint256 callValue, bytes memory callData) { + 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(StdStyle.cyan("From:"), vm.getLabel(from)); + console.log(StdStyle.cyan("To:"), vm.getLabel(to)); + console.log(StdStyle.cyan("Value:"), vm.toString(callValue)); + console.log( + StdStyle.cyan("Raw Calldata Data (Please double check using `cast pretty-calldata {raw_bytes}`):\n"), + string.concat(" - ", vm.toString(callData)) + ); + console.log(StdStyle.cyan("Cast Decoded Call Data:"), decodedCallData); + console.log("--------------------------------------------------------------------"); + + vm.prank(from); + (bool success, bytes memory returnOrRevertData) = to.call{ value: callValue }(callData); + success.handleRevert(bytes4(callData), returnOrRevertData); +} + +function decodeData( + bytes memory data +) returns (string memory decodedData) { + string[] memory commandInputs = new string[](3); + commandInputs[0] = "cast"; + commandInputs[1] = "4byte-decode"; + commandInputs[2] = vm.toString(data); + decodedData = string(vm.ffi(commandInputs)); +} + +function loadContract(TContract contractType, bool shouldRevert) view returns (address payable contractAddr) { + try vme.getAddressFromCurrentNetwork(contractType) returns (address payable res) { + contractAddr = res; + } catch { + if (shouldRevert) { + revert(string.concat("Utils: loadContract(TContract,bool): Contract not found. ", contractType.name())); + } else { + contractAddr = payable(address(0x0)); + } + } +} + +function prankOrBroadcast( + address by +) { + if (vme.isPostChecking() || vme.isPreChecking()) vm.prank(by); + else vm.broadcast(by); +} + +function deploySharedAddress(address where, bytes memory bytecode, string memory label) { + deploySharedAddress(where, bytecode, EMPTY_ARGS, label); +} + +function deploySharedAddress(address where, bytes memory bytecode, bytes memory callData, string memory label) { + if (where.code.length == 0) { + vm.makePersistent(where); + vm.allowCheatcodes(where); + if (bytes(label).length != 0) vm.label(where, label); + deployCodeTo(EMPTY_ARGS, bytecode, callData, 0, where); + } +} + +function deployCodeTo(bytes memory creationCode, address where) { + deployCodeTo(EMPTY_ARGS, creationCode, EMPTY_ARGS, 0, where); +} + +function deployCodeTo(bytes memory creationCode, bytes memory callData, uint256 value, address where) { + deployCodeTo(EMPTY_ARGS, creationCode, callData, value, where); +} + +function deployCodeTo( + bytes memory args, + bytes memory creationCode, + bytes memory callData, + uint256 value, + address where +) { + vm.etch(where, abi.encodePacked(creationCode, args)); + (bool success, bytes memory runtimeBytecode) = where.call{ value: value }(""); + success.handleRevert(bytes4(callData), runtimeBytecode); + + vm.etch(where, runtimeBytecode); + + bytes memory revertOrRevertData; + if (callData.length != 0) { + (success, revertOrRevertData) = where.call(callData); + success.handleRevert(bytes4(callData), revertOrRevertData); + } +} + +function deployCode(string memory what, bytes memory args) returns (address addr) { + bytes memory bytecode = abi.encodePacked(vm.getCode(what), args); + + assembly ("memory-safe") { + addr := create(0, add(bytecode, 0x20), mload(bytecode)) + } + + require(addr != address(0), "Utils: deployCode(string,bytes): Deployment failed."); +} + +function deployCode(string memory what, uint256 val) returns (address addr) { + bytes memory bytecode = vm.getCode(what); + + assembly ("memory-safe") { + addr := create(val, add(bytecode, 0x20), mload(bytecode)) + } + + require(addr != address(0), "Utils: deployCode(string,uint256): Deployment failed."); +} diff --git a/soldeer.lock b/soldeer.lock new file mode 100644 index 0000000..7ab1793 --- /dev/null +++ b/soldeer.lock @@ -0,0 +1,27 @@ +[[dependencies]] +name = "forge-std" +version = "1.9.3" +source = "https://github.com/foundry-rs/forge-std/archive/refs/tags/v1.9.3.zip" +checksum = "cc61148ae1a47f2415956a95ada670501b10ebd7f65cc00eda6ea7025770f42b" +integrity = "d53734fbd7201cd9e44de730ae045ed8958dd2c7b9493c30b322f6e6c21ca8b2" + +[[dependencies]] +name = "openzeppelin" +version = "5.0.2" +source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v5.0.2.zip" +checksum = "ca49e0776066328da0087977864dfaef0d5e54a0ea9859236a7cd9ad02abb9e5" +integrity = "55fe084dbf45643d367163b4a6750cf63cabf58ac2678d5f919ab47ca4fe1f67" + +[[dependencies]] +name = "openzeppelin-v4" +version = "4.9.5" +source = "https://github.com/OpenZeppelin/openzeppelin-contracts/archive/refs/tags/v4.9.5.zip" +checksum = "fe249a9cb37b7a6664f8bfe61a9633873fbbb895e5d4c3fac78db50fd9af3a73" +integrity = "57a77366cd0ae010b93ce7a3759c7dd0ccd181a3becd599a1c57aab331fb685f" + +[[dependencies]] +name = "solady" +version = "0.0.228" +source = "https://github.com/Vectorized/solady/archive/refs/tags/v0.0.228.zip" +checksum = "90c6c562403b67338e3fa5fbad36a80b129df333d89db6759c5fd3ed15d4f0da" +integrity = "f5bcf42b85af46cb672a86c6df1674706bf59c53195d09fedb97041cb1f85410" diff --git a/src/Proxy.sol b/src/Proxy.sol deleted file mode 100644 index 2e3280a..0000000 --- a/src/Proxy.sol +++ /dev/null @@ -1,21 +0,0 @@ -// 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) { } -} diff --git a/src/RoninTransparentProxy.sol b/src/RoninTransparentProxy.sol new file mode 100644 index 0000000..a2cafcb --- /dev/null +++ b/src/RoninTransparentProxy.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +pragma solidity ^0.8.20; + +import { ERC1967Proxy } from "../dependencies/openzeppelin-5.0.2/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Utils } from "../dependencies/openzeppelin-5.0.2/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { ITransparentUpgradeableProxy } from + "../dependencies/openzeppelin-5.0.2/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +/** + * @dev Contract TransparentUpgradeableProxy from Openzeppelin v5 with the following modifications: + * - Admin is a parameter in the constructor (like previous versions) instead of being deployed + * - Let the admin get access to the proxy via `functionDelegateCall` + * - Replace _msgSender() with msg.sender + */ +contract RoninTransparentProxy is ERC1967Proxy { + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. Admin must call via + * `functionDelegateCall`. + */ + error ProxyDeniedAdminAccess(); + + /** + * @dev The caller is not the admin. + */ + error OnlyAdmin(); + + /** + * @dev + * An immutable address for the admin to avoid unnecessary SLOADs before each call + * at the expense of removing the ability to change the admin once it's set. + * This is acceptable if the admin is always a ProxyAdmin instance or similar contract + * with its own ability to transfer the permissions to another account. + */ + address private immutable _ADMIN; + + /** + * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`, + * backed by the implementation at `logic`, and optionally initialized with `data` as explained in + * {ERC1967Proxy-constructor}. + */ + constructor(address logic, address admin, bytes memory data) payable ERC1967Proxy(logic, data) { + _ADMIN = admin; + // Set the storage value and emit an event for ERC-1967 compatibility + ERC1967Utils.changeAdmin(_proxyAdmin()); + } + + /** + * @dev Calls a function from the current implementation as specified by `data`, which should be an encoded function + * call. + * + * Requirements: + * - Only the admin can call this function. + * + * Note: The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid + * triggering some unexpected logic. This is to allow the administrator to explicitly call the proxy, please consider + * reviewing the encoded data `data` and the method which is called before using this. + * + */ + function functionDelegateCall( + bytes memory data + ) external payable { + if (msg.sender != _proxyAdmin()) revert OnlyAdmin(); + + address impl = _implementation(); + + assembly ("memory-safe") { + let result := delegatecall(gas(), impl, add(data, 32), mload(data), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal virtual returns (address admin) { + return _ADMIN; + } + + /** + * @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. + */ + function _fallback() internal virtual override { + if (msg.sender == _proxyAdmin()) { + if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) revert ProxyDeniedAdminAccess(); + else _dispatchUpgradeToAndCall(); + } else { + super._fallback(); + } + } + + /** + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + function _dispatchUpgradeToAndCall() private { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} diff --git a/src/SampleProxy.sol b/src/SampleProxy.sol deleted file mode 100644 index 97682d8..0000000 --- a/src/SampleProxy.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import { Initializable } from "../lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol"; - -contract SampleProxy is Initializable { - uint256[50] private __gap; - - string internal _message; - - constructor() { - _disableInitializers(); - } - - function initialize(string calldata message) external initializer { - _message = message; - } - - function setMessage(string memory message) public { - _message = message; - } - - function getMessage() public view returns (string memory) { - return _message; - } -} diff --git a/src/TransparentProxyOZv4_9_5.sol b/src/TransparentProxyOZv4_9_5.sol new file mode 100644 index 0000000..7ed36d9 --- /dev/null +++ b/src/TransparentProxyOZv4_9_5.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +/** + * @title TransparentProxyOZv4_9_5 + * @dev A contract that acts as a proxy for transparent upgrades. + */ +contract TransparentProxyOZv4_9_5 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) payable TransparentUpgradeableProxy(logic, admin, data) { } +} diff --git a/src/mocks/ForTesting/InitializableTesting.sol b/src/mocks/ForTesting/InitializableTesting.sol new file mode 100644 index 0000000..6091d1c --- /dev/null +++ b/src/mocks/ForTesting/InitializableTesting.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; +import "../../../dependencies/openzeppelin-v4-4.9.5/contracts/utils/Address.sol"; + +abstract contract InitializableTesting { + /** + * @dev Indicates that the contract has been initialized. + * @custom:oz-retyped-from bool + */ + uint8 internal _initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool internal _initializing; + + /** + * @dev Triggered when the contract has been initialized or reinitialized. + */ + event Initialized(uint8 version); + + /** + * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope, + * `onlyInitializing` functions can be used to initialize parent contracts. + * + * Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a + * constructor. + * + * Emits an {Initialized} event. + */ + modifier initializer() { + bool isTopLevelCall = !_initializing; + require( + (isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1), + "Initializable: contract is already initialized" + ); + _initialized = 1; + if (isTopLevelCall) _initializing = true; + _; + if (isTopLevelCall) { + _initializing = false; + emit Initialized(1); + } + } + + /** + * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the + * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be + * used to initialize parent contracts. + * + * A reinitializer may be used after the original initialization step. This is essential to configure modules that + * are added through upgrades and that require initialization. + * + * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` + * cannot be nested. If one is invoked in the context of another, execution will revert. + * + * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in + * a contract, executing them in the right order is up to the developer or operator. + * + * WARNING: setting the version to 255 will prevent any future reinitialization. + * + * Emits an {Initialized} event. + */ + modifier reinitializer( + uint8 version + ) { + require(!_initializing && _initialized < version, "Initializable: contract is already initialized"); + _initialized = version; + _initializing = true; + _; + _initializing = false; + emit Initialized(version); + } + + /** + * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the + * {initializer} and {reinitializer} modifiers, directly or indirectly. + */ + modifier onlyInitializing() { + require(_initializing, "Initializable: contract is not initializing"); + _; + } + + /** + * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. + * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized + * to any version. It is recommended to use this to lock implementation contracts that are designed to be called + * through proxies. + * + * Emits an {Initialized} event the first time it is successfully executed. + */ + function _disableInitializers() internal virtual { + require(!_initializing, "Initializable: contract is initializing"); + if (_initialized != type(uint8).max) { + _initialized = type(uint8).max; + emit Initialized(type(uint8).max); + } + } + + /** + * @dev Returns the highest version that has been initialized. See {reinitializer}. + */ + function _getInitializedVersion() internal view returns (uint8) { + return _initialized; + } + + /** + * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}. + */ + function _isInitializing() internal view returns (bool) { + return _initializing; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose.sol new file mode 100644 index 0000000..44cc67d --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxyForTestingPurpose is Ownable, InitializableV5 { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + + constructor() Ownable(msg.sender) { + // _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose2.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose2.sol new file mode 100644 index 0000000..7d4b179 --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose2.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; +import "./InitializableTesting.sol"; + +contract SampleProxyForTestingPurpose2 is Ownable, InitializableTesting { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) public { + _message = message; + } + + function abcXYZ( + string calldata message + ) public { } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose3.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose3.sol new file mode 100644 index 0000000..5cd83aa --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose3.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; +import "./InitializableTesting.sol"; + +contract SampleProxyForTestingPurpose3 is Ownable, InitializableTesting { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) public initializer { + _message = message; + _initialized = type(uint8).max - 10; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose4.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose4.sol new file mode 100644 index 0000000..546c4ab --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose4.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxyForTestingPurpose4 is Ownable, Initializable { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose5.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose5.sol new file mode 100644 index 0000000..50f5e4d --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose5.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxyForTestingPurpose5 is Ownable, Initializable { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + uint256 internal _newVariable; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function initializeV2( + uint256 newValues + ) external reinitializer(2) { + _newVariable = newValues; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose6.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose6.sol new file mode 100644 index 0000000..bde2fc4 --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose6.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxyForTestingPurpose6 is Ownable, Initializable { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + uint256 internal _newVariable; + uint256 internal _newVariable2; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function initializeV2( + uint256 newValues + ) external reinitializer(2) { + _newVariable = newValues; + } + + function initializeV3( + uint256 newValues + ) external reinitializer(3) { + _newVariable2 = newValues; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/mocks/ForTesting/SampleProxyForTestingPurpose7.sol b/src/mocks/ForTesting/SampleProxyForTestingPurpose7.sol new file mode 100644 index 0000000..e87e208 --- /dev/null +++ b/src/mocks/ForTesting/SampleProxyForTestingPurpose7.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { Ownable } from "../../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxyForTestingPurpose7 is Ownable, Initializable { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + uint256 internal _newVariable; + uint256 internal _newVariable2; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function initializeV2( + uint256 newValues + ) external reinitializer(2) { + _newVariable = newValues; + } + + function initializeV3( + uint256 newValues + ) external reinitializer(3) { + _newVariable2 = newValues; + } + + function initializeV4( + uint256 newValues + ) external reinitializer(3) { + _newVariable2 = newValues; + } + + function initializeV5( + uint256 newValues + ) external reinitializer(5) { + _newVariable2 = newValues; + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/Sample.sol b/src/mocks/Sample.sol similarity index 64% rename from src/Sample.sol rename to src/mocks/Sample.sol index 5766c24..d15d50d 100644 --- a/src/Sample.sol +++ b/src/mocks/Sample.sol @@ -4,11 +4,9 @@ pragma solidity ^0.8.19; contract Sample { string internal _message; - constructor(string memory message) { - _message = message; - } - - function setMessage(string memory message) public { + function setMessage( + string memory message + ) public { _message = message; } diff --git a/src/mocks/SampleProxy.sol b/src/mocks/SampleProxy.sol new file mode 100644 index 0000000..e67507b --- /dev/null +++ b/src/mocks/SampleProxy.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Ownable } from "../../dependencies/openzeppelin-5.0.2/contracts/access/Ownable.sol"; + +import { Initializable as InitializableV5 } from + "../../dependencies/openzeppelin-5.0.2/contracts/proxy/utils/Initializable.sol"; +import { Initializable } from "../../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxy is Ownable, InitializableV5 { + uint256[50] private __gap; + + string internal _message; + address internal _addr; + + constructor() Ownable(msg.sender) { + _disableInitializers(); + } + + function initialize( + string calldata message + ) external initializer { + _message = message; + } + + function initializeV2() external reinitializer(2) { } + + function initializeV3( + address a + ) external reinitializer(3) { + _addr = a; + } + + function initializeV4() external { + _disableInitializers(); + } + + function setMessage( + string memory message + ) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/Token.sol b/src/mocks/Token.sol similarity index 72% rename from src/Token.sol rename to src/mocks/Token.sol index 0a6d9c9..27c0aab 100644 --- a/src/Token.sol +++ b/src/mocks/Token.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { ERC20 } from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from "../../dependencies/openzeppelin-5.0.2/contracts/token/ERC20/ERC20.sol"; contract Token is ERC20 { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { } diff --git a/src/WNT.sol b/src/mocks/WNT.sol similarity index 74% rename from src/WNT.sol rename to src/mocks/WNT.sol index 6331eaf..0749e73 100644 --- a/src/WNT.sol +++ b/src/mocks/WNT.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import { ERC20 } from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import { ERC20 } from "../../dependencies/openzeppelin-5.0.2/contracts/token/ERC20/ERC20.sol"; import { IWNT } from "./interfaces/IWNT.sol"; -import { LibNativeTransfer } from "contract-libs/transfers/LibNativeTransfer.sol"; /// @notice Minimalist and modern Wrapped Ether implementation. /// @author Solmate @@ -19,11 +18,14 @@ contract WNT is IWNT, ERC20 { emit Deposit(sender, msg.value); } - function withdraw(uint256 amount) public virtual { + function withdraw( + uint256 amount + ) public virtual { address sender = _msgSender(); _burn(sender, amount); emit Withdrawal(sender, amount); - LibNativeTransfer.transfer(sender, amount, 2300); + (bool success,) = sender.call{ value: amount }(""); + require(success, "WNT: Native transfer failed"); } receive() external payable virtual { diff --git a/src/interfaces/IWNT.sol b/src/mocks/interfaces/IWNT.sol similarity index 61% rename from src/interfaces/IWNT.sol rename to src/mocks/interfaces/IWNT.sol index 67c2ce7..9f84a2f 100644 --- a/src/interfaces/IWNT.sol +++ b/src/mocks/interfaces/IWNT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "../../../dependencies/openzeppelin-5.0.2/contracts/token/ERC20/IERC20.sol"; interface IWNT is IERC20 { event Deposit(address indexed from, uint256 amount); @@ -10,5 +10,7 @@ interface IWNT is IERC20 { function deposit() external payable; - function withdraw(uint256 amount) external; + function withdraw( + uint256 amount + ) external; } diff --git a/test/LibDeploy.t.sol b/test/LibDeploy.t.sol new file mode 100644 index 0000000..c86f095 --- /dev/null +++ b/test/LibDeploy.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Test } from "../dependencies/forge-std-1.9.3/src/Test.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; + +import { MockERC20 } from "../dependencies/forge-std-1.9.3/src/mocks/MockERC20.sol"; +import { MockERC721 } from "../dependencies/forge-std-1.9.3/src/mocks/MockERC721.sol"; +import { ProxyAdmin } from "../dependencies/openzeppelin-5.0.2/contracts/proxy/transparent/ProxyAdmin.sol"; +import { BaseGeneralConfig } from "script/BaseGeneralConfig.sol"; +import { DeployInfo, LibDeploy, LibProxy, ProxyInterface, UpgradeInfo } from "script/libraries/LibDeploy.sol"; +import { vme } from "script/utils/Constants.sol"; +import { RoninTransparentProxy } from "src/RoninTransparentProxy.sol"; + +contract LibDeployTest is Test { + using LibProxy for *; + + function setUp() public { + deployCodeTo("BaseGeneralConfig.sol:BaseGeneralConfig", abi.encode("", "deployments/"), 0, address(vme)); + } + + function testConcrete_Upgrade_ProxyWithAdminIsEOA() public { + address eoa = makeAddr("eoa"); + address logic = address(new MockERC721()); + vm.label(logic, "Logic"); + address proxy = address(new RoninTransparentProxy(logic, eoa, "")); + vm.label(proxy, "Proxy"); + + UpgradeInfo memory info = UpgradeInfo({ + proxy: proxy, + logic: logic, + callValue: 0, + callData: abi.encodeCall(MockERC721.initialize, ("Name", "Symbol")), + proxyInterface: ProxyInterface.Transparent, + upgradeCallback: emptyFn, + shouldPrompt: false, + shouldUseCallback: false + }); + + info.upgrade(); + } + + function testConcrete_Upgrade_ProxyWithAdminIsProxyAdmin() public { + address owner = makeAddr("owner"); + vm.prank(owner); + address proxyAdmin = address(new ProxyAdmin(owner)); + vm.label(proxyAdmin, "ProxyAdmin"); + + address logic = address(new MockERC20()); + vm.label(logic, "Logic"); + address proxy = address(new RoninTransparentProxy(logic, proxyAdmin, "")); + vm.label(proxy, "Proxy"); + + UpgradeInfo memory info = UpgradeInfo({ + proxy: proxy, + logic: logic, + callValue: 0, + callData: abi.encodeCall(MockERC20.initialize, ("Name", "Symbol", 18)), + proxyInterface: ProxyInterface.Transparent, + upgradeCallback: emptyFn, + shouldPrompt: false, + shouldUseCallback: false + }); + + info.upgrade(); + } + + function testConcrete_Upgrade_ProxyWithAdminIsMultiSig() public { + address multisig = makeAddr("multisig"); + vm.etch(multisig, type(MockERC20).runtimeCode); + + address logic = address(new MockERC20()); + vm.label(logic, "Logic"); + address proxy = address(new RoninTransparentProxy(logic, multisig, "")); + vm.label(proxy, "Proxy"); + + UpgradeInfo memory info = UpgradeInfo({ + proxy: proxy, + logic: logic, + callValue: 0, + callData: abi.encodeCall(MockERC20.initialize, ("Name", "Symbol", 18)), + proxyInterface: ProxyInterface.Transparent, + upgradeCallback: emptyFn, + shouldPrompt: false, + shouldUseCallback: false + }); + + info.upgrade(); + } + + function testConcrete_Upgrade_ProxyWithAdminIsProxyAdmin_ButOwnerOfProxyAdminIsMultisig() external { + address multisig = makeAddr("multisig"); + vm.etch(multisig, type(MockERC20).runtimeCode); + vm.prank(multisig); + address proxyAdmin = address(new ProxyAdmin(multisig)); + assertTrue(ProxyAdmin(proxyAdmin).owner() == multisig, "Owner of ProxyAdmin is not multisig"); + vm.label(proxyAdmin, "ProxyAdmin"); + + address logic = address(new MockERC20()); + vm.label(logic, "Logic"); + address proxy = address(new RoninTransparentProxy(logic, proxyAdmin, "")); + vm.label(proxy, "Proxy"); + + console.log("ProxyAdmin: ", proxy.getProxyAdmin()); + + UpgradeInfo memory info = UpgradeInfo({ + proxy: proxy, + logic: logic, + callValue: 0, + callData: abi.encodeCall(MockERC20.initialize, ("Name", "Symbol", 18)), + proxyInterface: ProxyInterface.Transparent, + upgradeCallback: emptyFn, + shouldPrompt: false, + shouldUseCallback: false + }); + + info.upgrade(); + } + + function emptyFn( + address, /* proxy */ + address, /* logic */ + uint256, /* callValue */ + bytes memory, /* callData */ + ProxyInterface /* proxyInterface */ + ) internal { } +} diff --git a/test/LibInitializeGuard.t.sol b/test/LibInitializeGuard.t.sol new file mode 100644 index 0000000..e9ba1b8 --- /dev/null +++ b/test/LibInitializeGuard.t.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { StdStyle } from "../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Test } from "../dependencies/forge-std-1.9.3/src/Test.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; + +import { Initializable } from "../dependencies/openzeppelin-v4-4.9.5/contracts/proxy/utils/Initializable.sol"; + +import { MockConfig } from "./MockConfig.sol"; +import { BaseGeneralConfig } from "script/BaseGeneralConfig.sol"; +import { BaseMigration } from "script/BaseMigration.s.sol"; +import { LibInitializeGuard } from "script/libraries/LibInitializeGuard.sol"; +import { LibProxy } from "script/libraries/LibProxy.sol"; +import { SampleProxyDeploy } from "script/sample/contracts/SampleProxyDeploy.s.sol"; +import { Vm, vme } from "script/utils/Constants.sol"; + +import { SampleProxyForTestingPurpose2 } from "src/mocks/ForTesting/SampleProxyForTestingPurpose2.sol"; +import { SampleProxyForTestingPurpose5 } from "src/mocks/ForTesting/SampleProxyForTestingPurpose5.sol"; +import { SampleProxyForTestingPurpose6 } from "src/mocks/ForTesting/SampleProxyForTestingPurpose6.sol"; +import { SampleProxyForTestingPurpose7 } from "src/mocks/ForTesting/SampleProxyForTestingPurpose7.sol"; +import { SampleProxy } from "src/mocks/SampleProxy.sol"; + +interface ITransparentUpgradeableProxy { + function upgradeTo(address) external; + function upgradeToAndCall(address, bytes memory) external payable; +} + +contract ValidateWrapper { + function runValidate(Vm.Log[] memory logs, Vm.AccountAccess[] memory stateDiffs) public { + LibInitializeGuard.validate(logs, stateDiffs); + } +} + +contract LibInitializeGuardTest is Test { + using LibProxy for address; + using StdStyle for *; + + SampleProxy _sample; + + function setUp() public { + deployCodeTo("MockConfig.sol:MockConfig", abi.encode(""), 0, address(vme)); + } + + function testConcrete_DeployProxy_ForTheFirstTime() public { + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose4"); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + _sample = new SampleProxyDeploy().run(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + LibInitializeGuard.validate(logs, stateDiffs); + } + + function testConcrete_Upgrade_WithoutInitialization() public { + testConcrete_DeployProxy_ForTheFirstTime(); + _sample = new SampleProxyDeploy().run(); + address admin = address(_sample).getProxyAdmin(); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + address newLogic = address(new SampleProxyForTestingPurpose2()); + vm.prank(admin); + ITransparentUpgradeableProxy(address(_sample)).upgradeTo(newLogic); + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose2"); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + LibInitializeGuard.validate(logs, stateDiffs); + } + + function testConcrete_UpgradeFrom_V1_To_V2_ByCallingInitializeV2() public { + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose4"); + + _sample = new SampleProxyDeploy().run(); + address admin = address(_sample).getProxyAdmin(); + bytes memory callData = abi.encodeCall(SampleProxyForTestingPurpose5.initializeV2, (100)); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + address newLogic = address(new SampleProxyForTestingPurpose5()); + vm.prank(admin); + ITransparentUpgradeableProxy(address(_sample)).upgradeToAndCall(newLogic, callData); + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose5"); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + LibInitializeGuard.validate(logs, stateDiffs); + } + + function testConcrete_UpgradeFrom_V2_To_V3_ByCallingInitializeV3() public { + testConcrete_UpgradeFrom_V1_To_V2_ByCallingInitializeV2(); + address admin = address(_sample).getProxyAdmin(); + bytes memory callData = abi.encodeCall(SampleProxyForTestingPurpose6.initializeV3, (100)); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + address newLogic = address(new SampleProxyForTestingPurpose6()); + vm.prank(admin); + ITransparentUpgradeableProxy(address(_sample)).upgradeToAndCall(newLogic, callData); + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose6"); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + LibInitializeGuard.validate(logs, stateDiffs); + } + + function testRevert_When_UpgradeFrom_V3_To_V5_ByCallingInitilizeV5() public { + testConcrete_UpgradeFrom_V2_To_V3_ByCallingInitializeV3(); + address admin = address(_sample).getProxyAdmin(); + bytes memory callData = abi.encodeCall(SampleProxyForTestingPurpose7.initializeV5, (100)); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + address newLogic = address(new SampleProxyForTestingPurpose7()); + vm.prank(admin); + ITransparentUpgradeableProxy(address(_sample)).upgradeToAndCall(newLogic, callData); + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose7"); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + ValidateWrapper _wrapper = new ValidateWrapper(); + vm.expectRevert("LibInitializeGuard: Version does not correctly increment!"); + _wrapper.runValidate(logs, stateDiffs); + } + + function testRevert_When_FoundFourInitializeFunctions_But_ActualInitVerIsOne() public { + vm.recordLogs(); + vm.startStateDiffRecording(); + + _sample = new SampleProxyDeploy().run(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + ValidateWrapper _wrapper = new ValidateWrapper(); + vm.expectRevert(bytes(string.concat("LibInitializeGuard: Invalid initialized version! Expected: 4 Got: 1"))); + _wrapper.runValidate(logs, stateDiffs); + } + + function testRevert_When_NotDisableInitializedVersion() public { + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose"); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + _sample = new SampleProxyDeploy().run(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + ValidateWrapper _wrapper = new ValidateWrapper(); + vm.expectRevert( + bytes( + string.concat( + "LibInitializeGuard: Logic ", + vm.getLabel(address(_sample).getProxyImplementation()), + " did not disable initialized version!" + ) + ) + ); + _wrapper.runValidate(logs, stateDiffs); + } + + function testRevert_When_ProxyDoesNot_Initialize() public { + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose2"); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + _sample = new SampleProxyDeploy().run(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + ValidateWrapper _wrapper = new ValidateWrapper(); + vm.expectRevert( + bytes(string.concat("LibInitializeGuard: Proxy ", vm.getLabel(address(_sample)), " does not initialize!".red())) + ); + _wrapper.runValidate(logs, stateDiffs); + } + + function testRevert_When_Does_Not_Correctly_Increment() public { + MockConfig(address(vme)).updateSampleProxyLogicForTesting("SampleProxyForTestingPurpose3"); + + vm.recordLogs(); + vm.startStateDiffRecording(); + + _sample = new SampleProxyDeploy().run(); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + Vm.AccountAccess[] memory stateDiffs = vm.stopAndReturnStateDiff(); + + ValidateWrapper _wrapper = new ValidateWrapper(); + vm.expectRevert("LibInitializeGuard: Version does not correctly increment!"); + _wrapper.runValidate(logs, stateDiffs); + } +} diff --git a/test/MockConfig.sol b/test/MockConfig.sol new file mode 100644 index 0000000..32dd3c6 --- /dev/null +++ b/test/MockConfig.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; + +import "script/sample/SampleGeneralConfig.sol"; + +contract MockConfig is SampleGeneralConfig { + function updateSampleProxyLogicForTesting( + string memory contractName + ) public { + console.log("Cheating contract logic for testing..."); + _contractNameMap[Contract.SampleProxy.key()] = contractName; + } +} diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 20d882b..0000000 --- a/yarn.lock +++ /dev/null @@ -1,355 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -ansi-escapes@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-5.0.0.tgz#b6a0caf0eef0c41af190e9a749e0c00ec04bb2a6" - integrity sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA== - dependencies: - type-fest "^1.0.2" - -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - -ansi-styles@^6.0.0, ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -chalk@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -cli-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-4.0.0.tgz#3cecfe3734bf4fe02a8361cbdc0f6fe28c6a57ea" - integrity sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== - dependencies: - restore-cursor "^4.0.0" - -cli-truncate@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" - integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== - dependencies: - slice-ansi "^5.0.0" - string-width "^5.0.0" - -colorette@^2.0.20: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-11.0.0.tgz#43e19c25dbedc8256203538e8d7e9346877a6f67" - integrity sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ== - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - -execa@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-7.2.0.tgz#657e75ba984f42a70f38928cedc87d6f2d4fe4e9" - integrity sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^4.3.0" - is-stream "^3.0.0" - merge-stream "^2.0.0" - npm-run-path "^5.1.0" - onetime "^6.0.0" - signal-exit "^3.0.7" - strip-final-newline "^3.0.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -human-signals@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-4.3.1.tgz#ab7f811e851fca97ffbd2c1fe9a958964de321b2" - integrity sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ== - -husky@^8.0.3: - version "8.0.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-8.0.3.tgz#4936d7212e46d1dea28fef29bb3a108872cd9184" - integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== - -is-fullwidth-code-point@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" - integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" - integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -lilconfig@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" - integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== - -lint-staged@^14.0.1: - version "14.0.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-14.0.1.tgz#57dfa3013a3d60762d9af5d9c83bdb51291a6232" - integrity sha512-Mw0cL6HXnHN1ag0mN/Dg4g6sr8uf8sn98w2Oc1ECtFto9tvRF7nkXGJRbx8gPlHyoR0pLyBr2lQHbWwmUHe1Sw== - dependencies: - chalk "5.3.0" - commander "11.0.0" - debug "4.3.4" - execa "7.2.0" - lilconfig "2.1.0" - listr2 "6.6.1" - micromatch "4.0.5" - pidtree "0.6.0" - string-argv "0.3.2" - yaml "2.3.1" - -listr2@6.6.1: - version "6.6.1" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-6.6.1.tgz#08b2329e7e8ba6298481464937099f4a2cd7f95d" - integrity sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg== - dependencies: - cli-truncate "^3.1.0" - colorette "^2.0.20" - eventemitter3 "^5.0.1" - log-update "^5.0.1" - rfdc "^1.3.0" - wrap-ansi "^8.1.0" - -log-update@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-5.0.1.tgz#9e928bf70cb183c1f0c9e91d9e6b7115d597ce09" - integrity sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw== - dependencies: - ansi-escapes "^5.0.0" - cli-cursor "^4.0.0" - slice-ansi "^5.0.0" - strip-ansi "^7.0.1" - wrap-ansi "^8.0.1" - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -micromatch@4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -mimic-fn@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" - integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -npm-run-path@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.1.0.tgz#bc62f7f3f6952d9894bd08944ba011a6ee7b7e00" - integrity sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q== - dependencies: - path-key "^4.0.0" - -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -onetime@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" - integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== - dependencies: - mimic-fn "^4.0.0" - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" - integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pidtree@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" - integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== - -restore-cursor@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-4.0.0.tgz#519560a4318975096def6e609d44100edaa4ccb9" - integrity sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -signal-exit@^3.0.2, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -slice-ansi@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" - integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== - dependencies: - ansi-styles "^6.0.0" - is-fullwidth-code-point "^4.0.0" - -string-argv@0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" - integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== - -string-width@^5.0.0, string-width@^5.0.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-final-newline@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" - integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -type-fest@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - -yaml@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==