From de31478bdfcd6ec39bf86e9b0ba200e092d0c2f1 Mon Sep 17 00:00:00 2001 From: Matt Solomon Date: Tue, 10 Sep 2024 10:19:58 -0700 Subject: [PATCH] OPSM: Begin implementing OP Stack Manager code and it's deployment (#11623) * begin supporting specifying versions in OPSM * add deploy logic * deploy OPSM along with implementations * scaffold opsm interface between scripts * fixes * mvp of DeployOPChain * start working on an e2e opsm test, currently reverts * fix tests * test cleanup * rename opsmSingleton to opsm * chore: remove unused imports * refactor: switch from 'new' to blueprints, 50% code size reduction * fix semgrep * feat: add OPSM interop tests * test: add missing specs * add DisputeGameFactory deployment * chore: update snapshots * fix opsm interop support * chore: update snapshots * Update packages/contracts-bedrock/test/DeployOPChain.t.sol * fix: add L1StandardBridge setter and initialization * chore: small clarification of deploy flow * Update packages/contracts-bedrock/scripts/DeployImplementations.s.sol Co-authored-by: Blaine Malone * chore: add todos * fix: change bytes32 to string * rename addrs to opChainAddrs for clarity * test: fix assertion string numbering * chore: update semver lock: --------- Co-authored-by: Blaine Malone --- .../scripts/DeployImplementations.s.sol | 325 ++++++++++- .../scripts/DeployOPChain.s.sol | 69 ++- .../scripts/libraries/Solarray.sol | 34 +- packages/contracts-bedrock/semver-lock.json | 4 +- .../snapshots/abi/OPStackManager.json | 435 ++++++++++++++- .../snapshots/abi/OPStackManagerInterop.json | 512 ++++++++++++++++++ .../storageLayout/OPStackManager.json | 31 +- .../storageLayout/OPStackManagerInterop.json | 30 + .../src/L1/OPStackManager.sol | 508 ++++++++++++++++- .../src/L1/OPStackManagerInterop.sol | 58 ++ .../test/DeployImplementations.t.sol | 65 ++- .../test/DeployOPChain.t.sol | 164 +++++- .../test/L1/OPStackManager.t.sol | 81 ++- packages/contracts-bedrock/test/Specs.t.sol | 19 +- 14 files changed, 2225 insertions(+), 110 deletions(-) create mode 100644 packages/contracts-bedrock/snapshots/abi/OPStackManagerInterop.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPStackManagerInterop.json create mode 100644 packages/contracts-bedrock/src/L1/OPStackManagerInterop.sol diff --git a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/DeployImplementations.s.sol index 1cf246622837..3de9f3285ccc 100644 --- a/packages/contracts-bedrock/scripts/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/DeployImplementations.s.sol @@ -3,11 +3,21 @@ pragma solidity 0.8.15; import { Script } from "forge-std/Script.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; +import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { AddressManager } from "src/legacy/AddressManager.sol"; + import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol"; import { MIPS } from "src/cannon/MIPS.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; +import { OPStackManager } from "src/L1/OPStackManager.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; @@ -15,6 +25,12 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; +import { OPStackManagerInterop } from "src/L1/OPStackManagerInterop.sol"; +import { OptimismPortalInterop } from "src/L1/OptimismPortalInterop.sol"; +import { SystemConfigInterop } from "src/L1/SystemConfigInterop.sol"; + +import { Blueprint } from "src/libraries/Blueprint.sol"; + import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; @@ -26,6 +42,11 @@ contract DeployImplementationsInput { uint256 challengePeriodSeconds; uint256 proofMaturityDelaySeconds; uint256 disputeGameFinalityDelaySeconds; + // We also deploy OP Stack Manager here, which has a dependency on the prior step of deploying + // the superchain contracts. + string release; // The release version to set OPSM implementations for, of the format `op-contracts/vX.Y.Z`. + SuperchainConfig superchainConfigProxy; + ProtocolVersions protocolVersionsProxy; } bool public inputSet = false; @@ -81,12 +102,28 @@ contract DeployImplementationsInput { assertInputSet(); return inputs.disputeGameFinalityDelaySeconds; } + + function release() public view returns (string memory) { + assertInputSet(); + return inputs.release; + } + + function superchainConfigProxy() public view returns (SuperchainConfig) { + assertInputSet(); + return inputs.superchainConfigProxy; + } + + function protocolVersionsProxy() public view returns (ProtocolVersions) { + assertInputSet(); + return inputs.protocolVersionsProxy; + } } contract DeployImplementationsOutput { struct Output { + OPStackManager opsm; DelayedWETH delayedWETHImpl; - OptimismPortal2 optimismPortal2Impl; + OptimismPortal2 optimismPortalImpl; PreimageOracle preimageOracleSingleton; MIPS mipsSingleton; SystemConfig systemConfigImpl; @@ -94,13 +131,15 @@ contract DeployImplementationsOutput { L1ERC721Bridge l1ERC721BridgeImpl; L1StandardBridge l1StandardBridgeImpl; OptimismMintableERC20Factory optimismMintableERC20FactoryImpl; + DisputeGameFactory disputeGameFactoryImpl; } Output internal outputs; function set(bytes4 sel, address _addr) public { // forgefmt: disable-start - if (sel == this.optimismPortal2Impl.selector) outputs.optimismPortal2Impl = OptimismPortal2(payable(_addr)); + if (sel == this.opsm.selector) outputs.opsm = OPStackManager(payable(_addr)); + else if (sel == this.optimismPortalImpl.selector) outputs.optimismPortalImpl = OptimismPortal2(payable(_addr)); else if (sel == this.delayedWETHImpl.selector) outputs.delayedWETHImpl = DelayedWETH(payable(_addr)); else if (sel == this.preimageOracleSingleton.selector) outputs.preimageOracleSingleton = PreimageOracle(_addr); else if (sel == this.mipsSingleton.selector) outputs.mipsSingleton = MIPS(_addr); @@ -109,6 +148,7 @@ contract DeployImplementationsOutput { else if (sel == this.l1ERC721BridgeImpl.selector) outputs.l1ERC721BridgeImpl = L1ERC721Bridge(_addr); else if (sel == this.l1StandardBridgeImpl.selector) outputs.l1StandardBridgeImpl = L1StandardBridge(payable(_addr)); else if (sel == this.optimismMintableERC20FactoryImpl.selector) outputs.optimismMintableERC20FactoryImpl = OptimismMintableERC20Factory(_addr); + else if (sel == this.disputeGameFactoryImpl.selector) outputs.disputeGameFactoryImpl = DisputeGameFactory(_addr); else revert("DeployImplementationsOutput: unknown selector"); // forgefmt: disable-end } @@ -124,7 +164,8 @@ contract DeployImplementationsOutput { function checkOutput() public view { address[] memory addrs = Solarray.addresses( - address(outputs.optimismPortal2Impl), + address(outputs.opsm), + address(outputs.optimismPortalImpl), address(outputs.delayedWETHImpl), address(outputs.preimageOracleSingleton), address(outputs.mipsSingleton), @@ -132,14 +173,20 @@ contract DeployImplementationsOutput { address(outputs.l1CrossDomainMessengerImpl), address(outputs.l1ERC721BridgeImpl), address(outputs.l1StandardBridgeImpl), - address(outputs.optimismMintableERC20FactoryImpl) + address(outputs.optimismMintableERC20FactoryImpl), + address(outputs.disputeGameFactoryImpl) ); DeployUtils.assertValidContractAddresses(addrs); } - function optimismPortal2Impl() public view returns (OptimismPortal2) { - DeployUtils.assertValidContractAddress(address(outputs.optimismPortal2Impl)); - return outputs.optimismPortal2Impl; + function opsm() public view returns (OPStackManager) { + DeployUtils.assertValidContractAddress(address(outputs.opsm)); + return outputs.opsm; + } + + function optimismPortalImpl() public view returns (OptimismPortal2) { + DeployUtils.assertValidContractAddress(address(outputs.optimismPortalImpl)); + return outputs.optimismPortalImpl; } function delayedWETHImpl() public view returns (DelayedWETH) { @@ -181,6 +228,11 @@ contract DeployImplementationsOutput { DeployUtils.assertValidContractAddress(address(outputs.optimismMintableERC20FactoryImpl)); return outputs.optimismMintableERC20FactoryImpl; } + + function disputeGameFactoryImpl() public view returns (DisputeGameFactory) { + DeployUtils.assertValidContractAddress(address(outputs.disputeGameFactoryImpl)); + return outputs.disputeGameFactoryImpl; + } } contract DeployImplementations is Script { @@ -208,6 +260,7 @@ contract DeployImplementations is Script { function run(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public { require(_dsi.inputSet(), "DeployImplementations: input not set"); + // Deploy the implementations. deploySystemConfigImpl(_dsi, _dso); deployL1CrossDomainMessengerImpl(_dsi, _dso); deployL1ERC721BridgeImpl(_dsi, _dso); @@ -217,13 +270,110 @@ contract DeployImplementations is Script { deployDelayedWETHImpl(_dsi, _dso); deployPreimageOracleSingleton(_dsi, _dso); deployMipsSingleton(_dsi, _dso); + deployDisputeGameFactoryImpl(_dsi, _dso); + + // Deploy the OP Stack Manager with the new implementations set. + deployOPStackManager(_dsi, _dso); _dso.checkOutput(); } // -------- Deployment Steps -------- - function deploySystemConfigImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public { + // --- OP Stack Manager --- + + function opsmSystemConfigSetter( + DeployImplementationsInput, + DeployImplementationsOutput _dso + ) + internal + view + virtual + returns (OPStackManager.ImplementationSetter memory) + { + return OPStackManager.ImplementationSetter({ + name: "SystemConfig", + info: OPStackManager.Implementation(address(_dso.systemConfigImpl()), SystemConfig.initialize.selector) + }); + } + + function createOPSMContract( + DeployImplementationsInput _dsi, + DeployImplementationsOutput, + OPStackManager.Blueprints memory blueprints + ) + internal + virtual + returns (OPStackManager opsm_) + { + SuperchainConfig superchainConfigProxy = _dsi.superchainConfigProxy(); + ProtocolVersions protocolVersionsProxy = _dsi.protocolVersionsProxy(); + + vm.broadcast(msg.sender); + opsm_ = new OPStackManager({ + _superchainConfig: superchainConfigProxy, + _protocolVersions: protocolVersionsProxy, + _blueprints: blueprints + }); + } + + function deployOPStackManager(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public virtual { + string memory release = _dsi.release(); + + // First we deploy the blueprints for the singletons deployed by OPSM. + // forgefmt: disable-start + bytes32 salt = bytes32(0); + OPStackManager.Blueprints memory blueprints; + + vm.startBroadcast(msg.sender); + blueprints.addressManager = deployBytecode(Blueprint.blueprintDeployerBytecode(type(AddressManager).creationCode), salt); + blueprints.proxy = deployBytecode(Blueprint.blueprintDeployerBytecode(type(Proxy).creationCode), salt); + blueprints.proxyAdmin = deployBytecode(Blueprint.blueprintDeployerBytecode(type(ProxyAdmin).creationCode), salt); + blueprints.l1ChugSplashProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(type(L1ChugSplashProxy).creationCode), salt); + blueprints.resolvedDelegateProxy = deployBytecode(Blueprint.blueprintDeployerBytecode(type(ResolvedDelegateProxy).creationCode), salt); + vm.stopBroadcast(); + // forgefmt: disable-end + + // This call contains a broadcast to deploy OPSM. + OPStackManager opsm = createOPSMContract(_dsi, _dso, blueprints); + + OPStackManager.ImplementationSetter[] memory setters = new OPStackManager.ImplementationSetter[](6); + setters[0] = OPStackManager.ImplementationSetter({ + name: "L1ERC721Bridge", + info: OPStackManager.Implementation(address(_dso.l1ERC721BridgeImpl()), L1ERC721Bridge.initialize.selector) + }); + setters[1] = OPStackManager.ImplementationSetter({ + name: "OptimismPortal", + info: OPStackManager.Implementation(address(_dso.optimismPortalImpl()), OptimismPortal2.initialize.selector) + }); + setters[2] = opsmSystemConfigSetter(_dsi, _dso); + setters[3] = OPStackManager.ImplementationSetter({ + name: "OptimismMintableERC20Factory", + info: OPStackManager.Implementation( + address(_dso.optimismMintableERC20FactoryImpl()), OptimismMintableERC20Factory.initialize.selector + ) + }); + setters[4] = OPStackManager.ImplementationSetter({ + name: "L1CrossDomainMessenger", + info: OPStackManager.Implementation( + address(_dso.l1CrossDomainMessengerImpl()), L1CrossDomainMessenger.initialize.selector + ) + }); + setters[5] = OPStackManager.ImplementationSetter({ + name: "L1StandardBridge", + info: OPStackManager.Implementation(address(_dso.l1StandardBridgeImpl()), L1StandardBridge.initialize.selector) + }); + + vm.broadcast(msg.sender); + opsm.setRelease({ _release: release, _isLatest: true, _setters: setters }); + + vm.label(address(opsm), "OPStackManager"); + _dso.set(_dso.opsm.selector, address(opsm)); + } + + // --- Core Contracts --- + + function deploySystemConfigImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public virtual { vm.broadcast(msg.sender); SystemConfig systemConfigImpl = new SystemConfig(); @@ -231,7 +381,13 @@ contract DeployImplementations is Script { _dso.set(_dso.systemConfigImpl.selector, address(systemConfigImpl)); } - function deployL1CrossDomainMessengerImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public { + function deployL1CrossDomainMessengerImpl( + DeployImplementationsInput, + DeployImplementationsOutput _dso + ) + public + virtual + { vm.broadcast(msg.sender); L1CrossDomainMessenger l1CrossDomainMessengerImpl = new L1CrossDomainMessenger(); @@ -239,7 +395,7 @@ contract DeployImplementations is Script { _dso.set(_dso.l1CrossDomainMessengerImpl.selector, address(l1CrossDomainMessengerImpl)); } - function deployL1ERC721BridgeImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public { + function deployL1ERC721BridgeImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public virtual { vm.broadcast(msg.sender); L1ERC721Bridge l1ERC721BridgeImpl = new L1ERC721Bridge(); @@ -247,7 +403,7 @@ contract DeployImplementations is Script { _dso.set(_dso.l1ERC721BridgeImpl.selector, address(l1ERC721BridgeImpl)); } - function deployL1StandardBridgeImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public { + function deployL1StandardBridgeImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public virtual { vm.broadcast(msg.sender); L1StandardBridge l1StandardBridgeImpl = new L1StandardBridge(); @@ -260,6 +416,7 @@ contract DeployImplementations is Script { DeployImplementationsOutput _dso ) public + virtual { vm.broadcast(msg.sender); OptimismMintableERC20Factory optimismMintableERC20FactoryImpl = new OptimismMintableERC20Factory(); @@ -268,6 +425,8 @@ contract DeployImplementations is Script { _dso.set(_dso.optimismMintableERC20FactoryImpl.selector, address(optimismMintableERC20FactoryImpl)); } + // --- Fault Proofs Contracts --- + // The fault proofs contracts are configured as follows: // - DisputeGameFactory: Proxied, bespoke per chain. // - AnchorStateRegistry: Proxied, bespoke per chain. @@ -288,21 +447,27 @@ contract DeployImplementations is Script { // - PreimageOracle (singleton) // - MIPS (singleton) - function deployOptimismPortalImpl(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public { + function deployOptimismPortalImpl( + DeployImplementationsInput _dsi, + DeployImplementationsOutput _dso + ) + public + virtual + { uint256 proofMaturityDelaySeconds = _dsi.proofMaturityDelaySeconds(); uint256 disputeGameFinalityDelaySeconds = _dsi.disputeGameFinalityDelaySeconds(); vm.broadcast(msg.sender); - OptimismPortal2 optimismPortal2Impl = new OptimismPortal2({ + OptimismPortal2 optimismPortalImpl = new OptimismPortal2({ _proofMaturityDelaySeconds: proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds: disputeGameFinalityDelaySeconds }); - vm.label(address(optimismPortal2Impl), "OptimismPortal2Impl"); - _dso.set(_dso.optimismPortal2Impl.selector, address(optimismPortal2Impl)); + vm.label(address(optimismPortalImpl), "OptimismPortalImpl"); + _dso.set(_dso.optimismPortalImpl.selector, address(optimismPortalImpl)); } - function deployDelayedWETHImpl(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public { + function deployDelayedWETHImpl(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public virtual { uint256 withdrawalDelaySeconds = _dsi.withdrawalDelaySeconds(); vm.broadcast(msg.sender); @@ -312,7 +477,13 @@ contract DeployImplementations is Script { _dso.set(_dso.delayedWETHImpl.selector, address(delayedWETHImpl)); } - function deployPreimageOracleSingleton(DeployImplementationsInput _dsi, DeployImplementationsOutput _dso) public { + function deployPreimageOracleSingleton( + DeployImplementationsInput _dsi, + DeployImplementationsOutput _dso + ) + public + virtual + { uint256 minProposalSizeBytes = _dsi.minProposalSizeBytes(); uint256 challengePeriodSeconds = _dsi.challengePeriodSeconds(); @@ -324,7 +495,7 @@ contract DeployImplementations is Script { _dso.set(_dso.preimageOracleSingleton.selector, address(preimageOracleSingleton)); } - function deployMipsSingleton(DeployImplementationsInput, DeployImplementationsOutput _dso) public { + function deployMipsSingleton(DeployImplementationsInput, DeployImplementationsOutput _dso) public virtual { IPreimageOracle preimageOracle = IPreimageOracle(_dso.preimageOracleSingleton()); vm.broadcast(msg.sender); @@ -334,6 +505,20 @@ contract DeployImplementations is Script { _dso.set(_dso.mipsSingleton.selector, address(mipsSingleton)); } + function deployDisputeGameFactoryImpl( + DeployImplementationsInput, + DeployImplementationsOutput _dso + ) + public + virtual + { + vm.broadcast(msg.sender); + DisputeGameFactory disputeGameFactoryImpl = new DisputeGameFactory(); + + vm.label(address(disputeGameFactoryImpl), "DisputeGameFactoryImpl"); + _dso.set(_dso.disputeGameFactoryImpl.selector, address(disputeGameFactoryImpl)); + } + // -------- Utilities -------- function etchIOContracts() internal returns (DeployImplementationsInput dsi_, DeployImplementationsOutput dso_) { @@ -346,4 +531,108 @@ contract DeployImplementations is Script { dsi_ = DeployImplementationsInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployImplementationsInput")); dso_ = DeployImplementationsOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployImplementationsOutput")); } + + function deployBytecode(bytes memory _bytecode, bytes32 _salt) public returns (address newContract_) { + assembly ("memory-safe") { + newContract_ := create2(0, add(_bytecode, 0x20), mload(_bytecode), _salt) + } + require(newContract_ != address(0), "DeployImplementations: create2 failed"); + } +} + +// Similar to how DeploySuperchain.s.sol contains a lot of comments to thoroughly document the script +// architecture, this comment block documents how to update the deploy scripts to support new features. +// +// Using the base scripts and contracts (DeploySuperchain, DeployImplementations, DeployOPChain, and +// the corresponding OPStackManager) deploys a standard chain. For nonstandard and in-development +// features we need to modify some or all of those contracts, and we do that via inheritance. Using +// interop as an example, they've made the following changes to L1 contracts: +// - `OptimismPortalInterop is OptimismPortal`: A different portal implementation is used, and +// it's ABI is the same. +// - `SystemConfigInterop is SystemConfig`: A different system config implementation is used, and +// it's initializer has a different signature. This signature is different because there is a +// new input parameter, the `dependencyManager`. +// - Because of the different system config initializer, there is a new input parameter (dependencyManager). +// +// Similar to how inheritance was used to develop the new portal and system config contracts, we use +// inheritance to modify up to all of the deployer contracts. For this interop example, what this +// means is we need: +// - An `OPStackManagerInterop is OPStackManager` that knows how to encode the calldata for the +// new system config initializer. +// - A `DeployImplementationsInterop is DeployImplementations` that: +// - Deploys OptimismPortalInterop instead of OptimismPortal. +// - Deploys SystemConfigInterop instead of SystemConfig. +// - Deploys OPStackManagerInterop instead of OPStackManager, which contains the updated logic +// for encoding the SystemConfig initializer. +// - Updates the OPSM release setter logic to use the updated initializer. +// - A `DeployOPChainInterop is DeployOPChain` that allows the updated input parameter to be passed. +// +// Most of the complexity in the above flow comes from the the new input for the updated SystemConfig +// initializer. If all function signatures were the same, all we'd have to change is the contract +// implementations that are deployed then set in the OPSM. For now, to simplify things until we +// resolve https://github.com/ethereum-optimism/optimism/issues/11783, we just assume this new role +// is the same as the proxy admin owner. +contract DeployImplementationsInterop is DeployImplementations { + function createOPSMContract( + DeployImplementationsInput _dsi, + DeployImplementationsOutput, + OPStackManager.Blueprints memory blueprints + ) + internal + override + returns (OPStackManager opsm_) + { + SuperchainConfig superchainConfigProxy = _dsi.superchainConfigProxy(); + ProtocolVersions protocolVersionsProxy = _dsi.protocolVersionsProxy(); + + vm.broadcast(msg.sender); + opsm_ = new OPStackManagerInterop({ + _superchainConfig: superchainConfigProxy, + _protocolVersions: protocolVersionsProxy, + _blueprints: blueprints + }); + } + + function deployOptimismPortalImpl( + DeployImplementationsInput _dsi, + DeployImplementationsOutput _dso + ) + public + override + { + uint256 proofMaturityDelaySeconds = _dsi.proofMaturityDelaySeconds(); + uint256 disputeGameFinalityDelaySeconds = _dsi.disputeGameFinalityDelaySeconds(); + + vm.broadcast(msg.sender); + OptimismPortalInterop optimismPortalImpl = new OptimismPortalInterop({ + _proofMaturityDelaySeconds: proofMaturityDelaySeconds, + _disputeGameFinalityDelaySeconds: disputeGameFinalityDelaySeconds + }); + + vm.label(address(optimismPortalImpl), "OptimismPortalImpl"); + _dso.set(_dso.optimismPortalImpl.selector, address(optimismPortalImpl)); + } + + function deploySystemConfigImpl(DeployImplementationsInput, DeployImplementationsOutput _dso) public override { + vm.broadcast(msg.sender); + SystemConfigInterop systemConfigImpl = new SystemConfigInterop(); + + vm.label(address(systemConfigImpl), "systemConfigImpl"); + _dso.set(_dso.systemConfigImpl.selector, address(systemConfigImpl)); + } + + function opsmSystemConfigSetter( + DeployImplementationsInput, + DeployImplementationsOutput _dso + ) + internal + view + override + returns (OPStackManager.ImplementationSetter memory) + { + return OPStackManager.ImplementationSetter({ + name: "SystemConfig", + info: OPStackManager.Implementation(address(_dso.systemConfigImpl()), SystemConfigInterop.initialize.selector) + }); + } } diff --git a/packages/contracts-bedrock/scripts/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/DeployOPChain.s.sol index e42bd5df324d..17693f010a38 100644 --- a/packages/contracts-bedrock/scripts/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/DeployOPChain.s.sol @@ -15,6 +15,7 @@ import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; +import { OPStackManager } from "src/L1/OPStackManager.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; @@ -38,6 +39,7 @@ contract DeployOPChainInput { uint32 basefeeScalar; uint32 blobBaseFeeScalar; uint256 l2ChainId; + OPStackManager opsm; } bool public inputSet = false; @@ -59,6 +61,8 @@ contract DeployOPChainInput { require(_input.roles.unsafeBlockSigner != address(0), "DeployOPChainInput: null unsafeBlockSigner"); require(_input.roles.proposer != address(0), "DeployOPChainInput: null proposer"); require(_input.roles.challenger != address(0), "DeployOPChainInput: null challenger"); + require(_input.l2ChainId != 0 && _input.l2ChainId != block.chainid, "DeployOPChainInput: invalid l2ChainId"); + require(address(_input.opsm) != address(0), "DeployOPChainInput: null opsm"); inputSet = true; inputs = _input; @@ -117,6 +121,11 @@ contract DeployOPChainInput { assertInputSet(); return inputs.l2ChainId; } + + function opsm() public view returns (OPStackManager) { + assertInputSet(); + return inputs.opsm; + } } contract DeployOPChainOutput { @@ -298,10 +307,66 @@ contract DeployOPChain is Script { return dso.output(); } - function run(DeployOPChainInput _dsi, DeployOPChainOutput _dso) public view { + function run(DeployOPChainInput _dsi, DeployOPChainOutput _dso) public { require(_dsi.inputSet(), "DeployOPChain: input not set"); - // TODO call OP Stack Manager deploy method + OPStackManager opsm = _dsi.opsm(); + + OPStackManager.Roles memory roles = OPStackManager.Roles({ + opChainProxyAdminOwner: _dsi.opChainProxyAdminOwner(), + systemConfigOwner: _dsi.systemConfigOwner(), + batcher: _dsi.batcher(), + unsafeBlockSigner: _dsi.unsafeBlockSigner(), + proposer: _dsi.proposer(), + challenger: _dsi.challenger() + }); + OPStackManager.DeployInput memory deployInput = OPStackManager.DeployInput({ + roles: roles, + basefeeScalar: _dsi.basefeeScalar(), + blobBasefeeScalar: _dsi.blobBaseFeeScalar(), + l2ChainId: _dsi.l2ChainId() + }); + + vm.broadcast(msg.sender); + OPStackManager.DeployOutput memory deployOutput = opsm.deploy(deployInput); + + vm.label(address(deployOutput.opChainProxyAdmin), "opChainProxyAdmin"); + vm.label(address(deployOutput.addressManager), "addressManager"); + vm.label(address(deployOutput.l1ERC721BridgeProxy), "l1ERC721BridgeProxy"); + vm.label(address(deployOutput.systemConfigProxy), "systemConfigProxy"); + vm.label(address(deployOutput.optimismMintableERC20FactoryProxy), "optimismMintableERC20FactoryProxy"); + vm.label(address(deployOutput.l1StandardBridgeProxy), "l1StandardBridgeProxy"); + vm.label(address(deployOutput.l1CrossDomainMessengerProxy), "l1CrossDomainMessengerProxy"); + vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy"); + vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy"); + vm.label(address(deployOutput.disputeGameFactoryImpl), "disputeGameFactoryImpl"); + vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy"); + vm.label(address(deployOutput.anchorStateRegistryImpl), "anchorStateRegistryImpl"); + vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame"); + vm.label(address(deployOutput.permissionedDisputeGame), "permissionedDisputeGame"); + vm.label(address(deployOutput.delayedWETHPermissionedGameProxy), "delayedWETHPermissionedGameProxy"); + vm.label(address(deployOutput.delayedWETHPermissionlessGameProxy), "delayedWETHPermissionlessGameProxy"); + + _dso.set(_dso.opChainProxyAdmin.selector, address(deployOutput.opChainProxyAdmin)); + _dso.set(_dso.addressManager.selector, address(deployOutput.addressManager)); + _dso.set(_dso.l1ERC721BridgeProxy.selector, address(deployOutput.l1ERC721BridgeProxy)); + _dso.set(_dso.systemConfigProxy.selector, address(deployOutput.systemConfigProxy)); + _dso.set( + _dso.optimismMintableERC20FactoryProxy.selector, address(deployOutput.optimismMintableERC20FactoryProxy) + ); + _dso.set(_dso.l1StandardBridgeProxy.selector, address(deployOutput.l1StandardBridgeProxy)); + _dso.set(_dso.l1CrossDomainMessengerProxy.selector, address(deployOutput.l1CrossDomainMessengerProxy)); + _dso.set(_dso.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy)); + _dso.set(_dso.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy)); + _dso.set(_dso.disputeGameFactoryImpl.selector, address(deployOutput.disputeGameFactoryImpl)); + _dso.set(_dso.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy)); + _dso.set(_dso.anchorStateRegistryImpl.selector, address(deployOutput.anchorStateRegistryImpl)); + _dso.set(_dso.faultDisputeGame.selector, address(deployOutput.faultDisputeGame)); + _dso.set(_dso.permissionedDisputeGame.selector, address(deployOutput.permissionedDisputeGame)); + _dso.set(_dso.delayedWETHPermissionedGameProxy.selector, address(deployOutput.delayedWETHPermissionedGameProxy)); + _dso.set( + _dso.delayedWETHPermissionlessGameProxy.selector, address(deployOutput.delayedWETHPermissionlessGameProxy) + ); _dso.checkOutput(); } diff --git a/packages/contracts-bedrock/scripts/libraries/Solarray.sol b/packages/contracts-bedrock/scripts/libraries/Solarray.sol index d6049c5654f3..57ef9b320bb0 100644 --- a/packages/contracts-bedrock/scripts/libraries/Solarray.sol +++ b/packages/contracts-bedrock/scripts/libraries/Solarray.sol @@ -7,7 +7,7 @@ pragma solidity ^0.8.13; // since Solidity does not have great array UX. // // This library was generated using the `generator.py` script from the linked repo with the length -// set to 10, and then everything except the `addresses` functions was removed. +// set accordingly, and then everything except the `addresses` functions was removed. library Solarray { function addresses(address a) internal pure returns (address[] memory) { address[] memory arr = new address[](1); @@ -189,6 +189,38 @@ library Solarray { return arr; } + function addresses( + address a, + address b, + address c, + address d, + address e, + address f, + address g, + address h, + address i, + address j, + address k + ) + internal + pure + returns (address[] memory) + { + address[] memory arr = new address[](11); + arr[0] = a; + arr[1] = b; + arr[2] = c; + arr[3] = d; + arr[4] = e; + arr[5] = f; + arr[6] = g; + arr[7] = h; + arr[8] = i; + arr[9] = j; + arr[10] = k; + return arr; + } + function extend(address[] memory arr1, address[] memory arr2) internal pure returns (address[] memory newArr) { uint256 length1 = arr1.length; uint256 length2 = arr2.length; diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 96db34d38ed1..8ee179e7bd7c 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -32,8 +32,8 @@ "sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649" }, "src/L1/OPStackManager.sol": { - "initCodeHash": "0x67bf02405bf1ca7d78c4215c350ad9c5c7b4cece35d9fab837f279d65f995c5d", - "sourceCodeHash": "0x8e272e707e383d516b8f1cce0ea29ff46a0eb448c8386fa146e6a43f3100042a" + "initCodeHash": "0xe1eab75651e3d81ad20ca01b1e7d373b25d716ee5f8841a56e56b4531a6e0e70", + "sourceCodeHash": "0x5182a2678dadb200dd255ecdfa395e5f7b1e1e27288e78ddf8802ab51ed2dd81" }, "src/L1/OptimismPortal.sol": { "initCodeHash": "0x6bf59539298b20221de6c51db21016be8d3278bdbe0be1cdd49638dc828e003e", diff --git a/packages/contracts-bedrock/snapshots/abi/OPStackManager.json b/packages/contracts-bedrock/snapshots/abi/OPStackManager.json index 3f3a48ded1ff..8180e2799c96 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPStackManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPStackManager.json @@ -2,63 +2,374 @@ { "inputs": [ { - "internalType": "uint256", - "name": "_l2ChainId", - "type": "uint256" - }, - { - "internalType": "uint32", - "name": "_basefeeScalar", - "type": "uint32" + "internalType": "contract SuperchainConfig", + "name": "_superchainConfig", + "type": "address" }, { - "internalType": "uint32", - "name": "_blobBasefeeScalar", - "type": "uint32" + "internalType": "contract ProtocolVersions", + "name": "_protocolVersions", + "type": "address" }, { "components": [ { "internalType": "address", - "name": "proxyAdminOwner", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", "type": "address" }, { "internalType": "address", - "name": "systemConfigOwner", + "name": "resolvedDelegateProxy", + "type": "address" + } + ], + "internalType": "struct OPStackManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", "type": "address" }, { "internalType": "address", - "name": "batcher", + "name": "proxy", "type": "address" }, { "internalType": "address", - "name": "unsafeBlockSigner", + "name": "proxyAdmin", "type": "address" }, { "internalType": "address", - "name": "proposer", + "name": "l1ChugSplashProxy", "type": "address" }, { "internalType": "address", - "name": "challenger", + "name": "resolvedDelegateProxy", "type": "address" } ], - "internalType": "struct OPStackManager.Roles", - "name": "_roles", + "internalType": "struct OPStackManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "opChainProxyAdminOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "batcher", + "type": "address" + }, + { + "internalType": "address", + "name": "unsafeBlockSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct OPStackManager.Roles", + "name": "roles", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blobBasefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + } + ], + "internalType": "struct OPStackManager.DeployInput", + "name": "_input", "type": "tuple" } ], "name": "deploy", + "outputs": [ + { + "components": [ + { + "internalType": "contract ProxyAdmin", + "name": "opChainProxyAdmin", + "type": "address" + }, + { + "internalType": "contract AddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract L1ERC721Bridge", + "name": "l1ERC721BridgeProxy", + "type": "address" + }, + { + "internalType": "contract SystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract OptimismMintableERC20Factory", + "name": "optimismMintableERC20FactoryProxy", + "type": "address" + }, + { + "internalType": "contract L1StandardBridge", + "name": "l1StandardBridgeProxy", + "type": "address" + }, + { + "internalType": "contract L1CrossDomainMessenger", + "name": "l1CrossDomainMessengerProxy", + "type": "address" + }, + { + "internalType": "contract OptimismPortal2", + "name": "optimismPortalProxy", + "type": "address" + }, + { + "internalType": "contract DisputeGameFactory", + "name": "disputeGameFactoryProxy", + "type": "address" + }, + { + "internalType": "contract DisputeGameFactory", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "contract AnchorStateRegistry", + "name": "anchorStateRegistryProxy", + "type": "address" + }, + { + "internalType": "contract AnchorStateRegistry", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "contract FaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + }, + { + "internalType": "contract PermissionedDisputeGame", + "name": "permissionedDisputeGame", + "type": "address" + }, + { + "internalType": "contract DelayedWETH", + "name": "delayedWETHPermissionedGameProxy", + "type": "address" + }, + { + "internalType": "contract DelayedWETH", + "name": "delayedWETHPermissionlessGameProxy", + "type": "address" + } + ], + "internalType": "struct OPStackManager.DeployOutput", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "implementations", + "outputs": [ + { + "internalType": "address", + "name": "logic", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "initializer", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRelease", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolVersions", + "outputs": [ + { + "internalType": "contract ProtocolVersions", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_release", + "type": "string" + }, + { + "internalType": "bool", + "name": "_isLatest", + "type": "bool" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "logic", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "initializer", + "type": "bytes4" + } + ], + "internalType": "struct OPStackManager.Implementation", + "name": "info", + "type": "tuple" + } + ], + "internalType": "struct OPStackManager.ImplementationSetter[]", + "name": "_setters", + "type": "tuple[]" + } + ], + "name": "setRelease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "systemConfigs", "outputs": [ { "internalType": "contract SystemConfig", - "name": "systemConfig_", + "name": "", "type": "address" } ], @@ -97,25 +408,105 @@ "name": "Deployed", "type": "event" }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, { "inputs": [ { "internalType": "string", - "name": "reason", + "name": "addressName", "type": "string" } ], + "name": "AddressMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyReleased", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], "name": "DeploymentFailed", "type": "error" }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, { "inputs": [], "name": "InvalidChainId", "type": "error" }, + { + "inputs": [ + { + "internalType": "string", + "name": "role", + "type": "string" + } + ], + "name": "InvalidRoleAddress", + "type": "error" + }, { "inputs": [], - "name": "NotImplemented", + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPStackManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPStackManagerInterop.json new file mode 100644 index 000000000000..8180e2799c96 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPStackManagerInterop.json @@ -0,0 +1,512 @@ +[ + { + "inputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract ProtocolVersions", + "name": "_protocolVersions", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + } + ], + "internalType": "struct OPStackManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + } + ], + "internalType": "struct OPStackManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "opChainProxyAdminOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "batcher", + "type": "address" + }, + { + "internalType": "address", + "name": "unsafeBlockSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct OPStackManager.Roles", + "name": "roles", + "type": "tuple" + }, + { + "internalType": "uint32", + "name": "basefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blobBasefeeScalar", + "type": "uint32" + }, + { + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + } + ], + "internalType": "struct OPStackManager.DeployInput", + "name": "_input", + "type": "tuple" + } + ], + "name": "deploy", + "outputs": [ + { + "components": [ + { + "internalType": "contract ProxyAdmin", + "name": "opChainProxyAdmin", + "type": "address" + }, + { + "internalType": "contract AddressManager", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "contract L1ERC721Bridge", + "name": "l1ERC721BridgeProxy", + "type": "address" + }, + { + "internalType": "contract SystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract OptimismMintableERC20Factory", + "name": "optimismMintableERC20FactoryProxy", + "type": "address" + }, + { + "internalType": "contract L1StandardBridge", + "name": "l1StandardBridgeProxy", + "type": "address" + }, + { + "internalType": "contract L1CrossDomainMessenger", + "name": "l1CrossDomainMessengerProxy", + "type": "address" + }, + { + "internalType": "contract OptimismPortal2", + "name": "optimismPortalProxy", + "type": "address" + }, + { + "internalType": "contract DisputeGameFactory", + "name": "disputeGameFactoryProxy", + "type": "address" + }, + { + "internalType": "contract DisputeGameFactory", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "contract AnchorStateRegistry", + "name": "anchorStateRegistryProxy", + "type": "address" + }, + { + "internalType": "contract AnchorStateRegistry", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "contract FaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + }, + { + "internalType": "contract PermissionedDisputeGame", + "name": "permissionedDisputeGame", + "type": "address" + }, + { + "internalType": "contract DelayedWETH", + "name": "delayedWETHPermissionedGameProxy", + "type": "address" + }, + { + "internalType": "contract DelayedWETH", + "name": "delayedWETHPermissionlessGameProxy", + "type": "address" + } + ], + "internalType": "struct OPStackManager.DeployOutput", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "implementations", + "outputs": [ + { + "internalType": "address", + "name": "logic", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "initializer", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestRelease", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolVersions", + "outputs": [ + { + "internalType": "contract ProtocolVersions", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_release", + "type": "string" + }, + { + "internalType": "bool", + "name": "_isLatest", + "type": "bool" + }, + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "components": [ + { + "internalType": "address", + "name": "logic", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "initializer", + "type": "bytes4" + } + ], + "internalType": "struct OPStackManager.Implementation", + "name": "info", + "type": "tuple" + } + ], + "internalType": "struct OPStackManager.ImplementationSetter[]", + "name": "_setters", + "type": "tuple[]" + } + ], + "name": "setRelease", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract SuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "systemConfigs", + "outputs": [ + { + "internalType": "contract SystemConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "contract SystemConfig", + "name": "systemConfig", + "type": "address" + } + ], + "name": "Deployed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "addressName", + "type": "string" + } + ], + "name": "AddressMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyReleased", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidChainId", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "role", + "type": "string" + } + ], + "name": "InvalidRoleAddress", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPStackManager.json b/packages/contracts-bedrock/snapshots/storageLayout/OPStackManager.json index 0637a088a01e..3ef385443c3d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPStackManager.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPStackManager.json @@ -1 +1,30 @@ -[] \ No newline at end of file +[ + { + "bytes": "160", + "label": "blueprint", + "offset": 0, + "slot": "0", + "type": "struct OPStackManager.Blueprints" + }, + { + "bytes": "32", + "label": "latestRelease", + "offset": 0, + "slot": "5", + "type": "string" + }, + { + "bytes": "32", + "label": "implementations", + "offset": 0, + "slot": "6", + "type": "mapping(string => mapping(string => struct OPStackManager.Implementation))" + }, + { + "bytes": "32", + "label": "systemConfigs", + "offset": 0, + "slot": "7", + "type": "mapping(uint256 => contract SystemConfig)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPStackManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPStackManagerInterop.json new file mode 100644 index 000000000000..3ef385443c3d --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPStackManagerInterop.json @@ -0,0 +1,30 @@ +[ + { + "bytes": "160", + "label": "blueprint", + "offset": 0, + "slot": "0", + "type": "struct OPStackManager.Blueprints" + }, + { + "bytes": "32", + "label": "latestRelease", + "offset": 0, + "slot": "5", + "type": "string" + }, + { + "bytes": "32", + "label": "implementations", + "offset": 0, + "slot": "6", + "type": "mapping(string => mapping(string => struct OPStackManager.Implementation))" + }, + { + "bytes": "32", + "label": "systemConfigs", + "offset": 0, + "slot": "7", + "type": "mapping(uint256 => contract SystemConfig)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPStackManager.sol b/packages/contracts-bedrock/src/L1/OPStackManager.sol index bd798aa530b9..5c96cd6aeb8b 100644 --- a/packages/contracts-bedrock/src/L1/OPStackManager.sol +++ b/packages/contracts-bedrock/src/L1/OPStackManager.sol @@ -1,17 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; +import { Blueprint } from "src/libraries/Blueprint.sol"; + import { ISemver } from "src/universal/interfaces/ISemver.sol"; +import { Proxy } from "src/universal/Proxy.sol"; +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; + +import { L1ChugSplashProxy } from "src/legacy/L1ChugSplashProxy.sol"; +import { ResolvedDelegateProxy } from "src/legacy/ResolvedDelegateProxy.sol"; +import { AddressManager } from "src/legacy/AddressManager.sol"; + +import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; +import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; +import { GameTypes } from "src/dispute/lib/Types.sol"; + +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; +import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; +import { ResourceMetering } from "src/L1/ResourceMetering.sol"; +import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; +import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; +import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; +import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; /// @custom:proxied true contract OPStackManager is ISemver { - /// @custom:semver 1.0.0-beta.2 - string public constant version = "1.0.0-beta.2"; + // -------- Structs -------- /// @notice Represents the roles that can be set when deploying a standard OP Stack chain. struct Roles { - address proxyAdminOwner; + address opChainProxyAdminOwner; address systemConfigOwner; address batcher; address unsafeBlockSigner; @@ -19,39 +42,292 @@ contract OPStackManager is ISemver { address challenger; } + /// @notice The full set of inputs to deploy a new OP Stack chain. + struct DeployInput { + Roles roles; + uint32 basefeeScalar; + uint32 blobBasefeeScalar; + uint256 l2ChainId; + } + + /// @notice The full set of outputs from deploying a new OP Stack chain. + struct DeployOutput { + ProxyAdmin opChainProxyAdmin; + AddressManager addressManager; + L1ERC721Bridge l1ERC721BridgeProxy; + SystemConfig systemConfigProxy; + OptimismMintableERC20Factory optimismMintableERC20FactoryProxy; + L1StandardBridge l1StandardBridgeProxy; + L1CrossDomainMessenger l1CrossDomainMessengerProxy; + // Fault proof contracts below. + OptimismPortal2 optimismPortalProxy; + DisputeGameFactory disputeGameFactoryProxy; + DisputeGameFactory disputeGameFactoryImpl; + AnchorStateRegistry anchorStateRegistryProxy; + AnchorStateRegistry anchorStateRegistryImpl; + FaultDisputeGame faultDisputeGame; + PermissionedDisputeGame permissionedDisputeGame; + DelayedWETH delayedWETHPermissionedGameProxy; + DelayedWETH delayedWETHPermissionlessGameProxy; + } + + /// @notice The logic address and initializer selector for an implementation contract. + struct Implementation { + address logic; // Address containing the deployed logic contract. + bytes4 initializer; // Function selector for the initializer. + } + + /// @notice Used to set the implementation for a contract by mapping a contract + /// name to the implementation data. + struct ImplementationSetter { + string name; // Contract name. + Implementation info; // Implementation to set. + } + + /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size + /// contracts, to reduce the code size of this factory contract. If it deployed full contracts + /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would + /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to + /// reduce the code size of this contract. + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + } + + // -------- Constants and Variables -------- + + /// @custom:semver 1.0.0-beta.3 + string public constant version = "1.0.0-beta.3"; + + /// @notice Address of the SuperchainConfig contract shared by all chains. + SuperchainConfig public immutable superchainConfig; + + /// @notice Address of the ProtocolVersions contract shared by all chains. + ProtocolVersions public immutable protocolVersions; + + /// @notice Addresses of the Blueprint contracts. + /// This is internal because if public the autogenerated getter method would return a tuple of + /// addresses, but we want it to return a struct. + Blueprints internal blueprint; + + /// @notice The latest release of the OP Stack Manager, as a string of the format `op-contracts/vX.Y.Z`. + string public latestRelease; + + /// @notice Maps a release version to a contract name to it's implementation data. + mapping(string => mapping(string => Implementation)) public implementations; + + /// @notice Maps an L2 Chain ID to the SystemConfig for that chain. + mapping(uint256 => SystemConfig) public systemConfigs; + + // -------- Events -------- + /// @notice Emitted when a new OP Stack chain is deployed. /// @param l2ChainId The chain ID of the new chain. /// @param systemConfig The address of the new chain's SystemConfig contract. event Deployed(uint256 indexed l2ChainId, SystemConfig indexed systemConfig); + // -------- Errors -------- + + /// @notice Throw when two addresses do not match but are expected to. + error AddressMismatch(string addressName); + + /// @notice Thrown when an address is the zero address. + error AddressNotFound(address who); + + /// @notice Throw when a contract address has no code. + error AddressHasNoCode(address who); + + /// @notice Thrown when a release version is already set. + error AlreadyReleased(); + /// @notice Thrown when an invalid `l2ChainId` is provided to `deploy`. error InvalidChainId(); - /// @notice Thrown when a deployment fails. - error DeploymentFailed(string reason); + /// @notice Thrown when a role's address is not valid. + error InvalidRoleAddress(string role); - /// @notice Temporary error since the deploy method is not yet implemented. - error NotImplemented(); + // -------- Methods -------- - function deploy( - uint256 _l2ChainId, - uint32 _basefeeScalar, - uint32 _blobBasefeeScalar, - Roles calldata _roles - ) - external - view // This is only here to silence the compiler warning until the function is fully implemented. - returns (SystemConfig systemConfig_) - { - if (_l2ChainId == 0 || _l2ChainId == block.chainid) revert InvalidChainId(); + /// @notice OPSM is intended to be proxied when used in production. Since we are initially + /// focused on an OPSM version that unblocks interop, we are not proxying OPSM for simplicity. + /// Later, we will `_disableInitializers` in the constructor and replace any constructor logic + /// with an `initialize` function, which will be a breaking change to the OPSM interface. + constructor(SuperchainConfig _superchainConfig, ProtocolVersions _protocolVersions, Blueprints memory _blueprints) { + // TODO uncomment these as we add more validations to this contract, currently this will break a few tests. + // assertValidContractAddress(address(_superchainConfig)); + // assertValidContractAddress(address(_protocolVersions)); + // assertValidContractAddress(_blueprints.addressManager); + // assertValidContractAddress(_blueprints.proxy); + // assertValidContractAddress(_blueprints.proxyAdmin); + // assertValidContractAddress(_blueprints.l1ChugSplashProxy); + // assertValidContractAddress(_blueprints.resolvedDelegateProxy); + + superchainConfig = _superchainConfig; + protocolVersions = _protocolVersions; + blueprint = _blueprints; + } + + /// @notice Callable by the OPSM owner to release a set of implementation contracts for a given + /// release version. This must be called with `_isLatest` set to true before any chains can be deployed. + /// @param _release The release version to set implementations for, of the format `op-contracts/vX.Y.Z`. + /// @param _isLatest Whether the release version is the latest released version. This is + /// significant because the latest version is used to deploy chains in the `deploy` function. + /// @param _setters The set of implementations to set for the release version. + function setRelease(string memory _release, bool _isLatest, ImplementationSetter[] calldata _setters) external { + // TODO Add auth to this method. + + if (_isLatest) latestRelease = _release; + + for (uint256 i = 0; i < _setters.length; i++) { + ImplementationSetter calldata setter = _setters[i]; + Implementation storage impl = implementations[_release][setter.name]; + if (impl.logic != address(0)) revert AlreadyReleased(); + + impl.initializer = setter.info.initializer; + impl.logic = setter.info.logic; + } + } + + function deploy(DeployInput calldata _input) external returns (DeployOutput memory) { + assertValidInputs(_input); + + // TODO Determine how we want to choose salt, e.g. are we concerned about chain ID squatting + // since this approach means a chain ID can only be used once. + uint256 l2ChainId = _input.l2ChainId; + bytes32 salt = bytes32(_input.l2ChainId); + DeployOutput memory output; + + // -------- Deploy Chain Singletons -------- + + // The ProxyAdmin is the owner of all proxies for the chain. We temporarily set the owner to + // this contract, and then transfer ownership to the specified owner at the end of deployment. + // The AddressManager is used to store the implementation for the L1CrossDomainMessenger + // due to it's usage of the legacy ResolvedDelegateProxy. + output.addressManager = AddressManager(Blueprint.deployFrom(blueprint.addressManager, salt)); + output.opChainProxyAdmin = + ProxyAdmin(Blueprint.deployFrom(blueprint.proxyAdmin, salt, abi.encode(address(this)))); + output.opChainProxyAdmin.setAddressManager(output.addressManager); + + // -------- Deploy Proxy Contracts -------- + + // Deploy ERC-1967 proxied contracts. + output.l1ERC721BridgeProxy = L1ERC721Bridge(deployProxy(l2ChainId, output.opChainProxyAdmin, "L1ERC721Bridge")); + output.optimismPortalProxy = + OptimismPortal2(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "OptimismPortal"))); + output.systemConfigProxy = SystemConfig(deployProxy(l2ChainId, output.opChainProxyAdmin, "SystemConfig")); + output.optimismMintableERC20FactoryProxy = OptimismMintableERC20Factory( + deployProxy(l2ChainId, output.opChainProxyAdmin, "OptimismMintableERC20Factory") + ); + + // Deploy legacy proxied contracts. + output.l1StandardBridgeProxy = L1StandardBridge( + payable(Blueprint.deployFrom(blueprint.l1ChugSplashProxy, salt, abi.encode(output.opChainProxyAdmin))) + ); + output.opChainProxyAdmin.setProxyType(address(output.l1StandardBridgeProxy), ProxyAdmin.ProxyType.CHUGSPLASH); + + string memory contractName = "OVM_L1CrossDomainMessenger"; + output.l1CrossDomainMessengerProxy = L1CrossDomainMessenger( + Blueprint.deployFrom(blueprint.resolvedDelegateProxy, salt, abi.encode(output.addressManager, contractName)) + ); + output.opChainProxyAdmin.setProxyType( + address(output.l1CrossDomainMessengerProxy), ProxyAdmin.ProxyType.RESOLVED + ); + output.opChainProxyAdmin.setImplementationName(address(output.l1CrossDomainMessengerProxy), contractName); + + // Now that all proxies are deployed, we can transfer ownership of the AddressManager to the ProxyAdmin. + output.addressManager.transferOwnership(address(output.opChainProxyAdmin)); + + // -------- Set and Initialize Proxy Implementations -------- + Implementation storage impl; + bytes memory data; + + impl = getLatestImplementation("L1ERC721Bridge"); + data = encodeL1ERC721BridgeInitializer(impl.initializer, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), impl.logic, data); + + impl = getLatestImplementation("OptimismPortal"); + data = encodeOptimismPortalInitializer(impl.initializer, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.optimismPortalProxy), impl.logic, data); + + impl = getLatestImplementation("SystemConfig"); + data = encodeSystemConfigInitializer(impl.initializer, _input, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.systemConfigProxy), impl.logic, data); + + impl = getLatestImplementation("OptimismMintableERC20Factory"); + data = encodeOptimismMintableERC20FactoryInitializer(impl.initializer, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.optimismMintableERC20FactoryProxy), impl.logic, data); + + impl = getLatestImplementation("L1CrossDomainMessenger"); + // TODO add this check back in + // require( + // impl.logic == referenceAddressManager.getAddress("OVM_L1CrossDomainMessenger"), + // "OpStackManager: L1CrossDomainMessenger implementation mismatch" + // ); + data = encodeL1CrossDomainMessengerInitializer(impl.initializer, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.l1CrossDomainMessengerProxy), impl.logic, data); + + impl = getLatestImplementation("L1StandardBridge"); + data = encodeL1StandardBridgeInitializer(impl.initializer, output); + upgradeAndCall(output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), impl.logic, data); - // Silence compiler warnings. - _roles; - _basefeeScalar; - _blobBasefeeScalar; - systemConfig_; + // -------- TODO: Placeholders -------- + // For contracts we don't yet deploy, we set the outputs to dummy proxies so they have code to pass assertions. + output.disputeGameFactoryProxy = DisputeGameFactory(deployProxy(l2ChainId, output.opChainProxyAdmin, "1")); + output.disputeGameFactoryImpl = DisputeGameFactory(deployProxy(l2ChainId, output.opChainProxyAdmin, "2")); + output.anchorStateRegistryProxy = AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, "3")); + output.anchorStateRegistryImpl = AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, "4")); + output.faultDisputeGame = FaultDisputeGame(deployProxy(l2ChainId, output.opChainProxyAdmin, "5")); + output.permissionedDisputeGame = PermissionedDisputeGame(deployProxy(l2ChainId, output.opChainProxyAdmin, "6")); + output.delayedWETHPermissionedGameProxy = + DelayedWETH(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "7"))); + output.delayedWETHPermissionlessGameProxy = + DelayedWETH(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "8"))); - revert NotImplemented(); + // -------- Finalize Deployment -------- + // Transfer ownership of the ProxyAdmin from this contract to the specified owner. + output.opChainProxyAdmin.transferOwnership(_input.roles.opChainProxyAdminOwner); + + // Correctness checks. + // TODO these currently fail in tests because the tests use dummy implementation addresses that have no code. + // if (output.systemConfigProxy.owner() != _input.roles.systemConfigOwner) { + // revert AddressMismatch("systemConfigOwner"); + // } + // if (output.systemConfigProxy.l1CrossDomainMessenger() != address(output.l1CrossDomainMessengerProxy)) { + // revert AddressMismatch("l1CrossDomainMessengerProxy"); + // } + // if (output.systemConfigProxy.l1ERC721Bridge() != address(output.l1ERC721BridgeProxy)) { + // revert AddressMismatch("l1ERC721BridgeProxy"); + // } + // if (output.systemConfigProxy.l1StandardBridge() != address(output.l1StandardBridgeProxy)) { + // revert AddressMismatch("l1StandardBridgeProxy"); + // } + // if (output.systemConfigProxy.optimismPortal() != address(output.optimismPortalProxy)) { + // revert AddressMismatch("optimismPortalProxy"); + // } + // if ( + // output.systemConfigProxy.optimismMintableERC20Factory() != + // address(output.optimismMintableERC20FactoryProxy) + // ) revert AddressMismatch("optimismMintableERC20FactoryProxy"); + + return output; + } + + // -------- Utilities -------- + + /// @notice Verifies that all inputs are valid and reverts if any are invalid. + /// Typically the proxy admin owner is expected to have code, but this is not enforced here. + function assertValidInputs(DeployInput calldata _input) internal view { + if (_input.l2ChainId == 0 || _input.l2ChainId == block.chainid) revert InvalidChainId(); + + if (_input.roles.opChainProxyAdminOwner == address(0)) revert InvalidRoleAddress("opChainProxyAdminOwner"); + if (_input.roles.systemConfigOwner == address(0)) revert InvalidRoleAddress("systemConfigOwner"); + if (_input.roles.batcher == address(0)) revert InvalidRoleAddress("batcher"); + if (_input.roles.unsafeBlockSigner == address(0)) revert InvalidRoleAddress("unsafeBlockSigner"); + if (_input.roles.proposer == address(0)) revert InvalidRoleAddress("proposer"); + if (_input.roles.challenger == address(0)) revert InvalidRoleAddress("challenger"); } /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard @@ -64,4 +340,188 @@ contract OPStackManager is ISemver { bytes19 first19Bytes = bytes19(hashedChainId); return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); } + + /// @notice Deterministically deploys a new proxy contract owned by the provided ProxyAdmin. + /// The salt is computed as a function of the L2 chain ID and the contract name. This is required + /// because we deploy many identical proxies, so they each require a unique salt for determinism. + function deployProxy( + uint256 _l2ChainId, + ProxyAdmin _proxyAdmin, + string memory _contractName + ) + internal + returns (address) + { + bytes32 salt = keccak256(abi.encode(_l2ChainId, _contractName)); + return Blueprint.deployFrom(blueprint.proxy, salt, abi.encode(_proxyAdmin)); + } + + /// @notice Returns the implementation data for a contract name. + function getLatestImplementation(string memory _name) internal view returns (Implementation storage) { + return implementations[latestRelease][_name]; + } + + // -------- Initializer Encoding -------- + + /// @notice Helper method for encoding the L1ERC721Bridge initializer data. + function encodeL1ERC721BridgeInitializer( + bytes4 _selector, + DeployOutput memory _output + ) + internal + view + virtual + returns (bytes memory) + { + return abi.encodeWithSelector(_selector, _output.l1CrossDomainMessengerProxy, superchainConfig); + } + + /// @notice Helper method for encoding the OptimismPortal initializer data. + function encodeOptimismPortalInitializer( + bytes4 _selector, + DeployOutput memory _output + ) + internal + view + virtual + returns (bytes memory) + { + _output; + // TODO make GameTypes.CANNON an input once FPs are supported + return abi.encodeWithSelector( + _selector, _output.disputeGameFactoryProxy, _output.systemConfigProxy, superchainConfig, GameTypes.CANNON + ); + } + + /// @notice Helper method for encoding the SystemConfig initializer data. + function encodeSystemConfigInitializer( + bytes4 selector, + DeployInput memory _input, + DeployOutput memory _output + ) + internal + pure + virtual + returns (bytes memory) + { + (ResourceMetering.ResourceConfig memory referenceResourceConfig, SystemConfig.Addresses memory opChainAddrs) = + defaultSystemConfigParams(selector, _input, _output); + + return abi.encodeWithSelector( + selector, + _input.roles.systemConfigOwner, + _input.basefeeScalar, + _input.blobBasefeeScalar, + bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash + 30_000_000, // gasLimit, TODO should this be an input? + _input.roles.unsafeBlockSigner, + referenceResourceConfig, + chainIdToBatchInboxAddress(_input.l2ChainId), + opChainAddrs + ); + } + + /// @notice Helper method for encoding the OptimismMintableERC20Factory initializer data. + function encodeOptimismMintableERC20FactoryInitializer( + bytes4 _selector, + DeployOutput memory _output + ) + internal + pure + virtual + returns (bytes memory) + { + return abi.encodeWithSelector(_selector, _output.l1StandardBridgeProxy); + } + + /// @notice Helper method for encoding the L1CrossDomainMessenger initializer data. + function encodeL1CrossDomainMessengerInitializer( + bytes4 _selector, + DeployOutput memory _output + ) + internal + view + virtual + returns (bytes memory) + { + return + abi.encodeWithSelector(_selector, superchainConfig, _output.optimismPortalProxy, _output.systemConfigProxy); + } + + /// @notice Helper method for encoding the L1StandardBridge initializer data. + function encodeL1StandardBridgeInitializer( + bytes4 _selector, + DeployOutput memory _output + ) + internal + view + virtual + returns (bytes memory) + { + return abi.encodeWithSelector( + _selector, _output.l1CrossDomainMessengerProxy, superchainConfig, _output.systemConfigProxy + ); + } + + /// @notice Returns default, standard config arguments for the SystemConfig initializer. + /// This is used by subclasses to reduce code duplication. + function defaultSystemConfigParams( + bytes4, /* selector */ + DeployInput memory, /* _input */ + DeployOutput memory _output + ) + internal + pure + virtual + returns (ResourceMetering.ResourceConfig memory, SystemConfig.Addresses memory) + { + // TODO do any of these need to be configurable? are these values correct? + ResourceMetering.ResourceConfig memory referenceResourceConfig = ResourceMetering.ResourceConfig({ + maxResourceLimit: 2e7, + elasticityMultiplier: 10, + baseFeeMaxChangeDenominator: 8, + minimumBaseFee: 1e9, + systemTxMaxGas: 1e6, + maximumBaseFee: 340282366920938463463374607431768211455 + }); + + SystemConfig.Addresses memory opChainAddrs = SystemConfig.Addresses({ + l1CrossDomainMessenger: address(_output.l1CrossDomainMessengerProxy), + l1ERC721Bridge: address(_output.l1ERC721BridgeProxy), + l1StandardBridge: address(_output.l1StandardBridgeProxy), + disputeGameFactory: address(_output.disputeGameFactoryProxy), + optimismPortal: address(_output.optimismPortalProxy), + optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy), + gasPayingToken: address(0) + }); + + return (referenceResourceConfig, opChainAddrs); + } + + /// @notice Makes an external call to the target to initialize the proxy with the specified data. + /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. + function upgradeAndCall( + ProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data + ) + internal + { + assertValidContractAddress(address(_proxyAdmin)); + assertValidContractAddress(_target); + assertValidContractAddress(_implementation); + + _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); + } + + function assertValidContractAddress(address _who) internal view { + if (_who == address(0)) revert AddressNotFound(_who); + if (_who.code.length == 0) revert AddressHasNoCode(_who); + } + + /// @notice Returns the blueprint contract addresses. + function blueprints() public view returns (Blueprints memory) { + return blueprint; + } } diff --git a/packages/contracts-bedrock/src/L1/OPStackManagerInterop.sol b/packages/contracts-bedrock/src/L1/OPStackManagerInterop.sol new file mode 100644 index 000000000000..e9fa44c90f9b --- /dev/null +++ b/packages/contracts-bedrock/src/L1/OPStackManagerInterop.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { OPStackManager } from "src/L1/OPStackManager.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; +import { ResourceMetering } from "src/L1/ResourceMetering.sol"; +import { SystemConfig } from "src/L1/SystemConfig.sol"; +import { SystemConfigInterop } from "src/L1/SystemConfigInterop.sol"; + +/// @custom:proxied TODO this is not proxied yet. +contract OPStackManagerInterop is OPStackManager { + constructor( + SuperchainConfig _superchainConfig, + ProtocolVersions _protocolVersions, + Blueprints memory _blueprints + ) + OPStackManager(_superchainConfig, _protocolVersions, _blueprints) + { } + + // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument + // that we must account for. + function encodeSystemConfigInitializer( + bytes4 selector, + DeployInput memory _input, + DeployOutput memory _output + ) + internal + pure + virtual + override + returns (bytes memory) + { + (ResourceMetering.ResourceConfig memory referenceResourceConfig, SystemConfig.Addresses memory opChainAddrs) = + defaultSystemConfigParams(selector, _input, _output); + + // TODO For now we assume that the dependency manager is the same as the proxy admin owner. + // This is currently undefined since it's not part of the standard config, so we may need + // to update where this value is pulled from in the future. To support a different dependency + // manager in this contract without an invasive change of redefining the `Roles` struct, + // we will make the change described in https://github.com/ethereum-optimism/optimism/issues/11783. + address dependencyManager = address(_input.roles.opChainProxyAdminOwner); + + return abi.encodeWithSelector( + selector, + _input.roles.systemConfigOwner, + _input.basefeeScalar, + _input.blobBasefeeScalar, + bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash + 30_000_000, // gasLimit TODO make this configurable? + _input.roles.unsafeBlockSigner, + referenceResourceConfig, + chainIdToBatchInboxAddress(_input.l2ChainId), + opChainAddrs, + dependencyManager + ); + } +} diff --git a/packages/contracts-bedrock/test/DeployImplementations.t.sol b/packages/contracts-bedrock/test/DeployImplementations.t.sol index 4a6adfa6edc9..dc51da5982f1 100644 --- a/packages/contracts-bedrock/test/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/DeployImplementations.t.sol @@ -6,7 +6,11 @@ import { Test } from "forge-std/Test.sol"; import { DelayedWETH } from "src/dispute/weth/DelayedWETH.sol"; import { PreimageOracle } from "src/cannon/PreimageOracle.sol"; import { MIPS } from "src/cannon/MIPS.sol"; +import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; +import { OPStackManager } from "src/L1/OPStackManager.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; @@ -17,6 +21,7 @@ import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC2 import { DeployImplementationsInput, DeployImplementations, + DeployImplementationsInterop, DeployImplementationsOutput } from "scripts/DeployImplementations.s.sol"; @@ -28,7 +33,10 @@ contract DeployImplementationsInput_Test is Test { minProposalSizeBytes: 200, challengePeriodSeconds: 300, proofMaturityDelaySeconds: 400, - disputeGameFinalityDelaySeconds: 500 + disputeGameFinalityDelaySeconds: 500, + release: "op-contracts/latest", + superchainConfigProxy: SuperchainConfig(makeAddr("superchainConfigProxy")), + protocolVersionsProxy: ProtocolVersions(makeAddr("protocolVersionsProxy")) }); function setUp() public { @@ -80,7 +88,8 @@ contract DeployImplementationsOutput_Test is Test { function test_set_succeeds() public { DeployImplementationsOutput.Output memory output = DeployImplementationsOutput.Output({ - optimismPortal2Impl: OptimismPortal2(payable(makeAddr("optimismPortal2Impl"))), + opsm: OPStackManager(makeAddr("opsm")), + optimismPortalImpl: OptimismPortal2(payable(makeAddr("optimismPortalImpl"))), delayedWETHImpl: DelayedWETH(payable(makeAddr("delayedWETHImpl"))), preimageOracleSingleton: PreimageOracle(makeAddr("preimageOracleSingleton")), mipsSingleton: MIPS(makeAddr("mipsSingleton")), @@ -88,10 +97,12 @@ contract DeployImplementationsOutput_Test is Test { l1CrossDomainMessengerImpl: L1CrossDomainMessenger(makeAddr("l1CrossDomainMessengerImpl")), l1ERC721BridgeImpl: L1ERC721Bridge(makeAddr("l1ERC721BridgeImpl")), l1StandardBridgeImpl: L1StandardBridge(payable(makeAddr("l1StandardBridgeImpl"))), - optimismMintableERC20FactoryImpl: OptimismMintableERC20Factory(makeAddr("optimismMintableERC20FactoryImpl")) + optimismMintableERC20FactoryImpl: OptimismMintableERC20Factory(makeAddr("optimismMintableERC20FactoryImpl")), + disputeGameFactoryImpl: DisputeGameFactory(makeAddr("disputeGameFactoryImpl")) }); - vm.etch(address(output.optimismPortal2Impl), hex"01"); + vm.etch(address(output.opsm), hex"01"); + vm.etch(address(output.optimismPortalImpl), hex"01"); vm.etch(address(output.delayedWETHImpl), hex"01"); vm.etch(address(output.preimageOracleSingleton), hex"01"); vm.etch(address(output.mipsSingleton), hex"01"); @@ -100,8 +111,9 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(output.l1ERC721BridgeImpl), hex"01"); vm.etch(address(output.l1StandardBridgeImpl), hex"01"); vm.etch(address(output.optimismMintableERC20FactoryImpl), hex"01"); - - dso.set(dso.optimismPortal2Impl.selector, address(output.optimismPortal2Impl)); + vm.etch(address(output.disputeGameFactoryImpl), hex"01"); + dso.set(dso.opsm.selector, address(output.opsm)); + dso.set(dso.optimismPortalImpl.selector, address(output.optimismPortalImpl)); dso.set(dso.delayedWETHImpl.selector, address(output.delayedWETHImpl)); dso.set(dso.preimageOracleSingleton.selector, address(output.preimageOracleSingleton)); dso.set(dso.mipsSingleton.selector, address(output.mipsSingleton)); @@ -110,8 +122,10 @@ contract DeployImplementationsOutput_Test is Test { dso.set(dso.l1ERC721BridgeImpl.selector, address(output.l1ERC721BridgeImpl)); dso.set(dso.l1StandardBridgeImpl.selector, address(output.l1StandardBridgeImpl)); dso.set(dso.optimismMintableERC20FactoryImpl.selector, address(output.optimismMintableERC20FactoryImpl)); + dso.set(dso.disputeGameFactoryImpl.selector, address(output.disputeGameFactoryImpl)); - assertEq(address(output.optimismPortal2Impl), address(dso.optimismPortal2Impl()), "100"); + assertEq(address(output.opsm), address(dso.opsm()), "50"); + assertEq(address(output.optimismPortalImpl), address(dso.optimismPortalImpl()), "100"); assertEq(address(output.delayedWETHImpl), address(dso.delayedWETHImpl()), "200"); assertEq(address(output.preimageOracleSingleton), address(dso.preimageOracleSingleton()), "300"); assertEq(address(output.mipsSingleton), address(dso.mipsSingleton()), "400"); @@ -122,6 +136,7 @@ contract DeployImplementationsOutput_Test is Test { assertEq( address(output.optimismMintableERC20FactoryImpl), address(dso.optimismMintableERC20FactoryImpl()), "900" ); + assertEq(address(output.disputeGameFactoryImpl), address(dso.disputeGameFactoryImpl()), "950"); assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(dso.output())), "1000"); } @@ -130,7 +145,7 @@ contract DeployImplementationsOutput_Test is Test { bytes memory expectedErr = "DeployUtils: zero address"; vm.expectRevert(expectedErr); - dso.optimismPortal2Impl(); + dso.optimismPortalImpl(); vm.expectRevert(expectedErr); dso.delayedWETHImpl(); @@ -155,15 +170,18 @@ contract DeployImplementationsOutput_Test is Test { vm.expectRevert(expectedErr); dso.optimismMintableERC20FactoryImpl(); + + vm.expectRevert(expectedErr); + dso.disputeGameFactoryImpl(); } function test_getters_whenAddrHasNoCode_reverts() public { address emptyAddr = makeAddr("emptyAddr"); bytes memory expectedErr = bytes(string.concat("DeployUtils: no code at ", vm.toString(emptyAddr))); - dso.set(dso.optimismPortal2Impl.selector, emptyAddr); + dso.set(dso.optimismPortalImpl.selector, emptyAddr); vm.expectRevert(expectedErr); - dso.optimismPortal2Impl(); + dso.optimismPortalImpl(); dso.set(dso.delayedWETHImpl.selector, emptyAddr); vm.expectRevert(expectedErr); @@ -210,14 +228,24 @@ contract DeployImplementations_Test is Test { minProposalSizeBytes: 200, challengePeriodSeconds: 300, proofMaturityDelaySeconds: 400, - disputeGameFinalityDelaySeconds: 500 + disputeGameFinalityDelaySeconds: 500, + release: "op-contracts/latest", + superchainConfigProxy: SuperchainConfig(makeAddr("superchainConfigProxy")), + protocolVersionsProxy: ProtocolVersions(makeAddr("protocolVersionsProxy")) }); - function setUp() public { + function setUp() public virtual { deployImplementations = new DeployImplementations(); (dsi, dso) = deployImplementations.getIOContracts(); } + // By deploying the `DeployImplementations` contract with this virtual function, we provide a + // hook that child contracts can override to return a different implementation of the contract. + // This lets us test e.g. the `DeployImplementationsInterop` contract without duplicating test code. + function createDeployImplementationsContract() internal virtual returns (DeployImplementations) { + return new DeployImplementations(); + } + function test_run_succeeds(DeployImplementationsInput.Input memory _input) public { // This is a requirement in the PreimageOracle contract. _input.challengePeriodSeconds = bound(_input.challengePeriodSeconds, 0, type(uint64).max); @@ -232,7 +260,7 @@ contract DeployImplementations_Test is Test { assertEq(_input.disputeGameFinalityDelaySeconds, dsi.disputeGameFinalityDelaySeconds(), "500"); // Assert that individual output fields were properly set based on the output struct. - assertEq(address(output.optimismPortal2Impl), address(dso.optimismPortal2Impl()), "600"); + assertEq(address(output.optimismPortalImpl), address(dso.optimismPortalImpl()), "600"); assertEq(address(output.delayedWETHImpl), address(dso.delayedWETHImpl()), "700"); assertEq(address(output.preimageOracleSingleton), address(dso.preimageOracleSingleton()), "800"); assertEq(address(output.mipsSingleton), address(dso.mipsSingleton()), "900"); @@ -243,6 +271,7 @@ contract DeployImplementations_Test is Test { assertEq( address(output.optimismMintableERC20FactoryImpl), address(dso.optimismMintableERC20FactoryImpl()), "1400" ); + assertEq(address(output.disputeGameFactoryImpl), address(dso.disputeGameFactoryImpl()), "1450"); // Assert that the full input and output structs were properly set. assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeployImplementationsInput(dsi).input())), "1500"); @@ -254,9 +283,9 @@ contract DeployImplementations_Test is Test { assertEq(output.delayedWETHImpl.delay(), _input.withdrawalDelaySeconds, "1700"); assertEq(output.preimageOracleSingleton.challengePeriod(), _input.challengePeriodSeconds, "1800"); assertEq(output.preimageOracleSingleton.minProposalSize(), _input.minProposalSizeBytes, "1900"); - assertEq(output.optimismPortal2Impl.proofMaturityDelaySeconds(), _input.proofMaturityDelaySeconds, "2000"); + assertEq(output.optimismPortalImpl.proofMaturityDelaySeconds(), _input.proofMaturityDelaySeconds, "2000"); assertEq( - output.optimismPortal2Impl.disputeGameFinalityDelaySeconds(), _input.disputeGameFinalityDelaySeconds, "2100" + output.optimismPortalImpl.disputeGameFinalityDelaySeconds(), _input.disputeGameFinalityDelaySeconds, "2100" ); // Architecture assertions. @@ -273,3 +302,9 @@ contract DeployImplementations_Test is Test { deployImplementations.run(input); } } + +contract DeployImplementationsInterop_Test is DeployImplementations_Test { + function createDeployImplementationsContract() internal override returns (DeployImplementations) { + return new DeployImplementationsInterop(); + } +} diff --git a/packages/contracts-bedrock/test/DeployOPChain.t.sol b/packages/contracts-bedrock/test/DeployOPChain.t.sol index 5b4b60d175c4..a9fde919d3d7 100644 --- a/packages/contracts-bedrock/test/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/DeployOPChain.t.sol @@ -3,6 +3,15 @@ pragma solidity 0.8.15; import { Test } from "forge-std/Test.sol"; +import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/DeploySuperchain.s.sol"; +import { + DeployImplementationsInput, + DeployImplementations, + DeployImplementationsInterop, + DeployImplementationsOutput +} from "scripts/DeployImplementations.s.sol"; +import { DeployOPChainInput, DeployOPChain, DeployOPChainOutput } from "scripts/DeployOPChain.s.sol"; + import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { AddressManager } from "src/legacy/AddressManager.sol"; @@ -12,6 +21,9 @@ import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; import { FaultDisputeGame } from "src/dispute/FaultDisputeGame.sol"; import { PermissionedDisputeGame } from "src/dispute/PermissionedDisputeGame.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions, ProtocolVersion } from "src/L1/ProtocolVersions.sol"; +import { OPStackManager } from "src/L1/OPStackManager.sol"; import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; import { SystemConfig } from "src/L1/SystemConfig.sol"; import { L1CrossDomainMessenger } from "src/L1/L1CrossDomainMessenger.sol"; @@ -19,8 +31,6 @@ import { L1ERC721Bridge } from "src/L1/L1ERC721Bridge.sol"; import { L1StandardBridge } from "src/L1/L1StandardBridge.sol"; import { OptimismMintableERC20Factory } from "src/universal/OptimismMintableERC20Factory.sol"; -import { DeployOPChainInput, DeployOPChain, DeployOPChainOutput } from "scripts/DeployOPChain.s.sol"; - contract DeployOPChainInput_Test is Test { DeployOPChainInput dsi; @@ -35,7 +45,8 @@ contract DeployOPChainInput_Test is Test { }), basefeeScalar: 100, blobBaseFeeScalar: 200, - l2ChainId: 300 + l2ChainId: 300, + opsm: OPStackManager(makeAddr("opsm")) }); function setUp() public { @@ -57,9 +68,10 @@ contract DeployOPChainInput_Test is Test { assertEq(input.basefeeScalar, dsi.basefeeScalar(), "800"); assertEq(input.blobBaseFeeScalar, dsi.blobBaseFeeScalar(), "900"); assertEq(input.l2ChainId, dsi.l2ChainId(), "1000"); + assertEq(address(input.opsm), address(dsi.opsm()), "1100"); // Compare the test input struct to the `input` getter method. - assertEq(keccak256(abi.encode(input)), keccak256(abi.encode(dsi.input())), "1100"); + assertEq(keccak256(abi.encode(input)), keccak256(abi.encode(dsi.input())), "1200"); } function test_getters_whenNotSet_revert() public { @@ -304,3 +316,147 @@ contract DeployOPChainOutput_Test is Test { dso.delayedWETHPermissionlessGameProxy(); } } + +// To mimic a production environment, we default to integration tests here that actually run the +// DeploySuperchain and DeployImplementations scripts. +contract DeployOPChain_TestBase is Test { + DeployOPChain deployOPChain; + DeployOPChainInput dsi; + DeployOPChainOutput dso; + + // We define a default initial input struct for DeploySuperchain. The other input structs are + // dependent on the outputs of the previous scripts, so we initialize them here and populate + // the null values in the `setUp` method.assert + DeploySuperchainInput.Input deploySuperchainInput = DeploySuperchainInput.Input({ + roles: DeploySuperchainInput.Roles({ + proxyAdminOwner: makeAddr("defaultProxyAdminOwner"), + protocolVersionsOwner: makeAddr("defaultProtocolVersionsOwner"), + guardian: makeAddr("defaultGuardian") + }), + paused: false, + requiredProtocolVersion: ProtocolVersion.wrap(1), + recommendedProtocolVersion: ProtocolVersion.wrap(2) + }); + + DeployImplementationsInput.Input deployImplementationsInput = DeployImplementationsInput.Input({ + withdrawalDelaySeconds: 100, + minProposalSizeBytes: 200, + challengePeriodSeconds: 300, + proofMaturityDelaySeconds: 400, + disputeGameFinalityDelaySeconds: 500, + release: "op-contracts/latest", + // These are set during `setUp` since they are outputs of the previous step. + superchainConfigProxy: SuperchainConfig(address(0)), + protocolVersionsProxy: ProtocolVersions(address(0)) + }); + + DeployOPChainInput.Input deployOPChainInput = DeployOPChainInput.Input({ + roles: DeployOPChainInput.Roles({ + opChainProxyAdminOwner: makeAddr("defaultOPChainProxyAdminOwner"), + systemConfigOwner: makeAddr("defaultSystemConfigOwner"), + batcher: makeAddr("defaultBatcher"), + unsafeBlockSigner: makeAddr("defaultUnsafeBlockSigner"), + proposer: makeAddr("defaultProposer"), + challenger: makeAddr("defaultChallenger") + }), + basefeeScalar: 100, + blobBaseFeeScalar: 200, + l2ChainId: 300, + // This is set during `setUp` since it is an output of the previous step. + opsm: OPStackManager(address(0)) + }); + + // Set during `setUp`. + DeployImplementationsOutput.Output deployImplementationsOutput; + + function setUp() public { + // Initialize deploy scripts. + DeploySuperchain deploySuperchain = new DeploySuperchain(); + DeployImplementations deployImplementations = new DeployImplementations(); + deployOPChain = new DeployOPChain(); + (dsi, dso) = deployOPChain.getIOContracts(); + + // Deploy the superchain contracts. + DeploySuperchainOutput.Output memory superchainOutput = deploySuperchain.run(deploySuperchainInput); + + // Populate the input struct for DeployImplementations based on the output of DeploySuperchain. + deployImplementationsInput.superchainConfigProxy = superchainOutput.superchainConfigProxy; + deployImplementationsInput.protocolVersionsProxy = superchainOutput.protocolVersionsProxy; + + // Deploy the implementations using the updated DeployImplementations input struct. + deployImplementationsOutput = deployImplementations.run(deployImplementationsInput); + + // Set the OPStackManager on the input struct for DeployOPChain. + deployOPChainInput.opsm = deployImplementationsOutput.opsm; + } + + // See the function of the same name in the `DeployImplementations_Test` contract of + // `DeployImplementations.t.sol` for more details on why we use this method. + function createDeployImplementationsContract() internal virtual returns (DeployImplementations) { + return new DeployImplementations(); + } +} + +contract DeployOPChain_Test is DeployOPChain_TestBase { + function test_run_succeeds(DeployOPChainInput.Input memory _input) public { + vm.assume(_input.roles.opChainProxyAdminOwner != address(0)); + vm.assume(_input.roles.systemConfigOwner != address(0)); + vm.assume(_input.roles.batcher != address(0)); + vm.assume(_input.roles.unsafeBlockSigner != address(0)); + vm.assume(_input.roles.proposer != address(0)); + vm.assume(_input.roles.challenger != address(0)); + vm.assume(_input.l2ChainId != 0 && _input.l2ChainId != block.chainid); + + _input.opsm = deployOPChainInput.opsm; + + DeployOPChainOutput.Output memory output = deployOPChain.run(_input); + + // TODO Add fault proof contract assertions below once OPSM fully supports them. + + // Assert that individual input fields were properly set based on the input struct. + assertEq(_input.roles.opChainProxyAdminOwner, dsi.opChainProxyAdminOwner(), "100"); + assertEq(_input.roles.systemConfigOwner, dsi.systemConfigOwner(), "200"); + assertEq(_input.roles.batcher, dsi.batcher(), "300"); + assertEq(_input.roles.unsafeBlockSigner, dsi.unsafeBlockSigner(), "400"); + assertEq(_input.roles.proposer, dsi.proposer(), "500"); + assertEq(_input.roles.challenger, dsi.challenger(), "600"); + assertEq(_input.basefeeScalar, dsi.basefeeScalar(), "700"); + assertEq(_input.blobBaseFeeScalar, dsi.blobBaseFeeScalar(), "800"); + assertEq(_input.l2ChainId, dsi.l2ChainId(), "900"); + + // Assert that individual output fields were properly set based on the output struct. + assertEq(address(output.opChainProxyAdmin), address(dso.opChainProxyAdmin()), "1100"); + assertEq(address(output.addressManager), address(dso.addressManager()), "1200"); + assertEq(address(output.l1ERC721BridgeProxy), address(dso.l1ERC721BridgeProxy()), "1300"); + assertEq(address(output.systemConfigProxy), address(dso.systemConfigProxy()), "1400"); + assertEq( + address(output.optimismMintableERC20FactoryProxy), address(dso.optimismMintableERC20FactoryProxy()), "1500" + ); + assertEq(address(output.l1StandardBridgeProxy), address(dso.l1StandardBridgeProxy()), "1600"); + assertEq(address(output.l1CrossDomainMessengerProxy), address(dso.l1CrossDomainMessengerProxy()), "1700"); + assertEq(address(output.optimismPortalProxy), address(dso.optimismPortalProxy()), "1800"); + + // Assert that the full input and output structs were properly set. + assertEq(keccak256(abi.encode(_input)), keccak256(abi.encode(DeployOPChainInput(dsi).input())), "1900"); + assertEq(keccak256(abi.encode(output)), keccak256(abi.encode(DeployOPChainOutput(dso).output())), "2000"); + + // Assert inputs were properly passed through to the contract initializers. + assertEq(address(output.opChainProxyAdmin.owner()), _input.roles.opChainProxyAdminOwner, "2100"); + assertEq(address(output.systemConfigProxy.owner()), _input.roles.systemConfigOwner, "2200"); + address batcher = address(uint160(uint256(output.systemConfigProxy.batcherHash()))); + assertEq(batcher, _input.roles.batcher, "2300"); + assertEq(address(output.systemConfigProxy.unsafeBlockSigner()), _input.roles.unsafeBlockSigner, "2400"); + // assertEq(address(...proposer()), _input.roles.proposer, "2500"); // TODO once we deploy dispute games. + // assertEq(address(...challenger()), _input.roles.challenger, "2600"); // TODO once we deploy dispute games. + + // Most architecture assertions are handled within the OP Stack Manager itself and therefore + // we only assert on the things that are not visible onchain. + // TODO add these assertions: AddressManager, Proxy, ProxyAdmin, etc. + } +} + +contract DeployOPChain_Test_Interop is DeployOPChain_Test { + function createDeployImplementationsContract() internal override returns (DeployImplementations) { + return new DeployImplementationsInterop(); + } +} diff --git a/packages/contracts-bedrock/test/L1/OPStackManager.t.sol b/packages/contracts-bedrock/test/L1/OPStackManager.t.sol index c97e9632faac..b1af4fc1409f 100644 --- a/packages/contracts-bedrock/test/L1/OPStackManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPStackManager.t.sol @@ -1,14 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities import { Test } from "forge-std/Test.sol"; -// Target contract +import { DeployOPChainInput } from "scripts/DeployOPChain.s.sol"; +import { DeployOPChain_TestBase } from "test/DeployOPChain.t.sol"; + import { OPStackManager } from "src/L1/OPStackManager.sol"; +import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; // Exposes internal functions for testing. contract OPStackManager_Harness is OPStackManager { + constructor( + SuperchainConfig _superchainConfig, + ProtocolVersions _protocolVersions, + Blueprints memory _blueprints + ) + OPStackManager(_superchainConfig, _protocolVersions, _blueprints) + { } + function chainIdToBatchInboxAddress_exposed(uint256 l2ChainId) public pure returns (address) { return super.chainIdToBatchInboxAddress(l2ChainId); } @@ -17,38 +28,68 @@ contract OPStackManager_Harness is OPStackManager { // Unlike other test suites, we intentionally do not inherit from CommonTest or Setup. This is // because OPStackManager acts as a deploy script, so we start from a clean slate here and // work OPStackManager's deployment into the existing test setup, instead of using the existing -// test setup to deploy OPStackManager. -contract OPStackManager_Init is Test { - OPStackManager opsm; - - // Default dummy parameters for the deploy function. - OPStackManager.Roles roles; - uint256 l2ChainId = 1234; - uint32 basefeeScalar = 1; - uint32 blobBasefeeScalar = 1; - - function setUp() public { - opsm = new OPStackManager(); +// test setup to deploy OPStackManager. We do however inherit from DeployOPChain_TestBase so +// we can use its setup to deploy the implementations similarly to how a real deployment would +// happen. +contract OPStackManager_Deploy_Test is DeployOPChain_TestBase { + // This helper function is used to convert the input struct type defined in DeployOPChain.s.sol + // to the input struct type defined in OPStackManager.sol. + function toOPSMDeployInput(DeployOPChainInput.Input memory input) + internal + pure + returns (OPStackManager.DeployInput memory) + { + return OPStackManager.DeployInput({ + roles: OPStackManager.Roles({ + opChainProxyAdminOwner: input.roles.opChainProxyAdminOwner, + systemConfigOwner: input.roles.systemConfigOwner, + batcher: input.roles.batcher, + unsafeBlockSigner: input.roles.unsafeBlockSigner, + proposer: input.roles.proposer, + challenger: input.roles.challenger + }), + basefeeScalar: input.basefeeScalar, + blobBasefeeScalar: input.blobBaseFeeScalar, + l2ChainId: input.l2ChainId + }); } -} -contract OPStackManager_Deploy_Test is OPStackManager_Init { function test_deploy_l2ChainIdEqualsZero_reverts() public { + deployOPChainInput.l2ChainId = 0; vm.expectRevert(OPStackManager.InvalidChainId.selector); - opsm.deploy(0, basefeeScalar, blobBasefeeScalar, roles); + deployImplementationsOutput.opsm.deploy(toOPSMDeployInput(deployOPChainInput)); } function test_deploy_l2ChainIdEqualsCurrentChainId_reverts() public { + deployOPChainInput.l2ChainId = block.chainid; vm.expectRevert(OPStackManager.InvalidChainId.selector); - opsm.deploy(block.chainid, basefeeScalar, blobBasefeeScalar, roles); + deployImplementationsOutput.opsm.deploy(toOPSMDeployInput(deployOPChainInput)); + } + + function test_deploy_succeeds() public { + deployImplementationsOutput.opsm.deploy(toOPSMDeployInput(deployOPChainInput)); } } // These tests use the harness which exposes internal functions for testing. contract OPStackManager_InternalMethods_Test is Test { - function test_calculatesBatchInboxAddress_succeeds() public { - OPStackManager_Harness opsmHarness = new OPStackManager_Harness(); + OPStackManager_Harness opsmHarness; + + function setUp() public { + opsmHarness = new OPStackManager_Harness({ + _superchainConfig: SuperchainConfig(makeAddr("superchainConfig")), + _protocolVersions: ProtocolVersions(makeAddr("protocolVersions")), + _blueprints: OPStackManager.Blueprints({ + addressManager: makeAddr("addressManager"), + proxy: makeAddr("proxy"), + proxyAdmin: makeAddr("proxyAdmin"), + l1ChugSplashProxy: makeAddr("l1ChugSplashProxy"), + resolvedDelegateProxy: makeAddr("resolvedDelegateProxy") + }) + }); + } + function test_calculatesBatchInboxAddress_succeeds() public view { // These test vectors were calculated manually: // 1. Compute the bytes32 encoding of the chainId: bytes32(uint256(chainId)); // 2. Hash it and manually take the first 19 bytes, and prefixed it with 0x00. diff --git a/packages/contracts-bedrock/test/Specs.t.sol b/packages/contracts-bedrock/test/Specs.t.sol index 6fac763938fb..f9d6a0145bb3 100644 --- a/packages/contracts-bedrock/test/Specs.t.sol +++ b/packages/contracts-bedrock/test/Specs.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.15; import { CommonTest } from "test/setup/CommonTest.sol"; -import { Executables } from "scripts/libraries/Executables.sol"; import { console2 as console } from "forge-std/console2.sol"; import { ProtocolVersions } from "src/L1/ProtocolVersions.sol"; import { OptimismPortal } from "src/L1/OptimismPortal.sol"; @@ -823,7 +822,25 @@ contract Specification_Test is CommonTest { // OPStackManager _addSpec({ _name: "OPStackManager", _sel: _getSel("version()") }); + _addSpec({ _name: "OPStackManager", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "OPStackManager", _sel: _getSel("protocolVersions()") }); + _addSpec({ _name: "OPStackManager", _sel: _getSel("latestRelease()") }); + _addSpec({ _name: "OPStackManager", _sel: _getSel("implementations(string,string)") }); + _addSpec({ _name: "OPStackManager", _sel: _getSel("systemConfigs(uint256)") }); + _addSpec({ _name: "OPStackManager", _sel: OPStackManager.setRelease.selector }); _addSpec({ _name: "OPStackManager", _sel: OPStackManager.deploy.selector }); + _addSpec({ _name: "OPStackManager", _sel: OPStackManager.blueprints.selector }); + + // OPStackManagerInterop + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("version()") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("protocolVersions()") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("latestRelease()") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("implementations(string,string)") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: _getSel("systemConfigs(uint256)") }); + _addSpec({ _name: "OPStackManagerInterop", _sel: OPStackManager.setRelease.selector }); + _addSpec({ _name: "OPStackManagerInterop", _sel: OPStackManager.deploy.selector }); + _addSpec({ _name: "OPStackManagerInterop", _sel: OPStackManager.blueprints.selector }); // DeputyGuardianModule _addSpec({