diff --git a/.circleci/config.yml b/.circleci/config.yml index 99a44b786..57830e80a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,7 +59,6 @@ commands: --justfile ../../../nested.just \ simulate council - jobs: check_sepolia_rpc_endpoints: circleci_ip_ranges: true @@ -225,14 +224,21 @@ jobs: just simulate-council just prepare-json just simulate-council # simulate again to make sure the json is still valid - - simulate_eth_021: + + just_simulate_permissionless_fp_upgrade: + docker: + - image: << pipeline.parameters.ci_builder_image >> + steps: + - simulate_nested: + task: "/eth/ink-001-permissionless-proofs" + + just_simulate_ink_respected_game_type: docker: - image: << pipeline.parameters.ci_builder_image >> steps: - simulate: - task: "eth/021-holocene-protocol-versions" - + task: "/eth/ink-002-set-respected-game-type" + forge_build: docker: - image: <> @@ -279,13 +285,6 @@ jobs: yq --version forge --version - simulate_eth_022: - docker: - - image: << pipeline.parameters.ci_builder_image >> - steps: - - simulate_nested: - task: "eth/022-holocene-fp-upgrade" - workflows: main: jobs: @@ -313,10 +312,10 @@ workflows: - just_simulate_sc_rehearsal_1 - just_simulate_sc_rehearsal_2 - just_simulate_sc_rehearsal_4 + - just_simulate_permissionless_fp_upgrade + - just_simulate_ink_respected_game_type # Simulate non-terminal tasks. # This process finds all non-terminal tasks that currently # exist in the task list and simulates them to ensure that # they are still valid. - - simulate_non_terminal_tasks - - simulate_eth_021 - - simulate_eth_022 + - simulate_non_terminal_tasks \ No newline at end of file diff --git a/NESTED-VALIDATION.md b/NESTED-VALIDATION.md index c2dc7bad9..9f3dbfe95 100644 --- a/NESTED-VALIDATION.md +++ b/NESTED-VALIDATION.md @@ -1,7 +1,7 @@ # Validation - Nested Safe -This document describes the generic validation steps for running a Mainnet or Sepolia tasks for the -nested 2/2 Security Council/Foundation Safe. +This document describes the generic validation steps for running a Mainnet or Sepolia tasks for any +nested 2/2 Safe involving either the Security Council & Foundation Upgrade Safe or the Base and Foundation Operations Safe. ## State Overrides @@ -10,10 +10,12 @@ The following state overrides related to the nested Safe execution must be seen: ### `GnosisSafeProxy` - the 2/2 `ProxyAdminOwner` Safe The `ProxyAdminOwner` has the following address: -- Mainnet: [`0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A`](https://etherscan.io/address/0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A) -- Sepolia: [`0x1Eb2fFc903729a0F03966B917003800b145F56E2`](https://sepolia.etherscan.io/address/0x1Eb2fFc903729a0F03966B917003800b145F56E2) +- Mainnet: + - Superchain: [`0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A`](https://etherscan.io/address/0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A) + - Base/OP: [0x7bB41C3008B3f03FE483B28b8DB90e19Cf07595c](https://etherscan.io/address/0x7bB41C3008B3f03FE483B28b8DB90e19Cf07595c) +- Sepolia Superchain: [`0x1Eb2fFc903729a0F03966B917003800b145F56E2`](https://sepolia.etherscan.io/address/0x1Eb2fFc903729a0F03966B917003800b145F56E2) -These addresses are attested to in the [Optimism Docs](https://docs.optimism.io/chain/security/privileged-roles#addresses). +The Superchain addresses are attested to in the [Optimism Docs](https://docs.optimism.io/chain/security/privileged-roles#addresses). Enables the simulation by setting the threshold to 1: @@ -21,16 +23,18 @@ Enables the simulation by setting the threshold to 1: **Value:** `0x0000000000000000000000000000000000000000000000000000000000000001` **Meaning:** The threshold is set to 1. -### Security Council Safe or Foundation Safe +### Safe Signer -Depending on which role (Security Council or Foundation) the task was simulated for, +Depending on which role the task was simulated for, you must see the following overrides for the following address: - Mainnet - - Council Safe: [`0xc2819DC788505Aac350142A7A707BF9D03E3Bd03`](https://etherscan.io/address/0xc2819DC788505Aac350142A7A707BF9D03E3Bd03) - - Foundation Safe: [`0x847B5c174615B1B7fDF770882256e2D3E95b9D92`](https://etherscan.io/address/0x847B5c174615B1B7fDF770882256e2D3E95b9D92) + - Security Council Safe: [`0xc2819DC788505Aac350142A7A707BF9D03E3Bd03`](https://etherscan.io/address/0xc2819DC788505Aac350142A7A707BF9D03E3Bd03) + - Foundation Upgrade Safe: [`0x847B5c174615B1B7fDF770882256e2D3E95b9D92`](https://etherscan.io/address/0x847B5c174615B1B7fDF770882256e2D3E95b9D92) + - Foundation Operations Safe: [`0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A`](https://etherscan.io/address/0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A) + - Base Operations Safe: [`0x9855054731540A48b28990B63DcF4f33d8AE46A1`](https://etherscan.io/address/0x9855054731540A48b28990B63DcF4f33d8AE46A1) - Sepolia - - Council Safe: [`0xf64bc17485f0B4Ea5F06A96514182FC4cB561977`](https://sepolia.etherscan.io/address/0xf64bc17485f0B4Ea5F06A96514182FC4cB561977) - - Foundation Safe: [`0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B`](https://sepolia.etherscan.io/address/0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B) + - Fake Security Council Safe: [`0xf64bc17485f0B4Ea5F06A96514182FC4cB561977`](https://sepolia.etherscan.io/address/0xf64bc17485f0B4Ea5F06A96514182FC4cB561977) + - Fake Foundation Upgrade Safe: [`0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B`](https://sepolia.etherscan.io/address/0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B) The simulated role will also be called the **Safe Signer** in the remaining document. @@ -88,15 +92,19 @@ The GnosisSafe `approvedHashes` mapping is updated to indicate approval of this ``` The output of this command must match the key of the state change. -### Liveness Guard +### Liveness Guard (Security Council only) -When the Security Council executes a transaction, the liveness timestamp are updated for each owner that signed the tasks. -This is updating at the moment of the transaction is submitted (`block.timestamp`) into the [`lastLive`](https://github.com/ethereum-optimism/optimism/blob/e84868c27776fd04dc77e95176d55c8f6b1cc9a3/packages/contracts-bedrock/src/safe/LivenessGuard.sol#L41) mapping located at the slot `0`. +When the Security Council executes a transaction, the liveness timestamps are updated for each owner that signed the task. +This is updating at the moment when the transaction is submitted (`block.timestamp`) into the [`lastLive`](https://github.com/ethereum-optimism/optimism/blob/e84868c27776fd04dc77e95176d55c8f6b1cc9a3/packages/contracts-bedrock/src/safe/LivenessGuard.sol#L41) mapping located at the slot `0`. ### Nonce increments The only other state changes related to the nested execution are _three_ nonce increments: -- One on the `ProxyAdminOwner` 2/2. If this is not decoded, it corresponds to key `0x05` on a `GnosisSafeProxy`. -- One on the Council or Foundation Safe. If this is not decoded, it corresponds to key `0x05` on a `GnosisSafeProxy`. -- One of the EOA that is the first entry in the owner set of the simulated role. +- One increment of the `ProxyAdminOwner` Safe nonce, located as storage slot +`0x0000000000000000000000000000000000000000000000000000000000000005` on a +`GnosisSafeProxy`. +- One increment of the **Safe Signer** nonce, located as storage slot +`0x0000000000000000000000000000000000000000000000000000000000000005` on a +`GnosisSafeProxy`. +- One increment of the nonce of the EOA that is the first entry in the owner set of the Safe Signer. diff --git a/justfile b/justfile index c58f0222c..27aa765a4 100644 --- a/justfile +++ b/justfile @@ -12,7 +12,7 @@ install-eip712sign: PATH="$REPO_ROOT/bin:$PATH" cd $REPO_ROOT mkdir -p bin || true - GOBIN="$REPO_ROOT/bin" go install github.com/base-org/eip712sign@v0.0.8 + GOBIN="$REPO_ROOT/bin" go install github.com/base-org/eip712sign@v0.0.10 # Bundle path should be provided including the .json file extension. add-transaction bundlePath to sig *params: diff --git a/lib/base-contracts b/lib/base-contracts index ed36aac52..494586571 160000 --- a/lib/base-contracts +++ b/lib/base-contracts @@ -1 +1 @@ -Subproject commit ed36aac52a19bdad6dee09c59e7241fe3a194160 +Subproject commit 494586571e1a4d845ee6f381b65229d63c630986 diff --git a/lib/superchain-registry b/lib/superchain-registry index 2a82b7922..c08331ab4 160000 --- a/lib/superchain-registry +++ b/lib/superchain-registry @@ -1 +1 @@ -Subproject commit 2a82b7922a7d719e4243846c679e4a9946d04b8c +Subproject commit c08331ab44a3645608c08d8c94f78d9be46c13c9 diff --git a/runbooks/key-handover.md b/runbooks/key-handover.md index 281fade0a..035d74608 100644 --- a/runbooks/key-handover.md +++ b/runbooks/key-handover.md @@ -4,7 +4,7 @@ This document describes how to generate upgrade playbooks to upgrade chains to t ## Context -One of the requirement for getting to Stage, 1 as defined by [L2Beat](https://medium.com/l2beat/introducing-stages-a-framework-to-evaluate-rollups-maturity-d290bb22befe) is having a Security Council in place. The Security Council acts as a safeguard in the system, ready to step in in the event of bugs or issues with the proof system. It must function through a multisig setup consisting of at least 8 participants and require a 50% consensus threshold. Furthermore, at least half of the participants must be external to the organization running the rollup, with a minimum of two outsiders required for consensus. +One of the requirements for getting to Stage, 1 as defined by [L2Beat](https://medium.com/l2beat/introducing-stages-a-framework-to-evaluate-rollups-maturity-d290bb22befe) is having a Security Council in place. The Security Council acts as a safeguard in the system, ready to step in in the event of bugs or issues with the proof system. It must function through a multisig setup consisting of at least 8 participants and require a 50% consensus threshold. Furthermore, at least half of the participants must be external to the organization running the rollup, with a minimum of two outsiders required for consensus. This setup ensures a diversity of viewpoints and minimizes the risk of any single party exerting undue influence. For the sake of transparency and accountability, the identities (or the pseudonyms) of the council participants should also be publicly disclosed. diff --git a/script/verification/DisputeGameUpgrade.s.sol b/script/verification/DisputeGameUpgrade.s.sol index dffd4f4a7..371151bc8 100644 --- a/script/verification/DisputeGameUpgrade.s.sol +++ b/script/verification/DisputeGameUpgrade.s.sol @@ -18,26 +18,34 @@ interface IASR { function superchainConfig() external view returns (address superchainConfig_); } +interface IMIPS is ISemver { + function oracle() external view returns (address oracle_); +} + abstract contract DisputeGameUpgrade is VerificationBase, SuperchainRegistry { using LibString for string; bytes32 immutable expAbsolutePrestate; address immutable expFaultDisputeGame; address immutable expPermissionedDisputeGame; + DisputeGameFactory immutable dgfProxy; constructor(bytes32 _absolutePrestate, address _faultDisputeGame, address _permissionedDisputeGame) { expAbsolutePrestate = _absolutePrestate; expFaultDisputeGame = _faultDisputeGame; expPermissionedDisputeGame = _permissionedDisputeGame; + dgfProxy = DisputeGameFactory(proxies.DisputeGameFactory); + addAllowedStorageAccess(proxies.DisputeGameFactory); + + precheckDisputeGames(); } /// @notice Public function that must be called by the verification script. function checkDisputeGameUpgrade() public view { console.log("check dispute game implementations"); - DisputeGameFactory dgfProxy = DisputeGameFactory(proxies.DisputeGameFactory); FaultDisputeGame faultDisputeGame = FaultDisputeGame(address(dgfProxy.gameImpls(GameTypes.CANNON))); PermissionedDisputeGame permissionedDisputeGame = PermissionedDisputeGame(address(dgfProxy.gameImpls(GameTypes.PERMISSIONED_CANNON))); @@ -86,4 +94,53 @@ abstract contract DisputeGameUpgrade is VerificationBase, SuperchainRegistry { require(address(faultDisputeGame.weth()) != address(permissionedDisputeGame.weth()), "weth-200"); } + + function precheckDisputeGames() internal view { + _precheckDisputeGameImplementation(GameType.wrap(0), expFaultDisputeGame); + _precheckDisputeGameImplementation(GameType.wrap(1), expPermissionedDisputeGame); + } + + // _precheckDisputeGameImplementation checks that the new game being set has the same + // configuration as the existing implementation. + function _precheckDisputeGameImplementation(GameType _targetGameType, address _newImpl) internal view { + console.log("pre-check new game implementation", _targetGameType.raw()); + + FaultDisputeGame currentGame = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_targetGameType)))); + FaultDisputeGame newGame = FaultDisputeGame(_newImpl); + + if (vm.envOr("DISPUTE_GAME_CHANGE_WETH", false)) { + console.log("Expecting DelayedWETH to change"); + require(address(currentGame.weth()) != address(newGame.weth()), "pre-10"); + } else { + console.log("Expecting DelayedWETH to stay the same"); + require(address(currentGame.weth()) == address(newGame.weth()), "pre-10"); + } + + require(_targetGameType.raw() == newGame.gameType().raw(), "pre-20"); + require(address(currentGame.anchorStateRegistry()) == address(newGame.anchorStateRegistry()), "pre-30"); + require(currentGame.l2ChainId() == newGame.l2ChainId(), "pre-40"); + require(currentGame.splitDepth() == newGame.splitDepth(), "pre-50"); + require(currentGame.maxGameDepth() == newGame.maxGameDepth(), "pre-60"); + require(currentGame.maxClockDuration().raw() == newGame.maxClockDuration().raw(), "pre-70"); + require(currentGame.clockExtension().raw() == newGame.clockExtension().raw(), "pre-80"); + + if (_targetGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + PermissionedDisputeGame currentPDG = PermissionedDisputeGame(address(currentGame)); + PermissionedDisputeGame newPDG = PermissionedDisputeGame(address(newGame)); + require(address(currentPDG.proposer()) == address(newPDG.proposer()), "pre-90"); + require(address(currentPDG.challenger()) == address(newPDG.challenger()), "pre-100"); + } + + _precheckVm(newGame, currentGame); + } + + // _precheckVm checks that the new VM has the same oracle as the old VM. + function _precheckVm(FaultDisputeGame _newGame, FaultDisputeGame _currentGame) internal view { + console.log("pre-check VM implementation", _newGame.gameType().raw()); + + IMIPS newVm = IMIPS(address(_newGame.vm())); + IMIPS currentVm = IMIPS(address(_currentGame.vm())); + + require(newVm.oracle() == currentVm.oracle(), "vm-10"); + } } diff --git a/script/verification/Verification.s.sol b/script/verification/Verification.s.sol index 6a00f1bae..907efb0b1 100644 --- a/script/verification/Verification.s.sol +++ b/script/verification/Verification.s.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.15; +import {console2 as console} from "forge-std/console2.sol"; import {LibString} from "solady/utils/LibString.sol"; import {Types} from "@eth-optimism-bedrock/scripts/Types.sol"; import {CommonBase} from "forge-std/Base.sol"; @@ -75,6 +76,7 @@ contract SuperchainRegistry is CommonBase { opContractsReleaseQ = string.concat("\"op-contracts/", _opContractsRelease, "\""); _readSuperchainConfig(); _readStandardVersions(); + _applyOverrides(); } /// @notice Reads the contract addresses from the superchain registry. @@ -162,4 +164,15 @@ contract SuperchainRegistry is CommonBase { { sv_.version = stdToml.readString(data, string.concat("$.RELEASE.", key, ".version")); } + + function _applyOverrides() internal { + try vm.envAddress("SCR_OVERRIDE_MIPS_ADDRESS") returns (address mips) { + console.log("SuperchainRegistry: overriding MIPS address to %s", mips); + standardVersions.MIPS.Address = mips; + } catch { /* Ignore, no override */ } + try vm.envString("SCR_OVERRIDE_MIPS_VERSION") returns (string memory ver) { + console.log("SuperchainRegistry: overriding MIPS version to %s", ver); + standardVersions.MIPS.version = ver; + } catch { /* Ignore, no override */ } + } } diff --git a/tasks/eth/022-holocene-fp-upgrade/.env b/tasks/eth/022-holocene-fp-upgrade/.env index 71d189ccf..9036f46ef 100644 --- a/tasks/eth/022-holocene-fp-upgrade/.env +++ b/tasks/eth/022-holocene-fp-upgrade/.env @@ -7,4 +7,5 @@ SAFE_NONCE=6 # noop SAFE_NONCE_0XC2819DC788505AAC350142A7A707BF9D03E3BD03=8 # noop SAFE_NONCE_0X847B5C174615B1B7FDF770882256E2D3E95B9D92=11 # +1, task 021 -SIMULATE_WITHOUT_LEDGER=0 # 1 +# we change the WETH in this task, which is non-standard +DISPUTE_GAME_CHANGE_WETH=true diff --git a/tasks/eth/022-holocene-fp-upgrade/README.md b/tasks/eth/022-holocene-fp-upgrade/README.md index 1c6a3567e..a2f3d4262 100644 --- a/tasks/eth/022-holocene-fp-upgrade/README.md +++ b/tasks/eth/022-holocene-fp-upgrade/README.md @@ -1,6 +1,6 @@ # Holocene Hardfork Upgrade - OP Mainnet -Status: READY TO SIGN +Status: [EXECUTED](https://etherscan.io/tx/0x22ab07b0bb1bc8504256835d555307ff522b78651dca33c9cd5b05591ca5b4f7) ## Objective @@ -10,8 +10,8 @@ The proposal was: - [X] [Posted](https://gov.optimism.io/t/upgrade-proposal-11-holocene-network-upgrade/9313) on the governance forum. - [X] [Approved](https://vote.optimism.io/proposals/20127877429053636874064552098716749508236019236440427814457915785398876262515) by Token House voting. -- [ ] Not vetoed by the Citizens' house. -- [ ] Executed on OP Mainnet. +- [X] Not vetoed by the Citizens' house. +- [X] Executed on OP Mainnet. The governance proposal should be treated as the source of truth and used to verify the correctness of the onchain operations. diff --git a/tasks/eth/base-003-holocene-fp-upgrade/.env b/tasks/eth/base-003-holocene-fp-upgrade/.env new file mode 100644 index 000000000..31152ab26 --- /dev/null +++ b/tasks/eth/base-003-holocene-fp-upgrade/.env @@ -0,0 +1,15 @@ +ETH_RPC_URL="https://ethereum.publicnode.com" + +# Base L1 PAO +OWNER_SAFE=0x7bB41C3008B3f03FE483B28b8DB90e19Cf07595c +# This is Base's safe but the Nested.just file uses council as a keyword and +# we want to minimize changes +COUNCIL_SAFE=0x9855054731540A48b28990B63DcF4f33d8AE46A1 +# Foundation Operations Safe +FOUNDATION_SAFE=0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A + +# No nonce overrides needed, this is the next task for B1PAO, BOS, FOS. +# But doing them anyways. +SAFE_NONCE=4 +SAFE_NONCE_0X9855054731540A48B28990B63DCF4F33D8AE46A1=16 +SAFE_NONCE_0X9BA6E03D8B90DE867373DB8CF1A58D2F7F006B3A=97 diff --git a/tasks/eth/base-003-holocene-fp-upgrade/NestedSignFromJson.s.sol b/tasks/eth/base-003-holocene-fp-upgrade/NestedSignFromJson.s.sol new file mode 100644 index 000000000..c698b2dad --- /dev/null +++ b/tasks/eth/base-003-holocene-fp-upgrade/NestedSignFromJson.s.sol @@ -0,0 +1,57 @@ +// 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 {stdJson} from "forge-std/StdJson.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {NestedSignFromJson as OriginalNestedSignFromJson} from "script/NestedSignFromJson.s.sol"; +import {DisputeGameUpgrade} from "script/verification/DisputeGameUpgrade.s.sol"; +import {CouncilFoundationNestedSign} from "script/verification/CouncilFoundationNestedSign.s.sol"; +import {SuperchainRegistry} from "script/verification/Verification.s.sol"; + +contract NestedSignFromJson is OriginalNestedSignFromJson, CouncilFoundationNestedSign, DisputeGameUpgrade { + constructor() + SuperchainRegistry("mainnet", "base", "v1.8.0-rc.4") + DisputeGameUpgrade( + 0x03f89406817db1ed7fd8b31e13300444652cdb0b9c509a674de43483b2f83568, // absolutePrestate + 0xc5f3677c3C56DB4031ab005a3C9c98e1B79D438e, // faultDisputeGame + 0xF62c15e2F99d4869A925B8F57076cD85335832A2 // permissionedDisputeGame + ) + {} + + function setUp() public view { + checkInput(); + } + + function checkInput() public view { + string memory inputJson; + string memory path = "/tasks/eth/base-003-holocene-fp-upgrade/input.json"; + try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) { + inputJson = data; + } catch { + revert(string.concat("Failed to read ", path)); + } + + address inputPermissionedDisputeGame = + stdJson.readAddress(inputJson, "$.transactions[0].contractInputsValues._impl"); + address inputFaultDisputeGame = stdJson.readAddress(inputJson, "$.transactions[1].contractInputsValues._impl"); + require(expPermissionedDisputeGame == inputPermissionedDisputeGame, "input-pdg"); + require(expFaultDisputeGame == inputFaultDisputeGame, "input-fdg"); + } + + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override { + console.log("Running post-deploy assertions"); + checkStateDiff(accesses); + checkDisputeGameUpgrade(); + 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/eth/base-003-holocene-fp-upgrade/README.md b/tasks/eth/base-003-holocene-fp-upgrade/README.md new file mode 100644 index 000000000..0d70ec540 --- /dev/null +++ b/tasks/eth/base-003-holocene-fp-upgrade/README.md @@ -0,0 +1,37 @@ +# Holocene Hardfork Upgrade + +Status: [EXECUTED](https://etherscan.io/tx/0xb6fc07450001150de1d6edcda68e7b4de5725259bb55ca3e5c22d474c7127e9c) + +## Objective + +Upgrades the Base Mainnet Fault Proof contracts for the Holocene hardfork. + +The related Optimism governance post of the upgrade can be found at https://gov.optimism.io/t/upgrade-proposal-11-holocene-network-upgrade/9313. + +This upgrades the Fault Proof contracts in the +[op-contracts/v1.8.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4) release. + +## Pre-deployments + +- `MIPS` - `0x5fE03a12C1236F9C22Cb6479778DDAa4bce6299C` +- `FaultDisputeGame` - `0xc5f3677c3C56DB4031ab005a3C9c98e1B79D438e` +- `PermissionedDisputeGame` - `0xF62c15e2F99d4869A925B8F57076cD85335832A2` + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [NESTED.md](../../../NESTED.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/eth/base-003-holocene-fp-upgrade/NestedSignFromJson.s.sol`. +This ensures all safety checks are run. If the default `NestedSignFromJson.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 dispute game implementation of the `CANNON` and `PERMISSIONED_CANNON` game types to contain a `op-program` release for the Holocene hardfork, which contains + the Holocene fork implementation as well as a `ChainConfig` and `RollupConfig` for the L2 chain being upgraded. +* Upgrades `MIPS.sol` to support the `F_GETFD` syscall, required by the golang 1.22+ runtime. + +See the [overview](./OVERVIEW.md) and `input.json` bundle for more details. diff --git a/tasks/eth/base-003-holocene-fp-upgrade/VALIDATION.md b/tasks/eth/base-003-holocene-fp-upgrade/VALIDATION.md new file mode 100644 index 000000000..efcb7e918 --- /dev/null +++ b/tasks/eth/base-003-holocene-fp-upgrade/VALIDATION.md @@ -0,0 +1,48 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the upgrade +transactions. + +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. + +## Nested Safe State Overrides and Changes + +This task is executed by the nested 2/2 `ProxyAdminOwner` Safe. Refer to the +[generic nested Safe execution validation document](../../../NESTED-VALIDATION.md) +for the expected state overrides and changes. + +The `approvedHashes` mapping **key** of the `ProxyAdminOwner` that should change during the simulation is +- Council simulation: `0xca167e305357972ad4bb8d1353a317081afef9619b4f62f7173b0a2c18a5641b` +- Foundation simulation: `0xb6dd08d9ebb19e58638adf5c77f5bafb0340199b1b0e88b6cc82442a99461b73` + +calculated as explained in the nested validation doc: +```sh +SAFE_HASH=0xbf85fbc223d92f056c8a48975db99c6b2153fbcf78d6c48109ef9774c6e272a6 # "Nested hash:" +SAFE_ROLE=0x9855054731540A48b28990B63DcF4f33d8AE46A1 # "Council" - Base +cast index bytes32 $SAFE_HASH $(cast index address $SAFE_ROLE 8) +# 0xca167e305357972ad4bb8d1353a317081afef9619b4f62f7173b0a2c18a5641b + +SAFE_ROLE=0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A # Foundation Operations Safe +cast index bytes32 $SAFE_HASH $(cast index address $SAFE_ROLE 8) +# 0xb6dd08d9ebb19e58638adf5c77f5bafb0340199b1b0e88b6cc82442a99461b73 +``` + +## State Changes + +### `0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e` (`DisputeGameFactoryProxy`) + +- **Key**: `0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b`
+ **Before**: `0xCd3c0194db74C23807D4B90A5181e1B28cF7007C`
+ **After**: `0xc5f3677c3C56DB4031ab005a3C9c98e1B79D438e`
+ **Meaning**: Updates the CANNON game type implementation. You can verify which implementation is set using `cast call 0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e "gameImpls(uint32)(address)" 0`, where `0` is the [`CANNON` game type](https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.4.0/packages/contracts-bedrock/src/dispute/lib/Types.sol#L28). + Before this task has been executed, you will see that the returned address is `0xCd3c0194db74C23807D4B90A5181e1B28cF7007C`, matching the "Before" value of this slot, demonstrating this slot is storing the address of the CANNON implementation. + +- **Key**: `0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e`
+ **Before**: `0x19009dEBF8954B610f207D5925EEDe827805986e`
+ **After**: `0xF62c15e2F99d4869A925B8F57076cD85335832A2`
+ **Meaning**: Updates the PERMISSIONED_CANNON game type implementation. You can verify which implementation is set using `cast call 0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e "gameImpls(uint32)(address)" 1`, where `1` is the [`PERMISSIONED_CANNON` game type](https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.4.0/packages/contracts-bedrock/src/dispute/lib/Types.sol#L31). + Before this task has been executed, you will see that the returned address is `0x19009dEBF8954B610f207D5925EEDe827805986e`, matching the "Before" value of this slot, demonstrating this slot is storing the address of the PERMISSIONED_CANNON implementation. diff --git a/tasks/eth/base-003-holocene-fp-upgrade/input.json b/tasks/eth/base-003-holocene-fp-upgrade/input.json new file mode 100644 index 000000000..6f44bacc5 --- /dev/null +++ b/tasks/eth/base-003-holocene-fp-upgrade/input.json @@ -0,0 +1,67 @@ +{ + "chainId": 1, + "metadata": { + "name": "Base Mainnet Holocene - Proof Contract Upgrades", + "description": "Upgrades the `MIPS.sol`, `FaultDisputeGame.sol`, and `PermissionedDisputeGame.sol` contracts for Holocene." + }, + "transactions": [ + { + "metadata": { + "name": "Upgrade `PERMISSIONED_CANNON` game type in `DisputeGameFactory`", + "description": "Upgrades the `PERMISSIONED_CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash." + }, + "to": "0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f62c15e2f99d4869a925b8f57076cd85335832a2", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "1", + "_impl": "0xF62c15e2F99d4869A925B8F57076cD85335832A2" + } + }, + { + "metadata": { + "name": "Upgrade `CANNON` game type in `DisputeGameFactory`", + "description": "Upgrades the `CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash." + }, + "to": "0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5f3677c3c56db4031ab005a3c9c98e1b79d438e", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0", + "_impl": "0xc5f3677c3C56DB4031ab005a3C9c98e1B79D438e" + } + } + ] +} diff --git a/tasks/eth/ink-001-permissionless-proofs/.env b/tasks/eth/ink-001-permissionless-proofs/.env new file mode 100644 index 000000000..16ba0a21c --- /dev/null +++ b/tasks/eth/ink-001-permissionless-proofs/.env @@ -0,0 +1,16 @@ +ETH_RPC_URL="https://ethereum.publicnode.com" +COUNCIL_SAFE=0xc2819DC788505Aac350142A7A707BF9D03E3Bd03 +FOUNDATION_SAFE=0x847B5c174615B1B7fDF770882256e2D3E95b9D92 +SAFE_NONCE_0X5A0AAE59D09FCCBDDB6C6CCEB07B7279367C3D2A=7 +SAFE_NONCE_0XC2819DC788505AAC350142A7A707BF9D03E3BD03=9 +SAFE_NONCE_0X847B5C174615B1B7FDF770882256E2D3E95B9D92=12 +L1_CHAIN_NAME="mainnet" +L2_CHAIN_NAME="ink" +SYSTEM_CONFIG="0x62c0a111929fa32cec2f76adba54c16afb6e8364" +OWNER_SAFE="0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A" +PROXY_ADMIN="0xd56045e68956fce2576e680c95a4750cf8241f79" +ANCHOR_STATE_REGISTRY="0xde744491BcF6b2DD2F32146364Ea1487D75E2509" +NEW_FAULT_DISPUTE_GAME_IMPL="0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD" +NEW_PERMISSIONED_DISPUTE_GAME_IMPL="0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963" +NEW_ANCHOR_STATE_ROOT="0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a" +NEW_ANCHOR_STATE_BLOCK_NUMBER=523052 diff --git a/tasks/eth/ink-001-permissionless-proofs/NestedSignFromJson.s.sol b/tasks/eth/ink-001-permissionless-proofs/NestedSignFromJson.s.sol new file mode 100644 index 000000000..2ba012bff --- /dev/null +++ b/tasks/eth/ink-001-permissionless-proofs/NestedSignFromJson.s.sol @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {NestedSignFromJson as OriginalNestedSignFromJson} from "script/NestedSignFromJson.s.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {Vm, VmSafe} from "forge-std/Vm.sol"; +import {GnosisSafe} from "safe-contracts/GnosisSafe.sol"; +import {LibString} from "solady/utils/LibString.sol"; +import {Types} from "@eth-optimism-bedrock/scripts/Types.sol"; +import "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; +import {AnchorStateRegistry} from "@eth-optimism-bedrock/src/dispute/AnchorStateRegistry.sol"; +import {DisputeGameFactory} from "@eth-optimism-bedrock/src/dispute/DisputeGameFactory.sol"; +import {FaultDisputeGame} from "@eth-optimism-bedrock/src/dispute/FaultDisputeGame.sol"; +import {PermissionedDisputeGame} from "@eth-optimism-bedrock/src/dispute/PermissionedDisputeGame.sol"; +import {SystemConfig} from "@eth-optimism-bedrock/src/L1/SystemConfig.sol"; + +contract NestedSignFromJson is OriginalNestedSignFromJson { + using LibString for string; + + // Chains for this task. + string l1ChainName = vm.envString("L1_CHAIN_NAME"); + string l2ChainName = vm.envString("L2_CHAIN_NAME"); + + // Safe contract for this task. + GnosisSafe ownerSafe = GnosisSafe(payable(vm.envAddress("OWNER_SAFE"))); + GnosisSafe councilSafe = GnosisSafe(payable(vm.envAddress("COUNCIL_SAFE"))); + GnosisSafe foundationSafe = GnosisSafe(payable(vm.envAddress("FOUNDATION_SAFE"))); + + // The slot used to store the livenessGuard address in GnosisSafe. + // See https://github.com/safe-global/safe-smart-account/blob/186a21a74b327f17fc41217a927dea7064f74604/contracts/base/GuardManager.sol#L30 + bytes32 livenessGuardSlot = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; + + SystemConfig systemConfig = SystemConfig(vm.envAddress("SYSTEM_CONFIG")); + AnchorStateRegistry anchorStateRegistryProxy = AnchorStateRegistry(vm.envAddress("ANCHOR_STATE_REGISTRY")); + address newFaultDisputeGameImpl = vm.envAddress("NEW_FAULT_DISPUTE_GAME_IMPL"); + address newPermissionedDisputeGameImpl = vm.envAddress("NEW_PERMISSIONED_DISPUTE_GAME_IMPL"); + bytes32 newAnchorStateRoot = vm.envBytes32("NEW_ANCHOR_STATE_ROOT"); + uint256 newAnchorStateBlockNumber = vm.envUint("NEW_ANCHOR_STATE_BLOCK_NUMBER"); + + uint256 initBond = 0.08 ether; + + // DisputeGameFactoryProxy address. + DisputeGameFactory dgfProxy; + + address[] extraStorageAccessAddresses; + + function setUp() public { + require(address(anchorStateRegistryProxy.disputeGameFactory()) == address(systemConfig.disputeGameFactory()), "setup-100"); + dgfProxy = DisputeGameFactory(systemConfig.disputeGameFactory()); + extraStorageAccessAddresses.push(address(anchorStateRegistryProxy)); + _precheckAnchorStateCopy(GameType.wrap(1), GameType.wrap(0)); + _precheckDisputeGameImplementation(GameType.wrap(0), newFaultDisputeGameImpl); + _precheckDisputeGameImplementation(GameType.wrap(1), newPermissionedDisputeGameImpl); + } + + function getCodeExceptions() internal view override returns (address[] memory) { + // Safe owners will appear in storage in the LivenessGuard when added, and they are allowed + // to have code AND to have no code. + address[] memory securityCouncilSafeOwners = councilSafe.getOwners(); + + // To make sure we probably handle all signers whether or not they have code, first we count + // the number of signers that have no code. + uint256 numberOfSafeSignersWithNoCode; + for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) { + if (securityCouncilSafeOwners[i].code.length == 0) { + numberOfSafeSignersWithNoCode++; + } + } + + // Then we extract those EOA addresses into a dedicated array. + uint256 trackedSignersWithNoCode; + address[] memory safeSignersWithNoCode = new address[](numberOfSafeSignersWithNoCode); + for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) { + if (securityCouncilSafeOwners[i].code.length == 0) { + safeSignersWithNoCode[trackedSignersWithNoCode] = securityCouncilSafeOwners[i]; + trackedSignersWithNoCode++; + } + } + + // Here we add the standard (non Safe signer) exceptions. + address[] memory shouldHaveCodeExceptions = new address[](numberOfSafeSignersWithNoCode); + // And finally, we append the Safe signer exceptions. + for (uint256 i = 0; i < safeSignersWithNoCode.length; i++) { + shouldHaveCodeExceptions[i] = safeSignersWithNoCode[i]; + } + + return shouldHaveCodeExceptions; + } + + // _precheckDisputeGameImplementation checks that the new game being set has the same configuration as the existing + // implementation with the exception of the absolutePrestate. This is the most common scenario where the game + // implementation is upgraded to provide an updated fault proof program that supports an upcoming hard fork. + function _precheckDisputeGameImplementation(GameType _targetGameType, address _newImpl) internal view { + console.log("pre-check new game implementations", _targetGameType.raw()); + + FaultDisputeGame currentImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_targetGameType)))); + // No checks are performed if there is no prior implementation. + // When deploying the first implementation, it is recommended to implement custom checks. + if (address(currentImpl) == address(0)) { + return; + } + FaultDisputeGame faultDisputeGame = FaultDisputeGame(_newImpl); + require(address(currentImpl.vm()) == address(faultDisputeGame.vm()), "10"); + require(address(currentImpl.weth()) == address(faultDisputeGame.weth()), "20"); + require(address(currentImpl.anchorStateRegistry()) == address(faultDisputeGame.anchorStateRegistry()), "30"); + require(currentImpl.l2ChainId() == faultDisputeGame.l2ChainId(), "40"); + require(currentImpl.splitDepth() == faultDisputeGame.splitDepth(), "50"); + require(currentImpl.maxGameDepth() == faultDisputeGame.maxGameDepth(), "60"); + require(uint64(Duration.unwrap(currentImpl.maxClockDuration())) == uint64(Duration.unwrap(faultDisputeGame.maxClockDuration())), "70"); + require(uint64(Duration.unwrap(currentImpl.clockExtension())) == uint64(Duration.unwrap(faultDisputeGame.clockExtension())), "80"); + + if (_targetGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) { + PermissionedDisputeGame currentPDG = PermissionedDisputeGame(address(currentImpl)); + PermissionedDisputeGame permissionedDisputeGame = PermissionedDisputeGame(address(faultDisputeGame)); + require(address(currentPDG.proposer()) == address(permissionedDisputeGame.proposer()), "90"); + require(address(currentPDG.challenger()) == address(permissionedDisputeGame.challenger()), "100"); + } + } + + function _precheckAnchorStateCopy(GameType _fromType, GameType _toType) internal view { + console.log("pre-check anchor state copy", _toType.raw()); + + FaultDisputeGame fromImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_fromType)))); + // Must have existing game type implementation for the source + require(address(fromImpl) != address(0), "200"); + address fromRegistry = address(fromImpl.anchorStateRegistry()); + require(fromRegistry != address(0), "210"); + + FaultDisputeGame toImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_toType)))); + if (address(toImpl) != address(0)) { + // If there is an existing implementation, it must use the same anchor state registry. + address toRegistry = address(toImpl.anchorStateRegistry()); + require(toRegistry == fromRegistry, "210"); + } + } + + function getAllowedStorageAccess() internal view override returns (address[] memory allowed) { + allowed = new address[](5 + extraStorageAccessAddresses.length); + allowed[0] = address(dgfProxy); + allowed[1] = address(ownerSafe); + allowed[2] = address(councilSafe); + allowed[3] = address(foundationSafe); + address livenessGuard = address(uint160(uint256(vm.load(address(councilSafe), livenessGuardSlot)))); + allowed[4] = livenessGuard; + + for (uint256 i = 0; i < extraStorageAccessAddresses.length; i++) { + allowed[5 + i] = extraStorageAccessAddresses[i]; + } + return allowed; + } + + /// @notice Checks the correctness of the deployment + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override { + console.log("Running post-deploy assertions"); + + checkStateDiff(accesses); + _postcheckAnchorStateCopy(GameType.wrap(0), newAnchorStateRoot, newAnchorStateBlockNumber); + _postcheckHasAnchorState(GameType.wrap(1)); + _checkDisputeGameImplementation(GameType.wrap(0), newFaultDisputeGameImpl); + _checkDisputeGameImplementation(GameType.wrap(1), newPermissionedDisputeGameImpl); + _checkInitBonds(); + + console.log("All assertions passed!"); + } + + function _checkDisputeGameImplementation(GameType _targetGameType, address _newImpl) internal view { + console.log("check dispute game implementations", _targetGameType.raw()); + + require(_newImpl == address(dgfProxy.gameImpls(_targetGameType)), "check-100"); + } + + function _checkInitBonds() internal view { + console.log("check the initial bonds"); + + require(dgfProxy.initBonds(GameType.wrap(0)) == initBond, "check-bond-100"); + require(dgfProxy.initBonds(GameType.wrap(1)) == initBond, "check-bond-200"); + } + + function _postcheckAnchorStateCopy(GameType _gameType, bytes32 _root, uint256 _l2BlockNumber) internal view { + console.log("check anchor state value", _gameType.raw()); + + FaultDisputeGame impl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_gameType)))); + (Hash root, uint256 rootBlockNumber) = FaultDisputeGame(address(impl)).anchorStateRegistry().anchors(_gameType); + + require(root.raw() == _root, "check-200"); + require(rootBlockNumber == _l2BlockNumber, "check-210"); + } + + // @notice Checks the anchor state for the source game type still exists after re-initialization. + // The actual anchor state may have been updated since the task was defined so just assert it exists, not that + // it has a specific value. + function _postcheckHasAnchorState(GameType _gameType) internal view { + console.log("check anchor state exists", _gameType.raw()); + + FaultDisputeGame impl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_gameType)))); + (Hash root, uint256 rootBlockNumber) = FaultDisputeGame(address(impl)).anchorStateRegistry().anchors(_gameType); + + require(root.raw() != bytes32(0), "check-300"); + require(rootBlockNumber != 0, "check-310"); + } +} diff --git a/tasks/eth/ink-001-permissionless-proofs/README.md b/tasks/eth/ink-001-permissionless-proofs/README.md new file mode 100644 index 000000000..33b2e1dfb --- /dev/null +++ b/tasks/eth/ink-001-permissionless-proofs/README.md @@ -0,0 +1,33 @@ +# ProxyAdminOwner - Set Dispute Game Implementation + +Status: READY TO SIGN + +## Objective + +This task updates the fault dispute system for ink-mainnet: + +* Re-initialize `AnchorStateRegistry` 0xde744491BcF6b2DD2F32146364Ea1487D75E2509 with the anchor state for game types 0 set to 0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a, 523052 +* Set implementation for game type 0 to 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD in `DisputeGameFactory` 0x10d7B35078d3baabB96Dd45a9143B94be65b12CD: `setImplementation(0, 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD)` +* Set implementation for game type 1 to 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963 in `DisputeGameFactory` 0x10d7B35078d3baabB96Dd45a9143B94be65b12CD: `setImplementation(1, 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963)` +* Sets the initial bonds in the `DisputeGameFactory` for game type 0 and 1 to 0.08 ETH. **Important: the proposer will now need to be bonded for the permissioned games.** + +## Pre-deployments + +- `FaultDisputeGame` - [0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD](https://etherscan.io/address/0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD) +- `PermissionedDisputeGame` - [0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963](https://etherscan.io/address/0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963) + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [NESTED.md](../../../NESTED.md). + +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/ink-001-permissionless-proofs/NestedSignFromJson.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. + +Do NOT yet proceed to the "Execute the Transaction" section. + +## Signing and execution + +Please see the signing and execution instructions in [NESTED.md](../../../NESTED.md). + +### State Validations + +Please see the instructions for [validation](./VALIDATION.md). diff --git a/tasks/eth/ink-001-permissionless-proofs/VALIDATION.md b/tasks/eth/ink-001-permissionless-proofs/VALIDATION.md new file mode 100644 index 000000000..eb1c7e746 --- /dev/null +++ b/tasks/eth/ink-001-permissionless-proofs/VALIDATION.md @@ -0,0 +1,113 @@ +# 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 Overrides + +The following state overrides should be seen: + +### `0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A` (The 2/2 `ProxyAdmin` Owner) + +Links: +- [Etherscan](https://etherscan.io/address/0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A) + +Overrides: + +- **Key:** `0x0000000000000000000000000000000000000000000000000000000000000004`
+ **Value:** `0x0000000000000000000000000000000000000000000000000000000000000001`
+ **Meaning:** Enables the simulation by setting the threshold to 1. The key can be validated by the location of the `threshold` variable in the [Safe's Storage Layout](https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/examples/libraries/GnosisSafeStorage.sol#L14). + +### `0xc2819DC788505Aac350142A7A707BF9D03E3Bd03` (Council Safe) or `0x847B5c174615B1B7fDF770882256e2D3E95b9D92` (Foundation Safe) + +Links: +- [Etherscan (Council Safe)](https://etherscan.io/address/0xc2819DC788505Aac350142A7A707BF9D03E3Bd03). This address is attested to in the [Optimism docs](https://docs.optimism.io/chain/security/privileged-roles#l1-proxy-admin), as it's one of the signers of the L1 Proxy Admin owner. +- [Etherscan (Foundation Safe)](https://etherscan.io/address/0x847B5c174615B1B7fDF770882256e2D3E95b9D92). This address is attested to in the [Optimism docs](https://docs.optimism.io/chain/security/privileged-roles#l1-proxy-admin), as it's one of the signers of the L1 Proxy Admin owner. + +The Safe you are signing for will have the following overrides which will set the [Multicall](https://etherscan.io/address/0xca11bde05977b3631167028862be2a173976ca11#code) contract as the sole owner of the signing safe. This allows simulating both the approve hash and the final tx in a single Tenderly tx. + +- **Key:** 0x0000000000000000000000000000000000000000000000000000000000000003
+ **Value:** 0x0000000000000000000000000000000000000000000000000000000000000001
+ **Meaning:** The number of owners is set to 1. The key can be validated by the location of the `ownerCount` variable in the [Safe's Storage Layout](https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/examples/libraries/GnosisSafeStorage.sol#L13). + +- **Key:** 0x0000000000000000000000000000000000000000000000000000000000000004
+ **Value:** 0x0000000000000000000000000000000000000000000000000000000000000001
+ **Meaning:** The threshold is set to 1. The key can be validated by the location of the `threshold` variable in the [Safe's Storage Layout](https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/examples/libraries/GnosisSafeStorage.sol#L14). + +The following two overrides are modifications to the [`owners` mapping](https://github.com/safe-global/safe-contracts/blob/v1.3.0/contracts/examples/libraries/GnosisSafeStorage.sol#L12). For the purpose of calculating the storage, note that this mapping is in slot `2`. +This mapping implements a linked list for iterating through the list of owners. Since we'll only have one owner (Multicall), and the `0x01` address is used as the first and last entry in the linked list, we will see the following overrides: +- `owners[1] -> 0xca11bde05977b3631167028862be2a173976ca11` +- `owners[0xca11bde05977b3631167028862be2a173976ca11] -> 1` + +And we do indeed see these entries: + +- **Key:** 0x316a0aac0d94f5824f0b66f5bbe94a8c360a17699a1d3a233aafcf7146e9f11c
+ **Value:** 0x0000000000000000000000000000000000000000000000000000000000000001
+ **Meaning:** This is `owners[0xca11bde05977b3631167028862be2a173976ca11] -> 1`, so the key can be + derived from `cast index address 0xca11bde05977b3631167028862be2a173976ca11 2`. + +- **Key:** 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0
+ **Value:** 0x000000000000000000000000ca11bde05977b3631167028862be2a173976ca11
+ **Meaning:** This is `owners[1] -> 0xca11bde05977b3631167028862be2a173976ca11`, so the key can be + derived from `cast index address 0x0000000000000000000000000000000000000001 2`. + +## State Changes + +Note: The changes listed below do not include safe nonce updates or liveness guard related changes. + +### `0x10d7B35078d3baabB96Dd45a9143B94be65b12CD` (`DisputeGameFactoryProxy`) + +- **Key**: `0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e`
+ **Before**: `0x000000000000000000000000a8e6a9bf1ba2df76c6787eaebe2273ae98498059`
+ **After**: `0x0000000000000000000000000A780bE3eB21117b1bBCD74cf5D7624A3a482963`
+ **Meaning**: Updates the implementation for game type 1. Verify that the slot is correct using `cast index uint 1 101` where 1 is the game type and 101 is the [storage slot](https://github.com/ethereum-optimism/optimism/blob/33f06d2d5e4034125df02264a5ffe84571bd0359/packages/contracts-bedrock/snapshots/storageLayout/DisputeGameFactory.json#L41) of the `gameImpls` mapping. + +- **Key**: `0x6f48904484b35701cf1f41ad9068b394adf7e2f8a59d2309a04d10a155eaa72b`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000`
+ **After**: `0x000000000000000000000000000000000000000000000000011c37937e080000`
+ **Meanning**: Updates the `FaultDisputeGame` initial bond amount to 0.08 ETH. Verify that the slot is correct using `cast index uint 0 102`. Where `0` is the game type and 102 is the [storage slot](https://github.com/ethereum-optimism/optimism/blob/33f06d2d5e4034125df02264a5ffe84571bd0359/packages/contracts-bedrock/snapshots/storageLayout/DisputeGameFactory.json#L48). + +- **Key**: `0xe34b8b74e1cdcaa1b90aa77af7dd89e496ad9a4ae4a4d4759712101c7da2dce6`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000`
+ **After**: `0x000000000000000000000000000000000000000000000000011c37937e080000`
+ **Meanning**: Updates the `PermissionedDisputeGame` initial bond amount to 0.08 ETH. Verify that the slot is correct using `cast index uint 1 102`. Where `1` is the game type and 102 is the [storage slot](https://github.com/ethereum-optimism/optimism/blob/33f06d2d5e4034125df02264a5ffe84571bd0359/packages/contracts-bedrock/snapshots/storageLayout/DisputeGameFactory.json#L48). + +- **Key**: `0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000`
+ **After**: `0x0000000000000000000000006A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD`
+ **Meaning**: Updates the implementation for game type 0. Verify that the slot is correct using `cast index uint 0 101` where 0 is the game type and 101 is the [storage slot](https://github.com/ethereum-optimism/optimism/blob/33f06d2d5e4034125df02264a5ffe84571bd0359/packages/contracts-bedrock/snapshots/storageLayout/DisputeGameFactory.json#L41) of the `gameImpls` mapping. + +### `0xde744491BcF6b2DD2F32146364Ea1487D75E2509` (`AnchorStateRegistryProxy`) + +- **Key**: `0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000` (Note this may have changed if games of this type resolved)
+ **After**: `0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a`
+ **Meaning**: Set the anchor state output root for game type 0 to 0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a. This is the slot for the `anchors` mapping, which can be computed as `cast index uint 0 1`, where 0 is the game type and 1 is the slot of the `anchors` mapping. If you have access to the Ink Mainnet's op-node RPC endpoint and want to verify the new root, you can use the following command to verify: + ``` + cast rpc --rpc-url $OP_NODE_RPC "optimism_outputAtBlock" $(cast th 523052) + ``` + +- **Key**: `0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb4a`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000` (Note this may have changed if games of this type resolved)
+ **After**: `0x000000000000000000000000000000000000000000000000000000000007fb2c`
+ **Meaning**: Set the anchor state L2 block number for game type 0 to 523052. The slot number can be calculated using the same approach as above, and incremented by 1, based on the contract storage layout + +### Safe Contract State Changes + +The only other state changes should be restricted to one of the following addresses: + +- L1 2/2 ProxyAdmin Owner Safe: `0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A` + - The nonce (slot 0x5) should be increased from 7 to 8. + - Another key is set from 0 to 1 reflecting an entry in the `approvedHashes` mapping. +- Security Council L1 Safe: `0xc2819DC788505Aac350142A7A707BF9D03E3Bd03` + - The nonce (slot 0x5) should be increased from 9 to 10. This only occurs for Council signers. +- Foundation Safe: `0x847B5c174615B1B7fDF770882256e2D3E95b9D92` + - The nonce (slot 0x5) should be increased from 12 to 13. This only occurs for Foundation signers. diff --git a/tasks/eth/ink-001-permissionless-proofs/input.json b/tasks/eth/ink-001-permissionless-proofs/input.json new file mode 100644 index 000000000..2885f7f41 --- /dev/null +++ b/tasks/eth/ink-001-permissionless-proofs/input.json @@ -0,0 +1,195 @@ +{ + "metadata": { + "name": "ProxyAdminOwner - Set Dispute Game Implementation", + "description": "Re-initialize with anchor states for game types 0 set to 0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a, 523052 Sets the implementation for game type 0 to 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD in the `DisputeGameFactory`. Sets the implementation for game type 1 to 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963 in the `DisputeGameFactory`. " + }, + "transactions": [ + { + "metadata": { + "name": "Upgrade AnchorStateRegistry to StorageSetter and clear legacy initialized slot", + "description": "By clearing the initialized slot, we can call initialize again to set an anchor state for the new game type" + }, + "to": "0xd56045e68956fce2576e680c95a4750cf8241f79", + "value": "0x0", + "data": "0x9623609d000000000000000000000000de744491bcf6b2dd2f32146364ea1487d75e2509000000000000000000000000d81f43edbcacb4c29a9ba38a13ee5d79278270cc000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000444e91db080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "contractMethod": { + "type": "function", + "name": "upgradeAndCall", + "inputs": [ + { + "name": "_proxy", + "type": "address" + }, + { + "name": "_implementation", + "type": "address" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_proxy": "0xde744491BcF6b2DD2F32146364Ea1487D75E2509", + "_implementation": "0xd81f43eDBCAcb4c29a9bA38a13Ee5d79278270cC", + "_data": "0x4e91db0800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "metadata": { + "name": "Re-initialize the AnchorStateRegistryProxy", + "description": "Re-initialize with anchor states for game types 0 set to 0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a, 523052" + }, + "to": "0xd56045e68956fce2576e680c95a4750cf8241f79", + "value": "0x0", + "data": "0x9623609d000000000000000000000000de744491bcf6b2dd2f32146364ea1487d75e25090000000000000000000000007a78aa7d5dec2f8b368ca13f00df2fa4e5de3c3f000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c45e05fbd0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000095703e0982140d16f8eba6d158fccede42f04a4c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000005220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a000000000000000000000000000000000000000000000000000000000007fb2c00000000000000000000000000000000000000000000000000000000", + "contractMethod": { + "type": "function", + "name": "upgradeAndCall", + "inputs": [ + { + "internalType": "address", + "name": "_proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "_implementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_proxy": "0xde744491BcF6b2DD2F32146364Ea1487D75E2509", + "_implementation": "0x7A78aa7D5dec2F8B368ca13f00Df2fA4E5De3C3F", + "_data": "0x5e05fbd0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000095703e0982140d16f8eba6d158fccede42f04a4c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000005220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a000000000000000000000000000000000000000000000000000000000007fb2c" + } + }, + { + "metadata": { + "name": "Set implementation for game type", + "description": "Sets the implementation for game type 0 to 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD in the `DisputeGameFactory`." + }, + "to": "0x10d7B35078d3baabB96Dd45a9143B94be65b12CD", + "value": "0x0", + "data": "0x14f6b1a300000000000000000000000000000000000000000000000000000000000000000000000000000000000000006a8efcba5642eb15d743cbb29545bdc44d5ad8cd", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0", + "_impl": "0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD" + } + }, + { + "metadata": { + "name": "Set implementation for game type", + "description": "Sets the implementation for game type 1 to 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963 in the `DisputeGameFactory`." + }, + "to": "0x10d7B35078d3baabB96Dd45a9143B94be65b12CD", + "value": "0x0", + "data": "0x14f6b1a300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000a780be3eb21117b1bbcd74cf5d7624a3a482963", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "1", + "_impl": "0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963" + } + }, + { + "metadata": { + "name": "Sets the bond value for permissioned game type", + "description": "This sets the initial bond to 0.08 ETH to deploy a PermissionedDisputeGame." + }, + "to": "0x10d7B35078d3baabB96Dd45a9143B94be65b12CD", + "value": "0x0", + "data": "0x1e3342400000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000011c37937e080000", + "contractMethod": { + "type": "function", + "name": "setInitBond", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_initBond", + "type": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "1", + "_initBond": "80000000000000000" + } + }, + { + "metadata": { + "name": "Sets the bond value for permissionless", + "description": "This sets the initial bond to 0.08 ETH to deploy a FaultDisputeGame." + }, + "to": "0x10d7B35078d3baabB96Dd45a9143B94be65b12CD", + "value": "0x0", + "data": "0x1e3342400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011c37937e080000", + "contractMethod": { + "type": "function", + "name": "setInitBond", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_initBond", + "type": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0", + "_initBond": "80000000000000000" + } + } + ] +} diff --git a/tasks/eth/ink-002-set-respected-game-type/.env b/tasks/eth/ink-002-set-respected-game-type/.env new file mode 100644 index 000000000..a7ac99728 --- /dev/null +++ b/tasks/eth/ink-002-set-respected-game-type/.env @@ -0,0 +1,5 @@ +ETH_RPC_URL="https://ethereum.publicnode.com" +#`OWNER_SAFE` represents the address of the `deputyGuardian` gnosis safe attached to the `DeputyGuardianModule` +OWNER_SAFE=0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A # guardian multisig +SAFE_NONCE_0X9BA6E03D8B90DE867373DB8CF1A58D2F7F006B3A=98 +L2_CHAIN_NAME="ink" diff --git a/tasks/eth/ink-002-set-respected-game-type/README.md b/tasks/eth/ink-002-set-respected-game-type/README.md new file mode 100644 index 000000000..660f649c9 --- /dev/null +++ b/tasks/eth/ink-002-set-respected-game-type/README.md @@ -0,0 +1,12 @@ +# Deputy Guardian - Enable Permissioness Dispute Game + +Status: READY TO SIGN + +## Objective + +> [!WARNING] +> The first task, `ink-001-permissionless-proofs`, needs to be executed first to set the game implementations. +> This task should only be executed after the permissionless fault proofs have been tested in the cold path and the chain operator is ready to enable permissionless proofs. + +This task updates the `respectedGameType` in the `OptimismPortalProxy` to `CANNON`, enabling users to permissionlessly propose outputs as well as for anyone to participate in the dispute of these proposals. This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` that was created after this update occurs. To execute, collect signatures and execute the action according to the instructions in [SINGLE.md](../../../../SINGLE.md). + diff --git a/tasks/eth/ink-002-set-respected-game-type/SignFromJson.s.sol b/tasks/eth/ink-002-set-respected-game-type/SignFromJson.s.sol new file mode 100644 index 000000000..490b23f43 --- /dev/null +++ b/tasks/eth/ink-002-set-respected-game-type/SignFromJson.s.sol @@ -0,0 +1,72 @@ + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {SignFromJson as OriginalSignFromJson} from "script/SignFromJson.s.sol"; +import {OptimismPortal2, IDisputeGame} from "@eth-optimism-bedrock/src/L1/OptimismPortal2.sol"; +import {Types} from "@eth-optimism-bedrock/scripts/Types.sol"; +import {Vm, VmSafe} from "forge-std/Vm.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {LibString} from "solady/utils/LibString.sol"; +import {GnosisSafe} from "safe-contracts/GnosisSafe.sol"; +import "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + + // Chains for this task. + string constant l1ChainName = "mainnet"; + string l2ChainName = vm.envString("L2_CHAIN_NAME"); + + Types.ContractSet proxies; + + /// @notice Sets up the contract + function setUp() public { + proxies = _getContractSet(); + } + + function checkRespectedGameType() internal view { + OptimismPortal2 portal = OptimismPortal2(payable(proxies.OptimismPortal)); + require(portal.respectedGameType().raw() == GameTypes.CANNON.raw()); + } + + function getAllowedStorageAccess() internal view override returns (address[] memory allowed) { + allowed = new address[](2); + allowed[0] = proxies.OptimismPortal; + allowed[1] = vm.envAddress("OWNER_SAFE"); + } + + /// @notice Checks the correctness of the deployment + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory /* simPayload */ ) + internal + view + override + { + console.log("Running post-deploy assertions"); + + checkStateDiff(accesses); + checkRespectedGameType(); + + console.log("All assertions passed!"); + } + + /// @notice Reads the contract addresses from lib/superchain-registry/superchain/configs/${l1ChainName}/${l2ChainName}.toml + function _getContractSet() internal view returns (Types.ContractSet memory _proxies) { + string memory chainConfig; + + // Read chain-specific config toml file + string memory path = string.concat( + "/lib/superchain-registry/superchain/configs/", l1ChainName, "/", l2ChainName, ".toml" + ); + try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) { + chainConfig = data; + } catch { + revert(string.concat("Failed to read ", path)); + } + + // Read the chain-specific OptimismPortalProxy address + _proxies.OptimismPortal = stdToml.readAddress(chainConfig, "$.addresses.OptimismPortalProxy"); + } +} diff --git a/tasks/eth/ink-002-set-respected-game-type/VALIDATION.md b/tasks/eth/ink-002-set-respected-game-type/VALIDATION.md new file mode 100644 index 000000000..b8a3b4080 --- /dev/null +++ b/tasks/eth/ink-002-set-respected-game-type/VALIDATION.md @@ -0,0 +1,41 @@ +# 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 Overrides + +The following state overrides should be seen: + +### `0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A` (The 2/2 `ProxyAdmin` Owner) + +Links: +- [Etherscan](https://etherscan.io/address/0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A) + +Overrides: + +- **Key:** `0x0000000000000000000000000000000000000000000000000000000000000004`
+ **Value:** `0x0000000000000000000000000000000000000000000000000000000000000001`
+ **Meaning:** Enables the simulation by setting the threshold to 1. The key can be validated by the location of the `threshold` variable in the [Safe's Storage Layout](https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/examples/libraries/GnosisSafeStorage.sol#L14). + +## State Changes + + +### `0x5d66c1782664115999c47c9fa5cd031f495d3e4f` (`OptimismPortalProxy`) + +- **Key**: `0x000000000000000000000000000000000000000000000000000000000000003b`
+ **Before**: `0x0000000000000000000000000000000000000000000000006753162b00000001`
+ **After**: `0x00000000000000000000000000000000000000000000000TIMESTAMP00000000`
+ **Description**: Sets the `respectedGameType` to `0` (permissionless cannon game) and sets the `respectedGameTypeUpdatedAt` timestamp to the time when the upgrade transaction was executed (this will be a dynamic value). + +Additionally, the following nonce changes are present: +- The Foundation safe, `0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A`, has a nonce increment from 98 to 99 +- The address of your account should show a nonce increment diff --git a/tasks/eth/ink-002-set-respected-game-type/images/state_diff.png b/tasks/eth/ink-002-set-respected-game-type/images/state_diff.png new file mode 100644 index 000000000..6604fb6d7 Binary files /dev/null and b/tasks/eth/ink-002-set-respected-game-type/images/state_diff.png differ diff --git a/tasks/eth/ink-002-set-respected-game-type/input.json b/tasks/eth/ink-002-set-respected-game-type/input.json new file mode 100644 index 000000000..471d1adb8 --- /dev/null +++ b/tasks/eth/ink-002-set-respected-game-type/input.json @@ -0,0 +1,38 @@ +{ + "chainId": 1, + "metadata": { + "name": "Deputy Guardian - Enable Permissionless Dispute Game", + "description": "This task updates the `respectedGameType` in the `OptimismPortal` to `CANNON`, enabling users to permissionlessly propose outputs as well as for anyone to participate in the dispute of these proposals. This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` that was created after this update occurs." + }, + "transactions": [ + { + "metadata": { + "name": "Update `respectedGameType` in the `OptimismPortal`", + "description": "Update the `respectedGameType` to `CANNON` in the `OptimismPortal`, enabling permissionless proposals and challenging." + }, + "to": "0xc6901F65369FC59fC1B4D6D6bE7A2318Ff38dB5B", + "value": "0x0", + "data": "0xa1155ed90000000000000000000000005d66c1782664115999c47c9fa5cd031f495d3e4f0000000000000000000000000000000000000000000000000000000000000000", + "contractMethod": { + "type": "function", + "name": "setRespectedGameType", + "inputs": [ + { + "name": "_portal", + "type": "address" + }, + { + "name": "_gameType", + "type": "uint32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_portal": "0x5d66c1782664115999c47c9fa5cd031f495d3e4f", + "_gameType": "0" + } + } + ] +} \ No newline at end of file diff --git a/tasks/sep/026-fp-holocene-upgrade-fix/.env b/tasks/sep/026-fp-holocene-upgrade-fix/.env new file mode 100644 index 000000000..af3abe0e7 --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/.env @@ -0,0 +1,14 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0x1Eb2fFc903729a0F03966B917003800b145F56E2 +COUNCIL_SAFE=0xf64bc17485f0B4Ea5F06A96514182FC4cB561977 +FOUNDATION_SAFE=0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B + +SAFE_NONCE_0X1EB2FFC903729A0F03966B917003800B145F56E2=15 # noop +SAFE_NONCE_0XF64BC17485F0B4EA5F06A96514182FC4CB561977=21 # noop +SAFE_NONCE_0XDEE57160AAFCF04C34C887B5962D0A69676D3C8B=29 # noop + +# override since we use MIPS64, which is not in the SCR yet +SCR_OVERRIDE_MIPS_ADDRESS=0xa1e470b6bd25e8eea9ffcda6a1518be5eb8ee7bb +SCR_OVERRIDE_MIPS_VERSION=1.0.0-beta.7 +# we change the WETH in this task, which is non-standard +DISPUTE_GAME_CHANGE_WETH=true diff --git a/tasks/sep/026-fp-holocene-upgrade-fix/NestedSignFromJson.s.sol b/tasks/sep/026-fp-holocene-upgrade-fix/NestedSignFromJson.s.sol new file mode 100644 index 000000000..bd142d092 --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/NestedSignFromJson.s.sol @@ -0,0 +1,57 @@ +// 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 {stdJson} from "forge-std/StdJson.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.sol"; +import {NestedSignFromJson as OriginalNestedSignFromJson} from "script/NestedSignFromJson.s.sol"; +import {DisputeGameUpgrade} from "script/verification/DisputeGameUpgrade.s.sol"; +import {CouncilFoundationNestedSign} from "script/verification/CouncilFoundationNestedSign.s.sol"; +import {SuperchainRegistry} from "script/verification/Verification.s.sol"; + +contract NestedSignFromJson is OriginalNestedSignFromJson, CouncilFoundationNestedSign, DisputeGameUpgrade { + constructor() + SuperchainRegistry("sepolia", "op", "v1.8.0-rc.4") + DisputeGameUpgrade( + 0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b, // absolutePrestate + 0x833a817eF459f4eCdB83Fc5A4Bf04d09A4e83f3F, // faultDisputeGame + 0xbBD576128f71186A0f9ae2F2AAb4afb4aF2dae17 // permissionedDisputeGame + ) + {} + + function setUp() public view { + checkInput(); + } + + function checkInput() public view { + string memory inputJson; + string memory path = "/tasks/sep/026-fp-holocene-upgrade-fix/input.json"; + try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) { + inputJson = data; + } catch { + revert(string.concat("Failed to read ", path)); + } + + address inputPermissionedDisputeGame = + stdJson.readAddress(inputJson, "$.transactions[0].contractInputsValues._impl"); + address inputFaultDisputeGame = stdJson.readAddress(inputJson, "$.transactions[1].contractInputsValues._impl"); + require(expPermissionedDisputeGame == inputPermissionedDisputeGame, "input-pdg"); + require(expFaultDisputeGame == inputFaultDisputeGame, "input-fdg"); + } + + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override { + console.log("Running post-deploy assertions"); + checkStateDiff(accesses); + checkDisputeGameUpgrade(); + 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/026-fp-holocene-upgrade-fix/OVERVIEW.md b/tasks/sep/026-fp-holocene-upgrade-fix/OVERVIEW.md new file mode 100644 index 000000000..ea1d6fe90 --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/OVERVIEW.md @@ -0,0 +1,37 @@ +# Holocene Hardfork - Proof Contract Upgrades +Upgrades the `MIPS.sol`, `FaultDisputeGame.sol`, and `PermissionedDisputeGame.sol` contracts for Holocene. + +The batch will be executed on chain ID `11155111`, and contains `2` transactions. + +## Tx #1: Upgrade `PERMISSIONED_CANNON` game type in `DisputeGameFactory` +Upgrades the `PERMISSIONED_CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bbd576128f71186a0f9ae2f2aab4afb4af2dae17` + +### Inputs +**_gameType:** `1` + +**_impl:** `0xbBD576128f71186A0f9ae2F2AAb4afb4aF2dae17` + + +## Tx #2: Upgrade `CANNON` game type in `DisputeGameFactory` +Upgrades the `CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833a817ef459f4ecdb83fc5a4bf04d09a4e83f3f` + +### Inputs +**_gameType:** `0` + +**_impl:** `0x833a817eF459f4eCdB83Fc5A4Bf04d09A4e83f3F` diff --git a/tasks/sep/026-fp-holocene-upgrade-fix/README.md b/tasks/sep/026-fp-holocene-upgrade-fix/README.md new file mode 100644 index 000000000..3034e7b39 --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/README.md @@ -0,0 +1,41 @@ +# Holocene Hardfork Upgrade + +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x02746854719a3022f15fd86cc611d28a87901ff90886e877f44bacb36508d256) + +## Objective + +Upgrades the Fault Proof contracts of **OP Sepolia** for Holocene, fixing an upgrade with wrongly +newly deployed `DelayedWETH` from #374, and then subsequent faulty upgrade to MIPS64-MT in #410, +which again used the wrong `DelayedWETH`. + +Using a MIPS64 absolute prestate hash of `0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b`. + +Governance post of the upgrade can be found at https://gov.optimism.io/t/upgrade-proposal-11-holocene-network-upgrade/9313. + +This upgrades the Fault Proof contracts in the +[op-contracts/v1.8.0-rc.4](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.4) release. + +## Pre-deployments + +- `MIPS64` - `0xa1e470b6bd25e8eea9ffcda6a1518be5eb8ee7bb` +- `FaultDisputeGame` - `0x833a817eF459f4eCdB83Fc5A4Bf04d09A4e83f3F` +- `PermissionedDisputeGame` - `0xbBD576128f71186A0f9ae2F2AAb4afb4aF2dae17` + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [NESTED.md](../../../NESTED.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/025-fp-holocene-upgrade-fix/NestedSignFromJson.s.sol`. +This ensures all safety checks are run. If the default `NestedSignFromJson.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 dispute game implementation of the `CANNON` and `PERMISSIONED_CANNON` game types to contain a `op-program` release for the Holocene hardfork, which contains + the Holocene fork implementation as well as a `ChainConfig` and `RollupConfig` for the L2 chain being upgraded. +* Upgrades `MIPS.sol` to support the `F_GETFD` syscall, required by the golang 1.22+ runtime. + +See the [overview](./OVERVIEW.md) and `input.json` bundle for more details. diff --git a/tasks/sep/026-fp-holocene-upgrade-fix/VALIDATION.md b/tasks/sep/026-fp-holocene-upgrade-fix/VALIDATION.md new file mode 100644 index 000000000..fa7b9892d --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/VALIDATION.md @@ -0,0 +1,49 @@ +# Validation + +This document can be used to validate the state overrides and diffs resulting from the execution of the FP 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. + +## Nested Safe State Overrides and Changes + +This task is executed by the nested 2/2 `ProxyAdminOwner` Safe. Refer to the +[generic nested Safe execution validation document](../../../NESTED-VALIDATION.md) +for the expected state overrides and changes. + +The `approvedHashes` mapping **key** of the `ProxyAdminOwner` that should change during the simulation is +- Council simulation: `0x27f38a0f9fb806fdf71a205880cdcbd83a0bfb9ae1769b6a4aa6545c53fa4527` +- Foundation simulation: `0xf50bc38a52b16adccfd799639dd5bfabf655cb35d68c44864186a208b29b37fb` + +calculated as explained in the nested validation doc: +```sh +SAFE_HASH=0x63962452498b3b28f8813e99890a00437a292fea8dca8db1fbb97c417e082879 # "Nested hash:" +SAFE_ROLE=0xf64bc17485f0B4Ea5F06A96514182FC4cB561977 # Council +cast index bytes32 $SAFE_HASH $(cast index address $SAFE_ROLE 8) +# 0x27f38a0f9fb806fdf71a205880cdcbd83a0bfb9ae1769b6a4aa6545c53fa4527 + +SAFE_ROLE=0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B # Foundation +cast index bytes32 $SAFE_HASH $(cast index address $SAFE_ROLE 8) +# 0xf50bc38a52b16adccfd799639dd5bfabf655cb35d68c44864186a208b29b37fb +``` + +## State Changes + +This section describes the specific state changes of this upgrade, not related to the nested Safe state changes. + +### `0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1` (`DisputeGameFactoryProxy`) + +- **Key**: `0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b`
+ **Before**: `0x000000000000000000000000924d3d3b3b16e74bab577e50d23b2a38990dd52c`
+ **After**: `0x000000000000000000000000833a817ef459f4ecdb83fc5a4bf04d09a4e83f3f`
+ **Meaning**: Updates the CANNON game type implementation. You can verify which implementation is set using `cast call 0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1 "gameImpls(uint32)(address)" 0`, where `0` is the [`CANNON` game type](https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.4.0/packages/contracts-bedrock/src/dispute/lib/Types.sol#L28). + Before this task has been executed, you will see that the returned address is `0x924D3d3B3b16E74bAb577e50d23b2a38990dD52C`, matching the "Before" value of this slot, demonstrating this slot is storing the address of the CANNON implementation. + +- **Key**: `0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e`
+ **Before**: `0x000000000000000000000000879e899523ba9a4ab212a2d70cf1af73b906cbe5`
+ **After**: `0x000000000000000000000000bbd576128f71186a0f9ae2f2aab4afb4af2dae17`
+ **Meaning**: Updates the PERMISSIONED_CANNON game type implementation. You can verify which implementation is set using `cast call 0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1 "gameImpls(uint32)(address)" 1`, where `1` is the [`PERMISSIONED_CANNON` game type](https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.4.0/packages/contracts-bedrock/src/dispute/lib/Types.sol#L31). + Before this task has been executed, you will see that the returned address is `0x879e899523bA9a4Ab212a2d70cF1af73B906CbE5`, matching the "Before" value of this slot, demonstrating this slot is storing the address of the PERMISSIONED_CANNON implementation. diff --git a/tasks/sep/026-fp-holocene-upgrade-fix/input.json b/tasks/sep/026-fp-holocene-upgrade-fix/input.json new file mode 100644 index 000000000..e86665fd6 --- /dev/null +++ b/tasks/sep/026-fp-holocene-upgrade-fix/input.json @@ -0,0 +1,67 @@ +{ + "chainId": 11155111, + "metadata": { + "name": "Holocene Hardfork - Proof Contract Upgrades", + "description": "Upgrades the `MIPS.sol`, `FaultDisputeGame.sol`, and `PermissionedDisputeGame.sol` contracts for Holocene." + }, + "transactions": [ + { + "metadata": { + "name": "Upgrade `PERMISSIONED_CANNON` game type in `DisputeGameFactory`", + "description": "Upgrades the `PERMISSIONED_CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash." + }, + "to": "0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000001000000000000000000000000bbd576128f71186a0f9ae2f2aab4afb4af2dae17", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "1", + "_impl": "0xbBD576128f71186A0f9ae2F2AAb4afb4aF2dae17" + } + }, + { + "metadata": { + "name": "Upgrade `CANNON` game type in `DisputeGameFactory`", + "description": "Upgrades the `CANNON` game type to the new Holocene deployment, with an updated version of `op-program` as the absolute prestate hash." + }, + "to": "0x05F9613aDB30026FFd634f38e5C4dFd30a197Fa1", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000833a817ef459f4ecdb83fc5a4bf04d09a4e83f3f", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0", + "_impl": "0x833a817eF459f4eCdB83Fc5A4Bf04d09A4e83f3F" + } + } + ] +}