Skip to content

Commit

Permalink
Merge pull request #257 from ethereum-optimism/feat/superchain-token-…
Browse files Browse the repository at this point in the history
…standard-2

Interop: SuperchainERC20 Standard
  • Loading branch information
tynes authored Jul 9, 2024
2 parents a9033c1 + 28bfb6f commit 006391c
Showing 1 changed file with 152 additions and 62 deletions.
214 changes: 152 additions & 62 deletions specs/interop/token-bridging.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,186 @@

<!-- 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](#interface)
- [Functions](#functions)
- [`sendERC20`](#senderc20)
- [`relayERC20`](#relayerc20)
- [Events](#events)
- [`SendERC20`](#senderc20)
- [`RelayERC20`](#relayerc20)
- [Diagram](#diagram)
- [Implementation](#implementation)
- [L1 Native Tokens](#l1-native-tokens)
- [Cross Chain `transferFrom`](#cross-chain-transferfrom)
- [Factory](#factory)
- [Upgrading Existing `OptimismMintableERC20Token`s](#upgrading-existing-optimismmintableerc20tokens)
- [Invariants](#invariants)
- [Future Considerations](#future-considerations)
- [Cross Chain `transferFrom`](#cross-chain-transferfrom)

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

Unified ERC20 token bridging built on top of the messaging protocol
can enable tokens to be fungible and movable across the superchain.
An issue with bridging is that if the security model is not standardized,
then the bridged assets are not fungible with each other depending on
which bridge solution was used to facilitate the transfer between
domains.
## Overview

Without a standardized security model, bridged assets may not be fungible with each other.
The `SuperchainERC20` unifies ERC20 token bridging to make it fungible across the Superchain.
It builds on top of the messaging protocol, as the most trust minimized bridging solution.

Unlike other standards, such as [xERC20](https://www.xerc20.com/),
where users interact with a bridge possessing mint and burn
privileges on cross-chain enabled tokens, this approach allows users to call methods directly on the token contract.

## Interface

The `ISuperchainERC20` interface creates a standard way to send tokens
between domains. In contrast to [xERC20][xerc20] where the user calls
a bridge that has `mint` and `burn` privileges on the cross chain enabled
tokens, the user instead calls methods on the token contract directly.
### Functions

The standard will build on top of ERC20 and include the following functions:

#### `sendERC20`

[xerc20]: https://www.xerc20.com/
Transfer `_amount` amount of tokens to address `_to` in chain `_chainId`,
alongside with optional data `_data`.

It SHOULD burn `_amount` tokens and initialize a message to the `L2ToL2CrossChainMessenger` to mint the `_amount`
in the target address `_to` at `_chainId`. The optional `_data` will be included in as part of the
`L2ToL2CrossChainMessenger` message.

```solidity
interface ISuperchainERC20 {
function bridgeERC20(uint256 amount, uint256 chainId) external;
function bridgeERC20To(address to, uint256 amount, uint256 chainId) external;
function finalizeBridgeERC20(address to, uint256 amount) external;
}
sendERC20(address _to, uint256 _amount, uint256 _chainId, bytes _data)
```

## Implementation

An example implementation that depends on deterministic deployments across chains
for security is provided. This construction builds on top of the [L2ToL2CrossDomainMessenger][l2-to-l2]
for both replay protection and domain binding.
#### `relayERC20`

[l2-to-l2]: ./predeploys.md#l2tol2crossdomainmessenger
Process incoming messages IF AND ONLY IF initiated
by the same contract (token) address on a different chain
and come from the `L2ToL2CrossChainMessenger` in the local chain.
It will mint `_amount` to address `_to`, as defined in `sendERC20`.
If the message containes `_data`,
it will also include an external call to address `_to` with `_data`.

```solidity
contract OptimismSuperchainERC20 is ERC20, ISuperchainERC20 {
constructor(string memory _name, string memory _symbol, uint256 _decimals) ERC20(_name, _symbol, _decimals) {}
relayERC20(address _to, uint256 _amount, bytes _data)
```

function bridgeERC20(uint256 _amount, uint256 _chainId) external {
bridgeERC20To(msg.sender, _amount, _chainId);
}
### Events

function bridgeERC20To(address _to, uint256 _amount, uint256 _chainId) public {
_burn(msg.sender, amount);
#### `SendERC20`

L2ToL2CrossDomainMessenger.sendMessage({
_destination: chainId,
_target: address(this),
_message: abi.encodeCall(this.finalizeBridgeERC20, (to, amount))
});
}
MUST trigger when a cross-chain transfer is initiated using `sendERC20`.

```solidity
event SendERC20(address indexed _from, address indexed _to, uint256 _amount, uint256 _chainId, bytes memory _data)
```

function finalizeBridgeERC20(address _to, uint256 _amount) external {
require(msg.sender == address(L2ToL2CrossChainMessenger));
require(L2ToL2CrossChainMessenger.crossDomainMessageSender() == address(this));
#### `RelayERC20`

_mint(to, amount);
}
}
MUST trigger when a cross-chain transfer is finalized using `relayERC20`.

```solidity
event RelayERC20(address indexed to, uint256 amount, bytes data);
```

## Diagram

The following diagram depicts a cross-chain transfer.

```mermaid
sequenceDiagram
participant from
participant SuperERC20_A as SuperchainERC20 (Chain A)
participant Messenger_A as L2ToL2CrossDomainMessenger (Chain A)
participant Inbox as CrossL2Inbox
participant Messenger_B as L2ToL2CrossDomainMessenger (Chain B)
participant SuperERC20_B as SuperchainERC20 (Chain B)
participant Recipient as to
from->>SuperERC20_A: sendERC20To(to, amount, chainID, data)
SuperERC20_A->>SuperERC20_A: burn(from, amount)
SuperERC20_A->>Messenger_A: sendMessage(chainId, message)
note right of Messenger_A: relay or user calls
Messenger_A->>Inbox: executeMessage()
Inbox->>Messenger_B: relayMessage()
Messenger_B->>SuperERC20_B: relayERC20(to, amount, data)
SuperERC20_B->>SuperERC20_B: mint(to, amount)
note right of SuperERC20_B: Optional
SuperERC20_B->>Recipient: call(data)
```

## L1 Native Tokens
## Implementation

To support L1 native tokens with cross chain liquidity, the `OptimismMintableERC20Token`
interface MUST be supported in addition to the `SuperchainERC20` interface.
An example implementation that depends on deterministic deployments across chains
for security is provided.
This construction builds on top of the [L2ToL2CrossDomainMessenger][l2-to-l2]
for both replay protection and domain binding.

## Cross Chain `transferFrom`
[l2-to-l2]: ./predeploys.md#l2tol2crossdomainmessenger

Should an overloaded `approve` method be added to the interface so that a user can
approve a contract on a remote domain to bridge on their behalf? A new `allowances`
mapping could be added to the contract to enable this.
```solidity
function sendERC20(address _to, uint256 _amount, uint256 _chainId, bytes memory _data) public {
_burn(msg.sender, _amount);
bytes memory _message = abi.encodeCall(this.relayERC20, (_to, _amount, _data));
L2ToL2CrossDomainMessenger.sendMessage(_chainId, address(this), _message);
emit SendERC20(msg.sender, _to, _amount, _chainId, _data);
}
## Factory
function relayERC20(address _to, uint256 _amount, bytes memory _data) external {
require(msg.sender == address(L2ToL2CrossChainMessenger));
require(L2ToL2CrossChainMessenger.crossDomainMessageSender() == address(this));
_mint(_to, _amount);
A token factory predeploy can be used to ensure that `SuperchainERC20` tokens
can be permissionlessly and deterministically deployed. If a deterministic deployment
scheme is not used, maintaining a mapping of the token addresses between all of the
chains will not be scalable.
if (data.length > 0) {
(bool success, ) = _to.call(_data);
require(success, "External call failed");
}
## Upgrading Existing `OptimismMintableERC20Token`s
emit RelayERC20(_to, _amount, _data)
}
```

An `OptimismMintableERC20Token` does not natively have the functionality
to be sent between L2s. There are a few approaches to migrating L1 native tokens
to L2 ERC20 tokens that support the `SuperchainERC20` interface.
## Invariants

Besides the ERC20 invariants, the SuperchainERC20 will require the following interop specific properties:

- Conservation of bridged `amount`: The minted `amount` in `relayERC20()` should match the `amount`
that was burnt in `sendERC20()`, as long as target chain has the initiating chain in the dependency set.
- Corollary 1: Finalized cross-chain transactions will conserve the sum of `totalSupply`
and each user's balance for each chain in the Superchain.
- Corollary 2: Each initiated but not finalized message (included in initiating chain but not yet in target chain)
will decrease the `totalSupply` and the initiating user balance precisely by the burnt `amount`.
- Corollary 3: `SuperchainERC20s` should not charge a token fee or increase the balance when moving cross-chain.
- Note: if the target chain is not in the initiating chain dependency set,
funds will be locked, similar to sending funds to the wrong address.
If the target chain includes it later, these could be unlocked.
- Freedom of movement: Users should be able to send and receive tokens in any target
chain with the initiating chain in its dependency set
using `sendERC20()` and `relayERC20()`, respectively.
- Unique Messenger: The `sendERC20()` function must exclusively use the `L2toL2CrossDomainMessenger` for messaging.
Similarly, the `relayERC20()` function should only process messages originating from the `L2toL2CrossDomainMessenger`.
- Corollary: xERC20 and other standards from third-party bridges should use different functions.
- Unique Address: The `sendERC20()` function must exclusively send a message
to the same address on the target chain.
Similarly, the `relayERC20()` function should only process messages originating from the same address.
- Note: The 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.
For example, two SAFEs with the same address in two chains might have different owners.
With the prospects of a smart wallet future, it is impossible to assume
there will be a way to distinguish EOAs from smart wallets.
- A way to allow for remotely initiated bridging is to include remote approval,
i.e. approve a certain address in a certain chainId to spend local funds.
- Bridge Events:
- `sendERC20()` should emit a `SendERC20` event. `
- `relayERC20()` should emit a `RelayERC20` event.

## Future Considerations

### Cross Chain `transferFrom`

In addition to standard locally initialized bridging,
it is possible to allow contracts to be cross-chain interoperable.
For example, a contract in chain A could send pre-approved funds
from a user in chain B to a contract in chain C.

For the moment, the standard will not include any specific functionality
to facilitate such an action and rely on the usage of `permit2`.
If, at some point in the future, these actions were to be included in the standard,
a possible design could introduce a `remoteTransferFrom()` function.

0 comments on commit 006391c

Please sign in to comment.