diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 25d67be3828..1cb2599c9bc 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -19,6 +19,7 @@ import { IResourceMetering } from "src/L1/interfaces/IResourceMetering.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMessenger.sol"; import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; @@ -577,6 +578,21 @@ 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() == address(superchainConfig), "CHECK-SLB-20"); + } + /// @dev Asserts that for a given contract the value of a storage slot at an offset is 1 or 0xff. /// A call to `initialize` will set it to 1 and a call to _disableInitializers will set it to 0xff. function assertInitializedSlotIsSet(address _contractAddress, uint256 _slot, uint256 _offset) internal view { diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 6dc6a8bb90b..15951c2d098 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -162,7 +162,8 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721BridgeProxy"), ProtocolVersions: getAddress("ProtocolVersionsProxy"), SuperchainConfig: getAddress("SuperchainConfigProxy"), - OPContractsManager: getAddress("OPContractsManager") + OPContractsManager: getAddress("OPContractsManager"), + SharedLockbox: getAddress("SharedLockboxProxy") }); } @@ -183,7 +184,8 @@ contract Deploy is Deployer { L1ERC721Bridge: getAddress("L1ERC721Bridge"), ProtocolVersions: getAddress("ProtocolVersions"), SuperchainConfig: getAddress("SuperchainConfig"), - OPContractsManager: getAddress("OPContractsManager") + OPContractsManager: getAddress("OPContractsManager"), + SharedLockbox: getAddress("SharedLockbox") }); } @@ -222,16 +224,19 @@ 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 + /// @param _sharedLockboxProxy Address of the existing SharedLockbox proxy /// @param _includeDump Whether to include a state dump after deployment function runWithSuperchain( address payable _superchainConfigProxy, address payable _protocolVersionsProxy, + address payable _sharedLockboxProxy, bool _includeDump ) 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()); @@ -245,6 +250,10 @@ contract Deploy is Deployer { save("ProtocolVersions", pvProxy.implementation()); save("ProtocolVersionsProxy", _protocolVersionsProxy); + IProxy slProxy = IProxy(_sharedLockboxProxy); + save("SharedLockbox", slProxy.implementation()); + save("SharedLockboxProxy", _sharedLockboxProxy); + _run(false); if (_includeDump) { @@ -324,9 +333,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(); @@ -348,11 +358,18 @@ 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())); - // 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 }); + + // 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"); @@ -384,6 +401,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) { diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 71ba435df4c..bc21753e537 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 "src/L1/interfaces/IResourceMetering.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; @@ -53,6 +54,7 @@ contract DeployImplementationsInput is BaseDeployIO { // Outputs from DeploySuperchain.s.sol. ISuperchainConfig internal _superchainConfigProxy; IProtocolVersions internal _protocolVersionsProxy; + ISharedLockbox internal _sharedLockboxProxy; string internal _standardVersionsToml; @@ -88,6 +90,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"); } @@ -153,6 +156,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 { @@ -711,13 +719,15 @@ contract DeployImplementations is Script { } else { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); + address sharedLockbox = address(_dii.sharedLockboxProxy()); vm.broadcast(msg.sender); impl = IOptimismPortal2( DeployUtils.create1({ _name: "OptimismPortal2", _args: DeployUtils.encodeConstructor( abi.encodeCall( - IOptimismPortal2.__constructor__, (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) + IOptimismPortal2.__constructor__, + (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds, sharedLockbox) ) ) }) @@ -992,6 +1002,7 @@ contract DeployImplementationsInterop is DeployImplementations { } else { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); + address sharedLockbox = address(_dii.sharedLockboxProxy()); vm.broadcast(msg.sender); impl = IOptimismPortalInterop( DeployUtils.create1({ @@ -999,7 +1010,7 @@ contract DeployImplementationsInterop is DeployImplementations { _args: DeployUtils.encodeConstructor( abi.encodeCall( IOptimismPortalInterop.__constructor__, - (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) + (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds, sharedLockbox) ) ) }) diff --git a/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol index 5e35e8848c8..698e41c8c93 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeploySuperchain.s.sol @@ -8,6 +8,7 @@ import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; import { IProxyAdmin } from "src/universal/interfaces/IProxyAdmin.sol"; import { IProxy } from "src/universal/interfaces/IProxy.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; import { Solarray } from "scripts/libraries/Solarray.sol"; @@ -161,6 +162,8 @@ contract DeploySuperchainOutput is BaseDeployIO { ISuperchainConfig internal _superchainConfigImpl; ISuperchainConfig internal _superchainConfigProxy; IProxyAdmin internal _superchainProxyAdmin; + ISharedLockbox internal _sharedLockboxImpl; + ISharedLockbox internal _sharedLockboxProxy; // 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 +174,8 @@ 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 revert("DeploySuperchainOutput: unknown selector"); } @@ -182,7 +187,9 @@ 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()) ); DeployUtils.assertValidContractAddresses(addrs); @@ -190,12 +197,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 +235,22 @@ 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; + } + // -------- Deployment Assertions -------- function assertValidDeploy(DeploySuperchainInput _dsi) public { assertValidSuperchainProxyAdmin(_dsi); assertValidSuperchainConfig(_dsi); assertValidProtocolVersions(_dsi); + assertValidSharedLockbox(); } function assertValidSuperchainProxyAdmin(DeploySuperchainInput _dsi) internal view { @@ -280,6 +301,21 @@ 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() == address(superchainConfigProxy()), "SLB-30"); + vm.stopPrank(); + + // Implementation checks. + sl = sharedLockboxImpl(); + require(sl.SUPERCHAIN_CONFIG() == address(superchainConfigProxy()), "SLB-40"); + } } // For all broadcasts in this script we explicitly specify the deployer as `msg.sender` because for @@ -306,6 +342,7 @@ contract DeploySuperchain is Script { deploySuperchainImplementationContracts(_dsi, _dso); deployAndInitializeSuperchainConfig(_dsi, _dso); deployAndInitializeProtocolVersions(_dsi, _dso); + deploySharedLockbox(_dsi, _dso); // Transfer ownership of the ProxyAdmin from the deployer to the specified owner. transferProxyAdminOwnership(_dsi, _dso); @@ -415,6 +452,37 @@ contract DeploySuperchain is Script { _dso.set(_dso.protocolVersionsProxy.selector, address(protocolVersionsProxy)); } + function deploySharedLockbox(DeploySuperchainInput, DeploySuperchainOutput _dso) public { + IProxyAdmin superchainProxyAdmin = _dso.superchainProxyAdmin(); + ISuperchainConfig superchainConfigProxy = _dso.superchainConfigProxy(); + + vm.startBroadcast(msg.sender); + ISharedLockbox sharedLockboxImpl = ISharedLockbox( + DeployUtils.create1({ + _name: "SharedLockbox", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(ISharedLockbox.__constructor__, (address(superchainConfigProxy))) + ) + }) + ); + ISharedLockbox sharedLockboxProxy = ISharedLockbox( + DeployUtils.create1({ + _name: "Proxy", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IProxy.__constructor__, (address(superchainProxyAdmin))) + ) + }) + ); + superchainProxyAdmin.upgrade(payable(address(sharedLockboxProxy)), address(sharedLockboxImpl)); + vm.stopBroadcast(); + + vm.label(address(sharedLockboxImpl), "SharedLockboxImpl"); + vm.label(address(sharedLockboxProxy), "SharedLockboxProxy"); + + _dso.set(_dso.sharedLockboxImpl.selector, address(sharedLockboxImpl)); + _dso.set(_dso.sharedLockboxProxy.selector, address(sharedLockboxProxy)); + } + function transferProxyAdminOwnership(DeploySuperchainInput _dsi, DeploySuperchainOutput _dso) public { address superchainProxyAdminOwner = _dsi.superchainProxyAdminOwner(); diff --git a/packages/contracts-bedrock/scripts/libraries/Types.sol b/packages/contracts-bedrock/scripts/libraries/Types.sol index db37e19457a..6f7c39d3c88 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 2ab3157a714..505885461f3 100644 --- a/packages/contracts-bedrock/snapshots/.gas-snapshot +++ b/packages/contracts-bedrock/snapshots/.gas-snapshot @@ -4,13 +4,13 @@ GasBenchMark_L1BlockInterop_SetValuesInterop:test_setL1BlockValuesInterop_benchm GasBenchMark_L1BlockInterop_SetValuesInterop_Warm:test_setL1BlockValuesInterop_benchmark() (gas: 5099) GasBenchMark_L1Block_SetValuesEcotone:test_setL1BlockValuesEcotone_benchmark() (gas: 158531) GasBenchMark_L1Block_SetValuesEcotone_Warm:test_setL1BlockValuesEcotone_benchmark() (gas: 7597) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369280) -GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967465) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564398) -GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076613) -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: 72664) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 369342) +GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_1() (gas: 2967527) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_0() (gas: 564475) +GasBenchMark_L1StandardBridge_Deposit:test_depositERC20_benchmark_1() (gas: 4076690) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_0() (gas: 467025) +GasBenchMark_L1StandardBridge_Deposit:test_depositETH_benchmark_1() (gas: 3512729) +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: 68986) diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 2f52ed573d3..5ca5e6bc7ff 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -10,6 +10,11 @@ "internalType": "uint256", "name": "_disputeGameFinalityDelaySeconds", "type": "uint256" + }, + { + "internalType": "address", + "name": "_sharedLockbox", + "type": "address" } ], "stateMutability": "nonpayable", @@ -643,6 +648,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sharedLockbox", + "outputs": [ + { + "internalType": "address", + "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 5b9f72b9446..8eb9ed0f74d 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -10,6 +10,11 @@ "internalType": "uint256", "name": "_disputeGameFinalityDelaySeconds", "type": "uint256" + }, + { + "internalType": "address", + "name": "_sharedLockbox", + "type": "address" } ], "stateMutability": "nonpayable", @@ -661,6 +666,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "sharedLockbox", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 1fd49944195..e8cdf3df5e4 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -28,12 +28,12 @@ "sourceCodeHash": "0xbe34b82900d02f71bb0949818eabe49531f7e0d8d8bae01f6dac4a296530d1aa" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0x218358b48f640b3fcb2d239f00dc1cd3b11517ad46c8e1efa44953d38da63540", - "sourceCodeHash": "0x66ac1212760db53a2bb1839e4cd17dc071d9273b8e6fb80646b79e91b3371c1a" + "initCodeHash": "0x215f439adc85bc5ebb1b234c1acc128eecb7c9a43243edbfcd9e701f128224f1", + "sourceCodeHash": "0x4c33430270b28fe0850c112c05d121477429688aae74c58754249476fb2d6c8e" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0x39f66ac74341ec235fbdd0d79546283210bd8ac35a2ab2c4bd36c9722ce18411", - "sourceCodeHash": "0xbb98144285b9530e336f957d10b20363b350876597e30fd34821940896a2bae8" + "initCodeHash": "0x831af803158768b7fc229822eebda0ce7922cb1852fd770856b425023a379d47", + "sourceCodeHash": "0xe1dbe5e95ffed6587126ea65a5e4d3c8f11e1bd17a194a5665c7f0c0d32f29e3" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0xefd4806e8737716d5d2022ca2e9e9fba0a0cb5714b026166b58e472222c7d15f", diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 985711ed18e..6fce8c1e16c 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -45,6 +45,7 @@ import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol"; import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; /// @custom:proxied true /// @title OptimismPortal2 @@ -70,6 +71,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// finalized. uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; + /// @notice The Shared Lockbox contract. + ISharedLockbox internal immutable SHARED_LOCKBOX; + /// @notice Version of the deposit event. uint256 internal constant DEPOSIT_VERSION = 0; @@ -177,21 +181,21 @@ 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.6 + /// @custom:semver 3.11.0-beta.7 function version() public pure virtual returns (string memory) { - return "3.11.0-beta.6"; + return "3.11.0-beta.7"; } /// @notice Constructs the OptimismPortal contract. - constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) { + constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds, address _sharedLockbox) { PROOF_MATURITY_DELAY_SECONDS = _proofMaturityDelaySeconds; DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; + SHARED_LOCKBOX = ISharedLockbox(_sharedLockbox); initialize({ _disputeGameFactory: IDisputeGameFactory(address(0)), @@ -244,6 +248,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. @@ -267,6 +278,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 (address) { + return address(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. @@ -321,8 +337,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`. @@ -385,7 +402,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); } @@ -397,8 +415,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. @@ -419,6 +438,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) SHARED_LOCKBOX.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 @@ -440,7 +462,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 @@ -448,7 +470,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(); } } @@ -505,13 +527,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(); } @@ -548,6 +570,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. + SHARED_LOCKBOX.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 4c238c415d3..a86e9440549 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -18,14 +18,15 @@ import { Unauthorized } from "src/libraries/PortalErrors.sol"; contract OptimismPortalInterop is OptimismPortal2 { constructor( uint256 _proofMaturityDelaySeconds, - uint256 _disputeGameFinalityDelaySeconds + uint256 _disputeGameFinalityDelaySeconds, + address _sharedLockbox ) - OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) + OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds, _sharedLockbox) { } - /// @custom:semver +interop-beta.2 + /// @custom:semver +interop-beta.3 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.2"); + return string.concat(super.version(), "+interop-beta.3"); } /// @notice Sets static configuration options for the L2 system. diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol index 91f09d71431..662d6e1e92d 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortal2.sol @@ -69,6 +69,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 (address); function donateETH() external payable; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( @@ -114,5 +115,10 @@ interface IOptimismPortal2 { function systemConfig() external view returns (ISystemConfig); function version() external pure returns (string memory); - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__( + uint256 _proofMaturityDelaySeconds, + uint256 _disputeGameFinalityDelaySeconds, + address _sharedLockbox + ) + external; } diff --git a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol index 521c7232e12..276b9cea14b 100644 --- a/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/interfaces/IOptimismPortalInterop.sol @@ -70,6 +70,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 (address); function donateETH() external payable; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( @@ -116,5 +117,10 @@ interface IOptimismPortalInterop { function systemConfig() external view returns (ISystemConfig); function version() external pure returns (string memory); - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__( + uint256 _proofMaturityDelaySeconds, + uint256 _disputeGameFinalityDelaySeconds, + address _sharedLockbox + ) + external; } diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 97ef01262ab..a2e052e780e 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -163,6 +163,7 @@ contract L1StandardBridge_Receive_Test is CommonTest { /// @dev Tests receive bridges ETH successfully. function test_receive_succeeds() external { assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 0); // The legacy event must be emitted for backwards compatibility vm.expectEmit(address(l1StandardBridge)); @@ -186,7 +187,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, 100); + assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 100); } } @@ -212,6 +214,8 @@ contract PreBridgeETH is CommonTest { /// on whether the bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { 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)); @@ -277,11 +281,13 @@ 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 }); l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal).balance, 500); + + assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 500); } } @@ -310,11 +316,13 @@ 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 }); l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal).balance, 500); + + assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 500); } } @@ -336,6 +344,8 @@ contract PreBridgeETHTo is CommonTest { /// address depending on whether the bridge call is legacy or not. function _preBridgeETHTo(bool isLegacy, uint256 value) internal { 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)); @@ -403,11 +413,13 @@ 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 }); l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal).balance, 600); + + assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 600); } } @@ -436,11 +448,13 @@ 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 }); l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal).balance, 600); + + assertEq(address(optimismPortal).balance, 0); + assertEq(address(sharedLockbox).balance, 600); } } diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 083644755d3..0c06ae5ed39 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -55,6 +55,7 @@ contract OptimismPortal2_Test is CommonTest { assertEq(address(opImpl.superchainConfig()), address(0)); assertEq(opImpl.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(opImpl.respectedGameType().raw(), deploy.cfg().respectedGameType()); + assertEq(opImpl.sharedLockbox(), address(sharedLockbox)); } /// @dev Tests that the initializer sets the correct values. @@ -69,6 +70,7 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal2.paused(), false); assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); + assertEq(optimismPortal2.sharedLockbox(), address(sharedLockbox)); } /// @dev Tests that `pause` successfully pauses @@ -145,13 +147,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, _value); + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(sharedLockbox).balance, _value); } /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero @@ -243,6 +249,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 }({ @@ -252,7 +261,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, _mint); + + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(sharedLockbox).balance, _mint); } /// @dev Tests that `depositTransaction` succeeds for a contract. @@ -287,6 +298,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 }({ @@ -296,7 +310,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, _mint); + + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(sharedLockbox).balance, _mint); } /// @dev Tests that the gas paying token can be set. @@ -477,8 +493,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. @@ -862,9 +878,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; @@ -969,6 +986,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)); @@ -1005,6 +1026,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); @@ -1182,6 +1207,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); @@ -1208,6 +1237,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); @@ -1305,6 +1338,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); @@ -1345,7 +1382,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(); @@ -1394,6 +1433,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 index c3a9e946635..09b846b1c05 100644 --- a/packages/contracts-bedrock/test/L1/SharedLockbox.t.sol +++ b/packages/contracts-bedrock/test/L1/SharedLockbox.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.15; // Testing utilities -import { Test } from "forge-std/Test.sol"; +import { CommonTest } from "test/setup/CommonTest.sol"; import { Unauthorized } from "src/libraries/errors/CommonErrors.sol"; // Targent contract @@ -11,27 +11,13 @@ import { SharedLockbox } from "src/L1/SharedLockbox.sol"; // Interfaces import { IOptimismPortal } from "src/L1/interfaces/IOptimismPortal.sol"; -contract SharedLockboxTest is Test { +contract SharedLockboxTest is CommonTest { event ETHLocked(address indexed portal, uint256 amount); event ETHUnlocked(address indexed portal, uint256 amount); event AuthorizedPortal(address indexed portal); - address internal immutable SUPERCHAIN_CONFIG = makeAddr("SuperchainConfig"); - IOptimismPortal internal immutable PORTAL = IOptimismPortal(payable(makeAddr("OptimismPortal"))); - SharedLockbox public sharedLockbox; - - // TODO: Update setup to use CommonTest and simulate a real deployment environment - function setUp() public { - // Deploy the SharedLockbox contract - sharedLockbox = new SharedLockbox(SUPERCHAIN_CONFIG); - - // Etch the OptimismPortal contract code into the declared address - bytes memory code = vm.getDeployedCode("L1/OptimismPortal.sol:OptimismPortal"); - vm.etch(address(PORTAL), code); - } - /// @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)); @@ -47,7 +33,7 @@ contract SharedLockboxTest is Test { /// @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(SUPERCHAIN_CONFIG); + vm.prank(address(superchainConfig)); sharedLockbox.authorizePortal(_portal); // Deal the ETH amount to the portal @@ -85,35 +71,35 @@ contract SharedLockboxTest is Test { /// @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(SUPERCHAIN_CONFIG); - sharedLockbox.authorizePortal(address(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(PORTAL).balance; + uint256 _portalBalanceBefore = address(optimismPortal2).balance; uint256 _lockboxBalanceBefore = address(sharedLockbox).balance; // Expect `donateETH` function to be called on Portal - vm.expectCall(address(PORTAL), abi.encodeWithSelector(IOptimismPortal.donateETH.selector)); + vm.expectCall(address(optimismPortal2), abi.encodeWithSelector(IOptimismPortal.donateETH.selector)); // Look for the emit of the `ETHUnlocked` event vm.expectEmit(address(sharedLockbox)); - emit ETHUnlocked(address(PORTAL), _value); + emit ETHUnlocked(address(optimismPortal2), _value); // Call the `unlockETH` function with the portal - vm.prank(address(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(PORTAL).balance, _portalBalanceBefore + _value); + assertEq(address(optimismPortal2).balance, _portalBalanceBefore + _value); assertEq(address(sharedLockbox).balance, _lockboxBalanceBefore - _value); } /// @notice Tests it reverts when the caller is not the SuperchainConfig. function test_authorizePortal_notSuperchainConfig_reverts(address _caller) public { - vm.assume(_caller != SUPERCHAIN_CONFIG); + vm.assume(_caller != address(superchainConfig)); // Expect the revert with `Unauthorized` selector vm.expectRevert(Unauthorized.selector); @@ -134,7 +120,7 @@ contract SharedLockboxTest is Test { emit AuthorizedPortal(_portal); // Call the `authorizePortal` function with the SuperchainConfig - vm.prank(SUPERCHAIN_CONFIG); + vm.prank(address(superchainConfig)); sharedLockbox.authorizePortal(_portal); // Assert the portal's authorized status was updated correctly diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol index 5e0e866dcfb..d77bc8ca067 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 8d3feac2de0..863e443dd16 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 "src/dispute/interfaces/IDisputeGameFactory. import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; import { IOptimismPortal2 } from "src/L1/interfaces/IOptimismPortal2.sol"; import { ISystemConfig } from "src/L1/interfaces/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 5280328168b..5960a360bd4 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 "src/legacy/interfaces/IL1ChugSplashProxy.sol import { IResolvedDelegateProxy } from "src/legacy/interfaces/IResolvedDelegateProxy.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; import { OPContractsManager } from "src/L1/OPContractsManager.sol"; import { IProxy } from "src/universal/interfaces/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 924957cc180..4afb7a60a73 100644 --- a/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeploySuperchain.t.sol @@ -7,6 +7,7 @@ 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 { IProtocolVersions, ProtocolVersion } from "src/L1/interfaces/IProtocolVersions.sol"; import { DeploySuperchainInput, DeploySuperchain, DeploySuperchainOutput } from "scripts/deploy/DeploySuperchain.s.sol"; @@ -60,6 +61,8 @@ 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")); // Ensure each address has code, since these are expected to be contracts. vm.etch(address(superchainProxyAdmin), hex"01"); @@ -67,6 +70,8 @@ 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"); // Set the output data. dso.set(dso.superchainProxyAdmin.selector, address(superchainProxyAdmin)); @@ -74,6 +79,8 @@ 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)); // Compare the test data to the getter methods. assertEq(address(superchainProxyAdmin), address(dso.superchainProxyAdmin()), "100"); @@ -81,6 +88,8 @@ 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"); } function test_getters_whenNotSet_reverts() public { @@ -95,6 +104,12 @@ 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(); } function test_getters_whenAddrHasNoCode_reverts() public { @@ -116,6 +131,14 @@ 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(); } } @@ -176,17 +199,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/setup/CommonTest.sol b/packages/contracts-bedrock/test/setup/CommonTest.sol index 93ede20b629..da071530233 100644 --- a/packages/contracts-bedrock/test/setup/CommonTest.sol +++ b/packages/contracts-bedrock/test/setup/CommonTest.sol @@ -76,6 +76,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 ef2b654b241..3ee91b8bb07 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -23,6 +23,7 @@ import { IL1CrossDomainMessenger } from "src/L1/interfaces/IL1CrossDomainMesseng import { IL2OutputOracle } from "src/L1/interfaces/IL2OutputOracle.sol"; import { ISystemConfig } from "src/L1/interfaces/ISystemConfig.sol"; import { ISuperchainConfig } from "src/L1/interfaces/ISuperchainConfig.sol"; +import { ISharedLockbox } from "src/L1/interfaces/ISharedLockbox.sol"; import { IDataAvailabilityChallenge } from "src/L1/interfaces/IDataAvailabilityChallenge.sol"; import { IL1StandardBridge } from "src/L1/interfaces/IL1StandardBridge.sol"; import { IProtocolVersions } from "src/L1/interfaces/IProtocolVersions.sol"; @@ -87,6 +88,7 @@ contract Setup { IProtocolVersions protocolVersions; ISuperchainConfig superchainConfig; IDataAvailabilityChallenge dataAvailabilityChallenge; + ISharedLockbox sharedLockbox; // L2 contracts IL2CrossDomainMessenger l2CrossDomainMessenger = @@ -158,6 +160,7 @@ contract Setup { protocolVersions = IProtocolVersions(deploy.mustGetAddress("ProtocolVersionsProxy")); superchainConfig = ISuperchainConfig(deploy.mustGetAddress("SuperchainConfigProxy")); anchorStateRegistry = IAnchorStateRegistry(deploy.mustGetAddress("AnchorStateRegistryProxy")); + sharedLockbox = ISharedLockbox(deploy.mustGetAddress("SharedLockboxProxy")); vm.label(address(optimismPortal), "OptimismPortal"); vm.label(deploy.mustGetAddress("OptimismPortalProxy"), "OptimismPortalProxy"); @@ -180,6 +183,8 @@ 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(AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)), "L1CrossDomainMessenger_aliased"); if (!deploy.cfg().useFaultProofs()) { diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 49e35e835f3..ed143e2e826 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -43,7 +43,9 @@ contract Specification_Test is CommonTest { DELAYEDWETHOWNER, COUNCILSAFE, COUNCILSAFEOWNER, - DEPENDENCYMANAGER + DEPENDENCYMANAGER, + PORTAL, + SUPERCHAINCONFIG } /// @notice Represents the specification of a function. @@ -334,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)") }); @@ -381,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()") }); @@ -417,6 +421,14 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SuperchainConfig", _sel: _getSel("unpause()"), _auth: Role.GUARDIAN }); _addSpec({ _name: "SuperchainConfig", _sel: _getSel("version()") }); + // 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()") }); + // SystemConfig _addSpec({ _name: "SystemConfig", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("START_BLOCK_SLOT()") });