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