Commit de31478b authored by Matt Solomon's avatar Matt Solomon Committed by GitHub

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: default avatarBlaine Malone <blainemalone01@gmail.com>

* 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: default avatarBlaine Malone <blainemalone01@gmail.com>
parent 53fa7b9e
......@@ -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)
});
}
}
......@@ -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();
}
......
......@@ -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;
......
......@@ -32,8 +32,8 @@
"sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649"
},
"src/L1/OPStackManager.sol": {
"initCodeHash": "0x67bf02405bf1ca7d78c4215c350ad9c5c7b4cece35d9fab837f279d65f995c5d",
"sourceCodeHash": "0x8e272e707e383d516b8f1cce0ea29ff46a0eb448c8386fa146e6a43f3100042a"
"initCodeHash": "0xe1eab75651e3d81ad20ca01b1e7d373b25d716ee5f8841a56e56b4531a6e0e70",
"sourceCodeHash": "0x5182a2678dadb200dd255ecdfa395e5f7b1e1e27288e78ddf8802ab51ed2dd81"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0x6bf59539298b20221de6c51db21016be8d3278bdbe0be1cdd49638dc828e003e",
......
......@@ -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
[
{
"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
[]
\ 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
[
{
"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
// 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;
}
}
// 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
);
}
}
......@@ -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();
}
}
......@@ -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();
}
}
// 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.
......
......@@ -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({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment