diff --git a/.circleci/config.yml b/.circleci/config.yml index b2eb880f0..fad8dd331 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -214,13 +214,6 @@ jobs: just prepare-json just simulate-council # simulate again to make sure the json is still valid - simulate_eth_028: - docker: - - image: << pipeline.parameters.ci_builder_image >> - steps: - - simulate_nested: - task: "eth/028-holocene-system-config-upgrade-and-init-multi-chain" - just_simulate_ink_respected_game_type: docker: - image: << pipeline.parameters.ci_builder_image >> @@ -262,6 +255,20 @@ jobs: forge --version forge test -vvv + monorepo-integration-test: + docker: + - image: <> + steps: + - checkout + - run: + name: monorepo-integration-test + command: | + forge build + export ETH_RPC_URL=https://ethereum.publicnode.com + forge script src/fps/example/Runner.sol:Runner --sig "run(string)" lib/optimism/packages/contracts-bedrock/allocs.json --rpc-url $ETH_RPC_URL + export SUPERCHAIN_OPS_ALLOCS_PATH=./allocs.json + cd lib/optimism/packages/contracts-bedrock/ && just test-upgrade + print_versions: docker: - image: <> @@ -282,6 +289,7 @@ workflows: - forge_build - forge_fmt - forge_test + - monorepo-integration-test # RPC endpoint checks. - check_sepolia_rpc_endpoints - check_mainnet_rpc_endpoints @@ -302,5 +310,4 @@ workflows: - just_simulate_sc_rehearsal_2 - just_simulate_sc_rehearsal_4 - just_simulate_ink_respected_game_type - - simulate_eth_028 # sepolia diff --git a/lib/optimism b/lib/optimism index 57fcd5d54..0ffb64a90 160000 --- a/lib/optimism +++ b/lib/optimism @@ -1 +1 @@ -Subproject commit 57fcd5d5492b0645d111de52cf5766809333b7a9 +Subproject commit 0ffb64a907517801ef706a0d31f03ae47b2adce7 diff --git a/script/verification/HoloceneSystemConfigUpgrade.s.sol b/script/verification/HoloceneSystemConfigUpgrade.s.sol index 0e45d6c8c..0bc490111 100644 --- a/script/verification/HoloceneSystemConfigUpgrade.s.sol +++ b/script/verification/HoloceneSystemConfigUpgrade.s.sol @@ -72,7 +72,10 @@ contract HoloceneSystemConfigUpgrade is SuperchainRegistry, VerificationBase { // Read target version from SCR @ specified release targetVersion = standardVersions.SystemConfig.version; - if (sysCfg.version().eq("2.3.0") || sysCfg.version().eq("2.2.0")) { + if ( + sysCfg.version().eq("2.3.0") || sysCfg.version().eq("2.2.0") + || sysCfg.version().eq("2.2.0+max-gas-limit-400M") + ) { // Supported initial versions with a getter already targetDGF = sysCfg.disputeGameFactory(); } else if (sysCfg.version().eq("1.12.0")) { @@ -95,7 +98,9 @@ contract HoloceneSystemConfigUpgrade is SuperchainRegistry, VerificationBase { } function _addCodeExceptions() internal { - if (block.chainid != 1) addCodeException(previous.owner); + if (previous.owner.code.length == 0) { + addCodeException(previous.owner); + } addCodeException(address(uint160(uint256(previous.batcherHash)))); addCodeException(previous.unsafeBlockSigner); addCodeException(previous.batchInbox); diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/.env b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/.env new file mode 100644 index 000000000..770aa029b --- /dev/null +++ b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/.env @@ -0,0 +1,5 @@ +ETH_RPC_URL="https://ethereum.publicnode.com" +COUNCIL_SAFE=0x348eebA2609645182872CCd75256D873e689fCFf +PREVIOUS_OWNER=0x6Da310CC1f80EcCa6618D80c5040049dED25B6b9 +SIGNER_TO_REMOVE=0xa5A6A342daCE967d152510e2f1F492d543A93eB3 +SAFE_NONCE="" diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/README.md b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/README.md new file mode 100644 index 000000000..901e0a056 --- /dev/null +++ b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/README.md @@ -0,0 +1,253 @@ +# Rehearsal 2 - Signer Removal + +## Objective + +In this rehearsal we will be removing one of the owners of the Safe. + +Once completed: +- There will be 1 fewer owner +- The threshold should be unchanged + +The call that will be executed by the Safe contract is defined in a +json file. This will be the standard approach for all transactions. + +Note that no onchain actions will be taking place during this +signing. You won’t be submitting a transaction and your address +doesn’t even need to be funded. These are offchain signatures produced +with your wallet which will be collected by a Facilitator will execute +the contract, submitting all signatures for its execution. + +Execution can be finalized by anyone once a threshold of signatures +are collected, so a Facilitator will do the final execution for +convenience. + +## Approving the transaction + +### 1. Update repo and move to the appropriate folder for this rehearsal task: + +``` +cd superchain-ops +git pull +just install +cd security-council-rehearsals/2025-01-19-r2-remove-signer-250119 +``` + +### 2. Setup Ledger + +Your Ledger needs to be connected and unlocked. The Ethereum +application needs to be opened on Ledger with the message “Application +is ready”. + +### 3. Simulate and validate the transaction + +Make sure your ledger is still unlocked and run the following. + +Remember that by default `just` is running with the address derived +from `/0` (first nonce). If you wish to use a different account, run +`just simulate [X]`, where X is the derivation path of the address +that you want to use. + +``` shell +just simulate +``` + +You will see a "Simulation link" from the output. + +Paste this URL in your browser. A prompt may ask you to choose a +project, any project will do. You can create one if necessary. + +Click "Simulate Transaction". + +We will be performing 3 validations and ensure the domain hash and +message hash are the same between the Tenderly simulation and your +Ledger: + +1. Validate integrity of the simulation. +2. Validate correctness of the state diff. +3. Validate and extract domain hash and message hash to approve. + +#### 3.1. Validate integrity of the simulation. + +Make sure you are on the "Overview" tab of the tenderly simulation, to +validate integrity of the simulation, we need to + +1. "Network": Check the network is Ethereum Mainnet. +2. "Timestamp": Check the simulation is performed on a block with a + recent timestamp (i.e. close to when you run the script). +3. "Sender": Check the address shown is your signer account. If not, + you will need to determine which “number” it is in the list of + addresses on your ledger. By default the script will assume the + derivation path is m/44'/60'/0'/0/0. By calling the script with + `just simulate 1` it will derive the address using + m/44'/60'/1'/0/0 instead. + +Here is an example screenshot, note that the Timestamp and Sender +might be different in your simulation: + +![](./images/tenderly-overview-network.png) + +#### 3.2. Validate correctness of the state diff. + +Now click on the "State" tab. Verify that: + +1. The rehearsal Safe's `ownerCount` is reduced by one, and a signer + is being removed from the `owners` mapping. +2. You will see a `threshold` change from `1` to a new value. This + only exists in the simulation and is safe to ignore as long as the + new value is equal to the actual threshold of the Safe. +3. There are no other significant state changes except for 2 nonce + changes from the Safe and the signer address. +4. You will see a state override (not a state change). This is + expected and its purpose is to generate a successful Safe execution + simulation without collecting any signatures. + +Here is an example screenshot. Note that the addresses may be +different: + +![](./images/tenderly-r2-state-changes.png) + +#### 3.3. Extract the domain hash and the message hash to approve. + +Now that we have verified the transaction performs the right +operation, we need to extract the domain hash and the message hash to +approve. + +Go back to the "Overview" tab, and find the first +`GnosisSafe.domainSeparator` call. This call's return value will be +the domain hash that will show up in your Ledger. + +Here is an example screenshot. Note that the hash value may be +different: + +![](./images/tenderly-hashes-1.png) + +Right before the `GnosisSafe.domainSeparator` call, you will see a +call to `GnosisSafe.encodeTransactionData`. Its return value will be a +concatenation of `0x1901`, the domain hash, and the message hash: +`0x1901[domain hash][message hash]`. + +Here is an example screenshot. Note that the hash value may be +different: + +![](./images/tenderly-hashes-2.png) + +Note down both the domain hash and the message hash. You will need to +compare them with the ones displayed on the Ledger screen at signing. + +### 4. Approve the signature on your ledger + +Once the validations are done, it's time to actually sign the +transaction. Make sure your ledger is still unlocked and run the +following: + +``` shell +just sign # or just sign +``` + +> [!IMPORTANT] This is the most security critical part of the +> playbook: make sure the domain hash and message hash in the +> following two places match: + +1. on your Ledger screen. +2. in the Tenderly simulation. You should use the same Tenderly + simulation as the one you used to verify the state diffs, instead + of opening the new one printed in the console. + +There is no need to verify anything printed in the console. There is +no need to open the new Tenderly simulation link either. + +After verification, sign the transaction. You will see the `Data`, +`Signer` and `Signature` printed in the console. Format should be +something like this: + +``` +Data: +Signer:
+Signature: +``` + +Double check the signer address is the right one. + +### 5. Send the output to Facilitator(s) + +Nothing has occurred onchain - these are offchain signatures which +will be collected by Facilitators for execution. Execution can occur +by anyone once a threshold of signatures are collected, so a +Facilitator will do the final execution for convenience. + +Format should be something like this: + +``` +Data: +Signer:
+Signature: +``` + +Share the `Data`, `Signer` and `Signature` with the Facilitator, and +congrats, you are done! + +## [For Facilitator ONLY] How to prepare and execute the rehearsal + +### [Before the rehearsal] Prepare the rehearsal + +#### 1. Update .env file + +1. Set the `COUNCIL_SAFE` address in `.env` to the same one used in + `r1-hello-council`. +2. The `removeOwner` method on the multisig requires three parameters: + the `preOwner` address, the `owner` address to remove, and the new + `_threshold`. To find out the right `preOwner` address, run `just + get-owners` to get the list of owners. The address before the owner + to remove should be the right `preOwner` to use in the call. If the + owner to remove is the first one in the list, use + `0x0000000000000000000000000000000000000001`. +3. Update the `SIGNER_TO_REMOVE` and `PREVIOUS_OWNER` addresses in + `.env`. +4. Run `just prepare`. +5. Test the newly created rehearsal by following the security council + steps in the `Approving the transaction` section above. +6. Update the rehearsal folder name in the `1. Update repo and move to + the appropriate folder for this rehearsal task` section and the + addresses in the `3.2. Validate correctness of the state diff` + section above. +7. Commit the newly created files to Github. + +#### 2. Update input.json + +1. Run `just prepare-json` to update the `input.json` file. +2. Test the newly created rehearsal by following the security council + steps in the `Approving the transaction` section above. +3. Update the rehearsal folder name in the `1. Update repo and move to + the appropriate folder for this rehearsal task` section and the + address in the `3.2. Validate correctness of the state diff` + section above. +4. Commit the newly created files to Github. + +### [After the rehearsal] Execute the output + +1. Collect outputs from all participating signers. +2. Concatenate all signatures and export it as the `SIGNATURES` + environment variable, i.e. `export + SIGNATURES="0x[SIGNATURE1][SIGNATURE2]..."`. +3. Run `just execute 0 # or 1 or ...` to execute the transaction onchain. + +For example, if the quorum is 2 and you get the following outputs: + +``` shell +Data: 0xDEADBEEF +Signer: 0xC0FFEE01 +Signature: AAAA +``` + +``` shell +Data: 0xDEADBEEF +Signer: 0xC0FFEE02 +Signature: BBBB +``` + +Then you should run + +``` shell +export SIGNATURES="0xAAAABBBB" +just execute 0 # or 1 or ... +``` diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/SignFromJson.s.sol b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/SignFromJson.s.sol new file mode 100644 index 000000000..0a4b65fe7 --- /dev/null +++ b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/SignFromJson.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.15; + +// This import path should be relative to where the rehearsal script will be located after a new rehearsal is setup, +// which is one level above the current location in +// repo_root/security-council-rehearsals//SignFromJson.s.sol +import {SignFromJson as OriginalSignFromJson} from "../../script/SignFromJson.s.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {Vm, VmSafe} from "forge-std/Vm.sol"; + +contract SignFromJson is OriginalSignFromJson { + // Since after _postCheck hook `require(false)`, the transaction will revert + // contract extending SignFromJson must implement its own _postCheck method, thus enforcing a more robust implementation pattern. + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory simPayload) + internal + virtual + override + { + // Empty implementation for CI tasks + accesses; // Silences compiler warnings. + simPayload; + } +} diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-1.png b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-1.png new file mode 100644 index 000000000..7c0690509 Binary files /dev/null and b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-1.png differ diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-2.png b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-2.png new file mode 100644 index 000000000..eb0643c70 Binary files /dev/null and b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-hashes-2.png differ diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-overview-network.png b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-overview-network.png new file mode 100644 index 000000000..4c8c648c2 Binary files /dev/null and b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-overview-network.png differ diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-r2-state-changes.png b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-r2-state-changes.png new file mode 100644 index 000000000..2310ed2cc Binary files /dev/null and b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-r2-state-changes.png differ diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-state-changes.png b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-state-changes.png new file mode 100644 index 000000000..1333d079b Binary files /dev/null and b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/images/tenderly-state-changes.png differ diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/input.json b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/input.json new file mode 100644 index 000000000..6992fedf2 --- /dev/null +++ b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/input.json @@ -0,0 +1,42 @@ +{ + "version": "1.0", + "chainId": "1", + "meta": { + "name": "Signer Removal", + "description": "", + "txBuilderVersion": "1.16.3" + }, + "transactions": [ + { + "to": "0x348eebA2609645182872CCd75256D873e689fCFf", + "value": "0", + "data": "0xf8dc5dd90000000000000000000000006da310cc1f80ecca6618d80c5040049ded25b6b9000000000000000000000000a5a6a342dace967d152510e2f1f492d543a93eb30000000000000000000000000000000000000000000000000000000000000005", + "contractMethod": { + "inputs": [ + { + "internalType": "address", + "name": "prevOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_threshold", + "type": "uint256" + } + ], + "name": "removeOwner", + "payable": false + }, + "contractInputsValues": { + "prevOwner": "0x6Da310CC1f80EcCa6618D80c5040049dED25B6b9", + "owner": "0xa5A6A342daCE967d152510e2f1F492d543A93eB3", + "_threshold": "5" + } + } + ] +} diff --git a/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/justfile b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/justfile new file mode 100644 index 000000000..89e418b45 --- /dev/null +++ b/security-council-rehearsals/2025-01-19-r2-remove-signer-250119/justfile @@ -0,0 +1,62 @@ +set dotenv-load + +export rpcUrl := env_var('ETH_RPC_URL') +export signatures := env_var_or_default('SIGNATURES', '') +export bundle := "input" +export location := `pwd` +export OWNER_SAFE := env_var('COUNCIL_SAFE') + +simulate hdPath='0': + #!/usr/bin/env bash + echo "getting signer address..." + if [ t"$SIMULATE_WITHOUT_LEDGER" == t1 ] + then + signer="0xc49d46E3F834f5C9856A7FcBEB185baD052c98e7" + else + signer=$(cast wallet address --ledger --mnemonic-derivation-path "m/44'/60'/{{hdPath}}'/0/0") + fi + echo "Simulating with: ${signer}" + echo "" + forge build + forge script ./SignFromJson.s.sol:SignFromJson \ + --rpc-url ${rpcUrl} \ + --sig "signJson(string)" \ + "${location}/${bundle}.json" \ + --sender ${signer} + +sign hdPath='0': + #!/usr/bin/env bash + echo "getting signer address..." + signer=$(cast wallet address --ledger --mnemonic-derivation-path "m/44'/60'/{{hdPath}}'/0/0") + echo "Signing with: ${signer}" + echo "" + forge build + $(git rev-parse --show-toplevel)/bin/eip712sign --ledger --hd-paths "m/44'/60'/{{hdPath}}'/0/0" -- \ + forge script ./SignFromJson.s.sol:SignFromJson \ + --rpc-url ${rpcUrl} \ + --sig "signJson(string)" \ + "${location}/${bundle}.json" + +execute hdPath='0': + #!/usr/bin/env bash + sender=$(cast wallet address --ledger --mnemonic-derivation-path "m/44'/60'/{{hdPath}}'/0/0") + forge build + forge script --fork-url ${ETH_RPC_URL} ./SignFromJson.s.sol:SignFromJson \ + --sig "runJson(string,bytes)" "${location}/${bundle}.json" ${SIGNATURES} \ + --ledger --hd-paths "m/44'/60'/{{hdPath}}'/0/0" --broadcast \ + --sender ${sender} + +get-owners: + #!/usr/bin/env bash + cast call -r ${rpcUrl} ${COUNCIL_SAFE} "getOwners()(address[])" + +prepare: + #!/usr/bin/env bash + threshold=$(cast call -r ${rpcUrl} ${COUNCIL_SAFE} "getThreshold()(uint256)") + echo "Safe: ${COUNCIL_SAFE}, Threshold: ${threshold}." + jq ".transactions[0].to |= \"${COUNCIL_SAFE}\"" ./input.json > ./input.tmp && mv input.tmp input.json + jq ".transactions[0].contractInputsValues.prevOwner |= \"${PREVIOUS_OWNER}\"" ./input.json > ./input.tmp && mv input.tmp input.json + jq ".transactions[0].contractInputsValues.owner |= \"${SIGNER_TO_REMOVE}\"" ./input.json > ./input.tmp && mv input.tmp input.json + jq ".transactions[0].contractInputsValues._threshold |= \"${threshold}\"" ./input.json > ./input.tmp && mv input.tmp input.json + calldata=$(cast calldata "removeOwner(address,address,uint256)" ${PREVIOUS_OWNER} ${SIGNER_TO_REMOVE} ${threshold}) + jq ".transactions[0].data |= \"${calldata}\"" ./input.json > ./input.tmp && mv input.tmp input.json diff --git a/src/fps/AddressRegistry.sol b/src/fps/AddressRegistry.sol index bb0f2875a..9e9bcf969 100644 --- a/src/fps/AddressRegistry.sol +++ b/src/fps/AddressRegistry.sol @@ -58,9 +58,17 @@ contract AddressRegistry is IAddressRegistry, Test { "Unsupported network" ); - bytes memory chainListContent = vm.parseToml(vm.readFile(networkConfigFilePath), ".l2chains"); + bytes memory chainListContent; + try vm.parseToml(vm.readFile(networkConfigFilePath), ".l2chains") returns (bytes memory parsedChainListContent) + { + chainListContent = parsedChainListContent; + } catch { + revert(string.concat("Failed to parse network config file path: ", networkConfigFilePath)); + } + chains = abi.decode(chainListContent, (ChainInfo[])); + /// should never revert string memory chainAddressesContent = vm.readFile(SUPERCHAIN_REGISTRY_PATH); for (uint256 i = 0; i < chains.length; i++) { diff --git a/src/fps/FPS.md b/src/fps/FPS.md deleted file mode 100644 index c78d07ebc..000000000 --- a/src/fps/FPS.md +++ /dev/null @@ -1,51 +0,0 @@ -# Overview - -The forge proposal simulator was created to solve the problem of testing and validating governance proposals before and after they land onchain. With support for Gnosis Safes, Timelocks and Governor Bravo this tooling can be adapted to most use cases. The simulator allows developers to write tasks that simulate the state changes that would occur onchain if the proposal were to be executed. The simulator as installed in the superchain-ops repo can be run against any mainnet, sepolia, and devnet. - -The goal of using FPS for the superchain ops repo is to greatly simplify task development, increase security by reducing errors, reduce sharp edges, and speed development and review of tasks. The simulation is designed to simulate task runs, with all onchain state changes being run locally. - -## Task Development and Templates - -Installing FPS allows developers to create tasks without writing any Solidity code as long as a predefined template is used. The templates are designed to be as flexible as possible, allowing developers to create tasks that update multiple parameters for a single or super chain. Templates are currently in development and will be released in a future PR. - -# Task Configuration File - -Each task will have at least 2 config files. - -The first file is `taskConfig.toml` which contains the task configuration. - -## Task Fields - -- `safeAddressString` - the identifier of the safe in the task to run -- `allowedStorageAccesses` - the identifiers of the storage writes that are allowed for the task. if empty, no storage writes are allowed -- `name` - the name of the task, including the number of the task -- `description` - what the task does - -# Network Configuration File - -The second file is `Config.toml` which contains the network configurations to modify on L1. This file is used to configure which L2 network contracts will be used for the task on the given network. Task developers can specify up to three separate types of configuration files: - -- `mainnetConfig.toml` - the mainnet configuration file, which specifies which L2 contracts will be affected on L1 mainnet -- `sepoliaConfig.toml` - the sepolia configuration file, which specifies which L2 contracts will be affected on L1 sepolia -- `devnetConfig.toml` - the devnet configuration file, which specifies which L2 contracts will be affected on L1 devnet (sepolia testnet) - -## Network Fields - -The network configuration file contains the following fields. - -### L2Chains - -```toml -# L2Chains is a list of the L2 chains that the task will interact with -l2chains = [{"name": "Orderly", "chainId": 291}, {"name": "Metal", "chainId": 1750}, {"name": OP Mainnet", "chainId": 10}] - -# Nonce for the gnosis safe that will be used for the task -safeNonce = 67 - -# Whether the safe is nested -isNestedSafe = false -``` - -### Task Template Files - -TBD, will come in another PR diff --git a/src/fps/README.md b/src/fps/README.md new file mode 100644 index 000000000..2eb2330c7 --- /dev/null +++ b/src/fps/README.md @@ -0,0 +1,63 @@ +# Overview + +This new calldata simulation tooling allows developers to write tasks that simulate the state changes that would occur onchain if the task were to be executed. The simulator can be run against any mainnet, sepolia, and devnet. + +The goal of using this new task tooling for the superchain ops repo is to greatly simplify task development, increase security by reducing errors, reduce sharp edges, and speed development and review of tasks. The simulation is designed to simulate task runs, with all onchain state changes being run locally. + +## Task Development and Templates + +Developers can now create tasks without writing any Solidity code as long as a predefined template is used. The templates are designed to be as flexible as possible, allowing developers to create tasks that update multiple parameters for a single or super chain. Templates are currently in development and will be released in a future PR. + +# Task Configuration File + +The template configuration file is `Config.toml` which contains the network configurations to modify on L1. This file is used to configure which L2 network contracts will be used for the task on the given network. Task developers can specify up to three separate types of configuration files: + +- `mainnetConfig.toml` - the mainnet configuration file, which specifies which L2 contracts will be affected on L1 mainnet +- `sepoliaConfig.toml` - the sepolia configuration file, which specifies which L2 contracts will be affected on L1 sepolia +- `devnetConfig.toml` - the devnet configuration file, which specifies which L2 contracts will be affected on L1 devnet (sepolia testnet) + +### L2Chains + +```toml +# L2Chains is a list of the L2 chains that the task will interact with +l2chains = [{"name": "Orderly", "chainId": 291}, {"name": "Metal", "chainId": 1750}, {"name": OP Mainnet", "chainId": 10}] +``` + +### Task Template Files + +Task templates allow task developers to create new tasks without writing any Solidity code. The templates are designed to be as flexible as possible, allowing developers to create tasks that update multiple parameters for a single or superchain. Templates are currently in development and will be released in a future PR. + +The mainnet configuration file for task template example 00 can be found here: [task-00/mainnetConfig.toml](./example/task-00/mainnetConfig.toml) + +```toml +[gasConfigs] +gasLimits = [{chainId = 291, gasLimit = 100000000}, {chainId = 1750, gasLimit = 100000000}] +``` + +This toml configuration file allows task developers to set gas limits for the task. After the changes are applied in the simulation, validations are run to ensure that the new values are the expected values. + +### Running Example Tasks + +#### Template 00 to set gas configs: + +```bash +forge script src/fps/example/template/GasConfigTemplate.sol --sig "run(string)" src/fps/example/task-00/mainnetConfig.toml --rpc-url mainnet -vvv +``` + +#### Template 01 to set dispute game upgrade: + +```bash +forge script src/fps/example/template/DisputeGameUpgradeTemplate.sol --sig "run(string)" src/fps/example/task-01/mainnetConfig.toml --rpc-url mainnet -vvv +``` + +#### Template 02 to set respected game type: + +```bash +forge script src/fps/example/template/SetGameTypeTemplate.sol --sig "run(string)" src/fps/example/task-02/mainnetConfig.toml --rpc-url mainnet -vvvvv +``` + +#### Template 03 to set gas config: + +```bash +forge script src/fps/example/template/GasConfigTemplate.sol --sig "run(string)" src/fps/example/task-03/mainnetConfig.toml --rpc-url mainnet -vvvvv +``` diff --git a/src/fps/doc/ADDRESS_REGISTRY.md b/src/fps/doc/ADDRESS_REGISTRY.md index 3c8196fba..979f9ecc7 100644 --- a/src/fps/doc/ADDRESS_REGISTRY.md +++ b/src/fps/doc/ADDRESS_REGISTRY.md @@ -1,13 +1,35 @@ # Address Registry -The address registry contract stores contract addresses on a single network. On construction, it reads in all of the configurations from the specified TOML configuration file. This TOML configuration file tells the address registry which L2 contracts to read in and store. As an example, if a task only touched the OP Mainnet contracts, the TOML file would only have a single entry +The address registry contract stores contract addresses on a single network. On construction, it reads in all of the configurations from the specified TOML configuration file. This TOML configuration file tells the address registry which L2 contracts to read in and store. As an example, if a task only touched the OP Mainnet contracts, the TOML file would only have a single entry: ```toml -[[chains]] - name = "OP Mainnet" - chain_id = 10 +l2chains = [{name = "OP Mainnet", chainId = 10}] ``` ## Usage -Addresses can be fetched by calling the `getAddress(string memory identifier, uint256 l2ChainId)` function. This function will return the address of the contract with the given identifier on the given chain. If the contract does not exist, the function will revert. If the l2ChainId is unsupported by this address registry instance, the function will revert. +The Address Registry provides several methods to interact with stored addresses: + +### getAddress +```solidity +function getAddress(string memory identifier, uint256 l2ChainId) external view returns (address) +``` +Returns the address associated with the given identifier on the specified chain. If the contract does not exist, the function will revert. If the l2ChainId is unsupported by this address registry instance, the function will revert. + +### isAddressContract +```solidity +function isAddressContract(string memory identifier, uint256 l2ChainId) external view returns (bool) +``` +Returns true if the address associated with the given identifier is a contract on the specified chain. + +### isAddressRegistered +```solidity +function isAddressRegistered(string memory identifier, uint256 l2ChainId) external view returns (bool) +``` +Returns true if an address exists for the given identifier on the specified chain. + +### getChains +```solidity +function getChains() external view returns (ChainInfo[] memory) +``` +Returns an array of supported chains and their configurations. diff --git a/src/fps/doc/CALLDATA_DIFFERENCE.md b/src/fps/doc/CALLDATA_DIFFERENCE.md new file mode 100644 index 000000000..b2df0d047 --- /dev/null +++ b/src/fps/doc/CALLDATA_DIFFERENCE.md @@ -0,0 +1,62 @@ +# Overview + +With this task tooling upgrade, we want to make sure that the calldata and hashes generated by the new task simulation tooling are the exact same as the calldata and hashes generated by the current tooling. + +Testing a task where the gas targets are set to 60,000,000, which is a single call yielded different calldata. This difference in calldata is due to the fact that the current tooling uses the `aggregate3()` function on the multicall contract, while the new tooling uses the `aggregate3Value()` function on the multicall contract. This enables the new task tooling to support value being sent in a task. + +## Current Tooling + +### Calldata Sent From Safe: +``` +0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000229047fed2591dbec1ef1118d64f7af3db9eb290000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b40a817c000000000000000000000000000000000000000000000000000000000393870000000000000000000000000000000000000000000000000000000000 +``` + +## Function Selector +```solidity +aggregate3((address,bool,bytes)[]) +``` + +### Function Parameters + +target: `0x229047fed2591dbec1eF1118d64F7aF3dB9EB290` +allowFailure: false +data: `0xb40a817c0000000000000000000000000000000000000000000000000000000003938700`, calls setGasLimit to 60,000,000 + +`nonce = 14` + +### Command + +**note**: you must comment out line 36 in task [file](../../../tasks/eth/020-set-gas-target/SignFromJson.s.sol) + +```bash +cd tasks/eth/020-set-gas-target ; SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../single.just simulate +``` + +## New Tooling + +### Calldata Sent From Safe: +``` +0x174dea71000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000229047fed2591dbec1ef1118d64f7af3db9eb2900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000024b40a817c000000000000000000000000000000000000000000000000000000000393870000000000000000000000000000000000000000000000000000000000 +``` + +### Multicall Function Selector +```solidity +aggregate3Value((address,bool,uint256,bytes)[]) +``` + +### Function Parameters + +target: `0x229047fed2591dbec1eF1118d64F7aF3dB9EB290` +allowFailure: false +value: 0 +data: `0xb40a817c0000000000000000000000000000000000000000000000000000000003938700`, calls setGasLimit to 60,000,000 + + +`nonce = 14` + +command: +```bash +forge script src/fps/example/template/GasConfigTemplate.sol --sig "run(string)" src/fps/example/task-03/mainnetConfig.toml --rpc-url mainnet -vvv +``` + +Calldata diff can be found here https://www.diffchecker.com/HA7YFSWi/, select character comparison to see the difference. \ No newline at end of file diff --git a/src/fps/example/Runner.sol b/src/fps/example/Runner.sol new file mode 100644 index 000000000..d61024c31 --- /dev/null +++ b/src/fps/example/Runner.sol @@ -0,0 +1,40 @@ +pragma solidity 0.8.15; + +import {Script} from "forge-std/Script.sol"; +import {ITask} from "src/fps/task/ITask.sol"; + +contract Runner is Script { + string public network; + + struct TasksStatus { + string contractName; + string name; + string path; + uint256 status; + } + + function run() public { + string memory runnerConfigFileContents = vm.readFile("src/fps/example/runnerConfig.toml"); + TasksStatus[] memory tasksStatuses = + abi.decode(vm.parseToml(runnerConfigFileContents, ".tasks"), (TasksStatus[])); + if (block.chainid == 1) { + network = "mainnet"; + } else if (block.chainid == 11155111) { + network = "testnet"; + } else { + revert("Unsupported network"); + } + for (uint256 i = 0; i < tasksStatuses.length; i++) { + TasksStatus memory taskStatus = tasksStatuses[i]; + if (taskStatus.status >= 1 && taskStatus.status <= 3) { + string memory taskConfigFilePath = string.concat(taskStatus.path, "/", network, "Config.toml"); + ITask(deployCode(taskStatus.contractName)).run(taskConfigFilePath); + } + } + } + + function run(string memory dumpStatePath) public { + run(); + vm.dumpState(dumpStatePath); + } +} diff --git a/src/fps/example/runnerConfig.toml b/src/fps/example/runnerConfig.toml new file mode 100644 index 000000000..38eda66cc --- /dev/null +++ b/src/fps/example/runnerConfig.toml @@ -0,0 +1,31 @@ +[status] +draft = 0 +ready = 1 +contingency = 2 +signed = 3 +executed = 4 +cancelled = 5 + +[[tasks]] +name = "task-00" +status = 1 +path = "src/fps/example/task-00" +contractName = "GasConfigTemplate.sol:GasConfigTemplate" + +[[tasks]] +name = "task-01" +status = 1 +path = "src/fps/example/task-01" +contractName = "DisputeGameUpgradeTemplate.sol:DisputeGameUpgradeTemplate" + +[[tasks]] +name = "task-02" +status = 1 +path = "src/fps/example/task-02" +contractName = "SetGameTypeTemplate.sol:SetGameTypeTemplate" + +[[tasks]] +name = "task-03" +status = 1 +path = "src/fps/example/task-03" +contractName = "GasConfigTemplate.sol:GasConfigTemplate" \ No newline at end of file diff --git a/src/fps/example/mainnetConfig.toml b/src/fps/example/task-00/mainnetConfig.toml similarity index 50% rename from src/fps/example/mainnetConfig.toml rename to src/fps/example/task-00/mainnetConfig.toml index 46902a631..ed8ced0bc 100644 --- a/src/fps/example/mainnetConfig.toml +++ b/src/fps/example/task-00/mainnetConfig.toml @@ -1,4 +1,6 @@ # this is the file used to determine the network configuration -safeNonce = 67 -isNestedSafe = false -l2chains = [{name = "Orderly", chainId = 291}, {name = "Metal", chainId = 1750}] \ No newline at end of file + +l2chains = [{name = "Orderly", chainId = 291}, {name = "Metal", chainId = 1750}] + +[gasConfigs] +gasLimits = [{chainId = 291, gasLimit = 100000000}, {chainId = 1750, gasLimit = 100000000}] \ No newline at end of file diff --git a/src/fps/example/task-00/testnetConfig.toml b/src/fps/example/task-00/testnetConfig.toml new file mode 100644 index 000000000..138490772 --- /dev/null +++ b/src/fps/example/task-00/testnetConfig.toml @@ -0,0 +1,5 @@ +# this is the file used to determine the network configuration for the testnet task +l2chains = [{"name": "Mode Testnet", "chainId": 919}, {"name": "Metal L2 Testnet", "chainId": 1740}, {"name": "Zora Sepolia Testnet", "chainId": 999999999}] + +[gasConfigs] +gasLimits = [{chainId = 919, gasLimit = 100000000}, {chainId = 1740, gasLimit = 100000000}, {chainId = 999999999, gasLimit = 100000000}] \ No newline at end of file diff --git a/src/fps/example/task-01/mainnetConfig.toml b/src/fps/example/task-01/mainnetConfig.toml new file mode 100644 index 000000000..95bbde840 --- /dev/null +++ b/src/fps/example/task-01/mainnetConfig.toml @@ -0,0 +1,5 @@ +# this is the file used to determine the network configuration + +l2chains = [{name = "OP Mainnet", chainId = 10}] + +implementations = [{gameType = 0, implementation = "0xf691F8A6d908B58C534B624cF16495b491E633BA", l2ChainId = 10}] diff --git a/src/fps/example/task-02/mainnetConfig.toml b/src/fps/example/task-02/mainnetConfig.toml new file mode 100644 index 000000000..4abe9b3e5 --- /dev/null +++ b/src/fps/example/task-02/mainnetConfig.toml @@ -0,0 +1,3 @@ +l2chains = [{name = "OP Mainnet", chainId = 10}] + +respectedGameTypes = [{deputyGuardian = "0xc6901F65369FC59fC1B4D6D6bE7A2318Ff38dB5B", gameType = 1, l2ChainId = 10, portal = "OptimismPortalProxy"}] \ No newline at end of file diff --git a/src/fps/example/task-03/mainnetConfig.toml b/src/fps/example/task-03/mainnetConfig.toml new file mode 100644 index 000000000..2d4f1b342 --- /dev/null +++ b/src/fps/example/task-03/mainnetConfig.toml @@ -0,0 +1,6 @@ +# this is the file used to determine the network configuration + +l2chains = [{name = "Optimism Mainnet", chainId = 10}] + +[gasConfigs] +gasLimits = [{chainId = 10, gasLimit = 60000000}] \ No newline at end of file diff --git a/src/fps/example/taskConfig.toml b/src/fps/example/taskConfig.toml deleted file mode 100644 index 17ecbab11..000000000 --- a/src/fps/example/taskConfig.toml +++ /dev/null @@ -1,3 +0,0 @@ -[task] -allowedStorageAccesses = ["SystemConfigProxy"] -safeAddressString = "SystemConfigOwner" diff --git a/src/fps/example/template/DisputeGameUpgradeTemplate.sol b/src/fps/example/template/DisputeGameUpgradeTemplate.sol new file mode 100644 index 000000000..547744ab2 --- /dev/null +++ b/src/fps/example/template/DisputeGameUpgradeTemplate.sol @@ -0,0 +1,79 @@ +pragma solidity 0.8.15; + +import {IDisputeGameFactory, IDisputeGame} from "@eth-optimism-bedrock/interfaces/dispute/IDisputeGameFactory.sol"; +import {SystemConfig} from "@eth-optimism-bedrock/src/L1/SystemConfig.sol"; +import "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; + +import {MultisigTask} from "src/fps/task/MultisigTask.sol"; +import {AddressRegistry as Addresses} from "src/fps/AddressRegistry.sol"; + +/// @title DisputeGameUpgradeTemplate +/// @notice Template contract for upgrading dispute game implementations +contract DisputeGameUpgradeTemplate is MultisigTask { + /// @notice Struct containing configuration for setting a dispute game implementation + /// @param gameType The type of game to set the implementation for + /// @param implementation The address of the new implementation + /// @param l2ChainId The ID of the L2 chain + struct SetImplementation { + GameType gameType; + address implementation; + uint256 l2ChainId; + } + + /// @notice Mapping of L2 chain IDs to their respective implementation configurations + /// @dev Maps L2 chain ID to SetImplementation struct + mapping(uint256 => SetImplementation) public setImplementations; + + /// @notice Returns the safe address string identifier + /// @return The string "ProxyAdminOwner" + function safeAddressString() public pure override returns (string memory) { + return "ProxyAdminOwner"; + } + + /// @notice Returns the storage write permissions required for this task + /// @return Array of storage write permissions + function _taskStorageWrites() internal pure override returns (string[] memory) { + string[] memory storageWrites = new string[](1); + storageWrites[0] = "DisputeGameFactoryProxy"; + return storageWrites; + } + + /// @notice Sets up the template with implementation configurations from a TOML file + /// @param taskConfigFilePath Path to the TOML configuration file + function _templateSetup(string memory taskConfigFilePath) internal override { + SetImplementation[] memory setImplementation = + abi.decode(vm.parseToml(vm.readFile(taskConfigFilePath), ".implementations"), (SetImplementation[])); + + for (uint256 i = 0; i < setImplementation.length; i++) { + setImplementations[setImplementation[i].l2ChainId] = setImplementation[i]; + } + } + + /// @notice Builds the actions for setting dispute game implementations for a specific L2 chain ID + /// @param chainId The ID of the L2 chain to configure + function _build(uint256 chainId) internal override { + IDisputeGameFactory disputeGameFactory = + IDisputeGameFactory(addresses.getAddress("DisputeGameFactoryProxy", chainId)); + + if (setImplementations[chainId].l2ChainId != 0) { + disputeGameFactory.setImplementation( + setImplementations[chainId].gameType, IDisputeGame(setImplementations[chainId].implementation) + ); + } + } + + /// @notice Validates that implementations were set correctly for the specified chain ID + /// @param chainId The ID of the L2 chain to validate + function _validate(uint256 chainId) internal view override { + IDisputeGameFactory disputeGameFactory = + IDisputeGameFactory(addresses.getAddress("DisputeGameFactoryProxy", chainId)); + + if (setImplementations[chainId].l2ChainId != 0) { + assertEq( + address(disputeGameFactory.gameImpls(setImplementations[chainId].gameType)), + setImplementations[chainId].implementation, + "implementation not set" + ); + } + } +} diff --git a/src/fps/example/template/GasConfigTemplate.sol b/src/fps/example/template/GasConfigTemplate.sol new file mode 100644 index 000000000..d67c1a9ba --- /dev/null +++ b/src/fps/example/template/GasConfigTemplate.sol @@ -0,0 +1,69 @@ +pragma solidity 0.8.15; + +import {SystemConfig} from "@eth-optimism-bedrock/src/L1/SystemConfig.sol"; + +import {MultisigTask} from "src/fps/task/MultisigTask.sol"; +import {AddressRegistry as Addresses} from "src/fps/AddressRegistry.sol"; + +/// @title GasConfigTemplate +/// @notice Template contract for configuring gas limits +contract GasConfigTemplate is MultisigTask { + /// @notice Struct to store gas limits to be set for a specific L2 chain ID + /// @param chainId The ID of the L2 chain + /// @param gasLimit The gas limit to be set for the chain + struct GasConfig { + uint256 chainId; + uint64 gasLimit; + } + + /// @notice Mapping of chain IDs to their respective gas limits + /// @dev Maps L2 chain ID to its configured gas limit + mapping(uint256 => uint64) public gasLimits; + + /// @notice Returns the safe address string identifier + /// @return The string "SystemConfigOwner" + function safeAddressString() public pure override returns (string memory) { + return "SystemConfigOwner"; + } + + /// @notice Returns the storage write permissions required for this task + /// @return Array of storage write permissions + function _taskStorageWrites() internal pure override returns (string[] memory) { + string[] memory storageWrites = new string[](1); + storageWrites[0] = "SystemConfigProxy"; + return storageWrites; + } + + /// @notice Sets up the template with gas configurations from a TOML file + /// @param taskConfigFilePath Path to the TOML configuration file + function _templateSetup(string memory taskConfigFilePath) internal override { + GasConfig[] memory gasConfig = + abi.decode(vm.parseToml(vm.readFile(taskConfigFilePath), ".gasConfigs.gasLimits"), (GasConfig[])); + + for (uint256 i = 0; i < gasConfig.length; i++) { + gasLimits[gasConfig[i].chainId] = gasConfig[i].gasLimit; + } + } + + /// @notice Builds the actions for setting gas limits for a specific L2 chain ID + /// @param chainId The ID of the L2 chain to configure + function _build(uint256 chainId) internal override { + /// View only, filtered out by MultisigTask.sol + SystemConfig systemConfig = SystemConfig(addresses.getAddress("SystemConfigProxy", chainId)); + + if (gasLimits[chainId] != 0) { + /// Mutative call, recorded by MultisigTask.sol for generating multisig calldata + systemConfig.setGasLimit(gasLimits[chainId]); + } + } + + /// @notice Validates that gas limits were set correctly for the specified chain ID + /// @param chainId The ID of the L2 chain to validate + function _validate(uint256 chainId) internal view override { + SystemConfig systemConfig = SystemConfig(addresses.getAddress("SystemConfigProxy", chainId)); + + if (gasLimits[chainId] != 0) { + assertEq(systemConfig.gasLimit(), gasLimits[chainId], "l2 gas limit not set"); + } + } +} diff --git a/src/fps/example/template/SetGameTypeTemplate.sol b/src/fps/example/template/SetGameTypeTemplate.sol new file mode 100644 index 000000000..558e9cec5 --- /dev/null +++ b/src/fps/example/template/SetGameTypeTemplate.sol @@ -0,0 +1,82 @@ +pragma solidity 0.8.15; + +import { + DeputyGuardianModule, IOptimismPortal2, GameType +} from "@eth-optimism-bedrock/src/safe/DeputyGuardianModule.sol"; +import {LibGameType} from "@eth-optimism-bedrock/src/dispute/lib/LibUDT.sol"; + +import {MultisigTask} from "src/fps/task/MultisigTask.sol"; +import {AddressRegistry as Addresses} from "src/fps/AddressRegistry.sol"; + +/// @title SetGameTypeTemplate +/// @notice Template contract for setting game types in the Optimism system +contract SetGameTypeTemplate is MultisigTask { + using LibGameType for GameType; + + /// @notice Struct containing configuration for setting a respected game type + /// @param deputyGuardian Address of the deputy guardian + /// @param gameType The game type to be set + /// @param l2ChainId The ID of the L2 chain + /// @param portal The portal identifier string + struct SetRespectedGameType { + address deputyGuardian; + GameType gameType; + uint256 l2ChainId; + string portal; + } + + /// @notice Mapping of L2 chain IDs to their respective game type configurations + /// @dev Maps L2 chain ID to SetRespectedGameType struct + mapping(uint256 => SetRespectedGameType) public setRespectedGameTypes; + + /// @notice Returns the safe address string identifier + /// @return The string "Challenger" + function safeAddressString() public pure override returns (string memory) { + return "Challenger"; + } + + /// @notice Returns the storage write permissions required for this task + /// @return Array of storage write permissions + function _taskStorageWrites() internal pure override returns (string[] memory) { + string[] memory storageWrites = new string[](1); + storageWrites[0] = "OptimismPortalProxy"; + return storageWrites; + } + + /// @notice Sets up the template with game type configurations from a TOML file + /// @param taskConfigFilePath Path to the TOML configuration file + function _templateSetup(string memory taskConfigFilePath) internal override { + SetRespectedGameType[] memory setRespectedGameType = + abi.decode(vm.parseToml(vm.readFile(taskConfigFilePath), ".respectedGameTypes"), (SetRespectedGameType[])); + + for (uint256 i = 0; i < setRespectedGameType.length; i++) { + setRespectedGameTypes[setRespectedGameType[i].l2ChainId] = setRespectedGameType[i]; + } + } + + /// @notice Builds the actions for setting game types for a specific L2 chain ID + /// @param chainId The ID of the L2 chain to configure + function _build(uint256 chainId) internal override { + if (setRespectedGameTypes[chainId].l2ChainId != 0) { + DeputyGuardianModule(setRespectedGameTypes[chainId].deputyGuardian).setRespectedGameType( + IOptimismPortal2(payable(addresses.getAddress(setRespectedGameTypes[chainId].portal, chainId))), + setRespectedGameTypes[chainId].gameType + ); + } + } + + /// @notice Validates that game types were set correctly for the specified chain ID + /// @param chainId The ID of the L2 chain to validate + function _validate(uint256 chainId) internal view override { + IOptimismPortal2 optimismPortal = + IOptimismPortal2(payable(addresses.getAddress("OptimismPortalProxy", chainId))); + + if (setRespectedGameTypes[chainId].l2ChainId != 0) { + assertEq( + optimismPortal.respectedGameType().raw(), + setRespectedGameTypes[chainId].gameType.raw(), + "gameType not set" + ); + } + } +} diff --git a/src/fps/task/ITask.sol b/src/fps/task/ITask.sol index a41796a9c..1c0324ee2 100644 --- a/src/fps/task/ITask.sol +++ b/src/fps/task/ITask.sol @@ -4,23 +4,9 @@ pragma solidity 0.8.15; import {AddressRegistry as Addresses} from "src/fps/AddressRegistry.sol"; abstract contract ITask { - /// @notice task name, e.g. "BIP15". - /// @dev override this to set the task name. - - function name() external view virtual returns (string memory); - - /// @notice task description. - /// @dev override this to set the task description. - function description() external view virtual returns (string memory); - - /// @notice function to process the task to be called from templates. - /// @dev use flags to determine which actions to take - /// this function shoudn't be overriden. - function _processTask() internal virtual; - /// @notice return task actions. /// @dev this function shoudn't be overriden. - function getProposalActions() + function getTaskActions() external virtual returns (address[] memory targets, uint256[] memory values, bytes[] memory arguments); @@ -31,6 +17,10 @@ abstract contract ITask { /// @notice return task calldata function getCalldata() external virtual returns (bytes memory data); + /// @notice Runs the task with the given configuration file paths. + /// Sets the address registry, initializes the task and simulates the task. + function run(string memory taskConfigFilePath) external virtual; + /// @notice build the task actions /// @dev contract calls must be perfomed in plain solidity. /// overriden requires using buildModifier modifier to leverage @@ -50,10 +40,4 @@ abstract contract ITask { /// @notice print task description, actions and calldata function print() external virtual; - - /// @notice set the task config - function setTaskConfig(string memory taskConfigFilePath) external virtual; - - /// @notice set the L2 networks config - function setL2NetworksConfig(string memory networkConfigFilePath, Addresses _addresses) external virtual; } diff --git a/src/fps/task/MultisigTask.sol b/src/fps/task/MultisigTask.sol index c30f9e0de..54ad31902 100644 --- a/src/fps/task/MultisigTask.sol +++ b/src/fps/task/MultisigTask.sol @@ -25,9 +25,6 @@ abstract contract MultisigTask is Test, Script, ITask { /// @notice flag to determine if the safe is nested multisig bool public isNestedSafe; - /// @notice flag to determine if the task has been initialized - bool public initialized; - /// @notice owners the safe started with address[] public startingOwners; @@ -101,14 +98,6 @@ abstract contract MultisigTask is Test, Script, ITask { /// they all follow the same structure Action[] public actions; - /// @notice task name, e.g. "OIP15". - /// @dev set in the task config file - string public override name; - - /// @notice task description. - /// @dev set in the task config file - string public override description; - /// @notice Multicall3 call data struct /// @param target The address of the target contract /// @param allowFailure Flag to determine if the call should be allowed to fail @@ -124,8 +113,6 @@ abstract contract MultisigTask is Test, Script, ITask { /// @notice Task TOML config file values struct TaskConfig { string[] allowedStorageWriteAccesses; - string description; - string name; string safeAddressString; } @@ -150,48 +137,40 @@ abstract contract MultisigTask is Test, Script, ITask { _buildStarted = false; } - /// @notice Initialize the task with task and network configuration - /// @param taskConfigFilePath Path to the task configuration file - /// @param networkConfigFilePath Path to the network configuration file - /// @param _addresses Address registry contract - function _init(string memory taskConfigFilePath, string memory networkConfigFilePath, Addresses _addresses) - internal - { + /// @notice abstract function to be implemented by the inheriting contract + /// specifies the safe address string to run the template from + function safeAddressString() public pure virtual returns (string memory); + + /// @notice abstract function to be implemented by the inheriting contract + /// specifies the addresses that must have their storage written to + function _taskStorageWrites() internal pure virtual returns (string[] memory); + + /// @notice Runs the task with the given configuration file paths. + /// Sets the address registry, initializes the task and simulates the task. + /// @param taskConfigFilePath The path to the task configuration file. + function run(string memory taskConfigFilePath) public override { + Addresses _addresses = new Addresses(taskConfigFilePath); + + _templateSetup(taskConfigFilePath); + + /// set the task config require( - !initialized && bytes(config.safeAddressString).length == 0 && address(addresses) == address(0x0), + bytes(config.safeAddressString).length == 0 && address(addresses) == address(0x0), "MultisigTask: already initialized" ); - setTaskConfig(taskConfigFilePath); - setL2NetworksConfig(networkConfigFilePath, _addresses); - initialized = true; - } - - /// @notice Set the task configuration - /// @param taskConfigFilePath Path to the task configuration file - function setTaskConfig(string memory taskConfigFilePath) public override { require( block.chainid == getChain("mainnet").chainId || block.chainid == getChain("sepolia").chainId, string.concat("Unsupported network: ", vm.toString(block.chainid)) ); - string memory taskConfigFileContents = vm.readFile(taskConfigFilePath); - - bytes memory fileContents = vm.parseToml(taskConfigFileContents, ".task"); - config = abi.decode(fileContents, (TaskConfig)); + config.safeAddressString = safeAddressString(); + config.allowedStorageWriteAccesses = _taskStorageWrites(); - name = config.name; - description = config.description; - } - - /// @notice Sets the L2 networks configuration - /// @param networkConfigFilePath Path to the network configuration file - /// @param _addresses Address registry contract - function setL2NetworksConfig(string memory networkConfigFilePath, Addresses _addresses) public override { + /// set the addresses object addresses = _addresses; - string memory networkConfigFileContents = vm.readFile(networkConfigFilePath); - nonce = abi.decode(vm.parseToml(networkConfigFileContents, ".safeNonce"), (uint256)); - isNestedSafe = abi.decode(vm.parseToml(networkConfigFileContents, ".isNestedSafe"), (bool)); + /// assume safe is nested unless there is an EOA owner + isNestedSafe = true; /// get chains Addresses.ChainInfo[] memory chains = addresses.getChains(); @@ -200,6 +179,16 @@ abstract contract MultisigTask is Test, Script, ITask { /// check that the safe address is the same for all chains and then set safe in storage multisig = addresses.getAddress(config.safeAddressString, chains[0].chainId); + /// TODO change this once we implement task stacking + nonce = IGnosisSafe(multisig).nonce(); + + address[] memory owners = IGnosisSafe(multisig).getOwners(); + for (uint256 i = 0; i < owners.length; i++) { + if (owners[i].code.length == 0) { + isNestedSafe = false; + } + } + for (uint256 i = 1; i < chains.length; i++) { require( multisig == addresses.getAddress(config.safeAddressString, chains[i].chainId), @@ -228,25 +217,24 @@ abstract contract MultisigTask is Test, Script, ITask { ); } } - } - /// @notice function to be used by forge script. - /// @dev use flags to determine which actions to take - /// this function shoudn't be overriden. - function _processTask() internal override { + /// now execute task actions build(); simulate(); validate(); print(); } + /// @notice abstract function to be implemented by the inheriting contract to setup the template + function _templateSetup(string memory taskConfigFilePath) internal virtual; + /// @notice get the calldata to be executed by safe /// @dev callable only after the build function has been run and the /// calldata has been loaded up to storage /// @return data The calldata to be executed function getCalldata() public view override returns (bytes memory data) { /// get task actions - (address[] memory targets, uint256[] memory values, bytes[] memory arguments) = getProposalActions(); + (address[] memory targets, uint256[] memory values, bytes[] memory arguments) = getTaskActions(); /// create calls array with targets and arguments Call3Value[] memory calls = new Call3Value[](targets.length); @@ -271,10 +259,17 @@ abstract contract MultisigTask is Test, Script, ITask { } /// @notice get the data to sign by EOA for single multisig - /// @param safe The address of the safe /// @param data The calldata to be executed /// @return The data to sign function _getDataToSign(address safe, bytes memory data) internal view returns (bytes memory) { + uint256 useNonce; + + if (safe == multisig) { + useNonce = nonce; + } else { + useNonce = IGnosisSafe(safe).nonce(); + } + return IGnosisSafe(safe).encodeTransactionData({ to: MULTICALL3_ADDRESS, value: 0, @@ -285,7 +280,7 @@ abstract contract MultisigTask is Test, Script, ITask { gasPrice: 0, gasToken: address(0), refundReceiver: address(0), - _nonce: nonce + _nonce: useNonce }); } @@ -381,7 +376,7 @@ abstract contract MultisigTask is Test, Script, ITask { /// @return targets The targets of the actions /// @return values The values of the actions /// @return arguments The arguments of the actions - function getProposalActions() + function getTaskActions() public view override @@ -431,10 +426,7 @@ abstract contract MultisigTask is Test, Script, ITask { /// @notice print task description, actions, transfers, state changes and EOAs datas to sign function print() public virtual override { - console.log("\n---------------- Proposal Description ----------------"); - console.log(description); - - console.log("\n------------------ Proposal Actions ------------------"); + console.log("\n------------------ Task Actions ------------------"); for (uint256 i; i < actions.length; i++) { console.log("%d). %s", i + 1, actions[i].description); console.log("target: %s\npayload", _getAddressLabel(actions[i].target)); @@ -442,7 +434,7 @@ abstract contract MultisigTask is Test, Script, ITask { console.log("\n"); } - console.log("\n----------------- Proposal Transfers -------------------"); + console.log("\n----------------- Task Transfers -------------------"); if (_taskTransferFromAddresses.length() == 0) { console.log("\nNo Transfers\n"); } @@ -482,7 +474,7 @@ abstract contract MultisigTask is Test, Script, ITask { } } - console.log("\n----------------- Proposal State Changes -------------------"); + console.log("\n----------------- Task State Changes -------------------"); // print state changes for (uint256 k; k < _taskStateChangeAddresses.length(); k++) { address account = _taskStateChangeAddresses.at(k); @@ -497,18 +489,18 @@ abstract contract MultisigTask is Test, Script, ITask { } } - _printProposalCalldata(); + /// print calldata to be executed within the Safe + console.log("\n\n------------------ Task Calldata ------------------"); + console.logBytes(getCalldata()); if (isNestedSafe) { console.log("\n\n------------------ Nested Multisig EOAs Data to Sign ------------------"); printNestedDataToSign(); - // todo: check with op team if this is required console.log("\n\n------------------ Nested Multisig EOAs Hash to Approve ------------------"); printNestedHashToApprove(); } else { console.log("\n\n------------------ Single Multisig EOA Data to Sign ------------------"); printDataToSign(); - // todo: check with op team if this is required console.log("\n\n------------------ Single Multisig EOA Hash to Approve ------------------"); printHashToApprove(); } @@ -528,7 +520,6 @@ abstract contract MultisigTask is Test, Script, ITask { /// @notice print the hash to approve by EOA for nested multisig function printNestedHashToApprove() public view { bytes memory callData = _generateApproveMulticallData(); - for (uint256 i; i < startingOwners.length; i++) { bytes32 hash = keccak256(_getDataToSign(startingOwners[i], callData)); console.log("Nested multisig: %s", _getAddressLabel(startingOwners[i])); @@ -563,12 +554,6 @@ abstract contract MultisigTask is Test, Script, ITask { } } - /// @notice print the calldata to be executed by safe - function _printProposalCalldata() internal virtual { - console.log("\n\n------------------ Proposal Calldata ------------------"); - console.logBytes(getCalldata()); - } - /// @notice helper function to generate the approveHash calldata to be executed by child multisig owner on parent multisig function _generateApproveMulticallData() internal view returns (bytes memory) { bytes32 hash = getHash(); diff --git a/tasks/eth/028-holocene-system-config-upgrade-and-init-multi-chain/README.md b/tasks/eth/028-holocene-system-config-upgrade-and-init-multi-chain/README.md index 0b84c8631..faa499bfe 100644 --- a/tasks/eth/028-holocene-system-config-upgrade-and-init-multi-chain/README.md +++ b/tasks/eth/028-holocene-system-config-upgrade-and-init-multi-chain/README.md @@ -1,6 +1,6 @@ # Holocene Hardfork Upgrade - `SystemConfig` -Status: READY TO SIGN +Status: [EXECUTED](https://etherscan.io/tx/0x86da7386a26978c3db89e97c1f4feee613a8a0c07bbe4640624b05276f49c350) ## Objective diff --git a/tasks/sep/028-holocene-system-config-upgrade-and-init-base/.env b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/.env new file mode 100644 index 000000000..008fe9596 --- /dev/null +++ b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0x0fe884546476dDd290eC46318785046ef68a0BA9 +SAFE_NONCE="" diff --git a/tasks/sep/028-holocene-system-config-upgrade-and-init-base/README.md b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/README.md new file mode 100644 index 000000000..6c11edb11 --- /dev/null +++ b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/README.md @@ -0,0 +1,34 @@ +# Holocene Hardfork Upgrade - `SystemConfig` + +Status: READY TO SIGN + +## Objective + +Upgrades the `SystemConfig` for the Holocene hardfork and sets the EIP1559 parameters + +- This upgrades the `SystemConfig` in the [v1.8.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/v1.8.0-rc.4) release. + +- Unrelated to the Holocene upgrade itself, an additional transaction is included for convenience to adjust the EIP-1559 parameters to `_denominator = 1` and `_elasticity = 4`. While we cannot fully match mainnet in terms of gas throughput, we aim to modify the scaling factor for Sepolia ahead of mainnet. Changing `_elasticity` from `6` (currently hardcoded into the protocol) to `4`, while keeping the `_gasLimit` at 60M, corresponds to a 50% target increase, raising it from 5M to 7.5M, which we are comfortable with. + + +## Pre-deployments + +- `SystemConfig` - `0x33b83E4C305c908B2Fc181dDa36e230213058d7d` + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/028-holocene-system-config-upgrade-and-init-base/SignFrom.s.sol`. +This ensures all safety checks are run. If the default `SignFromJson.s.sol` script is shown (without the full path), something is wrong and the safety checks will not run. + +## State Validation + +Please see the instructions for [validation](./VALIDATION.md). + +## Execution + +This upgrade +* Changes the implementation of the `SystemConfig` to hold EIP-1559 parameters. +* Initializes the EIP-1559 parameters to `_denominator = 1` and `_elasticity = 4`. + +See the `input.json` bundle for more details. diff --git a/tasks/sep/028-holocene-system-config-upgrade-and-init-base/SignFromJson.s.sol b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/SignFromJson.s.sol new file mode 100644 index 000000000..4d3ec2c6a --- /dev/null +++ b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/SignFromJson.s.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {console2 as console} from "forge-std/console2.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {SignFromJson as OriginalSignFromJson} from "script/SignFromJson.s.sol"; +import {ISystemConfig, HoloceneSystemConfigUpgrade} from "script/verification/HoloceneSystemConfigUpgrade.s.sol"; +import {VerificationBase} from "script/verification/Verification.s.sol"; + +contract SignFromJson is OriginalSignFromJson, VerificationBase { + string constant l1ChainName = "sepolia"; + string constant release = "v1.8.0-rc.4"; + string constant l2ChainName = "base"; + + address ownerSafe = vm.envAddress("OWNER_SAFE"); + + HoloceneSystemConfigUpgrade sysCfgUpgrade; + + function setUp() public { + sysCfgUpgrade = new HoloceneSystemConfigUpgrade(l1ChainName, l2ChainName, release); + + console.log(""); + console.log("Set up verification data for chain", l2ChainName, "-", l1ChainName); + console.log("with SystemConfigProxy @", sysCfgUpgrade.systemConfigAddress()); + addAllowedStorageAccess(sysCfgUpgrade.systemConfigAddress()); + + // The OwnerSafe multisig nonce is incremented. + addAllowedStorageAccess(ownerSafe); + + addCodeExceptions(sysCfgUpgrade.getCodeExceptions()); + } + + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override { + console.log("Running post-deploy assertions"); + + checkStateDiff(accesses); + sysCfgUpgrade.checkSystemConfigUpgrade(); + + ISystemConfig systemConfig = ISystemConfig(sysCfgUpgrade.systemConfigAddress()); + vm.assertEq(systemConfig.eip1559Denominator(), 1, "incorrect EIP1559 denominator"); + vm.assertEq(systemConfig.eip1559Elasticity(), 4, "incorrect EIP1559 elasticity"); + + console.log("All assertions passed!"); + } + + function getAllowedStorageAccess() internal view override returns (address[] memory) { + return allowedStorageAccess; + } + + function getCodeExceptions() internal view override returns (address[] memory) { + return codeExceptions; + } +} diff --git a/tasks/sep/028-holocene-system-config-upgrade-and-init-base/VALIDATION.md b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/VALIDATION.md new file mode 100644 index 000000000..04430a13e --- /dev/null +++ b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/VALIDATION.md @@ -0,0 +1,36 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the upgrade +transaction. + +For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff are missing from this document. Additionally, please verify that for each contract: + +- The following state changes (and none others) are made to that contract. This validates that no unexpected state changes occur. +- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic. +- All key values match the semantic meaning provided, which can be validated using the storage layout links provided. + +## State Changes + +### `0x0fe884546476dDd290eC46318785046ef68a0BA9` (`GnosisSafeProxy`) + +- **Key**: `0x0000000000000000000000000000000000000000000000000000000000000005`
+ **Before**: `0x000000000000000000000000000000000000000000000000000000000000000b`
+ **After**: `0x000000000000000000000000000000000000000000000000000000000000000c`
+ **Meaning**: Increments the `GnosisSafeProxy`'s `nonce`. + +### `0xf272670eb55e895584501d564AfEB048bEd26194` (`SystemConfigProxy`) + +- **Key**: `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
+ **Before**: `0x00000000000000000000000031aad1062c25ce545c14bd9ee64dedef6c6b6fac`
+ **After**: `0x00000000000000000000000033b83e4c305c908b2fc181dda36e230213058d7d`
+ **Meaning**: Updates the `SystemConfig`'s implementation to version 2.3.0 at `0x33b83E4C305c908B2Fc181dDa36e230213058d7d`. + +- **Key**: `0x0000000000000000000000000000000000000000000000000000000000000068`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000003938700`
+ **After**: `0x00000000000000000000000000000000000a118b0000044d0000000003938700`
+ **Meaning**: Sets the new `SystemConfig`'s variables `blobbasefeeScalar` to `659851` (`cast td cast td 0x000a118b`) and `basefeeScalar` to `1101` (`cast td cast td 0x0000044d`). These share a slot with the `gasLimit` which remains at 60000000 (`cast td 0x0000000003938700`). See storage layout snapshot [here](https://github.com/ethereum-optimism/optimism/blob/3c75cd94849b265ff9d2ed424f9d35be124b0b4e/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json#L58-L78). + +- **Key**: `0x000000000000000000000000000000000000000000000000000000000000006a`
+ **Before**: `0x000000000000000000000000000000000000000000000000000000000042b1d7`
+ **After**: `0x0000000000000000000000000000000000000000000000000000000400000001`
+ **Meaning**: Sets the new `SystemConfig`'s variables `eip1559Denominator` to `1` (`cast td 0x00000001`) and `eip1559Elasticity` to `4` (`cast td cast td 0x00000004`). Slot 106 was previously used to store a `startBlock` which is why it has a non-zero "before" value of 4370903 (`cast td 0x42b1d7`). diff --git a/tasks/sep/028-holocene-system-config-upgrade-and-init-base/input.json b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/input.json new file mode 100644 index 000000000..02ef97a1f --- /dev/null +++ b/tasks/sep/028-holocene-system-config-upgrade-and-init-base/input.json @@ -0,0 +1,135 @@ +{ + "chainId": 11155111, + "metadata": { + "name": "Holocene Hardfork - SystemConfig Upgrade", + "description": "Upgrades the `SystemConfig.sol` contract for Holocene for base-sepolia." + }, + "transactions": [ + { + "metadata": { + "name": "ProxyAdmin.upgrade(SystemConfigProxy, StorageSetter)", + "description": "Upgrades the `SystemConfigProxy`to the StorageSetter." + }, + "to": "0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3", + "data": "0x99a88ec4000000000000000000000000f272670eb55e895584501d564afeb048bed26194000000000000000000000000d81f43edbcacb4c29a9ba38a13ee5d79278270cc", + "value": "0", + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false, + "outputs": [] + }, + "contractInputsValues": { + "_proxy": "0xf272670eb55e895584501d564AfEB048bEd26194", + "_implementation": "0xd81f43eDBCAcb4c29a9bA38a13Ee5d79278270cC" + } + }, + { + "metadata": { + "name": "SystemConfigProxy.setBytes32(0,0)", + "description": "Zeroes out the initialized state variable to allow reinitialization." + }, + "to": "0xf272670eb55e895584501d564AfEB048bEd26194", + "data": "0x4e91db0800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "value": "0", + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false, + "outputs": [] + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "metadata": { + "name": "ProxyAdmin.upgradeAndCall(SystemConfigProxy, SystemConfigImplementation, Initialize())", + "description": "Upgrades the SystemConfig to a new implementation and initializes it." + }, + "to": "0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3", + "data": "0x9623609d000000000000000000000000f272670eb55e895584501d564afeb048bed2619400000000000000000000000033b83e4c305c908b2fc181dda36e230213058d7d00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000284db9040fa0000000000000000000000000fe884546476ddd290ec46318785046ef68a0ba9000000000000000000000000000000000000000000000000000000000000044d00000000000000000000000000000000000000000000000000000000000a118b000000000000000000000000fc56e7272eebbba5bc6c544e159483c4a38f8ba30000000000000000000000000000000000000000000000000000000003938700000000000000000000000000b830b99c95ea32300039624cb567d324d4b1d83c0000000000000000000000000000000000000000000000000000000001312d00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000ff00000000000000000000000000000000084532000000000000000000000000c34855f4de64f1840e5686e64278da901e261f2000000000000000000000000021efd066e581fa55ef105170cc04d74386a09190000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb113120000000000000000000000000d6e6dbf4f7ea0ac412fd8b65ed297e64bb7a06e100000000000000000000000049f53e41452c74589e85ca1677426ba426459e85000000000000000000000000b1efb9650ad6d0cc1ed3ac4a0b7f1d5732696d37000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000", + "value": "0", + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "upgradeAndCall", + "payable": false, + "outputs": [] + }, + "contractInputsValues": { + "_data": "0xdb9040fa0000000000000000000000000fe884546476ddd290ec46318785046ef68a0ba9000000000000000000000000000000000000000000000000000000000000044d00000000000000000000000000000000000000000000000000000000000a118b000000000000000000000000fc56e7272eebbba5bc6c544e159483c4a38f8ba30000000000000000000000000000000000000000000000000000000003938700000000000000000000000000b830b99c95ea32300039624cb567d324d4b1d83c0000000000000000000000000000000000000000000000000000000001312d00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000f424000000000000000000000000000000000ffffffffffffffffffffffffffffffff000000000000000000000000ff00000000000000000000000000000000084532000000000000000000000000c34855f4de64f1840e5686e64278da901e261f2000000000000000000000000021efd066e581fa55ef105170cc04d74386a09190000000000000000000000000fd0bf71f60660e2f608ed56e1659c450eb113120000000000000000000000000d6e6dbf4f7ea0ac412fd8b65ed297e64bb7a06e100000000000000000000000049f53e41452c74589e85ca1677426ba426459e85000000000000000000000000b1efb9650ad6d0cc1ed3ac4a0b7f1d5732696d37000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "_proxy": "0xf272670eb55e895584501d564AfEB048bEd26194", + "_implementation": "0x33b83E4C305c908B2Fc181dDa36e230213058d7d" + } + }, + { + "metadata": { + "name": "SystemConfigProxy.setEIP1559Params(1,4)", + "description": "Set the EIP1559 `_denominator` to `1` and `_elasticity` to `4`" + }, + "to": "0xf272670eb55e895584501d564AfEB048bEd26194", + "data": "0xc0fd4b4100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000004", + "value": "0", + "contractMethod": { + "inputs": [ + { + "name": "_denominator", + "type": "uint32", + "internalType": "uint32" + }, + { + "name": "_elasticity", + "type": "uint32", + "internalType": "uint32" + } + ], + "name": "setEIP1559Params", + "payable": false, + "outputs": [] + }, + "contractInputsValues": { + "_denominator": "1", + "_elasticity": "4" + } + } + ] +} \ No newline at end of file diff --git a/test/AddressRegistry.t.sol b/test/AddressRegistry.t.sol index 5e1590a7e..9629700b7 100644 --- a/test/AddressRegistry.t.sol +++ b/test/AddressRegistry.t.sol @@ -10,7 +10,7 @@ contract MainnetAddressRegistryTest is Test { AddressRegistry private addresses; function setUp() public { - string memory networkConfigFilePath = "src/fps/example/mainnetConfig.toml"; + string memory networkConfigFilePath = "src/fps/example/task-00/mainnetConfig.toml"; vm.createSelectFork("mainnet"); @@ -126,7 +126,7 @@ contract MainnetAddressRegistryTest is Test { /// Construction failure tests function testInvalidChainIdInSuperchainsFails() public { - string memory networkConfigFilePath = "test/mock/networkConfig1.toml"; + string memory networkConfigFilePath = "test/mock/invalidChainIdNetworkConfig.toml"; vm.expectRevert("Invalid chain ID in config"); new AddressRegistry(networkConfigFilePath); diff --git a/test/mock/networkConfig1.toml b/test/mock/invalidChainIdNetworkConfig.toml similarity index 56% rename from test/mock/networkConfig1.toml rename to test/mock/invalidChainIdNetworkConfig.toml index 54be2fb5c..54baa715d 100644 --- a/test/mock/networkConfig1.toml +++ b/test/mock/invalidChainIdNetworkConfig.toml @@ -1,3 +1 @@ -safeNonce = 66 -isNestedSafe = false -l2chains = [{name = "Orderly", chainId = 0}, {name = "Metal", chainId = 1750}] \ No newline at end of file +l2chains = [{name = "Orderly", chainId = 0}, {name = "Metal", chainId = 1750}] diff --git a/test/mock/networkConfig2.toml b/test/mock/networkConfig2.toml deleted file mode 100644 index e95b61efb..000000000 --- a/test/mock/networkConfig2.toml +++ /dev/null @@ -1,3 +0,0 @@ -safeNonce = 66 -isNestedSafe = false -l2chains = [{name = "Orderly", chainId = 291}, {name = "", chainId = 1750}] \ No newline at end of file diff --git a/test/mock/networkConfig3.toml b/test/mock/networkConfig3.toml deleted file mode 100644 index aaee8075a..000000000 --- a/test/mock/networkConfig3.toml +++ /dev/null @@ -1,3 +0,0 @@ -safeNonce = 66 -isNestedSafe = false -l2chains = [{name = "Orderly", chainId = 291}, {name = "Orderly", chainId = 291}] \ No newline at end of file