diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.env.example b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.env.example new file mode 100644 index 000000000000..8e51b69bb97e --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.env.example @@ -0,0 +1,15 @@ +############################################## +# ↓ Required ↓ # +############################################## + +# Can be "mainnet" or "sepolia" +NETWORK= + +# Etherscan API key used to verify contract bytecode +ETHERSCAN_API_KEY= + +# RPC URL for the L1 network that matches $NETWORK +ETH_RPC_URL= + +# Private key used to deploy the new contracts for this upgrade +PRIVATE_KEY= diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.gitignore b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.gitignore new file mode 100644 index 000000000000..4c49bd78f1d0 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/.gitignore @@ -0,0 +1 @@ +.env diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/DeployUpgrade.s.sol b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/DeployUpgrade.s.sol new file mode 100644 index 000000000000..48b0c45bfb86 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/DeployUpgrade.s.sol @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Forge +import { StdAssertions } from "forge-std/StdAssertions.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +// Scripts +import { Deploy } from "scripts/deploy/Deploy.s.sol"; +import { ChainAssertions } from "scripts/deploy/ChainAssertions.sol"; +import { Types } from "scripts/libraries/Types.sol"; + +// Contracts +import { Proxy } from "src/universal/Proxy.sol"; +import { IAnchorStateRegistry } from "src/dispute/interfaces/IAnchorStateRegistry.sol"; +import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; +import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; +import { IDelayedWETH } from "src/dispute/interfaces/IDelayedWETH.sol"; +import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; +import { MIPS } from "src/cannon/MIPS.sol"; +import { IBigStepper } from "src/dispute/interfaces/IBigStepper.sol"; +import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; +import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { Blueprint } from "src/libraries/Blueprint.sol"; +import { GameTypes } from "src/dispute/lib/Types.sol"; +import { Duration } from "src/dispute/lib/LibUDT.sol"; + +// Libraries +import { GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +/// @title DeployUpgrade +/// @notice Script for deploying contracts required to upgrade from v1.8.0 L2OO to v1.8.0 in a +/// PERMISSIONED configuration. +contract DeployUpgrade is Deploy, StdAssertions { + /// @notice Address of the ProxyAdmin contract. + address public proxyAdmin; + + /// @notice Address of the SystemOwnerSafe contract. + address public systemOwnerSafe; + + /// @notice Address of the SuperchainConfigProxy contract. + address public superchainConfigProxy; + + /// @notice Deploys the contracts required to upgrade from v1.8.0 L2OO to v1.8.0 in a + /// PERMISSIONED configuration. + /// @param _proxyAdmin Address of the ProxyAdmin contract. + /// @param _systemOwnerSafe Address of the SystemOwnerSafe contract. + /// @param _superchainConfigProxy Address of the SuperchainConfigProxy contract. + /// @param _disputeGameFactoryImpl Address of the DisputeGameFactory implementation contract. + /// @param _delayedWethImpl Address of the DelayedWETH implementation contract. + /// @param _preimageOracleImpl Address of the PreimageOracle implementation contract. + /// @param _mipsImpl Address of the MIPS implementation contract. + /// @param _optimismPortal2Impl Address of the OptimismPortal2 implementation contract. + /// @param _anchorStateRegistryBlueprint Address of the AnchorStateRegistry implementation blueprint. + function deploy( + address _proxyAdmin, + address _systemOwnerSafe, + address _superchainConfigProxy, + address _disputeGameFactoryImpl, + address _delayedWethImpl, + address _preimageOracleImpl, + address _mipsImpl, + address _optimismPortal2Impl, + address _anchorStateRegistryBlueprint + ) + public + { + console.log("Deploying contracts required to upgrade from v1.8.0 L2OO to v1.8.0 permissioned"); + console.log("Using PERMISSIONED proof system"); + + // Set address variables. + proxyAdmin = _proxyAdmin; + systemOwnerSafe = _systemOwnerSafe; + superchainConfigProxy = _superchainConfigProxy; + + // Prank admin contracts. + prankDeployment("ProxyAdmin", msg.sender); + prankDeployment("SystemOwnerSafe", msg.sender); + + // Prank shared contracts. + prankDeployment("SuperchainConfigProxy", superchainConfigProxy); + prankDeployment("DisputeGameFactory", _disputeGameFactoryImpl); + prankDeployment("DelayedWETH", _delayedWethImpl); + prankDeployment("PreimageOracle", _preimageOracleImpl); + prankDeployment("OptimismPortal2", _optimismPortal2Impl); + + // Deploy proxy contracts. + deployERC1967Proxy("DisputeGameFactoryProxy"); + deployERC1967Proxy("AnchorStateRegistryProxy"); + deployERC1967Proxy("PermissionedDelayedWETHProxy"); + + // Deploy AnchorStateRegistry implementation contract. + // We can't use a pre-created implementation because the ASR implementation holds an + // immutable variable that points at the DisputeGameFactoryProxy. + deployAnchorStateRegistry(_anchorStateRegistryBlueprint); + + // Re-use the existing MIPS implementation, but ensure its address is available + save("Mips", _mipsImpl); + + // Initialize proxy contracts. + initializeDisputeGameFactoryProxy(); + initializeAnchorStateRegistryProxy(); + initializePermissionedDelayedWETHProxy(); + + // ONLY deploy and set up the PermissionedDisputeGame. + // We can't use a pre-created implementation because the PermissionedDisputeGame holds an + // immutable variable that refers to the L2 chain ID. + setPermissionedCannonFaultGameImplementation(); + + // Transfer contract ownership to ProxyAdmin. + transferPermissionedWETHOwnershipFinal(); + transferDGFOwnershipFinal(); + transferAnchorStateOwnershipFinal(); + + // Run post-deployment assertions. + postDeployAssertions(); + + // Print config summary. + printConfigSummary(); + + // Print deployment summary. + printDeploymentSummary(); + } + + /// @notice Deploys teh AnchorStateRegistry implementation + function deployAnchorStateRegistry(address _anchorStateRegistryBlueprint) internal broadcast { + address impl = Blueprint.deployFrom( + _anchorStateRegistryBlueprint, _implSalt(), abi.encode(mustGetAddress("DisputeGameFactoryProxy")) + ); + save("AnchorStateRegistry", impl); + } + + /// @notice Sets the implementation for the `PERMISSIONED_CANNON` game type in the `DisputeGameFactory` + function setPermissionedCannonFaultGameImplementation() public broadcast { + console.log("Setting Cannon PermissionedDisputeGame implementation"); + IDisputeGameFactory factory = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); + IDelayedWETH weth = IDelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); + + PermissionedDisputeGame impl = new PermissionedDisputeGame( + GameTypes.PERMISSIONED_CANNON, + loadMipsAbsolutePrestate(), + cfg.faultGameMaxDepth(), + cfg.faultGameSplitDepth(), + Duration.wrap(uint64(cfg.faultGameClockExtension())), + Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), + IBigStepper(mustGetAddress("Mips")), + weth, + IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")), + cfg.l2ChainID(), + cfg.l2OutputOracleProposer(), + cfg.l2OutputOracleChallenger() + ); + factory.setImplementation(GameTypes.PERMISSIONED_CANNON, IDisputeGame(address(impl))); + save("PermissionedDisputeGame", address(impl)); + console.log( + "DisputeGameFactoryProxy: set `FaultDisputeGame` implementation (Backend: Cannon | GameType: PERMISSIONED_CANNON)" + ); + } + + /// @notice Initializes the DisputeGameFactory proxy. + function initializeDisputeGameFactoryProxy() internal broadcast { + console.log("Initializing DisputeGameFactory proxy"); + Proxy(payable(mustGetAddress("DisputeGameFactoryProxy"))).upgradeToAndCall( + mustGetAddress("DisputeGameFactory"), abi.encodeCall(IDisputeGameFactory.initialize, msg.sender) + ); + + // We don't need to set the initialization bond for PermissionedDisputeGame because the + // initialization bond is meant to be zero anyway. We assert that this bond is zero in the + // post-checks that we perform either way, so no need for an explicit transaction. + } + + /// @notice Initializes the AnchorStateRegistry proxy. + function initializeAnchorStateRegistryProxy() internal broadcast { + // Set up the anchor state root array. + IAnchorStateRegistry.StartingAnchorRoot[] memory roots = new IAnchorStateRegistry.StartingAnchorRoot[](2); + roots[0] = IAnchorStateRegistry.StartingAnchorRoot({ + gameType: GameTypes.PERMISSIONED_CANNON, + outputRoot: OutputRoot({ + root: Hash.wrap(cfg.faultGameGenesisOutputRoot()), + l2BlockNumber: cfg.faultGameGenesisBlock() + }) + }); + + // Initialize AnchorStateRegistry proxy. + console.log("Initializing AnchorStateRegistry proxy"); + Proxy(payable(mustGetAddress("AnchorStateRegistryProxy"))).upgradeToAndCall( + mustGetAddress("AnchorStateRegistry"), + abi.encodeCall( + IAnchorStateRegistry.initialize, (roots, ISuperchainConfig(mustGetAddress("SuperchainConfigProxy"))) + ) + ); + } + + /// @notice Initializes the permissioned DelayedWETH proxy. + function initializePermissionedDelayedWETHProxy() internal broadcast { + // Initialize permissioned DelayedWETH proxy. + console.log("Initializing permissioned DelayedWETH proxy"); + Proxy(payable(mustGetAddress("PermissionedDelayedWETHProxy"))).upgradeToAndCall( + mustGetAddress("DelayedWETH"), + abi.encodeCall( + IDelayedWETH.initialize, (msg.sender, ISuperchainConfig(mustGetAddress("SuperchainConfigProxy"))) + ) + ); + } + + /// @notice Transfers ownership of the permissioned DelayedWETH proxy to the ProxyAdmin and + /// transfers ownership of the underlying DelayedWETH contract to the SystemOwnerSafe. + function transferPermissionedWETHOwnershipFinal() internal broadcast { + // Transfer ownership of permissioned DelayedWETH to SystemOwnerSafe. + console.log("Transferring ownership of underlying permissioned DelayedWETH"); + IDelayedWETH weth = IDelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy")); + weth.transferOwnership(systemOwnerSafe); + + // Transfer ownership of permissioned DelayedWETH proxy to ProxyAdmin. + console.log("Transferring ownership of permissioned DelayedWETH proxy"); + Proxy prox = Proxy(payable(address(weth))); + prox.changeAdmin(proxyAdmin); + } + + /// @notice Transfers ownership of the DisputeGameFactory proxy to the ProxyAdmin and transfers + /// ownership of the underlying DisputeGameFactory contract to the SystemOwnerSafe. + function transferDGFOwnershipFinal() internal broadcast { + // Transfer ownership of DisputeGameFactory to SystemOwnerSafe. + console.log("Transferring ownership of underlying DisputeGameFactory"); + IDisputeGameFactory dgf = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); + dgf.transferOwnership(systemOwnerSafe); + + // Transfer ownership of DisputeGameFactory proxy to ProxyAdmin. + console.log("Transferring ownership of DisputeGameFactory proxy"); + Proxy prox = Proxy(payable(address(dgf))); + prox.changeAdmin(proxyAdmin); + } + + /// @notice Transfers ownership of the AnchorStateRegistry proxy to the ProxyAdmin. + function transferAnchorStateOwnershipFinal() internal broadcast { + // Transfer ownership of AnchorStateRegistry proxy to ProxyAdmin. + console.log("Transferring ownership of AnchorStateRegistry proxy"); + IAnchorStateRegistry asr = IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")); + Proxy prox = Proxy(payable(address(asr))); + prox.changeAdmin(proxyAdmin); + } + + /// @notice Checks that the deployed system is configured correctly. + function postDeployAssertions() internal view { + Types.ContractSet memory contracts = _proxies(); + contracts.OptimismPortal2 = mustGetAddress("OptimismPortal2"); + + // Ensure that `useFaultProofs` is set to `true`. + assertTrue(cfg.useFaultProofs(), "DeployUpgrade: useFaultProofs is not set to true"); + + // Verify that the DGF is owned by the ProxyAdmin. + address dgfProxyAddr = mustGetAddress("DisputeGameFactoryProxy"); + assertEq( + address(uint160(uint256(vm.load(dgfProxyAddr, Constants.PROXY_OWNER_ADDRESS)))), + proxyAdmin, + "DeployUpgrade: DGF is not owned by ProxyAdmin" + ); + + // Verify that permissioned DelayedWETH is owned by the ProxyAdmin. + address soyWethProxyAddr = mustGetAddress("PermissionedDelayedWETHProxy"); + assertEq( + address(uint160(uint256(vm.load(soyWethProxyAddr, Constants.PROXY_OWNER_ADDRESS)))), + proxyAdmin, + "DeployUpgrade: Permissioned DelayedWETH is not owned by ProxyAdmin" + ); + + // Run standard assertions. + ChainAssertions.checkDisputeGameFactory(contracts, systemOwnerSafe, true); + ChainAssertions.checkPermissionedDelayedWETH(contracts, cfg, true, systemOwnerSafe); + ChainAssertions.checkOptimismPortal2(contracts, cfg, false); + + // Verify PreimageOracle configuration. + IPreimageOracle oracle = IPreimageOracle(mustGetAddress("PreimageOracle")); + assertEq( + oracle.minProposalSize(), + cfg.preimageOracleMinProposalSize(), + "DeployUpgrade: PreimageOracle minProposalSize is not set correctly" + ); + assertEq( + oracle.challengePeriod(), + cfg.preimageOracleChallengePeriod(), + "DeployUpgrade: PreimageOracle challengePeriod is not set correctly" + ); + + // Verify MIPS configuration. + MIPS mips = MIPS(mustGetAddress("Mips")); + assertEq(address(mips.oracle()), address(oracle), "DeployUpgrade: MIPS oracle is not set correctly"); + + // Verify AnchorStateRegistry configuration. + IAnchorStateRegistry asr = IAnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")); + (Hash root1, uint256 l2BlockNumber1) = asr.anchors(GameTypes.PERMISSIONED_CANNON); + assertEq( + root1.raw(), + cfg.faultGameGenesisOutputRoot(), + "DeployUpgrade: AnchorStateRegistry root is not set correctly" + ); + assertEq( + l2BlockNumber1, + cfg.faultGameGenesisBlock(), + "DeployUpgrade: AnchorStateRegistry l2BlockNumber is not set correctly" + ); + + // Verify DisputeGameFactory configuration. + IDisputeGameFactory dgf = IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")); + assertEq( + dgf.initBonds(GameTypes.PERMISSIONED_CANNON), + 0 ether, + "DeployUpgrade: DisputeGameFactory initBonds is not set correctly" + ); + assertEq( + address(dgf.gameImpls(GameTypes.CANNON)), + address(0), + "DeployUpgrade: DisputeGameFactory gameImpls CANNON is not set correctly" + ); + assertEq( + address(dgf.gameImpls(GameTypes.PERMISSIONED_CANNON)), + mustGetAddress("PermissionedDisputeGame"), + "DeployUpgrade: DisputeGameFactory gameImpls PERMISSIONED_CANNON is not set correctly" + ); + + // Verify security override yoke configuration. + address soyGameAddr = address(dgf.gameImpls(GameTypes.PERMISSIONED_CANNON)); + PermissionedDisputeGame soyGameImpl = PermissionedDisputeGame(payable(soyGameAddr)); + assertEq( + soyGameImpl.proposer(), + cfg.l2OutputOracleProposer(), + "DeployUpgrade: PermissionedDisputeGame proposer is not set correctly" + ); + assertEq( + soyGameImpl.challenger(), + cfg.l2OutputOracleChallenger(), + "DeployUpgrade: PermissionedDisputeGame challenger is not set correctly" + ); + assertEq( + soyGameImpl.maxGameDepth(), + cfg.faultGameMaxDepth(), + "DeployUpgrade: PermissionedDisputeGame maxGameDepth is not set correctly" + ); + assertEq( + soyGameImpl.splitDepth(), + cfg.faultGameSplitDepth(), + "DeployUpgrade: PermissionedDisputeGame splitDepth is not set correctly" + ); + assertEq( + soyGameImpl.clockExtension().raw(), + cfg.faultGameClockExtension(), + "DeployUpgrade: PermissionedDisputeGame clockExtension is not set correctly" + ); + assertEq( + soyGameImpl.maxClockDuration().raw(), + cfg.faultGameMaxClockDuration(), + "DeployUpgrade: PermissionedDisputeGame maxClockDuration is not set correctly" + ); + assertEq( + soyGameImpl.absolutePrestate().raw(), + bytes32(cfg.faultGameAbsolutePrestate()), + "DeployUpgrade: PermissionedDisputeGame absolutePrestate is not set correctly" + ); + assertEq( + address(soyGameImpl.weth()), + soyWethProxyAddr, + "DeployUpgrade: PermissionedDisputeGame weth is not set correctly" + ); + assertEq( + address(soyGameImpl.anchorStateRegistry()), + address(asr), + "DeployUpgrade: PermissionedDisputeGame anchorStateRegistry is not set correctly" + ); + assertEq( + address(soyGameImpl.vm()), address(mips), "DeployUpgrade: PermissionedDisputeGame vm is not set correctly" + ); + } + + /// @notice Prints a summary of the configuration used to deploy this system. + function printConfigSummary() internal view { + console.log("Configuration Summary (chainid: %d)", block.chainid); + console.log(" 0. Use Fault Proofs: %s", cfg.useFaultProofs() ? "true" : "false"); + console.log(" 1. Absolute Prestate: %x", cfg.faultGameAbsolutePrestate()); + console.log(" 2. Max Depth: %d", cfg.faultGameMaxDepth()); + console.log(" 3. Output / Execution split Depth: %d", cfg.faultGameSplitDepth()); + console.log(" 4. Clock Extension (seconds): %d", cfg.faultGameClockExtension()); + console.log(" 5. Max Clock Duration (seconds): %d", cfg.faultGameMaxClockDuration()); + console.log(" 6. L2 Genesis block number: %d", cfg.faultGameGenesisBlock()); + console.log(" 7. L2 Genesis output root: %x", uint256(cfg.faultGameGenesisOutputRoot())); + console.log(" 8. Proof Maturity Delay (seconds): %d", cfg.proofMaturityDelaySeconds()); + console.log(" 9. Dispute Game Finality Delay (seconds): %d", cfg.disputeGameFinalityDelaySeconds()); + console.log(" 10. Respected Game Type: %d", cfg.respectedGameType()); + console.log(" 11. Preimage Oracle Min Proposal Size (bytes): %d", cfg.preimageOracleMinProposalSize()); + console.log(" 12. Preimage Oracle Challenge Period (seconds): %d", cfg.preimageOracleChallengePeriod()); + console.log(" 13. ProxyAdmin: %s", proxyAdmin); + console.log(" 14. SystemOwnerSafe: %s", systemOwnerSafe); + console.log(" 15. SuperchainConfigProxy: %s", superchainConfigProxy); + } + + /// @notice Prints a summary of the contracts deployed during this script. + function printDeploymentSummary() internal view { + console.log("Deployment Summary (chainid: %d)", block.chainid); + console.log(" 0. DisputeGameFactoryProxy: %s", mustGetAddress("DisputeGameFactoryProxy")); + console.log(" 1. AnchorStateRegistryProxy: %s", mustGetAddress("AnchorStateRegistryProxy")); + console.log(" 2. AnchorStateRegistryImpl: %s", mustGetAddress("AnchorStateRegistry")); + console.log(" 3. PermissionedDelayedWETHProxy: %s", mustGetAddress("PermissionedDelayedWETHProxy")); + console.log( + " 4. PermissionedDisputeGame: %s", + address( + IDisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy")).gameImpls(GameTypes.PERMISSIONED_CANNON) + ) + ); + } +} diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/Dockerfile b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/Dockerfile new file mode 100644 index 000000000000..7089a8766952 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/Dockerfile @@ -0,0 +1,59 @@ +# Use a base image with necessary tools +FROM ubuntu:20.04 + +ARG REV +ARG RELEASE=op-contracts/v$REV + +# Install required packages +RUN apt-get update && apt-get install -y \ + git \ + bash \ + curl \ + build-essential \ + jq \ + && rm -rf /var/lib/apt/lists/* + +# Install Rust +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +# Install just +RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin + +# Install foundryup +RUN curl -L https://foundry.paradigm.xyz | bash +ENV PATH="/root/.foundry/bin:${PATH}" + +# Set the working directory +WORKDIR /app + +# Clone the repository at the target branch +RUN git clone -b $RELEASE https://github.com/ethereum-optimism/optimism.git . + +# Set the working directory to the root of the monorepo +WORKDIR /app + +# Install correct foundry version +RUN just update-foundry + +# Set the working directory to the root of the contracts package +WORKDIR /app/packages/contracts-bedrock + +# Install dependencies +RUN just install + +# Build the contracts package +RUN just prebuild forge-build + +COPY . ./scripts/upgrades/v1.8.0-permissioned + +# Deliberately run the upgrade script with invalid args to trigger a build +RUN forge script ./scripts/upgrades/v1.8.0-permissioned/DeployUpgrade.s.sol || true + +# Set the working directory to where upgrade.sh is located +WORKDIR /app/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned + +ENV CONTRACTS_VERSION=$REV + +# Set the entrypoint to the main.sh script +ENTRYPOINT ["./scripts/main.sh"] diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/README.md b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/README.md new file mode 100644 index 000000000000..6d429483f6c8 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/README.md @@ -0,0 +1,15 @@ +# v1.8.0 L2OO to Permissioned Fault Proofs Upgrader + +This directory contains a repeatable task for upgrading a chain running `op-contracts/v1.8.0` in L2OO configuration +to `op-contracts/v1.8.0` in permissioned fault proofs configuration. + +## Dependencies + +- [`docker`](https://docs.docker.com/engine/install/) +- [`just`](https://github.com/casey/just) +- [`foundry`](https://getfoundry.sh/) + +## Usage + +For full instructions, including the required preparation and infrastructure changes to perform this upgrade, refer to +the [full runbook](https://github.com/ethereum-optimism/superchain-ops/blob/a8a3f2a1aeda8d916081be3f4e2a8526286faa4d/runbooks/1.8.0-l2oo-to-permissioned-fps.md). diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/justfile b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/justfile new file mode 100644 index 000000000000..05e73aed8357 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/justfile @@ -0,0 +1,30 @@ +# Default recipe to list help menu. +default: + @just --list + +# Run the deployment / upgrade generation image. If the image is not present locally, +# it will be built. +run deploy-config-path deployment-path output-folder-path="$(pwd)/output/" *args='': + #!/bin/bash + if [ ! "$(docker images -q op-v1.8.0-permissioned:local 2> /dev/null)" ]; then + just build-image + fi + + mkdir -p {{output-folder-path}} + + # Run the deployment. + docker run -it \ + --rm \ + -v $(realpath {{output-folder-path}}):/outputs \ + -v $(realpath {{deploy-config-path}}):/deploy_config.json \ + -v $(realpath {{deployment-path}}):/deployment.json \ + --env-file=.env \ + op-v1.8.0-permissioned:local /deploy_config.json /deployment.json {{args}} + +# Build the image locally. +build-image contracts-version="1.8.0": + docker build \ + -t op-v1.8.0-permissioned:local \ + -f Dockerfile \ + --build-arg REV={{contracts-version}} \ + . diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/bundle.sh b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/bundle.sh new file mode 100755 index 000000000000..7817c63cf926 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/bundle.sh @@ -0,0 +1,615 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Grab the script directory +SCRIPT_DIR=$(dirname "$0") + +# Load common.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/common.sh" + +# Check required environment variables +reqenv "NETWORK" +reqenv "STORAGE_SETTER" +reqenv "DISPUTE_GAME_FACTORY_PROXY" +reqenv "DEPLOYMENTS_JSON_PATH" + +# Load addresses from deployments json +PROXY_ADMIN=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "ProxyAdmin") +OPTIMISM_PORTAL_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "OptimismPortalProxy") +SYSTEM_CONFIG_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "SystemConfigProxy") +L1_CROSS_DOMAIN_MESSENGER_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1CrossDomainMessengerProxy" "Proxy__OVM_L1CrossDomainMessenger") +L1_STANDARD_BRIDGE_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1StandardBridgeProxy" "Proxy__OVM_L1StandardBridge") +L1_ERC721_BRIDGE_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1ERC721BridgeProxy") +OPTIMISM_MINTABLE_ERC20_FACTORY_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "OptimismMintableERC20FactoryProxy") + +# Fetch addresses from standard address toml +SYSTEM_CONFIG_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "system_config") +OPTIMISM_PORTAL_2_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_portal") +L1_CROSS_DOMAIN_MESSENGER_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_cross_domain_messenger") +L1_STANDARD_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_standard_bridge") +L1_ERC721_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_erc721_bridge") +OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_mintable_erc20_factory") + +# Fetch SuperchainConfigProxy address +SUPERCHAIN_CONFIG_PROXY=$(fetch_superchain_config_address "$NETWORK") + +# Generate JSON +cat << EOF +{ + "version": "1.0", + "chainId": "11155111", + "createdAt": $(date +%s%3N), + "meta": { + "name": "Transactions Batch", + "description": "", + "txBuilderVersion": "1.17.0", + "createdFromSafeAddress": "", + "createdFromOwnerAddress": "" + }, + "transactions": [ + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$OPTIMISM_PORTAL_PROXY", + "_implementation": "$STORAGE_SETTER" + } + }, + { + "to": "$OPTIMISM_PORTAL_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$OPTIMISM_PORTAL_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000032", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$OPTIMISM_PORTAL_PROXY", + "_implementation": "$OPTIMISM_PORTAL_2_IMPL" + } + }, + { + "to": "$OPTIMISM_PORTAL_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_disputeGameFactory", + "type": "address", + "internalType": "contract DisputeGameFactory" + }, + { + "name": "_systemConfig", + "type": "address", + "internalType": "contract SystemConfig" + }, + { + "name": "_superchainConfig", + "type": "address", + "internalType": "contract SuperchainConfig" + }, + { + "name": "_initialRespectedGameType", + "type": "uint32", + "internalType": "GameType" + } + ], + "name": "initialize", + "payable": false + }, + "contractInputsValues": { + "_disputeGameFactory": "$DISPUTE_GAME_FACTORY_PROXY", + "_systemConfig": "$SYSTEM_CONFIG_PROXY", + "_superchainConfig": "$SUPERCHAIN_CONFIG_PROXY", + "_initialRespectedGameType": "1" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$SYSTEM_CONFIG_PROXY", + "_implementation": "$STORAGE_SETTER" + } + }, + { + "to": "$SYSTEM_CONFIG_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0xe52a667f71ec761b9b381c7b76ca9b852adf7e8905da0e0ad49986a0a6871815", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$SYSTEM_CONFIG_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], + "name": "setAddress", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x52322a25d9f59ea17656545543306b7aef62bc0cc53a0e65ccfa0c75b97aa906", + "_address": "$DISPUTE_GAME_FACTORY_PROXY" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$SYSTEM_CONFIG_PROXY", + "_implementation": "$SYSTEM_CONFIG_IMPL" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "_implementation": "$STORAGE_SETTER" + } + }, + { + "to": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "_implementation": "$L1_CROSS_DOMAIN_MESSENGER_IMPL" + } + }, + { + "to": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract OptimismPortal", + "name": "_portal", + "type": "address" + } + ], + "name": "initialize", + "payable": false + }, + "contractInputsValues": { + "_superchainConfig": "$SUPERCHAIN_CONFIG_PROXY", + "_portal": "$OPTIMISM_PORTAL_PROXY" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_STANDARD_BRIDGE_PROXY", + "_implementation": "$STORAGE_SETTER" + } + }, + { + "to": "$L1_STANDARD_BRIDGE_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_STANDARD_BRIDGE_PROXY", + "_implementation": "$L1_STANDARD_BRIDGE_IMPL" + } + }, + { + "to": "$L1_STANDARD_BRIDGE_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "internalType": "contract CrossDomainMessenger", + "name": "_messenger", + "type": "address" + }, + { + "internalType": "contract SuperchainConfig", + "name": "_superchainConfig", + "type": "address" + } + ], + "name": "initialize", + "payable": false + }, + "contractInputsValues": { + "_messenger": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "_superchainConfig": "$SUPERCHAIN_CONFIG_PROXY" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_ERC721_BRIDGE_PROXY", + "_implementation": "$STORAGE_SETTER" + } + }, + { + "to": "$L1_ERC721_BRIDGE_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_slot", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_value", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "name": "setBytes32", + "payable": false + }, + "contractInputsValues": { + "_slot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "_value": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$L1_ERC721_BRIDGE_PROXY", + "_implementation": "$L1_ERC721_BRIDGE_IMPL" + } + }, + { + "to": "$L1_ERC721_BRIDGE_PROXY", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_messenger", + "type": "address", + "internalType": "contract CrossDomainMessenger" + }, + { + "name": "_superchainConfig", + "type": "address", + "internalType": "contract SuperchainConfig" + } + ], + "name": "initialize", + "payable": false + }, + "contractInputsValues": { + "_messenger": "$L1_CROSS_DOMAIN_MESSENGER_PROXY", + "_superchainConfig": "$SUPERCHAIN_CONFIG_PROXY" + } + }, + { + "to": "$PROXY_ADMIN", + "value": "0", + "data": null, + "contractMethod": { + "inputs": [ + { + "name": "_proxy", + "type": "address", + "internalType": "address payable" + }, + { + "name": "_implementation", + "type": "address", + "internalType": "address" + } + ], + "name": "upgrade", + "payable": false + }, + "contractInputsValues": { + "_proxy": "$OPTIMISM_MINTABLE_ERC20_FACTORY_PROXY", + "_implementation": "$OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL" + } + } + ] +} +EOF diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/common.sh b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/common.sh new file mode 100755 index 000000000000..9f705c48a0d7 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/common.sh @@ -0,0 +1,232 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Associative array to store cached TOML content for different URLs +# Used by fetch_standard_address and fetch_superchain_config_address +declare -A CACHED_TOML_CONTENT + +# error_handler +# +# Basic error handler +error_handler() { + echo "Error occurred in ${BASH_SOURCE[1]} at line: ${BASH_LINENO[0]}" 1>&2 + echo "Error message: $BASH_COMMAND" 1>&2 + exit 1 +} + +# Register the error handler +trap error_handler ERR + +# reqenv +# +# Checks if a specified environment variable is set. +# +# Arguments: +# $1 - The name of the environment variable to check +# +# Exits with status 1 if: +# - The specified environment variable is not set +reqenv() { + if [ -z "${!1}" ]; then + echo "Error: $1 is not set" 1>&2 + exit 1 + fi +} + +# load_local_address +# +# Loads an address from a deployments JSON file. +# +# Arguments: +# $1 - Path to the deployments JSON file +# $2 - Name of the address to load from the JSON file +# $3 - Alternative name of the address to load from the JSON file (optional) +# +# Returns: +# The address associated with the specified name +# +# Exits with status 1 if: +# - The deployments JSON file is not found +# - The specified address name is not found in the JSON file +load_local_address() { + local deployments_json_path="$1" + local address_name="$2" + local alt_name="${3:-}" + + if [ ! -f "$deployments_json_path" ]; then + echo "Error: Deployments JSON file not found: $deployments_json_path" 1>&2 + exit 1 + fi + + local address + address=$(jq -r ".$address_name" "$deployments_json_path") + + if [ -z "$address" ] || [ "$address" == "null" ]; then + if [ -n "$alt_name" ]; then + address=$(jq -r ".$alt_name" "$deployments_json_path") + fi + + if [ -z "$address" ] || [ "$address" == "null" ]; then + echo "Error: $address_name not found in deployments JSON" 1>&2 + exit 1 + fi + fi + + echo "$address" +} + +# fetch_standard_address +# +# Fetches the implementation address for a given contract from a TOML file. +# The TOML file is downloaded from a URL specified in ADDRESSES_TOML_URL +# environment variable. Results are cached to avoid repeated downloads. +# +# Arguments: +# $1 - Network name +# $2 - The release version +# $3 - The name of the contract to look up +# +# Returns: +# The implementation address of the specified contract +# +# Exits with status 1 if: +# - Failed to fetch the TOML file +# - The release version is not found in the TOML file +# - The implementation address for the specified contract is not found +fetch_standard_address() { + local network_name="$1" + local release_version="$2" + local contract_name="$3" + + # Determine the correct toml url + local toml_url="https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/a54d203e727f1cdeff06b883e239febf92b4b1d6/validation/standard/standard-versions" + if [ "$network_name" = "mainnet" ]; then + toml_url="$toml_url.toml" + elif [ "$network_name" = "sepolia" ]; then + toml_url="$toml_url-sepolia.toml" + else + echo "Error: NETWORK must be set to 'mainnet' or 'sepolia'" 1>&2 + exit 1 + fi + + # Fetch the TOML file content from the URL if not already cached for this URL + if [ -z "${CACHED_TOML_CONTENT[$toml_url]:-}" ]; then + CACHED_TOML_CONTENT[$toml_url]=$(curl -s "$toml_url") + # shellcheck disable=SC2181 + if [ $? -ne 0 ]; then + echo "Error: Failed to fetch TOML file from $toml_url" 1>&2 + exit 1 + fi + fi + + # Use the cached content for the current URL + local toml_content + toml_content="${CACHED_TOML_CONTENT[$toml_url]}" + + # Find the section for required contracts release + local section_content + section_content=$(echo "$toml_content" | awk -v version="$release_version" ' + $0 ~ "^\\[releases.\"op-contracts/v" version "\"\\]" { + flag=1; + next + } + flag && /^\[/ { + exit + } + flag { + print + } + ') + if [ -z "$section_content" ]; then + echo "Error: v$release_version release section not found in addresses TOML" 1>&2 + exit 1 + fi + + # Extract the implementation address for the specified contract + local address + address=$(echo "$section_content" | grep -oP "${contract_name} *= *\{.*(address|implementation_address) *= *\"\K[^\"]+") || true + + # Error if not found + if [ -z "$address" ]; then + echo "Error: Implementation address for $contract_name not found in v$release_version release" 1>&2 + exit 1 + fi + + # Return the address + echo "$address" +} + +# fetch_superchain_config_address +# +# Fetches the superchain config address from a TOML file. +# The TOML file is downloaded from a URL based on the network name. +# Results are cached to avoid repeated downloads. +# +# Arguments: +# $1 - Network name +# +# Returns: +# The superchain config address +# +# Exits with status 1 if: +# - Failed to fetch the TOML file +# - The superchain_config_addr is not found in the TOML file +fetch_superchain_config_address() { + local network_name="$1" + + # Determine the correct toml url + local toml_url="https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/a54d203e727f1cdeff06b883e239febf92b4b1d6/superchain/configs/$network_name/superchain.toml" + + # Fetch the TOML file content from the URL if not already cached for this URL + if [ -z "${CACHED_TOML_CONTENT[$toml_url]:-}" ]; then + CACHED_TOML_CONTENT[$toml_url]=$(curl -s "$toml_url") + # shellcheck disable=SC2181 + if [ $? -ne 0 ]; then + echo "Error: Failed to fetch TOML file from $toml_url" 1>&2 + exit 1 + fi + fi + + # Extract the superchain_config_addr from the TOML content + local superchain_config_addr + superchain_config_addr=$(echo "${CACHED_TOML_CONTENT[$toml_url]}" | grep "superchain_config_addr" | awk -F '"' '{print $2}') + + # Error if not found + if [ -z "$superchain_config_addr" ]; then + echo "Error: superchain_config_addr not found in the TOML file" 1>&2 + exit 1 + fi + + # Return the address + echo "$superchain_config_addr" +} + +# pad_to_n_bytes +# +# Pads an input string to n bytes, removing the '0x' prefix if it exists. +# +# Arguments: +# $1 - The input string (e.g., an Ethereum address) +# $2 - The number of bytes to pad to +# +# Returns: +# The input string left-padded with zeros to the specified number of bytes +pad_to_n_bytes() { + local input_string="$1" + local num_bytes="$2" + + # Remove '0x' prefix if it exists + if [[ "$input_string" == 0x* ]]; then + input_string="${input_string:2}" + fi + + # Calculate the total length in hex characters (2 hex characters per byte) + local total_length=$((num_bytes * 2)) + + # Left-pad the input string with zeros to the specified length + local padded_string + padded_string=$(printf "%0${total_length}s" "$input_string" | tr ' ' '0') + + # Add '0x' prefix to the padded string + echo "0x$padded_string" +} diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/deploy.sh b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/deploy.sh new file mode 100755 index 000000000000..aedee5455624 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/deploy.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Grab the script directory +SCRIPT_DIR=$(dirname "$0") + +# Load common.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/common.sh" + +# Check required environment variables +reqenv "ETH_RPC_URL" +reqenv "PRIVATE_KEY" +reqenv "ETHERSCAN_API_KEY" +reqenv "DEPLOY_CONFIG_PATH" +reqenv "DEPLOYMENTS_JSON_PATH" +reqenv "NETWORK" +reqenv "IMPL_SALT" +reqenv "SYSTEM_OWNER_SAFE" + +# Load addresses from deployments json +PROXY_ADMIN=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "ProxyAdmin") + +# Fetch addresses from standard address toml +DISPUTE_GAME_FACTORY_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "dispute_game_factory") +DELAYED_WETH_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "delayed_weth") +PREIMAGE_ORACLE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "preimage_oracle") +MIPS_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "mips") +OPTIMISM_PORTAL_2_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_portal") + +case "${NETWORK}" in + mainnet) + ANCHOR_STATE_REGISTRY_BLUEPRINT="0xbA7Be2bEE016568274a4D1E6c852Bb9a99FaAB8B" + ;; + sepolia) + ANCHOR_STATE_REGISTRY_BLUEPRINT="0xB98095199437883b7661E0D58256060f3bc730a4" + ;; + *) + echo "No AnchorStateRegistry blueprint for $NETWORK" + exit 1 + ;; +esac + +# Fetch the SuperchainConfigProxy address +SUPERCHAIN_CONFIG_PROXY=$(fetch_superchain_config_address "$NETWORK") + +# Run the upgrade script +forge script DeployUpgrade.s.sol \ + --rpc-url "$ETH_RPC_URL" \ + --private-key "$PRIVATE_KEY" \ + --etherscan-api-key "$ETHERSCAN_API_KEY" \ + --sig "deploy(address,address,address,address,address,address,address,address,address)" \ + "$PROXY_ADMIN" \ + "$SYSTEM_OWNER_SAFE" \ + "$SUPERCHAIN_CONFIG_PROXY" \ + "$DISPUTE_GAME_FACTORY_IMPL" \ + "$DELAYED_WETH_IMPL" \ + "$PREIMAGE_ORACLE_IMPL" \ + "$MIPS_IMPL" \ + "$OPTIMISM_PORTAL_2_IMPL" \ + "$ANCHOR_STATE_REGISTRY_BLUEPRINT" \ + --broadcast \ + --slow \ + --verify diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/main.sh b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/main.sh new file mode 100755 index 000000000000..133325fe5dc3 --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/main.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Grab the script directory +SCRIPT_DIR=$(dirname "$0") + +# Load common.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/common.sh" + +reqenv CONTRACTS_VERSION +echo "Using contracts version ${CONTRACTS_VERSION}" + +# Check if both input files are provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Set variables from environment or generate them +export NETWORK=${NETWORK:?NETWORK must be set} +export ETHERSCAN_API_KEY=${ETHERSCAN_API_KEY:?ETHERSCAN_API_KEY must be set} +export ETH_RPC_URL=${ETH_RPC_URL:?ETH_RPC_URL must be set} +export PRIVATE_KEY=${PRIVATE_KEY:?PRIVATE_KEY must be set} + +# Set IMPL_SALT to a random value +IMPL_SALT=$(openssl rand -hex 16) +export IMPL_SALT + +# Check that network is either "mainnet" or "sepolia" +if [ "$NETWORK" != "mainnet" ] && [ "$NETWORK" != "sepolia" ]; then + echo "Error: NETWORK must be either 'mainnet' or 'sepolia'" + exit 1 +fi + +# Find the contracts-bedrock directory +CONTRACTS_BEDROCK_DIR=$(pwd) +while [[ "$CONTRACTS_BEDROCK_DIR" != "/" && "${CONTRACTS_BEDROCK_DIR##*/}" != "contracts-bedrock" ]]; do + CONTRACTS_BEDROCK_DIR=$(dirname "$CONTRACTS_BEDROCK_DIR") +done + +# Error out if we couldn't find it for some reason +if [[ "$CONTRACTS_BEDROCK_DIR" == "/" ]]; then + echo "Error: 'contracts-bedrock' directory not found" + exit 1 +fi + +# Set file paths from command-line arguments +export DEPLOY_CONFIG_PATH="$CONTRACTS_BEDROCK_DIR/deploy-config/deploy-config.json" +export DEPLOYMENTS_JSON_PATH="$CONTRACTS_BEDROCK_DIR/deployments/deployments.json" + +# Copy the files into the paths so that the script can actually access it +cp "$1" "$DEPLOY_CONFIG_PATH" +cp "$2" "$DEPLOYMENTS_JSON_PATH" + +# Set the StorageSetter address +export STORAGE_SETTER=0xd81f43edbcacb4c29a9ba38a13ee5d79278270cc + +# Get the SystemOwnerSafe address from the ProxyAdmin +PROXY_ADMIN=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "ProxyAdmin") +SYSTEM_OWNER_SAFE=$(cast call "$PROXY_ADMIN" "owner()" | cast parse-bytes32-address) +export PROXY_ADMIN +export SYSTEM_OWNER_SAFE + +# Run deploy.sh +if ! "$SCRIPT_DIR/deploy.sh" | tee deploy.log; then + echo "Error: deploy.sh failed" + exit 1 +fi + +# Extract the address of the DisputeGameFactoryProxy from the deploy.log +DISPUTE_GAME_FACTORY_PROXY=$(grep "0. DisputeGameFactoryProxy:" deploy.log | awk '{print $3}') +export DISPUTE_GAME_FACTORY_PROXY +ANCHOR_STATE_REGISTRY_PROXY=$(grep "1. AnchorStateRegistryProxy:" deploy.log | awk '{print $3}') +export ANCHOR_STATE_REGISTRY_PROXY +ANCHOR_STATE_REGISTRY_IMPL=$(grep "2. AnchorStateRegistryImpl:" deploy.log | awk '{print $3}') +export ANCHOR_STATE_REGISTRY_IMPL +PERMISSIONED_DELAYED_WETH_PROXY=$(grep "3. PermissionedDelayedWETHProxy:" deploy.log | awk '{print $3}') +export PERMISSIONED_DELAYED_WETH_PROXY +PERMISSIONED_DISPUTE_GAME=$(grep "4. PermissionedDisputeGame:" deploy.log | awk '{print $3}') +export PERMISSIONED_DISPUTE_GAME + +# Make sure everything was extracted properly +reqenv "DISPUTE_GAME_FACTORY_PROXY" +reqenv "ANCHOR_STATE_REGISTRY_PROXY" +reqenv "ANCHOR_STATE_REGISTRY_IMPL" +reqenv "PERMISSIONED_DELAYED_WETH_PROXY" +reqenv "PERMISSIONED_DISPUTE_GAME" + +# Generate deployments.json with extracted addresses +cat << EOF > "deployments.json" +{ + "DisputeGameFactoryProxy": "$DISPUTE_GAME_FACTORY_PROXY", + "AnchorStateRegistryProxy": "$ANCHOR_STATE_REGISTRY_PROXY", + "AnchorStateRegistryImpl": "$ANCHOR_STATE_REGISTRY_IMPL", + "PermissionedDelayedWETHProxy": "$PERMISSIONED_DELAYED_WETH_PROXY", + "PermissionedDisputeGame": "$PERMISSIONED_DISPUTE_GAME" +} +EOF + +# Extract the path to the transactions json file +TRANSACTIONS_JSON_PATH=$(grep "Transactions saved to:" deploy.log | awk -F': ' '{print $2}') +export TRANSACTIONS_JSON_PATH + +# Verify that the path was extracted successfully +reqenv "TRANSACTIONS_JSON_PATH" + +# Run bundle.sh +if ! "$SCRIPT_DIR/bundle.sh" > bundle.json; then + echo "Error: bundle.sh failed" 1>&2 + exit 1 +fi + +# Run verify.sh +if ! "$SCRIPT_DIR/verify.sh" > validation.txt; then + echo "Error: verify.sh failed" 1>&2 + exit 1 +fi + +# Grab the various standard implementation addresses +SYSTEM_CONFIG_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "system_config") +OPTIMISM_PORTAL_2_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_portal") +L1_CROSS_DOMAIN_MESSENGER_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_cross_domain_messenger") +L1_STANDARD_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_standard_bridge") +L1_ERC721_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_erc721_bridge") +OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_mintable_erc20_factory") +MIPS_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "mips") + +# Generate standard-addresses.json +cat << EOF > "standard-addresses.json" +{ + "SystemConfigImpl": "$SYSTEM_CONFIG_IMPL", + "OptimismPortal2Impl": "$OPTIMISM_PORTAL_2_IMPL", + "L1CrossDomainMessengerImpl": "$L1_CROSS_DOMAIN_MESSENGER_IMPL", + "L1StandardBridgeImpl": "$L1_STANDARD_BRIDGE_IMPL", + "L1ERC721BridgeImpl": "$L1_ERC721_BRIDGE_IMPL", + "OptimismMintableERC20FactoryImpl": "$OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL", + "MIPS": "$MIPS_IMPL" +} +EOF + +# Copy results into output directory +cp deploy.log /outputs/deploy.log +cp bundle.json /outputs/bundle.json +cp validation.txt /outputs/validation.txt +cp deployments.json /outputs/deployments.json +cp standard-addresses.json /outputs/standard-addresses.json +cp "$TRANSACTIONS_JSON_PATH" /outputs/transactions.json diff --git a/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/verify.sh b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/verify.sh new file mode 100755 index 000000000000..f0314d4a615c --- /dev/null +++ b/packages/contracts-bedrock/scripts/upgrades/v1.8.0-permissioned/scripts/verify.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Grab the script directory +SCRIPT_DIR=$(dirname "$0") + +# Load common.sh +# shellcheck disable=SC1091 +source "$SCRIPT_DIR/common.sh" + +# Check required environment variables +reqenv "NETWORK" +reqenv "ETH_RPC_URL" +reqenv "DISPUTE_GAME_FACTORY_PROXY" +reqenv "DEPLOYMENTS_JSON_PATH" +reqenv "SYSTEM_OWNER_SAFE" + +# Load addresses from deployments json +L1_STANDARD_BRIDGE_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1StandardBridgeProxy" "Proxy__OVM_L1StandardBridge") +L1_CROSS_DOMAIN_MESSENGER_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1CrossDomainMessengerProxy" "Proxy__OVM_L1CrossDomainMessenger") +L1_ERC721_BRIDGE_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "L1ERC721BridgeProxy") +OPTIMISM_PORTAL_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "OptimismPortalProxy") +SYSTEM_CONFIG_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "SystemConfigProxy") +OPTIMISM_MINTABLE_ERC20_FACTORY_PROXY=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "OptimismMintableERC20FactoryProxy") +ADDRESS_MANAGER=$(load_local_address "$DEPLOYMENTS_JSON_PATH" "AddressManager") + +# Fetch addresses from standard address toml +L1_STANDARD_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_standard_bridge") +L1_CROSS_DOMAIN_MESSENGER_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_cross_domain_messenger") +L1_ERC721_BRIDGE_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "l1_erc721_bridge") +OPTIMISM_PORTAL_2_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_portal") +OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL=$(fetch_standard_address "$NETWORK" "$CONTRACTS_VERSION" "optimism_mintable_erc20_factory") + +# Fetch SuperchainConfigProxy address +SUPERCHAIN_CONFIG_PROXY=$(fetch_superchain_config_address "$NETWORK") + +# Generate verification text +cat << EOF +**L1StandardBridgeProxy ($L1_STANDARD_BRIDGE_PROXY)** + +- Key: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc + - Value: $(pad_to_n_bytes "$L1_STANDARD_BRIDGE_IMPL" 32) + - Description: Implementation address changed to $L1_STANDARD_BRIDGE_IMPL + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address +- Key: 0x0000000000000000000000000000000000000000000000000000000000000032 + - Value: $(pad_to_n_bytes "$SUPERCHAIN_CONFIG_PROXY" 32) + - Description: SuperchainConfig address variable set to shared contract address + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address + +**AddressManager ($ADDRESS_MANAGER)** + +- Key: 0x515216935740e67dfdda5cf8e248ea32b3277787818ab59153061ac875c9385e + - Value: $(pad_to_n_bytes "$L1_CROSS_DOMAIN_MESSENGER_IMPL" 32) + - Description: L1CrossDomainMessenger address changed to $L1_CROSS_DOMAIN_MESSENGER_IMPL + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address + +**L1CrossDomainMessengerProxy ($L1_CROSS_DOMAIN_MESSENGER_PROXY)** + +- Key: 0x00000000000000000000000000000000000000000000000000000000000000FB + - Value: $(pad_to_n_bytes "$SUPERCHAIN_CONFIG_PROXY" 32) + - Description: SuperchainConfig address variable set to shared contract address + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address + +**L1ERC721BridgeProxy ($L1_ERC721_BRIDGE_PROXY)** + +- Key: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc + - Value: $(pad_to_n_bytes "$L1_ERC721_BRIDGE_IMPL" 32) + - Description: Implementation address changed to $L1_ERC721_BRIDGE_IMPL + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address +- Key: 0x0000000000000000000000000000000000000000000000000000000000000032 + - Value: $(pad_to_n_bytes "$SUPERCHAIN_CONFIG_PROXY" 32) + - Description: SuperchainConfig address variable set to shared contract address + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address + +**SystemConfigProxy ($SYSTEM_CONFIG_PROXY)** + +- Key: 0x52322a25d9f59ea17656545543306b7aef62bc0cc53a0e65ccfa0c75b97aa906 + - Value: $(pad_to_n_bytes "$DISPUTE_GAME_FACTORY_PROXY" 32) + - Description: Slot at keccak(systemconfig.disputegamefactory)-1 set to address of DisputeGameFactoryProxy deployed via upgrade script +- Key: 0xe52a667f71ec761b9b381c7b76ca9b852adf7e8905da0e0ad49986a0a6871815 + - Value: 0x0000000000000000000000000000000000000000000000000000000000000000 + - Description: Slot at keccak(systemconfig.l2outputoracle)-1 deleted + +**OptimismPortalProxy ($OPTIMISM_PORTAL_PROXY)** + +- Key: 0x0000000000000000000000000000000000000000000000000000000000000035 + - Value: $(pad_to_n_bytes "$SUPERCHAIN_CONFIG_PROXY" 31)00 + - Description: SuperchainConfig address variable set to shared contract address + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address +- Key: 0x0000000000000000000000000000000000000000000000000000000000000038 + - Value: $(pad_to_n_bytes "$DISPUTE_GAME_FACTORY_PROXY" 32) + - Description: DisputeGameFactory address variable set to the address deployed in upgrade script +- Key: 0x000000000000000000000000000000000000000000000000000000000000003b + - Value: 0x00000000000000000000000000000000000000000000000TIMESTAMP00000001 + - Description: Sets the respectedGameType to 1 (permissioned game) and sets the respectedGameTypeUpdatedAt timestamp to the time when the upgrade transaction was executed (will be a dynamic value) +- Key: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc + - Value: $(pad_to_n_bytes "$OPTIMISM_PORTAL_2_IMPL" 32) + - Description: Implementation address changed to $OPTIMISM_PORTAL_2_IMPL + +**SystemOwnerSafe ($SYSTEM_OWNER_SAFE)** + +- Key: 0x0000000000000000000000000000000000000000000000000000000000000005 + - Value: increment + - Description: Nonce bumped by 1 in the SystemOwnerSafe + +**OptimismMintableERC20FactoryProxy ($OPTIMISM_MINTABLE_ERC20_FACTORY_PROXY)** + +- Key: 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc + - Value: $(pad_to_n_bytes "$OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL" 32) + - Description: Implementation address changed to $OPTIMISM_MINTABLE_ERC20_FACTORY_IMPL + - WARNING: ⚠️ You MAY not see this change if you are already using the correct address +EOF