diff --git a/specs/interop/dependency-set.md b/specs/interop/dependency-set.md index 8c6351237..33b5738d8 100644 --- a/specs/interop/dependency-set.md +++ b/specs/interop/dependency-set.md @@ -47,16 +47,8 @@ It is a known issue that not all software in the Ethereum can handle 32 byte cha ## Updating the Dependency Set -The `SystemConfig` is updated to manage a new role, `dependencyManager`. -It can only updated by the `ProxyAdmin` during an contract upgrade. -The sole holder of this role is the only address -permissioned to update (remove/add to) the dependency set of that chain. - -The `SystemConfig` is also updated to manage the dependency set. -The address with the `dependency manager` role can add or remove -chains from the dependency set through the `SystemConfig`. - -The `SystemConfig` MUST enforce that the maximum size of the dependency set is `type(uint8).max` or 255. +The `SuperchainConfig` is updated to manage the dependency set. +More details can be found on the [dependency manager section](./../protocol/superchain-configuration.md#dependency-manager). ## Security Considerations diff --git a/specs/interop/optimism-portal-interop.md b/specs/interop/optimism-portal-interop.md new file mode 100644 index 000000000..b0a33eb87 --- /dev/null +++ b/specs/interop/optimism-portal-interop.md @@ -0,0 +1,77 @@ +# OptimismPortal + + + +**Table of Contents** + +- [Overview](#overview) + - [Integrating `SharedLockbox`](#integrating-sharedlockbox) + - [`depositTransaction`](#deposittransaction) + - [`finalizeWithdrawalTransactionExternalProof`](#finalizewithdrawaltransactionexternalproof) + - [Invariants](#invariants) + + + +## Overview + +The `OptimismPortal` contract is upgraded to integrate the `SharedLockbox` and start using the shared ETH liquidity. +This liquidity consists of every ETH balance migrated from each `OptimismPortal` +when joining the op-governed dependency set. + +It is possible to upgrade to this version without being part of the op-governed dependency set. In this case, +the corresponding chain would need to deploy and manage its own `SharedLockbox` and `SuperchainConfig`. + +### Integrating `SharedLockbox` + +The integration with the `SharedLockbox` involves locking ETH when executing deposit transactions and unlocking ETH +when finalizing withdrawal transactions, without altering other aspects of the current `OptimismPortal` implementation. + +To implement this solution, the following changes are needed: + +#### `depositTransaction` + +Calls `lockETH` on the `SharedLockbox` with the `msg.value`. + +- The function MUST call `lockETH` with `msg.value` on the `SharedLockbox` if: + - The token is `ETHER`. + - `msg.value` is greater than zero. + +```mermaid +sequenceDiagram + participant User + participant OptimismPortal + participant SharedLockbox + + User->>OptimismPortal: depositTransaction(...) + OptimismPortal->>SharedLockbox: lockETH() + OptimismPortal->>OptimismPortal: emit TransactionDeposited() +``` + +#### `finalizeWithdrawalTransactionExternalProof` + +Calls `unlockETH` on the `SharedLockbox` with the `tx.value`. + +- The function MUST call `unlockETH` on the `SharedLockbox` if: + - The token is `ETHER`. + - `tx.value` is greater than zero. +- The ETH is received by the `OptimismPortal` and then sent with the withdrawal transaction + +```mermaid +sequenceDiagram + participant User + participant OptimismPortal + participant SharedLockbox + + User->>OptimismPortal: finalizeWithdrawalTransactionExternalProof(...) + OptimismPortal->>SharedLockbox: unlockETH(uint256 value) + SharedLockbox->>OptimismPortal: donateETH() + OptimismPortal->>OptimismPortal: emit WithdrawalFinalized() +``` + +### Invariants + +- It MUST lock the ETH amount on the `SharedLockbox` when on a deposit transaction with value greater than zero + +- It MUST unlock the ETH amount being withdrawn from the `SharedLockbox` if it is greater than zero + +- It MUST NOT hold any ETH balance from any deposit transaction. diff --git a/specs/interop/predeploys.md b/specs/interop/predeploys.md index e9ea9c79e..25a82057f 100644 --- a/specs/interop/predeploys.md +++ b/specs/interop/predeploys.md @@ -40,7 +40,6 @@ - [Overview](#overview-2) - [L1Block](#l1block) - [Static Configuration](#static-configuration) - - [Dependency Set](#dependency-set) - [Deposit Context](#deposit-context) - [`isDeposit()`](#isdeposit) - [`depositsComplete()`](#depositscomplete) @@ -128,10 +127,10 @@ Emits the `ExecutingMessage` event to signal the transaction has a cross chain m The following fields are required for validating a cross chain message: -| Name | Type | Description | -| -------- | ---------- | -------------------------------------------------------------------------- | -| `_id` | Identifier | A [`Identifier`] pointing to the initiating message. | -| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. | +| Name | Type | Description | +| ---------- | ---------- | -------------------------------------------------------------------------- | +| `_id` | Identifier | A [`Identifier`] pointing to the initiating message. | +| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. | ```solidity validateMessage(Identifier calldata _id, bytes32 _msgHash) @@ -656,22 +655,6 @@ where Calls to `setConfig` MUST originate from `SystemConfig` and are forwarded to `L1Block` by `OptimismPortal`. -### Dependency Set - -`L1Block` is updated to include the set of allowed chains. These chains are added and removed through `setConfig` calls -with `ADD_DEPENDENCY` or `REMOVE_DEPENDENCY`, respectively. The maximum size of the dependency set is `type(uint8).max`, -and adding a chain id when the dependency set size is at its maximum MUST revert. If a chain id already in the -dependency set, such as the chain's chain id, is attempted to be added, the call MUST revert. If a chain id that is not -in the dependency set is attempted to be removed, the call MUST revert. If the chain's chain id is attempted to be -removed, the call also MUST revert. - -`L1Block` MUST provide a public getter to check if a particular chain is in the dependency set called -`isInDependencySet(uint256)`. This function MUST return true when a chain id in the dependency set, or the chain's chain -id, is passed in as an argument, and false otherwise. Additionally, `L1Block` MUST provide a public getter to return the -dependency set called `dependencySet()`. This function MUST return the array of chain ids that are in the dependency set. -`L1Block` MUST also provide a public getter to get the dependency set size called `dependencySetSize()`. This function -MUST return the length of the dependency set array. - ### Deposit Context New methods will be added on the `L1Block` contract to interact with [deposit contexts](./derivation.md#deposit-context). @@ -954,9 +937,9 @@ sequenceDiagram L2SBA->>SuperERC20_A: crosschainBurn(from, amount) SuperERC20_A-->SuperERC20_A: emit CrosschainBurn(from, amount) L2SBA->>Messenger_A: sendMessage(chainId, message) - Messenger_A->>L2SBA: return msgHash_ + Messenger_A->>L2SBA: return msgHash_ L2SBA-->L2SBA: emit SentERC20(tokenAddr, from, to, amount, destination) - L2SBA->>from: return msgHash_ + L2SBA->>from: return msgHash_ Inbox->>Messenger_B: relayMessage() Messenger_B->>L2SBB: relayERC20(tokenAddr, from, to, amount) L2SBB->>SuperERC20_B: crosschainMint(to, amount) @@ -987,7 +970,7 @@ The bridging of `SuperchainERC20` using the `SuperchainERC20Bridge` will require to the same address on the target chain. Similarly, the `relayERC20()` function should only process messages originating from the same address. - Note: The [`Create2Deployer` preinstall](../protocol/preinstalls.md#create2deployer) - and the custom Factory will ensure same address deployment. + and the custom Factory will ensure same address deployment. - Locally initiated: The bridging action should be initialized from the chain where funds are located only. - This is because the same address might correspond to different users cross-chain. diff --git a/specs/interop/shared-lockbox-upgrade.md b/specs/interop/shared-lockbox-upgrade.md new file mode 100644 index 000000000..fca35e66f --- /dev/null +++ b/specs/interop/shared-lockbox-upgrade.md @@ -0,0 +1,127 @@ +# Shared Lockbox - Upgrade and migration process + + + +**Table of Contents** + +- [Overview](#overview) + - [Add dependency to the op-governed dependency set in `SuperchainConfig`](#add-dependency-to-the-op-governed-dependency-set-in-superchainconfig) + - [Migrate ETH liquidity from `OptimismPortal` to `SharedLockbox`](#migrate-eth-liquidity-from-optimismportal-to-sharedlockbox) + - [`LiquidityMigrator`](#liquiditymigrator) + - [`OptimismPortal` code upgrade](#optimismportal-code-upgrade) +- [Batch transaction process](#batch-transaction-process) + - [Diagram](#diagram) +- [Future Considerations / Additional Notes](#future-considerations--additional-notes) + + + +## Overview + +Based on the assumption that a chain joining the op-governed dependency set is an irreversible process, +it is assumed that joining the Shared Lockbox is equivalent to it. + +The upgrade process consists of three main points: + +- Add dependency to the op-governed dependency set in `SuperchainConfig` +- Move ETH liquidity from `OptimismPortal` to `SharedLockbox` +- Upgrade the code of `OptimismPortal` to include the `SharedLockbox` integration + +This process also requires that: + +- `SharedLockbox` is deployed +- `LiquidityMigrator` is deployed +- `SuperchainConfig` is upgraded to manage the dependency set + +### Add dependency to the op-governed dependency set in `SuperchainConfig` + +The `SuperchainConfig` contract will be responsible for storing and managing the dependency set. +Its `addDependency` function will be used to add the chain ID to the dependency set. +It will also allowlist the corresponding `OptimismPortal`, enabling it to lock and unlock ETH from the `SharedLockbox`. + +### Migrate ETH liquidity from `OptimismPortal` to `SharedLockbox` + +The ETH will be transferred from the `OptimismPortal` to the `SharedLockbox` using the `LiquidityMigrator` contract. +This contract functions similarly to upgrades using the `StorageSetter`, being updated immediately before to the real implementation. +Its sole purpose is to transfer the ETH balance. +This approach eliminates the need for adding code to move the liquidity to the lockbox that won't be used again. + +#### `LiquidityMigrator` + +This contract is meant to be used as an intermediate step for the liquidity migration. +Its unique purpose is to transfer the whole ETH balance from `OptimismPortal` to `SharedLockbox`. +This approach avoids adding extra code to the `initialize` function, which could be prone to errors in future updates. + +**Interface and properties** + +**`migrateETH`** + +Transfers the entire ETH balance from the `OptimismPortal` to the `SharedLockbox`. + +```solidity +function migrateETH() external; +``` + +**Invariants** + +- It MUST migrate the whole `OptimismPortal` ETH balance to the `SharedLockbox` + +- It MUST emit `ETHMigrated` when migrating the balance + +### `OptimismPortal` code upgrade + +The `OptimismPortal` will start locking and unlocking ETH through the `SharedLockbox`. +It will continue to handle deposits and withdrawals but won't directly hold the ETH liquidity. +To set this up, the upgrade function will be called via `ProxyAdmin` to implement the new code, +which includes the necessary `SharedLockbox` integration. + +## Batch transaction process + +The approach consists of handling the entire migration process in a single batched transaction. +This transaction will include: + +1. Call `addDependency` in the `SuperchainConfig` + - Sending chain ID + system config address +2. Call `upgradeAndCall` in the `ProxyAdmin` for the `OptimismPortal` + - Update provisionally to the `LiquidityMigrator` to transfer the whole ETH balance to the `SharedLockbox` in this call. +3. Call `upgrade` in the `ProxyAdmin` for the `OptimismPortal` + - The `SharedLockbox` address is set as immutable in the new implementation + +The L1 ProxyAdmin owner (L1PAO) will execute this transaction. As the entity responsible for updating contracts, +it has the authority to perform the second and third steps. +For the first step, the L1PAO has to be set as authorized for adding a dependency to the op-governed dependency set +on the `SuperchainConfig` when initializing. +This process can be set as a [superchain-ops](https://github.com/ethereum-optimism/superchain-ops) task. + +### Diagram + +```mermaid +sequenceDiagram + participant L1PAO as L1 ProxyAdmin Owner + participant ProxyAdmin as ProxyAdmin + participant SuperchainConfig + participant OptimismPortalProxy as OptimismPortal + participant LiquidityMigrator + participant SharedLockbox + + Note over L1PAO: Start batch + + %% Step 1: Add dependency to SuperchainConfig + L1PAO->>SuperchainConfig: addDependency(chainId, SystemConfig address) + SuperchainConfig->>SharedLockbox: authorizePortal(OptimismPortal address) + + %% Step 2: Upgrade OptimismPortal to intermediate implementation that transfers ETH + L1PAO->>ProxyAdmin: upgradeAndCall() + ProxyAdmin->>OptimismPortalProxy: Upgrade to LiquidityMigrator + OptimismPortalProxy->>LiquidityMigrator: Call migrateETH() + OptimismPortalProxy->>SharedLockbox: Transfer entire ETH balance + + %% Step 3: Upgrade OptimismPortal to final implementation + L1PAO->>ProxyAdmin: upgrade() + ProxyAdmin->>OptimismPortalProxy: Upgrade to new OptimismPortal implementation + + Note over L1PAO: End batch +``` + +## Future Considerations / Additional Notes + +- Before calling `addDependency`, it MUST be ensured that the `chainId` and `systemConfig` match diff --git a/specs/interop/shared-lockbox.md b/specs/interop/shared-lockbox.md new file mode 100644 index 000000000..054c33ad3 --- /dev/null +++ b/specs/interop/shared-lockbox.md @@ -0,0 +1,169 @@ +# Shared Lockbox + + + +**Table of Contents** + +- [Overview](#overview) +- [Design](#design) + - [Interface and properties](#interface-and-properties) + - [Events](#events) +- [Invariants](#invariants) + - [System level invariants](#system-level-invariants) + - [Contract level invariants](#contract-level-invariants) +- [Reference implementation](#reference-implementation) + + + +## Overview + +With interoperable ETH, withdrawals will fail if the referenced `OptimismPortal` lacks sufficient ETH. +This is due to having the possibility to move ETH liquidity across the different chains and it could happen +that a chain ends up with more liquidity than its `OptimismPortal`. +The `SharedLockbox` improves the Superchain's interoperable ETH withdrawal user experience and avoids this issue. +To do so, it unifies ETH L1 liquidity in a single contract (`SharedLockbox`), enabling seamless withdrawals of ETH +from any OP chain in the Superchain, regardless of where the ETH was initially deposited. + +## Design + +The `SharedLockbox` contract is designed to manage the unified ETH liquidity for the Superchain. +It implements two main functions: `lockETH` for depositing ETH into the lockbox, +and `unlockETH` for withdrawing ETH from the lockbox. +These functions are called by the `OptimismPortal` contracts to manage the shared ETH liquidity +when making deposits or finalizing withdrawals. +These `OptimismPortal`s will be allowlisted by the `SuperchainConfig` using the `authorizePortal` function +when a chain is added. +The `SharedLockbox` contract is proxied and managed by the L1 `ProxyAdmin`. + +### Interface and properties + +**`lockETH`** + +Deposits and locks ETH into the lockbox's liquidity pool. + +- The function MUST accept ETH. +- Only authorized `OptimismPortal` addresses MUST be allowed to interact. +- The function MUST NOT revert when called by an authorized `OptimismPortal` +- The function MUST emit the `ETHLocked` event with the `portal` that called it and the `amount`. + +```solidity +function lockETH() external payable; +``` + +**`unlockETH`** + +Withdraws a specified amount of ETH from the lockbox's liquidity pool. + +- Only authorized `OptimismPortal` addresses MUST be allowed to interact. +- The function MUST NOT revert when called by an authorized `OptimismPortal` +- The function MUST emit the `ETHUnlocked` event with the `portal` that called it and the `amount`. + +```solidity +function unlockETH(uint256 _value) external; +``` + +**`authorizePortal`** + +Grants authorization to a specific `OptimismPortal` contract. + +- Only `SuperchainConfig` address MUST be allowed to interact. +- The function MUST add the specified address to the mapping of authorized portals. +- The function MUST emit the [`PortalAuthorized`](#events) event when a portal is successfully added. + +```solidity +function authorizePortal(address _portal) external; +``` + +### Events + +**`ETHLocked`** + +MUST be triggered when `lockETH` is called + +```solidity +event ETHLocked(address indexed portal, uint256 amount); +``` + +**`ETHUnlocked`** + +MUST be triggered when `unlockETH` is called + +```solidity +event ETHUnlocked(address indexed portal, uint256 amount); +``` + +**`PortalAuthorized`** + +MUST be triggered when `authorizePortal` is called + +```solidity +event PortalAuthorized(address indexed portal); +``` + +## Invariants + +### System level invariants + +- The ETH held in the SharedLockbox MUST never be less than the amount deposited but not yet withdrawn by the `OptimismPortal`s + +- The ETH unlocked by any `OptimismPortal` MUST NOT exceed the available shared liquidity in the `SharedLockbox`. + +- The total withdrawable ETH amount present on all the dependency set’s chains MUST NEVER be more than the amount held + by the `SharedLockbox` of the cluster + > With "withdrawable amount", the ETH balance held on `ETHLiquidity` is excluded + +### Contract level invariants + +- It MUST allow only authorized portals to lock ETH + +- It MUST allow only authorized portals to unlock ETH + +- Only the `SuperchainConfig` contract MUST be able to authorize an `OptimismPortal` + +- It MUST be in paused state if the `SuperchainConfig` is paused + +- No Ether MUST flow out of the contract when in a paused state + +- No `OptimismPortal` can be authorized when in a paused state + +- It MUST NOT trigger a new deposit when ETH amount is being unlocked from the `SharedLockbox` by the `OptimismPortal` + +- It MUST emit: + + - An `ETHLocked` event when locking ETH + + - An `ETHUnlocked` event when unlocking ETH + + - A `PortalAuthorized` event when authorizing a new portal + +## Reference implementation + +An example implementation could look like this: + +```solidity +// OptimismPortals that are part of the dependency cluster. +mapping(address _portal => bool) internal _authorizedPortals; + +function lockETH() external payable { + require(_authorizedPortals[msg.sender], "Unauthorized"); + + emit ETHLocked(msg.sender, msg.value); +} + +function unlockETH(uint256 _value) external { + require(_authorizedPortals[msg.sender], "Unauthorized"); + + // Using `donateETH` to not trigger a deposit + IOptimismPortal(msg.sender).donateETH{ value: _value }(); + + emit ETHUnlocked(msg.sender, _value); +} + +function authorizePortal(address _portal) external { + require(msg.sender == superchainConfig, "Unauthorized"); + + _authorizedPortals[_portal] = true; + + emit PortalAuthorized(_portal); +} +``` diff --git a/specs/protocol/superchain-configuration.md b/specs/protocol/superchain-configuration.md index 000b7cb34..6dc75a079 100644 --- a/specs/protocol/superchain-configuration.md +++ b/specs/protocol/superchain-configuration.md @@ -10,13 +10,22 @@ - [Pausability](#pausability) - [Paused identifiers](#paused-identifiers) - [Scope of pausability](#scope-of-pausability) +- [Dependency manager](#dependency-manager) + - [Interface and properties](#interface-and-properties) + - [`SHARED_LOCKBOX`](#shared_lockbox) + - [`dependencyManager`](#dependencymanager) + - [`dependencySet`](#dependencyset) + - [`addDependency`](#adddependency) + - [Events](#events) + - [`DependencyAdded`](#dependencyadded) + - [Invariants](#invariants) ## Overview The SuperchainConfig contract is used to manage global configuration values for multiple OP Chains within -a single Superchain network. +a single Superchain network. Also is in charge of managing and keeping track of the network's dependency set. ## Configurable values @@ -26,6 +35,7 @@ The `SuperchainConfig` contract manages the following configuration values: - `PAUSED_SLOT`: A boolean value indicating whether the Superchain is paused. - `GUARDIAN_SLOT`: The address of the guardian, which can pause and unpause the system. +- `DEPENDENCY_MANAGER_SLOT`: The address of the dependency manager, which can add a chain to the depenceny set. ## Configuration data flow @@ -38,6 +48,7 @@ StandardBridge --> SuperchainConfig L1ERC721Bridge --> SuperchainConfig L1CrossDomainMessenger --> SuperchainConfig OptimismPortal --> SuperchainConfig +SharedLockbox --> SuperchainConfig ``` ### Pausability @@ -64,3 +75,72 @@ When the Pause is activated, the following methods are disabled: 1. `StandardBridge.finalizeBridgeERC20()` 1. `StandardBridge.finalizeBridgeETH()` 1. `L1ERC721Bridge.finalizeBridgeERC721()` +1. `SharedLockbox.unlockETH()` +1. `SharedLockbox.authorizePortal()` + +## Dependency manager + +The `SuperchainConfig` contract will manage and keep track of the dependency graph. +It will be queried as the source of truth to get which chains are part of the Superchain. + +The `SuperchainConfig` contract is updated with a new `DEPENDENCY_MANAGER` role that has the ability +to add a dependency to the dependency set. + +### Interface and properties + +The contract will add the following storage layout and function: + +#### `SHARED_LOCKBOX` + +- An immutable address pointing to the `SharedLockbox` contract. +- This address MUST be immutable because there's only one `SharedLockbox` for each cluster. + +#### `dependencyManager` + +- An address with the ability to add a dependency to the dependency set. +- The `dependencyManager` can only be set during initialization. + +#### `dependencySet` + +- Stores the current list of chain IDs in the op-governed dependency set. +- It MUST contain all the chain IDs of the chains that integrate the corresponding Superchain network. + +#### `addDependency` + +The `addDependency` function adds a new chain to the op-governed cluster. + +It can only be called by the `DEPENDENCY_MANAGER` role in the `SuperchainConfig` and ensures that the chain ID +is not already included in the dependency set. + +Before proceeding, it verifies that the dependency set is not full (`type(uint8).max`). +This function allowlists the new chain's `OptimismPortal` in the `SharedLockbox`. + +Finally, it emits the `DependencyAdded` event with the `chainId`, its corresponding `SystemConfig`, and `OptimismPortal`. + +```solidity +function addDependency(uint256 _chainId, address _systemConfig) external; +``` + +### Events + +#### `DependencyAdded` + +MUST be triggered when `addDependency` is called + +```solidity +event DependencyAdded(uint256 indexed chainId, address indexed systemConfig, address indexed portal); +``` + +### Invariants + +- Only the `DEPENDENCY_MANAGER` role MUST be able to add a new dependency to the dependency set + +- A new chain CAN NOT be added to the dependency set once the maximum size is reached. + +- The chain ID MUST be different from the current `block.chainid` + +- The same chain MUST NOT be added more than once + +- It MUST authorize the new chain’s `OptimismPortal` to interact with the `SharedLockbox` of the cluster + +- It MUST emit a `ChainAdded` event when the chain is added