From 123a110bb8df61937968a9bae4df93495d1c8de1 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter Date: Tue, 28 Jan 2025 16:06:51 -0600 Subject: [PATCH] feat(sep): fp-recovery template for game retirement Adds a template into fp-recovery that will retire existing games. --- tasks/sep/fp-recovery/006-retire-games/.env | 5 + .../fp-recovery/006-retire-games/README.md | 75 ++++++++++++ .../006-retire-games/SignFromJson.s.sol | 110 ++++++++++++++++++ .../006-retire-games/input-template.json | 38 ++++++ .../sep/fp-recovery/006-retire-games/justfile | 17 +++ 5 files changed, 245 insertions(+) create mode 100644 tasks/sep/fp-recovery/006-retire-games/.env create mode 100644 tasks/sep/fp-recovery/006-retire-games/README.md create mode 100644 tasks/sep/fp-recovery/006-retire-games/SignFromJson.s.sol create mode 100644 tasks/sep/fp-recovery/006-retire-games/input-template.json create mode 100644 tasks/sep/fp-recovery/006-retire-games/justfile diff --git a/tasks/sep/fp-recovery/006-retire-games/.env b/tasks/sep/fp-recovery/006-retire-games/.env new file mode 100644 index 000000000..ea762e03a --- /dev/null +++ b/tasks/sep/fp-recovery/006-retire-games/.env @@ -0,0 +1,5 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +SUPERCHAIN_CONFIG_ADDR=0xC2Be75506d5724086DEB7245bd260Cc9753911Be +OWNER_SAFE=0x837DE453AD5F21E89771e3c06239d8236c0EFd5E +SAFE_NONCE="" +L2_CHAIN_NAME="" diff --git a/tasks/sep/fp-recovery/006-retire-games/README.md b/tasks/sep/fp-recovery/006-retire-games/README.md new file mode 100644 index 000000000..59126dce8 --- /dev/null +++ b/tasks/sep/fp-recovery/006-retire-games/README.md @@ -0,0 +1,75 @@ +# Deputy Guardian - Retire Games + +Status: CONTINGENCY TASK, SIGN AS NEEDED + +## Objective + +This batch retires all games by calling the setRespectedGameType function in the OptimismPortal with a value of type(uint32).max. This action requires all in-progress withdrawals to be re-proven against a new game that was created after this update occurs. + +The batch will be executed on chain ID `11155111`, and contains `1` transactions. + +## Tx #1: Call `setRespectedGameType` in the `OptimismPortalProxy` + +Calls `setRespectedGameType` with the reserved value of `type(uint32).max` to trigger the game retirement mechanism. + +**Function Signature:** `setRespectedGameType(address,uint32)` + +**To:** `0x4220C5deD9dC2C8a8366e684B098094790C72d3c` + +**Value:** `0 WEI` + +**Raw Input Data:** `0xa1155ed900000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff` + +### Inputs + +**\_gameType:** `4294967295` (reserved for retirement mechanism) + +**\_portal:** `` + +## Preparing the Operation + +1. Locate the address of the `OptimismPortalProxy` to change the respected game type of. + +2. Generate the batch with `just generate-input `. + +3. Set the `L2_CHAIN_NAME` configuration to the appropriate chain in the `.env` file. + +4. Collect signatures and execute the action according to the instructions in [SINGLE.md](../../../../SINGLE.md). + +### State Validations + +The two state modifications that are made by this action are: + +1. An update to the nonce of the Gnosis safe owner of the `DeputyGuardianModule`. +2. An update to the shared slot between the `respectedGameType` and `respectedGameTypeUpdatedAt` variables. + +Slot [`0x000000000000000000000000000000000000000000000000000000000000003b`](https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.4.0-rc.4/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json#L100C3-L113C5) in the `OptimismPortalProxy` has the following packed layout: + +| Offset | Description | +| ---------- | ------------------------------------------------------------ | +| `[0, 20)` | Unused; Should be zero'd out. | +| `[20, 28)` | `respectedGameTypeUpdatedAt` timestamp (64 bits, big-endian) | +| `[28, 32)` | `respectedGameType` (32 bits, big-endian) | + +Note that the offsets in the above table refer to the slot value's big-endian representation. You can compute the offset values with chisel: +``` +➜ uint256 x = 0x000000000000000000000000000000000000000000000000669eeed200000001 +➜ uint64 respectedGameTypeUpdatedAt = uint64(x >> 32) +➜ respectedGameTypeUpdatedAt +Type: uint64 +├ Hex: 0x +├ Hex (full word): 0x669eeed2 +└ Decimal: 1721691858 +➜ uint32 respectedGameType = uint32(x & 0xFFFFFFFF) +➜ respectedGameType +Type: uint32 +├ Hex: 0x +├ Hex (full word): 0x1 +└ Decimal: 1 +``` + +To verify the diff: + +1. Check that the only modification to state belongs to the `OptimismPortalProxy` at slot `0x000000000000000000000000000000000000000000000000000000000000003b` +1. Check that the lower 4 bytes are the current unchanged game type when read as a big-endian 32-bit uint. +1. Check that bytes `[20, 28]` equal the timestamp of the transaction's submission when read as a big-endian 64-bit uint. diff --git a/tasks/sep/fp-recovery/006-retire-games/SignFromJson.s.sol b/tasks/sep/fp-recovery/006-retire-games/SignFromJson.s.sol new file mode 100644 index 000000000..23f006324 --- /dev/null +++ b/tasks/sep/fp-recovery/006-retire-games/SignFromJson.s.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import {SignFromJson as OriginalSignFromJson} from "script/SignFromJson.s.sol"; +import {Simulation} from "@base-contracts/script/universal/Simulation.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"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + + // Chains for this task. + string constant l1ChainName = "sepolia"; + string l2ChainName = vm.envString("L2_CHAIN_NAME"); + + // Safe contract for this task. + GnosisSafe securityCouncilSafe = GnosisSafe(payable(0xf64bc17485f0B4Ea5F06A96514182FC4cB561977)); + GnosisSafe foundationSafe = GnosisSafe(payable(0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B)); + + // Known EOAs to exclude from safety checks. + address l2OutputOracleProposer; // cast call $L2OO "PROPOSER()(address)" + address l2OutputOracleChallenger; // In registry addresses. + address systemConfigOwner; // In registry addresses. + address batchSenderAddress; // In registry genesis-system-configs + address p2pSequencerAddress; // cast call $SystemConfig "unsafeBlockSigner()(address)" + address batchInboxAddress; // In registry yaml. + + 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() != type(uint32).max); + } + + function getCodeExceptions() internal view override returns (address[] memory) { + // Safe owners will appear in storage in the LivenessGuard when added + address[] memory securityCouncilSafeOwners = securityCouncilSafe.getOwners(); + address[] memory shouldHaveCodeExceptions = new address[](6 + securityCouncilSafeOwners.length); + + shouldHaveCodeExceptions[0] = l2OutputOracleProposer; + shouldHaveCodeExceptions[1] = l2OutputOracleChallenger; + shouldHaveCodeExceptions[2] = systemConfigOwner; + shouldHaveCodeExceptions[3] = batchSenderAddress; + shouldHaveCodeExceptions[4] = p2pSequencerAddress; + shouldHaveCodeExceptions[5] = batchInboxAddress; + + for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) { + shouldHaveCodeExceptions[6 + i] = securityCouncilSafeOwners[i]; + } + + return shouldHaveCodeExceptions; + } + + 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 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 known EOAs out of the config toml file + l2OutputOracleProposer = stdToml.readAddress(chainConfig, "$.addresses.Proposer"); + l2OutputOracleChallenger = stdToml.readAddress(chainConfig, "$.addresses.Challenger"); + systemConfigOwner = stdToml.readAddress(chainConfig, "$.addresses.SystemConfigOwner"); + batchSenderAddress = stdToml.readAddress(chainConfig, "$.addresses.BatchSubmitter"); + p2pSequencerAddress = stdToml.readAddress(chainConfig, "$.addresses.UnsafeBlockSigner"); + batchInboxAddress = stdToml.readAddress(chainConfig, "$.batch_inbox_addr"); + + // Read the chain-specific OptimismPortalProxy address + _proxies.OptimismPortal = stdToml.readAddress(chainConfig, "$.addresses.OptimismPortalProxy"); + } +} diff --git a/tasks/sep/fp-recovery/006-retire-games/input-template.json b/tasks/sep/fp-recovery/006-retire-games/input-template.json new file mode 100644 index 000000000..9b4c5ef76 --- /dev/null +++ b/tasks/sep/fp-recovery/006-retire-games/input-template.json @@ -0,0 +1,38 @@ +{ + "chainId": 11155111, + "metadata": { + "name": "Deputy Guardian - Retire Games", + "description": "This batch retires all games by calling the setRespectedGameType function in the OptimismPortal with a value of type(uint32).max" + }, + "transactions": [ + { + "metadata": { + "name": "Retire Games", + "description": "Retires all games by calling the setRespectedGameType function in the OptimismPortal with a value of type(uint32).max" + }, + "to": "0xfd7E6Ef1f6c9e4cC34F54065Bf8496cE41A4e2e8", + "value": "0x0", + "data": "0xa1155ed9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffff", + "contractMethod": { + "type": "function", + "name": "setRespectedGameType", + "inputs": [ + { + "name": "_portal", + "type": "address" + }, + { + "name": "_gameType", + "type": "uint32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_portal": "0x0000000000000000000000000000000000000000", + "_gameType": "4294967295" + } + } + ] +} \ No newline at end of file diff --git a/tasks/sep/fp-recovery/006-retire-games/justfile b/tasks/sep/fp-recovery/006-retire-games/justfile new file mode 100644 index 000000000..2b35e385d --- /dev/null +++ b/tasks/sep/fp-recovery/006-retire-games/justfile @@ -0,0 +1,17 @@ +set positional-arguments + +# default recipe to display help information +default: + @just --list + +# Generate the `input.json` with a dispute game address to blacklist +generate-input *args='': + #!/usr/bin/env bash + RESERVED_GAME_TYPE="4294967295" + SET_GAME_TYPE_SIG="setRespectedGameType(address,uint32)" + ENCODED_CALL=$(cast calldata $SET_GAME_TYPE_SIG $1 $RESERVED_GAME_TYPE) + + cp ./input-template.json ./input.json + jq "(.transactions[0].data = \"$ENCODED_CALL\") | + (.transactions[0].contractInputsValues._portal = \"$1\")" ./input.json > ./input.tmp.json + mv ./input.tmp.json ./input.json