Skip to content

Commit 1e1327b

Browse files
authored
design-doc: interoperable ether (#25)
* design-doc: interoperable ether Adds a design doc for what interoperable ether can look like. * design: add prototype
1 parent f9d0ea1 commit 1e1327b

File tree

1 file changed

+198
-0
lines changed

1 file changed

+198
-0
lines changed

protocol/interoperable-ether.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Purpose
2+
3+
This document exists to align on a design for interoperable `ether`. Without this, user experience would
4+
be greatly hurt and `ether` is commonly used to pay for gas, which is necessary to use a chain.
5+
6+
# Summary
7+
8+
A new `WETH` predeploy is introduced that adds the `SuperchainERC20` functionality. It has a simple migration path from
9+
legacy `WETH` and maintains the ability to have interop with custom gas token chains.
10+
11+
# Problem Statement + Context
12+
13+
The enshrinement of native asset sends between chains was removed as part of the interop design. This greatly reduced
14+
the scope of the project as a native ether send feature could be subject to a double spend attack. Instead, we opt
15+
for using `WETH` to send between chains. The existing `WETH` contract is not proxied meaning that it cannot easily
16+
be upgraded. It also existed from before the times of `SuperchainERC20`, meaning that it has no built in cross chain
17+
sending ability.
18+
19+
With the introduction of custom gas token, the existing `WETH` predeploy now has the semantics of "wrapped native asset",
20+
meaning that the `WETH` predeploy is not guaranteed to be `WETH`. This means one of two things:
21+
22+
- A new `WETH` predeploy is introduced that supports `SuperchainERC20`
23+
- Custom gas token chains can never be interoperable
24+
25+
# Proposed Solution
26+
27+
A new `WETH` predeploy is introduced that supports interfaces `ISuperchainERC20`.
28+
This means that the new `WETH` predeploy allows for cross chain transfers.
29+
30+
The main problem with this solution is the fact that it will break liquidity for wrapped `ether` into
31+
2 different contracts. Many applications already exist today that integrate with the existing predeploy.
32+
This will be annoying, but it should be very simple to migrate from the old `WETH` to the new `WETH`.
33+
Its a simple unwrap and wrap, and technically we could build support directly into the new `WETH` contract
34+
to make the migration extra simple.
35+
36+
An important problem to solve is ensuring that there are no liquidity constraints. If a wrap/unwrap mechanism
37+
is used rather than a mint/burn mechanism, it will result in liquidity constraints. Therefore we prefer a
38+
mint/burn mechanism. We need a solution to the liquidity constraint that specifically happens when a user is
39+
trying to send ether to a remote domain. They have to wrap the ether into weth and then it gets sent between
40+
chains as weth and then we need to unwrap the weth. Its possible that there isn’t enough weth to unwrap on the other side.
41+
42+
We introduce 2 new predeploys
43+
44+
- `SuperchainWETH`
45+
- Implements `SuperchainERC20`
46+
- Guaranteed to be wrapped ether rather than wrapped native asset
47+
- `ETHLiquidity`
48+
- Contains an artificially large pool of `ether` to provide liquidity for cross chain sends
49+
- We “burn” ether by sending it here and “mint” ether by pulling it out
50+
- Only `SuperchainWETH` can interact with this contract
51+
- Only works on non custom gas token chains
52+
- This is similar to the Scroll bridge where in genesis they mint a ton of ether into the contract where it can only be unlocked via a L1 to L2 deposit
53+
- Placed in genesis with a balance equal to `type(uint248).max`
54+
55+
The important invariant is that `ether` cannot be withdrawn from the `SuperchainWETH` contract by entities that do not own it.
56+
This would result in inflating the supply of ether.
57+
58+
### SuperchainWETH
59+
60+
- Users can send ether between chains as this contract will handle the wrapping and unwrapping, this only works when
61+
the source and destination are not custom gas token
62+
- Users can send ether from a non custom gas token chain to a custom gas token chain and it will end up as weth
63+
- Users can send weth from a custom gas token chain to a non custom gas token chain and it will end up as ether
64+
- Users can send weth between custom gas token chains
65+
66+
```solidity
67+
contract SuperchainWETH is WETH98, ISuperchainERC20 {
68+
L1Block internal l1Block = L1Block(Predeploys.L1_BLOCK);
69+
L2ToL2CrossDomainMessenger messenger = L2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSSDOMAIN_MESSENGER);
70+
ETHLiquidity liquidity = ETHLiquidity(Predeploys.ETH_LIQUIDITY);
71+
72+
function deposit() public payable override {
73+
if (l1block.isCustomGasToken()) revert IsCustomGasToken();
74+
super.deposit();
75+
}
76+
77+
function withdraw(uint256 wad) public override {
78+
if (l1block.isCustomGasToken()) revert IsCustomGasToken();
79+
super.withdraw(wad);
80+
}
81+
82+
function sendETHTo(address to, uint256 chainId) public payable {
83+
if (l1block.isCustomGasToken()) revert IsCustomGasToken();
84+
85+
liquidity.burn{ value: msg.value }();
86+
87+
sendMessage({
88+
_destination: chainId,
89+
_message: abi.encodeCall(SuperchainWETH.relayETH, (to, msg.value))
90+
});
91+
}
92+
93+
function sendERC20(uint256 wad, uint256 chainId) {
94+
sendERC20To(msg.sender, wad, chainID);
95+
}
96+
97+
function sendERC20To(address to, uint256 wad, uint256 chainId) external {
98+
require(balanceOf[msg.sender] >= wad);
99+
balanceOf[msg.sender] -= wad;
100+
101+
if (l1Block.isCustomGasToken() == false) {
102+
liquidity.burn{ value: wad }();
103+
}
104+
105+
sendMessage({
106+
_destination: chainId,
107+
_message: abi.encodeCall(SuperchainWETH.relayERC20, (to, wad))
108+
});
109+
}
110+
111+
function sendMessage(uint256 _destination, bytes memory _message) internal {
112+
messenger.sendMessage({
113+
_destination: _destination,
114+
_target: address(this),
115+
_message: _message
116+
});
117+
}
118+
119+
function relayERC20(address to, uint256 amount) external {
120+
if (msg.sender != Predeploys.L2_TO_L2_CROSSDOMAIN_MESSENGER) revert Unauthorized();
121+
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();
122+
123+
if (l1Block.isCustomGasToken() == false) {
124+
liquidity.source(amount);
125+
}
126+
127+
balanceOf[to] += amount;
128+
}
129+
130+
function relayETH(address to, uint256 amount) external {
131+
if (l1block.isCustomGasToken()) revert IsCustomGasToken();
132+
if (msg.sender != Predeploys.L2_TO_L2_CROSSDOMAIN_MESSENGER) revert Unauthorized();
133+
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();
134+
135+
liquidity.source(amount);
136+
137+
bool success = SafeCall.transfer({ _target: to, _amount: amount });
138+
require(success);
139+
}
140+
}
141+
142+
// Placed in genesis with a balance equal to type(uint248).max
143+
contract ETHLiquidity {
144+
address weth internal = Predeploys.SUPERCHAIN_WETH;
145+
L1Block internal l1Block = L1Block(Predeploys.L1_BLOCK);
146+
147+
function burn() external payable {
148+
if (msg.sender != weth) revert Unauthorized();
149+
}
150+
151+
function source(uint256 amount) {
152+
if (msg.sender != weth) revert Unauthorized();
153+
if (l1Block.isCustomGasToken()) revert OnlyEther();
154+
require(SafeCall.transfer({ _target: weth, _value: amount }));
155+
}
156+
}
157+
```
158+
159+
### Longer term nice to haves
160+
161+
- Enable ETH interface on `StandardBridge` for custom gas token chains and have it mint `SuperchainWETH` for deposits
162+
- We can call this out of scope for now, but a future upgrade can make `SuperchainWETH` an `OptimismMintableERC20` to enable this
163+
164+
# Open Questions
165+
166+
## Naming
167+
168+
We need to come up with a new name for this "superchain wrapped ether" to differentiate it. Not sure
169+
if it should be called `SWETH` or just stick with `WETH`. Without a different name, it will be confusing,
170+
but then it will create more overhead to get people to understand that its portable `WETH`.
171+
172+
Since the usage of custom gas token is legible from within both L1 and L2, this means that the superchain `WETH`
173+
can block deposits of native asset when its a custom gas token chain.
174+
175+
## `IOptimismMintableERC20` Support
176+
177+
It is also possible to add in support for `IOptimismMintableERC20` so that `WETH` can be deposited directly into
178+
this predeploy. This would solve the problem of having `ETH` and native asset liquidity on the L2, since the chain
179+
operator likely needs to sell the earned native asset into `ether` to pay for DA. This could look like the `L1StandardBridge`
180+
automatically converting `ether` into `WETH` and then depositing it such that it mints the superchain `WETH` on L2.
181+
Without this solution, it means there is no easy way to get fungible `ether` on to a custom gas token chain.
182+
183+
# Alternatives Considered
184+
185+
## Upgrade Existing WETH Predeploy
186+
187+
The existing `WETH` predeploy is not proxied, meaning the only way to upgrade
188+
it is with an irregular state transition. This is not a solution that we should
189+
take often, it adds technical debt that must be implemented in every execution
190+
layer client that supports OP Stack.
191+
192+
Following this decision would be inconsistent with previous decision making where
193+
we decided specifically to remove native ether sends from within the protocol
194+
so that custom gas token chains can be interoperable.
195+
196+
# Risks & Uncertainties
197+
198+
- This adds a lot of bridge risk as its a new way to send `ether` between chains. We need to be sure to think in terms of state machines and invariants.

0 commit comments

Comments
 (0)