Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3b4f29b
Upgrade 18 Template
Wazabie Dec 3, 2025
8cc318c
fix: remove references to superchainconfig
ZakAyesh Dec 3, 2025
07ee22a
Merge branch 'main' into u18-template
Wazabie Dec 3, 2025
9ae22a3
Update env and config of example task
Wazabie Dec 3, 2025
c73c436
Merge branch 'u18-template' of https://github.com/ethereum-optimism/s…
Wazabie Dec 3, 2025
e880551
Include the superchainConfig upgrade task
Wazabie Dec 3, 2025
4a3ca5e
Merge branch 'main' into u18-template
Wazabie Dec 10, 2025
6f3b59f
Update templates for v600 breaking changes
Wazabie Dec 11, 2025
a3107d9
Update example tasks to run on betanet
Wazabie Dec 11, 2025
c9cd5d8
Update references
Wazabie Dec 19, 2025
193f7c9
Chore Update
Wazabie Dec 19, 2025
638e110
Chore updates
Wazabie Dec 19, 2025
af94fb4
Remove unused superchainConfig
Wazabie Dec 19, 2025
b71c1f4
formatting fixes
Wazabie Dec 19, 2025
a70b776
Merge branch 'main' into u18-template
Wazabie Dec 19, 2025
b84f9be
format fix
Wazabie Dec 19, 2025
d39eaa8
Merge branch 'u18-template' of https://github.com/ethereum-optimism/s…
Wazabie Dec 19, 2025
7e2173e
Update OPCMUpgradeV600.sol
Wazabie Dec 19, 2025
54cfb0d
Update Regression.t.sol
Wazabie Dec 19, 2025
7f9cc61
Revert "Update Regression.t.sol"
Wazabie Dec 19, 2025
2ed9b79
Update config.toml
Wazabie Dec 19, 2025
fd932d0
Update to use sepolia for example task
Wazabie Dec 19, 2025
462fb98
Update Regression.t.sol
Wazabie Dec 19, 2025
4de9247
Update config.toml
Wazabie Dec 19, 2025
a89d0eb
Update comments
Wazabie Dec 19, 2025
ef3b6fc
fix: address matt's code review
ZakAyesh Dec 22, 2025
9a39ed1
Update the overrides checks
Wazabie Dec 22, 2025
8d55b2f
Update OPCMUpgradeV600.sol
Wazabie Dec 22, 2025
7147ded
Update OPCMUpgradeV600.sol
Wazabie Dec 22, 2025
48c9651
Update OPCMUpgradeV600.sol
Wazabie Dec 22, 2025
17d5f82
Update OPCMUpgradeV600.sol
Wazabie Dec 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions src/template/OPCMUpgradeV600.sol
Original file line number Diff line number Diff line change
@@ -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) {}
Comment thread
Wazabie marked this conversation as resolved.

/// @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];
Comment thread
mds1 marked this conversation as resolved.
}
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.

// 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");
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
}

/// @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);
Comment thread
Wazabie marked this conversation as resolved.

for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
require(upgrades[chainId].chainId != 0, "OPCMUpgradeV600: Config not found for chain");
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.

require(
Claim.unwrap(upgrades[chainId].cannonPrestate) != bytes32(0), "OPCMUpgradeV600: cannonPrestate is zero"
);
require(
Claim.unwrap(upgrades[chainId].cannonKonaPrestate) != bytes32(0),
"OPCMUpgradeV600: cannonKonaPrestate is zero"
);
Comment thread
Wazabie marked this conversation as resolved.

opChainConfigs[i] = IOPContractsManagerV600.OpChainConfig({
systemConfigProxy: ISystemConfig(superchainAddrRegistry.getAddress("SystemConfigProxy", chainId)),
cannonPrestate: upgrades[chainId].cannonPrestate,
cannonKonaPrestate: upgrades[chainId].cannonKonaPrestate
});
}
Comment thread
Wazabie marked this conversation as resolved.

// Delegatecall the OPCM.upgrade() function
(bool ok,) = OPCM_TARGETS[0].delegatecall(
abi.encodeWithSelector(IOPContractsManagerV600.upgrade.selector, opChainConfigs)
);
require(ok, "OPCMUpgradeV600: Delegatecall failed in _build.");
Comment thread
Wazabie marked this conversation as resolved.
}

/// @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);
}
27 changes: 27 additions & 0 deletions test/tasks/Regression.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 <council|foundation>
function testRegressionCallDataMatches_OPCMUpgradeV600Template() public {
string memory taskConfigFilePath = "test/tasks/example/sep/033-opcm-upgrade-v600/config.toml";
string memory expectedCallData =
Comment thread
Wazabie marked this conversation as resolved.
"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
Expand Down
3 changes: 3 additions & 0 deletions test/tasks/example/sep/033-opcm-upgrade-v600/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
TENDERLY_GAS=15000000
FORK_BLOCK_NUMBER=9874342
NESTED_SAFE_NAME_DEPTH_1=foundation
15 changes: 15 additions & 0 deletions test/tasks/example/sep/033-opcm-upgrade-v600/config.toml
Original file line number Diff line number Diff line change
@@ -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