Skip to content

Commit

Permalink
feat: add shared lockbox spec (#7)
Browse files Browse the repository at this point in the history
* feat: add shared lockbox spec

* feat: update shared lockbox spec

* fix: specs

* feat: add mermaid diagram

* fix: prs comments

* fix: nits

* fix: remove dependency manager

* feat: refactor lockbox specs

* fix: nit

* fix: misspellings

* fix: verb tense

* fix: prs comments

* fix: small changes
  • Loading branch information
agusduha authored Nov 22, 2024
1 parent bb897d8 commit f9ef0fc
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 1 deletion.
49 changes: 49 additions & 0 deletions specs/interop/optimism-portal-interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# OptimismPortal

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [Overview](#overview)
- [Interface and properties](#interface-and-properties)
- [Integrating `SharedLockbox`](#integrating-sharedlockbox)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

The `OptimismPortal` contract is upgraded to integrate the `SharedLockbox` and start using the shared ETH liquidity.

### Interface and properties

The `OptimismPortal` contract will add the following storage layout and modified functions:

**`SHARED_LOCKBOX`**

- An immutable address pointing to the `SharedLockbox` contract.
- This address MUST be immutable because all `OptimismPortals` will point to the same `SharedLockbox`,
and this address SHOULD not change.

### Integrating `SharedLockbox`

The integration with the `SharedLockbox` involves adding extra steps when executing deposit transactions
or finalizing withdrawal transactions.
These steps include locking and unlocking ETH 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` on the `SharedLockbox` if:
- The token is `ETHER`.
- `msg.value` is greater than zero.

**`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
128 changes: 128 additions & 0 deletions specs/interop/shared-lockbox-upgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Shared Lockbox - Upgrade and migration process

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**

- [Overview](#overview)
- [Add the chain to the op-governed dependency set](#add-the-chain-to-the-op-governed-dependency-set)
- [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)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

Based on the assumption that a chain joining the dependency set is an irreversible process,
the on-chain chains list is simplified by assuming that joining the Shared Lockbox is
equivalent to joining the op-governed dependency set.

The upgrade process consists of three main points:

- Add the chain to the op-governed dependency set
- 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

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 this process is complete, 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 an intermediate 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`.

- It MUST transfer the whole ETH balance to the `SharedLockbox` when called.

```solidity
function migrateETH() external;
```

### `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.
The `SharedLockbox` address will be set during the `initialize` function. After this step,
the `OptimismPortal` will not be able to process deposits and withdrawals until the chain is registered
in `SuperchainConfig`.

## 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)
%% 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
128 changes: 128 additions & 0 deletions specs/interop/shared-lockbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Shared Lockbox

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

**Table of Contents**

- [Overview](#overview)
- [Design](#design)
- [Interface and properties](#interface-and-properties)
- [Events](#events)
- [Reference implementation](#reference-implementation)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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 accross 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 `SuperchanConfig` using the `authorizePortal` function
when a chain is added.

### Interface and properties

**`lockETH`**

Deposits and locks ETH into the lockbox's liquidity pool.

- The function MUST accept ETH.
- Only authorized `OptimismPortal` addresses SHOULD be allowed to interact.
- 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 SHOULD be allowed to interact.
- 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 SHOULD 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);
```

## 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);
}
```
58 changes: 57 additions & 1 deletion specs/protocol/superchain-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

**Table of Contents**

- [Overview](#overview)
Expand All @@ -10,13 +11,16 @@
- [Pausability](#pausability)
- [Paused identifiers](#paused-identifiers)
- [Scope of pausability](#scope-of-pausability)
- [Dependency manager](#dependency-manager)
- [Interface and properties](#interface-and-properties)
- [Events](#events)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 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

Expand Down Expand Up @@ -64,3 +68,55 @@ When the Pause is activated, the following methods are disabled:
1. `StandardBridge.finalizeBridgeERC20()`
1. `StandardBridge.finalizeBridgeETH()`
1. `L1ERC721Bridge.finalizeBridgeERC721()`

## 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.

### 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.

**`systemConfigs`**

- A mapping that associates chain IDs with their respective SystemConfig addresses.
- It will be used when updating dependencies along each chain.

**`dependencySet`**

- An `EnumerableSet` that stores the current list of chain IDs in the 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 SHOULD only be callable by the authorized `updater` role of the `SuperchainConfig`.
- It MUST NOT add a chain ID to the dependency set if it is already included.
- It MUST check that the new chain dependency set size is zero.
- It MUST update all chain dependencies through deposit txs to form a complete mesh graph.
- It MUST store the provided `SystemConfig` address in the `systemConfigs` mapping.
- It MUST allowlist the new chain's `OptimismPortal` in the `SharedLockbox`.
- It MUST emit the `ChainAdded` event with the `chainId` and
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);
```

0 comments on commit f9ef0fc

Please sign in to comment.