diff --git a/packages/contracts-bedrock/interfaces/L1/ILiquidityMigrator.sol b/packages/contracts-bedrock/interfaces/L1/ILiquidityMigrator.sol new file mode 100644 index 000000000000..f66b142bfdc6 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/ILiquidityMigrator.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; + +/// @title ILiquidityMigrator +/// @notice Interface for the LiquidityMigrator contract +interface ILiquidityMigrator { + event ETHMigrated(uint256 amount); + + function __constructor__(address _sharedLockbox) external; + + function SHARED_LOCKBOX() external view returns (ISharedLockbox); + + function migrateETH() external; + + function version() external view returns (string memory); +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index 74ba7b4790e9..b0693bcec9a7 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -7,6 +7,7 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; interface IOptimismPortal2 { error AlreadyFinalized(); @@ -69,6 +70,7 @@ interface IOptimismPortal2 { function disputeGameBlacklist(IDisputeGame) external view returns (bool); function disputeGameFactory() external view returns (IDisputeGameFactory); function disputeGameFinalityDelaySeconds() external view returns (uint256); + function sharedLockbox() external view returns (ISharedLockbox); function donateETH() external payable; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 58fe5eff5dca..4201790d7878 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -8,6 +8,7 @@ import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol" import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; interface IOptimismPortalInterop { error AlreadyFinalized(); @@ -70,6 +71,7 @@ interface IOptimismPortalInterop { function disputeGameBlacklist(IDisputeGame) external view returns (bool); function disputeGameFactory() external view returns (IDisputeGameFactory); function disputeGameFinalityDelaySeconds() external view returns (uint256); + function sharedLockbox() external view returns (ISharedLockbox); function donateETH() external payable; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( diff --git a/packages/contracts-bedrock/interfaces/L1/ISharedLockbox.sol b/packages/contracts-bedrock/interfaces/L1/ISharedLockbox.sol new file mode 100644 index 000000000000..029fa91fe00c --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/ISharedLockbox.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; + +/// @title ISharedLockbox +/// @notice Interface for the SharedLockbox contract +interface ISharedLockbox is ISemver { + error Unauthorized(); + + error Paused(); + + event ETHLocked(address indexed portal, uint256 amount); + + event ETHUnlocked(address indexed portal, uint256 amount); + + event PortalAuthorized(address indexed portal); + + function SUPERCHAIN_CONFIG() external view returns (ISuperchainConfig); + + function authorizedPortals(address) external view returns (bool); + + function __constructor__(address _superchainConfig) external; + + function paused() external view returns (bool); + + function unlockETH(uint256 _value) external; + + function lockETH() external payable; + + function authorizePortal(address _portal) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol index dc83893958b0..fab9d001ec49 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISuperchainConfig.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -interface ISuperchainConfig { +import { IDependencySet } from "interfaces/L2/IDependencySet.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; + +interface ISuperchainConfig is IDependencySet { enum UpdateType { GUARDIAN } @@ -10,15 +13,26 @@ interface ISuperchainConfig { event Initialized(uint8 version); event Paused(string identifier); event Unpaused(); + event ChainAdded(uint256 indexed chainId, address indexed systemConfig, address indexed portal); + + error Unauthorized(); + error ChainAlreadyHasDependencies(); + error ChainAlreadyAdded(); function GUARDIAN_SLOT() external view returns (bytes32); function PAUSED_SLOT() external view returns (bytes32); + function UPGRADER_SLOT() external view returns (bytes32); + function SHARED_LOCKBOX() external view returns (ISharedLockbox); function guardian() external view returns (address guardian_); - function initialize(address _guardian, bool _paused) external; + function systemConfigs(uint256) external view returns (address); + function upgrader() external view returns (address upgrader_); + function initialize(address _guardian, address _upgrader, bool _paused) external; function pause(string memory _identifier) external; function paused() external view returns (bool paused_); function unpause() external; function version() external view returns (string memory); + function addChain(uint256 _chainId, address _systemConfig) external; + function dependencySet() external view returns (uint256[] memory); - function __constructor__() external; + function __constructor__(address _sharedLockbox) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index 4cf4a06f943f..67d73c998a8c 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -55,7 +55,7 @@ interface ISystemConfigInterop { function addDependency(uint256 _chainId) external; function removeDependency(uint256 _chainId) external; - function dependencyManager() external view returns (address); + function dependencyCounter() external view returns (uint256); function initialize( address _owner, uint32 _basefeeScalar, @@ -65,11 +65,11 @@ interface ISystemConfigInterop { address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, - ISystemConfig.Addresses memory _addresses, - address _dependencyManager + ISystemConfig.Addresses memory _addresses ) external; function version() external pure returns (string memory); + function SUPERCHAIN_CONFIG() external view returns (address); - function __constructor__() external; + function __constructor__(address _superchainConfig) external; } diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index d1410efeef26..22858b926c41 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -26,7 +26,7 @@ var excludeContracts = []string{ // TODO: Interfaces that need to be fixed "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", - "KontrolCheatsBase", "ISystemConfigInterop", "IResolvedDelegateProxy", + "KontrolCheatsBase", "IResolvedDelegateProxy", } type ContractDefinition struct { diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 18dfbd0a8b90..fc8cd599bf51 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -23,6 +23,8 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; +import { ILiquidityMigrator } from "interfaces/L1/ILiquidityMigrator.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; @@ -142,6 +144,7 @@ library ChainAssertions { /// @notice Asserts that the SystemConfigInterop is setup correctly function checkSystemConfigInterop( Types.ContractSet memory _contracts, + Types.ContractSet memory _proxies, DeployConfig _cfg, bool _isProxy ) @@ -149,6 +152,8 @@ library ChainAssertions { view { ISystemConfigInterop config = ISystemConfigInterop(_contracts.SystemConfig); + ISuperchainConfig superchainConfig = ISuperchainConfig(_proxies.SuperchainConfig); + console.log( "Running chain assertions on the SystemConfigInterop %s at %s", _isProxy ? "proxy" : "implementation", @@ -156,13 +161,9 @@ library ChainAssertions { ); checkSystemConfig(_contracts, _cfg, _isProxy); - if (_isProxy) { - // TODO: this is not being set in the deployment, nor is a config value. - // Update this when it has an entry in hardhat.json - require(config.dependencyManager() == address(0), "CHECK-SCFGI-10"); - } else { - require(config.dependencyManager() == address(0), "CHECK-SCFGI-20"); - } + + require(config.dependencyCounter() == 0, "CHECK-SCFGI-10"); + require(config.SUPERCHAIN_CONFIG() == address(superchainConfig), "CHECK-SCFGI-20"); } /// @notice Asserts that the L1CrossDomainMessenger is setup correctly @@ -586,4 +587,26 @@ library ChainAssertions { // TODO: Add assertions for blueprints and setters? } + + /// @notice Asserts that the SharedLockbox is setup correctly + function checkSharedLockbox(Types.ContractSet memory _contracts, bool _isProxy) internal view { + ISharedLockbox sharedLockbox = ISharedLockbox(_contracts.SharedLockbox); + ISuperchainConfig superchainConfig = ISuperchainConfig(_contracts.SuperchainConfig); + + console.log( + "Running chain assertions on the SharedLockbox %s at %s", + _isProxy ? "proxy" : "implementation", + address(sharedLockbox) + ); + + require(address(sharedLockbox) != address(0), "CHECK-SLB-10"); + require(sharedLockbox.SUPERCHAIN_CONFIG() == superchainConfig, "CHECK-SLB-20"); + } + + /// @notice Asserts that the LiquidityMigrator is setup correctly + function checkLiquidityMigrator(Types.ContractSet memory _contracts, address _liquidityMigrator) internal view { + ISharedLockbox sharedLockbox = ISharedLockbox(_contracts.SharedLockbox); + + require(ILiquidityMigrator(_liquidityMigrator).SHARED_LOCKBOX() == sharedLockbox, "LM-10"); + } } diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index f115a372764c..223421aaca64 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -129,7 +129,8 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"), SuperchainConfig: getAddress("SuperchainConfigProxy"), - OPContractsManager: getAddress("OPContractsManager") + OPContractsManager: getAddress("OPContractsManager"), + SharedLockbox: getAddress("SharedLockboxProxy") }); } @@ -150,7 +151,8 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721Bridge"), ProtocolVersions: getAddress("ProtocolVersions"), SuperchainConfig: getAddress("SuperchainConfig"), - OPContractsManager: getAddress("OPContractsManager") + OPContractsManager: getAddress("OPContractsManager"), + SharedLockbox: getAddress("SharedLockbox") }); } @@ -167,9 +169,17 @@ contract Deploy is Deployer { /// @notice Deploy a new OP Chain using an existing SuperchainConfig and ProtocolVersions /// @param _superchainConfigProxy Address of the existing SuperchainConfig proxy /// @param _protocolVersionsProxy Address of the existing ProtocolVersions proxy - function runWithSuperchain(address payable _superchainConfigProxy, address payable _protocolVersionsProxy) public { + /// @param _sharedLockboxProxy Address of the existing SharedLockbox proxy + function runWithSuperchain( + address payable _superchainConfigProxy, + address payable _protocolVersionsProxy, + address payable _sharedLockboxProxy + ) + public + { require(_superchainConfigProxy != address(0), "Deploy: must specify address for superchain config proxy"); require(_protocolVersionsProxy != address(0), "Deploy: must specify address for protocol versions proxy"); + require(_sharedLockboxProxy != address(0), "Deploy: must specify address for shared lockbox proxy"); vm.chainId(cfg.l1ChainID()); @@ -183,6 +193,10 @@ contract Deploy is Deployer { save("ProtocolVersions", pvProxy.implementation()); save("ProtocolVersionsProxy", _protocolVersionsProxy); + IProxy slProxy = IProxy(_sharedLockboxProxy); + save("SharedLockbox", slProxy.implementation()); + save("SharedLockboxProxy", _sharedLockboxProxy); + _run({ _needsSuperchain: false }); } @@ -255,9 +269,10 @@ contract Deploy is Deployer { //////////////////////////////////////////////////////////////// /// @notice Deploy a full system with a new SuperchainConfig - /// The Superchain system has 2 singleton contracts which lie outside of an OP Chain: + /// The Superchain system has 3 singleton contracts which lie outside of an OP Chain: /// 1. The SuperchainConfig contract /// 2. The ProtocolVersions contract + /// 3. The SharedLockbox contract function deploySuperchain() public { console.log("Setting up Superchain"); DeploySuperchain ds = new DeploySuperchain(); @@ -279,11 +294,25 @@ contract Deploy is Deployer { save("SuperchainConfig", address(dso.superchainConfigImpl())); save("ProtocolVersionsProxy", address(dso.protocolVersionsProxy())); save("ProtocolVersions", address(dso.protocolVersionsImpl())); + save("SharedLockboxProxy", address(dso.sharedLockboxProxy())); + save("SharedLockbox", address(dso.sharedLockboxImpl())); + save("LiquidityMigrator", address(dso.liquidityMigratorImpl())); - // First run assertions for the ProtocolVersions and SuperchainConfig proxy contracts. + // First run assertions for the ProtocolVersions, SuperchainConfig and SharedLockbox proxy contracts. Types.ContractSet memory contracts = _proxies(); ChainAssertions.checkProtocolVersions({ _contracts: contracts, _cfg: cfg, _isProxy: true }); ChainAssertions.checkSuperchainConfig({ _contracts: contracts, _cfg: cfg, _isProxy: true, _isPaused: false }); + ChainAssertions.checkSharedLockbox({ _contracts: contracts, _isProxy: true }); + + // Test the LiquidityMigrator contract is setup correctly. + ChainAssertions.checkLiquidityMigrator({ + _contracts: contracts, + _liquidityMigrator: mustGetAddress("LiquidityMigrator") + }); + + // Then replace the SharedLockbox proxy with the implementation address and run assertions on it. + contracts.SharedLockbox = mustGetAddress("SharedLockbox"); + ChainAssertions.checkSharedLockbox({ _contracts: contracts, _isProxy: false }); // Then replace the ProtocolVersions proxy with the implementation address and run assertions on it. contracts.ProtocolVersions = mustGetAddress("ProtocolVersions"); @@ -315,6 +344,7 @@ contract Deploy is Deployer { ); dii.set(dii.superchainConfigProxy.selector, mustGetAddress("SuperchainConfigProxy")); dii.set(dii.protocolVersionsProxy.selector, mustGetAddress("ProtocolVersionsProxy")); + dii.set(dii.sharedLockboxProxy.selector, mustGetAddress("SharedLockboxProxy")); dii.set(dii.salt.selector, _implSalt()); if (_isInterop) { @@ -364,7 +394,13 @@ contract Deploy is Deployer { _oracle: IPreimageOracle(address(dio.preimageOracleSingleton())) }); if (_isInterop) { - ChainAssertions.checkSystemConfigInterop({ _contracts: contracts, _cfg: cfg, _isProxy: false }); + Types.ContractSet memory proxies = _proxies(); + ChainAssertions.checkSystemConfigInterop({ + _contracts: contracts, + _proxies: proxies, + _cfg: cfg, + _isProxy: false + }); } else { ChainAssertions.checkSystemConfig({ _contracts: contracts, _cfg: cfg, _isProxy: false }); } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 63357584a880..75dad61a91ec 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -8,6 +8,7 @@ import { LibString } from "@solady/utils/LibString.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; import { Bytes } from "src/libraries/Bytes.sol"; @@ -23,8 +24,6 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; - -import { OPContractsManagerInterop } from "src/L1/OPContractsManagerInterop.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; @@ -51,6 +50,7 @@ contract DeployImplementationsInput is BaseDeployIO { // Outputs from DeploySuperchain.s.sol. ISuperchainConfig internal _superchainConfigProxy; IProtocolVersions internal _protocolVersionsProxy; + ISharedLockbox internal _sharedLockboxProxy; string internal _standardVersionsToml; @@ -86,6 +86,7 @@ contract DeployImplementationsInput is BaseDeployIO { require(_addr != address(0), "DeployImplementationsInput: cannot set zero address"); if (_sel == this.superchainConfigProxy.selector) _superchainConfigProxy = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = IProtocolVersions(_addr); + else if (_sel == this.sharedLockboxProxy.selector) _sharedLockboxProxy = ISharedLockbox(_addr); else revert("DeployImplementationsInput: unknown selector"); } @@ -151,6 +152,11 @@ contract DeployImplementationsInput is BaseDeployIO { require(address(_protocolVersionsProxy) != address(0), "DeployImplementationsInput: not set"); return _protocolVersionsProxy; } + + function sharedLockboxProxy() public view returns (ISharedLockbox) { + require(address(_sharedLockboxProxy) != address(0), "DeployImplementationsInput: not set"); + return _sharedLockboxProxy; + } } contract DeployImplementationsOutput is BaseDeployIO { @@ -914,22 +920,16 @@ contract DeployImplementations is Script { // - `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). +// it's constructor has a different signature. This signature is different because there is a +// new input parameter, the `superchainConfig`. +// - Because of the different system config constructor, there is a new input parameter (superchainConfig). // // 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 `OPContractsManagerInterop is OPContractsManager` 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 OPContractsManagerInterop instead of OPContractsManager, which contains the updated logic -// for encoding the SystemConfig initializer. -// - Updates the OPCM 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 @@ -937,41 +937,6 @@ contract DeployImplementations is Script { // 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 createOPCMContract( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio, - OPContractsManager.Blueprints memory _blueprints, - string memory _l1ContractsRelease - ) - internal - virtual - override - returns (OPContractsManager opcm_) - { - ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); - IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - - OPContractsManager.Implementations memory implementations = OPContractsManager.Implementations({ - l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), - optimismPortalImpl: address(_dio.optimismPortalImpl()), - systemConfigImpl: address(_dio.systemConfigImpl()), - optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), - l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), - l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()), - disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()), - delayedWETHImpl: address(_dio.delayedWETHImpl()), - mipsImpl: address(_dio.mipsSingleton()) - }); - - vm.broadcast(msg.sender); - opcm_ = new OPContractsManagerInterop( - superchainConfigProxy, protocolVersionsProxy, _l1ContractsRelease, _blueprints, implementations - ); - - vm.label(address(opcm_), "OPContractsManager"); - _dio.set(_dio.opcm.selector, address(opcm_)); - } - function deployOptimismPortalImpl( DeployImplementationsInput _dii, DeployImplementationsOutput _dio @@ -1025,11 +990,14 @@ contract DeployImplementationsInterop is DeployImplementations { if (existingImplementation != address(0)) { impl = ISystemConfigInterop(existingImplementation); } else { + address superchainConfig = address(_dii.superchainConfigProxy()); vm.broadcast(msg.sender); impl = ISystemConfigInterop( DeployUtils.create1({ _name: "SystemConfigInterop", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfigInterop.__constructor__, ())) + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ISystemConfigInterop.__constructor__, (superchainConfig)) + ) }) ); } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol index 3ee86f4155d6..3e6e62210fcc 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOwnership.s.sol @@ -323,7 +323,7 @@ contract DeployOwnership is Deploy { _save: this, _salt: _implSalt(), _name: "SuperchainConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, (address(0)))) }) ); diff --git a/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol index b91f4b31bf53..fcb2b5005d6a 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol @@ -8,6 +8,8 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; +import { ILiquidityMigrator } from "interfaces/L1/ILiquidityMigrator.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; @@ -69,6 +71,7 @@ import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; // All contracts of the form `DeployInput` should inherit from `BaseDeployIO`, as it provides // shared functionality for all deploy scripts, such as access to cheat codes. contract DeploySuperchainInput is BaseDeployIO { + // We use the `stdToml` library to parse TOML input files. This allows us to easily parse using stdToml for string; // All inputs are set in storage individually. We put any roles first, followed by the remaining @@ -161,6 +164,9 @@ contract DeploySuperchainOutput is BaseDeployIO { ISuperchainConfig internal _superchainConfigImpl; ISuperchainConfig internal _superchainConfigProxy; IProxyAdmin internal _superchainProxyAdmin; + ISharedLockbox internal _sharedLockboxImpl; + ISharedLockbox internal _sharedLockboxProxy; + ILiquidityMigrator internal _liquidityMigratorImpl; // This method lets each field be set individually. The selector of an output's getter method // is used to determine which field to set. @@ -171,6 +177,9 @@ contract DeploySuperchainOutput is BaseDeployIO { else if (_sel == this.superchainConfigProxy.selector) _superchainConfigProxy = ISuperchainConfig(_address); else if (_sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = IProtocolVersions(_address); else if (_sel == this.protocolVersionsProxy.selector) _protocolVersionsProxy = IProtocolVersions(_address); + else if (_sel == this.sharedLockboxImpl.selector) _sharedLockboxImpl = ISharedLockbox(_address); + else if (_sel == this.sharedLockboxProxy.selector) _sharedLockboxProxy = ISharedLockbox(_address); + else if (_sel == this.liquidityMigratorImpl.selector) _liquidityMigratorImpl = ILiquidityMigrator(_address); else revert("DeploySuperchainOutput: unknown selector"); } @@ -182,7 +191,10 @@ contract DeploySuperchainOutput is BaseDeployIO { address(this.superchainConfigImpl()), address(this.superchainConfigProxy()), address(this.protocolVersionsImpl()), - address(this.protocolVersionsProxy()) + address(this.protocolVersionsProxy()), + address(this.sharedLockboxImpl()), + address(this.sharedLockboxProxy()), + address(this.liquidityMigratorImpl()) ); DeployUtils.assertValidContractAddresses(addrs); @@ -190,12 +202,15 @@ contract DeploySuperchainOutput is BaseDeployIO { vm.startPrank(address(0)); address actualSuperchainConfigImpl = IProxy(payable(address(_superchainConfigProxy))).implementation(); address actualProtocolVersionsImpl = IProxy(payable(address(_protocolVersionsProxy))).implementation(); + address actualSharedLockboxImpl = IProxy(payable(address(_sharedLockboxProxy))).implementation(); vm.stopPrank(); require(actualSuperchainConfigImpl == address(_superchainConfigImpl), "100"); // nosemgrep: // sol-style-malformed-require require(actualProtocolVersionsImpl == address(_protocolVersionsImpl), "200"); // nosemgrep: // sol-style-malformed-require + require(actualSharedLockboxImpl == address(_sharedLockboxImpl), "300"); // nosemgrep: + // sol-style-malformed-require assertValidDeploy(_dsi); } @@ -225,11 +240,28 @@ contract DeploySuperchainOutput is BaseDeployIO { return _protocolVersionsProxy; } + function sharedLockboxImpl() public view returns (ISharedLockbox) { + DeployUtils.assertValidContractAddress(address(_sharedLockboxImpl)); + return _sharedLockboxImpl; + } + + function sharedLockboxProxy() public view returns (ISharedLockbox) { + DeployUtils.assertValidContractAddress(address(_sharedLockboxProxy)); + return _sharedLockboxProxy; + } + + function liquidityMigratorImpl() public view returns (ILiquidityMigrator) { + DeployUtils.assertValidContractAddress(address(_liquidityMigratorImpl)); + return _liquidityMigratorImpl; + } + // -------- Deployment Assertions -------- function assertValidDeploy(DeploySuperchainInput _dsi) public { assertValidSuperchainProxyAdmin(_dsi); assertValidSuperchainConfig(_dsi); assertValidProtocolVersions(_dsi); + assertValidSharedLockbox(); + assertValidLiquidityMigrator(); } function assertValidSuperchainProxyAdmin(DeploySuperchainInput _dsi) internal view { @@ -285,6 +317,27 @@ contract DeploySuperchainOutput is BaseDeployIO { require(ProtocolVersion.unwrap(pv.required()) == 0, "PV-70"); require(ProtocolVersion.unwrap(pv.recommended()) == 0, "PV-80"); } + + function assertValidSharedLockbox() internal { + // Proxy checks. + ISharedLockbox sl = sharedLockboxProxy(); + + vm.startPrank(address(0)); + require(IProxy(payable(address(sl))).implementation() == address(sharedLockboxImpl()), "SLB-10"); + require(IProxy(payable(address(sl))).admin() == address(superchainProxyAdmin()), "SLB-20"); + require(sl.SUPERCHAIN_CONFIG() == superchainConfigProxy(), "SLB-30"); + vm.stopPrank(); + + // Implementation checks. + sl = sharedLockboxImpl(); + require(sl.SUPERCHAIN_CONFIG() == superchainConfigProxy(), "SLB-40"); + } + + function assertValidLiquidityMigrator() internal view { + // Implementation checks. + ILiquidityMigrator lm = liquidityMigratorImpl(); + require(lm.SHARED_LOCKBOX() == sharedLockboxProxy(), "LM-10"); + } } // For all broadcasts in this script we explicitly specify the deployer as `msg.sender` because for @@ -292,6 +345,13 @@ contract DeploySuperchainOutput is BaseDeployIO { // default sender would be the broadcaster during test, but the broadcaster needs to be the deployer // since they are set to the initial proxy admin owner. contract DeploySuperchain is Script { + // The `PrecalculatedAddresses` stores the precalculated addresses so then they can be checked on the actual + // deployment. + struct PrecalculatedAddresses { + address superchainConfigProxy; + address sharedLockboxProxy; + } + // -------- Core Deployment Methods -------- function run(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { @@ -307,10 +367,8 @@ contract DeploySuperchain is Script { // Deploy the proxy admin, with the owner set to the deployer. deploySuperchainProxyAdmin(_dsi, _dso); - // Deploy and initialize the superchain contracts. - deploySuperchainImplementationContracts(_dsi, _dso); - deployAndInitializeSuperchainConfig(_dsi, _dso); - deployAndInitializeProtocolVersions(_dsi, _dso); + // Deploy implementations, proxies and then initialize the superchain contracts. + deploySuperchain(_dsi, _dso); // Transfer ownership of the ProxyAdmin from the deployer to the specified owner. transferProxyAdminOwnership(_dsi, _dso); @@ -338,39 +396,144 @@ contract DeploySuperchain is Script { _dso.set(_dso.superchainProxyAdmin.selector, address(superchainProxyAdmin)); } - function deploySuperchainImplementationContracts(DeploySuperchainInput, DeploySuperchainOutput _dso) public { - // Deploy implementation contracts. + function deploySuperchain(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { + // Precalculate the proxies addresses. Needed since there are circular dependencies between them. + PrecalculatedAddresses memory precalculatedAddresses; + precalculatedAddresses.superchainConfigProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 4); + precalculatedAddresses.sharedLockboxProxy = vm.computeCreateAddress(msg.sender, vm.getNonce(msg.sender) + 8); + + // Deploy implementation contracts + deploySuperchainImplementationContracts(_dsi, _dso, precalculatedAddresses); + + // Deploy proxy contracts + deployAndInitializeSuperchainProxyContracts(_dsi, _dso, precalculatedAddresses); + } + + function deploySuperchainImplementationContracts( + DeploySuperchainInput, + DeploySuperchainOutput _dso, + PrecalculatedAddresses memory _precalculatedAddresses + ) + internal + { vm.startBroadcast(msg.sender); + + // Deploy SuperchainConfig implementation ISuperchainConfig superchainConfigImpl = ISuperchainConfig( DeployUtils.create1({ _name: "SuperchainConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ISuperchainConfig.__constructor__, (_precalculatedAddresses.sharedLockboxProxy)) + ) }) ); + + // Deploy ProtocolVersions implementation IProtocolVersions protocolVersionsImpl = IProtocolVersions( DeployUtils.create1({ _name: "ProtocolVersions", _args: DeployUtils.encodeConstructor(abi.encodeCall(IProtocolVersions.__constructor__, ())) }) ); + + // Deploy SharedLockbox implementation + ISharedLockbox sharedLockboxImpl = ISharedLockbox( + DeployUtils.create1({ + _name: "SharedLockbox", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ISharedLockbox.__constructor__, (_precalculatedAddresses.superchainConfigProxy)) + ) + }) + ); + + // Deploy LiquidityMigrator implementation + ILiquidityMigrator liquidityMigratorImpl = ILiquidityMigrator( + DeployUtils.create1({ + _name: "LiquidityMigrator", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ILiquidityMigrator.__constructor__, (_precalculatedAddresses.sharedLockboxProxy)) + ) + }) + ); + vm.stopBroadcast(); vm.label(address(superchainConfigImpl), "SuperchainConfigImpl"); vm.label(address(protocolVersionsImpl), "ProtocolVersionsImpl"); + vm.label(address(sharedLockboxImpl), "SharedLockboxImpl"); + vm.label(address(liquidityMigratorImpl), "LiquidityMigratorImpl"); _dso.set(_dso.superchainConfigImpl.selector, address(superchainConfigImpl)); _dso.set(_dso.protocolVersionsImpl.selector, address(protocolVersionsImpl)); + _dso.set(_dso.sharedLockboxImpl.selector, address(sharedLockboxImpl)); + _dso.set(_dso.liquidityMigratorImpl.selector, address(liquidityMigratorImpl)); } - function deployAndInitializeSuperchainConfig(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { - address guardian = _dsi.guardian(); - bool paused = _dsi.paused(); - + function deployAndInitializeSuperchainProxyContracts( + DeploySuperchainInput _dsi, + DeploySuperchainOutput _dso, + PrecalculatedAddresses memory _precalculatedAddresses + ) + internal + { IProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); - ISuperchainConfig superchainConfigImpl = _dso.superchainConfigImpl(); + // Deploy SuperchainConfig proxy + ISuperchainConfig superchainConfigProxy; + { + address guardian = _dsi.guardian(); + address upgrader = _dsi.superchainProxyAdminOwner(); + bool paused = _dsi.paused(); + + vm.startBroadcast(msg.sender); + superchainConfigProxy = ISuperchainConfig( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IProxy.__constructor__, (address(superchainProxyAdmin))) + ) + }) + ); + superchainProxyAdmin.upgradeAndCall( + payable(address(superchainConfigProxy)), + address(_dso.superchainConfigImpl()), + abi.encodeCall(ISuperchainConfig.initialize, (guardian, upgrader, paused)) + ); + vm.stopBroadcast(); + } + + // Deploy ProtocolVersions proxy + IProtocolVersions protocolVersionsProxy; + { + address protocolVersionsOwner = _dsi.protocolVersionsOwner(); + ProtocolVersion requiredProtocolVersion = _dsi.requiredProtocolVersion(); + ProtocolVersion recommendedProtocolVersion = _dsi.recommendedProtocolVersion(); + IProtocolVersions protocolVersions = _dso.protocolVersionsImpl(); + + vm.startBroadcast(msg.sender); + // Deploy ProtocolVersion proxy + protocolVersionsProxy = IProtocolVersions( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IProxy.__constructor__, (address(superchainProxyAdmin))) + ) + }) + ); + superchainProxyAdmin.upgradeAndCall( + payable(address(protocolVersionsProxy)), + address(protocolVersions), + abi.encodeCall( + IProtocolVersions.initialize, + (protocolVersionsOwner, requiredProtocolVersion, recommendedProtocolVersion) + ) + ); + vm.stopBroadcast(); + } + + // Deploy SharedLockbox proxy vm.startBroadcast(msg.sender); - ISuperchainConfig superchainConfigProxy = ISuperchainConfig( + ISharedLockbox sharedLockboxProxy = ISharedLockbox( DeployUtils.create1({ _name: "Proxy", _args: DeployUtils.encodeConstructor( @@ -378,46 +541,27 @@ contract DeploySuperchain is Script { ) }) ); - superchainProxyAdmin.upgradeAndCall( - payable(address(superchainConfigProxy)), - address(superchainConfigImpl), - abi.encodeCall(ISuperchainConfig.initialize, (guardian, paused)) - ); + superchainProxyAdmin.upgrade(payable(address(sharedLockboxProxy)), address(_dso.sharedLockboxImpl())); vm.stopBroadcast(); vm.label(address(superchainConfigProxy), "SuperchainConfigProxy"); _dso.set(_dso.superchainConfigProxy.selector, address(superchainConfigProxy)); - } - - function deployAndInitializeProtocolVersions(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { - address protocolVersionsOwner = _dsi.protocolVersionsOwner(); - ProtocolVersion requiredProtocolVersion = _dsi.requiredProtocolVersion(); - ProtocolVersion recommendedProtocolVersion = _dsi.recommendedProtocolVersion(); - - IProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); - IProtocolVersions protocolVersionsImpl = _dso.protocolVersionsImpl(); - - vm.startBroadcast(msg.sender); - IProtocolVersions protocolVersionsProxy = IProtocolVersions( - DeployUtils.create1({ - _name: "Proxy", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IProxy.__constructor__, (address(superchainProxyAdmin))) - ) - }) - ); - superchainProxyAdmin.upgradeAndCall( - payable(address(protocolVersionsProxy)), - address(protocolVersionsImpl), - abi.encodeCall( - IProtocolVersions.initialize, - (protocolVersionsOwner, requiredProtocolVersion, recommendedProtocolVersion) - ) + // To ensure deployments are correct, check that the precalculated address matches the actual address. + require( + address(superchainConfigProxy) == _precalculatedAddresses.superchainConfigProxy, + "superchain config expected address mismatch" ); - vm.stopBroadcast(); vm.label(address(protocolVersionsProxy), "ProtocolVersionsProxy"); _dso.set(_dso.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + + vm.label(address(sharedLockboxProxy), "SharedLockboxProxy"); + _dso.set(_dso.sharedLockboxProxy.selector, address(sharedLockboxProxy)); + // To ensure deployments are correct, check that the precalculated address matches the actual address. + require( + address(sharedLockboxProxy) == _precalculatedAddresses.sharedLockboxProxy, + "shared lockbox expected address mismatch" + ); } function transferProxyAdminOwnership(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { diff --git a/packages/contracts-bedrock/scripts/libraries/Types.sol b/packages/contracts-bedrock/scripts/libraries/Types.sol index db37e19457a8..6f7c39d3c88b 100644 --- a/packages/contracts-bedrock/scripts/libraries/Types.sol +++ b/packages/contracts-bedrock/scripts/libraries/Types.sol @@ -19,5 +19,6 @@ library Types { address ProtocolVersions; address SuperchainConfig; address OPContractsManager; + address SharedLockbox; } } diff --git a/packages/contracts-bedrock/snapshots/.gas-snapshot b/packages/contracts-bedrock/snapshots/.gas-snapshot index 935d131f6331..8e8ff71d5952 100644 --- a/packages/contracts-bedrock/snapshots/.gas-snapshot +++ b/packages/contracts-bedrock/snapshots/.gas-snapshot @@ -4,14 +4,14 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5144) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158553) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7619) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369235) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967442) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564398) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076568) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467098) -GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512802) -GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72619) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369297) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967504) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564460) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076630) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467064) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512768) +GasBenchMark_L1StandardBridge_Finalize:test_finalizeETHWithdrawal_benchmark() (gas: 72661) GasBenchMark_L2OutputOracle:test_proposeL2Output_benchmark() (gas: 92973) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark() (gas: 68422) GasBenchMark_OptimismPortal:test_depositTransaction_benchmark_1() (gas: 69008) -GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155565) \ No newline at end of file +GasBenchMark_OptimismPortal:test_proveWithdrawalTransaction_benchmark() (gas: 155610) \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/LiquidityMigrator.json b/packages/contracts-bedrock/snapshots/abi/LiquidityMigrator.json new file mode 100644 index 000000000000..c1c723a90262 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/LiquidityMigrator.json @@ -0,0 +1,59 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_sharedLockbox", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SHARED_LOCKBOX", + "outputs": [ + { + "internalType": "contract ISharedLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "migrateETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHMigrated", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json deleted file mode 100644 index a541ae2e1016..000000000000 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json +++ /dev/null @@ -1,652 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract ISuperchainConfig", - "name": "_superchainConfig", - "type": "address" - }, - { - "internalType": "contract IProtocolVersions", - "name": "_protocolVersions", - "type": "address" - }, - { - "internalType": "string", - "name": "_l1ContractsRelease", - "type": "string" - }, - { - "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": "address", - "name": "anchorStateRegistry", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "_blueprints", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address", - "name": "l1ERC721BridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismPortalImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "systemConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismMintableERC20FactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1CrossDomainMessengerImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1StandardBridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "disputeGameFactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "delayedWETHImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "mipsImpl", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Implementations", - "name": "_implementations", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "OUTPUT_VERSION", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "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": "address", - "name": "anchorStateRegistry", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_l2ChainId", - "type": "uint256" - } - ], - "name": "chainIdToBatchInboxAddress", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "pure", - "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 OPContractsManager.Roles", - "name": "roles", - "type": "tuple" - }, - { - "internalType": "uint32", - "name": "basefeeScalar", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "blobBasefeeScalar", - "type": "uint32" - }, - { - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "startingAnchorRoots", - "type": "bytes" - }, - { - "internalType": "string", - "name": "saltMixer", - "type": "string" - }, - { - "internalType": "uint64", - "name": "gasLimit", - "type": "uint64" - }, - { - "internalType": "GameType", - "name": "disputeGameType", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "disputeAbsolutePrestate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "disputeMaxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "disputeSplitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "disputeClockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "disputeMaxClockDuration", - "type": "uint64" - } - ], - "internalType": "struct OPContractsManager.DeployInput", - "name": "_input", - "type": "tuple" - } - ], - "name": "deploy", - "outputs": [ - { - "components": [ - { - "internalType": "contract IProxyAdmin", - "name": "opChainProxyAdmin", - "type": "address" - }, - { - "internalType": "contract IAddressManager", - "name": "addressManager", - "type": "address" - }, - { - "internalType": "contract IL1ERC721Bridge", - "name": "l1ERC721BridgeProxy", - "type": "address" - }, - { - "internalType": "contract ISystemConfig", - "name": "systemConfigProxy", - "type": "address" - }, - { - "internalType": "contract IOptimismMintableERC20Factory", - "name": "optimismMintableERC20FactoryProxy", - "type": "address" - }, - { - "internalType": "contract IL1StandardBridge", - "name": "l1StandardBridgeProxy", - "type": "address" - }, - { - "internalType": "contract IL1CrossDomainMessenger", - "name": "l1CrossDomainMessengerProxy", - "type": "address" - }, - { - "internalType": "contract IOptimismPortal2", - "name": "optimismPortalProxy", - "type": "address" - }, - { - "internalType": "contract IDisputeGameFactory", - "name": "disputeGameFactoryProxy", - "type": "address" - }, - { - "internalType": "contract IAnchorStateRegistry", - "name": "anchorStateRegistryProxy", - "type": "address" - }, - { - "internalType": "contract IAnchorStateRegistry", - "name": "anchorStateRegistryImpl", - "type": "address" - }, - { - "internalType": "contract IFaultDisputeGame", - "name": "faultDisputeGame", - "type": "address" - }, - { - "internalType": "contract IPermissionedDisputeGame", - "name": "permissionedDisputeGame", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETHPermissionedGameProxy", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETHPermissionlessGameProxy", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.DeployOutput", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "implementations", - "outputs": [ - { - "components": [ - { - "internalType": "address", - "name": "l1ERC721BridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismPortalImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "systemConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismMintableERC20FactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1CrossDomainMessengerImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1StandardBridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "disputeGameFactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "delayedWETHImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "mipsImpl", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Implementations", - "name": "", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1ContractsRelease", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "protocolVersions", - "outputs": [ - { - "internalType": "contract IProtocolVersions", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "superchainConfig", - "outputs": [ - { - "internalType": "contract ISuperchainConfig", - "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": "outputVersion", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "deployer", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "deployOutput", - "type": "bytes" - } - ], - "name": "Deployed", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "AddressHasNoCode", - "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": "IdentityPrecompileCallFailed", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidChainId", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "role", - "type": "string" - } - ], - "name": "InvalidRoleAddress", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidStartingAnchorRoots", - "type": "error" - }, - { - "inputs": [], - "name": "LatestReleaseNotSet", - "type": "error" - }, - { - "inputs": [], - "name": "NotABlueprint", - "type": "error" - }, - { - "inputs": [], - "name": "ReservedBitsSet", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "UnexpectedPreambleData", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "UnsupportedERCVersion", - "type": "error" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 2f52ed573d37..9ec54a2db545 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -643,6 +643,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sharedLockbox", + "outputs": [ + { + "internalType": "contract ISharedLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 5b9f72b9446c..59d72ab1fbc4 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -661,6 +661,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sharedLockbox", + "outputs": [ + { + "internalType": "contract ISharedLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", diff --git a/packages/contracts-bedrock/snapshots/abi/SharedLockbox.json b/packages/contracts-bedrock/snapshots/abi/SharedLockbox.json new file mode 100644 index 000000000000..ef1961339528 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/SharedLockbox.json @@ -0,0 +1,165 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_superchainConfig", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "SUPERCHAIN_CONFIG", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_portal", + "type": "address" + } + ], + "name": "authorizePortal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorizedPortals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lockETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "unlockETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHUnlocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + } + ], + "name": "PortalAuthorized", + "type": "event" + }, + { + "inputs": [], + "name": "Paused", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainConfig.json b/packages/contracts-bedrock/snapshots/abi/SuperchainConfig.json index 0304f507eb50..f8d349727689 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainConfig.json @@ -1,6 +1,12 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "address", + "name": "_sharedLockbox", + "type": "address" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, @@ -30,6 +36,63 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "SHARED_LOCKBOX", + "outputs": [ + { + "internalType": "contract ISharedLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADER_SLOT", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_systemConfig", + "type": "address" + } + ], + "name": "addChain", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dependencySet", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "guardian", @@ -50,6 +113,11 @@ "name": "_guardian", "type": "address" }, + { + "internalType": "address", + "name": "_upgrader", + "type": "address" + }, { "internalType": "bool", "name": "_paused", @@ -61,6 +129,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_chainId", + "type": "uint256" + } + ], + "name": "isInDependencySet", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -87,6 +174,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "systemConfigs", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "unpause", @@ -94,6 +200,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "upgrader", + "outputs": [ + { + "internalType": "address", + "name": "upgrader_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "version", @@ -107,6 +226,31 @@ "stateMutability": "view", "type": "function" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "systemConfig", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + } + ], + "name": "ChainAdded", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -157,5 +301,20 @@ "inputs": [], "name": "Unpaused", "type": "event" + }, + { + "inputs": [], + "name": "ChainAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "ChainAlreadyHasDependencies", + "type": "error" + }, + { + "inputs": [], + "name": "Unauthorized", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index a459af15801b..c664e9a48d89 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -1,4 +1,15 @@ [ + { + "inputs": [ + { + "internalType": "address", + "name": "_superchainConfig", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "inputs": [], "name": "BATCH_INBOX_SLOT", @@ -103,6 +114,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "SUPERCHAIN_CONFIG", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "UNSAFE_BLOCK_SIGNER_SLOT", @@ -196,12 +220,12 @@ }, { "inputs": [], - "name": "dependencyManager", + "name": "dependencyCounter", "outputs": [ { - "internalType": "address", + "internalType": "uint256", "name": "", - "type": "address" + "type": "uint256" } ], "stateMutability": "view", @@ -377,133 +401,6 @@ "name": "_batchInbox", "type": "address" }, - { - "components": [ - { - "internalType": "address", - "name": "l1CrossDomainMessenger", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ERC721Bridge", - "type": "address" - }, - { - "internalType": "address", - "name": "l1StandardBridge", - "type": "address" - }, - { - "internalType": "address", - "name": "disputeGameFactory", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismPortal", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismMintableERC20Factory", - "type": "address" - }, - { - "internalType": "address", - "name": "gasPayingToken", - "type": "address" - } - ], - "internalType": "struct SystemConfig.Addresses", - "name": "_addresses", - "type": "tuple" - }, - { - "internalType": "address", - "name": "_dependencyManager", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - }, - { - "internalType": "uint32", - "name": "_basefeeScalar", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "_blobbasefeeScalar", - "type": "uint32" - }, - { - "internalType": "bytes32", - "name": "_batcherHash", - "type": "bytes32" - }, - { - "internalType": "uint64", - "name": "_gasLimit", - "type": "uint64" - }, - { - "internalType": "address", - "name": "_unsafeBlockSigner", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint32", - "name": "maxResourceLimit", - "type": "uint32" - }, - { - "internalType": "uint8", - "name": "elasticityMultiplier", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "baseFeeMaxChangeDenominator", - "type": "uint8" - }, - { - "internalType": "uint32", - "name": "minimumBaseFee", - "type": "uint32" - }, - { - "internalType": "uint32", - "name": "systemTxMaxGas", - "type": "uint32" - }, - { - "internalType": "uint128", - "name": "maximumBaseFee", - "type": "uint128" - } - ], - "internalType": "struct IResourceMetering.ResourceConfig", - "name": "_config", - "type": "tuple" - }, - { - "internalType": "address", - "name": "_batchInbox", - "type": "address" - }, { "components": [ { diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 7adf312a94d9..4606ba37fc16 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -19,6 +19,10 @@ "initCodeHash": "0xd992c45b8461b9546fe4e3cecbce15d17ce366a62aab17058aad3b15bf36d21d", "sourceCodeHash": "0xa35478e9e2659a320da725a117b200dea2826175d2b17d881de1196da0cc91eb" }, + "src/L1/LiquidityMigrator.sol": { + "initCodeHash": "0x708f764a2de821caa3d520c93f1951e24128b136a5c41b06e2b1444a1a34e2e8", + "sourceCodeHash": "0x4f719e707583e2b23b9fcbd6e70935df099f45ac0efc50d1156051609bc26f69" + }, "src/L1/OPContractsManager.sol": { "initCodeHash": "0x9b704574a7005dc2aa8d6a3e0d85572493cc4bbd60033a23e437632a5fef7720", "sourceCodeHash": "0x05ed7ad68e4e9bca7334314e794a1f66e5899532bb01cfa3a7716cb2688df9d5" @@ -28,28 +32,32 @@ "sourceCodeHash": "0x77e0f0f8caa9742896a3e7cdee15b0061e298f9c950159bb7231b8196517c9d2" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0xfd14fd690752519064d6de6c3e15d69ec9146bc8714e56ac286305773dbb1533", - "sourceCodeHash": "0x3dbd4601c67a43c42f403f6b28e6e2d8bf4f3d2cf2f2d8f7460026e0c6c66def" + "initCodeHash": "0xf2b48d356afe813c35e3084bba81de2cf7d392d7fd9bb5227fc9a2c9514a196d", + "sourceCodeHash": "0xa0f411c08b04df19153908c8d459af0a708adba5062d3efa74c0398998b68a07" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0xc7a9282ef32425b65a647039908ea2b8d6ef231ba1b87c345c7b9f3f73acc240", - "sourceCodeHash": "0x85e9f10ba1884b1a45737fd35ae4c2f9a9054f81a6aba08941ab7a95e74543da" + "initCodeHash": "0xd122f99c1d0ae0377063169b15ad4755a675f9752d9039cae6862d356d7df3f8", + "sourceCodeHash": "0xae2fbe02c0f8685692babeed0252ae8a624dc6d3bfb082fc3807d7b84869004b" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0x0000ec89712d8b4609873f1ba76afffd4205bf9110818995c90134dbec12e91e", "sourceCodeHash": "0xd4284db247fc1e686bd4b57755c7ac9a073a173d6df4f93448eb7fb5518882f7" }, + "src/L1/SharedLockbox.sol": { + "initCodeHash": "0x914d95090b8d5b37744a030cd9ac5a2b57c367d695f6a6ae3ad3aa894c599c3c", + "sourceCodeHash": "0xbc1e6321e241f18175c2c3c0cf898490a31ebe0dbaced1213c59621cdafd63c2" + }, "src/L1/SuperchainConfig.sol": { - "initCodeHash": "0xcc35362cfd686d2f50e1db8c4a864cbc1eb847eaf58007d8ce7295ae9e101102", - "sourceCodeHash": "0xafa784ea78818a382ff3a61e2d84be58c7978110c06b9273db68c0213ead02d3" + "initCodeHash": "0x291c5a1696d6eb2a7d53f59a547d642ed6b41a4977d1e995375dfcb758151055", + "sourceCodeHash": "0x60fef6bd049c7ec6527d87319a12790561924f7d4355c5e2c3f5b06227b63b8e" }, "src/L1/SystemConfig.sol": { "initCodeHash": "0x11707ca5ef2bfc0da82e945cb97e62226852d850e8bdecb8ec22fc1ab7967894", "sourceCodeHash": "0x71cedc36b538c3db2ac43d1d9554583a3f09eee9e9a5c5d39608418bd466ee0e" }, "src/L1/SystemConfigInterop.sol": { - "initCodeHash": "0x53d4adf9db26e1217daccff4e91f9717925ad2e666d0953d43009535efd5ad3d", - "sourceCodeHash": "0x8a08e71f05581449c32ea15702c1881463a878243fd8bb90dea33a795abd0b84" + "initCodeHash": "0xd03b97cd34c51d95e246678303f6f870ef39495af39d888915cbd31bf58cc5e3", + "sourceCodeHash": "0x49636aeaaf48925cf6dcfce6f55f4949fc35a9c873faed1fbd30ded1841a4da1" }, "src/L2/BaseFeeVault.sol": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/LiquidityMigrator.json b/packages/contracts-bedrock/snapshots/storageLayout/LiquidityMigrator.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/LiquidityMigrator.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json deleted file mode 100644 index 5eae73490b12..000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json +++ /dev/null @@ -1,23 +0,0 @@ -[ - { - "bytes": "32", - "label": "l1ContractsRelease", - "offset": 0, - "slot": "0", - "type": "string" - }, - { - "bytes": "256", - "label": "blueprint", - "offset": 0, - "slot": "1", - "type": "struct OPContractsManager.Blueprints" - }, - { - "bytes": "288", - "label": "implementation", - "offset": 0, - "slot": "9", - "type": "struct OPContractsManager.Implementations" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SharedLockbox.json b/packages/contracts-bedrock/snapshots/storageLayout/SharedLockbox.json new file mode 100644 index 000000000000..7c441f14dcc0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/SharedLockbox.json @@ -0,0 +1,9 @@ +[ + { + "bytes": "32", + "label": "authorizedPortals", + "offset": 0, + "slot": "0", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainConfig.json index 70e559b7bf6d..e818b2b6d519 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SuperchainConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperchainConfig.json @@ -12,5 +12,19 @@ "offset": 1, "slot": "0", "type": "bool" + }, + { + "bytes": "32", + "label": "systemConfigs", + "offset": 0, + "slot": "1", + "type": "mapping(uint256 => address)" + }, + { + "bytes": "64", + "label": "_dependencySet", + "offset": 0, + "slot": "2", + "type": "struct EnumerableSet.UintSet" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/LiquidityMigrator.sol b/packages/contracts-bedrock/src/L1/LiquidityMigrator.sol new file mode 100644 index 000000000000..37c3326c7f1c --- /dev/null +++ b/packages/contracts-bedrock/src/L1/LiquidityMigrator.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; + +/// @custom:proxied true +/// @title LiquidityMigrator +/// @notice A contract to migrate the OptimisPortal's ETH balance to the SharedLockbox. One-time use logic, executed in +/// a batch of transactions to enable the SharedLockbox interaction within the OptimismPortal. +contract LiquidityMigrator is ISemver { + /// @notice Emitted when the contract's ETH balance is migrated to the SharedLockbox. + /// @param amount The amount corresponding to the contract's ETH balance migrated. + event ETHMigrated(uint256 amount); + + /// @notice The SharedLockbox contract. + ISharedLockbox public immutable SHARED_LOCKBOX; + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + string public constant version = "1.0.0-beta.1"; + + /// @notice Constructs the LiquidityMigrator contract. + /// @param _sharedLockbox The address of the SharedLockbox contract. + constructor(address _sharedLockbox) { + SHARED_LOCKBOX = ISharedLockbox(_sharedLockbox); + } + + /// @notice Migrates the contract's whole ETH balance to the SharedLockbox. + /// One-time use logic upgraded over OptimismPortalProxy address and then deprecated by another approval. + function migrateETH() external { + uint256 balance = address(this).balance; + SHARED_LOCKBOX.lockETH{ value: balance }(); + emit ETHMigrated(balance); + } +} diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol deleted file mode 100644 index b40ea48b2019..000000000000 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Contracts -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; - -// Interfaces -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; - -contract OPContractsManagerInterop is OPContractsManager { - constructor( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations - ) - OPContractsManager(_superchainConfig, _protocolVersions, _l1ContractsRelease, _blueprints, _implementations) - { } - - // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument - // that we must account for. - function encodeSystemConfigInitializer( - DeployInput memory _input, - DeployOutput memory _output - ) - internal - view - virtual - override - returns (bytes memory) - { - bytes4 selector = ISystemConfigInterop.initialize.selector; - (IResourceMetering.ResourceConfig memory referenceResourceConfig, ISystemConfig.Addresses memory opChainAddrs) = - defaultSystemConfigParams(selector, _input, _output); - - // TODO For now we assume that the dependency manager is the same as system config 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.systemConfigOwner); - - return abi.encodeWithSelector( - selector, - _input.roles.systemConfigOwner, - _input.basefeeScalar, - _input.blobBasefeeScalar, - bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash - _input.gasLimit, - _input.roles.unsafeBlockSigner, - referenceResourceConfig, - chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs, - dependencyManager - ); - } -} diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 10448f0b7fb6..a42afe3e79b8 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -45,6 +45,7 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; /// @custom:proxied true /// @title OptimismPortal2 @@ -177,15 +178,14 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); /// @notice Reverts when paused. - modifier whenNotPaused() { + function _whenNotPaused() internal view { if (paused()) revert CallPaused(); - _; } /// @notice Semantic version. - /// @custom:semver 3.11.0-beta.9 + /// @custom:semver 3.11.0-beta.10 function version() public pure virtual returns (string memory) { - return "3.11.0-beta.9"; + return "3.11.0-beta.10"; } /// @notice Constructs the OptimismPortal contract. @@ -239,6 +239,13 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } } + /// @notice Returns the `_token` balance of the `_account`. + /// @param _token Address of the token to check the balance of. + /// @param _account The address of the account to query the balance for. + function _balanceOf(address _token, address _account) internal view returns (uint256) { + return IERC20(_token).balanceOf(_account); + } + /// @notice Getter function for the address of the guardian. /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. /// @return Address of the guardian. @@ -262,6 +269,11 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { return DISPUTE_GAME_FINALITY_DELAY_SECONDS; } + /// @notice Getter for the address of the shared lockbox. + function sharedLockbox() public view returns (ISharedLockbox) { + return superchainConfig.SHARED_LOCKBOX(); + } + /// @notice Computes the minimum gas limit for a deposit. /// The minimum gas limit linearly increases based on the size of the calldata. /// This is to prevent users from creating L2 resource usage without paying for it. @@ -282,8 +294,6 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Accepts ETH value without triggering a deposit to L2. - /// This function mainly exists for the sake of the migration between the legacy - /// Optimism system and Bedrock. function donateETH() external payable { // Intentionally empty. } @@ -316,8 +326,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { bytes[] calldata _withdrawalProof ) external - whenNotPaused { + _whenNotPaused(); + // Prevent users from creating a deposit transaction where this address is the message // sender on L2. Because this is checked here, we do not need to check again in // `finalizeWithdrawalTransaction`. @@ -380,7 +391,8 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Finalizes a withdrawal transaction. /// @param _tx Withdrawal transaction to finalize. - function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused { + function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external { + _whenNotPaused(); finalizeWithdrawalTransactionExternalProof(_tx, msg.sender); } @@ -392,8 +404,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { address _proofSubmitter ) public - whenNotPaused { + _whenNotPaused(); + // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other // than the default value when a withdrawal transaction is being finalized. This check is // a defacto reentrancy guard. @@ -414,6 +427,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { bool success; (address token,) = gasPayingToken(); if (token == Constants.ETHER) { + // Unlock and receive the ETH from the shared lockbox. + if (_tx.value != 0) sharedLockbox().unlockETH(_tx.value); + // Trigger the call to the target contract. We use a custom low level method // SafeCall.callWithMinGas to ensure two key properties // 1. Target contracts cannot force this call to run out of gas by returning a very large @@ -435,7 +451,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Read the balance of the target contract before the transfer so the consistency // of the transfer can be checked afterwards. - uint256 startBalance = IERC20(token).balanceOf(address(this)); + uint256 startBalance = _balanceOf(token, address(this)); // Transfer the ERC20 balance to the target, accounting for non standard ERC20 // implementations that may not return a boolean. This reverts if the low level @@ -443,7 +459,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value }); // The balance must be transferred exactly. - if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) { + if (_balanceOf(token, address(this)) != startBalance - _tx.value) { revert TransferFailed(); } } @@ -500,13 +516,13 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { _balance += _mint; // Get the balance of the portal before the transfer. - uint256 startBalance = IERC20(token).balanceOf(address(this)); + uint256 startBalance = _balanceOf(token, address(this)); // Take ownership of the token. It is assumed that the user has given the portal an approval. IERC20(token).safeTransferFrom({ from: msg.sender, to: address(this), value: _mint }); // Double check that the portal now has the exact amount of token. - if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) { + if (_balanceOf(token, address(this)) != startBalance + _mint) { revert TransferFailed(); } @@ -543,6 +559,11 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { (address token,) = gasPayingToken(); if (token != Constants.ETHER && msg.value != 0) revert NoValue(); + if (token == Constants.ETHER && msg.value != 0) { + // Lock the ETH in the shared lockbox. + sharedLockbox().lockETH{ value: msg.value }(); + } + _depositTransaction({ _to: _to, _mint: msg.value, diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index fecd4dc0d6b2..811c7136191e 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -25,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 { OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) { } - /// @custom:semver +interop-beta.6 + /// @custom:semver +interop-beta.7 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.6"); + return string.concat(super.version(), "+interop-beta.7"); } /// @notice Sets static configuration options for the L2 system. diff --git a/packages/contracts-bedrock/src/L1/SharedLockbox.sol b/packages/contracts-bedrock/src/L1/SharedLockbox.sol new file mode 100644 index 000000000000..157668a33f0d --- /dev/null +++ b/packages/contracts-bedrock/src/L1/SharedLockbox.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { Unauthorized, Paused } from "src/libraries/errors/CommonErrors.sol"; + +/// @custom:proxied true +/// @title SharedLockbox +/// @notice Manages ETH liquidity locking and unlocking for authorized OptimismPortals, enabling unified ETH liquidity +/// management across chains in the superchain cluster. +contract SharedLockbox is ISemver { + /// @notice Emitted when ETH is locked in the lockbox by an authorized portal. + /// @param portal The address of the portal that locked the ETH. + /// @param amount The amount of ETH locked. + event ETHLocked(address indexed portal, uint256 amount); + + /// @notice Emitted when ETH is unlocked from the lockbox by an authorized portal. + /// @param portal The address of the portal that unlocked the ETH. + /// @param amount The amount of ETH unlocked. + event ETHUnlocked(address indexed portal, uint256 amount); + + /// @notice Emitted when a portal is set as authorized to interact with the lockbox. + /// @param portal The address of the authorized portal. + event PortalAuthorized(address indexed portal); + + /// @notice The address of the SuperchainConfig contract. + ISuperchainConfig public immutable SUPERCHAIN_CONFIG; + + /// @notice OptimismPortals that are part of the dependency cluster authorized to interact with the SharedLockbox + mapping(address => bool) public authorizedPortals; + + /// @notice Semantic version. + /// @custom:semver 1.0.0-beta.1 + function version() public view virtual returns (string memory) { + return "1.0.0-beta.1"; + } + + /// @notice Constructs the SharedLockbox contract. + /// @param _superchainConfig The address of the SuperchainConfig contract. + constructor(address _superchainConfig) { + SUPERCHAIN_CONFIG = ISuperchainConfig(_superchainConfig); + } + + /// @notice Reverts when paused. + function _whenNotPaused() internal view { + if (paused()) revert Paused(); + } + + /// @notice Getter for the current paused status. + function paused() public view returns (bool) { + return SUPERCHAIN_CONFIG.paused(); + } + + /// @notice Locks ETH in the lockbox. + /// Called by an authorized portal when migrating its ETH liquidity or when depositing with some ETH value. + function lockETH() external payable { + if (!authorizedPortals[msg.sender]) revert Unauthorized(); + + emit ETHLocked(msg.sender, msg.value); + } + + /// @notice Unlocks ETH from the lockbox. + /// Called by an authorized portal when finalizing a withdrawal that requires ETH. + function unlockETH(uint256 _value) external { + _whenNotPaused(); + if (!authorizedPortals[msg.sender]) revert Unauthorized(); + + // Using `donateETH` to avoid triggering a deposit + IOptimismPortal(payable(msg.sender)).donateETH{ value: _value }(); + emit ETHUnlocked(msg.sender, _value); + } + + /// @notice Authorizes a portal to interact with the lockbox. + function authorizePortal(address _portal) external { + _whenNotPaused(); + if (msg.sender != address(SUPERCHAIN_CONFIG)) revert Unauthorized(); + + authorizedPortals[_portal] = true; + emit PortalAuthorized(_portal); + } +} diff --git a/packages/contracts-bedrock/src/L1/SuperchainConfig.sol b/packages/contracts-bedrock/src/L1/SuperchainConfig.sol index ddea4a36ec51..ee85b8e144c9 100644 --- a/packages/contracts-bedrock/src/L1/SuperchainConfig.sol +++ b/packages/contracts-bedrock/src/L1/SuperchainConfig.sol @@ -6,6 +6,10 @@ import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable // Libraries import { Storage } from "src/libraries/Storage.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; @@ -15,10 +19,14 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; /// @title SuperchainConfig /// @notice The SuperchainConfig contract is used to manage configuration of global superchain values. contract SuperchainConfig is Initializable, ISemver { + using EnumerableSet for EnumerableSet.UintSet; + /// @notice Enum representing different types of updates. /// @custom:value GUARDIAN Represents an update to the guardian. + /// @custom:value UPGRADER Represents an update to the upgrader. enum UpdateType { - GUARDIAN + GUARDIAN, + UPGRADER } /// @notice Whether or not the Superchain is paused. @@ -28,6 +36,13 @@ contract SuperchainConfig is Initializable, ISemver { /// It can only be modified by an upgrade. bytes32 public constant GUARDIAN_SLOT = bytes32(uint256(keccak256("superchainConfig.guardian")) - 1); + /// @notice The address of the upgrader, which can add a chain to the dependency set. + /// It can only be modified by an upgrade. + bytes32 public constant UPGRADER_SLOT = bytes32(uint256(keccak256("superchainConfig.upgrader")) - 1); + + // The Shared Lockbox contract + ISharedLockbox public immutable SHARED_LOCKBOX; + /// @notice Emitted when the pause is triggered. /// @param identifier A string helping to identify provenance of the pause transaction. event Paused(string identifier); @@ -40,20 +55,41 @@ contract SuperchainConfig is Initializable, ISemver { /// @param data Encoded update data. event ConfigUpdate(UpdateType indexed updateType, bytes data); + /// @notice Emitted when a new chain is added as part of the dependency set. + /// @param chainId The chain ID. + /// @param systemConfig The address of the SystemConfig contract. + /// @param portal The address of the OptimismPortal contract. + event ChainAdded(uint256 indexed chainId, address indexed systemConfig, address indexed portal); + + /// @notice Thrown when the input chain's system config already contains dependencies on its set. + error ChainAlreadyHasDependencies(); + + /// @notice Thrown when the input chain is already added to the dependency set. + error ChainAlreadyAdded(); + /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.4 - string public constant version = "1.1.1-beta.4"; + /// @custom:semver 1.1.1-beta.5 + string public constant version = "1.1.1-beta.5"; + + // Mapping from chainId to SystemConfig address + mapping(uint256 => address) public systemConfigs; + + // Dependency set of chains that are part of the same cluster + EnumerableSet.UintSet internal _dependencySet; /// @notice Constructs the SuperchainConfig contract. - constructor() { + constructor(address _sharedLockbox) { + SHARED_LOCKBOX = ISharedLockbox(_sharedLockbox); _disableInitializers(); } /// @notice Initializer. /// @param _guardian Address of the guardian, can pause the OptimismPortal. + /// @param _upgrader Address of the upgrader, can add a chain to the dependency set. /// @param _paused Initial paused status. - function initialize(address _guardian, bool _paused) external initializer { + function initialize(address _guardian, address _upgrader, bool _paused) external initializer { _setGuardian(_guardian); + _setUpgrader(_upgrader); if (_paused) { _pause("Initializer paused"); } @@ -64,6 +100,11 @@ contract SuperchainConfig is Initializable, ISemver { guardian_ = Storage.getAddress(GUARDIAN_SLOT); } + /// @notice Getter for the upgrader address. + function upgrader() public view returns (address upgrader_) { + upgrader_ = Storage.getAddress(UPGRADER_SLOT); + } + /// @notice Getter for the current paused status. function paused() public view returns (bool paused_) { paused_ = Storage.getBool(PAUSED_SLOT); @@ -97,4 +138,56 @@ contract SuperchainConfig is Initializable, ISemver { Storage.setAddress(GUARDIAN_SLOT, _guardian); emit ConfigUpdate(UpdateType.GUARDIAN, abi.encode(_guardian)); } + + /// @notice Sets the upgrader address. This is only callable during initialization, so an upgrade + /// will be required to change the upgrader. + /// @param _upgrader The new upgrader address. + function _setUpgrader(address _upgrader) internal { + Storage.setAddress(UPGRADER_SLOT, _upgrader); + emit ConfigUpdate(UpdateType.UPGRADER, abi.encode(_upgrader)); + } + + /// @notice Adds a new chain to the dependency set. + /// Adds the new chain as a dependency for all existing chains in the dependency set, and vice versa. It + /// also stores the SystemConfig address of it, and authorizes the OptimismPortal on the SharedLockbox. + /// @param _chainId The chain ID. + /// @param _systemConfig The SystemConfig contract address of the chain to add. + function addChain(uint256 _chainId, address _systemConfig) external { + if (msg.sender != upgrader()) revert Unauthorized(); + if (ISystemConfigInterop(_systemConfig).dependencyCounter() != 0) revert ChainAlreadyHasDependencies(); + + // Add to the dependency set and check it is not already added (`add()` returns false if it already exists) + if (!_dependencySet.add(_chainId)) revert ChainAlreadyAdded(); + + systemConfigs[_chainId] = _systemConfig; + + // Loop through the dependency set and update the dependency for each chain. Using length - 1 to exclude the + // current chain from the loop. + for (uint256 i; i < _dependencySet.length() - 1; i++) { + uint256 currentId = _dependencySet.at(i); + + // Add the new chain as dependency for the current chain on the loop + ISystemConfigInterop(systemConfigs[currentId]).addDependency(_chainId); + + // Add the current chain on the loop as dependency for the new chain + ISystemConfigInterop(_systemConfig).addDependency(currentId); + } + + // Authorize the portal on the shared lockbox + address portal = ISystemConfigInterop(_systemConfig).optimismPortal(); + SHARED_LOCKBOX.authorizePortal(portal); + + emit ChainAdded(_chainId, _systemConfig, portal); + } + + /// @notice Checks if a chain is part or not of the dependency set. + /// @param _chainId The chain ID to check for. + function isInDependencySet(uint256 _chainId) public view returns (bool) { + return _dependencySet.contains(_chainId); + } + + /// @notice Getter for the chain ids list on the dependency set. + function dependencySet() external view returns (uint256[] memory) { + return _dependencySet.values(); + } } diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index 94c4a37f3ff8..e39a6ae6f386 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -13,7 +13,6 @@ import { Storage } from "src/libraries/Storage.sol"; // Interfaces import { IOptimismPortalInterop as IOptimismPortal } from "interfaces/L1/IOptimismPortalInterop.sol"; -import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// @custom:proxied true @@ -22,55 +21,23 @@ import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// All configuration is stored on L1 and picked up by L2 as part of the derviation of /// the L2 chain. contract SystemConfigInterop is SystemConfig { - /// @notice Storage slot where the dependency manager address is stored - /// @dev Equal to bytes32(uint256(keccak256("systemconfig.dependencymanager")) - 1) - bytes32 internal constant DEPENDENCY_MANAGER_SLOT = - 0x1708e077affb93e89be2665fb0fb72581be66f84dc00d25fed755ae911905b1c; - - /// @notice Initializer. - /// @param _owner Initial owner of the contract. - /// @param _basefeeScalar Initial basefee scalar value. - /// @param _blobbasefeeScalar Initial blobbasefee scalar value. - /// @param _batcherHash Initial batcher hash. - /// @param _gasLimit Initial gas limit. - /// @param _unsafeBlockSigner Initial unsafe block signer address. - /// @param _config Initial ResourceConfig. - /// @param _batchInbox Batch inbox address. An identifier for the op-node to find - /// canonical data. - /// @param _addresses Set of L1 contract addresses. These should be the proxies. - /// @param _dependencyManager The addressed allowed to add/remove from the dependency set - function initialize( - address _owner, - uint32 _basefeeScalar, - uint32 _blobbasefeeScalar, - bytes32 _batcherHash, - uint64 _gasLimit, - address _unsafeBlockSigner, - IResourceMetering.ResourceConfig memory _config, - address _batchInbox, - SystemConfig.Addresses memory _addresses, - address _dependencyManager - ) - external - { - // This method has an initializer modifier, and will revert if already initialized. - initialize({ - _owner: _owner, - _basefeeScalar: _basefeeScalar, - _blobbasefeeScalar: _blobbasefeeScalar, - _batcherHash: _batcherHash, - _gasLimit: _gasLimit, - _unsafeBlockSigner: _unsafeBlockSigner, - _config: _config, - _batchInbox: _batchInbox, - _addresses: _addresses - }); - Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); - } + /// @notice Storage slot where the dependency counter is stored + /// @dev Equal to bytes32(uint256(keccak256("systemconfig.dependencyCounter")) - 1) + bytes32 internal constant DEPENDENCY_COUNTER_SLOT = + 0x4ae776ed628fe5cb57f351f2986b1f5aee6fda3f6ec83fa6166e8098945adb19; + + /// @notice The address of the SuperchainConfig contract. + address public immutable SUPERCHAIN_CONFIG; - /// @custom:semver +interop-beta.7 + /// @custom:semver +interop-beta.8 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.7"); + return string.concat(super.version(), "+interop-beta.8"); + } + + /// @notice Constructs the SystemConfig contract. + /// @param _superchainConfig The address of the SuperchainConfig contract. + constructor(address _superchainConfig) { + SUPERCHAIN_CONFIG = _superchainConfig; } /// @notice Internal setter for the gas paying token address, includes validation. @@ -102,26 +69,32 @@ contract SystemConfigInterop is SystemConfig { } } - /// @notice Adds a chain to the interop dependency set. Can only be called by the dependency manager. + /// @notice Adds a chain to the interop dependency set. Can only be called by the SuperchainConfig. /// @param _chainId Chain ID of chain to add. function addDependency(uint256 _chainId) external { - require(msg.sender == dependencyManager(), "SystemConfig: caller is not the dependency manager"); + require(msg.sender == SUPERCHAIN_CONFIG, "SystemConfig: caller is not the SuperchainConfig"); + + Storage.setUint(DEPENDENCY_COUNTER_SLOT, dependencyCounter() + 1); + IOptimismPortal(payable(optimismPortal())).setConfig( ConfigType.ADD_DEPENDENCY, StaticConfig.encodeAddDependency(_chainId) ); } - /// @notice Removes a chain from the interop dependency set. Can only be called by the dependency manager + /// @notice Removes a chain from the interop dependency set. Can only be called by the SuperchainConfig /// @param _chainId Chain ID of the chain to remove. function removeDependency(uint256 _chainId) external { - require(msg.sender == dependencyManager(), "SystemConfig: caller is not the dependency manager"); + require(msg.sender == SUPERCHAIN_CONFIG, "SystemConfig: caller is not the SuperchainConfig"); + + Storage.setUint(DEPENDENCY_COUNTER_SLOT, dependencyCounter() - 1); + IOptimismPortal(payable(optimismPortal())).setConfig( ConfigType.REMOVE_DEPENDENCY, StaticConfig.encodeRemoveDependency(_chainId) ); } - /// @notice getter for the dependency manager address - function dependencyManager() public view returns (address) { - return Storage.getAddress(DEPENDENCY_MANAGER_SLOT); + /// @notice getter for the dependency counter + function dependencyCounter() public view returns (uint256) { + return Storage.getUint(DEPENDENCY_COUNTER_SLOT); } } diff --git a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol index 30ce96972a19..390d0a416f24 100644 --- a/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol +++ b/packages/contracts-bedrock/src/libraries/errors/CommonErrors.sol @@ -15,3 +15,6 @@ error TransferFailed(); /// @notice Thrown when attempting to perform an operation and the account is the zero address. error ZeroAddress(); + +/// @notice Thrown when a function is called while the contract is paused. +error Paused(); diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 839fe3bd2aca..38e7fa6f9ec3 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -169,7 +169,8 @@ contract L1StandardBridge_Initialize_TestFail is CommonTest { } contract L1StandardBridge_Receive_Test is CommonTest { /// @dev Tests receive bridges ETH successfully. function test_receive_succeeds() external { - uint256 balanceBefore = address(optimismPortal).balance; + uint256 portalBalanceBefore = address(optimismPortal).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; // The legacy event must be emitted for backwards compatibility vm.expectEmit(address(l1StandardBridge)); @@ -193,7 +194,8 @@ contract L1StandardBridge_Receive_Test is CommonTest { vm.prank(alice, alice); (bool success,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertEq(success, true); - assertEq(address(optimismPortal).balance, balanceBefore + 100); + assertEq(address(optimismPortal).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + 100); } } @@ -223,7 +225,9 @@ contract PreBridgeETH is CommonTest { function _preBridgeETH(bool isLegacy, uint256 value) internal { if (!isForkTest()) { assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 0); } + uint256 nonce = l1CrossDomainMessenger.messageNonce(); uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION address l1MessengerAliased = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); @@ -289,12 +293,16 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { /// Emits ETHDepositInitiated and ETHBridgeInitiated events. /// Calls depositTransaction on the OptimismPortal. /// Only EOA can call depositETH. - /// ETH ends up in the optimismPortal. + /// ETH ends up in the sharedLockbox. function test_depositETH_succeeds() external { _preBridgeETH({ isLegacy: true, value: 500 }); - uint256 balanceBefore = address(optimismPortal).balance; + uint256 portalBalanceBefore = address(optimismPortal).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal).balance, balanceBefore + 500); + + assertEq(address(optimismPortal).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + 500); } } @@ -326,12 +334,16 @@ contract L1StandardBridge_BridgeETH_Test is PreBridgeETH { /// Emits ETHDepositInitiated and ETHBridgeInitiated events. /// Calls depositTransaction on the OptimismPortal. /// Only EOA can call bridgeETH. - /// ETH ends up in the optimismPortal. + /// ETH ends up in the sharedLockbox. function test_bridgeETH_succeeds() external { _preBridgeETH({ isLegacy: false, value: 500 }); - uint256 balanceBefore = address(optimismPortal).balance; + uint256 portalBalanceBefore = address(optimismPortal).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal).balance, balanceBefore + 500); + + assertEq(address(optimismPortal).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + 500); } } @@ -422,12 +434,16 @@ contract L1StandardBridge_DepositETHTo_Test is PreBridgeETHTo { /// Emits ETHDepositInitiated event. /// Calls depositTransaction on the OptimismPortal. /// EOA or contract can call depositETHTo. - /// ETH ends up in the optimismPortal. + /// ETH ends up in the sharedLockbox. function test_depositETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: true, value: 600 }); - uint256 balanceBefore = address(optimismPortal).balance; + uint256 portalBalanceBefore = address(optimismPortal).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal).balance, balanceBefore + 600); + + assertEq(address(optimismPortal).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + 600); } } @@ -459,12 +475,16 @@ contract L1StandardBridge_BridgeETHTo_Test is PreBridgeETHTo { /// Emits ETHDepositInitiated and ETHBridgeInitiated events. /// Calls depositTransaction on the OptimismPortal. /// Only EOA can call bridgeETHTo. - /// ETH ends up in the optimismPortal. + /// ETH ends up in the sharedLockbox. function test_bridgeETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: false, value: 600 }); - uint256 balanceBefore = address(optimismPortal).balance; + uint256 portalBalanceBefore = address(optimismPortal).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal).balance, balanceBefore + 600); + + assertEq(address(optimismPortal).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + 600); } } diff --git a/packages/contracts-bedrock/test/L1/LiquidityMigrator.t.sol b/packages/contracts-bedrock/test/L1/LiquidityMigrator.t.sol new file mode 100644 index 000000000000..44d2d7ea8051 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/LiquidityMigrator.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { CommonTest } from "test/setup/CommonTest.sol"; +import { LiquidityMigrator } from "src/L1/LiquidityMigrator.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; + +contract LiquidityMigratorTest is CommonTest { + event ETHMigrated(uint256 amount); + + function setUp() public virtual override { + super.enableInterop(); + super.setUp(); + } + + /// @notice Tests the migration of the contract's ETH balance to the SharedLockbox works properly. + function test_migrateETH_succeeds(uint256 _ethAmount) public { + vm.deal(address(liquidityMigrator), _ethAmount); + + // Get the balance of the migrator before the migration to compare later on the assertions + uint256 migratorEthBalance = address(liquidityMigrator).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + + // Set the migrator as an authorized portal so it can lock the ETH while migrating + vm.prank(address(superchainConfig)); + sharedLockbox.authorizePortal(address(liquidityMigrator)); + + // Look for the emit of the `ETHMigrated` event + vm.expectEmit(address(liquidityMigrator)); + emit ETHMigrated(migratorEthBalance); + + // Call the `migrateETH` function with the amount + liquidityMigrator.migrateETH(); + + // Assert the balances after the migration happened + assert(address(liquidityMigrator).balance == 0); + assert(address(sharedLockbox).balance == lockboxBalanceBefore + migratorEthBalance); + } + + /// @notice Tests the migration of the portal's ETH balance to the SharedLockbox works properly. + function test_portal_migrateETH_succeeds(uint256 _ethAmount) public { + vm.deal(address(optimismPortal2), _ethAmount); + + // Get the balance of the portal before the migration to compare later on the assertions + uint256 portalEthBalance = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + + // Get the proxy admin address and it's owner + IProxyAdmin proxyAdmin = IProxyAdmin(deploy.mustGetAddress("ProxyAdmin")); + address proxyAdminOwner = proxyAdmin.owner(); + + // Look for the emit of the `ETHMigrated` event + vm.expectEmit(address(optimismPortal2)); + emit ETHMigrated(portalEthBalance); + + // Update the portal proxy implementation to the LiquidityMigrator contract + vm.prank(proxyAdminOwner); + proxyAdmin.upgradeAndCall({ + _proxy: payable(optimismPortal2), + _implementation: address(liquidityMigrator), + _data: abi.encodeCall(LiquidityMigrator.migrateETH, ()) + }); + + // Assert the balances after the migration happened + assert(address(optimismPortal2).balance == 0); + assert(address(sharedLockbox).balance == lockboxBalanceBefore + portalEthBalance); + } +} diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 8334f0a2dc54..971165cbf01e 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -79,6 +79,7 @@ contract OptimismPortal2_Test is CommonTest { // This check is not valid on forked tests as the respectedGameType varies between OP Chains. assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); + assertEq(address(optimismPortal2.sharedLockbox()), address(sharedLockbox)); } /// @dev Tests that `pause` successfully pauses @@ -142,10 +143,26 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.paused(), true); } + /// @dev Tests that `sharedLockbox` returns correctly + function testFuzz_sharedLockbox_succeeds(address _caller, address _lockbox) external { + // Mock and expect the SuperchainConfig's SharedLockbox + vm.mockCall( + address(superchainConfig), abi.encodeCall(superchainConfig.SHARED_LOCKBOX, ()), abi.encode(_lockbox) + ); + vm.expectCall(address(superchainConfig), 0, abi.encodeCall(superchainConfig.SHARED_LOCKBOX, ())); + + vm.prank(_caller); + address _result = address(optimismPortal2.sharedLockbox()); + + assertEq(_result, _lockbox); + } + /// @dev Tests that `receive` successdully deposits ETH. function testFuzz_receive_succeeds(uint256 _value) external { - uint256 balanceBefore = address(optimismPortal2).balance; - _value = bound(_value, 0, type(uint256).max - balanceBefore); + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + + _value = bound(_value, 0, type(uint256).max - lockboxBalanceBefore); vm.expectEmit(address(optimismPortal2)); emitTransactionDeposited({ @@ -158,13 +175,17 @@ contract OptimismPortal2_Test is CommonTest { _data: hex"" }); + // Expect call to the SharedLockbox to lock the funds + if (_value > 0) vm.expectCall(address(sharedLockbox), _value, abi.encodeCall(sharedLockbox.lockETH, ())); + // give alice money and send as an eoa vm.deal(alice, _value); vm.prank(alice, alice); (bool s,) = address(optimismPortal2).call{ value: _value }(hex""); assertTrue(s); - assertEq(address(optimismPortal2).balance, balanceBefore + _value); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + _value); } /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero @@ -243,8 +264,9 @@ contract OptimismPortal2_Test is CommonTest { ); if (_isCreation) _to = address(0); - uint256 balanceBefore = address(optimismPortal2).balance; - _mint = bound(_mint, 0, type(uint256).max - balanceBefore); + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + _mint = bound(_mint, 0, type(uint256).max - lockboxBalanceBefore); // EOA emulation vm.expectEmit(address(optimismPortal2)); @@ -258,6 +280,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the SharedLockbox to lock the funds + if (_mint > 0) vm.expectCall(address(sharedLockbox), _mint, abi.encodeCall(sharedLockbox.lockETH, ())); + vm.deal(depositor, _mint); vm.prank(depositor, depositor); optimismPortal2.depositTransaction{ value: _mint }({ @@ -267,7 +292,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that `depositTransaction` succeeds for a contract. @@ -290,8 +317,9 @@ contract OptimismPortal2_Test is CommonTest { ); if (_isCreation) _to = address(0); - uint256 balanceBefore = address(optimismPortal2).balance; - _mint = bound(_mint, 0, type(uint256).max - balanceBefore); + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(sharedLockbox).balance; + _mint = bound(_mint, 0, type(uint256).max - lockboxBalanceBefore); vm.expectEmit(address(optimismPortal2)); emitTransactionDeposited({ @@ -304,6 +332,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the SharedLockbox to lock the funds + if (_mint > 0) vm.expectCall(address(sharedLockbox), _mint, abi.encodeCall(sharedLockbox.lockETH, ())); + vm.deal(address(this), _mint); vm.prank(address(this)); optimismPortal2.depositTransaction{ value: _mint }({ @@ -313,7 +344,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(sharedLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that the gas paying token can be set. @@ -559,8 +592,8 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Warp beyond the chess clocks and finalize the game. vm.warp(block.timestamp + game.maxClockDuration().raw() + 1 seconds); - // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), 0xFFFFFFFF); } /// @dev Asserts that the reentrant call will revert. @@ -944,9 +977,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { uint256 _proposedGameIndex_noData = disputeGameFactory.gameCount() - 1; // Warp beyond the chess clocks and finalize the game. vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); - // Fund the portal so that we can withdraw ETH. - vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _defaultTx_noData.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_defaultTx_noData.value))); uint256 bobBalanceBefore = bob.balance; @@ -1051,6 +1085,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_finalizeWithdrawalTransaction_provenWithdrawalHashEther_succeeds() external { uint256 bobBalanceBefore = address(bob).balance; + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _defaultTx.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_defaultTx.value))); + vm.expectEmit(address(optimismPortal2)); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(address(optimismPortal2)); @@ -1087,6 +1125,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Warp 1 second into the future so that the proof is submitted after the timestamp of game creation. vm.warp(block.timestamp + 1 seconds); + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _defaultTx.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_defaultTx.value))); + // Prove the withdrawal transaction against the invalid dispute game, as 0xb0b. vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); @@ -1264,6 +1306,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { uint256 bobBalanceBefore = address(bob).balance; vm.etch(bob, hex"fe"); // Contract with just the invalid opcode. + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _defaultTx.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_defaultTx.value))); + vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -1290,6 +1336,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `finalizeWithdrawalTransaction` reverts if the withdrawal has already been /// finalized. function test_finalizeWithdrawalTransaction_onReplay_reverts() external { + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _defaultTx.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_defaultTx.value))); + vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -1387,6 +1437,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Return a mock output root from the game. vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(outputRoot)); + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), _testTx.value); + vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (_testTx.value))); + vm.expectEmit(true, true, true, true); emit WithdrawalProven(withdrawalHash, alice, address(this)); vm.expectEmit(true, true, true, true); @@ -1426,7 +1480,9 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Total ETH supply is currently about 120M ETH. uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); + + // Add ETH to the SharedLockbox for the portal to withdraw. + vm.deal(address(sharedLockbox), value); uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -1475,6 +1531,9 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Warp past the finalization period vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + // Expect call to the SharedLockbox to unlock the funds + if (value > 0) vm.expectCall(address(sharedLockbox), abi.encodeCall(sharedLockbox.unlockETH, (value))); + // Finalize the withdrawal transaction vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); optimismPortal2.finalizeWithdrawalTransaction(_tx); diff --git a/packages/contracts-bedrock/test/L1/SharedLockbox.t.sol b/packages/contracts-bedrock/test/L1/SharedLockbox.t.sol new file mode 100644 index 000000000000..e76a3f142fcf --- /dev/null +++ b/packages/contracts-bedrock/test/L1/SharedLockbox.t.sol @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; +import { Unauthorized, Paused as PausedError } from "src/libraries/errors/CommonErrors.sol"; + +// Interfaces +import { IOptimismPortal } from "interfaces/L1/IOptimismPortal.sol"; + +contract SharedLockboxTest is CommonTest { + event ETHLocked(address indexed portal, uint256 amount); + + event ETHUnlocked(address indexed portal, uint256 amount); + + event PortalAuthorized(address indexed portal); + + function setUp() public virtual override { + super.enableInterop(); + super.setUp(); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function test_lockETH_unauthorizedPortal_reverts(address _caller) public { + vm.assume(!sharedLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); + + // Call the `lockETH` function with an unauthorized caller + vm.prank(_caller); + sharedLockbox.lockETH(); + } + + /// @notice Tests the ETH is correctly locked when the caller is an authorized portal. + function test_lockETH_succeeds(address _portal, uint256 _amount) public { + // Set the caller as an authorized portal + vm.prank(address(superchainConfig)); + sharedLockbox.authorizePortal(_portal); + + // Deal the ETH amount to the portal + vm.deal(_portal, _amount); + + // Get the balance of the portal and lockbox before the lock to compare later on the assertions + uint256 _portalBalanceBefore = address(_portal).balance; + uint256 _lockboxBalanceBefore = address(sharedLockbox).balance; + + // Look for the emit of the `ETHLocked` event + vm.expectEmit(address(sharedLockbox)); + emit ETHLocked(_portal, _amount); + + // Call the `lockETH` function with the portal + vm.prank(_portal); + sharedLockbox.lockETH{ value: _amount }(); + + // Assert the portal's balance decreased and the lockbox's balance increased by the amount locked + assertEq(address(_portal).balance, _portalBalanceBefore - _amount); + assertEq(address(sharedLockbox).balance, _lockboxBalanceBefore + _amount); + } + + /// @notice Tests `unlockETH` reverts when the contract is paused. + function test_unlockETH_paused_reverts(address _caller, uint256 _value) public { + // Set the paused status to true + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("test"); + + // Expect the revert with `Paused` selector + vm.expectRevert(PausedError.selector); + + // Call the `unlockETH` function with the caller + vm.prank(_caller); + sharedLockbox.unlockETH(_value); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function test_unlockETH_unauthorizedPortal_reverts(address _caller, uint256 _value) public { + vm.assume(!sharedLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); + + // Call the `unlockETH` function with an unauthorized caller + vm.prank(_caller); + sharedLockbox.unlockETH(_value); + } + + /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. + function test_unlockETH_succeeds(uint256 _value) public { + // Set the caller as an authorized portal + vm.prank(address(superchainConfig)); + sharedLockbox.authorizePortal(address(optimismPortal2)); + + // Deal the ETH amount to the lockbox + vm.deal(address(sharedLockbox), _value); + + // Get the balance of the portal and lockbox before the unlock to compare later on the assertions + uint256 _portalBalanceBefore = address(optimismPortal2).balance; + uint256 _lockboxBalanceBefore = address(sharedLockbox).balance; + + // Expect `donateETH` function to be called on Portal + vm.expectCall(address(optimismPortal2), abi.encodeWithSelector(IOptimismPortal.donateETH.selector)); + + // Look for the emit of the `ETHUnlocked` event + vm.expectEmit(address(sharedLockbox)); + emit ETHUnlocked(address(optimismPortal2), _value); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + sharedLockbox.unlockETH(_value); + + // Assert the portal's balance increased and the lockbox's balance decreased by the amount unlocked + assertEq(address(optimismPortal2).balance, _portalBalanceBefore + _value); + assertEq(address(sharedLockbox).balance, _lockboxBalanceBefore - _value); + } + + /// @notice Tests `authorizePortal` reverts when the contract is paused. + function test_authorizePortal_paused_reverts(address _caller, address _portal) public { + // Set the paused status to true + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("test"); + + // Expect the revert with `Paused` selector + vm.expectRevert(PausedError.selector); + + // Call the `authorizePortal` function with the caller + vm.prank(_caller); + sharedLockbox.authorizePortal(_portal); + } + + /// @notice Tests it reverts when the caller is not the SuperchainConfig. + function test_authorizePortal_notSuperchainConfig_reverts(address _caller) public { + vm.assume(_caller != address(superchainConfig)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(Unauthorized.selector); + + // Call the `authorizePortal` function with a non-SuperchainConfig caller + vm.prank(_caller); + sharedLockbox.authorizePortal(_caller); + } + + /// @notice Tests the portal is correctly authorized when the caller is the SuperchainConfig. + function test_authorizePortal_succeeds(address _portal) public { + // Check the portal's authorized status before the authorization to compare later on the assertions. + // Adding this check to make it more future proof in case something changes on the setup. + vm.assume(sharedLockbox.authorizedPortals(_portal) == false); + + // Look for the emit of the `PortalAuthorized` event + vm.expectEmit(address(sharedLockbox)); + emit PortalAuthorized(_portal); + + // Call the `authorizePortal` function with the SuperchainConfig + vm.prank(address(superchainConfig)); + sharedLockbox.authorizePortal(_portal); + + // Assert the portal's authorized status was updated correctly + assertEq(sharedLockbox.authorizedPortals(_portal), true); + } + + /// @notice Tests the paused status is correctly returned. + function test_paused_succeeds() public { + // Assert the paused status is false + assertEq(sharedLockbox.paused(), false); + + // Set the paused status to true + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("test"); + + // Assert the paused status is true + assertEq(sharedLockbox.paused(), true); + } +} diff --git a/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol b/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol index db70b7336f7e..5ed4eecc0b4f 100644 --- a/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SuperchainConfig.t.sol @@ -5,11 +5,37 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Target contract dependencies import { IProxy } from "interfaces/universal/IProxy.sol"; +import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; // Target contract import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { SuperchainConfig, ISharedLockbox, ISystemConfigInterop } from "src/L1/SuperchainConfig.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +/// @notice For testing purposes contract, with setters to facilitate replicating complex scenarios when needed. +contract SuperchainConfigForTest is SuperchainConfig { + using EnumerableSet for EnumerableSet.UintSet; + + constructor(address _sharedLockbox) SuperchainConfig(_sharedLockbox) { } + + function forTest_addChainOnDependencySet(uint256 _chainId) external { + _dependencySet.add(_chainId); + } + + function forTest_addChainAndSystemConfig(uint256 _chainId, address _systemConfig) external { + _dependencySet.add(_chainId); + systemConfigs[_chainId] = _systemConfig; + } + + function forTest_setUpgrader(address _upgrader) external { + bytes32 slot = UPGRADER_SLOT; + assembly { + sstore(slot, _upgrader) + } + } +} contract SuperchainConfig_Init_Test is CommonTest { function setUp() public virtual override { @@ -18,9 +44,10 @@ contract SuperchainConfig_Init_Test is CommonTest { } /// @dev Tests that initialization sets the correct values. These are defined in CommonTest.sol. - function test_initialize_unpaused_succeeds() external view { + function test_initialize_succeeds() external view { assertFalse(superchainConfig.paused()); assertEq(superchainConfig.guardian(), deploy.cfg().superchainConfigGuardian()); + assertEq(superchainConfig.upgrader(), deploy.cfg().finalSystemOwner()); } /// @dev Tests that it can be intialized as paused. @@ -34,18 +61,24 @@ contract SuperchainConfig_Init_Test is CommonTest { ISuperchainConfig newImpl = ISuperchainConfig( DeployUtils.create1({ _name: "SuperchainConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ISuperchainConfig.__constructor__, (address(sharedLockbox))) + ) }) ); vm.startPrank(alice); newProxy.upgradeToAndCall( address(newImpl), - abi.encodeCall(ISuperchainConfig.initialize, (deploy.cfg().superchainConfigGuardian(), true)) + abi.encodeCall( + ISuperchainConfig.initialize, + (deploy.cfg().superchainConfigGuardian(), deploy.cfg().finalSystemOwner(), true) + ) ); assertTrue(ISuperchainConfig(address(newProxy)).paused()); assertEq(ISuperchainConfig(address(newProxy)).guardian(), deploy.cfg().superchainConfigGuardian()); + assertEq(ISuperchainConfig(address(newProxy)).upgrader(), deploy.cfg().finalSystemOwner()); } } @@ -110,3 +143,216 @@ contract SuperchainConfig_Unpause_Test is CommonTest { assertFalse(superchainConfig.paused()); } } + +contract SuperchainConfig_AddChain_Test is CommonTest { + event ChainAdded(uint256 indexed chainId, address indexed systemConfig, address indexed portal); + + function setUp() public virtual override { + super.enableInterop(); + super.setUp(); + } + + function _mockAndExpect(address _target, bytes memory _calldata, bytes memory _returnData) internal { + vm.mockCall(_target, _calldata, _returnData); + vm.expectCall(_target, _calldata); + } + + /// @notice Tests that `addChain` reverts when called by an unauthorized address. + function test_addChain_unauthorized_reverts(address _caller, uint256 _chainId, address _systemConfig) external { + vm.assume(_caller != superchainConfig.upgrader()); + + vm.expectRevert(Unauthorized.selector); + vm.prank(_caller); + superchainConfig.addChain(_chainId, _systemConfig); + } + + /// @notice Tests that `addChain` reverts when the input chain already contains dependencies on its set. + function test_addChain_alreadyHasDependencies_reverts(uint256 _chainId, address _systemConfig) external { + // Mock the number of dependencies to be greater than 0. + uint256 numberOfDependencies = 1; + _mockAndExpect( + _systemConfig, + abi.encodeWithSelector(ISystemConfigInterop.dependencyCounter.selector), + abi.encode(numberOfDependencies) + ); + + vm.prank(superchainConfig.upgrader()); + vm.expectRevert(ISuperchainConfig.ChainAlreadyHasDependencies.selector); + superchainConfig.addChain(_chainId, _systemConfig); + } + + /// @notice Tests that `addChain` reverts when the chain is already in the dependency set. + function test_addChain_chainAlreadyExists_reverts(uint256 _chainId, address _systemConfig) external { + SuperchainConfigForTest superchainConfig = new SuperchainConfigForTest(address(sharedLockbox)); + superchainConfig.forTest_addChainOnDependencySet(_chainId); + + // Mock the call over `dependencyCounter` to return 0 and avoid a previous revert + _mockAndExpect( + _systemConfig, abi.encodeWithSelector(ISystemConfigInterop.dependencyCounter.selector), abi.encode(0) + ); + + vm.prank(superchainConfig.upgrader()); + vm.expectRevert(SuperchainConfig.ChainAlreadyAdded.selector); + superchainConfig.addChain(_chainId, _systemConfig); + } + + /// @notice Tests that `addChain` successfully adds a chain to the dependency set when it is empty. + function test_addChain_onEmptyDependencySet_succeeds(uint256 _chainId, address _portal) external { + vm.assume(!superchainConfig.isInDependencySet(_chainId)); + + // Store the PORTAL address we expect to be used in a call in the SystemConfig OptimsimPortal slot, and expect + // it to be called + vm.store( + address(systemConfig), + bytes32(uint256(keccak256("systemconfig.optimismportal")) - 1), + bytes32(uint256(uint160(_portal))) + ); + vm.expectCall(address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.optimismPortal.selector)); + + // Mock and expect the call to authorize the portal on the SharedLockbox with the `_portal` address + vm.expectCall(address(sharedLockbox), abi.encodeWithSelector(ISharedLockbox.authorizePortal.selector, _portal)); + + // Expect the `addDependency` function call to not be called since the dependency set is empty + uint64 zeroCalls = 0; + vm.expectCall( + address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector), zeroCalls + ); + + // Expect the ChainAdded event to be emitted + vm.expectEmit(address(superchainConfig)); + emit ChainAdded(_chainId, address(systemConfig), _portal); + + // Add the new chain to the dependency set + vm.prank(superchainConfig.upgrader()); + superchainConfig.addChain(_chainId, address(systemConfig)); + + // Check that the new chain is in the dependency set + assertTrue(superchainConfig.isInDependencySet(_chainId)); + } + + /// @notice Tests that `addChain` successfully adds a chain to the dependency set when it is not empty. + /// This tests deploys a new SuperchainConfigForTest contract and mocks several calls regarding SystemConfig + /// and SharedLockbox contracts of the added chains with the purpose of reducing test complexity and making + /// it more readable on the trade-off of getting a less realistic environment -- but finally checking the + /// logic that is being tested when having multiple dependencies. + function test_addChain_withMultipleDependencies_succeeds(uint256 _chainId, address _portal) external { + vm.assume(_chainId > 3); + + // Deploy a new SuperchainConfigForTest contract and set the address(sharedLockbox) and upgrader addresses + SuperchainConfigForTest superchainConfigForTest = new SuperchainConfigForTest(address(sharedLockbox)); + superchainConfigForTest.forTest_setUpgrader(superchainConfig.upgrader()); + + // Define the chains to be added to the dependency set + (uint256 chainIdOne, address systemConfigOne) = (1, makeAddr("SystemConfigOne")); + (uint256 chainIdTwo, address systemConfigTwo) = (2, makeAddr("SystemConfigTwo")); + (uint256 chainIdThree, address systemConfigThree) = (3, makeAddr("SystemConfigThree")); + + // Add the first three chains to the dependency set + superchainConfigForTest.forTest_addChainAndSystemConfig(chainIdOne, systemConfigOne); + superchainConfigForTest.forTest_addChainAndSystemConfig(chainIdTwo, systemConfigTwo); + superchainConfigForTest.forTest_addChainAndSystemConfig(chainIdThree, systemConfigThree); + + // Mock and expect the call to `dependencyCounter` to return 0 for the new chain + _mockAndExpect( + address(systemConfig), + abi.encodeWithSelector(ISystemConfigInterop.dependencyCounter.selector), + abi.encode(0) + ); + + // Mock and expect the calls when looping through the first chain of the dependency set + _mockAndExpect( + systemConfigOne, abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, _chainId), "" + ); + _mockAndExpect( + address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, chainIdOne), "" + ); + + // Mock and expect the calls when looping through the second chain of the dependency set + _mockAndExpect( + systemConfigTwo, abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, _chainId), "" + ); + _mockAndExpect( + address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, chainIdTwo), "" + ); + + // Mock and expect the calls when looping through the third chain of the dependency set + _mockAndExpect( + systemConfigThree, abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, _chainId), "" + ); + _mockAndExpect( + address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.addDependency.selector, chainIdThree), "" + ); + + // Store the PORTAL address we expect to be used in a call in the SystemConfig's OptimsimPortal slot, and expect + // it to be called + vm.store( + address(systemConfig), + bytes32(uint256(keccak256("systemconfig.optimismportal")) - 1), + bytes32(uint256(uint160(_portal))) + ); + vm.expectCall(address(systemConfig), abi.encodeWithSelector(ISystemConfigInterop.optimismPortal.selector)); + + // Mock and expect the call to authorize the portal on the SharedLockbox with the `_portal` address + _mockAndExpect( + address(sharedLockbox), abi.encodeWithSelector(ISharedLockbox.authorizePortal.selector, _portal), "" + ); + + // Expect the ChainAdded event to be emitted + vm.expectEmit(address(superchainConfigForTest)); + emit ChainAdded(_chainId, address(systemConfig), _portal); + + // Add the new chain to the dependency set + vm.prank(superchainConfigForTest.upgrader()); + superchainConfigForTest.addChain(_chainId, address(systemConfig)); + + // Check that the new chain is in the dependency set + assertTrue(superchainConfigForTest.isInDependencySet(_chainId)); + } +} + +contract SuperchainConfig_IsInDependencySet_Test is CommonTest { + /// @dev Tests that `isInDependencySet` returns false when the chain is not in the dependency set. Checking if empty + /// to ensure that should always be false. + function test_isInDependencySet_false_succeeds(uint256 _chainId) external view { + assert(superchainConfig.dependencySet().length == 0); + assertFalse(superchainConfig.isInDependencySet(_chainId)); + } + + /// @dev Tests that `isInDependencySet` returns true when the chain is in the dependency set. + function test_isInDependencySet_true_succeeds(uint256 _chainId) external { + SuperchainConfigForTest superchainConfig = new SuperchainConfigForTest(address(sharedLockbox)); + superchainConfig.forTest_addChainOnDependencySet(_chainId); + + assertTrue(superchainConfig.isInDependencySet(_chainId)); + } +} + +contract SuperchainConfig_DependencySet_Test is CommonTest { + using EnumerableSet for EnumerableSet.UintSet; + + EnumerableSet.UintSet internal chainIds; + + /// @notice Tests that the dependency set returns properly the dependencies added. + function test_dependencySet_succeeds(uint256[] calldata _chainIdsArray) public { + SuperchainConfigForTest superchainConfigForTest = new SuperchainConfigForTest(address(sharedLockbox)); + + // Ensure there are no repeated values on the input array + for (uint256 i; i < _chainIdsArray.length; i++) { + chainIds.add(_chainIdsArray[i]); + } + + // Add the dependencies to the dependency set + for (uint256 i; i < chainIds.length(); i++) { + superchainConfigForTest.forTest_addChainOnDependencySet(chainIds.at(i)); + } + + // Check that the dependency set has the same length as the dependencies + uint256[] memory dependencySet = superchainConfigForTest.dependencySet(); + assertEq(dependencySet.length, chainIds.length()); + + // Check that the dependency set has the same chain IDs as the dependencies + for (uint256 i; i < chainIds.length(); i++) { + assertEq(dependencySet[i], chainIds.at(i)); + } + } +} diff --git a/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol b/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol index 8fd6daeed846..707be99c2481 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfigInterop.t.sol @@ -26,6 +26,13 @@ contract SystemConfigInterop_Test is CommonTest { super.setUp(); } + /// @dev Tests that the constructor sets the correct values. + function test_constructor_succeeds() external view { + ISystemConfigInterop impl = ISystemConfigInterop(payable(deploy.mustGetAddress("SystemConfig"))); + assertEq(impl.SUPERCHAIN_CONFIG(), address(superchainConfig)); + assertEq(_systemConfigInterop().SUPERCHAIN_CONFIG(), address(superchainConfig)); + } + /// @dev Tests that when the decimals is not 18, initialization reverts. function test_initialize_decimalsIsNot18_reverts(uint8 decimals) external { vm.assume(decimals != 18); @@ -96,20 +103,27 @@ contract SystemConfigInterop_Test is CommonTest { ) ); - vm.prank(_systemConfigInterop().dependencyManager()); + vm.prank(address(superchainConfig)); _systemConfigInterop().addDependency(_chainId); + + assertEq(_systemConfigInterop().dependencyCounter(), 1); } - /// @dev Tests that adding a dependency as not the dependency manager reverts. - function testFuzz_addDependency_notDependencyManager_reverts(uint256 _chainId) public { - require(alice != _systemConfigInterop().dependencyManager(), "SystemConfigInterop_Test: 100"); - vm.expectRevert("SystemConfig: caller is not the dependency manager"); + /// @dev Tests that adding a dependency as not the SuperchainConfig reverts. + function testFuzz_addDependency_notSuperchainConfig_reverts(uint256 _chainId) public { + require(alice != address(superchainConfig), "SystemConfigInterop_Test: 100"); + vm.expectRevert("SystemConfig: caller is not the SuperchainConfig"); vm.prank(alice); _systemConfigInterop().addDependency(_chainId); } /// @dev Tests that a dependency can be removed. function testFuzz_removeDependency_succeeds(uint256 _chainId) public { + // Add the dependency first + vm.prank(address(superchainConfig)); + _systemConfigInterop().addDependency(_chainId); + assertEq(_systemConfigInterop().dependencyCounter(), 1); + vm.expectCall( address(optimismPortal), abi.encodeCall( @@ -118,18 +132,34 @@ contract SystemConfigInterop_Test is CommonTest { ) ); - vm.prank(_systemConfigInterop().dependencyManager()); + vm.prank(address(superchainConfig)); _systemConfigInterop().removeDependency(_chainId); + + assertEq(_systemConfigInterop().dependencyCounter(), 0); } - /// @dev Tests that removing a dependency as not the dependency manager reverts. - function testFuzz_removeDependency_notDependencyManager_reverts(uint256 _chainId) public { - require(alice != _systemConfigInterop().dependencyManager(), "SystemConfigInterop_Test: 100"); - vm.expectRevert("SystemConfig: caller is not the dependency manager"); + /// @dev Tests that removing a dependency as not the SuperchainConfig reverts. + function testFuzz_removeDependency_notSuperchainConfig_reverts(uint256 _chainId) public { + require(alice != address(superchainConfig), "SystemConfigInterop_Test: 100"); + vm.expectRevert("SystemConfig: caller is not the SuperchainConfig"); vm.prank(alice); _systemConfigInterop().removeDependency(_chainId); } + function test_dependencyCounter_succeeds() public { + assertEq(_systemConfigInterop().dependencyCounter(), 0); + + // Add a dependency + vm.prank(address(superchainConfig)); + _systemConfigInterop().addDependency(1); + assertEq(_systemConfigInterop().dependencyCounter(), 1); + + // Remove the dependency + vm.prank(address(superchainConfig)); + _systemConfigInterop().removeDependency(1); + assertEq(_systemConfigInterop().dependencyCounter(), 0); + } + /// @dev Helper to clean storage and then initialize the system config with an arbitrary gas token address. function _cleanStorageAndInit(address _token) internal { // Wipe out the initialized slot so the proxy can be initialized again diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol index 0a870bc651f3..2d8934da4065 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol @@ -136,8 +136,8 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { game.resolveClaim(0, 0); game.resolve(); - // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + // Fund the SharedLockbox so that we can withdraw ETH. + vm.deal(address(sharedLockbox), 0xFFFFFFFF); } } diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index 584aa59a9c2b..ab0599e47548 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -11,6 +11,7 @@ import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol" import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -39,6 +40,7 @@ contract DeployImplementationsInput_Test is Test { string release = "dev-release"; // this means implementation contracts will be deployed ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + ISharedLockbox sharedLockboxProxy = ISharedLockbox(makeAddr("sharedLockboxProxy")); function setUp() public { dii = new DeployImplementationsInput(); @@ -71,6 +73,9 @@ contract DeployImplementationsInput_Test is Test { vm.expectRevert("DeployImplementationsInput: not set"); dii.standardVersionsToml(); + + vm.expectRevert("DeployImplementationsInput: not set"); + dii.sharedLockboxProxy(); } } @@ -223,6 +228,7 @@ contract DeployImplementations_Test is Test { uint256 disputeGameFinalityDelaySeconds = 500; ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + ISharedLockbox sharedLockboxProxy = ISharedLockbox(makeAddr("sharedLockboxProxy")); function setUp() public virtual { deployImplementations = new DeployImplementations(); @@ -342,6 +348,7 @@ contract DeployImplementations_Test is Test { vm.etch(address(superchainProxyAdmin), address(superchainProxyAdmin).code); vm.etch(address(superchainConfigProxy), address(superchainConfigProxy).code); vm.etch(address(protocolVersionsProxy), hex"01"); + vm.etch(address(sharedLockboxProxy), hex"01"); dii.set(dii.withdrawalDelaySeconds.selector, withdrawalDelaySeconds); dii.set(dii.minProposalSizeBytes.selector, minProposalSizeBytes); @@ -352,6 +359,7 @@ contract DeployImplementations_Test is Test { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.sharedLockboxProxy.selector, address(sharedLockboxProxy)); deployImplementations.run(dii, dio); @@ -365,6 +373,7 @@ contract DeployImplementations_Test is Test { assertEq(release, dii.l1ContractsRelease(), "525"); assertEq(address(superchainConfigProxy), address(dii.superchainConfigProxy()), "550"); assertEq(address(protocolVersionsProxy), address(dii.protocolVersionsProxy()), "575"); + assertEq(address(sharedLockboxProxy), address(dii.sharedLockboxProxy()), "577"); // Architecture assertions. assertEq(address(dio.mipsSingleton().oracle()), address(dio.preimageOracleSingleton()), "600"); @@ -386,6 +395,7 @@ contract DeployImplementations_Test is Test { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.sharedLockboxProxy.selector, address(sharedLockboxProxy)); // Set the challenge period to a value that is too large, using vm.store because the setter // method won't allow it. diff --git a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol index 5ac899579d65..98d77f561d3b 100644 --- a/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployOPChain.t.sol @@ -23,6 +23,7 @@ import { IL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; @@ -314,7 +315,7 @@ contract DeployOPChain_TestBase is Test { ProtocolVersion recommendedProtocolVersion = ProtocolVersion.wrap(2); // Define default inputs for DeployImplementations. - // `superchainConfigProxy` and `protocolVersionsProxy` are set during `setUp` since they are + // `superchainConfigProxy`, `protocolVersionsProxy` and `sharedLockboxProxy` are set during `setUp` since they are // outputs of the previous step. uint256 withdrawalDelaySeconds = 100; uint256 minProposalSizeBytes = 200; @@ -324,6 +325,7 @@ contract DeployOPChain_TestBase is Test { string release = "dev-release"; // this means implementation contracts will be deployed ISuperchainConfig superchainConfigProxy; IProtocolVersions protocolVersionsProxy; + ISharedLockbox sharedLockboxProxy; // Define default inputs for DeployOPChain. // `opcm` is set during `setUp` since it is an output of the previous step. @@ -384,6 +386,7 @@ contract DeployOPChain_TestBase is Test { // Populate the inputs for DeployImplementations based on the output of DeploySuperchain. superchainConfigProxy = dso.superchainConfigProxy(); protocolVersionsProxy = dso.protocolVersionsProxy(); + sharedLockboxProxy = dso.sharedLockboxProxy(); // Configure and deploy Implementation contracts DeployImplementations deployImplementations = createDeployImplementationsContract(); @@ -398,6 +401,7 @@ contract DeployOPChain_TestBase is Test { dii.set(dii.l1ContractsRelease.selector, release); dii.set(dii.superchainConfigProxy.selector, address(superchainConfigProxy)); dii.set(dii.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dii.set(dii.sharedLockboxProxy.selector, address(sharedLockboxProxy)); // End users of the DeployImplementations contract will need to set the `standardVersionsToml`. string memory standardVersionsTomlPath = string.concat(vm.projectRoot(), "/test/fixtures/standard-versions.toml"); diff --git a/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol b/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol index 93c3c0c9344a..cd1d8d5ebf8d 100644 --- a/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol @@ -7,6 +7,8 @@ import { stdToml } from "forge-std/StdToml.sol"; import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; import { Proxy } from "src/universal/Proxy.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { SharedLockbox } from "src/L1/SharedLockbox.sol"; +import { LiquidityMigrator } from "src/L1/LiquidityMigrator.sol"; import { IProtocolVersions, ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; @@ -60,6 +62,9 @@ contract DeploySuperchainOutput_Test is Test { SuperchainConfig superchainConfigProxy = SuperchainConfig(makeAddr("superchainConfigProxy")); IProtocolVersions protocolVersionsImpl = IProtocolVersions(makeAddr("protocolVersionsImpl")); IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); + SharedLockbox sharedLockboxImpl = SharedLockbox(makeAddr("sharedLockboxImpl")); + SharedLockbox sharedLockboxProxy = SharedLockbox(makeAddr("sharedLockboxProxy")); + LiquidityMigrator liquidityMigratorImpl = LiquidityMigrator(makeAddr("liquidityMigratorImpl")); // Ensure each address has code, since these are expected to be contracts. vm.etch(address(superchainProxyAdmin), hex"01"); @@ -67,6 +72,9 @@ contract DeploySuperchainOutput_Test is Test { vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsImpl), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); + vm.etch(address(sharedLockboxImpl), hex"01"); + vm.etch(address(sharedLockboxProxy), hex"01"); + vm.etch(address(liquidityMigratorImpl), hex"01"); // Set the output data. dso.set(dso.superchainProxyAdmin.selector, address(superchainProxyAdmin)); @@ -74,6 +82,9 @@ contract DeploySuperchainOutput_Test is Test { dso.set(dso.superchainConfigProxy.selector, address(superchainConfigProxy)); dso.set(dso.protocolVersionsImpl.selector, address(protocolVersionsImpl)); dso.set(dso.protocolVersionsProxy.selector, address(protocolVersionsProxy)); + dso.set(dso.sharedLockboxImpl.selector, address(sharedLockboxImpl)); + dso.set(dso.sharedLockboxProxy.selector, address(sharedLockboxProxy)); + dso.set(dso.liquidityMigratorImpl.selector, address(liquidityMigratorImpl)); // Compare the test data to the getter methods. assertEq(address(superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100"); @@ -81,6 +92,9 @@ contract DeploySuperchainOutput_Test is Test { assertEq(address(superchainConfigProxy), address(dso.superchainConfigProxy()), "300"); assertEq(address(protocolVersionsImpl), address(dso.protocolVersionsImpl()), "400"); assertEq(address(protocolVersionsProxy), address(dso.protocolVersionsProxy()), "500"); + assertEq(address(sharedLockboxImpl), address(dso.sharedLockboxImpl()), "600"); + assertEq(address(sharedLockboxProxy), address(dso.sharedLockboxProxy()), "700"); + assertEq(address(liquidityMigratorImpl), address(dso.liquidityMigratorImpl()), "800"); } function test_getters_whenNotSet_reverts() public { @@ -95,6 +109,15 @@ contract DeploySuperchainOutput_Test is Test { vm.expectRevert("DeployUtils: zero address"); dso.protocolVersionsProxy(); + + vm.expectRevert("DeployUtils: zero address"); + dso.sharedLockboxImpl(); + + vm.expectRevert("DeployUtils: zero address"); + dso.sharedLockboxProxy(); + + vm.expectRevert("DeployUtils: zero address"); + dso.liquidityMigratorImpl(); } function test_getters_whenAddrHasNoCode_reverts() public { @@ -116,6 +139,18 @@ contract DeploySuperchainOutput_Test is Test { dso.set(dso.protocolVersionsProxy.selector, emptyAddr); vm.expectRevert(expectedErr); dso.protocolVersionsProxy(); + + dso.set(dso.sharedLockboxImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dso.sharedLockboxImpl(); + + dso.set(dso.sharedLockboxProxy.selector, emptyAddr); + vm.expectRevert(expectedErr); + dso.sharedLockboxProxy(); + + dso.set(dso.liquidityMigratorImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dso.liquidityMigratorImpl(); } } @@ -176,17 +211,20 @@ contract DeploySuperchain_Test is Test { assertEq(dso.superchainConfigProxy().paused(), paused, "400"); assertEq(unwrap(dso.protocolVersionsProxy().required()), unwrap(requiredProtocolVersion), "500"); assertEq(unwrap(dso.protocolVersionsProxy().recommended()), unwrap(recommendedProtocolVersion), "600"); + assertEq(address(dso.sharedLockboxProxy().SUPERCHAIN_CONFIG()), address(dso.superchainConfigProxy()), "700"); // Architecture assertions. // We prank as the zero address due to the Proxy's `proxyCallIfNotAdmin` modifier. Proxy superchainConfigProxy = Proxy(payable(address(dso.superchainConfigProxy()))); Proxy protocolVersionsProxy = Proxy(payable(address(dso.protocolVersionsProxy()))); + Proxy sharedLockboxProxy = Proxy(payable(address(dso.sharedLockboxProxy()))); vm.startPrank(address(0)); assertEq(superchainConfigProxy.implementation(), address(dso.superchainConfigImpl()), "700"); assertEq(protocolVersionsProxy.implementation(), address(dso.protocolVersionsImpl()), "800"); assertEq(superchainConfigProxy.admin(), protocolVersionsProxy.admin(), "900"); assertEq(superchainConfigProxy.admin(), address(dso.superchainProxyAdmin()), "1000"); + assertEq(sharedLockboxProxy.implementation(), address(dso.sharedLockboxImpl()), "1100"); vm.stopPrank(); // Ensure that `checkOutput` passes. This is called by the `run` function during execution, diff --git a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol index 2dca24b486bd..72f7fbdaab9e 100644 --- a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol +++ b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol @@ -79,12 +79,12 @@ contract SetDisputeGameImpl_Test is Test { input = new SetDisputeGameImplInput(); DisputeGameFactory dgfImpl = new DisputeGameFactory(); OptimismPortal2 portalImpl = new OptimismPortal2(0, 0); - SuperchainConfig supConfigImpl = new SuperchainConfig(); + SuperchainConfig supConfigImpl = new SuperchainConfig(address(0)); Proxy supConfigProxy = new Proxy(address(1)); vm.prank(address(1)); supConfigProxy.upgradeToAndCall( - address(supConfigImpl), abi.encodeCall(supConfigImpl.initialize, (address(this), false)) + address(supConfigImpl), abi.encodeCall(supConfigImpl.initialize, (address(this), address(this), false)) ); Proxy factoryProxy = new Proxy(address(1)); diff --git a/packages/contracts-bedrock/test/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index 26283afdc9e1..5caa492b97dc 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -97,6 +97,12 @@ contract CommonTest is Test, Setup, Events { // Deploy L2 Setup.L2(); + // Authorize portals to interact with the SharedLockbox. + vm.startPrank(address(superchainConfig)); + sharedLockbox.authorizePortal(address(optimismPortal)); + sharedLockbox.authorizePortal(address(optimismPortal2)); + vm.stopPrank(); + // Call bridge initializer setup function bridgeInitializerSetUp(); } diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 7c4f019968cc..9aa97b3458d1 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -25,6 +25,8 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s import { IL2OutputOracle } from "interfaces/L1/IL2OutputOracle.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { ISharedLockbox } from "interfaces/L1/ISharedLockbox.sol"; +import { ILiquidityMigrator } from "interfaces/L1/ILiquidityMigrator.sol"; import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; @@ -92,6 +94,8 @@ contract Setup { IProtocolVersions protocolVersions; ISuperchainConfig superchainConfig; IDataAvailabilityChallenge dataAvailabilityChallenge; + ISharedLockbox sharedLockbox; + ILiquidityMigrator liquidityMigrator; // L2 contracts IL2CrossDomainMessenger l2CrossDomainMessenger = @@ -206,6 +210,8 @@ contract Setup { protocolVersions = IProtocolVersions(deploy.mustGetAddress("ProtocolVersionsProxy")); superchainConfig = ISuperchainConfig(deploy.mustGetAddress("SuperchainConfigProxy")); anchorStateRegistry = IAnchorStateRegistry(deploy.mustGetAddress("AnchorStateRegistryProxy")); + sharedLockbox = ISharedLockbox(deploy.mustGetAddress("SharedLockboxProxy")); + liquidityMigrator = ILiquidityMigrator(deploy.mustGetAddress("LiquidityMigrator")); vm.label(address(optimismPortal), "OptimismPortal"); vm.label(deploy.mustGetAddress("OptimismPortalProxy"), "OptimismPortalProxy"); @@ -228,6 +234,9 @@ contract Setup { vm.label(deploy.mustGetAddress("ProtocolVersionsProxy"), "ProtocolVersionsProxy"); vm.label(address(superchainConfig), "SuperchainConfig"); vm.label(deploy.mustGetAddress("SuperchainConfigProxy"), "SuperchainConfigProxy"); + vm.label(address(sharedLockbox), "SharedLockbox"); + vm.label(deploy.mustGetAddress("SharedLockboxProxy"), "SharedLockboxProxy"); + vm.label(address(liquidityMigrator), "LiquidtyMigrator"); vm.label(address(anchorStateRegistry), "AnchorStateRegistryProxy"); vm.label(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)), "L1CrossDomainMessenger_aliased"); diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 69b2fa84c5a7..ba7cea19fbd0 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -44,7 +44,8 @@ contract Specification_Test is CommonTest { DELAYEDWETHOWNER, COUNCILSAFE, COUNCILSAFEOWNER, - DEPENDENCYMANAGER + PORTAL, + SUPERCHAINCONFIG } /// @notice Represents the specification of a function. @@ -335,6 +336,7 @@ contract Specification_Test is CommonTest { _sel: IOptimismPortalInterop.setConfig.selector, _auth: Role.SYSTEMCONFIGOWNER }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("sharedLockbox()") }); // OptimismPortal2 _addSpec({ _name: "OptimismPortal2", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); @@ -382,6 +384,7 @@ contract Specification_Test is CommonTest { _sel: _getSel("depositERC20Transaction(address,uint256,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("setGasPayingToken(address,uint8,bytes32,bytes32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("sharedLockbox()") }); // ProtocolVersions _addSpec({ _name: "ProtocolVersions", _sel: _getSel("RECOMMENDED_SLOT()") }); @@ -410,13 +413,29 @@ contract Specification_Test is CommonTest { // SuperchainConfig _addSpec({ _name: "SuperchainConfig", _sel: _getSel("GUARDIAN_SLOT()") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("UPGRADER_SLOT()") }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("PAUSED_SLOT()") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("SHARED_LOCKBOX()") }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("guardian()") }); - _addSpec({ _name: "SuperchainConfig", _sel: _getSel("initialize(address,bool)") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("upgrader()") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("systemConfigs(uint256)") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("initialize(address,address,bool)") }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("pause(string)"), _auth: Role.GUARDIAN }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("paused()") }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("unpause()"), _auth: Role.GUARDIAN }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("version()") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("addChain(uint256,address)") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("isInDependencySet(uint256)") }); + _addSpec({ _name: "SuperchainConfig", _sel: _getSel("dependencySet()") }); + + // SharedLockbox + _addSpec({ _name: "SharedLockbox", _sel: _getSel("SUPERCHAIN_CONFIG()") }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("authorizedPortals(address)") }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("lockETH()"), _auth: Role.PORTAL }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("unlockETH(uint256)"), _auth: Role.PORTAL }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("authorizePortal(address)"), _auth: Role.SUPERCHAINCONFIG }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("version()") }); + _addSpec({ _name: "SharedLockbox", _sel: _getSel("paused()") }); // SystemConfig _addSpec({ _name: "SystemConfig", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); @@ -481,7 +500,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasLimit()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Denominator()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Elasticity()") }); - _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.minimumGasLimit.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("overhead()") }); @@ -548,13 +566,14 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("basefeeScalar()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("blobbasefeeScalar()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("maximumGasLimit()") }); - _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("addDependency(uint256)"), _auth: Role.DEPENDENCYMANAGER }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("addDependency(uint256)"), _auth: Role.SUPERCHAINCONFIG }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("removeDependency(uint256)"), - _auth: Role.DEPENDENCYMANAGER + _auth: Role.SUPERCHAINCONFIG }); - _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("dependencyManager()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("dependencyCounter()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("SUPERCHAIN_CONFIG()") }); // ProxyAdmin _addSpec({ _name: "ProxyAdmin", _sel: _getSel("addressManager()") }); @@ -845,17 +864,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector }); _addSpec({ _name: "OPContractsManager", _sel: OPContractsManager.implementations.selector }); - // OPContractsManagerInterop - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("superchainConfig()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("protocolVersions()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("l1ContractsRelease()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("OUTPUT_VERSION()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.deploy.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.blueprints.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.chainIdToBatchInboxAddress.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: OPContractsManager.implementations.selector }); - // DeputyGuardianModule _addSpec({ _name: "DeputyGuardianModule", @@ -923,6 +931,11 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "LivenessModule", _sel: _getSel("safe()") }); _addSpec({ _name: "LivenessModule", _sel: _getSel("thresholdPercentage()") }); _addSpec({ _name: "LivenessModule", _sel: _getSel("version()") }); + + // LiquidityMigrator + _addSpec({ _name: "LiquidityMigrator", _sel: _getSel("migrateETH()") }); + _addSpec({ _name: "LiquidityMigrator", _sel: _getSel("SHARED_LOCKBOX()") }); + _addSpec({ _name: "LiquidityMigrator", _sel: _getSel("version()") }); } /// @dev Computes the selector from a function signature. diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 1f9fd946c51d..739eec6d19d6 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -55,7 +55,7 @@ contract Initializer_Test is CommonTest { InitializeableContract({ name: "SuperchainConfig", target: deploy.mustGetAddress("SuperchainConfig"), - initCalldata: abi.encodeCall(superchainConfig.initialize, (address(0), false)) + initCalldata: abi.encodeCall(superchainConfig.initialize, (address(0), address(0), false)) }) ); // SuperchainConfigProxy @@ -63,7 +63,7 @@ contract Initializer_Test is CommonTest { InitializeableContract({ name: "SuperchainConfigProxy", target: address(superchainConfig), - initCalldata: abi.encodeCall(superchainConfig.initialize, (address(0), false)) + initCalldata: abi.encodeCall(superchainConfig.initialize, (address(0), address(0), false)) }) ); // L1CrossDomainMessengerImpl @@ -409,7 +409,6 @@ contract Initializer_Test is CommonTest { excludes[5] = "src/dispute/PermissionedDisputeGame.sol"; // TODO: Eventually remove this exclusion. Same reason as above dispute contracts. excludes[6] = "src/L1/OPContractsManager.sol"; - excludes[7] = "src/L1/OPContractsManagerInterop.sol"; // The L2OutputOracle is not always deployed (and is no longer being modified) excludes[8] = "src/L1/L2OutputOracle.sol";