diff --git a/specs/interop/dependency-set.md b/specs/interop/dependency-set.md index 8c6351237..2f0176035 100644 --- a/specs/interop/dependency-set.md +++ b/specs/interop/dependency-set.md @@ -6,6 +6,7 @@ - [Chain ID](#chain-id) - [Updating the Dependency Set](#updating-the-dependency-set) + - [Invariants](#invariants) - [Security Considerations](#security-considerations) - [Layer 1 as Part of the Dependency Set](#layer-1-as-part-of-the-dependency-set) @@ -47,17 +48,18 @@ 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 updated to have the `SuperchainConfig` address and to manage the dependency set. -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 `SuperchainConfig` 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. +### Invariants + +- Only the `SuperchainConfig` contract MUST be able to add a new dependency + +- Only the `SuperchainConfig` contract MUST be able to remove a dependency + ## Security Considerations ### Layer 1 as Part of the Dependency Set 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/shared-lockbox-upgrade.md b/specs/interop/shared-lockbox-upgrade.md new file mode 100644 index 000000000..1f7700f66 --- /dev/null +++ b/specs/interop/shared-lockbox-upgrade.md @@ -0,0 +1,129 @@ +# Shared Lockbox - Upgrade and migration process + + + +**Table of Contents** + +- [Overview](#overview) + - [Add the chain to the op-governed dependency set in `SuperchainConfig`](#add-the-chain-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 the chain 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 +- `SuperchainConfig` is upgraded to manage the dependency set +- `SystemConfig` is upgraded to the interop contract version + +### Add the chain to the op-governed dependency set in `SuperchainConfig` + +The `SuperchainConfig` contract will be responsible for storing and managing the dependency set. +Its `addChain` function will be used to add the chain to the dependency set and call the `SystemConfig` of each chain +to keep them in sync. +It will also allowlist the corresponding `OptimismPortal`, enabling it to lock and unlock ETH from the `SharedLockbox`. +Once `addChain` is called, the system will be ready to process deposits and withdrawals. + +### 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 `addChain` 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 chain 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 chain to SuperchainConfig + L1PAO->>SuperchainConfig: addChain(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 `addChain`, 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..8c42ffa31 100644 --- a/specs/protocol/superchain-configuration.md +++ b/specs/protocol/superchain-configuration.md @@ -10,13 +10,23 @@ - [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) + - [`upgrader`](#upgrader) + - [`systemConfigs`](#systemconfigs) + - [`dependencySet`](#dependencyset) + - [`addChain`](#addchain) + - [Events](#events) + - [`ChainAdded`](#chainadded) + - [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 +36,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. +- `UPGRADER_SLOT`: The address of the upgrader, which can add a chain to the depenceny set. ## Configuration data flow @@ -38,6 +49,7 @@ StandardBridge --> SuperchainConfig L1ERC721Bridge --> SuperchainConfig L1CrossDomainMessenger --> SuperchainConfig OptimismPortal --> SuperchainConfig +SharedLockbox --> SuperchainConfig ``` ### Pausability @@ -64,3 +76,84 @@ 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. +It will also allow to add a chain to the op-governed cluster and update each chain’s dependency set. + +The `SuperchainConfig` contract is updated with a new `UPGRADER` role that has the ability +to add a chain 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. + +#### `upgrader` + +- An address with the ability to add a chain to the dependency set. +- The `upgrader` can only be set during initialization. + +#### `systemConfigs` + +- A mapping that associates chain IDs with their respective SystemConfig addresses. +- It will be used when updating dependencies along each chain. + +#### `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. + +#### `addChain` + +The `addChain` function adds a new chain to the op-governed cluster. + +It can only be called by the `UPGRADER` role in the `SuperchainConfig` and ensures that the chain ID +is not already included in the dependency set. + +Before proceeding, it verifies that the new chain's dependency set size is zero. +The function updates all chain dependencies by executing deposit transactions to form a complete mesh graph, +stores the provided `SystemConfig` address in the `systemConfigs` mapping, and allowlists the +new chain's `OptimismPortal` in the `SharedLockbox`. + +Finally, it emits the `ChainAdded` event with the `chainId`, its corresponding `SystemConfig`, and `OptimismPortal`. + +```solidity +function addChain(uint256 _chainId, address _systemConfig) external; +``` + +### Events + +#### `ChainAdded` + +MUST be triggered when `addChain` is called + +```solidity +event ChainAdded(uint256 indexed chainId, address indexed systemConfig, address indexed portal); +``` + +### Invariants + +- Only the `UPGRADER` role MUST be able to add a new chain to the dependency set + +- The chain being added MUST NOT have any other dependencies before joining a cluster + +- The same chain MUST NOT be added more than once + +- It MUST add the new chain as a dependency on each chain of the cluster + +- It MUST add the whole cluster as dependency set for the new chain + +- It MUST NOT add the new chain to its own dependency set + +- 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