diff --git a/README.md b/README.md index c30a19633c..040a3e64ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ > **⚠️ Important Notice: System Upgrade (August 2025)** -> +> > The Superchain-ops system has undergone a significant upgrade. For access to historical executed tasks and previous system documentation, please refer to this [tag](https://github.com/ethereum-optimism/superchain-ops/tree/legacy-superchain-ops) for the archived tasks repository. > > • Need help? [Create an issue](https://github.com/ethereum-optimism/superchain-ops/issues) on this repo. @@ -86,7 +86,7 @@ The `[stateOverrides]` TOML table is optional, but in most cases we use it to sp ```toml # USE HEX ENCODED STRINGS WHEN POSSIBLE. [stateOverrides] -0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ +0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ { key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = "0x0000000000000000000000000000000000000000000000000000000000000017" } ] ``` @@ -95,7 +95,7 @@ However, in some cases it's possible to use the decimal value directly: ```toml # IN SOME CASES, YOU CAN USE THE DECIMAL VALUE DIRECTLY. [stateOverrides] -0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ +0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ { key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 23 } ] ``` @@ -104,7 +104,7 @@ But **do not** pass the decimal value as a string—this will cause undefined be ```toml # ❌ INCORRECT: DO NOT USE STRINGIFIED DECIMALS. [stateOverrides] -0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ +0x847B5c174615B1B7fDF770882256e2D3E95b9D92 = [ { key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = "23" } ] ``` @@ -127,7 +127,7 @@ just --dotenv-path $(pwd)/.env simulate [child-safe-name-depth-1] [child-safe-na - **Nested Safe Operations** (see [NESTED.md](src/NESTED.md)): ```bash just --dotenv-path $(pwd)/.env simulate foundation - just --dotenv-path $(pwd)/.env simulate council + just --dotenv-path $(pwd)/.env simulate council just --dotenv-path $(pwd)/.env simulate chain-governor ``` @@ -149,7 +149,7 @@ just simulate-stack [child-safe-name-depth-1] [child-safe- 7. Fill out the `README.md` and `VALIDATION.md` files. - If your task status is not `EXECUTED` or `CANCELLED`, it is considered non-terminal and will automatically be included in stacked simulations. - - If your task has a `VALIDATION.md` file, you **must** fill out the `Normalized State Diff Hash Attestation` section. This is so that we can detect if the normalized state diff hash changes unexpectedly. You **must** also fill out the `Expected Domain and Message Hashes` section. This is so that we can detect if the domain and message hashes change unexpectedly. Any mismatches will cause the task to revert. + - If your task has a `VALIDATION.md` file, you **must** fill out the `Expected Domain and Message Hashes` section. This is so that we can detect if the domain and message hashes change unexpectedly. Any mismatches will cause the task to revert. ## FAQ @@ -163,7 +163,7 @@ Stacked simulations are supported. To use this feature, you can use the followin just simulate-stack [task] [child-safe-name-depth-1] [child-safe-name-depth-2] ``` -e.g. +e.g. ```bash just simulate-stack eth # Simulate all tasks for ethereum just simulate-stack eth 001-example # Simulate specific task on root safe @@ -282,7 +282,7 @@ By adding an address to [`addresses.toml`](./src/addresses.toml), you ensure it' ### What if I want to upgrade a chain that is not in the superchain-registry? -If the chain you want to upgrade is not in the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry), you can manually provide a fallback JSON file in your task's `config.toml` (as `fallbackAddressesJsonPath`). +If the chain you want to upgrade is not in the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry), you can manually provide a fallback JSON file in your task's `config.toml` (as `fallbackAddressesJsonPath`). ```toml l2chains = [{name = "Unichain", chainId = 1333330}] @@ -298,19 +298,6 @@ When the task runs, it will first attempt to use the superchain-registry. If the > ⚠️ **Note**: You must manually provide all contract addresses required by your task template in the fallback JSON file. -### What is the 'Normalized State Diff Hash'? - -The normalized state diff hash is a single fingerprint of all the onchain state changes your task would make if executed. We “normalize” the diff first (stable ordering and encoding) so the hash only changes when the actual intended state changes do. - -- Why it exists: To make it easy for reviewers and signers to verify they’re approving the exact same change set that was simulated and reviewed. -- Where to see it: It’s printed at the end of simulation/stacked simulation and recorded in each task’s `VALIDATION.md` under “Normalized State Diff Hash Attestation”. -- When it changes: If any task state we consider meaningful changes, the hash will also change. - -What to do if it doesn’t match: -- **Do not proceed or sign**. -- Contact the task authors and update the task only after review. - - ## Available Templates -All available templates can be found in the [template](src/template/) directory. \ No newline at end of file +All available templates can be found in the [template](src/template/) directory. diff --git a/src/NESTED.md b/src/NESTED.md index 7c461dfe06..4e3d34db09 100644 --- a/src/NESTED.md +++ b/src/NESTED.md @@ -119,6 +119,22 @@ message hash: `0x1901[domain hash][message hash]`. Note down this value. You will need to compare it with the ones displayed on the Ledger screen at signing. +#### 3.4 Validate with op-txverify + +Use [op-txverify](https://github.com/ethereum-optimism/op-txverify) to confirm +the transaction and the message/domain hashes. + +1. Run the `simulate` command for your given Superchain Ops task. +2. Find the **`OP-TXVERIFY LINK`** section of the Superchain Ops task output. +3. Copy the link as shown in the output (links to `op-txverify.optimism.io`) and send it to another device (phone is easiest). +4. Run `op-txverify qr` on your first device. This will open a QR scanner. +5. Open the link on your second device. This will start flashing a series of QR codes. +6. Place your second device in front of the camera of the first device. Once enough QR codes have been scanned, you’ll see a success message. +7. Return to the first device and view the output of `op-txverify`. +8. Review the transaction contents and confirm that it matches the transaction that you expect to send, including all addresses and input parameters. +9. Review the message hash and domain hash and confirm that it matches the hashes presented in Superchain Ops. +10. Return to Superchain Ops to complete signing. + ### 4. Approve the signature on your ledger Once the validations are done, it's time to actually sign the diff --git a/src/SINGLE.md b/src/SINGLE.md index fbabb86075..a697282f9c 100644 --- a/src/SINGLE.md +++ b/src/SINGLE.md @@ -114,6 +114,22 @@ message hash: `0x1901[domain hash][message hash]`. Note down this value. You will need to compare it with the ones displayed on the Ledger screen at signing. +#### 3.4 Validate with op-txverify + +Use [op-txverify](https://github.com/ethereum-optimism/op-txverify) to confirm +the transaction and the message/domain hashes. + +1. Run the `simulate` command for your given Superchain Ops task. +2. Find the **`OP-TXVERIFY LINK`** section of the Superchain Ops task output. +3. Copy the link as shown in the output (links to `op-txverify.optimism.io`) and send it to another device (phone is easiest). +4. Run `op-txverify qr` on your first device. This will open a QR scanner. +5. Open the link on your second device. This will start flashing a series of QR codes. +6. Place your second device in front of the camera of the first device. Once enough QR codes have been scanned, you’ll see a success message. +7. Return to the first device and view the output of `op-txverify`. +8. Review the transaction contents and confirm that it matches the transaction that you expect to send, including all addresses and input parameters. +9. Review the message hash and domain hash and confirm that it matches the hashes presented in Superchain Ops. +10. Return to Superchain Ops to complete signing. + ### 4. Approve the signature on your ledger Once the validations are done, it's time to actually sign the diff --git a/src/libraries/AccountAccessParser.sol b/src/libraries/AccountAccessParser.sol index 4068af7f6c..e0564c5e8c 100644 --- a/src/libraries/AccountAccessParser.sol +++ b/src/libraries/AccountAccessParser.sol @@ -13,7 +13,7 @@ import {Utils} from "src/libraries/Utils.sol"; /// @notice Parses account accesses into decoded transfers and state diffs. /// The core methods intended to be part of the public interface are `decodeAndPrint`, `decode`, -/// `getUniqueWrites`, `getStateDiffFor`, and `normalizedStateDiffHash`. Example usage: +/// `getUniqueWrites`, and `getStateDiffFor`. Example usage: /// /// ```solidity /// contract MyContract { @@ -37,9 +37,6 @@ import {Utils} from "src/libraries/Utils.sol"; /// /// // Get all new contracts created. /// address[] memory newContracts = accountAccesses.getNewContracts(); -/// -/// // Get the normalized state diff hash. -/// bytes32 normalizedStateDiffHash = accountAccesses.normalizedStateDiffHash(parentMultisig, txHash); /// } /// } /// ``` @@ -87,14 +84,6 @@ library AccountAccessParser { address tokenAddress; } - // This struct represents a state change with the account information - struct AccountStateDiff { - address who; - bytes32 slot; - bytes32 firstOld; - bytes32 lastNew; - } - // Leading underscore because some of the raw keys are reserved words in Solidity, and we need // the keys to be ordered alphabetically here for foundry. struct JsonStorageLayout { @@ -139,7 +128,7 @@ library AccountAccessParser { bytes32 internal constant ANCHOR_STATE_REGISTRY_V410_PROPOSAL_ROOT_SLOT = bytes32(uint256(3)); bytes32 internal constant ANCHOR_STATE_REGISTRY_V410_PROPOSAL_L2_SEQUENCE_NUMBER_SLOT = bytes32(uint256(4)); bytes32 internal constant ANCHOR_STATE_REGISTRY_V410_RETIREMENT_TIMESTAMP_SLOT = bytes32(uint256(6)); - + // op-contracts/v3.0.0 - AnchorStateRegistry version: 2.2.2 bytes32 internal constant ANCHOR_STATE_REGISTRY_V300_OUTPUT_ROOT_STARTING_ANCHOR_ROOT_SLOT = bytes32(uint256(4)); bytes32 internal constant ANCHOR_STATE_REGISTRY_V300_OUTPUT_ROOT_L2_BLOCK_NUMBER_SLOT = bytes32(uint256(5)); @@ -267,212 +256,6 @@ library AccountAccessParser { } } - /// @notice Computes a hash of the normalized state diff from account accesses. The spec for - /// method is: - /// 1. The input is an array of `VmSafe.AccountAccess[]` containing all storage writes. - /// 2. It calls `AccountAccessParser.getUniqueWrites` to filter down all storage writes to a state diff - /// 3. With that state diff, we normalize it by removing data from the state diff array that - /// may change between initial simulation and execution. Removal is done by simply removing - /// the entry from the `AccountAccess[]` array. The set of state changes to remove is: - /// 1. If the state change is an EOA nonce increment, remove it. - /// 2. Remove the following state changes from Gnosis Safes: - /// 1. Nonce increments. - /// 2. Setting an approve hash in storage (the hash is dependent on the nonce, which may change) - /// 3. If the storage slot contains a timestamp, normalize that timestamp to be all zeroes. - /// 1. This will have to be informed by knowing the storage layouts, which is ok - /// 2. We should only normalize the specific section of the slot corresponding to the - /// timestamp, since some timestamps are packed into slots with other data. - /// 4. If the slot is on the LivenessGuard, remove it. - /// 5. The hash to return is computed as `keccak256(abi.encode(normalizedArray))`. - /// @return bytes32 hash of the normalized state diff - function normalizedStateDiffHash( - VmSafe.AccountAccess[] memory _accountAccesses, - address _parentMultisig, - bytes32 _txHash - ) internal view noGasMetering returns (bytes32) { - // Get all storage writes as a state diff. - address[] memory uniqueAddresses = getUniqueWrites({accesses: _accountAccesses, _sort: false}); - - // Create a temporary array to store normalized state changes. - AccountStateDiff[] memory normalizedChanges = new AccountStateDiff[](MAX_STATE_CHANGES); - uint256 normalizedCount = 0; - - // Process each account with storage writes. - for (uint256 i = 0; i < uniqueAddresses.length; i++) { - address account = uniqueAddresses[i]; - StateDiff[] memory diffs = getStateDiffFor({accesses: _accountAccesses, who: account, _sort: false}); - - // Process each diff and apply normalization logic. - for (uint256 j = 0; j < diffs.length; j++) { - StateDiff memory diff = diffs[j]; - if (shouldIncludeDiff(account, diff, _parentMultisig, _txHash)) { - diff = normalizeTimestamp(account, diff); // Normalize the timestamp if present. - normalizedChanges[normalizedCount] = AccountStateDiff({ - who: account, - slot: diff.slot, - firstOld: diff.oldValue, - lastNew: diff.newValue - }); - normalizedCount++; - require(normalizedCount < MAX_STATE_CHANGES, "AccountAccessParser: Max state changes reached"); - } - } - } - - // Create the final array with the correct size. - AccountStateDiff[] memory finalArray = new AccountStateDiff[](normalizedCount); - for (uint256 i = 0; i < normalizedCount; i++) { - finalArray[i] = normalizedChanges[i]; - } - - // Return keccak256 hash of the abi-encoded normalized array. - return keccak256(abi.encode(finalArray)); - } - - function shouldIncludeDiff(address account, StateDiff memory diff, address _parentMultisig, bytes32 _txHash) - internal - view - returns (bool) - { - if (isEOANonceIncrement(account, diff)) { - // 1. If the state change is an EOA nonce increment, remove it. - return false; - } else if (isGnosisSafe(account)) { - // 2. Remove Gnosis Safe nonce increment and approve hash changes. - if (isGnosisSafeNonceIncrement(diff) || isGnosisSafeApproveHash(diff, _parentMultisig, _txHash)) { - // 2.1 Nonce increment or 2.2 Setting an approve hash in storage. - return false; - } - } else if (isLivenessGuardTimestamp(account, diff, _parentMultisig)) { - // 4. If the slot is on the LivenessGuard, don't include it. - return false; - } else if (isOptimismPortalResourceMetering(diff)) { - // 5. If the slot is on the OptimismPortalResourceParams, don't include it. - return false; - } else if (isAnchorStateRegistryProposal(account, diff)) { - // 6. If the diff is an AnchorStateRegistry Proposal, don't include it. - return false; - } - return true; - } - - /// @notice Any function in the OptimismPortal that has the 'metered' modifier will have a non-deterministic state change. - function isOptimismPortalResourceMetering(StateDiff memory _diff) internal view returns (bool) { - if (_diff.slot == OPTIMISM_PORTAL_RESOURCE_PARAMS_SLOT) { - // Extract prevBlockNum from the packed value. It's located in the most significant 64 bits. - // ResourceParams is packed as follows: prevBlockNum (64 bits) | prevBoughtGas (64 bits) | prevBaseFee (128 bits) - uint256 prevBlockNum = uint64(uint256(_diff.newValue) >> (128 + 64)); - // If the current block number is equal to the new values prevBlockNum, then we should remove this - // state change because it means we have a nondeterministic change based on block number at simulation time - return block.number == prevBlockNum; - } - return false; - } - - /// @notice Checks if the state diff represents an EOA nonce increment - function isEOANonceIncrement(address _account, StateDiff memory _diff) internal view returns (bool) { - uint256 codeSize = _account.code.length; - return codeSize == 0 && _diff.slot == bytes32(0) && uint256(_diff.newValue) == uint256(_diff.oldValue) + 1; - } - - /// @notice Checks if the state diff represents a Gnosis Safe nonce increment - function isGnosisSafeNonceIncrement(StateDiff memory _diff) internal pure returns (bool) { - // In Gnosis Safe, the nonce is stored at slot 5. See `GnosisSafeStorage.sol` to verify. - return _diff.slot == GNOSIS_SAFE_NONCE_SLOT && uint256(_diff.newValue) == uint256(_diff.oldValue) + 1; - } - - /// @notice Checks if the state diff represents setting an approve hash in a Gnosis Safe - function isGnosisSafeApproveHash(StateDiff memory _diff, address _parentMultisig, bytes32 _txHash) - internal - view - returns (bool) - { - bytes32[] memory hashSlots = calculateApproveHashSlots(IGnosisSafe(_parentMultisig).getOwners(), _txHash); - for (uint256 i = 0; i < hashSlots.length; i++) { - if (_diff.slot == hashSlots[i]) { - require( - (_diff.oldValue == bytes32(0) && _diff.newValue == bytes32(uint256(1))) - // Some Gnosis Safe versions set approvedHashes to zero upon execution e.g. mainnet FoundationOperationsSafe. - || (_diff.oldValue == bytes32(uint256(1)) && _diff.newValue == bytes32(0)), - "AccountAccessParser: Unexpected approve hash state change." - ); - return true; - } - } - return false; - } - - /// @notice Checks if the given slot matches any liveness guard timestamp for the signers on child multisigs. - function isLivenessGuardTimestamp(address _account, StateDiff memory _diff, address _parentMultisig) - internal - view - returns (bool) - { - if (!isLivenessGuard(_account)) { - return false; - } - - return _checkOwnersForLivenessSlot(_parentMultisig, _diff.slot); - } - - /// @notice Checks if the given slot matches any liveness guard timestamp for owners and nested safe owners - function _checkOwnersForLivenessSlot(address _multisig, bytes32 _slot) internal view returns (bool) { - address[] memory owners = IGnosisSafe(_multisig).getOwners(); - - for (uint256 i = 0; i < owners.length; i++) { - // Check if this owner's liveness slot matches - if (_isOwnerLivenessSlot(owners[i], _slot)) { - return true; - } - - // If this owner is a nested safe, check its owners too - if (isGnosisSafe(owners[i])) { - address[] memory nestedOwners = IGnosisSafe(owners[i]).getOwners(); - for (uint256 j = 0; j < nestedOwners.length; j++) { - if (_isOwnerLivenessSlot(nestedOwners[j], _slot)) { - return true; - } - } - } - } - - return false; - } - - /// @notice Checks if the given slot matches the liveness guard timestamp slot for a specific owner - function _isOwnerLivenessSlot(address _owner, bytes32 _slot) internal pure returns (bool) { - bytes32 ownerSlot = keccak256(abi.encode(_owner, LIVENESS_GUARD_LAST_LIVE_SLOT)); - return _slot == ownerSlot; - } - - function isAnchorStateRegistryProposal(address _account, StateDiff memory _diff) internal view returns (bool) { - if (isAnchorStateRegistry(_account)) { - // The proposal is stored in slot 3 and 4. - if (isAnchorStateRegistryV410(_account)) { - return _diff.slot == ANCHOR_STATE_REGISTRY_V410_PROPOSAL_ROOT_SLOT - || _diff.slot == ANCHOR_STATE_REGISTRY_V410_PROPOSAL_L2_SEQUENCE_NUMBER_SLOT; - } else if (isAnchorStateRegistryV300(_account)) { - return _diff.slot == ANCHOR_STATE_REGISTRY_V300_OUTPUT_ROOT_STARTING_ANCHOR_ROOT_SLOT - || _diff.slot == ANCHOR_STATE_REGISTRY_V300_OUTPUT_ROOT_L2_BLOCK_NUMBER_SLOT; - } - } - return false; - } - - /// @notice Normalizes a timestamp in a storage slot by zeroing out only the timestamp portion if present. - function normalizeTimestamp(address _account, StateDiff memory _diff) internal view returns (StateDiff memory) { - if (_diff.slot == ANCHOR_STATE_REGISTRY_V410_RETIREMENT_TIMESTAMP_SLOT) { - if (isAnchorStateRegistry(_account)) { - // The retirementTimestamp is introduced in the AnchorStateRegistry post op-contracts/v3.0.0-rc.2. - // Define a static mask to zero out 64 bits at offset 4 in little-endian format - bytes32 MASK = bytes32(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFF); - // Apply the static mask to zero out the specified bytes in the new value - _diff.newValue &= MASK; - } - } - return _diff; - } - /// @notice Extracts all unique storage writes (i.e. writes where the value has actually changed) function getUniqueWrites(VmSafe.AccountAccess[] memory accesses, bool _sort) internal diff --git a/src/libraries/MultisigTaskPrinter.sol b/src/libraries/MultisigTaskPrinter.sol index 5d999b5b2f..8637b140e0 100644 --- a/src/libraries/MultisigTaskPrinter.sol +++ b/src/libraries/MultisigTaskPrinter.sol @@ -104,17 +104,6 @@ library MultisigTaskPrinter { // ======= Verification Information ======== // ========================================== - /// @notice Prints normalized state diff hash. - function printNormalizedStateDiffHash(bytes32 normalizedStateDiffHash) internal pure { - printTitle("NORMALIZED STATE DIFF HASH"); - // forgefmt: disable-start - console.log("Ensure the normalized state diff hash from your simulation matches the hash in the VALIDATION.md file for this task."); - console.log(""); - console.log("Normalized hash: %s", vm.toString(normalizedStateDiffHash)); - console.log(""); - // forgefmt: disable-end - } - /// @notice Prints an OP-TxVerify link for transaction verification. function printOPTxVerifyLink( uint256 chainId, diff --git a/src/tasks/MultisigTask.sol b/src/tasks/MultisigTask.sol index 6bcb44d496..acbca1e98a 100644 --- a/src/tasks/MultisigTask.sol +++ b/src/tasks/MultisigTask.sol @@ -79,7 +79,7 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage /// This works by printing the 'data to sign' for the nested safe which is then passed to the eip712sign binary for signing. function simulate(string memory taskConfigFilePath, address[] memory _childSafes) public - returns (VmSafe.AccountAccess[] memory, Action[] memory, bytes32, bytes memory, address) + returns (VmSafe.AccountAccess[] memory, Action[] memory, bytes memory, address) { return _runTask(taskConfigFilePath, "", _childSafes, true); } @@ -89,7 +89,7 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage public returns (VmSafe.AccountAccess[] memory) { - (VmSafe.AccountAccess[] memory accountAccesses,,,,) = + (VmSafe.AccountAccess[] memory accountAccesses,,,) = _runTask(taskConfigFilePath, signatures, _childSafes, false); return accountAccesses; } @@ -574,16 +574,7 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage bytes memory _signatures, address[] memory _childSafes, bool _isSimulate - ) - internal - returns ( - VmSafe.AccountAccess[] memory, - Action[] memory, - bytes32 normalizedHash_, - bytes memory dataToSign_, - address rootSafe - ) - { + ) internal returns (VmSafe.AccountAccess[] memory, Action[] memory, bytes memory dataToSign_, address rootSafe) { (TaskPayload memory payload, Action[] memory actions) = _taskSetup(_taskConfigFilePath, _childSafes); uint256 rootSafeIndex = payload.safes.length - 1; rootSafe = payload.safes[rootSafeIndex]; @@ -591,14 +582,14 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage executeTaskStep(_signatures, payload, rootSafeIndex); validate(accountAccesses, actions, payload); - (normalizedHash_, dataToSign_) = print(accountAccesses, _isSimulate, txHash, payload); + dataToSign_ = print(accountAccesses, _isSimulate, txHash, payload); // Sanity check that the root safe is a nested safe. if (payload.safes.length > 1) { require(isNestedSafe(rootSafe), "MultisigTask: multisig must be a nested safe."); } - return (accountAccesses, actions, normalizedHash_, dataToSign_, rootSafe); + return (accountAccesses, actions, dataToSign_, rootSafe); } /// @notice Using the tasks config.toml file, this function configures the task. @@ -776,7 +767,7 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage bool isSimulate, bytes32 txHash, TaskPayload memory payload - ) public returns (bytes32 normalizedHash_, bytes memory dataToSign_) { + ) public returns (bytes memory dataToSign_) { console.log(""); MultisigTaskPrinter.printWelcomeMessage(); SafeData memory rootSafe = Utils.getSafeData(payload, payload.safes.length - 1); @@ -810,8 +801,6 @@ abstract contract MultisigTask is Test, Script, StateOverrideManager, TaskManage } _printTenderlySimulationData(payload); } - normalizedHash_ = AccountAccessParser.normalizedStateDiffHash(accountAccesses, rootSafe.safe, txHash); - MultisigTaskPrinter.printNormalizedStateDiffHash(normalizedHash_); } /// @notice Helper function to print the final safe information. diff --git a/src/tasks/TaskManager.sol b/src/tasks/TaskManager.sol index bcc31c3660..7f77ab4d25 100644 --- a/src/tasks/TaskManager.sol +++ b/src/tasks/TaskManager.sol @@ -97,7 +97,7 @@ contract TaskManager is Script { function executeTask(TaskConfig memory config, address[] memory _childSafes) public - returns (VmSafe.AccountAccess[] memory accesses_, bytes32 normalizedHash_, bytes memory dataToSign_) + returns (VmSafe.AccountAccess[] memory accesses_, bytes memory dataToSign_) { // Deploy and run the template string memory templatePath = string.concat("out/", config.templateName, ".sol/", config.templateName, ".json"); @@ -110,16 +110,7 @@ contract TaskManager is Script { string[] memory parts = vm.split(config.basePath, "/"); string memory taskName = parts[parts.length - 1]; - (accesses_, normalizedHash_, dataToSign_) = execute(config, task, _childSafes, taskName, formattedRootSafe); - require( - checkNormalizedHash(normalizedHash_, config), - string.concat( - "TaskManager: Normalized hash for task: ", - taskName, - " does not match. Got: ", - vm.toString(normalizedHash_) - ) - ); + (accesses_, dataToSign_) = execute(config, task, _childSafes, taskName, formattedRootSafe); require( checkDataToSign(dataToSign_, config), string.concat( @@ -138,7 +129,7 @@ contract TaskManager is Script { address[] memory _childSafes, string memory _taskName, string memory _formattedParentMultisig - ) private returns (VmSafe.AccountAccess[] memory accesses_, bytes32 normalizedHash_, bytes memory dataToSign_) { + ) private returns (VmSafe.AccountAccess[] memory accesses_, bytes memory dataToSign_) { string memory line = unicode"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; if (_config.isNested) { @@ -153,7 +144,7 @@ contract TaskManager is Script { console.log(""); address[] memory allSafes = Solarray.extend(_childSafes, Solarray.addresses(address(_config.rootSafe))); Utils.validateSafesOrder(allSafes); - (accesses_,, normalizedHash_, dataToSign_,) = _task.simulate(_config.configPath, _childSafes); + (accesses_,, dataToSign_,) = _task.simulate(_config.configPath, _childSafes); } else { // forgefmt: disable-start console.log(string.concat("SIMULATING SINGLE TASK: ", _taskName, " FOR ROOT SAFE: ", _formattedParentMultisig)); @@ -164,7 +155,7 @@ contract TaskManager is Script { _childSafes.length == 0, "TaskManager: child safes provided but not expected for a single safe task." ); - (accesses_,, normalizedHash_, dataToSign_,) = _task.simulate(_config.configPath, new address[](0)); + (accesses_,, dataToSign_,) = _task.simulate(_config.configPath, new address[](0)); } } @@ -191,17 +182,6 @@ contract TaskManager is Script { return false; } - /// @notice Cross check most recent normalized hash with normalized hash stored in VALIDATION markdown file. - /// @return 'false' when VALIDATION file is empty or contains the wrong hash. 'true' when VALIDATION file does not exist or contains the correct hash. - function checkNormalizedHash(bytes32 _normalizedHash, TaskConfig memory _config) public view returns (bool) { - bytes memory normalizedHashBytes = abi.encodePacked(_normalizedHash); - return checkValidationFile( - normalizedHashBytes, - _config, - "Normalized hash does not match. Please check that you've added it to the VALIDATION markdown file." - ); - } - /// @notice Cross check most recent data to sign with the domain and message hashes stored in VALIDATION markdown file. /// @return 'false' when VALIDATION file is empty or contains the wrong data to sign. 'true' when VALIDATION file does not exist or contains the correct data to sign. function checkDataToSign(bytes memory _dataToSign, TaskConfig memory _config) public view returns (bool) { diff --git a/src/tasks/eth/022-U16a-opcm-upgrade-v410-base/README.md b/src/tasks/eth/022-U16a-opcm-upgrade-v410-base/README.md index cfb02dd2fa..82ba798608 100644 --- a/src/tasks/eth/022-U16a-opcm-upgrade-v410-base/README.md +++ b/src/tasks/eth/022-U16a-opcm-upgrade-v410-base/README.md @@ -1,6 +1,6 @@ # 022-U16a-opcm-upgrade-v410-base: Upgrades Base Mainnet to `op-contracts/v4.1.0` (i.e. U16a) -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x98ef4f3ecc10996b184385bd4ca5b877dce0a3b527d88f4ee0cfe296cf004a38) ## Objective diff --git a/src/tasks/eth/025-zora-main-u13-to-u16a/README.md b/src/tasks/eth/025-zora-main-u13-to-u16a/README.md index 27256cf2aa..f352351423 100644 --- a/src/tasks/eth/025-zora-main-u13-to-u16a/README.md +++ b/src/tasks/eth/025-zora-main-u13-to-u16a/README.md @@ -1,6 +1,6 @@ # 025-zora-main-u13-to-u16a -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x3c9df2c9f2502ed27df838f21bf474be0544246f8c0c3513a698d81e0c2890ae) ## Objective diff --git a/src/tasks/eth/026-metal-main-u13-to-u16a/README.md b/src/tasks/eth/026-metal-main-u13-to-u16a/README.md index 6bbef5997e..29646675b1 100644 --- a/src/tasks/eth/026-metal-main-u13-to-u16a/README.md +++ b/src/tasks/eth/026-metal-main-u13-to-u16a/README.md @@ -1,6 +1,6 @@ # 026-metal-main-u13-to-u16a -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x9c091d94c0f98efb21730224c89e59e50d344b74e677ee6de2b91d2a16f565d2) ## Objective diff --git a/src/tasks/eth/027-mode-main-u13-to-u16a/README.md b/src/tasks/eth/027-mode-main-u13-to-u16a/README.md index dd81242ade..f020cdc685 100644 --- a/src/tasks/eth/027-mode-main-u13-to-u16a/README.md +++ b/src/tasks/eth/027-mode-main-u13-to-u16a/README.md @@ -1,6 +1,6 @@ # 027-mode-main-u13-to-u16a -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x3ac45d51da454abfba887b5ab1dae831a78e068615893fb62d8034437bb17063) ## Objective diff --git a/src/tasks/eth/028-arena-z-main-u13-to-u16a/README.md b/src/tasks/eth/028-arena-z-main-u13-to-u16a/README.md index 9835ceb7af..6c519f6c10 100644 --- a/src/tasks/eth/028-arena-z-main-u13-to-u16a/README.md +++ b/src/tasks/eth/028-arena-z-main-u13-to-u16a/README.md @@ -1,6 +1,6 @@ # 028-arena-z-main-u13-to-u16a -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x38010d19d63d834b95936728d49e38dbf71ae7b3a44f555b235b890cd2f40eaf) ## Objective diff --git a/src/tasks/eth/029-swell-main-u13-to-u16a/README.md b/src/tasks/eth/029-swell-main-u13-to-u16a/README.md index e48862d0f9..57974e6f3b 100644 --- a/src/tasks/eth/029-swell-main-u13-to-u16a/README.md +++ b/src/tasks/eth/029-swell-main-u13-to-u16a/README.md @@ -1,6 +1,6 @@ # 029-swell-main-u13-to-u16a -Status: [READY TO SIGN]() +Status: [EXECUTED](https://etherscan.io/tx/0x6e9cfd7c22acaf263f9a5afaaa701934c77fa2015b9a65262fe29dee108b092c) ## Objective diff --git a/src/tasks/eth/030-fus-rotation/README.md b/src/tasks/eth/030-fus-rotation/README.md new file mode 100644 index 0000000000..77f89a8036 --- /dev/null +++ b/src/tasks/eth/030-fus-rotation/README.md @@ -0,0 +1,21 @@ +# 030-fus-rotation + +Status: [READY TO SIGN]() + +## Objective + +This task removes a FoundationUpgradeSafe owner and replaces it with a new one. + +## Simulation & Signing + +Simulation commands: +```bash +cd src/tasks/eth/030-fus-rotation +SIMULATE_WITHOUT_LEDGER=1 just simulate +``` + +Signing commands: +```bash +cd src/tasks/eth/030-fus-rotation +just sign +``` diff --git a/src/tasks/eth/030-fus-rotation/VALIDATION.md b/src/tasks/eth/030-fus-rotation/VALIDATION.md new file mode 100644 index 0000000000..8ad06c05fb --- /dev/null +++ b/src/tasks/eth/030-fus-rotation/VALIDATION.md @@ -0,0 +1,68 @@ +# Validation + +This document can be used to validate the inputs and result of the execution of the transaction which you are +signing. + +The steps are: + +1. [Validate the Domain and Message Hashes](#expected-domain-and-message-hashes) +2. [Verifying the transaction input](#understanding-task-calldata) + +## Expected Domain and Message Hashes + +First, we need to validate the domain and message hashes. These values should match both the values on your ledger and +the values printed to the terminal when you run the task. + +> [!CAUTION] +> +> Before signing, ensure the below hashes match what is on your ledger. +> +> ### Single Safe Signer Data +> +> - Domain Hash: `0xa4a9c312badf3fcaa05eafe5dc9bee8bd9316c78ee8b0bebe3115bb21b732672` +> - Message Hash: `0x3895b6a145b0f2e6dd7441c80c3f73260a7d8554b92266e44cb94c14ff00c839` + +## Understanding Task Calldata + +This document provides a detailed analysis of the final calldata executed on-chain for the signer rotation. + +By reconstructing the calldata, we can confirm that the execution precisely implements the approved plan with no unexpected modifications or side effects. + +### Inputs to `safe.swapOwner()` + +`safe.swapOwner()` function is called with the address to be removed and the previous module: + +The address of the new signer: 0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02 +The address of the signer to be removed: 0x7cb07fe039a92b3d784f284d919503a381bec54f +The address of the previous signer: 0x4d014f3c5f33aa9cd1dc29ce29618d07ae666d15 (this gets calculated by the template) + +Thus, the command to encode the calldata is: + +```bash +cast calldata 'swapOwner(address, address, address)' "0x4d014f3c5f33aa9cd1dc29ce29618d07ae666d15" "0x7cb07fe039a92b3d784f284d919503a381bec54f" "0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02" +``` + +### Inputs to `Multicall3DelegateCall` + +The output from the previous section becomes the `data` in the argument to the `Multicall3DelegateCall.aggregate3Value()` function. + +This function is called with a tuple of four elements: + +Call3 struct for Multicall3DelegateCall: + +- `target`: 0x847B5c174615B1B7fDF770882256e2D3E95b9D92 - Foundation Upgrade Safe +- `allowFailure`: false +- `value`: 0 +- `callData`: `0xe318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd02` (output from the previous section) + +Command to encode: + +```bash +cast calldata 'aggregate3Value((address,bool,uint256,bytes)[])' "[(0x847B5c174615B1B7fDF770882256e2D3E95b9D92,false,0,0xe318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd02)]" +``` + +The resulting calldata sent from the ProxyAdminOwner safe is thus: + +``` +0x174dea71000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000847b5c174615b1b7fdf770882256e2d3e95b9d920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064e318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd0200000000000000000000000000000000000000000000000000000000 +``` \ No newline at end of file diff --git a/src/tasks/eth/030-fus-rotation/config.toml b/src/tasks/eth/030-fus-rotation/config.toml index 17c190fe3f..0270fa6b4c 100644 --- a/src/tasks/eth/030-fus-rotation/config.toml +++ b/src/tasks/eth/030-fus-rotation/config.toml @@ -2,7 +2,7 @@ templateName = "GnosisSafeRotateSigner" safeAddressString = "FoundationUpgradeSafe" -ownerToRemove = "0xBF93D4d727F7Ba1F753E1124C3e532dCb04Ea2c8" +ownerToRemove = "0x7cB07FE039a92B3D784f284D919503A381BEC54f" ownerToAdd = "0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02" [stateOverrides] diff --git a/src/tasks/eth/031-fos-rotation/README.md b/src/tasks/eth/031-fos-rotation/README.md new file mode 100644 index 0000000000..641da74796 --- /dev/null +++ b/src/tasks/eth/031-fos-rotation/README.md @@ -0,0 +1,21 @@ +# 031-fos-rotation + +Status: [READY TO SIGN]() + +## Objective + +This task removes a FoundationOperationsSafe owner and replaces it with a new one. + +## Simulation & Signing + +Simulation commands: +```bash +cd src/tasks/eth/031-fos-rotation +SIMULATE_WITHOUT_LEDGER=1 just simulate +``` + +Signing commands: +```bash +cd src/tasks/eth/031-fos-rotation +just sign +``` diff --git a/src/tasks/eth/031-fos-rotation/VALIDATION.md b/src/tasks/eth/031-fos-rotation/VALIDATION.md new file mode 100644 index 0000000000..9418c2df85 --- /dev/null +++ b/src/tasks/eth/031-fos-rotation/VALIDATION.md @@ -0,0 +1,69 @@ +# Validation + +This document can be used to validate the inputs and result of the execution of the transaction which you are +signing. + +The steps are: + +1. [Validate the Domain and Message Hashes](#expected-domain-and-message-hashes) +2. [Verifying the transaction input](#understanding-task-calldata) + +## Expected Domain and Message Hashes + +First, we need to validate the domain and message hashes. These values should match both the values on your ledger and +the values printed to the terminal when you run the task. + +> [!CAUTION] +> +> Before signing, ensure the below hashes match what is on your ledger. +> +> ### Single Safe Signer Data +> +> - Domain Hash: `0x4e6a6554de0308f5ece8ff736beed8a1b876d16f5c27cac8e466d7de0c703890` +> - Message Hash: `0xec0f740e6db2c7fb9f4910ecb3f4c65fb8157d95f1e755aeff9fcdaa77e6d8a6` + +## Understanding Task Calldata + +This document provides a detailed analysis of the final calldata executed on-chain for the signer rotation. + +By reconstructing the calldata, we can confirm that the execution precisely implements the approved plan with no unexpected modifications or side effects. + + +### Inputs to `safe.swapOwner()` + +`safe.swapOwner()` function is called with the address to be removed and the previous module: + +The address of the new signer: 0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02 +The address of the signer to be removed: 0x7cb07fe039a92b3d784f284d919503a381bec54f +The address of the previous signer: 0x4d014f3c5f33aa9cd1dc29ce29618d07ae666d15 (this gets calculated by the template) + +Thus, the command to encode the calldata is: + +```bash +cast calldata 'swapOwner(address, address, address)' "0x4d014f3c5f33aa9cd1dc29ce29618d07ae666d15" "0x7cb07fe039a92b3d784f284d919503a381bec54f" "0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02" +``` + +### Inputs to `Multicall3DelegateCall` + +The output from the previous section becomes the `data` in the argument to the `Multicall3DelegateCall.aggregate3Value()` function. + +This function is called with a tuple of four elements: + +Call3 struct for Multicall3DelegateCall: + +- `target`: 0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A - Foundation Operations Safe +- `allowFailure`: false +- `value`: 0 +- `callData`: `0xe318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd02` (output from the previous section) + +Command to encode: + +```bash +cast calldata 'aggregate3Value((address,bool,uint256,bytes)[])' "[(0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A,false,0,0xe318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd02)]" +``` + +The resulting calldata sent from the ProxyAdminOwner safe is thus: + +``` +0x174dea710000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000009ba6e03d8b90de867373db8cf1a58d2f7f006b3a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000064e318b52b0000000000000000000000004d014f3c5f33aa9cd1dc29ce29618d07ae666d150000000000000000000000007cb07fe039a92b3d784f284d919503a381bec54f00000000000000000000000069acfe2096dfb8d5a041ef37693553c48d9bfd0200000000000000000000000000000000000000000000000000000000 +``` \ No newline at end of file diff --git a/src/tasks/eth/031-fos-rotation/config.toml b/src/tasks/eth/031-fos-rotation/config.toml index c3cfa2d64d..b88cbda45d 100644 --- a/src/tasks/eth/031-fos-rotation/config.toml +++ b/src/tasks/eth/031-fos-rotation/config.toml @@ -1,12 +1,12 @@ templateName = "GnosisSafeRotateSigner" -safeAddressString = "FoundationOperationSafe" +safeAddressString = "FoundationOperationsSafe" -ownerToRemove = "0xBF93D4d727F7Ba1F753E1124C3e532dCb04Ea2c8" +ownerToRemove = "0x7cB07FE039a92B3D784f284D919503A381BEC54f" ownerToAdd = "0x69acfE2096Dfb8d5A041eF37693553c48d9BFd02" [stateOverrides] 0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A = [ - {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 108} + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 110} ] \ No newline at end of file diff --git a/src/tasks/sep/034-op-ink-sep-fusaka-prestate/.env b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/.env new file mode 100644 index 0000000000..7c96bef7da --- /dev/null +++ b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/.env @@ -0,0 +1 @@ +TENDERLY_GAS=30000000 diff --git a/src/tasks/sep/034-op-ink-sep-fusaka-prestate/README.md b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/README.md new file mode 100644 index 0000000000..58c45bf0f6 --- /dev/null +++ b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/README.md @@ -0,0 +1,21 @@ +# 034-op-ink-sep-fusaka-prestate + +Status: [[EXECUTED](https://sepolia.etherscan.io/tx/0x02d13afd79fb045ab34a05e5f9bc7f23738afd739a042f97bc314ad9e90e282e)] + +## Objective + +This task uses `op-contract/v4.1.0` OPContractsManager to update the prestate of OP Sepolia and Ink Sepolia to the Fusaka compatible prestate. + +## Simulation & Signing + +Simulation commands for each safe: +```bash +cd src/tasks/sep/034-op-ink-sep-fusaka-prestate +SIMULATE_WITHOUT_LEDGER=1 SKIP_DECODE_AND_PRINT=1 just --dotenv-path $(pwd)/.env simulate +``` + +Signing commands for each safe: +```bash +cd src/tasks/sep/034-op-ink-sep-fusaka-prestate +SKIP_DECODE_AND_PRINT=1 just --dotenv-path $(pwd)/.env sign +``` diff --git a/src/tasks/sep/034-op-ink-sep-fusaka-prestate/VALIDATION.md b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/VALIDATION.md new file mode 100644 index 0000000000..00625948ea --- /dev/null +++ b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/VALIDATION.md @@ -0,0 +1,42 @@ +# Validation + +This document can be used to validate the inputs and result of the execution of the upgrade transaction which you are +signing. + +The steps are: + +1. [Validate the Domain and Message Hashes](#expected-domain-and-message-hashes) +2. [Verifying the state changes via the normalized state diff hash](#normalized-state-diff-hash-attestation) +3. [Verifying the transaction input](#understanding-task-calldata) +4. [Verifying the state changes](#task-state-changes) + +## Expected Domain and Message Hashes + +First, we need to validate the domain and message hashes. These values should match both the values on your ledger and +the values printed to the terminal when you run the task. + +> [!CAUTION] +> +> Before signing, ensure the below hashes match what is on your ledger. +> +> ### Security Council Safe (`0xf64bc17485f0B4Ea5F06A96514182FC4cB561977`) +> +> - Domain Hash: `0xbe081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b8533` +> - Message Hash: `0xa311fd9776ab0338d1680920fd903a3eeb8127b6157a7d8adb9c9250c51a3552` +> +> ### Foundation Safe (`0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B`) +> +> - Domain Hash: `0x37e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb` +> - Message Hash: `0x0100ad527dc0a5bfde1487c1e459f345375c236cdcdd25855dbe4ce4a1ea023e` +## Normalized State Diff Hash Attestation + +Ensure that the normalized state diff hash matches the output in your terminal. + +**Normalized hash:** `0x46c2ce3b0ad59afaa4b38c2240580cd823c763fc52dccb3b4e3631b7e500409e` + +## Task Calldata + +Calldata: +``` +0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bb6437aba031afbf9cb3538fa064161e2bf2d780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001049a72745b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538000000000000000000000000189abaaaa82dfc015a588a7dbad6f13b1d3485bc0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f9100000000000000000000000005c993e60179f28bf649a2bb5b00b5f4283bd525000000000000000000000000d7db319a49362b2328cf417a934300cccb442c8d0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f9100000000000000000000000000000000000000000000000000000000 +``` \ No newline at end of file diff --git a/src/tasks/sep/034-op-ink-sep-fusaka-prestate/config.toml b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/config.toml new file mode 100644 index 0000000000..4cd40df065 --- /dev/null +++ b/src/tasks/sep/034-op-ink-sep-fusaka-prestate/config.toml @@ -0,0 +1,33 @@ +templateName = "OPCMUpdatePrestateV410" + +[[l2chains]] +chainId = 11155420 +name = "OP Sepolia Testnet" + +[[l2chains]] +chainId = 763373 +name = "Ink Sepolia Testnet" + +[[opcmUpgrades]] +chainId = 11155420 +absolutePrestate = "0x0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f91" # Fusaka Absolute Prestate published here https://github.com/ethereum-optimism/superchain-registry/blob/df5d4feb51f5d698d043a72ac2c6dfa9ce2a4afa/validation/standard/standard-prestates.toml#L6C7-L6C73 +expectedValidationErrors = "OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER" # The template provides PAO and Challenger overrides to the validator from the SCR. These simply indicate overrides were used. + +[[opcmUpgrades]] +chainId = 763373 +absolutePrestate = "0x0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f91" # Fusaka Absolute Prestate published here https://github.com/ethereum-optimism/superchain-registry/blob/df5d4feb51f5d698d043a72ac2c6dfa9ce2a4afa/validation/standard/standard-prestates.toml#L6C7-L6C73 +expectedValidationErrors = "OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER" # The template provides PAO and Challenger overrides to the validator from the SCR. These simply indicate overrides were used. + +[addresses] +OPCM = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" # version 3.2.0 https://github.com/ethereum-optimism/superchain-registry/blob/40526b1288534f6b84b7aae21d13c0b5f5b12f47/validation/standard/standard-versions-sepolia.toml#L23 + +[stateOverrides] +0x1Eb2fFc903729a0F03966B917003800b145F56E2 = [ # L1PAO + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 37} +] +0xf64bc17485f0B4Ea5F06A96514182FC4cB561977 = [ # SC + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 48} +] +0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B = [ # FUS + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 51} +] diff --git a/src/tasks/sep/035-uni-sep-fusaka-prestate/.env b/src/tasks/sep/035-uni-sep-fusaka-prestate/.env new file mode 100644 index 0000000000..adf17d369c --- /dev/null +++ b/src/tasks/sep/035-uni-sep-fusaka-prestate/.env @@ -0,0 +1 @@ +TENDERLY_GAS=15000000 diff --git a/src/tasks/sep/035-uni-sep-fusaka-prestate/README.md b/src/tasks/sep/035-uni-sep-fusaka-prestate/README.md new file mode 100644 index 0000000000..70b9574c03 --- /dev/null +++ b/src/tasks/sep/035-uni-sep-fusaka-prestate/README.md @@ -0,0 +1,21 @@ +# 035-uni-sep-fusaka-prestate + +Status: [[EXECUTED](https://sepolia.etherscan.io/tx/0xa260348e1aacf29ac53487ee3ef34b2b638adbb3589a93a1d36aec18fc156395)] + +## Objective + +This task uses `op-contract/v4.1.0` OPContractsManager to update the prestate of Unichain Sepolia to the Fusaka compatible prestate. + +## Simulation & Signing + +Simulation commands for each safe: +```bash +cd src/tasks/sep/035-uni-sep-fusaka-prestate +SIMULATE_WITHOUT_LEDGER=1 SKIP_DECODE_AND_PRINT=1 just --dotenv-path $(pwd)/.env simulate +``` + +Signing commands for each safe: +```bash +cd src/tasks/sep/035-uni-sep-fusaka-prestate +SKIP_DECODE_AND_PRINT=1 just --dotenv-path $(pwd)/.env sign +``` diff --git a/src/tasks/sep/035-uni-sep-fusaka-prestate/VALIDATION.md b/src/tasks/sep/035-uni-sep-fusaka-prestate/VALIDATION.md new file mode 100644 index 0000000000..e441c0a29b --- /dev/null +++ b/src/tasks/sep/035-uni-sep-fusaka-prestate/VALIDATION.md @@ -0,0 +1,36 @@ +# Validation + +This document can be used to validate the inputs and result of the execution of the upgrade transaction which you are +signing. + +The steps are: + +1. [Validate the Domain and Message Hashes](#expected-domain-and-message-hashes) +2. [Verifying the transaction input](#understanding-task-calldata) +3. [Verifying the state changes](#task-state-changes) + +## Expected Domain and Message Hashes + +First, we need to validate the domain and message hashes. These values should match both the values on your ledger and +the values printed to the terminal when you run the task. + +> [!CAUTION] +> +> Before signing, ensure the below hashes match what is on your ledger. +> +> ### L1PAO: `@0xd363339eE47775888Df411A163c586a8BdEA9dbf` +> +> - Domain Hash: `0x2fedecce87979400ff00d5cec4c77da942d43ab3b9db4a5ffc51bb2ef498f30b` +> - Message Hash: `0x9151c19e2511e6ddf5366deaf4592fd30dd41e0f7d3e371ed85190008ca6e566` +## Normalized State Diff Hash Attestation + +Ensure that the normalized state diff hash matches the output in your terminal. + +**Normalized hash:** `0x72702882bcde0413668da3fabcb6450df3c993b1892b9d881fa750e7cb9c5b52` + +## Task Calldata + +Calldata: +``` +0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bb6437aba031afbf9cb3538fa064161e2bf2d780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a49a72745b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000aee94b9ab7752d3f7704bde212c0c6a0b701571d0000000000000000000000002bf403e5353a7a082ef6bb3ae2be3b866d8d3ea40339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f9100000000000000000000000000000000000000000000000000000000 +``` \ No newline at end of file diff --git a/src/tasks/sep/035-uni-sep-fusaka-prestate/config.toml b/src/tasks/sep/035-uni-sep-fusaka-prestate/config.toml new file mode 100644 index 0000000000..fbbe6ffe00 --- /dev/null +++ b/src/tasks/sep/035-uni-sep-fusaka-prestate/config.toml @@ -0,0 +1,19 @@ +templateName = "OPCMUpdatePrestateV410" + +[[l2chains]] +chainId = 1301 +name = "Unichain Sepolia Testnet" + +[[opcmUpgrades]] +chainId = 1301 +absolutePrestate = "0x0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f91" # Fusaka Absolute Prestate published here https://github.com/ethereum-optimism/superchain-registry/blob/df5d4feb51f5d698d043a72ac2c6dfa9ce2a4afa/validation/standard/standard-prestates.toml#L6C7-L6C73 +expectedValidationErrors = "OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER" # The template provides PAO and Challenger overrides to the validator from the SCR. These simply indicate overrides were used. + +[addresses] +OPCM = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" # version 3.2.0 https://github.com/ethereum-optimism/superchain-registry/blob/df5d4feb51f5d698d043a72ac2c6dfa9ce2a4afa/validation/standard/standard-versions-sepolia.toml#L23 + +[stateOverrides] +# Unichain Sepolia ProxyAdminOwner: +0xd363339eE47775888Df411A163c586a8BdEA9dbf = [ # L1PAO + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 38} +] diff --git a/src/template/AddGameTypeTemplate.sol b/src/template/AddGameTypeTemplate.sol new file mode 100644 index 0000000000..ba62f9129c --- /dev/null +++ b/src/template/AddGameTypeTemplate.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {VmSafe} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; + +import {OPCMTaskBase} from "src/tasks/types/OPCMTaskBase.sol"; +import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol"; +import {Action} from "src/libraries/MultisigTypes.sol"; + +import {GameType, Claim, Duration} from "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; +import { + IOPContractsManager, + IDisputeGameFactory, + IFaultDisputeGame, + IBigStepper, + IProxyAdmin, + IDelayedWETH, + ISystemConfig +} from "@eth-optimism-bedrock/interfaces/L1/IOPContractsManager.sol"; + +/// @title AddGameTypeTemplate +/// @notice This template is used to add a game type to the DisputeGameFactory contract. +contract AddGameTypeTemplate is OPCMTaskBase { + using stdToml for string; + + /// @notice Struct that extends the original AddGameInput struct and includes the chain id. + /// Notably the fields here are also in alphabetical order, this is required because of + /// the way that Foundry parses TOML data. This MUST be kept in alphabetical order. If + /// you are adding a new field, you MUST make sure it's in order. Seriously. + struct AddGameInputWithChainId { + uint256 chainId; + IDelayedWETH delayedWETH; + Claim disputeAbsolutePrestate; + Duration disputeClockExtension; + GameType disputeGameType; + Duration disputeMaxClockDuration; + uint256 disputeMaxGameDepth; + uint256 disputeSplitDepth; + uint256 initialBond; + bool permissioned; + IProxyAdmin proxyAdmin; + string saltMixer; + ISystemConfig systemConfig; + IBigStepper vm; + } + + /// @notice Mapping of chain ID to configuration for the task. + mapping(uint256 => AddGameInputWithChainId) private cfg; + + /// @notice Address of the OPCM contract. + address private OPCM; + + /// @notice Returns string identifiers for addresses that are expected to have their storage written to. + function _taskStorageWrites() internal view virtual override returns (string[] memory) { + string[] memory storageWrites = new string[](1); + storageWrites[0] = "DisputeGameFactoryProxy"; + return storageWrites; + } + + /// @notice Sets up the template with implementation configurations from a TOML file. + function _templateSetup(string memory taskConfigFilePath, address rootSafe) internal override { + super._templateSetup(taskConfigFilePath, rootSafe); + string memory tomlContent = vm.readFile(taskConfigFilePath); + + // Load configuration. + AddGameInputWithChainId[] memory configs = + abi.decode(tomlContent.parseRaw(".configs"), (AddGameInputWithChainId[])); + for (uint256 i = 0; i < configs.length; i++) { + cfg[configs[i].chainId] = configs[i]; + } + + // Load OPCM address. + OPCM = tomlContent.readAddress(".addresses.OPCM"); + require(OPCM != address(0), "OPCM not set"); + vm.label(OPCM, "OPCM"); + + // Set OPCM as the target for delegatecalls. + OPCM_TARGETS = new address[](1); + OPCM_TARGETS[0] = OPCM; + } + + /// @notice Write the calls that you want to execute for the task. + function _build(address) internal override { + // Iterate over the chains pull out the configs. + SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); + IOPContractsManager.AddGameInput[] memory configs = new IOPContractsManager.AddGameInput[](chains.length); + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + configs[i] = _toAddGameInput(cfg[chainId]); + } + + // Delegatecall the OPCM.addGameType() function. + (bool success,) = OPCM.delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (configs))); + require(success, "AddGameType: failed to add game type"); + } + + /// @notice This method performs all validations and assertions that verify the calls executed as expected. + function _validate(VmSafe.AccountAccess[] memory, Action[] memory, address) internal view override { + // Iterate over the chains and validate the respected game type. + SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + address factoryAddress = superchainAddrRegistry.getAddress("DisputeGameFactoryProxy", chainId); + IDisputeGameFactory factory = IDisputeGameFactory(factoryAddress); + IFaultDisputeGame game = IFaultDisputeGame(address(factory.gameImpls(cfg[chainId].disputeGameType))); + + // Assert that everything is as expected. + assertEq(address(game.weth()), address(cfg[chainId].delayedWETH)); + assertEq(game.gameType().raw(), cfg[chainId].disputeGameType.raw()); + assertEq(game.absolutePrestate().raw(), cfg[chainId].disputeAbsolutePrestate.raw()); + assertEq(game.maxGameDepth(), cfg[chainId].disputeMaxGameDepth); + assertEq(game.splitDepth(), cfg[chainId].disputeSplitDepth); + assertEq(game.clockExtension().raw(), cfg[chainId].disputeClockExtension.raw()); + assertEq(game.maxClockDuration().raw(), cfg[chainId].disputeMaxClockDuration.raw()); + + // Assert that the bond is set correctly. + assertEq(factory.initBonds(cfg[chainId].disputeGameType), cfg[chainId].initialBond); + } + } + + /// @notice Override to return a list of addresses that should not be checked for code length. + function _getCodeExceptions() internal view virtual override returns (address[] memory) {} + + /// @notice Converts the AddGameInputWithChainId struct to the AddGameInput struct. + function _toAddGameInput(AddGameInputWithChainId memory _input) + internal + pure + returns (IOPContractsManager.AddGameInput memory) + { + return IOPContractsManager.AddGameInput({ + saltMixer: _input.saltMixer, + systemConfig: _input.systemConfig, + proxyAdmin: _input.proxyAdmin, + delayedWETH: _input.delayedWETH, + disputeGameType: _input.disputeGameType, + disputeAbsolutePrestate: _input.disputeAbsolutePrestate, + disputeMaxGameDepth: _input.disputeMaxGameDepth, + disputeSplitDepth: _input.disputeSplitDepth, + disputeClockExtension: _input.disputeClockExtension, + disputeMaxClockDuration: _input.disputeMaxClockDuration, + initialBond: _input.initialBond, + vm: _input.vm, + permissioned: _input.permissioned + }); + } +} diff --git a/src/template/OPCMUpdatePrestateV410.sol b/src/template/OPCMUpdatePrestateV410.sol new file mode 100644 index 0000000000..723bf4082c --- /dev/null +++ b/src/template/OPCMUpdatePrestateV410.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {ISystemConfig, IProxyAdmin} from "@eth-optimism-bedrock/interfaces/L1/IOPContractsManager.sol"; +import {IOPContractsManager} from "lib/optimism/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol"; +import {Claim} from "@eth-optimism-bedrock/src/dispute/lib/Types.sol"; +import {VmSafe} from "forge-std/Vm.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {console2 as console} from "forge-std/console2.sol"; +import {LibString} from "solady/utils/LibString.sol"; + +import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol"; +import {OPCMTaskBase} from "src/tasks/types/OPCMTaskBase.sol"; +import {Action} from "src/libraries/MultisigTypes.sol"; + +/// @notice This template provides OPCM-based absolute prestate updates. +/// Supports: op-contracts/v4.1.0 +contract OPCMUpdatePrestateV410 is OPCMTaskBase { + using stdToml for string; + using LibString for string; + + /// @notice Struct to store inputs for OPCM.updatePrestate() function per l2 chain + struct OPCMUpgrade { + Claim absolutePrestate; + uint256 chainId; + string expectedValidationErrors; + } + + /// @notice Mapping of l2 chain IDs to their respective prestates + mapping(uint256 => OPCMUpgrade) public upgrades; + + /// @notice The Standard Validator returned by OPCM + IOPContractsManagerStandardValidator public STANDARD_VALIDATOR; + + /// @notice Returns the storage write permissions required for this task + function _taskStorageWrites() internal pure virtual override returns (string[] memory) { + string[] memory storageWrites = new string[](1); + storageWrites[0] = "DisputeGameFactoryProxy"; + return storageWrites; + } + + /// @notice Sets up the template with implementation configurations from a TOML file. + function _templateSetup(string memory taskConfigFilePath, address rootSafe) internal override { + super._templateSetup(taskConfigFilePath, rootSafe); + string memory tomlContent = vm.readFile(taskConfigFilePath); + + OPCMUpgrade[] memory _upgrades = abi.decode(tomlContent.parseRaw(".opcmUpgrades"), (OPCMUpgrade[])); + for (uint256 i = 0; i < _upgrades.length; i++) { + console.log("Adding upgrade - chainID: %s, absolutePrestate:", _upgrades[i].chainId); + console.logBytes32(Claim.unwrap(_upgrades[i].absolutePrestate)); + console.log("Expected errors: %s", _upgrades[i].expectedValidationErrors); + upgrades[_upgrades[i].chainId] = _upgrades[i]; + } + + address OPCM = tomlContent.readAddress(".addresses.OPCM"); + OPCM_TARGETS.push(OPCM); + require(IOPContractsManager(OPCM).version().eq("3.2.0"), "Incorrect OPCM - expected version 3.2.0"); + vm.label(OPCM, "OPCM"); + + // Fetch the validator directly from OPCM so it doesn't need to be configured in TOML + address validatorAddr = address(IOPCM(OPCM).opcmStandardValidator()); + require(validatorAddr != address(0), "OPCM returned zero validator"); + require(validatorAddr.code.length > 0, "Validator has no code"); + STANDARD_VALIDATOR = IOPContractsManagerStandardValidator(validatorAddr); + vm.label(address(STANDARD_VALIDATOR), "OPCMStandardValidator"); + } + + /// @notice Before implementing the `_build` function, template developers must consider the following: + /// 1. Which Multicall contract does this template use — `Multicall3` or `Multicall3Delegatecall`? + /// 2. Based on the contract, should the target be called using `call` or `delegatecall`? + /// 3. Ensure that the call to the target uses the appropriate method (`call` or `delegatecall`) accordingly. + /// Guidelines: + /// - `Multicall3Delegatecall`: + /// If the template inherits from `OPCMTaskBase`, it uses the `Multicall3Delegatecall` contract. + /// In this case, calls to the target **must** use `delegatecall`, e.g.: + /// `(bool success,) = OPCM.delegatecall(abi.encodeWithSelector(IOPCMPrestateUpdate.upgrade.selector, opChainConfigs));` + function _build(address) internal override { + SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); + IOPContractsManager.OpChainConfig[] memory opChainConfigs = + new IOPContractsManager.OpChainConfig[](chains.length); + + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + opChainConfigs[i] = IOPContractsManager.OpChainConfig({ + systemConfigProxy: ISystemConfig(superchainAddrRegistry.getAddress("SystemConfigProxy", chainId)), + proxyAdmin: IProxyAdmin(superchainAddrRegistry.getAddress("ProxyAdmin", chainId)), + absolutePrestate: upgrades[chainId].absolutePrestate + }); + } + + (bool success,) = OPCM_TARGETS[0].delegatecall( + abi.encodeWithSelector(IOPCMPrestateUpdate.updatePrestate.selector, opChainConfigs) + ); + require(success, "OPCM.updatePrestate() failed"); + } + + /// @notice This method performs all validations and assertions that verify the calls executed as expected. + function _validate(VmSafe.AccountAccess[] memory, Action[] memory, address) internal view override { + SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); + + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + bytes32 expAbsolutePrestate = Claim.unwrap(upgrades[chainId].absolutePrestate); + string memory expErrors = upgrades[chainId].expectedValidationErrors; + address proxyAdmin = superchainAddrRegistry.getAddress("ProxyAdmin", chainId); + address sysCfg = superchainAddrRegistry.getAddress("SystemConfigProxy", chainId); + + IOPContractsManagerStandardValidator.ValidationInput memory input = IOPContractsManagerStandardValidator + .ValidationInput({ + proxyAdmin: IProxyAdmin(proxyAdmin), + sysCfg: ISystemConfig(sysCfg), + absolutePrestate: expAbsolutePrestate, + l2ChainID: chainId + }); + + IOPContractsManagerStandardValidator.ValidationOverrides memory overrides_ = + IOPContractsManagerStandardValidator.ValidationOverrides({ + l1PAOMultisig: superchainAddrRegistry.getAddress("ProxyAdminOwner", chainId), + challenger: superchainAddrRegistry.getAddress("Challenger", chainId) + }); + + string memory errors = + STANDARD_VALIDATOR.validateWithOverrides({_input: input, _allowFailure: true, _overrides: overrides_}); + + require(errors.eq(expErrors), string.concat("Unexpected errors: ", errors, "; expected: ", expErrors)); + } + } + + /// @notice Override to return a list of addresses that should not be checked for code length. + function _getCodeExceptions() internal view virtual override returns (address[] memory) {} +} + +interface IOPCMPrestateUpdate { + function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs) external; +} + +/// @notice Interface to retrieve the standard validator from OPCM. +interface IOPCM { + function opcmStandardValidator() external view returns (IOPContractsManagerStandardValidator); +} + +/// @notice Validator interface for validateWithOverrides usage. +interface IOPContractsManagerStandardValidator { + struct ValidationInput { + IProxyAdmin proxyAdmin; + ISystemConfig sysCfg; + bytes32 absolutePrestate; + uint256 l2ChainID; + } + + struct ValidationOverrides { + address l1PAOMultisig; + address challenger; + } + + function validate(ValidationInput memory _input, bool _allowFailure) external view returns (string memory); + + function validateWithOverrides( + ValidationInput memory _input, + bool _allowFailure, + ValidationOverrides memory _overrides + ) external view returns (string memory); + + function version() external view returns (string memory); +} diff --git a/src/template/SetRespectedGameTypeTemplate.sol b/src/template/SetRespectedGameTypeTemplate.sol index 871060a1b6..ee48f8e717 100644 --- a/src/template/SetRespectedGameTypeTemplate.sol +++ b/src/template/SetRespectedGameTypeTemplate.sol @@ -3,10 +3,6 @@ pragma solidity 0.8.15; import {VmSafe} from "forge-std/Vm.sol"; import {stdToml} from "forge-std/StdToml.sol"; -import { - IDeputyGuardianModule, - IOptimismPortal2 -} from "lib/optimism/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol"; import {GameType} from "lib/optimism/packages/contracts-bedrock/src/dispute/lib/Types.sol"; import {L2TaskBase} from "src/tasks/types/L2TaskBase.sol"; @@ -14,8 +10,7 @@ import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol"; import {Action} from "src/libraries/MultisigTypes.sol"; /// @title SetRespectedGameTypeTemplate -/// @notice This template is used to set the respected game type in the OptimismPortal2 contract -/// for a given chain or set of chains. +/// @notice Sets the respected game type in AnchorStateRegistry for a given chain or set of chains. contract SetRespectedGameTypeTemplate is L2TaskBase { using stdToml for string; @@ -28,17 +23,15 @@ contract SetRespectedGameTypeTemplate is L2TaskBase { /// @notice Mapping of chain ID to configuration for the task. mapping(uint256 => SetRespectedGameTypeTaskConfig) public cfg; - /// @notice Returns the string identifier for the safe executing this transaction. + /// @notice Execute as the Guardian safe (authorized on ASR). function safeAddressString() public pure override returns (string memory) { - return "FoundationOperationsSafe"; + return "GuardianSafe"; } /// @notice Returns string identifiers for addresses that are expected to have their storage written to. function _taskStorageWrites() internal pure override returns (string[] memory) { - string[] memory storageWrites = new string[](3); - storageWrites[0] = "DeputyGuardianModule"; - storageWrites[1] = "Guardian"; - storageWrites[2] = "OptimismPortalProxy"; + string[] memory storageWrites = new string[](1); + storageWrites[0] = "AnchorStateRegistryProxy"; return storageWrites; } @@ -55,15 +48,14 @@ contract SetRespectedGameTypeTemplate is L2TaskBase { /// @notice Write the calls that you want to execute for the task. function _build(address) internal override { - // Load the DeputyGuardianModule contract. - IDeputyGuardianModule dgm = IDeputyGuardianModule(superchainAddrRegistry.get("DeputyGuardianModule")); - // Iterate over the chains and set the respected game type. SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); for (uint256 i = 0; i < chains.length; i++) { uint256 chainId = chains[i].chainId; - address portalAddress = superchainAddrRegistry.getAddress("OptimismPortalProxy", chainId); - dgm.setRespectedGameType(IOptimismPortal2(payable(portalAddress)), cfg[chainId].gameType); + address asrAddress = superchainAddrRegistry.getAddress("AnchorStateRegistryProxy", chainId); + + // Call ASR to set the current respected game type: + IAnchorStateRegistry(asrAddress).setRespectedGameType(cfg[chainId].gameType); } } @@ -73,12 +65,20 @@ contract SetRespectedGameTypeTemplate is L2TaskBase { SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); for (uint256 i = 0; i < chains.length; i++) { uint256 chainId = chains[i].chainId; - address portalAddress = superchainAddrRegistry.getAddress("OptimismPortalProxy", chainId); - IOptimismPortal2 portal = IOptimismPortal2(payable(portalAddress)); - assertEq(portal.respectedGameType().raw(), cfg[chainId].gameType.raw()); + address asrAddress = superchainAddrRegistry.getAddress("AnchorStateRegistryProxy", chainId); + IAnchorStateRegistry asr = IAnchorStateRegistry(asrAddress); + assertEq(asr.respectedGameType().raw(), cfg[chainId].gameType.raw()); } } /// @notice Override to return a list of addresses that should not be checked for code length. - function _getCodeExceptions() internal pure override returns (address[] memory) {} + function _getCodeExceptions() internal pure override returns (address[] memory) { + return new address[](0); + } +} + +// Minimal local copy; only what this template needs. +interface IAnchorStateRegistry { + function respectedGameType() external view returns (GameType); + function setRespectedGameType(GameType _gameType) external; } diff --git a/src/template/boilerplate/VALIDATION.template.md b/src/template/boilerplate/VALIDATION.template.md index 5dc51a1ef7..680cd7115e 100644 --- a/src/template/boilerplate/VALIDATION.template.md +++ b/src/template/boilerplate/VALIDATION.template.md @@ -8,9 +8,8 @@ signing. The steps are: 1. [Expected Domain and Message Hashes](#expected-domain-and-message-hashes) -2. [Normalized State Diff Hash Attestation](#normalized-state-diff-hash-attestation) -3. [Understanding Task Calldata](#understanding-task-calldata) -4. [Task State Changes](#task-state-changes) +2. [Understanding Task Calldata](#understanding-task-calldata) +3. [Task State Changes](#task-state-changes) ## Expected Domain and Message Hashes @@ -26,12 +25,6 @@ the values printed to the terminal when you run the task. > - Domain Hash: `` > - Message Hash: `` -## Normalized State Diff Hash Attestation - -The normalized state diff hash is a single fingerprint of all the onchain state changes your task would make if executed. We “normalize” the diff first (stable ordering and encoding) so the hash only changes when the actual intended state changes do. You **MUST** ensure that the normalized hash produced from your simulation matches the normalized hash in this document. - -**Normalized hash:** `` - ## Understanding Task Calldata The command to encode the calldata is: @@ -59,4 +52,4 @@ Note: The changes listed below do not include threshold, nonce and owner mapping ### Task State Changes -TODO: You can copy the markdown state changes printed in the terminal and paste them here. \ No newline at end of file +TODO: You can copy the markdown state changes printed in the terminal and paste them here. diff --git a/test/libraries/AccountAccessParser.t.sol b/test/libraries/AccountAccessParser.t.sol index 1e2fafff22..ce74111d1b 100644 --- a/test/libraries/AccountAccessParser.t.sol +++ b/test/libraries/AccountAccessParser.t.sol @@ -1094,79 +1094,6 @@ contract AccountAccessParser_decodeAndPrint_Test is Test { assertEq(blobbasefeeScalar, "1014213", "Failed to extract uint32 from bytes32"); } - /// The retirementTimestamp is introduced in the AnchorStateRegistry post op-contracts/v3.0.0-rc.2 - function test_normalizeTimestamp_AnchorStateRegistry_retirementTimestamp() public { - vm.createSelectFork("mainnet", 22319975); - address anchorStateRegistry = address(0x1c68ECfbf9C8B1E6C0677965b3B9Ecf9A104305b); // op mainnet AnchorStateRegistryProxy - // [offset: 8, bytes: 8, value: 0xFFFFFFFFFFFFFFFF, name: retirementTimestamp] - bytes32 newValue1 = bytes32(uint256(0x0000000000000000000000000000000000000000FFFFFFFFFFFFFFFF00000000)); - AccountAccessParser.StateDiff memory diff1 = AccountAccessParser.StateDiff({ - slot: bytes32(uint256(6)), - oldValue: bytes32(uint256(0)), - newValue: newValue1 - }); - AccountAccessParser.normalizeTimestamp(anchorStateRegistry, diff1); - assertEq(diff1.newValue, bytes32(uint256(0))); - - bytes32 newValue2 = bytes32(uint256(0x0000000000000000000000000000000000000000000000000000000000000000)); - AccountAccessParser.StateDiff memory diff2 = AccountAccessParser.StateDiff({ - slot: bytes32(uint256(6)), - oldValue: bytes32(uint256(0)), - newValue: newValue2 - }); - AccountAccessParser.normalizeTimestamp(anchorStateRegistry, diff2); - assertEq(diff2.newValue, bytes32(uint256(0)), "Value changed for op mainnet AnchorStateRegistryProxy"); - - bytes32 newValue3 = bytes32(uint256(0x0000000000000000000000000000000000000000FFFFFF0FFFFFFFFF00000000)); - AccountAccessParser.StateDiff memory diff3 = AccountAccessParser.StateDiff({ - slot: bytes32(uint256(6)), - oldValue: bytes32(uint256(0)), - newValue: newValue3 - }); - AccountAccessParser.normalizeTimestamp(anchorStateRegistry, diff3); - assertEq(diff3.newValue, bytes32(uint256(0)), "Value changed for op mainnet AnchorStateRegistryProxy"); - - bytes32 newValue4 = bytes32(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000010000000FFFFFFFF)); - AccountAccessParser.StateDiff memory diff4 = AccountAccessParser.StateDiff({ - slot: bytes32(uint256(6)), - oldValue: bytes32(uint256(0)), - newValue: newValue4 - }); - AccountAccessParser.normalizeTimestamp(anchorStateRegistry, diff4); - assertEq( - diff4.newValue, - bytes32(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFF)), - "Value changed for op mainnet AnchorStateRegistryProxy" - ); - } - - function test_normalizeTimestamp_noChangeOnWrongContract() public view { - bytes32 original = bytes32(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)); - - AccountAccessParser.StateDiff memory diff = - AccountAccessParser.StateDiff({slot: bytes32(uint256(6)), oldValue: bytes32(0), newValue: original}); - - diff = AccountAccessParser.normalizeTimestamp(address(0x1234), diff); // wrong contract - - // Should remain unchanged because contract doesn't match - assertEq(diff.newValue, original, "Value changed for wrong contract"); - } - - function test_normalizeTimestamp_noChangeOnWrongSlot() public view { - bytes32 original = bytes32(uint256(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)); - - AccountAccessParser.StateDiff memory diff = AccountAccessParser.StateDiff({ - slot: bytes32(uint256(999)), // wrong slot - oldValue: bytes32(0), - newValue: original - }); - - diff = AccountAccessParser.normalizeTimestamp(address(0x1c68ECfbf9C8B1E6C0677965b3B9Ecf9A104305b), diff); - - // Should remain unchanged because slot doesn't match - assertEq(diff.newValue, original, "Value changed for wrong slot"); - } - function test_EmptyLayout() public pure { AccountAccessParser.JsonStorageLayout[] memory layout = new AccountAccessParser.JsonStorageLayout[](0); assertEq(AccountAccessParser.isSlotShared(layout, 0), false); @@ -1353,384 +1280,3 @@ contract AccountAccessParser_decodeAndPrint_Test is Test { } // TODO Add integration tests in a follow up PR that actually send transactions and use the recorded state diff. -contract AccountAccessParser_normalizedStateDiffHash_Test is Test { - using AccountAccessParser for VmSafe.AccountAccess[]; - - bytes32 internal constant GNOSIS_SAFE_NONCE_SLOT = bytes32(uint256(5)); - bytes32 internal constant GNOSIS_SAFE_APPROVE_HASHES_SLOT = bytes32(uint256(8)); - bytes32 internal constant LIVENESS_GUARD_LAST_LIVE_SLOT = bytes32(uint256(0)); - bytes32 internal constant OPTIMISM_PORTAL_RESOURCE_PARAMS_SLOT = bytes32(uint256(1)); - - bool constant isWrite = true; - bool constant reverted = true; - - bytes32 constant slot0 = bytes32(uint256(0)); - bytes32 constant slot1 = bytes32(uint256(1)); - bytes32 constant slot2 = bytes32(uint256(2)); - - bytes32 constant val0 = bytes32(uint256(0)); - bytes32 constant val1 = bytes32(uint256(1)); - bytes32 constant val2 = bytes32(uint256(2)); - - address constant EOA_ADDR = address(0x1111); - address constant SAFE_ADDR = address(0x2222); - address constant RANDOM_CONTRACT_ADDR = address(0x3333); - - function setupTests() public { - bytes memory safeCode = hex"01"; - vm.etch(SAFE_ADDR, safeCode); - vm.mockCall(SAFE_ADDR, abi.encodeWithSignature("getThreshold()"), abi.encode(uint256(1))); - - assertTrue(AccountAccessParser.isGnosisSafe(SAFE_ADDR), "SAFE_ADDR should be detected as a Gnosis Safe"); - assertEq(EOA_ADDR.code.length, 0, "EOA_ADDR should have no code"); - } - - function test_normalizedStateDiffHash_EOANonceIncrement() public { - setupTests(); - - // Create a state diff for an EOA nonce increment (should be removed) - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = storageAccess(EOA_ADDR, slot0, isWrite, val0, val1); // nonce 0 -> 1 - - VmSafe.AccountAccess[] memory accesses = new VmSafe.AccountAccess[](1); - accesses[0] = accountAccess(EOA_ADDR, storageAccesses); - - // Get the normalized hash - bytes32 hash = accesses.normalizedStateDiffHash(address(0), bytes32(0)); - - // Since this is just an EOA nonce increment, the normalized array should be empty - // and the hash should match an empty array - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - - assertEq(hash, expectedHash, "EOA nonce increment should be removed"); - } - - function test_normalizedStateDiffHash_GnosisSafeNonceIncrement() public { - setupTests(); - - // Create a state diff for a Gnosis Safe nonce increment (should be removed) - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = storageAccess(SAFE_ADDR, GNOSIS_SAFE_NONCE_SLOT, isWrite, val0, val1); // nonce 0 -> 1 - - VmSafe.AccountAccess[] memory accesses = new VmSafe.AccountAccess[](1); - accesses[0] = accountAccess(SAFE_ADDR, storageAccesses); - - // Get the normalized hash - bytes32 hash = accesses.normalizedStateDiffHash(address(0), bytes32(0)); - - // Since this is just a Safe nonce increment, the normalized array should be empty - // and the hash should match an empty array - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - - assertEq(hash, expectedHash, "Gnosis Safe nonce increment should be removed"); - } - - /// This test uses a real transaction that was approved on mainnet. - /// Find more details here: https://github.com/ethereum-optimism/superchain-ops/blob/main/src/tasks/eth/003-opcm-upgrade-v300-op-ink-soneium/VALIDATION.md - function test_normalizedStateDiffHash_GnosisSafeApproveHash() public { - vm.createSelectFork("mainnet", 22319975); - - address multisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 approveHashSlot = 0xb83cd9f113d329914a61adce818feb77eb750bf02115fdb71f059425216265be; - - // Create a state diff for a Gnosis Safe approve hash (should be removed) - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = storageAccess(multisig, approveHashSlot, isWrite, val0, val1); // approve hash - - VmSafe.AccountAccess[] memory accesses = new VmSafe.AccountAccess[](1); - accesses[0] = accountAccess(multisig, storageAccesses); - - bytes32 txHash = 0x0d1a3b425e64a0c9bd90f6933632c1cc0042896a1c5831ac8ef290cab8205e83; - bytes32 hash = accesses.normalizedStateDiffHash(multisig, txHash); - - // Since this is just a Safe approve hash, the normalized array should be empty - // and the hash should match an empty array - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - - assertEq(hash, expectedHash, "Gnosis Safe approve hash should be removed"); - } - - function test_normalizedStateDiffHash_OtherChanges() public { - setupTests(); - - // Create a state diff for a regular contract (should be included) - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = storageAccess(RANDOM_CONTRACT_ADDR, slot1, isWrite, val0, val2); // some random change - - VmSafe.AccountAccess[] memory accesses = new VmSafe.AccountAccess[](1); - accesses[0] = accountAccess(RANDOM_CONTRACT_ADDR, storageAccesses); - - // Get the normalized hash - bytes32 hash = accesses.normalizedStateDiffHash(address(0), bytes32(0)); - - // This should be included in the normalized array - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - assertNotEq(hash, keccak256(abi.encode(emptyArray)), "Regular state change should be included"); - - // Create the expected AccountStateDiff array - AccountAccessParser.AccountStateDiff[] memory expectedArray = new AccountAccessParser.AccountStateDiff[](1); - expectedArray[0] = AccountAccessParser.AccountStateDiff({ - who: RANDOM_CONTRACT_ADDR, - slot: slot1, - firstOld: val0, - lastNew: val2 - }); - - bytes32 expectedHash = keccak256(abi.encode(expectedArray)); - assertEq(hash, expectedHash, "Hash should match the expected AccountStateDiff"); - } - - function test_normalizedStateDiffHash_MixedChanges() public { - vm.createSelectFork("mainnet", 22319975); - setupTests(); - - address multisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 txHash = keccak256("fake tx hash"); - bytes32 ownerSlot = keccak256(abi.encode(IGnosisSafe(multisig).getOwners()[0], GNOSIS_SAFE_APPROVE_HASHES_SLOT)); - bytes32 fakeApproveHashSlot = keccak256(abi.encode(txHash, ownerSlot)); - // Create the combined account accesses array with all our test cases - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](3); - - // 1. EOA nonce increment (should be filtered out) - VmSafe.StorageAccess[] memory eoaStorageAccesses = new VmSafe.StorageAccess[](1); - eoaStorageAccesses[0] = storageAccess(EOA_ADDR, slot0, isWrite, val0, val1); - allAccesses[0] = accountAccess(EOA_ADDR, eoaStorageAccesses); - - // 2. Gnosis Safe changes (should be filtered out) - VmSafe.StorageAccess[] memory safeStorageAccesses = new VmSafe.StorageAccess[](2); - safeStorageAccesses[0] = storageAccess(multisig, GNOSIS_SAFE_NONCE_SLOT, isWrite, val0, val1); // nonce increment - safeStorageAccesses[1] = storageAccess(multisig, fakeApproveHashSlot, isWrite, val0, val1); // approve hash - allAccesses[1] = accountAccess(multisig, safeStorageAccesses); - - // 3. Regular contract change (should be kept) - VmSafe.StorageAccess[] memory regularStorageAccesses = new VmSafe.StorageAccess[](1); - regularStorageAccesses[0] = storageAccess(RANDOM_CONTRACT_ADDR, slot1, isWrite, val0, val2); - allAccesses[2] = accountAccess(RANDOM_CONTRACT_ADDR, regularStorageAccesses); - - // Get the normalized hash - bytes32 hash = allAccesses.normalizedStateDiffHash(multisig, txHash); - - // Manually construct what we expect the normalized state to be using AccountStateDiff - AccountAccessParser.AccountStateDiff[] memory expectedArray = new AccountAccessParser.AccountStateDiff[](1); - expectedArray[0] = AccountAccessParser.AccountStateDiff({ - who: RANDOM_CONTRACT_ADDR, - slot: slot1, - firstOld: val0, - lastNew: val2 - }); - - bytes32 expectedHash = keccak256(abi.encode(expectedArray)); - - // Now let's check that the regular contract write is included and other writes are excluded - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - assertTrue( - hash != keccak256(abi.encode(emptyArray)), - "Hash should not be of an empty array (regular writes should be included)" - ); - - assertEq(hash, expectedHash, "Normalized hash should match expected state with only regular contract changes"); - } - - /// This test uses data from a real liveness guard timestamp update on mainnet. - /// Find more details here: https://github.com/ethereum-optimism/superchain-ops/blob/main/src/tasks/eth/003-opcm-upgrade-v300-op-ink-soneium/VALIDATION.md - function test_normalizedStateDiffHash_LivenessGuardTimestamp() public { - vm.createSelectFork("mainnet", 22319975); - setupTests(); - - address livenessGuard = address(0x24424336F04440b1c28685a38303aC33C9D14a25); - address firstOwnerOnSecurityCouncil = address(0x07dC0893cAfbF810e3E72505041f2865726Fd073); - bytes32 lastLiveSlot = keccak256(abi.encode(firstOwnerOnSecurityCouncil, LIVENESS_GUARD_LAST_LIVE_SLOT)); - - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](1); - // Create a state diff for a LivenessGuard timestamp (should be removed) - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = storageAccess(livenessGuard, lastLiveSlot, isWrite, val0, val1); - allAccesses[0] = accountAccess(livenessGuard, storageAccesses); - - address parentMultisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 hash = allAccesses.normalizedStateDiffHash(parentMultisig, bytes32(0)); - - // Since this is just a LivenessGuard timestamp update, the normalized array should be empty - // and the hash should match an empty array - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - - assertEq(hash, expectedHash, "LivenessGuard timestamp update should be removed"); - } - - // Version 3.5.0 - op-contracts/v4.1.0-rc.3 - AnchorStateRegistry - function test_normalizedStateDiffHash_AnchorStateRegistryRetirementTimestamp() public { - vm.createSelectFork("mainnet", 22990600); - setupTests(); - - address anchorStateRegistry = address(0x23B2C62946350F4246f9f9D027e071f0264FD113); - bytes32 retirementTimestampSlot = bytes32(uint256(6)); - bytes32 retirementTimestamp = - bytes32(uint256(0x0000000000000000000000000000000000000000FFFFFFFFFFFFFFFF00000000)); - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](1); - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - storageAccesses[0] = - storageAccess(anchorStateRegistry, retirementTimestampSlot, isWrite, val0, retirementTimestamp); - allAccesses[0] = accountAccess(anchorStateRegistry, storageAccesses); - - address parentMultisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 hash = allAccesses.normalizedStateDiffHash(parentMultisig, bytes32(0)); - // This hash is the zero'd out retirement timestamp slot. Stepped through code to prove this. - bytes32 expectedHash = bytes32(0xba500e0a45c2487e48cabd2c832376eabc7d10080926d10c0340829a3a97e622); - assertEq(hash, expectedHash, "AnchorStateRegistry should match the expected hash"); - - bytes32 retirementTimestamp2 = - bytes32(uint256(0x0000000000000000000000000000000000000000AAAAAAAAAAAAAAAA00000000)); - storageAccesses[0] = - storageAccess(anchorStateRegistry, retirementTimestampSlot, isWrite, val0, retirementTimestamp2); - allAccesses[0] = accountAccess(anchorStateRegistry, storageAccesses); - - bytes32 hash2 = allAccesses.normalizedStateDiffHash(parentMultisig, bytes32(0)); - assertEq(hash2, expectedHash, "AnchorStateRegistry should still match the expected hash"); - } - - // Version 3.5.0 - op-contracts/v4.1.0-rc.3 - AnchorStateRegistry - function test_normalizedStateDiffHash_AnchorStateRegistryProposal() public { - vm.createSelectFork("mainnet", 22990600); - setupTests(); - - address anchorStateRegistry = address(0x23B2C62946350F4246f9f9D027e071f0264FD113); - bytes32 proposalRootSlot = bytes32(uint256(3)); - bytes32 proposalRoot = bytes32(uint256(0x08ce0a407e15a1776bd43cd669328edc6825fcab988b8e2052258774251c25d2)); - bytes32 proposalL2SequenceNumberSlot = bytes32(uint256(4)); - bytes32 proposalL2SequenceNumber = - bytes32(uint256(0x0000000000000000000000000000000000000000000000000000000001287b8d)); - - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](1); - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](2); - storageAccesses[0] = storageAccess(anchorStateRegistry, proposalRootSlot, isWrite, val0, proposalRoot); - storageAccesses[1] = - storageAccess(anchorStateRegistry, proposalL2SequenceNumberSlot, isWrite, val0, proposalL2SequenceNumber); - allAccesses[0] = accountAccess(anchorStateRegistry, storageAccesses); - - address parentMultisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 hash = allAccesses.normalizedStateDiffHash(parentMultisig, bytes32(0)); - - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - - assertEq(hash, expectedHash, "AnchorStateRegistry proposal should be removed"); - } - - // Version 2.2.2 - op-contracts/v3.0.0 - AnchorStateRegistry - function test_normalizedStateDiffHash_AnchorStateRegistryOutputRootSlots() public { - vm.createSelectFork("mainnet", 22319975); - setupTests(); - - address anchorStateRegistry = address(0x496286e5eE7758de84Dd17e6d2d97afC2ACE4cc7); - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](1); - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](2); - - bytes32 startingAnchorRootSlot = bytes32(uint256(4)); - bytes32 l2BlockNumberSlot = bytes32(uint256(5)); - - bytes32 startingAnchorRoot = - bytes32(uint256(0x08ce0a407e15a1776bd43cd669328edc6825fcab988b8e2052258774251c25d2)); - bytes32 l2BlockNumber = bytes32(uint256(0x0000000000000000000000000000000000000000000000000000000000123456)); - - storageAccesses[0] = - storageAccess(anchorStateRegistry, startingAnchorRootSlot, isWrite, val0, startingAnchorRoot); - storageAccesses[1] = storageAccess(anchorStateRegistry, l2BlockNumberSlot, isWrite, val0, l2BlockNumber); - allAccesses[0] = accountAccess(anchorStateRegistry, storageAccesses); - - address parentMultisig = address(0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A); - bytes32 hash = allAccesses.normalizedStateDiffHash(parentMultisig, bytes32(0)); - - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - assertEq(hash, expectedHash, "AnchorStateRegistry output root writes should be removed"); - } - - /// It's possible for there to be more storage writes than accesses. - /// This test checks that the function handles this case correctly. - function test_more_storage_writes_than_accesses_passes() public pure { - address who = address(0xabcd); - VmSafe.AccountAccess[] memory accesses = new VmSafe.AccountAccess[](1); - VmSafe.StorageAccess[] memory sa = new VmSafe.StorageAccess[](2); - sa[0] = storageAccess(who, bytes32(uint256(0x1)), isWrite, val0, val1); - sa[1] = storageAccess(who, bytes32(uint256(0x2)), isWrite, val0, val1); - accesses[0] = accountAccess(who, sa); - - AccountAccessParser.StateDiff[] memory diffs = AccountAccessParser.getStateDiffFor(accesses, who, false); - assertEq(diffs.length, sa.length, "The number of diffs should be equal to the number of storage writes"); - } - - function test_normalizedStateDiffHash_OptimismPortalResourceMetering() public { - setupTests(); - vm.createSelectFork("mainnet", 22319975); // Use a realistic block number - VmSafe.AccountAccess[] memory allAccesses = new VmSafe.AccountAccess[](1); - // Create a state diff for OptimismPortal ResourceMetering - address who = address(0xabcd); - VmSafe.StorageAccess[] memory storageAccesses = new VmSafe.StorageAccess[](1); - IResourceMetering.ResourceParams memory resourceParams = IResourceMetering.ResourceParams({ - prevBaseFee: uint128(5), - prevBoughtGas: uint64(6), - prevBlockNum: uint64(block.number) - }); - bytes32 resourceParamsSlot = packResourceParams(resourceParams); - storageAccesses[0] = - storageAccess(who, OPTIMISM_PORTAL_RESOURCE_PARAMS_SLOT, isWrite, bytes32(uint256(0)), resourceParamsSlot); - allAccesses[0] = accountAccess(who, storageAccesses); - bytes32 hash = allAccesses.normalizedStateDiffHash(address(1), bytes32(0)); - - AccountAccessParser.AccountStateDiff[] memory emptyArray = new AccountAccessParser.AccountStateDiff[](0); - bytes32 expectedHash = keccak256(abi.encode(emptyArray)); - assertEq(hash, expectedHash, "OptimismPortal ResourceParams update should be removed"); - } - - /// @notice Packs the resource params into a bytes32. Where prevBlockNum is the most significant 64 bits, prevBoughtGas is the next 64 bits, and prevBaseFee is the least significant 128 bits. - function packResourceParams(IResourceMetering.ResourceParams memory _resourceParams) - internal - pure - returns (bytes32) - { - return (bytes32(uint256(_resourceParams.prevBlockNum)) << (128 + 64)) - | (bytes32(uint256(_resourceParams.prevBoughtGas)) << 128) | (bytes32(uint256(_resourceParams.prevBaseFee))); - } - - /// Helper functions similar to those in AccountAccessParser.t.sol - function accountAccess(address _account, VmSafe.StorageAccess[] memory _storageAccesses) - internal - pure - returns (VmSafe.AccountAccess memory) - { - return VmSafe.AccountAccess({ - chainInfo: VmSafe.ChainInfo({chainId: 1, forkId: 1}), - kind: VmSafe.AccountAccessKind.Call, - account: _account, - accessor: address(0), - initialized: true, - oldBalance: 0, - newBalance: 0, - deployedCode: new bytes(0), - value: 0, - data: new bytes(0), - reverted: false, - storageAccesses: _storageAccesses, - depth: 0 - }); - } - - function storageAccess(address _account, bytes32 _slot, bool _isWrite, bytes32 _previousValue, bytes32 _newValue) - internal - pure - returns (VmSafe.StorageAccess memory) - { - return VmSafe.StorageAccess({ - account: _account, - slot: _slot, - isWrite: _isWrite, - previousValue: _previousValue, - newValue: _newValue, - reverted: false - }); - } -} diff --git a/test/tasks/MultisigTask.t.sol b/test/tasks/MultisigTask.t.sol index c79561bf72..325c250516 100644 --- a/test/tasks/MultisigTask.t.sol +++ b/test/tasks/MultisigTask.t.sol @@ -153,7 +153,7 @@ contract MultisigTaskUnitTest is Test { public returns (VmSafe.AccountAccess[] memory accountAccesses, Action[] memory actions) { - (accountAccesses, actions,,,) = task.simulate(taskConfigFilePath, Solarray.addresses(childMultisig)); + (accountAccesses, actions,,) = task.simulate(taskConfigFilePath, Solarray.addresses(childMultisig)); (address[] memory targets, uint256[] memory values, bytes[] memory calldatas) = task.processTaskActions(actions); diff --git a/test/tasks/NestedMultisigTask.t.sol b/test/tasks/NestedMultisigTask.t.sol index 358c30f32c..3bed164851 100644 --- a/test/tasks/NestedMultisigTask.t.sol +++ b/test/tasks/NestedMultisigTask.t.sol @@ -72,7 +72,7 @@ contract NestedMultisigTaskTest is Test { string memory configFilePath = MultisigTaskTestHelper.createTempTomlFile(_taskConfigFilePath, TESTING_DIRECTORY, _salt); - (accountAccesses, actions,,, rootSafe) = multisigTask.simulate(configFilePath, _childSafes); + (accountAccesses, actions,, rootSafe) = multisigTask.simulate(configFilePath, _childSafes); MultisigTaskTestHelper.removeFile(configFilePath); addrRegistry = multisigTask.addrRegistry(); @@ -273,7 +273,7 @@ contract NestedMultisigTaskTest is Test { address[] memory childSafes = Solarray.addresses(foundationChildMultisig); - (VmSafe.AccountAccess[] memory accountAccesses, Action[] memory actions,,, address rootSafe) = + (VmSafe.AccountAccess[] memory accountAccesses, Action[] memory actions,, address rootSafe) = multisigTask.simulate(opcmTaskConfigFilePath, childSafes); address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe, foundationChildMultisig); @@ -303,7 +303,7 @@ contract NestedMultisigTaskTest is Test { // Snapshot before running the task so we can roll back to this pre-state uint256 newSnapshot = vm.snapshotState(); - (accountAccesses, actions,,,) = multisigTask.simulate(opcmTaskConfigFilePath, childSafes); + (accountAccesses, actions,,) = multisigTask.simulate(opcmTaskConfigFilePath, childSafes); bytes32 taskHash = multisigTask.getHash( testData.rootSafeCalldata, address(testData.rootSafe), 0, testData.originalRootSafeNonce, testData.allSafes ); @@ -320,7 +320,7 @@ contract NestedMultisigTaskTest is Test { string memory toml = "l2chains = [{name = \"OP Mainnet\", chainId = 10}]\n" "\n" "templateName = \"SetEIP1967Implementation\"\n contractIdentifier = \"OptimismPortalProxy\"\n newImplementation = \"0x0000000FFfFFfffFffFfFffFFFfffffFffFFffFf\"\n"; string memory configFilePath = MultisigTaskTestHelper.createTempTomlFile(toml, TESTING_DIRECTORY, "005"); - (,,,, address rootSafe) = + (,,, address rootSafe) = multisigTask.simulate(configFilePath, Solarray.addresses(SECURITY_COUNCIL_CHILD_MULTISIG)); assertEq(multisigTask.isNestedSafe(rootSafe), true, "Expected isNestedSafe to be true"); MultisigTaskTestHelper.removeFile(configFilePath); @@ -594,7 +594,7 @@ contract NestedMultisigTaskTest is Test { string memory config = MultisigTaskTestHelper.createTempTomlFile(_taskConfigToml, TESTING_DIRECTORY, "002"); - (_accountAccesses, _actions,,,) = multisigTask.simulate(config, _testData.childSafes); + (_accountAccesses, _actions,,) = multisigTask.simulate(config, _testData.childSafes); MultisigTaskTestHelper.removeFile(config); diff --git a/test/tasks/Regression.t.sol b/test/tasks/Regression.t.sol index 3ee2303357..e3d7cb0b81 100644 --- a/test/tasks/Regression.t.sol +++ b/test/tasks/Regression.t.sol @@ -11,6 +11,7 @@ import {OPCMUpgradeV200} from "src/template/OPCMUpgradeV200.sol"; import {OPCMUpgradeV300} from "src/template/OPCMUpgradeV300.sol"; import {OPCMUpgradeV400} from "src/template/OPCMUpgradeV400.sol"; import {OPCMUpdatePrestateV300} from "src/template/OPCMUpdatePrestateV300.sol"; +import {OPCMUpdatePrestateV410} from "src/template/OPCMUpdatePrestateV410.sol"; import {SetRespectedGameTypeTemplate} from "src/template/SetRespectedGameTypeTemplate.sol"; import {UpdateRetirementTimestampV200} from "src/template/UpdateRetirementTimestampV200.sol"; import {UpdateRetirementTimestampV400} from "src/template/UpdateRetirementTimestampV400.sol"; @@ -41,6 +42,7 @@ import {RevenueShareV100UpgradePath} from "src/template/RevenueShareUpgradePath. import {DeployFeesDepositor} from "src/template/DeployFeesDepositor.sol"; import {LateOptInRevenueShare} from "src/template/LateOptInRevenueShare.sol"; import {L1PortalExecuteL2Call} from "src/template/L1PortalExecuteL2Call.sol"; +import {AddGameTypeTemplate} from "src/template/AddGameTypeTemplate.sol"; /// @notice Ensures that simulating the task consistently produces the same call data and data to sign. /// This guarantees determinism if a bug is introduced in the task logic, the call data or data to sign @@ -105,29 +107,27 @@ contract RegressionTest is Test { ); } - /// @notice expected call data and data to sign generated by manually running the SetRespectedGameTypeTemplate at block 21724199 on mainnet using script: - /// forge script src/template/SetRespectedGameTypeTemplate.sol --sig "simulate(string)" test/tasks/mock/configs/SetRespectedGameTypeTemplate.toml --rpc-url mainnet --fork-block-number 21724199 -vv + /// @notice expected call data and data to sign generated by manually running the SetRespectedGameTypeTemplate at block 23591161 on mainnet using script: + /// cd test/tasks/example/eth/004-fp-set-respected-game-type + /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/justfile simulate council function testRegressionCallDataMatches_SetRespectedGameTypeTemplate() public { - string memory taskConfigFilePath = "test/tasks/mock/configs/SetRespectedGameTypeTemplate.toml"; + string memory taskConfigFilePath = "test/tasks/example/eth/004-fp-set-respected-game-type/config.toml"; string memory expectedCallData = - "0x174dea71000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c6901f65369fc59fc1b4d6d6be7a2318ff38db5b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000044a1155ed9000000000000000000000000beb5fc579115071764c7423a4f12edde41f106ed000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + "0x174dea710000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000004890928941e62e273da359374b105f803329f47300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000247fc48504000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; MultisigTask multisigTask = new SetRespectedGameTypeTemplate(); - address rootSafe = address(0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A); // FoundationOperationSafe + address rootSafe = address(0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2); // GuardianSafe address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe); (Action[] memory actions, uint256[] memory allOriginalNonces) = - _setupAndSimulate(taskConfigFilePath, 21724199, "mainnet", multisigTask, allSafes); + _setupAndSimulate(taskConfigFilePath, 23591161, "mainnet", multisigTask, allSafes); - bytes memory rootSafeCalldata = - _assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData); - uint256 rootSafeNonce = allOriginalNonces[allOriginalNonces.length - 1]; + _assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData); - string memory expectedDataToSign = - "0x19014e6a6554de0308f5ece8ff736beed8a1b876d16f5c27cac8e466d7de0c7038904a8d9abb28e3fbba3ffc928e3357077c716885b5b1e2c51f2ee976a24f02445d"; + string[] memory expectedDataToSign = new string[](1); + expectedDataToSign[0] = + "0x1901df53d510b56e539b90b369ef08fce3631020fbf921e3136ea5f8747c20bce96784a424304de6939f40116e6a55f5653b172e5aa34a67b95ea28feb3d8c507528"; - _assertDataToSignSingleMultisig( - rootSafe, rootSafeCalldata, expectedDataToSign, rootSafeNonce, MULTICALL3_ADDRESS - ); + _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); } /// @notice expected call data and data to sign generated by manually running the example task test/tasks/example/sep/020-blacklist-games-v140: @@ -421,6 +421,35 @@ contract RegressionTest is Test { _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); } + /// @notice Expected call data and data to sign generated by manually running the OPCMUpdatePrestateV410 template at block 9327881 on sepolia + /// Simulate from task directory (test/tasks/example/sep/028-opcm-update-prestate-v410) with: + /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path "$(pwd)/.env" --justfile ../../../../../src/justfile simulate + function testRegressionCallDataMatches_OPCMUpdatePrestateV410() public { + string memory taskConfigFilePath = "test/tasks/example/sep/028-opcm-update-prestate-v410/config.toml"; + // Call data generated by manually running the OPCMUpdatePrestateV410 template at block 9327881 on sepolia. + string memory expectedCallData = + "0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bb6437aba031afbf9cb3538fa064161e2bf2d780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a49a72745b00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538000000000000000000000000189abaaaa82dfc015a588a7dbad6f13b1d3485bcdead00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + MultisigTask multisigTask = new OPCMUpdatePrestateV410(); + address rootSafe = address(0x1Eb2fFc903729a0F03966B917003800b145F56E2); + address foundationChildMultisig = 0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B; + address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe, foundationChildMultisig); + + (Action[] memory actions, uint256[] memory allOriginalNonces) = + _setupAndSimulate(taskConfigFilePath, 9327881, "sepolia", multisigTask, allSafes); + + _assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData); + + // Data to sign generated by manually running the OPCMUpdatePrestateV410 template at block 9327881 on sepolia. + string[] memory expectedDataToSign = new string[](2); + // Foundation + expectedDataToSign[0] = + "0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb08ad152c64b380ea271693325a628e4a7aa2d70ff311f22cc0ceff34c34d9877"; + // Security council + expectedDataToSign[1] = + "0x1901be081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b8533766facb22395ca23edfd1485fd285bfeb3c25354a43ebd02ef6f8d072c69aa14"; + _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); + } + /// @notice Expected call data and data to sign generated by manually running the TransferL2PAOFromL1 template at block 22447773 on mainnet. /// Simulate from task directory (test/tasks/example/eth/008-transfer-l2pao) with: /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/nested.just simulate @@ -841,7 +870,7 @@ contract RegressionTest is Test { _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); } - /// @notice Expected call data and data to sign generated by manually running the L1PortalExecuteL2CallUpgradeGovernor template at block 23197819 on mainnet. + // @notice Expected call data and data to sign generated by manually running the L1PortalExecuteL2CallUpgradeGovernor template at block 23197819 on mainnet. /// Simulate from task directory (test/tasks/example/eth/014-noop-call-optimismportal/config.toml) with: /// just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/justfile simulate (foundation|council) function testRegressionCallDataMatches_L1PortalExecuteL2CallUpgradeGovernor() public { @@ -977,6 +1006,34 @@ contract RegressionTest is Test { ); } + /// @notice Expected call data and data to sign generated by manually running the AddGameType template at block 9431469 on Sepolia. + /// Simulate from task directory (test/tasks/example/sep/012-add-game-type) with: + /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/single.just simulate + function testRegressionCalldataMatches_AddGameType() public { + string memory taskConfigFilePath = "test/tasks/example/sep/012-add-game-type/config.toml"; + string memory expectedCallData = + "0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bb6437aba031afbf9cb3538fa064161e2bf2d780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002441661a2e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000004ca9608fef202216bc21d543798ec854539baad3000000000000000000000000ff9d236641962cebf9dbfb54e7b8e91f99f10db0000000000000000000000000b39c1730dff54f25f9e45667c119e0a8fee7315600000000000000000000000000000000000000000000000000000000000000000339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f910000000000000000000000000000000000000000000000000000000000000049000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000049d40000000000000000000000000000000000000000000000000011c37937e08000000000000000000000000000007babe08ee4d07dba236530183b24055535a7011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000147468697320697320612073616c74206d6978657200000000000000000000000000000000000000000000000000000000000000000000000000000000"; + MultisigTask multisigTask = new AddGameTypeTemplate(); + address rootSafe = address(0x1Eb2fFc903729a0F03966B917003800b145F56E2); + address foundationChildMultisig = address(0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B); + address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe, foundationChildMultisig); + + (Action[] memory actions, uint256[] memory allOriginalNonces) = + _setupAndSimulate(taskConfigFilePath, 9431469, "sepolia", multisigTask, allSafes); + + _assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData); + + string[] memory expectedDataToSign = new string[](2); + // Foundation + expectedDataToSign[0] = + "0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb219585bec52931beea5abdd75155cfff7596dc1bc3697006ee74dcd3683a9062"; + // Security council + expectedDataToSign[1] = + "0x1901be081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b853367060414e1617ef70777db275e9b27086a8ad00395f28214b4b9d1099a6d99df"; + + _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); + } + /// @notice Expected call data and data to sign generated by manually running the LateOptInRevenueShare template at block 23197819 on mainnet, using a custom calculator. /// Simulate from task directory (test/tasks/example/eth/017-opt-in-revenue-share-late-custom-calc/config.toml) with: /// just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/justfile simulate (foundation|council) @@ -1052,7 +1109,7 @@ contract RegressionTest is Test { childSafes = new address[](0); } allOriginalNonces = MultisigTaskTestHelper.getAllOriginalNonces(allSafes); - (, actions,,,) = multisigTask.simulate(taskConfigFilePath, childSafes); + (, actions,,) = multisigTask.simulate(taskConfigFilePath, childSafes); } /// @notice Assert that the call data generated by the multisig task matches the expected call data. diff --git a/test/tasks/SingleMultisigTask.t.sol b/test/tasks/SingleMultisigTask.t.sol index baa32a420b..5666653ad0 100644 --- a/test/tasks/SingleMultisigTask.t.sol +++ b/test/tasks/SingleMultisigTask.t.sol @@ -48,7 +48,7 @@ contract SingleMultisigTaskTest is Test { returns (VmSafe.AccountAccess[] memory accountAccesses, Action[] memory actions, address rootSafe) { multisigTask = new GasConfigTemplate(); - (accountAccesses, actions,,, rootSafe) = multisigTask.simulate(taskConfigFilePath, new address[](0)); + (accountAccesses, actions,, rootSafe) = multisigTask.simulate(taskConfigFilePath, new address[](0)); } function toSuperchainAddrRegistry(AddressRegistry _addrRegistry) @@ -106,7 +106,7 @@ contract SingleMultisigTaskTest is Test { vm.expectRevert("No actions found"); localMultisigTask.processTaskActions(actions); - (accountAccesses, actions,,,) = localMultisigTask.simulate(taskConfigFilePath, new address[](0)); + (accountAccesses, actions,,) = localMultisigTask.simulate(taskConfigFilePath, new address[](0)); addrRegistry = localMultisigTask.addrRegistry(); diff --git a/test/tasks/StateOverrideManager.t.sol b/test/tasks/StateOverrideManager.t.sol index 7f3a264ea4..51f6e41c81 100644 --- a/test/tasks/StateOverrideManager.t.sol +++ b/test/tasks/StateOverrideManager.t.sol @@ -283,7 +283,7 @@ contract StateOverrideManagerUnitTest is Test { string memory fileName = helper.createTempTomlFile(nonNestedSafeToml, TESTING_DIRECTORY, "011"); MockSetEIP1967ImplTask si = new MockSetEIP1967ImplTask(); - (,,,, address rootSafe) = si.simulate(fileName, new address[](0)); + (,,, address rootSafe) = si.simulate(fileName, new address[](0)); // Only parent overrides will be checked because child multisig is not set. Simulation.StateOverride[] memory allOverrides = assertDefaultStateOverrides(1, si, new address[](0), rootSafe); @@ -555,7 +555,7 @@ contract StateOverrideManagerUnitTest is Test { returns (MultisigTask task, address rootSafe) { task = new MockMultisigTask(); - (,,,, rootSafe) = task.simulate(fileName, Solarray.addresses(childMultisig)); + (,,, rootSafe) = task.simulate(fileName, Solarray.addresses(childMultisig)); return (task, rootSafe); } diff --git a/test/tasks/TaskManager.t.sol b/test/tasks/TaskManager.t.sol index 0e9ba7538e..53c5f5a7c0 100644 --- a/test/tasks/TaskManager.t.sol +++ b/test/tasks/TaskManager.t.sol @@ -93,45 +93,6 @@ contract TaskManagerUnitTest is StateOverrideManager, Test { tm.requireSignerOnSafe(signer, safe); } - function testNormalizedHashCheck_Passes() public { - TaskManager tm = new TaskManager(); - TaskConfig memory config = TaskConfig({ - optionalL2Chains: new L2Chain[](0), - basePath: "test/tasks/example/eth/004-fp-set-respected-game-type", - configPath: "", - templateName: "", - rootSafe: address(0), - isNested: true, - task: address(0) - }); - // Doesn't have a VALIDATION markdown file. - assertTrue(tm.checkNormalizedHash(bytes32(hex"1230"), config)); - assertTrue(tm.checkNormalizedHash(bytes32(hex"1234"), config)); - - // Does have a VALIDATION markdown file and hash matches. - config.basePath = "src/tasks/eth/013-gas-params-op"; - assertTrue( - tm.checkNormalizedHash( - bytes32(hex"2576512ad010b917c049a392e916bb02de1c168477fe29c4f8cbc4fcb016a4b0"), config - ) - ); - } - - function testNormalizedHashCheck_Fails() public { - TaskManager tm = new TaskManager(); - TaskConfig memory config = TaskConfig({ - optionalL2Chains: new L2Chain[](0), - basePath: "src/tasks/eth/013-gas-params-op", - configPath: "", - templateName: "", - rootSafe: address(0), - isNested: true, - task: address(0) - }); - // Does have a VALIDATION markdown file and hash does not match. - assertFalse(tm.checkNormalizedHash(bytes32(hex"10"), config)); - } - function testDataToSignCheck_Passes() public { vm.createSelectFork("mainnet"); // Pinning to a block. TaskManager tm = new TaskManager(); @@ -252,7 +213,7 @@ contract TaskManagerUnitTest is StateOverrideManager, Test { TaskManager tm = new TaskManager(); L2Chain[] memory l2Chains = new L2Chain[](1); l2Chains[0] = L2Chain({chainId: 10, name: "OP Mainnet"}); - (,, bytes memory dataToSign) = tm.executeTask( + (, bytes memory dataToSign) = tm.executeTask( TaskConfig({ optionalL2Chains: l2Chains, basePath: "test/tasks/example/eth/006-system-config-gas-params", diff --git a/test/tasks/example/eth/004-fp-set-respected-game-type/.env b/test/tasks/example/eth/004-fp-set-respected-game-type/.env index dc044ed770..08903d7662 100644 --- a/test/tasks/example/eth/004-fp-set-respected-game-type/.env +++ b/test/tasks/example/eth/004-fp-set-respected-game-type/.env @@ -1 +1,2 @@ -FORK_BLOCK_NUMBER=22990492 \ No newline at end of file +FORK_BLOCK_NUMBER=23591161 +NESTED_SAFE_NAME_DEPTH_1=council diff --git a/test/tasks/example/eth/004-fp-set-respected-game-type/config.toml b/test/tasks/example/eth/004-fp-set-respected-game-type/config.toml index fe8b90b4cf..44e9a189e6 100644 --- a/test/tasks/example/eth/004-fp-set-respected-game-type/config.toml +++ b/test/tasks/example/eth/004-fp-set-respected-game-type/config.toml @@ -1,10 +1,10 @@ l2chains = [ - {name = "OP Mainnet", chainId = 10} + {name = "Soneium", chainId = 1868} ] templateName = "SetRespectedGameTypeTemplate" [gameTypes] configs = [ - {chainId = 10, gameType = 1} + {chainId = 1868, gameType = 0} ] diff --git a/test/tasks/example/sep/012-add-game-type/.env b/test/tasks/example/sep/012-add-game-type/.env new file mode 100644 index 0000000000..b076a25c96 --- /dev/null +++ b/test/tasks/example/sep/012-add-game-type/.env @@ -0,0 +1,2 @@ +FORK_BLOCK_NUMBER=9431469 +NESTED_SAFE_NAME_DEPTH_1=foundation diff --git a/test/tasks/example/sep/012-add-game-type/config.toml b/test/tasks/example/sep/012-add-game-type/config.toml new file mode 100644 index 0000000000..af1c29b61d --- /dev/null +++ b/test/tasks/example/sep/012-add-game-type/config.toml @@ -0,0 +1,24 @@ +templateName = "AddGameTypeTemplate" + +l2chains = [ + {name = "Soneium Testnet Minato", chainId = 1946}, +] + +[[configs]] +chainId = 1946 +saltMixer = "this is a salt mixer" +systemConfig = "0x4Ca9608Fef202216bc21D543798ec854539bAAd3" +proxyAdmin = "0xff9d236641962Cebf9DBFb54E7b8e91F99f10Db0" +delayedWETH = "0xB39c1730DFF54f25F9e45667c119e0a8FeE73156" +disputeGameType = 0 +disputeAbsolutePrestate = "0x0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f91" +disputeMaxGameDepth = 73 +disputeSplitDepth = 30 +disputeClockExtension = 10800 +disputeMaxClockDuration = 302400 +initialBond = 80000000000000000 +vm = "0x07babe08ee4d07dba236530183b24055535a7011" +permissioned = false + +[addresses] +OPCM = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" diff --git a/test/tasks/example/sep/028-opcm-update-prestate-v410/.env b/test/tasks/example/sep/028-opcm-update-prestate-v410/.env new file mode 100644 index 0000000000..4387ef676b --- /dev/null +++ b/test/tasks/example/sep/028-opcm-update-prestate-v410/.env @@ -0,0 +1,3 @@ +TENDERLY_GAS=15000000 +FORK_BLOCK_NUMBER=9327881 +NESTED_SAFE_NAME_DEPTH_1=foundation diff --git a/test/tasks/example/sep/028-opcm-update-prestate-v410/config.toml b/test/tasks/example/sep/028-opcm-update-prestate-v410/config.toml new file mode 100644 index 0000000000..5fe50931a4 --- /dev/null +++ b/test/tasks/example/sep/028-opcm-update-prestate-v410/config.toml @@ -0,0 +1,24 @@ +templateName = "OPCMUpdatePrestateV410" + +[[l2chains]] +chainId = 11155420 +name = "OP Sepolia Testnet" + +[[opcmUpgrades]] +chainId = 11155420 +absolutePrestate = "0xdead000000000000000000000000000000000000000000000000000000000000" # Dummy test prestate +expectedValidationErrors = "OVERRIDES-L1PAOMULTISIG,OVERRIDES-CHALLENGER" # The template provides PAO and Challenger overrides to the validator from the SCR. These simply indicate overrides were used. + +[addresses] +OPCM = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" # version 3.2.0 https://github.com/ethereum-optimism/superchain-registry/blob/40526b1288534f6b84b7aae21d13c0b5f5b12f47/validation/standard/standard-versions-sepolia.toml#L23 + +[stateOverrides] +0x1Eb2fFc903729a0F03966B917003800b145F56E2 = [ # L1PAO + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 37} +] +0xf64bc17485f0B4Ea5F06A96514182FC4cB561977 = [ # SC + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 48} +] +0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B = [ # FUS + {key = "0x0000000000000000000000000000000000000000000000000000000000000005", value = 51} +] diff --git a/test/template/RevenueShareUpgradePath.t.sol b/test/template/RevenueShareUpgradePath.t.sol index 1dc7646b3c..e691aa4460 100644 --- a/test/template/RevenueShareUpgradePath.t.sol +++ b/test/template/RevenueShareUpgradePath.t.sol @@ -140,7 +140,7 @@ contract RevenueShareUpgradePathTest is Test { string memory _actionCountMessage ) internal { // Step 1: Run simulate to prepare everything and get the actions - (, Action[] memory _actions,,, address _rootSafe) = template.simulate(_configPath, new address[](0)); + (, Action[] memory _actions,, address _rootSafe) = template.simulate(_configPath, new address[](0)); // Verify we got the expected safe and action count assertEq(_rootSafe, PROXY_ADMIN_OWNER, "Root safe should be ProxyAdminOwner"); diff --git a/test/template/late-opt-in-rev-share/LateOptInRevenueShare.t.sol b/test/template/late-opt-in-rev-share/LateOptInRevenueShare.t.sol index cd220206f3..5ae7211b52 100644 --- a/test/template/late-opt-in-rev-share/LateOptInRevenueShare.t.sol +++ b/test/template/late-opt-in-rev-share/LateOptInRevenueShare.t.sol @@ -98,7 +98,7 @@ contract LateOptInRevenueShareTest is Test { address calculator ) internal { // Step 1: Run simulate to prepare everything and get the actions - (, Action[] memory actions,,, address rootSafe) = template.simulate(configPath, new address[](0)); + (, Action[] memory actions,, address rootSafe) = template.simulate(configPath, new address[](0)); // Verify we got the expected safe and action count assertEq(rootSafe, PROXY_ADMIN_OWNER, "Root safe should be ProxyAdminOwner");