From 4d20efba7fc18a87ef83f06f8d62eb7be4fb4592 Mon Sep 17 00:00:00 2001 From: Ethnical Date: Fri, 6 Dec 2024 16:30:14 +0100 Subject: [PATCH 01/13] Swap 1 Previous Owners and add 2 new Owners to `FakeFoundationOperationSafe`. (#385) * add task `021-refresh-signers-fakeFoS` * fix the todo + remove the livenessGuard that is not used in this task * fix: hardcoded removal and checks if removal is correct. * fix: adding the check on the ownerCount after upgrade. * add `VALIDATION.md` --- tasks/sep/021-refresh-signers-fakeFoS/.env | 3 + .../sep/021-refresh-signers-fakeFoS/README.md | 34 +++++++ .../SignFromJson.s.sol | 88 +++++++++++++++++++ .../021-refresh-signers-fakeFoS/VALIDATION.md | 74 ++++++++++++++++ .../021-refresh-signers-fakeFoS/input.json | 16 ++++ 5 files changed, 215 insertions(+) create mode 100644 tasks/sep/021-refresh-signers-fakeFoS/.env create mode 100644 tasks/sep/021-refresh-signers-fakeFoS/README.md create mode 100644 tasks/sep/021-refresh-signers-fakeFoS/SignFromJson.s.sol create mode 100644 tasks/sep/021-refresh-signers-fakeFoS/VALIDATION.md create mode 100644 tasks/sep/021-refresh-signers-fakeFoS/input.json diff --git a/tasks/sep/021-refresh-signers-fakeFoS/.env b/tasks/sep/021-refresh-signers-fakeFoS/.env new file mode 100644 index 00000000..1e034c81 --- /dev/null +++ b/tasks/sep/021-refresh-signers-fakeFoS/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0x837DE453AD5F21E89771e3c06239d8236c0EFd5E +SAFE_NONCE="" diff --git a/tasks/sep/021-refresh-signers-fakeFoS/README.md b/tasks/sep/021-refresh-signers-fakeFoS/README.md new file mode 100644 index 00000000..8ef37840 --- /dev/null +++ b/tasks/sep/021-refresh-signers-fakeFoS/README.md @@ -0,0 +1,34 @@ +# Sepolia Update Signers of the `FakeFoS` + +Status: [READY TO SIGN] + +## Objective + +This task **update** the signers of the `FakeFoS`(`0x837DE453AD5F21E89771e3c06239d8236c0EFd5E`) on Sepolia. +Since some signers are not active anymore or part of the organisation, this is necessary to remove them. +The **1 signer** that will be removed by this task will be: + +- `0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c` + +Moreover, 3 engineers will be added (_Engineer 1_: `0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE`, _Engineer 2_: `0x95E774787A63f145f7B05028a1479bDc9D055f3d` and _Engineer 3_: `0xa03dafade71f1544f4b0120145eec9b89105951f`) that require to sign on Sepolia will be added as owners on both Safes. + +This action will be performed by swaping previous owner that was not active anymore by the new _Engineer 1_. + +To Recap the action for swaping will be: +| PreviousOwner | newOwner | +| --- | --- | +| 0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c | 0x95E774787A63f145f7B05028a1479bDc9D055f3d | + +For _Engineer 2_ and _Engineer 3_ we will be using the `addOwnerWithThreshold(address owner, uint256 _threshold)` function of the safe without changing the threshold. + +To Recap the action will be: +| Owner | Threshold | +| --- | --- | +| 0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE | 2 | +| 0xa03dafade71f1544f4b0120145eec9b89105951f| 2 | + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/021-refresh-signers-FakeFoS/SignFromJson.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. diff --git a/tasks/sep/021-refresh-signers-fakeFoS/SignFromJson.s.sol b/tasks/sep/021-refresh-signers-fakeFoS/SignFromJson.s.sol new file mode 100644 index 00000000..3a8b0847 --- /dev/null +++ b/tasks/sep/021-refresh-signers-fakeFoS/SignFromJson.s.sol @@ -0,0 +1,88 @@ +// 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"; +import {ModuleManager} from "safe-contracts/base/ModuleManager.sol"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + // Safe contract for this task. + GnosisSafe foundationOperationsSafe = + GnosisSafe(payable(vm.envAddress("OWNER_SAFE"))); // We take from the "OWNER_SAFE" as this is the "TARGET_SAFE". + + Types.ContractSet proxies; + address previousowner = address(0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c); + uint256 numberOwners = foundationOperationsSafe.getOwners().length; + + /// @notice Sets up the contract + function setUp() public {} + + function getCodeExceptions() + internal + view + override + returns (address[] memory) + { + address[] + memory foundationOperationsSafeOwners = foundationOperationsSafe + .getOwners(); + address[] memory shouldHaveCodeExceptions = new address[]( + foundationOperationsSafeOwners.length + 1 // 3 is the number of the address we wants to remove here. + ); + + for (uint256 i = 0; i < foundationOperationsSafeOwners.length; i++) { + shouldHaveCodeExceptions[i] = foundationOperationsSafeOwners[i]; + } + // add the exception of the address that has to be removed. + shouldHaveCodeExceptions[ + foundationOperationsSafeOwners.length + ] = previousowner; + + return shouldHaveCodeExceptions; + } + + function getAllowedStorageAccess() + internal + view + override + returns (address[] memory allowed) + { + allowed = new address[](1); + allowed[0] = address(foundationOperationsSafe); + } + + /// @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"); + + address[] + memory foundationOperationsSafeOwners = foundationOperationsSafe + .getOwners(); + + for (uint256 i = 0; i < foundationOperationsSafeOwners.length; i++) { + require( + foundationOperationsSafeOwners[i] != previousowner, + "Previous owner found in the owners list, should have been removed" + ); + } + require( + numberOwners + 2 == foundationOperationsSafe.getOwners().length, + "The number of owners should have been increased by 2." + ); + checkStateDiff(accesses); + + console.log("All assertions passed!"); + } +} diff --git a/tasks/sep/021-refresh-signers-fakeFoS/VALIDATION.md b/tasks/sep/021-refresh-signers-fakeFoS/VALIDATION.md new file mode 100644 index 00000000..75360216 --- /dev/null +++ b/tasks/sep/021-refresh-signers-fakeFoS/VALIDATION.md @@ -0,0 +1,74 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the transaction. + +For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff are missing from this document. Additionally, please verify that for each contract: + +- The following state changes (and none others) are made to that contract. This validates that no unexpected state changes occur. +- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic. +- All key values match the semantic meaning provided, which can be validated using the storage layout links provided. + +## State Changes + +### `0x837DE453AD5F21E89771e3c06239d8236c0EFd5E` (FoundationOperationSafe `GnosisSafe`) + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000003 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000008 + **After**: 0x000000000000000000000000000000000000000000000000000000000000000a + **Meaning**: The owner count will increment by **2** to **10**. + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000004 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000001 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000002 + **Meaning**: **Only during simulation**, the threshold is set back to **2**. Otherwise, the threshold remain **2**. + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000005 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000001 + **Meaning**: The nonce of the safe will increase from **0** to **1**. + +## Owners + +The changes of the owners should be as follows below. +This require multiples storage changes because the owners list is represented by a linked list. + +- Key: 0x00e6aef00fdbd2b42a717bf8a7596fb523db8d5582c30496b08056c941430612 + Before: 0x0000000000000000000000001084092ac2f04c866806cf3d4a385afa4f6a6c97 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c` from the owners list. We now point to `0x0`. + +- Key: 0x731ef62987fe322b3f9f4c6aba97f7d530106638c83990f4b62ff43b1f6e60ce + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + Meaning: Make the `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` address point to the address `0xf0871b2f75ecd07e2d709b7a2cc0af6848c1ce76` to be part of the linked list. + +- Key: 0xa844827b271b29dbac0d7fedb175e5576d2f0566d49e27e314cb0609e727121b + Before: 0x000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c + After: 0x00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d + Meaning: The pointer will now point on 1 new address `0x95e774787a63f145f7b05028a1479bdc9d055f3d` instead of the old address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c`. + +- Key: 0xac7275083894a2bfce66bfe6389c992881b2ca5e5fefd5d284ca2bccb33727ec + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x00000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade + Meaning: Make the `0xa03dafade71f1544f4b0120145eec9b89105951f` address point to `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` to be part of the linked list. + +- Key: 0xb772eb4066939c7bc71246d020e9ccdefe1aacb8c9340739bed6bb7e7b3faa6a + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x0000000000000000000000001084092ac2f04c866806cf3d4a385afa4f6a6c97 + Meaning: Make the `0x95e774787a63f145f7b05028a1479bdc9d055f3d` address point to `0x1084092ac2f04c866806cf3d4a385afa4f6a6c97` to be part of the linked list. + +- Key: 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0 + Before: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + After: 0x000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f + Meaning: Make the `SENTINEL_ADDRESS` point to the address `0xa03dafade71f1544f4b0120145eec9b89105951f` to be part of the linked list. + +To precompute the storage location we need to use the `cast index`: + +```shell +cast index address 0x0000000000000000000000000000000000000001 2 +0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0 // this is the storage of the SENTINEL_ADDRESS pointing to. +``` + +The address `0x0000000000000000000000000000000000000001` is the `SENTINEL_ADDRESS` and the `2` is the slot of the mapping (address => address). + +We can reproduce the same process for the other addresses. diff --git a/tasks/sep/021-refresh-signers-fakeFoS/input.json b/tasks/sep/021-refresh-signers-fakeFoS/input.json new file mode 100644 index 00000000..28833535 --- /dev/null +++ b/tasks/sep/021-refresh-signers-fakeFoS/input.json @@ -0,0 +1,16 @@ +{ + "transactions": [ + { + "to": "0x837DE453AD5F21E89771e3c06239d8236c0EFd5E", + "data": "0xe318b52b00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d" + }, + { + "to": "0x837DE453AD5F21E89771e3c06239d8236c0EFd5E", + "data": "0x0d582f1300000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade0000000000000000000000000000000000000000000000000000000000000002" + }, + { + "to": "0x837DE453AD5F21E89771e3c06239d8236c0EFd5E", + "data": "0x0d582f13000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f0000000000000000000000000000000000000000000000000000000000000002" + } + ] +} From cb63e03da30fa346349a84532be6454884f08b04 Mon Sep 17 00:00:00 2001 From: Ethnical Date: Mon, 9 Dec 2024 13:41:20 +0100 Subject: [PATCH 02/13] Swap `3` Previous Owners with `3` new Owners on the `FakeSecurityCouncil`. (#383) * add the task 021 with the `README.md` * Update the README + env * Input transaction to swapowners * Create the `signFromJson.s.sol` for the FakeSecurityCouncil * Rename + add the a new address to `swap()` * remove unnecessary codes * chore: allowingStorageAccess * chore: remove other safes changes from this tasks to fix comment * chore: address the change for the `livenessGuard` to not be hardoded. * add `VALIDATION.md` * rename task `021` to `024` * chore: remove the `TODO` * add the test on the number of owners + ensuring the previous owners has been removed. * fix typo --------- Co-authored-by: blaine --- tasks/sep/024-refresh-signers-fakeSC/.env | 3 + .../sep/024-refresh-signers-fakeSC/README.md | 30 +++++ .../SignFromJson.s.sol | 117 +++++++++++++++++ .../024-refresh-signers-fakeSC/VALIDATION.md | 123 ++++++++++++++++++ .../sep/024-refresh-signers-fakeSC/input.json | 16 +++ 5 files changed, 289 insertions(+) create mode 100644 tasks/sep/024-refresh-signers-fakeSC/.env create mode 100644 tasks/sep/024-refresh-signers-fakeSC/README.md create mode 100644 tasks/sep/024-refresh-signers-fakeSC/SignFromJson.s.sol create mode 100644 tasks/sep/024-refresh-signers-fakeSC/VALIDATION.md create mode 100644 tasks/sep/024-refresh-signers-fakeSC/input.json diff --git a/tasks/sep/024-refresh-signers-fakeSC/.env b/tasks/sep/024-refresh-signers-fakeSC/.env new file mode 100644 index 00000000..30adde02 --- /dev/null +++ b/tasks/sep/024-refresh-signers-fakeSC/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0xf64bc17485f0B4Ea5F06A96514182FC4cB561977 +SAFE_NONCE="" diff --git a/tasks/sep/024-refresh-signers-fakeSC/README.md b/tasks/sep/024-refresh-signers-fakeSC/README.md new file mode 100644 index 00000000..5523061f --- /dev/null +++ b/tasks/sep/024-refresh-signers-fakeSC/README.md @@ -0,0 +1,30 @@ +# Sepolia Update Signers `FakeSC` + +Status: [READY TO SIGN] + +## Objective + +This task **update** the signers of the `FakeSC`(`0xf64bc17485f0B4Ea5F06A96514182FC4cB561977`) on Sepolia. +Since some signers are not active anymore or part of the organisation, this is necessary to remove them. +The **3 signers** that will be removed by this task will be: + +- `0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f` +- `0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c` +- `0x78339d822c23d943e4a2d4c3dd5408f66e6d662d` + +Moreover, 3 engineers will be added (_Engineer 1_: `0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE`, _Engineer 2_: `0x95E774787A63f145f7B05028a1479bDc9D055f3d` and _Engineer 3_: `0xa03dafade71f1544f4b0120145eec9b89105951f`) that require to sign on Sepolia will be added as owners on both Safes. + +This action will be performed by swaping previous owner that was not active anymore by the new _Engineer 1_ and _Engineer 2_ and _Engineer 3_ using the `swapOwner(address prevOwner, address oldOwner, address newOwner)` function of the safes. + +To Recap the action will be: +| PreviousOwner | newOwner | +| --- | --- | +| 0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c | 0x95E774787A63f145f7B05028a1479bDc9D055f3d | +| 0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f | 0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE | +| 0x78339d822c23d943e4a2d4c3dd5408f66e6d662d | 0xa03dafade71f1544f4b0120145eec9b89105951f | + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/024-refresh-signers-fakeSC/SignFromJson.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. diff --git a/tasks/sep/024-refresh-signers-fakeSC/SignFromJson.s.sol b/tasks/sep/024-refresh-signers-fakeSC/SignFromJson.s.sol new file mode 100644 index 00000000..58453dd3 --- /dev/null +++ b/tasks/sep/024-refresh-signers-fakeSC/SignFromJson.s.sol @@ -0,0 +1,117 @@ +// 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"; +import {ModuleManager} from "safe-contracts/base/ModuleManager.sol"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + + // Safe contract for this task. + GnosisSafe securityCouncilSafe = + GnosisSafe(payable(vm.envAddress("OWNER_SAFE"))); // We take from the "OWNER_SAFE" as this is the "TARGET_SAFE". + + uint256 numberOwners = securityCouncilSafe.getOwners().length; + + address previousowner1 = + address(0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c); + address previousowner2 = + address(0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f); + address previousowner3 = + address(0x78339d822c23D943E4a2d4c3DD5408F66e6D662D); + + bytes32 livenessGuardSlot = + 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8; + address livenessGuard = + address( + uint160( + uint256( + vm.load(address(securityCouncilSafe), livenessGuardSlot) + ) + ) + ); + + Types.ContractSet proxies; + + /// @notice Sets up the contract + function setUp() public {} + + function getCodeExceptions() + internal + view + override + returns (address[] memory) + { + address[] memory securityCouncilSafeOwners = securityCouncilSafe + .getOwners(); + address[] memory shouldHaveCodeExceptions = new address[]( + securityCouncilSafeOwners.length + 3 // 3 is the number of the address we wants to remove here. + ); + + for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) { + shouldHaveCodeExceptions[i] = securityCouncilSafeOwners[i]; + } + // add the exception of the address that has to be removed. + shouldHaveCodeExceptions[ + securityCouncilSafeOwners.length + ] = previousowner1; + + shouldHaveCodeExceptions[ + securityCouncilSafeOwners.length + 1 + ] = previousowner2; + + shouldHaveCodeExceptions[ + securityCouncilSafeOwners.length + 2 + ] = previousowner3; + + return shouldHaveCodeExceptions; + } + + function getAllowedStorageAccess() + internal + view + override + returns (address[] memory allowed) + { + allowed = new address[](2); + allowed[0] = address(securityCouncilSafe); + allowed[1] = livenessGuard; + } + + /// @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"); + + address[] memory securityCouncilSafeOwners = securityCouncilSafe + .getOwners(); + + for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) { + require( + securityCouncilSafeOwners[i] != previousowner1 && + securityCouncilSafeOwners[i] != previousowner2 && + securityCouncilSafeOwners[i] != previousowner3, + "A previous owner found in the owners list, should have been removed!" + ); + } + require( + numberOwners == securityCouncilSafeOwners.length, + "The number of owners should not have been increased." + ); + + checkStateDiff(accesses); + + console.log("All assertions passed!"); + } +} diff --git a/tasks/sep/024-refresh-signers-fakeSC/VALIDATION.md b/tasks/sep/024-refresh-signers-fakeSC/VALIDATION.md new file mode 100644 index 00000000..8fe4a8ff --- /dev/null +++ b/tasks/sep/024-refresh-signers-fakeSC/VALIDATION.md @@ -0,0 +1,123 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the transaction. + +For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff are missing from this document. Additionally, please verify that for each contract: + +- The following state changes (and none others) are made to that contract. This validates that no unexpected state changes occur. +- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic. +- All key values match the semantic meaning provided, which can be validated using the storage layout links provided. + +## State Changes + +### `0xc26977310bC89DAee5823C2e2a73195E85382cC7` (`LivenessGuard`) + +- **Key**: 0x39099d5c6e8943c9b1cb45b371daa80a1c6571b8d7598c4a1c9aa3aa98a2d715 + **Before**: 0x0000000000000000000000000000000000000000000000000000000066bfc07c + **After**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **Meaning**: Set the liveness timestamp to `0` of the address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c`. + +- **Key**: 0x469c687eed4a2321c43edddca66a279757c13045315736c5b4aef71e0c60653a + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **After**: 0x0000000000000000000000000000000000000000000000000000000067519d75 + **Meaning**: Set the liveness timestamp to the current `block.timestamp` of the address `0x41fb1d8c3262e88a056ee3099f5718405cc8cade`. + +- **Key**: 0x56ee16ca3ade18209faccff732edefbb77524a2f2c0c642df2abe4924871e783 + **Before**: 0x0000000000000000000000000000000000000000000000000000000066561b10 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **Meaning**: Set the liveness timestamp to `0` of the address `0x78339d822c23d943e4a2d4c3dd5408f66e6d662d`. + +- **Key**: 0x8b832b208e2b85d2569164b1655368f5b5eddb1c56f6c1acf41053cac08f5141 + **Before**: 0x0000000000000000000000000000000000000000000000000000000066561b10 + **After**: 0x0000000000000000000000000000000000000000000000000000000067519d75 + **Meaning**: [Only during simulation] the address `0x1084092ac2f04c866806cf3d4a385afa4f6a6c97` will be set to the current `block.timestamp` since we are executing the simulation from this address. + +- **Key**: 0x918861783ab45836f1fa81ac70d801c660e20fc205429a163129e933eed11b59 + **Before**: 0x0000000000000000000000000000000000000000000000000000000066561b10 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **Meaning**: Set the liveness timestamp to `0` of the address `0xe09d881a1a13c805ed2c6823f0c7e4443a260f2f`. + +- **Key**: 0xa563a25c73689413382dc6229640ac64d0f7adcc900a76d3efa4b407755549c6 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **After**: 0x0000000000000000000000000000000000000000000000000000000067519d75 + **Meaning**: Set the liveness timestamp to the current `block.timestamp` of the address `0xa03dafade71f1544f4b0120145eec9b89105951f`. + +- **Key**: 0xd9f6815e6bf76c7dcbd38f589dfc2ef8deef1ee9123252314e2e80fcde9f4078 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000000 + **After**: 0x0000000000000000000000000000000000000000000000000000000067519d75 + **Meaning**: Set the liveness timestamp to the current `block.timestamp` of the address `0x95e774787a63f145f7b05028a1479bdc9d055f3d`. + +To precompute the storage location we need to use the `cast index`: + +```shell +cast index address 0x1084092ac2f04c866806cf3d4a385afa4f6a6c97 0 +0x8b832b208e2b85d2569164b1655368f5b5eddb1c56f6c1acf41053cac08f5141 +``` + +## `0xf64bc17485f0B4Ea5F06A96514182FC4cB561977` (FakeSecurityCouncil `GnosisSafe`) + +### Nonces + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000005 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000012 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000013 + **Meaning**: The nonce of the safe will increase from **18** to **19**. + +### Owners + +The changes of the owners should be as follows below. +This require multiples storage changes because the owners list is represented by a linked list. + +- Key: 0x00e6aef00fdbd2b42a717bf8a7596fb523db8d5582c30496b08056c941430612 + Before: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c` from the owners list. We now point to `0x0`. + +- Key: 0x08dff6893f2a37dde66796f17032759252c00a1f4ade9918c4ded35450b8b0de + Before: 0x00000000000000000000000078339d822c23d943e4a2d4c3dd5408f66e6d662d + After: 0x000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f + Meaning: the pointer will now point on 1 new address `0xa03dafade71f1544f4b0120145eec9b89105951f` instead of the old address `0x78339d822c23d943e4a2d4c3dd5408f66e6d662d`. + +- Key: 0x1766faf9c381105993d15464f882a01ed4dc7884a8181f075960bd30c6a9a556 + Before: 0x000000000000000000000000e09d881a1a13c805ed2c6823f0c7e4443a260f2f + After: 0x00000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade + Meaning: Make the `0x2e2e33fedd27fdecfc851ae98e45a5ecb76904fe` address point to the address `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` to be part of the linked list. + +- Key: 0x6a893420fa61cee16053e747f52b48285baaad2df4457e404b96ef1b39326daa + Before: 0x0000000000000000000000000000000000000000000000000000000000000001 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0x78339d822c23d943e4a2d4c3dd5408f66e6d662d` from the owners list, we now point to `0x0`. + +- Key: 0x6f61ddabce3d1c468ed3160ef2e2c327f4eae7ae60ddb2356493c3511c00b205 + Before: 0x00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0xe09d881a1a13c805ed2c6823f0c7e4443a260f2f` from the owners list, we now point to `0x0`. + +- Key: 0x731ef62987fe322b3f9f4c6aba97f7d530106638c83990f4b62ff43b1f6e60ce + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721 + Meaning: Make the `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` address point to the address `0x10303fe151a505be8afd23e1d285d3c733bdc721` to be part of the linked list. + +- Key: 0xa844827b271b29dbac0d7fedb175e5576d2f0566d49e27e314cb0609e727121b + Before: 0x000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c + After: 0x00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d + Meaning: The pointer will now point on 1 new address `0x95e774787a63f145f7b05028a1479bdc9d055f3d` instead of the old address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c`. + +- Key: 0xac7275083894a2bfce66bfe6389c992881b2ca5e5fefd5d284ca2bccb33727ec + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x0000000000000000000000000000000000000000000000000000000000000001 + Meaning: Make the `0xa03dafade71f1544f4b0120145eec9b89105951f` address point to `SENTINEL_ADDRESS` to be part of the linked list. + +- Key: 0xb772eb4066939c7bc71246d020e9ccdefe1aacb8c9340739bed6bb7e7b3faa6a + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + Meaning: Make the `0x95e774787a63f145f7b05028a1479bdc9d055f3d` address point to `0xf0871b2f75ecd07e2d709b7a2cc0af6848c1ce76` to be part of the linked list. + +To precompute the storage location we need to use the `cast index`: + +```shell +cast index address 0x95e774787a63f145f7b05028a1479bdc9d055f3d 2 +0xb772eb4066939c7bc71246d020e9ccdefe1aacb8c9340739bed6bb7e7b3faa6a // this is the storage of the 0x95e774787a63f145f7b05028a1479bdc9d055f3d pointing to. +``` + +We can reproduce the same process for the other addresses. diff --git a/tasks/sep/024-refresh-signers-fakeSC/input.json b/tasks/sep/024-refresh-signers-fakeSC/input.json new file mode 100644 index 00000000..7889db03 --- /dev/null +++ b/tasks/sep/024-refresh-signers-fakeSC/input.json @@ -0,0 +1,16 @@ +{ + "transactions": [ + { + "to": "0xf64bc17485f0B4Ea5F06A96514182FC4cB561977", + "data": "0xe318b52b00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d" + }, + { + "to": "0xf64bc17485f0B4Ea5F06A96514182FC4cB561977", + "data": "0xe318b52b000000000000000000000000a367e326694aa2236ce2886e3e2aa4965eea1d7f000000000000000000000000e09d881a1a13c805ed2c6823f0c7e4443a260f2f00000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade" + }, + { + "to": "0xf64bc17485f0B4Ea5F06A96514182FC4cB561977", + "data": "0xe318b52b0000000000000000000000002e2e33fedd27fdecfc851ae98e45a5ecb76904fe00000000000000000000000078339d822c23d943e4a2d4c3dd5408f66e6d662d000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f" + } + ] +} From 7bf4fd89f7dac4c5130a440b0afe545edb15d02d Mon Sep 17 00:00:00 2001 From: Ethnical Date: Mon, 9 Dec 2024 13:44:26 +0100 Subject: [PATCH 03/13] Swap 2 Previous Owners and add 1 new Owners to `FakeUpgradeSafe`. (#386) * add task `021-refresh-signers-fakeFuS` * fix: remove todo, comments, and rename incorrect variables * add the validation file * fix the `||` by `&&` to ensure both of the signers is removed correctly * rename the task to `023` instead of the `021` --------- Co-authored-by: blaine --- tasks/sep/023-refresh-signers-fakeFuS/.env | 3 + .../sep/023-refresh-signers-fakeFuS/README.md | 35 +++++++ .../SignFromJson.s.sol | 95 +++++++++++++++++++ .../023-refresh-signers-fakeFuS/VALIDATION.md | 84 ++++++++++++++++ .../023-refresh-signers-fakeFuS/input.json | 16 ++++ 5 files changed, 233 insertions(+) create mode 100644 tasks/sep/023-refresh-signers-fakeFuS/.env create mode 100644 tasks/sep/023-refresh-signers-fakeFuS/README.md create mode 100644 tasks/sep/023-refresh-signers-fakeFuS/SignFromJson.s.sol create mode 100644 tasks/sep/023-refresh-signers-fakeFuS/VALIDATION.md create mode 100644 tasks/sep/023-refresh-signers-fakeFuS/input.json diff --git a/tasks/sep/023-refresh-signers-fakeFuS/.env b/tasks/sep/023-refresh-signers-fakeFuS/.env new file mode 100644 index 00000000..6f90a38e --- /dev/null +++ b/tasks/sep/023-refresh-signers-fakeFuS/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B +SAFE_NONCE="" diff --git a/tasks/sep/023-refresh-signers-fakeFuS/README.md b/tasks/sep/023-refresh-signers-fakeFuS/README.md new file mode 100644 index 00000000..e0df3f5b --- /dev/null +++ b/tasks/sep/023-refresh-signers-fakeFuS/README.md @@ -0,0 +1,35 @@ +# Sepolia Update Signers of the `FakeFuS` + +Status: [READY TO SIGN] + +## Objective + +This task **update** the signers of the `FakeFuS`(`0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B`) on Sepolia. +Since some signers are not active anymore or part of the organisation, this is necessary to remove them. +The **2 signers** that will be removed by this task will be: + +- `0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c` +- `0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f` + +Moreover, 3 engineers will be added (_Engineer 1_: `0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE`, _Engineer 2_: `0x95E774787A63f145f7B05028a1479bDc9D055f3d` and _Engineer 3_: `0xa03dafade71f1544f4b0120145eec9b89105951f`) that require to sign on Sepolia will be added as owners on both Safes. + +This action will be performed by swaping previous owner that was not active anymore by the new _Engineer 1_ and _Engineer 2_. + +To Recap the action for swaping will be: +| PreviousOwner | newOwner | +| --- | --- | +| 0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c | 0x95E774787A63f145f7B05028a1479bDc9D055f3d | +| 0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f| 0x41fb1d8c3262e88a056ee3099f5718405CC8cAdE | + +For _Engineer 3_ we will be using the `addOwnerWithThreshold(address owner, uint256 _threshold)` function of the safe without changing the threshold. + +To Recap the action will be: +| Owner | Threshold | +| --- | --- | +| 0xa03dafade71f1544f4b0120145eec9b89105951f| 2 | + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/023-refresh-signers-FakeFuS/SignFromJson.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. diff --git a/tasks/sep/023-refresh-signers-fakeFuS/SignFromJson.s.sol b/tasks/sep/023-refresh-signers-fakeFuS/SignFromJson.s.sol new file mode 100644 index 00000000..5d41dd3b --- /dev/null +++ b/tasks/sep/023-refresh-signers-fakeFuS/SignFromJson.s.sol @@ -0,0 +1,95 @@ +// 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"; +import {ModuleManager} from "safe-contracts/base/ModuleManager.sol"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + // Safe contract for this task. + GnosisSafe foundationUpgradeSafe = + GnosisSafe(payable(vm.envAddress("OWNER_SAFE"))); // We take from the "OWNER_SAFE" as this is the "TARGET_SAFE". + + address previousowner1 = + address(0xad70Ad7Ac30Cee75EB9638D377EACD8DfDfE0C3c); + address previousowner2 = + address(0xE09d881A1A13C805ED2c6823f0C7E4443A260f2f); + + uint256 numberOwners = foundationUpgradeSafe.getOwners().length; + Types.ContractSet proxies; + + /// @notice Sets up the contract + function setUp() public {} + + function getCodeExceptions() + internal + view + override + returns (address[] memory) + { + address[] memory foundationUpgradeSafeOwners = foundationUpgradeSafe + .getOwners(); + address[] memory shouldHaveCodeExceptions = new address[]( + foundationUpgradeSafeOwners.length + 2 // 2 is the number of the address we wants to remove here. + ); + + for (uint256 i = 0; i < foundationUpgradeSafeOwners.length; i++) { + shouldHaveCodeExceptions[i] = foundationUpgradeSafeOwners[i]; + } + // add the exception of the address that has to be removed. + shouldHaveCodeExceptions[ + foundationUpgradeSafeOwners.length + ] = previousowner1; + + shouldHaveCodeExceptions[ + foundationUpgradeSafeOwners.length + 1 + ] = previousowner2; + + return shouldHaveCodeExceptions; + } + + function getAllowedStorageAccess() + internal + view + override + returns (address[] memory allowed) + { + allowed = new address[](1); + allowed[0] = address(foundationUpgradeSafe); + } + + /// @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"); + + address[] memory foundationUpgradeSafeOwners = foundationUpgradeSafe + .getOwners(); + + for (uint256 i = 0; i < foundationUpgradeSafeOwners.length; i++) { + require( + foundationUpgradeSafeOwners[i] != previousowner1 && + foundationUpgradeSafeOwners[i] != previousowner2, + "Previous owners found in the owners list, should have been removed" + ); + } + require( + numberOwners + 1 == foundationUpgradeSafe.getOwners().length, + "The number of owners should have been increased by 1." + ); + checkStateDiff(accesses); + + console.log("All assertions passed!"); + } +} diff --git a/tasks/sep/023-refresh-signers-fakeFuS/VALIDATION.md b/tasks/sep/023-refresh-signers-fakeFuS/VALIDATION.md new file mode 100644 index 00000000..55184444 --- /dev/null +++ b/tasks/sep/023-refresh-signers-fakeFuS/VALIDATION.md @@ -0,0 +1,84 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the transaction. + +For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff are missing from this document. Additionally, please verify that for each contract: + +- The following state changes (and none others) are made to that contract. This validates that no unexpected state changes occur. +- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic. +- All key values match the semantic meaning provided, which can be validated using the storage layout links provided. + +## State Changes + +### `0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B` (FoundationUpgradeSafe `GnosisSafe`) + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000003 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000009 + **After**: 0x000000000000000000000000000000000000000000000000000000000000000a + **Meaning**: The owner count will increment by **1** to **10**. + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000004 + **Before**: 0x0000000000000000000000000000000000000000000000000000000000000001 + **After**: 0x0000000000000000000000000000000000000000000000000000000000000002 + **Meaning**: **Only during simulation**, the threshold is set back to **2**. Otherwise, the threshold remain **2**. + +- **Key**: 0x0000000000000000000000000000000000000000000000000000000000000005 + **Before**: 0x000000000000000000000000000000000000000000000000000000000000001a + **After**: 0x000000000000000000000000000000000000000000000000000000000000001b + **Meaning**: The nonce of the safe will increase from **26** to **27**. + +## Owners + +The changes of the owners should be as follows below. +This require multiples storage changes because the owners list is represented by a linked list. + +- Key: 0x00e6aef00fdbd2b42a717bf8a7596fb523db8d5582c30496b08056c941430612 + Before: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c` from the owners list. We now point to `0x0`. + +- Key: 0x1766faf9c381105993d15464f882a01ed4dc7884a8181f075960bd30c6a9a556 + Before: 0x000000000000000000000000e09d881a1a13c805ed2c6823f0c7e4443a260f2f + After: 0x00000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade + Meaning: The previous address will now point on 1 new address `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` instead of the old address `0xe09d881a1a13c805ed2c6823f0c7e4443a260f2f`. + +- Key: 0x6f61ddabce3d1c468ed3160ef2e2c327f4eae7ae60ddb2356493c3511c00b205 + Before: 0x00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721 + After: 0x0000000000000000000000000000000000000000000000000000000000000000 + Meaning: To remove the address `0xe09d881a1a13c805ed2c6823f0c7e4443a260f2f` from the owners list, we now point to `0x0`. + +- Key: 0x731ef62987fe322b3f9f4c6aba97f7d530106638c83990f4b62ff43b1f6e60ce + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721 + Meaning: Make the `0x41fb1d8c3262e88a056ee3099f5718405cc8cade` address point to the address `0x10303fe151a505be8afd23e1d285d3c733bdc721` to be part of the linked list. + +- Key: 0xa844827b271b29dbac0d7fedb175e5576d2f0566d49e27e314cb0609e727121b + Before: 0x000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c + After: 0x00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d + Meaning: The pointer will now point on 1 new address `0x95e774787a63f145f7b05028a1479bdc9d055f3d` instead of the old address `0xad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c`. + +- Key: 0xac7275083894a2bfce66bfe6389c992881b2ca5e5fefd5d284ca2bccb33727ec + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x0000000000000000000000002e2e33fedd27fdecfc851ae98e45a5ecb76904fe + Meaning: Make the `0xa03dafade71f1544f4b0120145eec9b89105951f` address point to `0x2e2e33fedd27fdecfc851ae98e45a5ecb76904fe` to be part of the linked list. + +- Key: 0xb772eb4066939c7bc71246d020e9ccdefe1aacb8c9340739bed6bb7e7b3faa6a + Before: 0x0000000000000000000000000000000000000000000000000000000000000000 + After: 0x000000000000000000000000f0871b2f75ecd07e2d709b7a2cc0af6848c1ce76 + Meaning: Make the `0x95e774787a63f145f7b05028a1479bdc9d055f3d` address point to `0xf0871b2f75ecd07e2d709b7a2cc0af6848c1ce76` to be part of the linked list. + +- Key: 0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0 + Before: 0x0000000000000000000000002e2e33fedd27fdecfc851ae98e45a5ecb76904fe + After: 0x000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f + Meaning: Make the `SENTINEL_ADDRESS` point to the address `0xa03dafade71f1544f4b0120145eec9b89105951f` to be part of the linked list. + +To precompute the storage location we need to use the `cast index`: + +```shell +cast index address 0x0000000000000000000000000000000000000001 2 +0xe90b7bceb6e7df5418fb78d8ee546e97c83a08bbccc01a0644d599ccd2a7c2e0 // this is the storage of the SENTINEL_ADDRESS pointing to. +``` + +The address `0x0000000000000000000000000000000000000001` is the `SENTINEL_ADDRESS` and the `2` is the slot of the mapping (address => address). + +We can reproduce the same process for the other addresses. diff --git a/tasks/sep/023-refresh-signers-fakeFuS/input.json b/tasks/sep/023-refresh-signers-fakeFuS/input.json new file mode 100644 index 00000000..e6fad43c --- /dev/null +++ b/tasks/sep/023-refresh-signers-fakeFuS/input.json @@ -0,0 +1,16 @@ +{ + "transactions": [ + { + "to": "0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B", + "data": "0xe318b52b00000000000000000000000010303fe151a505be8afd23e1d285d3c733bdc721000000000000000000000000ad70ad7ac30cee75eb9638d377eacd8dfdfe0c3c00000000000000000000000095e774787a63f145f7b05028a1479bdc9d055f3d" + }, + { + "to": "0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B", + "data": "0xe318b52b000000000000000000000000a367e326694aa2236ce2886e3e2aa4965eea1d7f000000000000000000000000e09d881a1a13c805ed2c6823f0c7e4443a260f2f00000000000000000000000041fb1d8c3262e88a056ee3099f5718405cc8cade" + }, + { + "to": "0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B", + "data": "0x0d582f13000000000000000000000000a03dafade71f1544f4b0120145eec9b89105951f0000000000000000000000000000000000000000000000000000000000000002" + } + ] +} From 1fd8d7b412ae96a5399d31a029bc77771ec9dd4a Mon Sep 17 00:00:00 2001 From: George Knee Date: Wed, 11 Dec 2024 10:38:40 +0000 Subject: [PATCH 04/13] update eip712sign (#399) Closes #375 --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index e02d9afb..c58f0222 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.7 + GOBIN="$REPO_ROOT/bin" go install github.com/base-org/eip712sign@v0.0.8 # Bundle path should be provided including the .json file extension. add-transaction bundlePath to sig *params: From 5a499c67d1400d740574799c3a36e7b5af34f2f0 Mon Sep 17 00:00:00 2001 From: Alexis Williams <148368153+awilliams1-cb@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:37:32 -0500 Subject: [PATCH 05/13] Base Sepolia Holocene FP Upgrade Task (#376) * Add Base Sepolia holocene fp upgrade task * Updated status and made changes to the md files --- tasks/sep/base-005-fp-holocene-upgrade/.env | 3 + .../base-005-fp-holocene-upgrade/OVERVIEW.md | 37 ++++ .../base-005-fp-holocene-upgrade/README.md | 35 ++++ .../SignFromJson.s.sol | 161 ++++++++++++++++++ .../VALIDATION.md | 46 +++++ .../base-005-fp-holocene-upgrade/input.json | 67 ++++++++ 6 files changed, 349 insertions(+) create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/.env create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/OVERVIEW.md create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/README.md create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/SignFromJson.s.sol create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/VALIDATION.md create mode 100644 tasks/sep/base-005-fp-holocene-upgrade/input.json diff --git a/tasks/sep/base-005-fp-holocene-upgrade/.env b/tasks/sep/base-005-fp-holocene-upgrade/.env new file mode 100644 index 00000000..008fe959 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0x0fe884546476dDd290eC46318785046ef68a0BA9 +SAFE_NONCE="" diff --git a/tasks/sep/base-005-fp-holocene-upgrade/OVERVIEW.md b/tasks/sep/base-005-fp-holocene-upgrade/OVERVIEW.md new file mode 100644 index 00000000..9fca51b7 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/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, an updated MIPS VM, and an updated version. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068f600e592799c16d1b096616edbf1681fb9c0de` + +### Inputs +**_impl:** `0x68f600e592799c16d1b096616edbf1681fb9c0de` + +**_gameType:** `1` + + +## 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, an updated MIPS VM, and an updated version. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b7fb44a61fde2b9db28a84366e168b14d1a1b103` + +### Inputs +**_gameType:** `0` + +**_impl:** `0xb7fb44a61fde2b9db28a84366e168b14d1a1b103` diff --git a/tasks/sep/base-005-fp-holocene-upgrade/README.md b/tasks/sep/base-005-fp-holocene-upgrade/README.md new file mode 100644 index 00000000..0d816705 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/README.md @@ -0,0 +1,35 @@ +# Holocene Hardfork Upgrade - Base Sepolia + +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x3ab5ff5700afe57b89d7d23c0ad9e535f38a68b55c571e66c642719d85bcb888) + +## Objective + +Upgrades the Fault Proof contracts of Base Sepolia for the Holocene hardfork. + +This upgrades the Fault Proof contracts in the +[op-contracts/v1.8.0-rc.2](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.8.0-rc.2) release. + +## Pre-deployments + +- `MIPS` - `0x69470D6970Cd2A006b84B1d4d70179c892cFCE01` +- `FaultDisputeGame` - `0xB7fB44a61fdE2b9DB28a84366e168b14D1a1b103` +- `PermissionedDisputeGame` - `0x68f600e592799c16D1b096616eDbf1681FB9c0De` + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep/base-005-fp-holocene-upgrade/SignFromJson.s.sol`. +This ensures all safety checks are run. If the default `SignFromJson.s.sol` script is shown (without the full path), something is wrong and the safety checks will not run. + +## State Validation + +Please see the instructions for [validation](./VALIDATION.md). + +## Execution + +This upgrade +* Changes 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/base-005-fp-holocene-upgrade/SignFromJson.s.sol b/tasks/sep/base-005-fp-holocene-upgrade/SignFromJson.s.sol new file mode 100644 index 00000000..8d4fc3f4 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/SignFromJson.s.sol @@ -0,0 +1,161 @@ +// 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 {console2 as console} from "forge-std/console2.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {Vm, VmSafe} from "forge-std/Vm.sol"; +import {GnosisSafe} from "safe-contracts/GnosisSafe.sol"; +import {LibString} from "solady/utils/LibString.sol"; +import "@eth-optimism-bedrock/src/dispute/lib/Types.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 {MIPS} from "@eth-optimism-bedrock/src/cannon/MIPS.sol"; +import {ISemver} from "@eth-optimism-bedrock/src/universal/ISemver.sol"; + +contract SignFromJson is OriginalSignFromJson { + using LibString for string; + + // Safe contract for this task. + GnosisSafe ownerSafe = GnosisSafe(payable(vm.envAddress("OWNER_SAFE"))); + + // Current dispute game implementations + FaultDisputeGame currentFDG; + PermissionedDisputeGame currentPDG; + + // New dispute game implementations + FaultDisputeGame newFDG; + PermissionedDisputeGame newPDG; + + // https://github.com/ethereum-optimism/superchain-registry/blob/main/superchain/configs/sepolia/base.toml#L56 + DisputeGameFactory constant dgfProxy = DisputeGameFactory(0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1); + + // Other expected values + bytes32 constant absolutePrestate = 0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f5e8afe; + address constant newMips = 0x69470D6970Cd2A006b84B1d4d70179c892cFCE01; + // https://docs.base.org/docs/base-contracts/#ethereum-testnet-sepolia + address constant oracle = 0x92240135b46fc1142dA181f550aE8f595B858854; + string constant gameVersion = "1.3.1"; + uint256 constant chainId = 84532; + + function setUp() public { + // Get the current dispute game implementations + currentFDG = FaultDisputeGame( + address(dgfProxy.gameImpls(GameTypes.CANNON)) + ); + currentPDG = PermissionedDisputeGame( + address(dgfProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)) + ); + + // Get the new dispute game implementations, parsed from the input.json + string memory inputJson; + string memory path = "/tasks/sep/base-005-fp-holocene-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)); + } + + newPDG = PermissionedDisputeGame(stdJson.readAddress(inputJson, "$.transactions[0].contractInputsValues._impl")); + newFDG = FaultDisputeGame(stdJson.readAddress(inputJson, "$.transactions[1].contractInputsValues._impl")); + + // Check the correctness of the new dispute games with the current ones + _precheckDisputeGameImplementation(); + } + + function _precheckDisputeGameImplementation() internal view { + console.log("pre-check new game implementations"); + + // check the current and new fault dispute game implementations + require(address(currentFDG.anchorStateRegistry()) == address(newFDG.anchorStateRegistry()), "precheck-100"); + require(currentFDG.l2ChainId() == newFDG.l2ChainId(), "precheck-101"); + require(currentFDG.splitDepth() == newFDG.splitDepth(), "precheck-102"); + require(currentFDG.maxGameDepth() == newFDG.maxGameDepth(), "precheck-103"); + require( + uint64(Duration.unwrap(currentFDG.maxClockDuration())) == + uint64(Duration.unwrap(newFDG.maxClockDuration())), + "precheck-104" + ); + require( + uint64(Duration.unwrap(currentFDG.clockExtension())) == + uint64(Duration.unwrap(newFDG.clockExtension())), + "precheck-105" + ); + + // check the current and new permissioned dispute game implementations + require(address(currentPDG.anchorStateRegistry()) == address(newPDG.anchorStateRegistry()), "precheck-200"); + require(currentPDG.l2ChainId() == newPDG.l2ChainId(), "precheck-201"); + require(currentPDG.splitDepth() == newPDG.splitDepth(), "precheck-202"); + require(currentPDG.maxGameDepth() == newPDG.maxGameDepth(), "precheck-203"); + require( + uint64(Duration.unwrap(currentPDG.maxClockDuration())) == + uint64(Duration.unwrap(newPDG.maxClockDuration())), + "precheck-204" + ); + require( + uint64(Duration.unwrap(currentPDG.clockExtension())) == + uint64(Duration.unwrap(newPDG.clockExtension())), + "precheck-205" + ); + require(address(currentPDG.proposer()) == address(newPDG.proposer()), "precheck-206"); + require(address(currentPDG.challenger()) == address(newPDG.challenger()), "precheck-207"); + } + + function getAllowedStorageAccess() + internal + view + override + returns (address[] memory allowed) + { + allowed = new address[](2); + allowed[0] = address(dgfProxy); + allowed[1] = address(ownerSafe); + } + + function getCodeExceptions() internal view override returns (address[] memory exceptions) { + // No exceptions are expected in this task, but it must be implemented. + } + + /// @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); + checkDGFProxyAndGames(); + checkMips(); + + console.log("All assertions passed!"); + } + + function checkDGFProxyAndGames() internal view { + console.log("check dispute game implementations"); + + require(address(newFDG) == address(dgfProxy.gameImpls(GameTypes.CANNON)), "dgf-100"); + require(address(newPDG) == address(dgfProxy.gameImpls(GameTypes.PERMISSIONED_CANNON)), "dgf-200"); + + require(newFDG.version().eq(gameVersion), "game-100"); + require(newPDG.version().eq(gameVersion), "game-200"); + + require(newFDG.absolutePrestate().raw() == absolutePrestate, "game-300"); + require(newPDG.absolutePrestate().raw() == absolutePrestate, "game-400"); + + require(address(newFDG.vm()) == newMips, "game-500"); + require(address(newPDG.vm()) == newMips, "game-600"); + + require(newFDG.l2ChainId() == chainId, "game-700"); + require(newPDG.l2ChainId() == chainId, "game-800"); + } + + function checkMips() internal view{ + console.log("check MIPS"); + + require(newMips.code.length != 0, "MIPS-100"); + vm.assertEq(ISemver(newMips).version(), "1.2.1"); + require(address(MIPS(newMips).oracle()) == oracle, "MIPS-200"); + } +} diff --git a/tasks/sep/base-005-fp-holocene-upgrade/VALIDATION.md b/tasks/sep/base-005-fp-holocene-upgrade/VALIDATION.md new file mode 100644 index 00000000..0fce8ac9 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/VALIDATION.md @@ -0,0 +1,46 @@ +# 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 override should be seen: + +### `0x0fe884546476dDd290eC46318785046ef68a0BA9` (Gnosis Safe `ProxyAdmin` owner) + +Links: +- [Sepolia Etherscan](https://sepolia.etherscan.io/address/0x0fe884546476ddd290ec46318785046ef68a0ba9) + +Overrides: +- **Key:** `0x0000000000000000000000000000000000000000000000000000000000000004`
+ **Value:** `0x0000000000000000000000000000000000000000000000000000000000000001`
+ **Meaning:** Enables the simulation by setting the signature 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 + +### `0x0fe884546476dDd290eC46318785046ef68a0BA9` (Gnosis Safe `ProxyAdmin` owner) + +- **Key**: `0x0000000000000000000000000000000000000000000000000000000000000005`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000009`
+ **After**: `0x000000000000000000000000000000000000000000000000000000000000000a`
+ **Meaning**: The nonce in slot `0x05` of the L1 Gnosis Safe `ProxyAdmin` owner is incremented from 9 to 10. + +### `0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1` (`DisputeGameFactoryProxy`) + +- **Key**: `0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b`
+ **Before**: `0x0000000000000000000000005062792ed6a85cf72a1424a1b7f39ed0f7972a4b`
+ **After**: `0x000000000000000000000000b7fb44a61fde2b9db28a84366e168b14d1a1b103`
+ **Meaning**: Updates the CANNON game type implementation. Prior to this upgrade running, verify the old implementation is set using `cast call 0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1 "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). Once the upgrade has been executed, the same command should now return the new implementation address. + +- **Key**: `0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e`
+ **Before**: `0x000000000000000000000000593d20c4c69485b95d11507239be2c725ea2a6fd`
+ **After**: `0x00000000000000000000000068f600e592799c16d1b096616edbf1681fb9c0de`
+ **Meaning**: Updates the PERMISSIONED_CANNON game type implementation. Prior to this upgrade running, verify the old implementation is set using `cast call 0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1 "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). Once the upgrade has been executed, the same command should now return the new implementation address. \ No newline at end of file diff --git a/tasks/sep/base-005-fp-holocene-upgrade/input.json b/tasks/sep/base-005-fp-holocene-upgrade/input.json new file mode 100644 index 00000000..5fa58be2 --- /dev/null +++ b/tasks/sep/base-005-fp-holocene-upgrade/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": "0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1", + "value": "0x0", + "data": "0x14f6b1a3000000000000000000000000000000000000000000000000000000000000000100000000000000000000000068f600e592799c16d1b096616edbf1681fb9c0de", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": 1, + "_impl": "0x68f600e592799c16d1b096616edbf1681fb9c0de" + } + }, + { + "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": "0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b7fb44a61fde2b9db28a84366e168b14d1a1b103", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "name": "_gameType", + "type": "uint32" + }, + { + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": 0, + "_impl": "0xb7fb44a61fde2b9db28a84366e168b14d1a1b103" + } + } + ] +} From f6d1f9c0e891e8a31e2911f683f57bf9fead4eaa Mon Sep 17 00:00:00 2001 From: Ethnical Date: Thu, 12 Dec 2024 15:39:35 +0100 Subject: [PATCH 06/13] update the status of 21, 23, 24 to `EXECUTED`. (#403) --- tasks/sep/021-refresh-signers-fakeFoS/README.md | 2 +- tasks/sep/023-refresh-signers-fakeFuS/README.md | 2 +- tasks/sep/024-refresh-signers-fakeSC/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tasks/sep/021-refresh-signers-fakeFoS/README.md b/tasks/sep/021-refresh-signers-fakeFoS/README.md index 8ef37840..ae483b40 100644 --- a/tasks/sep/021-refresh-signers-fakeFoS/README.md +++ b/tasks/sep/021-refresh-signers-fakeFoS/README.md @@ -1,6 +1,6 @@ # Sepolia Update Signers of the `FakeFoS` -Status: [READY TO SIGN] +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0xf82ded62c5eb6c9549579e8d35a70b5bf9a02c73d9fb39f40dbd5c0dc3276fe4) ## Objective diff --git a/tasks/sep/023-refresh-signers-fakeFuS/README.md b/tasks/sep/023-refresh-signers-fakeFuS/README.md index e0df3f5b..2613d98a 100644 --- a/tasks/sep/023-refresh-signers-fakeFuS/README.md +++ b/tasks/sep/023-refresh-signers-fakeFuS/README.md @@ -1,6 +1,6 @@ # Sepolia Update Signers of the `FakeFuS` -Status: [READY TO SIGN] +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x49e06b2aec3f5fe428fd8d3affde24dd4a0230feb56676fe509ba4e908fd8b37) ## Objective diff --git a/tasks/sep/024-refresh-signers-fakeSC/README.md b/tasks/sep/024-refresh-signers-fakeSC/README.md index 5523061f..5d1e8b7f 100644 --- a/tasks/sep/024-refresh-signers-fakeSC/README.md +++ b/tasks/sep/024-refresh-signers-fakeSC/README.md @@ -1,6 +1,6 @@ # Sepolia Update Signers `FakeSC` -Status: [READY TO SIGN] +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x343762f9c1cae4c0833f52b3cc12db3194e48066548efe69d2ec8a2925aec996) ## Objective From 6f213969fbc8928533784e27ef41b5a942006cfc Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 13 Dec 2024 07:53:44 -0800 Subject: [PATCH 07/13] cannon: Add scripts to deploy MTCannon to op devnet (#405) * cannon: Add script to configure mt-cannon on the op devnet * cannon: Reorganize post-state checks * cannon: Tweak validation instructions, add gnosis safe changes * cannon: Update to new PermissionedDisputeGame * cannon: Add more validations * cannon: Address TODOs * cannon: Skip owner() checks for now * cannon: Add line-break * cannon: Set status to 'READY TO SIGN' --- tasks/sep-dev-0/007-mt-cannon/.env | 3 + tasks/sep-dev-0/007-mt-cannon/OVERVIEW.md | 38 ++++ tasks/sep-dev-0/007-mt-cannon/README.md | 36 ++++ .../007-mt-cannon/SignFromJson.s.sol | 188 ++++++++++++++++++ tasks/sep-dev-0/007-mt-cannon/VALIDATION.md | 41 ++++ tasks/sep-dev-0/007-mt-cannon/input.json | 71 +++++++ 6 files changed, 377 insertions(+) create mode 100644 tasks/sep-dev-0/007-mt-cannon/.env create mode 100644 tasks/sep-dev-0/007-mt-cannon/OVERVIEW.md create mode 100644 tasks/sep-dev-0/007-mt-cannon/README.md create mode 100644 tasks/sep-dev-0/007-mt-cannon/SignFromJson.s.sol create mode 100644 tasks/sep-dev-0/007-mt-cannon/VALIDATION.md create mode 100644 tasks/sep-dev-0/007-mt-cannon/input.json diff --git a/tasks/sep-dev-0/007-mt-cannon/.env b/tasks/sep-dev-0/007-mt-cannon/.env new file mode 100644 index 00000000..df12ab88 --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/.env @@ -0,0 +1,3 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" +OWNER_SAFE=0x4377BB0F0103992b31eC12b4d796a8687B8dC8E9 +SAFE_NONCE="" diff --git a/tasks/sep-dev-0/007-mt-cannon/OVERVIEW.md b/tasks/sep-dev-0/007-mt-cannon/OVERVIEW.md new file mode 100644 index 00000000..36a261a5 --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/OVERVIEW.md @@ -0,0 +1,38 @@ +# MTCannon Deployment +Configures new MTCannon DisputeGame implementations on the DisputeGameFactoryProxy. + +The batch will be executed on chain ID `11155111`, and contains `2` transactions. + +## Tx #1: Set dispute game implementation (type 0 - CANNON) +Sets the FaultDisputeGame implementation on DisputeGameFactoryProxy. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0x54416A2E28E8cbC761fbce0C7f107307991282e5` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030aca4aea0cf48bd53dca03b34e35d05b9635c7` + +### Inputs +**_gameType:** `0x0` + +**_impl:** `0x030aca4aea0cf48bd53dca03b34e35d05b9635c7` + + +## Tx #2: Set dispute game implementation (type 1 - PERMISSIONED_CANNON) +Sets the PermissionedDisputeGame implementation on DisputeGameFactoryProxy. + +**Function Signature:** `setImplementation(uint32,address)` + +**To:** `0x54416A2E28E8cbC761fbce0C7f107307991282e5` + +**Value:** `0 WEI` + +**Raw Input Data:** `0x14f6b1a300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004001542871a610a551b11dcaaea52dc5ca6fdb6a` + +### Inputs +**_impl:** `0x4001542871a610a551b11dcaaea52dc5ca6fdb6a` + +**_gameType:** `0x1` + diff --git a/tasks/sep-dev-0/007-mt-cannon/README.md b/tasks/sep-dev-0/007-mt-cannon/README.md new file mode 100644 index 00000000..74f4cede --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/README.md @@ -0,0 +1,36 @@ +# MTCannon Deployment + +Status: READY TO SIGN + +## Objective + +Configures new MTCannon DisputeGame implementations on the DisputeGameFactoryProxy. + +Contracts upgraded are included within the +[op-contracts/v1.9.0-rc.3](https://github.com/ethereum-optimism/optimism/tree/op-contracts/v1.9.0-rc.3) release. + +## Pre-deployments + +- `MIPS64` - [0x2b82752b3809a6b7f1662536af72c519000610e3](https://sepolia.etherscan.io/address/0x2b82752b3809a6b7f1662536af72c519000610e3) +- `FaultDisputeGame` - [0x030aca4aea0cf48bd53dca03b34e35d05b9635c7](https://sepolia.etherscan.io/address/0x030aca4aea0cf48bd53dca03b34e35d05b9635c7) +- `PermissionedDisputeGame` - [0x4001542871a610a551b11dcaaea52dc5ca6fdb6a](https://sepolia.etherscan.io/address/0x4001542871a610a551b11dcaaea52dc5ca6fdb6a) +- `PermissionedDelayedWETHProxy` - [0x81b05ce22ec8e79078d58df7de44957bd3c93125](https://sepolia.etherscan.io/address/0x81b05ce22ec8e79078d58df7de44957bd3c93125) + +## Simulation + +Please see the "Simulating and Verifying the Transaction" instructions in [SINGLE.md](../../../SINGLE.md). +When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/sep-dev-0/007-mt-cannon/SignFromJson.s.sol`. +This ensures all safety checks are run. If the default `SignFromJson.s.sol` script is shown (without the full path), something is wrong and the safety checks will not run. + +## State Validation + +Please see the instructions for [validation](./VALIDATION.md). + +## Execution + +This upgrade +* Updates `DisputeGameFactoryProxy` game implementations to use Cannon dispute games that run on 64-bit MTCannon: + * Type 0 (CANNON) implementation is updated to a `FaultDisputeGame` configured with the `MIPS64` vm + * Type 1 (PERMISSIONED_CANNON) implementation is updated to a `PermissionedDisputeGame` configured with the `MIPS64` vm + +See the [overview](./OVERVIEW.md) and `input.json` bundle for more details. diff --git a/tasks/sep-dev-0/007-mt-cannon/SignFromJson.s.sol b/tasks/sep-dev-0/007-mt-cannon/SignFromJson.s.sol new file mode 100644 index 00000000..8dec4562 --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/SignFromJson.s.sol @@ -0,0 +1,188 @@ +// 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 {Types} from "@eth-optimism-bedrock/scripts/Types.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {stdJson} from "forge-std/StdJson.sol"; +import {LibString} from "solady/utils/LibString.sol"; +import {Vm, VmSafe} from "forge-std/Vm.sol"; +import {DisputeGameFactory} from "@eth-optimism-bedrock/src/dispute/DisputeGameFactory.sol"; +import {FaultDisputeGame, Duration, Claim} from "@eth-optimism-bedrock/src/dispute/FaultDisputeGame.sol"; +import {PermissionedDisputeGame} from "@eth-optimism-bedrock/src/dispute/PermissionedDisputeGame.sol"; +import {GameType, GameTypes} from "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; +import {ISemver} from "@eth-optimism-bedrock/src/universal/ISemver.sol"; + +contract NestedSignFromJson is OriginalSignFromJson { + using LibString for string; + + uint256 immutable l2ChainId = 11155421; + address immutable proxyAdminOwnerSafe = vm.envAddress("OWNER_SAFE"); + + // Main contracts + address dgfProxy; + address faultDisputeGame; + address permissionedDisputeGame; + + // Expectations for DisputeGame implementations + string disputeGameExpectedVersion = "1.3.1"; + bytes32 immutable absolutePrestate = 0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b; + uint256 immutable maxGameDepth = 73; + uint256 immutable splitDepth = 30; + uint64 immutable maxClockDuration = 14400; + uint64 immutable clockExtension = 3600; + address anchorStateRegistryProxy; + // Expectations for PermissionedDisputeGame + address proposer; + address challenger; + + // Expectations for vm + string vmExpectedVersion = "1.0.0-beta.7"; + address preimageOracle; + + // Expectations for weth + uint256 immutable wethDelay = 600; + + + /// @notice Sets up the contract references + function setUp() public { + string memory inputJson; + string memory path = "/tasks/sep-dev-0/007-mt-cannon/input.json"; + try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) { + inputJson = data; + } catch { + revert(string.concat("Failed to read ", path)); + } + + // Find contract addresses + dgfProxy = readContractAddress("DisputeGameFactoryProxy"); + faultDisputeGame = stdJson.readAddress(inputJson, "$.transactions[0].contractInputsValues._impl"); + permissionedDisputeGame = stdJson.readAddress(inputJson, "$.transactions[1].contractInputsValues._impl"); + anchorStateRegistryProxy = readContractAddress("AnchorStateRegistryProxy"); + proposer = readContractAddress("Proposer"); + challenger = readContractAddress("Challenger"); + preimageOracle = readContractAddress("PreimageOracle"); + } + + function getCodeExceptions() internal view override returns (address[] memory exceptions) { + // None + } + + function getAllowedStorageAccess() internal view override returns (address[] memory allowed) { + allowed = new address[](2); + allowed[0] = dgfProxy; + allowed[1] = proxyAdminOwnerSafe; + } + + function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override { + console.log("Running post-deploy assertions"); + checkStateDiff(accesses); + checkDGFProxy(); + checkDisputeGames(); + checkVm(); + checkWeths(); + console.log("All assertions passed!"); + } + + function checkDGFProxy() internal view { + console.log("check DisputeGameFactoryProxy"); + DisputeGameFactory dgf = DisputeGameFactory(dgfProxy); + + require(proxyAdminOwnerSafe == dgf.owner(), "dgf-100"); + require(faultDisputeGame == address(dgf.gameImpls(GameTypes.CANNON)), "dgf-200"); + require(permissionedDisputeGame == address(dgf.gameImpls(GameTypes.PERMISSIONED_CANNON)), "dgf-300"); + } + + function checkDisputeGames() internal view { + console.log("check dispute game implementations"); + + checkDisputeGame(faultDisputeGame, GameTypes.CANNON); + checkDisputeGame(permissionedDisputeGame, GameTypes.PERMISSIONED_CANNON); + } + + function checkDisputeGame(address implAddress, GameType gameType) internal view { + FaultDisputeGame gameImpl = FaultDisputeGame(implAddress); + string memory gameStr = LibString.toString(GameType.unwrap(gameType)); + string memory errPrefix = string.concat("dg", gameStr, "-"); + + console.log(string.concat("check dispute game implementation of GameType ", gameStr)); + require(gameImpl.l2ChainId() == l2ChainId, string.concat(errPrefix, "100")); + require(GameType.unwrap(gameImpl.gameType()) == GameType.unwrap(gameType), string.concat(errPrefix, "200")); + assertStringsEqual(gameImpl.version(), disputeGameExpectedVersion, string.concat(errPrefix, "300")); + require(address(gameImpl.anchorStateRegistry()) == anchorStateRegistryProxy, string.concat(errPrefix, "400")); + require(Claim.unwrap(gameImpl.absolutePrestate()) == absolutePrestate, string.concat(errPrefix, "500")); + require(gameImpl.maxGameDepth() == maxGameDepth, string.concat(errPrefix, "600")); + require(gameImpl.splitDepth() == splitDepth, string.concat(errPrefix, "700")); + require(Duration.unwrap(gameImpl.maxClockDuration()) == maxClockDuration, string.concat(errPrefix, "800")); + require(Duration.unwrap(gameImpl.clockExtension()) == clockExtension, string.concat(errPrefix, "900")); + + if(GameType.unwrap(gameType) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON)) { + PermissionedDisputeGame permImpl = PermissionedDisputeGame(implAddress); + require(permImpl.proposer() == proposer, string.concat(errPrefix, "1000")); + require(permImpl.challenger() == challenger, string.concat(errPrefix, "1100")); + } + } + + function checkVm() internal view { + console.log("check VM implementation"); + + address vmAddr0 = address(FaultDisputeGame(faultDisputeGame).vm()); + address vmAddr1 = address(PermissionedDisputeGame(permissionedDisputeGame).vm()); + IMIPS vm = IMIPS(vmAddr0); + string memory vmVersion = vm.version(); + + require(vmAddr0 == vmAddr1, "vm-100"); + assertStringsEqual(vmVersion, vmExpectedVersion, "vm-200"); + require(vm.oracle() == preimageOracle, "vm-300"); + } + + function checkWeths() internal view { + console.log("check IDelayedWETH implementations"); + + address weth0 = address(FaultDisputeGame(faultDisputeGame).weth()); + address weth1 = address(PermissionedDisputeGame(permissionedDisputeGame).weth()); + + require(address(weth0) != address(weth1), "weths-100"); + checkWeth(IDelayedWETH(weth0), GameTypes.CANNON); + checkWeth(IDelayedWETH(weth1), GameTypes.PERMISSIONED_CANNON); + } + + function checkWeth(IDelayedWETH weth, GameType gameType) internal view { + string memory gameStr = LibString.toString(GameType.unwrap(gameType)); + string memory errPrefix = string.concat("weth", gameStr, "-"); + + console.log(string.concat("check IDelayedWETH implementation for GameType ", gameStr)); + // TODO: Fix DelayedWeth owners which are currently misconfigured + // require(weth.owner() == proxyAdminOwnerSafe, string.concat(errPrefix, "100")); + require(weth.delay() == wethDelay, string.concat(errPrefix, "200")); + } + + function assertStringsEqual(string memory a, string memory b, string memory errorMessage) internal pure { + require(keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b)), errorMessage); + } + + function readContractAddress(string memory contractName) internal view returns (address) { + string memory addressesJson; + + // Read addresses json + string memory path = "/lib/superchain-registry/superchain/extra/addresses/addresses.json"; + + try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) { + addressesJson = data; + } catch { + revert(string.concat("Failed to read ", path)); + } + + return stdJson.readAddress(addressesJson, string.concat("$.", LibString.toString(l2ChainId), ".", contractName)); + } +} + +interface IMIPS is ISemver { + function oracle() external view returns (address oracle_); +} + +interface IDelayedWETH { + function owner() external view returns (address); + function delay() external view returns (uint256); +} diff --git a/tasks/sep-dev-0/007-mt-cannon/VALIDATION.md b/tasks/sep-dev-0/007-mt-cannon/VALIDATION.md new file mode 100644 index 00000000..46243f1e --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/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 Changes + +### `0x2419423C72998eb1c6c15A235de2f112f8E38efF` (`DisputeGameFactoryProxy`) + +**Key**: 0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e +**Before**: 0x0000000000000000000000006a962628aa48564b7c48d97e1a738044ffec686f +**After**: 0x0000000000000000000000004001542871a610a551b11dcaaea52dc5ca6fdb6a +**Meaning**: Updates the PERMISSIONED_CANNON game type implementation. Verify that the new implementation is set using `cast call 0x2419423c72998eb1c6c15a235de2f112f8e38eff gameImpls(uint32)(address) 1`. +Confirm the expected key slot with the following: +``` +cast index uint32 1 101 +0x4d5a9bd2e41301728d41c8e705190becb4e74abe869f75bdb405b63716a35f9e +``` + +**Key**: 0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b +**Before**: 0x000000000000000000000000e5e89e67f9715ca9e6be0bd7e50ce143d177117b +**After**: 0x000000000000000000000000030aca4aea0cf48bd53dca03b34e35d05b9635c7 +**Meaning**: Updates the CANNON game type implementation. Verify that the new implementation is set using `cast call 0x2419423c72998eb1c6c15a235de2f112f8e38eff gameImpls(uint32)(address) 0`. +Confirm the expected key slot with the following: +``` +cast index uint32 0 101 +0xffdfc1249c027f9191656349feb0761381bb32c9f557e01f419fd08754bf5a1b +``` + +### Safe Contract State Changes + +The only other state changes should be restricted to one of the following addresses: + +- L1 1/1 ProxyAdmin Owner Safe: `0x4377BB0F0103992b31eC12b4d796a8687B8dC8E9` + - The nonce (slot 0x5) should be increased from 25 to 26. diff --git a/tasks/sep-dev-0/007-mt-cannon/input.json b/tasks/sep-dev-0/007-mt-cannon/input.json new file mode 100644 index 00000000..a41b5f96 --- /dev/null +++ b/tasks/sep-dev-0/007-mt-cannon/input.json @@ -0,0 +1,71 @@ +{ + "chainId": 11155111, + "metadata": { + "name": "MTCannon Deployment", + "description": "Configures new MTCannon DisputeGame implementations on the DisputeGameFactoryProxy." + }, + "transactions": [ + { + "metadata": { + "name": "Set dispute game implementation (type 0 - CANNON)", + "description": "Sets the FaultDisputeGame implementation on DisputeGameFactoryProxy." + }, + "to": "0x2419423C72998eb1c6c15A235de2f112f8E38efF", + "value": "0x0", + "data": "0x14f6b1a30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030aca4aea0cf48bd53dca03b34e35d05b9635c7", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + }, + { + "internalType": "contract IDisputeGame", + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0x0", + "_impl": "0x030aca4aea0cf48bd53dca03b34e35d05b9635c7" + } + }, + { + "metadata": { + "name": "Set dispute game implementation (type 1 - PERMISSIONED_CANNON)", + "description": "Sets the PermissionedDisputeGame implementation on DisputeGameFactoryProxy." + }, + "to": "0x2419423C72998eb1c6c15A235de2f112f8E38efF", + "value": "0x0", + "data": "0x14f6b1a300000000000000000000000000000000000000000000000000000000000000010000000000000000000000004001542871a610a551b11dcaaea52dc5ca6fdb6a", + "contractMethod": { + "type": "function", + "name": "setImplementation", + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + }, + { + "internalType": "contract IDisputeGame", + "name": "_impl", + "type": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_gameType": "0x1", + "_impl": "0x4001542871a610a551b11dcaaea52dc5ca6fdb6a" + } + } + ] +} From c5c0fa1c296bfb31d69a1cad4c9d482a3410a81c Mon Sep 17 00:00:00 2001 From: Michael de Hoog Date: Fri, 13 Dec 2024 07:36:00 -1000 Subject: [PATCH 08/13] Bump base-contracts dependency (#402) Co-authored-by: Matt Solomon --- lib/base-contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base-contracts b/lib/base-contracts index ed36aac5..49458657 160000 --- a/lib/base-contracts +++ b/lib/base-contracts @@ -1 +1 @@ -Subproject commit ed36aac52a19bdad6dee09c59e7241fe3a194160 +Subproject commit 494586571e1a4d845ee6f381b65229d63c630986 From e95e6312e71d2aca005f9d2821a512f1f2dd1eb8 Mon Sep 17 00:00:00 2001 From: Inphi Date: Fri, 13 Dec 2024 14:23:19 -0500 Subject: [PATCH 09/13] sep-dev-0: Update 007-mt-cannon task status (#408) --- tasks/sep-dev-0/007-mt-cannon/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/sep-dev-0/007-mt-cannon/README.md b/tasks/sep-dev-0/007-mt-cannon/README.md index 74f4cede..0aaf3497 100644 --- a/tasks/sep-dev-0/007-mt-cannon/README.md +++ b/tasks/sep-dev-0/007-mt-cannon/README.md @@ -1,6 +1,6 @@ # MTCannon Deployment -Status: READY TO SIGN +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x5002821d69ec13dcf913e6f53224141388dd96242b559ff1cc44c3bef46d48ef) ## Objective From 8ba0014203a861d761487aed7871d21d4bea070a Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Mon, 16 Dec 2024 05:21:34 -0800 Subject: [PATCH 10/13] doc: add license (#387) --- LICENSE | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a8fd52b4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright 2023-2024 Optimism + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 1d3cbecbfc0fd560aa022e7c8dd52bf8000e6ce3 Mon Sep 17 00:00:00 2001 From: Elliot <34463580+ElliotFriedman@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:11:25 -0800 Subject: [PATCH 11/13] FPS Initialize (#406) * ignore lcov file Signed-off-by: Elliot * add: rpc endpoints Signed-off-by: Elliot * fps init: address registry and mock addresses Signed-off-by: Elliot * init: mock data for address registry tests Signed-off-by: Elliot * add: mainnet registry tests Signed-off-by: Elliot * add: local registry tests Signed-off-by: Elliot * add logic to read superchain addresses * add tests * use l2 instance chain id in registry mapping * update interface * move chainList toml to addresses folder * update addresses toml file names as per chain ids in chainList toml * fix tests * forge fmt * update superchain addresses type check * update variable name * add: forge test to circleci Signed-off-by: Elliot * add additional assertions for supported chain ids, add more construction failure tests Signed-off-by: Elliot * add supported chainids and corresponding checks on getters, natspec, improve error messages Signed-off-by: Elliot * natspec Signed-off-by: Elliot * initial constants Signed-off-by: Elliot * mock with duplicate chain configurations Signed-off-by: Elliot * remove lcov from gitignore Signed-off-by: Elliot * address registry doco Signed-off-by: Elliot * remove supportedChainId variable Signed-off-by: Elliot * naming: OPTIMISM_CHAINID -> OP_CHAINID Signed-off-by: Elliot * fmt Signed-off-by: Elliot --------- Signed-off-by: Elliot Co-authored-by: prateek --- .circleci/config.yml | 11 ++ foundry.toml | 4 + src/fps/AddressRegistry.sol | 184 ++++++++++++++++++++++++++ src/fps/IAddressRegistry.sol | 23 ++++ src/fps/addresses/10.toml | 14 ++ src/fps/addresses/8453.toml | 14 ++ src/fps/addresses/chainList.toml | 7 + src/fps/doc/ADDRESS_REGISTRY.md | 13 ++ src/fps/utils/Constants.sol | 26 ++++ test/AddressRegistry.t.sol | 217 +++++++++++++++++++++++++++++++ test/mock/chainList1.toml | 7 + test/mock/chainList2.toml | 7 + test/mock/chainList3.toml | 7 + test/mock/data1/10.toml | 14 ++ test/mock/data1/chainList.toml | 3 + test/mock/data2/10.toml | 14 ++ test/mock/data2/chainList.toml | 3 + test/mock/data3/10.toml | 4 + test/mock/data3/chainList.toml | 3 + test/mock/data4/10.toml | 9 ++ test/mock/data4/chainList.toml | 3 + 21 files changed, 587 insertions(+) create mode 100644 src/fps/AddressRegistry.sol create mode 100644 src/fps/IAddressRegistry.sol create mode 100644 src/fps/addresses/10.toml create mode 100644 src/fps/addresses/8453.toml create mode 100644 src/fps/addresses/chainList.toml create mode 100644 src/fps/doc/ADDRESS_REGISTRY.md create mode 100644 src/fps/utils/Constants.sol create mode 100644 test/AddressRegistry.t.sol create mode 100644 test/mock/chainList1.toml create mode 100644 test/mock/chainList2.toml create mode 100644 test/mock/chainList3.toml create mode 100644 test/mock/data1/10.toml create mode 100644 test/mock/data1/chainList.toml create mode 100644 test/mock/data2/10.toml create mode 100644 test/mock/data2/chainList.toml create mode 100644 test/mock/data3/10.toml create mode 100644 test/mock/data3/chainList.toml create mode 100644 test/mock/data4/10.toml create mode 100644 test/mock/data4/chainList.toml diff --git a/.circleci/config.yml b/.circleci/config.yml index dae0bfdf..48a16171 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -197,6 +197,16 @@ jobs: just install forge --version forge fmt --check + forge_test: + docker: + - image: <> + steps: + - checkout + - run: + name: forge test + command: | + forge --version + forge test -vvv print_versions: docker: @@ -217,6 +227,7 @@ workflows: # Forge checks. - forge_build - forge_fmt + - forge_test # RPC endpoint checks. - check_sepolia_rpc_endpoints - check_mainnet_rpc_endpoints diff --git a/foundry.toml b/foundry.toml index f2045417..106163a0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -26,3 +26,7 @@ remappings = [ [profile.ci] deny_warnings = true + +[rpc_endpoints] +localhost = "http://127.0.0.1:8545" +mainnet = "https://ethereum.publicnode.com" diff --git a/src/fps/AddressRegistry.sol b/src/fps/AddressRegistry.sol new file mode 100644 index 00000000..51270e7e --- /dev/null +++ b/src/fps/AddressRegistry.sol @@ -0,0 +1,184 @@ +pragma solidity 0.8.15; + +import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +import {Test} from "forge-std/Test.sol"; +import {IAddressRegistry} from "src/fps/IAddressRegistry.sol"; +import {SUPERCHAIN_REGISTRY_PATH} from "src/fps/utils/Constants.sol"; + +/// @title Network Address Manager +/// @notice This contract provides a single source of truth for storing and retrieving addresses across multiple networks. +/// @dev Handles addresses for contracts and externally owned accounts (EOAs) while ensuring correctness and uniqueness. +contract AddressRegistry is IAddressRegistry, Test { + using EnumerableSet for EnumerableSet.UintSet; + + /// @dev Structure for reading address details from JSON files. + struct InputAddress { + /// Blockchain network identifier + address addr; + /// contract identifier (name) + string identifier; + /// Address (contract or EOA) + /// Indicates if the address is a contract + bool isContract; + } + + /// @dev Structure for storing address details in the contract. + struct RegistryEntry { + address addr; + /// Address (contract or EOA) + /// Indicates if the address is a contract + bool isContract; + } + + /// @dev Structure for reading chain list details from toml file + struct Superchain { + uint256 chainId; + string name; + } + + /// @notice Maps an identifier and l2 instance chain ID to a stored address entry. + /// All addresses will live on the same chain. + mapping(string => mapping(uint256 => RegistryEntry)) private registry; + + /// @notice Supported L2 chain IDs for this Address Registry instance. + mapping(uint256 => bool) public supportedL2ChainIds; + + /// @notice Array of supported superchains and their configurations + Superchain[] public superchains; + + /// @notice Initializes the contract by loading addresses from TOML files and configuring the supported L2 chains. + /// @param addressFolderPath The path to the folder containing chain-specific TOML address files + /// @param superchainListFilePath The path to the TOML file containing the list of supported L2 chains + constructor(string memory addressFolderPath, string memory superchainListFilePath) { + bytes memory superchainListContent = vm.parseToml(vm.readFile(superchainListFilePath), ".chains"); + superchains = abi.decode(superchainListContent, (Superchain[])); + + string memory superchainAddressesContent = vm.readFile(SUPERCHAIN_REGISTRY_PATH); + + for (uint256 i = 0; i < superchains.length; i++) { + uint256 superchainId = superchains[i].chainId; + string memory superchainName = superchains[i].name; + require(!supportedL2ChainIds[superchainId], "Duplicate chain ID in superchain config"); + require(superchainId != 0, "Invalid chain ID in superchain config"); + require(bytes(superchainName).length > 0, "Empty name in superchain config"); + + supportedL2ChainIds[superchainId] = true; + + string memory filePath = + string(abi.encodePacked(addressFolderPath, "/", vm.toString(superchainId), ".toml")); + bytes memory fileContent = vm.parseToml(vm.readFile(filePath), ".addresses"); + + InputAddress[] memory parsedAddresses = abi.decode(fileContent, (InputAddress[])); + + for (uint256 j = 0; j < parsedAddresses.length; j++) { + string memory identifier = parsedAddresses[j].identifier; + address contractAddress = parsedAddresses[j].addr; + bool isContract = parsedAddresses[j].isContract; + + require(contractAddress != address(0), "Invalid address: cannot be zero"); + require( + registry[identifier][superchainId].addr == address(0), + "Address already registered with this identifier and chain ID" + ); + + _typeCheckAddress(contractAddress, isContract); + + registry[identifier][superchainId] = RegistryEntry(contractAddress, isContract); + string memory prefixedIdentifier = + string(abi.encodePacked(vm.replace(vm.toUppercase(superchainName), " ", "_"), "_", identifier)); + vm.label(contractAddress, prefixedIdentifier); // Add label for debugging purposes + } + + string[] memory keys = + vm.parseJsonKeys(superchainAddressesContent, string.concat("$.", vm.toString(superchainId))); + + for (uint256 j = 0; j < keys.length; j++) { + string memory key = keys[j]; + address addr = vm.parseJsonAddress( + superchainAddressesContent, string.concat("$.", vm.toString(superchainId), ".", key) + ); + + require(addr != address(0), "Invalid address: cannot be zero"); + require( + registry[key][superchainId].addr == address(0), + "Address already registered with this identifier and chain ID" + ); + + registry[key][superchainId] = RegistryEntry(addr, addr.code.length > 0); + string memory prefixedIdentifier = + string(abi.encodePacked(vm.replace(vm.toUppercase(superchainName), " ", "_"), "_", key)); + vm.label(addr, prefixedIdentifier); + } + } + } + + /// @notice Retrieves an address by its identifier for a specified L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return The address associated with the given identifier on the specified chain + function getAddress(string memory identifier, uint256 l2ChainId) public view returns (address) { + _l2ChainIdSupported(l2ChainId); + + // Fetch the stored registry entry + RegistryEntry memory entry = registry[identifier][l2ChainId]; + address resolvedAddress = entry.addr; + + require(resolvedAddress != address(0), "Address not found"); + + return resolvedAddress; + } + + /// @notice Checks if an address is a contract for a given identifier and L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return True if the address is a contract, false otherwise + function isAddressContract(string memory identifier, uint256 l2ChainId) public view returns (bool) { + _l2ChainIdSupported(l2ChainId); + _checkAddressRegistered(identifier, l2ChainId); + + return registry[identifier][l2ChainId].isContract; + } + + /// @notice Checks if an address exists for a specified identifier and L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return True if the address exists, false otherwise + function isAddressRegistered(string memory identifier, uint256 l2ChainId) public view returns (bool) { + return registry[identifier][l2ChainId].addr != address(0); + } + + /// @notice Verifies that an address is registered for a given identifier and chain + /// @dev Reverts if the address is not registered + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + function _checkAddressRegistered(string memory identifier, uint256 l2ChainId) private view { + require( + isAddressRegistered(identifier, l2ChainId), + string( + abi.encodePacked("Address not found for identifier ", identifier, " on chain ", vm.toString(l2ChainId)) + ) + ); + } + + /// @notice Verifies that the given L2 chain ID is supported + /// @param l2ChainId The chain ID of the L2 network to verify + function _l2ChainIdSupported(uint256 l2ChainId) private view { + require( + supportedL2ChainIds[l2ChainId], + string(abi.encodePacked("L2 Chain ID ", vm.toString(l2ChainId), " not supported")) + ); + } + + /// @notice Validates whether an address matches its expected type (contract or EOA) + /// @dev Reverts if the address type does not match the expected type + /// @param addr The address to validate + /// @param isContract True if the address should be a contract, false if it should be an EOA + function _typeCheckAddress(address addr, bool isContract) private view { + if (isContract) { + require(addr.code.length > 0, "Address must contain code"); + } else { + require(addr.code.length == 0, "Address must not contain code"); + } + } +} diff --git a/src/fps/IAddressRegistry.sol b/src/fps/IAddressRegistry.sol new file mode 100644 index 00000000..149275d2 --- /dev/null +++ b/src/fps/IAddressRegistry.sol @@ -0,0 +1,23 @@ +pragma solidity 0.8.15; + +/// @title Network Address Registry Interface +/// @notice Interface for managing and retrieving addresses across different networks. +interface IAddressRegistry { + /// @notice Retrieves an address by its identifier for a specified L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return The address associated with the given identifier on the specified chain + function getAddress(string memory identifier, uint256 l2ChainId) external view returns (address); + + /// @notice Checks if an address is a contract for a given identifier and L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return True if the address is a contract, false otherwise + function isAddressContract(string memory identifier, uint256 l2ChainId) external view returns (bool); + + /// @notice Checks if an address exists for a specified identifier and L2 chain + /// @param identifier The unique identifier associated with the address + /// @param l2ChainId The chain ID of the L2 network + /// @return True if the address exists, false otherwise + function isAddressRegistered(string memory identifier, uint256 l2ChainId) external view returns (bool); +} diff --git a/src/fps/addresses/10.toml b/src/fps/addresses/10.toml new file mode 100644 index 00000000..99f0067d --- /dev/null +++ b/src/fps/addresses/10.toml @@ -0,0 +1,14 @@ +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = false + +[[addresses]] +addr = "0xc0Da02939E1441F497fd74F78cE7Decb17B66529" +identifier = "COMPOUND_GOVERNOR_BRAVO" +isContract = true + +[[addresses]] +addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3" +identifier = "COMPOUND_CONFIGURATOR" +isContract = true \ No newline at end of file diff --git a/src/fps/addresses/8453.toml b/src/fps/addresses/8453.toml new file mode 100644 index 00000000..99f0067d --- /dev/null +++ b/src/fps/addresses/8453.toml @@ -0,0 +1,14 @@ +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = false + +[[addresses]] +addr = "0xc0Da02939E1441F497fd74F78cE7Decb17B66529" +identifier = "COMPOUND_GOVERNOR_BRAVO" +isContract = true + +[[addresses]] +addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3" +identifier = "COMPOUND_CONFIGURATOR" +isContract = true \ No newline at end of file diff --git a/src/fps/addresses/chainList.toml b/src/fps/addresses/chainList.toml new file mode 100644 index 00000000..20fe5373 --- /dev/null +++ b/src/fps/addresses/chainList.toml @@ -0,0 +1,7 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 + +[[chains]] + name = "Base" + chain_id = 8453 \ No newline at end of file diff --git a/src/fps/doc/ADDRESS_REGISTRY.md b/src/fps/doc/ADDRESS_REGISTRY.md new file mode 100644 index 00000000..3c8196fb --- /dev/null +++ b/src/fps/doc/ADDRESS_REGISTRY.md @@ -0,0 +1,13 @@ +# Address Registry + +The address registry contract stores contract addresses on a single network. On construction, it reads in all of the configurations from the specified TOML configuration file. This TOML configuration file tells the address registry which L2 contracts to read in and store. As an example, if a task only touched the OP Mainnet contracts, the TOML file would only have a single entry + +```toml +[[chains]] + name = "OP Mainnet" + chain_id = 10 +``` + +## Usage + +Addresses can be fetched by calling the `getAddress(string memory identifier, uint256 l2ChainId)` function. This function will return the address of the contract with the given identifier on the given chain. If the contract does not exist, the function will revert. If the l2ChainId is unsupported by this address registry instance, the function will revert. diff --git a/src/fps/utils/Constants.sol b/src/fps/utils/Constants.sol new file mode 100644 index 00000000..d93f7ad3 --- /dev/null +++ b/src/fps/utils/Constants.sol @@ -0,0 +1,26 @@ +pragma solidity 0.8.15; + +// Mainnet Chain Ids +uint256 constant BASE_CHAIN_ID = 8453; +uint256 constant OP_CHAIN_ID = 10; +uint256 constant MODE_CHAIN_ID = 34443; +uint256 constant ORDERLY_CHAIN_ID = 291; +uint256 constant RACE_CHAIN_ID = 6805; +uint256 constant ZORA_CHAIN_ID = 7777777; +uint256 constant LYRA_CHAIN_ID = 957; +uint256 constant METAL_CHAIN_ID = 1750; +uint256 constant BINARY_CHAIN_ID = 624; + +// Testnet Chain Ids +uint256 constant BASE_SEPOLIA_CHAIN_ID = 84532; +uint256 constant OP_SEPOLIA_CHAIN_ID = 11155420; +uint256 constant MODE_SEPOLIA_CHAIN_ID = 919; +uint256 constant BASE_DEVNET_CHAIN_ID = 11763072; +uint256 constant METAL_SEPOLIA_CHAIN_ID = 1740; +uint256 constant RACE_SEPOLIA_CHAIN_ID = 6806; +uint256 constant ZORA_SEPOLIA_CHAIN_ID = 999999999; +uint256 constant OPLABS_DEVNET_CHAIN_ID = 11155421; + +uint256 constant LOCAL_CHAIN_ID = 31337; + +string constant SUPERCHAIN_REGISTRY_PATH = "lib/superchain-registry/superchain/extra/addresses/addresses.json"; diff --git a/test/AddressRegistry.t.sol b/test/AddressRegistry.t.sol new file mode 100644 index 00000000..76f3c94c --- /dev/null +++ b/test/AddressRegistry.t.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {Test} from "forge-std/Test.sol"; + +import {AddressRegistry} from "src/fps/AddressRegistry.sol"; +import {BASE_CHAIN_ID, OP_CHAIN_ID} from "src/fps/utils/Constants.sol"; + +contract MainnetAddressRegistryTest is Test { + AddressRegistry private addresses; + + function setUp() public { + string memory tomlFilePath = "src/fps/addresses"; + + string memory tomlchainListPath = "src/fps/addresses/chainList.toml"; + + vm.createSelectFork("mainnet"); + + addresses = new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testContractState() public view { + assertTrue(addresses.supportedL2ChainIds(OP_CHAIN_ID), "Optimism chain ID not supported"); + assertTrue(addresses.supportedL2ChainIds(BASE_CHAIN_ID), "Base chain ID not supported"); + } + + function testLocalAddressesLoaded() public view { + assertEq( + addresses.getAddress("DEPLOYER_EOA", OP_CHAIN_ID), + 0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739, + "DEPLOYER_EOA address mismatch" + ); + assertEq( + addresses.getAddress("COMPOUND_GOVERNOR_BRAVO", OP_CHAIN_ID), + 0xc0Da02939E1441F497fd74F78cE7Decb17B66529, + "COMPOUND_GOVERNOR_BRAVO address mismatch" + ); + assertEq( + addresses.getAddress("COMPOUND_CONFIGURATOR", OP_CHAIN_ID), + 0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3, + "COMPOUND_CONFIGURATOR address mismatch" + ); + + assertTrue( + addresses.isAddressContract("COMPOUND_GOVERNOR_BRAVO", OP_CHAIN_ID), + "Address governor bravo should be a contract" + ); + assertTrue( + addresses.isAddressContract("COMPOUND_CONFIGURATOR", OP_CHAIN_ID), + "Address configurator should be a contract" + ); + assertFalse(addresses.isAddressContract("DEPLOYER_EOA", OP_CHAIN_ID), "EOA address should not be a contract"); + + assertTrue(addresses.isAddressRegistered("DEPLOYER_EOA", OP_CHAIN_ID), "DEPLOYER_EOA should be registered"); + assertTrue( + addresses.isAddressRegistered("COMPOUND_GOVERNOR_BRAVO", OP_CHAIN_ID), + "COMPOUND_GOVERNOR_BRAVO should be registered" + ); + assertTrue( + addresses.isAddressRegistered("COMPOUND_CONFIGURATOR", OP_CHAIN_ID), + "COMPOUND_CONFIGURATOR should be registered" + ); + assertFalse( + addresses.isAddressRegistered("NON_EXISTENT_ADDRESS", OP_CHAIN_ID), + "Non-existent address should not be registered" + ); + } + + function testSuperchainAddressesLoaded() public view { + assertEq( + addresses.getAddress("OptimismPortalProxy", OP_CHAIN_ID), + 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed, + "OP Portal address mismatch" + ); + assertEq( + addresses.getAddress("L1StandardBridgeProxy", OP_CHAIN_ID), + 0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1, + "OP Bridge address mismatch" + ); + assertEq( + addresses.getAddress("L1CrossDomainMessengerProxy", OP_CHAIN_ID), + 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1, + "OP Messenger address mismatch" + ); + + assertTrue(addresses.isAddressContract("OptimismPortalProxy", OP_CHAIN_ID), "OP Portal should be a contract"); + assertTrue(addresses.isAddressContract("L1StandardBridgeProxy", OP_CHAIN_ID), "OP Bridge should be a contract"); + assertTrue( + addresses.isAddressContract("L1CrossDomainMessengerProxy", OP_CHAIN_ID), "OP Messenger should be a contract" + ); + + assertTrue(addresses.isAddressRegistered("OptimismPortalProxy", OP_CHAIN_ID), "OP Portal should be registered"); + assertTrue( + addresses.isAddressRegistered("L1StandardBridgeProxy", OP_CHAIN_ID), "OP Bridge should be registered" + ); + assertTrue( + addresses.isAddressRegistered("L1CrossDomainMessengerProxy", OP_CHAIN_ID), + "OP Messenger should be registered" + ); + + assertEq( + addresses.getAddress("OptimismPortalProxy", BASE_CHAIN_ID), + 0x49048044D57e1C92A77f79988d21Fa8fAF74E97e, + "Base Portal address mismatch" + ); + assertEq( + addresses.getAddress("L1StandardBridgeProxy", BASE_CHAIN_ID), + 0x3154Cf16ccdb4C6d922629664174b904d80F2C35, + "Base Bridge address mismatch" + ); + assertEq( + addresses.getAddress("L1CrossDomainMessengerProxy", BASE_CHAIN_ID), + 0x866E82a600A1414e583f7F13623F1aC5d58b0Afa, + "Base Messenger address mismatch" + ); + + assertTrue( + addresses.isAddressContract("OptimismPortalProxy", BASE_CHAIN_ID), "Base Portal should be a contract" + ); + assertTrue( + addresses.isAddressContract("L1StandardBridgeProxy", BASE_CHAIN_ID), "Base Bridge should be a contract" + ); + assertTrue( + addresses.isAddressContract("L1CrossDomainMessengerProxy", BASE_CHAIN_ID), + "Base Messenger should be a contract" + ); + + assertTrue( + addresses.isAddressRegistered("OptimismPortalProxy", BASE_CHAIN_ID), "Base Portal should be registered" + ); + assertTrue( + addresses.isAddressRegistered("L1StandardBridgeProxy", BASE_CHAIN_ID), "Base Bridge should be registered" + ); + assertTrue( + addresses.isAddressRegistered("L1CrossDomainMessengerProxy", BASE_CHAIN_ID), + "Base Messenger should be registered" + ); + } + + function testInvalidL2ChainIdGetAddressFails() public { + vm.expectRevert("L2 Chain ID 999 not supported"); + addresses.getAddress("DEPLOYER_EOA", 999); + } + + function testGetNonExistentAddressFails() public { + vm.expectRevert("Address not found"); + addresses.getAddress("NON_EXISTENT_ADDRESS", OP_CHAIN_ID); + } + + function testInvalidL2ChainIdIsAddressContractFails() public { + vm.expectRevert("L2 Chain ID 999 not supported"); + addresses.isAddressContract("DEPLOYER_EOA", 999); + } + + function testGetIsAddressContractNonExistentAddressFails() public { + vm.expectRevert("Address not found for identifier NON_EXISTENT_ADDRESS on chain 10"); + addresses.isAddressContract("NON_EXISTENT_ADDRESS", OP_CHAIN_ID); + } + + /// Construction failure tests + + function testInvalidChainIdInSuperchainsFails() public { + string memory tomlFilePath = "src/fps/addresses"; + string memory tomlchainListPath = "test/mock/chainList1.toml"; + + vm.expectRevert("Invalid chain ID in superchain config"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testDuplicateChainIdInSuperchainsFails() public { + string memory tomlFilePath = "src/fps/addresses"; + string memory tomlchainListPath = "test/mock/chainList3.toml"; + + vm.expectRevert("Duplicate chain ID in superchain config"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testEmptyNameInSuperchainsFails() public { + string memory tomlFilePath = "src/fps/addresses"; + string memory tomlchainListPath = "test/mock/chainList2.toml"; + + vm.expectRevert("Empty name in superchain config"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testConstructionFailsIncorrectTypesEOA() public { + string memory tomlFilePath = "test/mock/data1"; + string memory tomlchainListPath = "test/mock/data1/chainList.toml"; + + vm.expectRevert("Address must contain code"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testConstructionFailsIncorrectTypesContract() public { + string memory tomlFilePath = "test/mock/data2"; + string memory tomlchainListPath = "test/mock/data2/chainList.toml"; + + vm.expectRevert("Address must not contain code"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testConstructionFailsAddressZero() public { + string memory tomlFilePath = "test/mock/data3"; + string memory tomlchainListPath = "test/mock/data3/chainList.toml"; + + vm.expectRevert("Invalid address: cannot be zero"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } + + function testConstructionFailsDuplicateAddress() public { + string memory tomlFilePath = "test/mock/data4"; + string memory tomlchainListPath = "test/mock/data4/chainList.toml"; + + vm.expectRevert("Address already registered with this identifier and chain ID"); + new AddressRegistry(tomlFilePath, tomlchainListPath); + } +} diff --git a/test/mock/chainList1.toml b/test/mock/chainList1.toml new file mode 100644 index 00000000..ac9bdc9f --- /dev/null +++ b/test/mock/chainList1.toml @@ -0,0 +1,7 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 0 + +[[chains]] + name = "Base" + chain_id = 8453 \ No newline at end of file diff --git a/test/mock/chainList2.toml b/test/mock/chainList2.toml new file mode 100644 index 00000000..57c947ea --- /dev/null +++ b/test/mock/chainList2.toml @@ -0,0 +1,7 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 + +[[chains]] + name = "" + chain_id = 8453 \ No newline at end of file diff --git a/test/mock/chainList3.toml b/test/mock/chainList3.toml new file mode 100644 index 00000000..e4641910 --- /dev/null +++ b/test/mock/chainList3.toml @@ -0,0 +1,7 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 + +[[chains]] + name = "OP Mainnet" + chain_id = 10 \ No newline at end of file diff --git a/test/mock/data1/10.toml b/test/mock/data1/10.toml new file mode 100644 index 00000000..d23d3598 --- /dev/null +++ b/test/mock/data1/10.toml @@ -0,0 +1,14 @@ +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = true + +[[addresses]] +addr = "0xc0da02939e1441f497fd74f78ce7decb17b66529" +identifier = "COMPOUND_GOVERNOR_BRAVO" +isContract = true + +[[addresses]] +addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3" +identifier = "COMPOUND_CONFIGURATOR" +isContract = true \ No newline at end of file diff --git a/test/mock/data1/chainList.toml b/test/mock/data1/chainList.toml new file mode 100644 index 00000000..d3c8114d --- /dev/null +++ b/test/mock/data1/chainList.toml @@ -0,0 +1,3 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 \ No newline at end of file diff --git a/test/mock/data2/10.toml b/test/mock/data2/10.toml new file mode 100644 index 00000000..681ab956 --- /dev/null +++ b/test/mock/data2/10.toml @@ -0,0 +1,14 @@ +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = false + +[[addresses]] +addr = "0xc0da02939e1441f497fd74f78ce7decb17b66529" +identifier = "COMPOUND_GOVERNOR_BRAVO" +isContract = false + +[[addresses]] +addr = "0x316f9708bB98af7dA9c68C1C3b5e79039cD336E3" +identifier = "COMPOUND_CONFIGURATOR" +isContract = true \ No newline at end of file diff --git a/test/mock/data2/chainList.toml b/test/mock/data2/chainList.toml new file mode 100644 index 00000000..d3c8114d --- /dev/null +++ b/test/mock/data2/chainList.toml @@ -0,0 +1,3 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 \ No newline at end of file diff --git a/test/mock/data3/10.toml b/test/mock/data3/10.toml new file mode 100644 index 00000000..eed11e74 --- /dev/null +++ b/test/mock/data3/10.toml @@ -0,0 +1,4 @@ +[[addresses]] +addr = "0x0000000000000000000000000000000000000000" +identifier = "DEPLOYER_EOA" +isContract = false diff --git a/test/mock/data3/chainList.toml b/test/mock/data3/chainList.toml new file mode 100644 index 00000000..d3c8114d --- /dev/null +++ b/test/mock/data3/chainList.toml @@ -0,0 +1,3 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 \ No newline at end of file diff --git a/test/mock/data4/10.toml b/test/mock/data4/10.toml new file mode 100644 index 00000000..a7336d5c --- /dev/null +++ b/test/mock/data4/10.toml @@ -0,0 +1,9 @@ +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = false + +[[addresses]] +addr = "0x9679E26bf0C470521DE83Ad77BB1bf1e7312f739" +identifier = "DEPLOYER_EOA" +isContract = false diff --git a/test/mock/data4/chainList.toml b/test/mock/data4/chainList.toml new file mode 100644 index 00000000..d3c8114d --- /dev/null +++ b/test/mock/data4/chainList.toml @@ -0,0 +1,3 @@ +[[chains]] + name = "OP Mainnet" + chain_id = 10 \ No newline at end of file From 3744a38c50b53a4dad5280fef2c340607b3d2044 Mon Sep 17 00:00:00 2001 From: blaine Date: Mon, 16 Dec 2024 17:40:43 -0500 Subject: [PATCH 12/13] Resubmission: Unichain sepolia init holocene fp (#416) * Add anchor state registry init task for unichain sepolia * Document execution * Add set impls tx hash * fix: push through pr - link tx hash. --------- Co-authored-by: Joshua Gutow --- tasks/sep/unichain-001-init-holocene-fp/.env | 7 + .../unichain-001-init-holocene-fp/README.md | 16 ++ .../SignFromJson.s.sol | 149 ++++++++++++++++++ .../VALIDATION.md | 28 ++++ .../unichain-001-init-holocene-fp/input.json | 79 ++++++++++ 5 files changed, 279 insertions(+) create mode 100644 tasks/sep/unichain-001-init-holocene-fp/.env create mode 100644 tasks/sep/unichain-001-init-holocene-fp/README.md create mode 100644 tasks/sep/unichain-001-init-holocene-fp/SignFromJson.s.sol create mode 100644 tasks/sep/unichain-001-init-holocene-fp/VALIDATION.md create mode 100644 tasks/sep/unichain-001-init-holocene-fp/input.json diff --git a/tasks/sep/unichain-001-init-holocene-fp/.env b/tasks/sep/unichain-001-init-holocene-fp/.env new file mode 100644 index 00000000..cce56409 --- /dev/null +++ b/tasks/sep/unichain-001-init-holocene-fp/.env @@ -0,0 +1,7 @@ +ETH_RPC_URL="https://ethereum-sepolia.publicnode.com" + +SAFE_NONCE="" +L1_CHAIN_NAME="sepolia" +L2_CHAIN_NAME="unichain-sepolia" +OWNER_SAFE="0xd363339eE47775888Df411A163c586a8BdEA9dbf" +SYSTEM_CONFIG="0xaeE94b9aB7752D3F7704bDE212c0C6A0b701571D" diff --git a/tasks/sep/unichain-001-init-holocene-fp/README.md b/tasks/sep/unichain-001-init-holocene-fp/README.md new file mode 100644 index 00000000..4ce9a06d --- /dev/null +++ b/tasks/sep/unichain-001-init-holocene-fp/README.md @@ -0,0 +1,16 @@ +# ProxyAdminOwner - Set Dispute Game Implementation + +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x10a424c2014650d3f66908180e201569390464edbe131ec65b4d1b7044df0e33) + +The DisputeGameFactory was updated to use the Holocene FP contracts in Tx 0xfe8a204c3994f6e0a62a944ecf2f634c2de172bddec714fefab368c6f95604e2. + +## Objective + +This task updates the fault dispute system for unichain-sepolia: + +* Re-initialize `AnchorStateRegistry` 0xf971F1b0D80eb769577135b490b913825BfcF00B with the anchor state for game types 0 set to 0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9, 6801092 + + +### State Validations + +Please see the instructions for [validation](./VALIDATION.md). diff --git a/tasks/sep/unichain-001-init-holocene-fp/SignFromJson.s.sol b/tasks/sep/unichain-001-init-holocene-fp/SignFromJson.s.sol new file mode 100644 index 00000000..cb0c3b1e --- /dev/null +++ b/tasks/sep/unichain-001-init-holocene-fp/SignFromJson.s.sol @@ -0,0 +1,149 @@ +// 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 {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 {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 SignFromJson is OriginalSignFromJson { + 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"))); + + // 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")); + + // DisputeGameFactoryProxy address. + DisputeGameFactory dgfProxy; + + address[] extraStorageAccessAddresses; + + function setUp() public { + dgfProxy = DisputeGameFactory(systemConfig.disputeGameFactory()); + extraStorageAccessAddresses.push(0xf971F1b0D80eb769577135b490b913825BfcF00B); + _precheckAnchorStateCopy(GameType.wrap(1), GameType.wrap(0)); + // INSERT NEW PRE CHECKS HERE + } + + function getCodeExceptions() internal view override returns (address[] memory) { + return new address[](0); + } + + // _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); + + 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), bytes32(0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9), 6801092); + _postcheckHasAnchorState(GameType.wrap(1)); + // INSERT NEW POST CHECKS HERE + + 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 _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/sep/unichain-001-init-holocene-fp/VALIDATION.md b/tasks/sep/unichain-001-init-holocene-fp/VALIDATION.md new file mode 100644 index 00000000..a4d99b31 --- /dev/null +++ b/tasks/sep/unichain-001-init-holocene-fp/VALIDATION.md @@ -0,0 +1,28 @@ +# Validation + +This document can be used to validate the state diff resulting from the execution of the upgrade transaction. + +For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff +are missing from this document. Additionally, please verify that for each contract: + +- The following state changes (and none others) are made to that contract. This validates that no unexpected state + changes occur. +- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain + Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic. +- All key values match the semantic meaning provided, which can be validated using the storage layout links provided. + +## State Changes + +Note: The changes listed below do not include safe nonce updates or liveness guard related changes. + +### `0xf971F1b0D80eb769577135b490b913825BfcF00B` (`AnchorStateRegistryProxy`) + +- **Key**: `0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000` (Note this may have changed if games of this type resolved)
+ **After**: `0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9`
+ **Meaning**: Set the anchor state output root for game type 0 to 0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9. + +- **Key**: `0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb4a`
+ **Before**: `0x0000000000000000000000000000000000000000000000000000000000000000` (Note this may have changed if games of this type resolved)
+ **After**: `0x67c6c4`
+ **Meaning**: Set the anchor state L2 block number for game type 0 to 6801092. diff --git a/tasks/sep/unichain-001-init-holocene-fp/input.json b/tasks/sep/unichain-001-init-holocene-fp/input.json new file mode 100644 index 00000000..dcf5c1e5 --- /dev/null +++ b/tasks/sep/unichain-001-init-holocene-fp/input.json @@ -0,0 +1,79 @@ +{ + "metadata": { + "name": "ProxyAdminOwner - Set Dispute Game Implementation", + "description": "Re-initialize with anchor states for game types 0 set to 0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9, 6801092 " + }, + "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": "0x2BF403E5353A7a082ef6bb3Ae2Be3B866D8D3ea4", + "value": "0x0", + "data": "0x9623609d000000000000000000000000f971f1b0d80eb769577135b490b913825bfcf00b00000000000000000000000054f8076f4027e21a010b4b3900c86211dd2c2deb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000444e91db080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "contractMethod": { + "type": "function", + "name": "upgradeAndCall", + "inputs": [ + { + "name": "_proxy", + "type": "address" + }, + { + "name": "_implementation", + "type": "address" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + "contractInputsValues": { + "_proxy": "0xf971F1b0D80eb769577135b490b913825BfcF00B", + "_implementation": "0x54F8076f4027e21A010b4B3900C86211Dd2C2DEB", + "_data": "0x4e91db0800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "metadata": { + "name": "Re-initialize the AnchorStateRegistryProxy", + "description": "Re-initialize with anchor states for game types 0 set to 0x3dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9, 6801092" + }, + "to": "0x2BF403E5353A7a082ef6bb3Ae2Be3B866D8D3ea4", + "value": "0x0", + "data": "0x9623609d000000000000000000000000f971f1b0d80eb769577135b490b913825bfcf00b000000000000000000000000cc9ecb6a9b83c333af2bf666158bd0eef73c7858000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c45e05fbd00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000003dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9000000000000000000000000000000000000000000000000000000000067c6c400000000000000000000000000000000000000000000000000000000", + "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": "0xf971F1b0D80eb769577135b490b913825BfcF00B", + "_implementation": "0xCc9eCB6A9B83C333af2BF666158BD0eEF73c7858", + "_data": "0x5e05fbd00000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000003dd61be7c3e870294e842a0e3a7150fb5b73539260a9ec55d59151ba5f2201e9000000000000000000000000000000000000000000000000000000000067c6c4" + } + } + ] +} From 059f815b4da95791ad48a67b116f62d4918d56b7 Mon Sep 17 00:00:00 2001 From: blaine Date: Tue, 17 Dec 2024 12:43:10 -0500 Subject: [PATCH 13/13] fix: make as executed. (#419) --- tasks/sep/base-003-fp-granite-prestate/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/sep/base-003-fp-granite-prestate/README.md b/tasks/sep/base-003-fp-granite-prestate/README.md index f88c5a22..ed2b5a98 100644 --- a/tasks/sep/base-003-fp-granite-prestate/README.md +++ b/tasks/sep/base-003-fp-granite-prestate/README.md @@ -1,6 +1,6 @@ # Sepolia FP Upgrade - Granite Prestate Update -Status: READY TO SIGN +Status: [EXECUTED](https://sepolia.etherscan.io/tx/0x2f2ce171fab5c56b31e8b1b7dc33ca0453d00e7b09c7317302916b2be98b7a0e) ## Objective