diff --git a/src/template/OPCMUpgradeV600.sol b/src/template/OPCMUpgradeV600.sol new file mode 100644 index 000000000..4feaef597 --- /dev/null +++ b/src/template/OPCMUpgradeV600.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +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 {LibString} from "solady/utils/LibString.sol"; + +import {OPCMTaskBase} from "src/tasks/types/OPCMTaskBase.sol"; +import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol"; +import {Action} from "src/libraries/MultisigTypes.sol"; + +/// @notice A template contract for configuring OPCMTaskBase templates. +/// Supports: op-contracts/v6.0.0 +contract OPCMUpgradeV600 is OPCMTaskBase { + using stdToml for string; + using LibString for string; + + /// @notice Struct to store inputs data for each L2 chain. + struct OPCMUpgrade { + Claim cannonPrestate; + Claim cannonKonaPrestate; + uint256 chainId; + string expectedValidationErrors; + } + + /// @notice Mapping of L2 chain IDs to their respective OPCMUpgrade structs. + mapping(uint256 => OPCMUpgrade) public upgrades; + + /// @notice The Standard Validator returned by OPCM + IOPContractsManagerStandardValidator public STANDARD_VALIDATOR; + + /// @notice OPCM we delegatecall into (must be v6.0.0). + address public OPCM; + + /// @notice Names in the SuperchainAddressRegistry that are expected to be written during this task. + function _taskStorageWrites() internal pure virtual override returns (string[] memory) { + string[] memory storageWrites = new string[](9); + storageWrites[0] = "DisputeGameFactoryProxy"; + storageWrites[1] = "SystemConfigProxy"; + storageWrites[2] = "OptimismPortalProxy"; + storageWrites[3] = "OptimismMintableERC20FactoryProxy"; + storageWrites[4] = "AddressManager"; + storageWrites[5] = "ProxyAdminOwner"; + storageWrites[6] = "AnchorStateRegistryProxy"; + storageWrites[7] = "L1StandardBridgeProxy"; + storageWrites[8] = "L1ERC721BridgeProxy"; + return storageWrites; + } + + /// @notice Returns an array of strings that refer to contract names in the address registry. + /// Contracts with these names are expected to have their balance changes during the task. + /// By default returns an empty array. Override this function if your task expects balance changes. + function _taskBalanceChanges() internal view virtual override returns (string[] memory) {} + + /// @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 upgrades from TOML + OPCMUpgrade[] memory _upgrades = abi.decode(tomlContent.parseRaw(".opcmUpgrades"), (OPCMUpgrade[])); + for (uint256 i = 0; i < _upgrades.length; i++) { + upgrades[_upgrades[i].chainId] = _upgrades[i]; + } + + // OPCM from TOML; must be v6.0.0 + OPCM = tomlContent.readAddress(".addresses.OPCM"); + OPCM_TARGETS.push(OPCM); + require(IOPContractsManagerV600(OPCM).version().eq("6.0.0"), "Incorrect OPCM"); + 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 Builds the actions for executing the operations. + function _build(address) internal override { + SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains(); + IOPContractsManagerV600.OpChainConfig[] memory opChainConfigs = + new IOPContractsManagerV600.OpChainConfig[](chains.length); + + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + require(upgrades[chainId].chainId != 0, "OPCMUpgradeV600: Config not found for chain"); + + require( + Claim.unwrap(upgrades[chainId].cannonPrestate) != bytes32(0), "OPCMUpgradeV600: cannonPrestate is zero" + ); + require( + Claim.unwrap(upgrades[chainId].cannonKonaPrestate) != bytes32(0), + "OPCMUpgradeV600: cannonKonaPrestate is zero" + ); + + opChainConfigs[i] = IOPContractsManagerV600.OpChainConfig({ + systemConfigProxy: ISystemConfig(superchainAddrRegistry.getAddress("SystemConfigProxy", chainId)), + cannonPrestate: upgrades[chainId].cannonPrestate, + cannonKonaPrestate: upgrades[chainId].cannonKonaPrestate + }); + } + + // Delegatecall the OPCM.upgrade() function + (bool ok,) = OPCM_TARGETS[0].delegatecall( + abi.encodeWithSelector(IOPContractsManagerV600.upgrade.selector, opChainConfigs) + ); + require(ok, "OPCMUpgradeV600: Delegatecall failed in _build."); + } + + /// @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(); + + // Cache standard validator's expected values (same for all chains) + address standardL1PAO = STANDARD_VALIDATOR.l1PAOMultisig(); + address standardChallenger = STANDARD_VALIDATOR.challenger(); + + for (uint256 i = 0; i < chains.length; i++) { + uint256 chainId = chains[i].chainId; + + IOPContractsManagerStandardValidator.ValidationInputDev memory input = IOPContractsManagerStandardValidator + .ValidationInputDev({ + sysCfg: ISystemConfig(superchainAddrRegistry.getAddress("SystemConfigProxy", chainId)), + cannonPrestate: Claim.unwrap(upgrades[chainId].cannonPrestate), + cannonKonaPrestate: Claim.unwrap(upgrades[chainId].cannonKonaPrestate), + l2ChainID: chainId, + proposer: superchainAddrRegistry.getAddress("Proposer", chainId) + }); + + // Compute overrides: non-zero only if chain differs from standard + address l1PAOOverride = superchainAddrRegistry.getAddress("ProxyAdminOwner", chainId); + address challengerOverride = superchainAddrRegistry.getAddress("Challenger", chainId); + + l1PAOOverride = l1PAOOverride != standardL1PAO ? l1PAOOverride : address(0); + challengerOverride = challengerOverride != standardChallenger ? challengerOverride : address(0); + + string memory errors; + if (l1PAOOverride != address(0) || challengerOverride != address(0)) { + errors = STANDARD_VALIDATOR.validateWithOverrides({ + _input: input, + _allowFailure: true, + _overrides: IOPContractsManagerStandardValidator.ValidationOverrides({ + l1PAOMultisig: l1PAOOverride, + challenger: challengerOverride + }) + }); + } else { + errors = STANDARD_VALIDATOR.validate({_input: input, _allowFailure: true}); + } + + string memory expErrors = upgrades[chainId].expectedValidationErrors; + 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) {} +} + +/* ---------- Interfaces ---------- */ +/// @notice OPCM Interface. +interface IOPContractsManagerV600 { + struct OpChainConfig { + ISystemConfig systemConfigProxy; + Claim cannonPrestate; + Claim cannonKonaPrestate; + } + + function version() external view returns (string memory); + + function upgrade(OpChainConfig[] memory _opChainConfigs) external; + + function opcmStandardValidator() external view returns (IOPContractsManagerStandardValidator); +} + +/// @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 ValidationInputDev { + ISystemConfig sysCfg; + bytes32 cannonPrestate; + bytes32 cannonKonaPrestate; + uint256 l2ChainID; + address proposer; + } + + struct ValidationOverrides { + address l1PAOMultisig; + address challenger; + } + + function validate(ValidationInputDev memory _input, bool _allowFailure) external view returns (string memory); + function l1PAOMultisig() external view returns (address); + function challenger() external view returns (address); + function validateWithOverrides( + ValidationInputDev memory _input, + bool _allowFailure, + ValidationOverrides memory _overrides + ) external view returns (string memory); + + function version() external view returns (string memory); +} + +interface ISystemConfig { + struct Addresses { + address l1CrossDomainMessenger; + address l1ERC721Bridge; + address l1StandardBridge; + address optimismPortal; + address optimismMintableERC20Factory; + address delayedWETH; + address opcm; + } + + function getAddresses() external view returns (Addresses memory); +} diff --git a/test/tasks/Regression.t.sol b/test/tasks/Regression.t.sol index fcce55356..4a5535760 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 {OPCMUpgradeV500} from "src/template/OPCMUpgradeV500.sol"; +import {OPCMUpgradeV600} from "src/template/OPCMUpgradeV600.sol"; import {OPCMUpdatePrestateV300} from "src/template/OPCMUpdatePrestateV300.sol"; import {OPCMUpdatePrestateV410} from "src/template/OPCMUpdatePrestateV410.sol"; import {SetRespectedGameTypeTemplate} from "src/template/SetRespectedGameTypeTemplate.sol"; @@ -871,6 +872,32 @@ contract RegressionTest is Test { _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); } + /// @notice Expected call data generated by manually running the OPCMUpgradeV600Template at block 9874342 on sepolia. + /// Simulate from task directory (test/tasks/example/sep/033-opcm-upgrade-v600/config.toml) with: + /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path "$(pwd)"/.env --justfile ../../../../../src/justfile simulate + function testRegressionCallDataMatches_OPCMUpgradeV600Template() public { + string memory taskConfigFilePath = "test/tasks/example/sep/033-opcm-upgrade-v600/config.toml"; + string memory expectedCallData = + "0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000f0a2e224519e876979ea6b2cd15ef5cc3d6703bd0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a4cbeda5a700000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b53803845751c66672c0b09e68ba7c3024a7543a1a22edaa90d7c2c90ebc8cecee6803f833cc2a644a9f7bba9718c13f622b867c513f3c43f3eb5a0cad17784bd40800000000000000000000000000000000000000000000000000000000"; + MultisigTask multisigTask = new OPCMUpgradeV600(); + address rootSafe = address(0x1Eb2fFc903729a0F03966B917003800b145F56E2); + address nestedSafe = address(0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B); // sepolia + address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe, nestedSafe); + (Action[] memory actions, uint256[] memory allOriginalNonces) = + _setupAndSimulate(taskConfigFilePath, 9874342, "sepolia", multisigTask, allSafes); + + _assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData); + + string[] memory expectedDataToSign = new string[](2); + // Foundation + expectedDataToSign[0] = + "0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbbdebb61a012c61cc79de4251db4172889f9c2bea3593fdb3cf79b7016d90b88d8"; + // Security Council + expectedDataToSign[1] = + "0x1901be081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b85338af46bfe0ef8d4c4dcd740b727f664c01b5d0ee612f67383466fdebaff9ae1fb"; + _assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe); + } + /// @notice Expected call data generated by manually running the OPCMUpgradeSuperchainConfigV410Template at block 9167980 on sepolia. /// Simulate from task directory (test/tasks/example/sep/025-opcm-upgrade-superchainconfig-v410/config.toml) with: /// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path "$(pwd)"/.env --justfile ../../../../../src/justfile simulate diff --git a/test/tasks/example/sep/033-opcm-upgrade-v600/.env b/test/tasks/example/sep/033-opcm-upgrade-v600/.env new file mode 100644 index 000000000..a17801577 --- /dev/null +++ b/test/tasks/example/sep/033-opcm-upgrade-v600/.env @@ -0,0 +1,3 @@ +TENDERLY_GAS=15000000 +FORK_BLOCK_NUMBER=9874342 +NESTED_SAFE_NAME_DEPTH_1=foundation diff --git a/test/tasks/example/sep/033-opcm-upgrade-v600/config.toml b/test/tasks/example/sep/033-opcm-upgrade-v600/config.toml new file mode 100644 index 000000000..d88a2275d --- /dev/null +++ b/test/tasks/example/sep/033-opcm-upgrade-v600/config.toml @@ -0,0 +1,15 @@ +l2chains = [ + {name = "OP Sepolia Testnet", chainId = 11155420} +] + +templateName = "OPCMUpgradeV600" + +[[opcmUpgrades]] +chainId = 11155420 +# Dummy prestates from betanet: https://www.notion.so/oplabs/Betanet-2a9f153ee16280859261e3000d866ee9?source=copy_link#2c5f153ee162805abcc3e949ab6c837e +cannonPrestate = "0x03f833cc2a644a9f7bba9718c13f622b867c513f3c43f3eb5a0cad17784bd408" +cannonKonaPrestate = "0x03845751c66672c0b09e68ba7c3024a7543a1a22edaa90d7c2c90ebc8cecee68" +expectedValidationErrors = "" # The template provides PAO and Challenger overrides to the validator from the SCR. These simply indicate overrides were used. + +[addresses] +OPCM = "0xf0a2e224519e876979ea6b2cd15ef5cc3d6703bd" # v600 OPCM source: https://www.notion.so/oplabs/Upgrade-18-Hub-2a3f153ee1628173bfe4f732657e4f01?source=copy_link#2cef153ee162806482fceb958964651e