Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# unichain-004-superchain-config-fix: Unichain Sepolia SuperchainConfig fix

Status: READY TO SIGN

## Objective

This task reinitializes Unichain Sepolia's L1 system contracts so they all point to the shared SuperchainConfig implementation target on Sepolia.

Unichain Sepolia currently uses a non-standard SuperchainConfig. Until this task is completed, Unichain Sepolia is blocked from doing U13+ using OPCM because OPCM assumes the chain is part of the Superchain.

## Transaction creation

The transaction is generated by the [UniFix script](../../../template/UniFix.sol), which reads the inputs from the [`config.toml`](./config.toml) file.

## Signing and execution

Follow the instructions in the [Single Execution](../../../SINGLE.md) guide for the following steps:

- [1. Update repo](../../../SINGLE.md#1-update-repo)
- [2. Setup Ledger](../../../SINGLE.md#2-setup-ledger)
- [3. Simulate and validate the transaction](../../../SINGLE.md#3-simulate-and-validate-the-transaction)

Then follow the instructions in the [Validation](./VALIDATION.md) guide.

## Simulation

When simulating, ensure the logs say `Using script <your_path_to_superchain_ops>/superchain-ops/src/improvements/template/UniFix.sol`.
Navigate to the correct task directory then run the simulate command.
```
cd 003-unichain-superchain-config-fix
SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../single.just simulate
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 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:

- [Validation](#validation)
- [Expected Domain and Message Hashes](#expected-domain-and-message-hashes)
- [State Validations](#state-validations)
- [Task 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.
>
> ### Single Safe Signer Data
>
> - Domain Hash: `2fedecce87979400ff00d5cec4c77da942d43ab3b9db4a5ffc51bb2ef498f30b`
> - Message Hash: `32c2e453d52e98a2fba9b805b4cf69ed1844ba3b3fe1586daa36239d4db5166d`

# State Validations

For each contract listed in the state diff, please verify that no contracts or state changes shown in the Tenderly diff are missing from this document. Additionally, please verify that for each contract:

- The following state changes (and none others) are made to that contract. This validates that no unexpected state
changes occur.
- All addresses (in section headers and storage values) match the provided name, using the Etherscan and Superchain
Registry links provided. This validates the bytecode deployed at the addresses contains the correct logic.
- All key values match the semantic meaning provided, which can be validated using the storage layout links provided.

### Task State Changes

<pre>
<code>
----- DecodedStateDiff[0] -----
Who: 0x0d83dab629f0e0F9d36c0Cbc89B69a489f0751bD
Contract: OptimismPortal2
Chain ID: 1301
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000035
Raw Old Value: 0x0000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc00
Raw New Value: 0x0000000000000000000000c2be75506d5724086deb7245bd260cc9753911be00
Decoded Kind: bool
Decoded Old Value: false
Decoded New Value: false
Summary: spacer_53_0_1
Detail:

----- DecodedStateDiff[1] -----
Who: 0x448A37330A60494E666F6DD60aD48d930AEbA381
Contract: L1CrossDomainMessenger
Chain ID: 1301
Raw Slot: 0x00000000000000000000000000000000000000000000000000000000000000fb
Raw Old Value: 0x000000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc
Raw New Value: 0x000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be
Decoded Kind: contract ISuperchainConfig
Decoded Old Value:
Decoded New Value:
Summary: superchainConfig
Detail:

----- DecodedStateDiff[2] -----
Who: 0x4696b5e042755103fe558738Bcd1ecEe7A45eBfe
Contract: L1ERC721Bridge
Chain ID: 1301
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000032
Raw Old Value: 0x000000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc
Raw New Value: 0x000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be
Decoded Kind: contract ISuperchainConfig
Decoded Old Value:
Decoded New Value:
Summary: superchainConfig
Detail:

----- DecodedStateDiff[3] -----
Who: 0x4E7e6dC46CE003A1E353B6848BF5a4fc1FeAC8Ae
Contract:
Chain ID:
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000068
Raw Old Value: 0x000000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc
Raw New Value: 0x000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be
[WARN] Slot was not decoded

----- DecodedStateDiff[4] -----
Who: 0x73D18d6Caa14AeEc15449d0A25A31D4e7E097a5c
Contract:
Chain ID:
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000068
Raw Old Value: 0x000000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc
Raw New Value: 0x000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be
[WARN] Slot was not decoded

----- DecodedStateDiff[5] -----
Who: 0xd363339eE47775888Df411A163c586a8BdEA9dbf
Contract: ProxyAdminOwner (GnosisSafe)
Chain ID: 1301
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000005
Raw Old Value: 0x000000000000000000000000000000000000000000000000000000000000001a
Raw New Value: 0x000000000000000000000000000000000000000000000000000000000000001b
Decoded Kind: uint256
Decoded Old Value: 26
Decoded New Value: 27
Summary: nonce
Detail:

----- DecodedStateDiff[6] -----
Who: 0xea58fcA6849d79EAd1f26608855c2D6407d54Ce2
Contract: L1StandardBridge
Chain ID: 1301
Raw Slot: 0x0000000000000000000000000000000000000000000000000000000000000032
Raw Old Value: 0x000000000000000000000000e7e23eba32a6fd2ac79dd5ec72fe7f6217b41bdc
Raw New Value: 0x000000000000000000000000c2be75506d5724086deb7245bd260cc9753911be
Decoded Kind: contract ISuperchainConfig
Decoded Old Value:
Decoded New Value:
Summary: superchainConfig
Detail:
</code>
</pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
l2chains = [{name = "Unichain Sepolia", chainId = 1301}]
templateName = "UniFix"

[addresses]
StandardValidatorV180 = "0x0a5bf8ebb4b177b2dcc6eba933db726a2e2e2b4d"
186 changes: 186 additions & 0 deletions src/improvements/template/UniFix.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {console} from "forge-std/console.sol";
import {VmSafe} from "forge-std/Vm.sol";
import {IGnosisSafe} from "@base-contracts/script/universal/IGnosisSafe.sol";
import {IStandardValidatorV180} from "@eth-optimism-bedrock/interfaces/L1/IStandardValidator.sol";

import {SuperchainAddressRegistry} from "src/improvements/SuperchainAddressRegistry.sol";
import {L2TaskBase, MultisigTask, AddressRegistry} from "src/improvements/tasks/MultisigTask.sol";

import {
IOptimismPortal2,
IProxyAdmin,
ISuperchainConfig,
IDisputeGameFactory,
IFaultDisputeGame,
GameType,
Claim
} from "@eth-optimism-bedrock/interfaces/L1/IOPContractsManager.sol";
import {StorageSetter} from "@eth-optimism-bedrock/src/universal/StorageSetter.sol";

import {stdToml} from "forge-std/StdToml.sol";
import {LibString} from "solady/utils/LibString.sol";

/// @notice A template contract for configuring protocol parameters.
/// This file is intentionally stripped down; please add your logic where indicated.
/// Please make sure to address all TODOs and remove the require() statements.
contract UniFix is L2TaskBase {
using stdToml for string;
using LibString for string;

/// @notice The StandardValidatorV180 address
IStandardValidatorV180 public STANDARD_VALIDATOR_V180;

IProxyAdmin public proxyAdmin;
ISuperchainConfig public superchainConfig;

/// @notice Returns the string identifier for the safe executing this transaction.
function safeAddressString() public pure override returns (string memory) {
return "ProxyAdminOwner";
}

/// @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[](8);
storageWrites[0] = "L1CrossDomainMessengerProxy";
storageWrites[1] = "AddressManager";
storageWrites[2] = "L1ERC721BridgeProxy";
storageWrites[3] = "L1StandardBridgeProxy";
storageWrites[4] = "OptimismPortalProxy";
storageWrites[5] = "AnchorStateRegistryProxy";
storageWrites[6] = "PermissionedWETH"; // GameType 1
storageWrites[7] = "PermissionlessWETH"; // GameType 0

return storageWrites;
}

function _configureTask(string memory configPath)
internal
override
returns (AddressRegistry addrRegistry_, IGnosisSafe parentMultisig_, address multicallTarget_)
{
(addrRegistry_, parentMultisig_, multicallTarget_) = super._configureTask(configPath);
}

/// @notice Sets up the template with implementation configurations from a TOML file.
function _templateSetup(string memory taskConfigFilePath) internal override {
super._templateSetup(taskConfigFilePath);
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
assertEq(chains.length, 1);
assertEq(chains[0].chainId, 1301);

// Get the SuperchainConfig address for Op Sepolia.
// https://github.com/ethereum-optimism/superchain-registry/blob/2c60e5723c64b5a1b58ab72c5d3816927ff9391a/superchain/extra/addresses/addresses.json#L64
IOptimismPortal2 opSepoliaOptimismPortal = IOptimismPortal2(payable(0x16Fc5058F25648194471939df75CF27A2fdC48BC));
superchainConfig = ISuperchainConfig(opSepoliaOptimismPortal.superchainConfig());

// Get the ProxyAdmin address for Uni Sepolia.
proxyAdmin = IProxyAdmin(superchainAddrRegistry.getAddress("ProxyAdmin", 1301));

string memory tomlContent = vm.readFile(taskConfigFilePath);
STANDARD_VALIDATOR_V180 = IStandardValidatorV180(tomlContent.readAddress(".addresses.StandardValidatorV180"));
require(STANDARD_VALIDATOR_V180.disputeGameFactoryVersion().eq("1.0.0"), "Incorrect StandardValidatorV180");
vm.label(address(STANDARD_VALIDATOR_V180), "StandardValidatorV180");
}

/// @notice Write the calls that you want to execute for the task.
function _build() internal override {
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
// We are only operating on Uni Sepolia.
assertEq(chains.length, 1);
assertEq(chains[0].chainId, 1301);

// Get the addresses for Uni Sepolia.
address l1CrossDomainMessengerProxy = superchainAddrRegistry.getAddress("L1CrossDomainMessengerProxy", 1301);
address l1ERC721BridgeProxy = superchainAddrRegistry.getAddress("L1ERC721BridgeProxy", 1301);
address l1StandardBridgeProxy = superchainAddrRegistry.getAddress("L1StandardBridgeProxy", 1301);
address optimismPortalProxy = superchainAddrRegistry.getAddress("OptimismPortalProxy", 1301);
address anchorStateRegistryProxy = superchainAddrRegistry.getAddress("AnchorStateRegistryProxy", 1301);

// These are wrong in the registry, I queried them directly from the chain.
address delayedWETHPermissioned = 0x73D18d6Caa14AeEc15449d0A25A31D4e7E097a5c;
address delayedWETHPermissionless = 0x4E7e6dC46CE003A1E353B6848BF5a4fc1FeAC8Ae;

bytes32 superchainConfigSlotValue = bytes32(uint256(uint160(address(superchainConfig))));

// https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/L1CrossDomainMessenger.json#L132
_writeToProxy(l1CrossDomainMessengerProxy, bytes32(uint256(251)), superchainConfigSlotValue);

//https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/L1ERC721Bridge.json#L55
_writeToProxy(l1ERC721BridgeProxy, bytes32(uint256(50)), superchainConfigSlotValue);

// https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/L1StandardBridge.json#L62
_writeToProxy(l1StandardBridgeProxy, bytes32(uint256(50)), superchainConfigSlotValue);

// https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json#L27
_writeToProxy(anchorStateRegistryProxy, bytes32(uint256(2)), superchainConfigSlotValue);

// https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/DelayedWETH.json#L62
_writeToProxy(delayedWETHPermissioned, bytes32(uint256(104)), superchainConfigSlotValue);
_writeToProxy(delayedWETHPermissionless, bytes32(uint256(104)), superchainConfigSlotValue);

// https://github.com/ethereum-optimism/optimism/blob/2073f4059bd806af3e8b76b820aa3fa0b42016d0/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json#L61-L62
bytes32 superchainConfigSlotValueWithSpacer = bytes32(uint256(uint160(address(superchainConfig))) << 8);
_writeToProxy(optimismPortalProxy, bytes32(uint256(53)), superchainConfigSlotValueWithSpacer);
}

/// @notice Writes a value to a proxy contract.
/// @dev This is accomplished by upgrading the proxy to the StorageSetter, writing the value,
/// and then upgrading the proxy back to the previous implementation.
/// @param proxy The address of the proxy contract.
/// @param slot The slot to write to.
/// @param value The value to write.
function _writeToProxy(address proxy, bytes32 slot, bytes32 value) internal {
address storageSetter = 0xd81f43eDBCAcb4c29a9bA38a13Ee5d79278270cC;

// Upgrade the proxy to the StorageSetter.
address implBefore = proxyAdmin.getProxyImplementation(proxy);
proxyAdmin.upgrade(payable(proxy), storageSetter);

StorageSetter(proxy).setBytes32(slot, value);
proxyAdmin.upgrade(payable(proxy), implBefore);
}

/// @notice This method performs all validations and assertions that verify the calls executed as expected.
function _validate(VmSafe.AccountAccess[] memory, Action[] memory) internal view override {
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
assertEq(chains.length, 1);
assertEq(chains[0].chainId, 1301);

uint256 chainId = chains[0].chainId;
IDisputeGameFactory disputeGameFactory =
IDisputeGameFactory(superchainAddrRegistry.getAddress("DisputeGameFactoryProxy", chainId));
bytes32 currentAbsolutePrestate =
Claim.unwrap(IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameType.wrap(1)))).absolutePrestate());

address sysCfg = superchainAddrRegistry.getAddress("SystemConfigProxy", chainId);

IStandardValidatorV180.InputV180 memory input = IStandardValidatorV180.InputV180({
proxyAdmin: address(proxyAdmin),
sysCfg: sysCfg,
absolutePrestate: currentAbsolutePrestate,
l2ChainID: chainId
});

string memory reasons = STANDARD_VALIDATOR_V180.validate({_input: input, _allowFailure: true});
// "PROXYA-10", // Proxy admin owner must be l1PAOMultisig - This is OK because it is checking for the OP Sepolia PAO
// "SYSCON-20", // System config gas limit must be 60,000,000 - This is OK because we don't touch the system config
// "DF-30", // Dispute factory owner must be l1PAOMultisig - It is checking for the OP Sepolia PAO
// "PDDG-DWETH-30", // Delayed WETH owner must be l1PAOMultisig (for permissioned dispute game) - It is checking for the OP Sepolia PAO
// "PDDG-ANCHORP-40", // Anchor state registry root must match expected dead root (for permissioned dispute game) - This does not apply to any chain more than 1 week old
// "PDDG-120", // Permissioned dispute game challenger must match challenger address - It is checking for the OP Sepolia Challenger
// "PLDG-DWETH-30", // Delayed WETH owner must be l1PAOMultisig (for permissionless dispute game) - It is checking for the OP Sepolia PAO
// "PLDG-ANCHORP-40" // Anchor state registry root must match expected dead root (for permissionless dispute game) - This does not apply to any chain more than 1 week old
string memory expectedErrors_1310 =
"PROXYA-10,SYSCON-20,DF-30,PDDG-DWETH-30,PDDG-ANCHORP-40,PDDG-120,PLDG-DWETH-30,PLDG-ANCHORP-40";

require(reasons.eq(expectedErrors_1310), string.concat("Unexpected errors: ", reasons));
}

/// @notice Override to return a list of addresses that should not be checked for code length.
function getCodeExceptions() internal pure override returns (address[] memory) {
return new address[](0);
}
}
15 changes: 15 additions & 0 deletions test/tasks/example/sep/004-replace-superchain-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# 004-replace-superchain-config: Replace SuperchainConfig

Status: [DRAFT]()

## Objective

This is an example task that should be **deleted** once we have real tasks to run. It's purpose is to have a task that runs before the monorepo-integration-test task.

### Timing

## Transaction creation

## Signing and execution


Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
l2chains = [{name = "Unichain Sepolia", chainId = 1301}]
templateName = "UniFix"

[addresses]
StandardValidatorV180 = "0x0a5bf8ebb4b177b2dcc6eba933db726a2e2e2b4d"