Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

design-doc: interoperable ether #25

Merged
merged 2 commits into from
Jun 24, 2024
Merged

design-doc: interoperable ether #25

merged 2 commits into from
Jun 24, 2024

Conversation

tynes
Copy link
Contributor

@tynes tynes commented May 23, 2024

Description

Adds a design doc for what interoperable ether. This is a core feature for interop devnet v2.

The ISuperchainERC20 spec can be found here

@tynes
Copy link
Contributor Author

tynes commented May 23, 2024

This should include the following options:

  • new predeploy
  • keep existing predeploy (do nothing)

It should also cover how custom gas token impacts things

@tynes

This comment was marked as outdated.

@tynes tynes mentioned this pull request Jun 17, 2024
tynes added 2 commits June 17, 2024 13:21
Adds a design doc for what interoperable ether can look like.
@tynes tynes self-assigned this Jun 17, 2024
@tynes tynes marked this pull request as ready for review June 17, 2024 20:46
The main problem with this solution is the fact that it will break liquidity for wrapped `ether` into
2 different contracts. Many applications already exist today that integrate with the existing predeploy.
This will be annoying, but it should be very simple to migrate from the old `WETH` to the new `WETH`.
Its a simple unwrap and wrap, and technically we could build support directly into the new `WETH` contract

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relevant holders for OP Mainnet, looks like a mix of Aave and bridges: https://optimistic.etherscan.io/token/0x4200000000000000000000000000000000000006#balances

With the introduction of custom gas token, the existing `WETH` predeploy now has the semantics of "wrapped native asset",
meaning that the `WETH` predeploy is not guaranteed to be `WETH`. This means one of two things:

- A new `WETH` predeploy is introduced that supports `SuperchainERC20`
Copy link
Contributor

@mds1 mds1 Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the code currently in contracts-bedrock/src/L2/WETH.sol is renamed to something like WrappedNativeToken.sol and is still deployed at 0x4200000000000000000000000000000000000006, then a new SuperchainERC20-comatible WETH.sol is added and placed at a different address?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly

- We “burn” ether by sending it here and “mint” ether by pulling it out
- Only `SuperchainWETH` can interact with this contract
- Only works on non custom gas token chains
- 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a docs link where I can learn more? Searched briefly but didn't see it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cant find docs on it but you can see on the block explorer here: https://scrollscan.com/address/0x781e90f1c8fc4611c9b7497c3b47f99ef6969cbc

The balance is absurdly high

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I was able to find it in the genesis.json of their docs here based on the balance of that account at block 0

});
}

function sendERC20(uint256 wad, uint256 chainId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the user perspective, we are calling a WETH contract to move Ether (either natively, or in wrapped form between chains). Therefore I think it would be nice if we can simplify the interface to remove unify the sendETHTo and sendERC20To method into a single sendWETHTo method. Similar for relayETH vs. relayERC20. This method would be have conditionals of course

function sendWETHTo(address to, uint256 wad, uint256 chainId) external payable {
  if (l1block.isCustomGasToken()) {
    require(msg.value == 0);
  } else {
    require(msg.value == wad);
  }
}

I'd also be ok with adding an overload that has no wad input for chains where the gas token is ETH. But having the (W)ETH holder worry about the chain's gas token with different named methods feels confusing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could certainly simplify things, this is a good suggestion. Ideally there is a smaller interface

Comment on lines +147 to +151
function burn() external payable {
if (msg.sender != weth) revert Unauthorized();
}

function source(uint256 amount) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming nit: burn and source don't sound like inverses—maybe lock/unlock or burn/mint? either way, not a blocker and can defer on this until the spec is written

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can have the naming nit convo on the specs PR

L1Block internal l1Block = L1Block(Predeploys.L1_BLOCK);

function burn() external payable {
if (msg.sender != weth) revert Unauthorized();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also have if (l1Block.isCustomGasToken()) revert OnlyEther()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we need this necessarily, if we want weth.totalSupply() to be more accurate we may need to call burn on ether paying chains

Comment on lines +161 to +162
- Enable ETH interface on `StandardBridge` for custom gas token chains and have it mint `SuperchainWETH` for deposits
- We can call this out of scope for now, but a future upgrade can make `SuperchainWETH` an `OptimismMintableERC20` to enable this
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand on what "Enable ETH interface" means? What are the benefits/tradeoffs of this nice to have?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The following ABI on the StandardBridge is disabled for custom gas token chains:

  • bridgeETH(uint32,bytes)
  • bridgeETHTo(address,uint32,bytes)
  • receive()

You could imagine enabling these functions and having the ether deposits become WETH on L2 on custom gas token chains automatically

Comment on lines +168 to +170
We need to come up with a new name for this "superchain wrapped ether" to differentiate it. Not sure
if it should be called `SWETH` or just stick with `WETH`. Without a different name, it will be confusing,
but then it will create more overhead to get people to understand that its portable `WETH`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question, applies to generic SuperchainERC20 tokens too

## `IOptimismMintableERC20` Support

It is also possible to add in support for `IOptimismMintableERC20` so that `WETH` can be deposited directly into
this predeploy. This would solve the problem of having `ETH` and native asset liquidity on the L2, since the chain
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "this predeploy" do you mean SuperchainWETH or ETHLiquidity?


# Risks & Uncertainties

- 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other risks:

  • Placing type(uint248).max into ETHLiquidity, since we might introduce a bug that lets a disproportionate amount of ETH be withdrawn
  • User confusion: Lots of new concepts, naming questions, etc.
  • Are there any risks or footguns we introduce here by splitting up WETH supply between two predeploys? Want to think on the consequences there more

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are great to add to the failure mode analysis

sendERC20To(msg.sender, wad, chainID);
}

function sendERC20To(address to, uint256 wad, uint256 chainId) external {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is named sendERC20To instead of sendWETHTo to avoid users confusing.fat-fingering it with sendETHTo, right or there's any other reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is meant to satisfy SuperchainERC20 which is why it has this naming, per ethereum-optimism/specs#71

}

// Placed in genesis with a balance equal to type(uint248).max
contract ETHLiquidity {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this solution, this predeploy is the only thing that unnerves me a bit. Mostly because Scroll has it contained in a single chain, and here we will have it in many chains that interact with one another. This wouldn't be needed with the approach of allowing the L1StandardBridge to wrap ether into WETH and minting this in the L2s, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a problem where when sending ether between 2 chains, there is no way to "mint" the ether on the remote chain. We can mint as much WETH as we want from within the protocol, but we are unable to unwrap that arbitrarily unless we stick a ton of ether into state that can only be unlocked

@smartcontracts
Copy link
Contributor

I'm going to be taking over this spec/design work from @tynes. Dropping my own version of the initial design doc here as a comment before I likely turn this into its own PR following the design review meeting today.

Context and Key Problems

ETH is the native asset of Ethereum and has by extension also become the native asset of many Ethereum L2 blockchains. In its role as a native asset, ETH can be used to pay for transaction fees and can be transferred from account to account via calls with attached value. ETH plays a significant role in the economics of most L2s and any protocol that enables interoperability between chains must be able to account for ETH.

We want to handle native assets other than ETH

Not all chains using the OP Stack use ETH as the native asset. We would like these chains to be able to interoperate with chains that do use ETH as a native asset. Certain solutions that might work when all chains use ETH as a native asset begin to break down when alternative native assets are introduced. For example, a protocol that burns the native asset on one chain and mints it on another will work if both chains use the same native asset but will obviously fail if either chain uses a different native asset.

We want to minimize protocol complexity

Support for native ETH opens the door to unnecessary complexity. Any solution to this problem should aim to minimize the amount of protocol code required to support native ETH. This generally points towards an app-layer solution if possible but does not preclude a protocol-layer solution as long as we minimize implementation size.

We want to maximize functionality

Any solution should strive to maximize the functionality of the underlying message-passing protocol. Support for native ETH should not unnecessarily restrict the interop protocol.

We want to streamline the user experience

An ideal solution would make it possible to streamline the user experience such that ETH transfers between chains look just like ETH transfers between addresses on the same chain. Much of the user experience can be solved at the wallet level but it’s also important that the underlying protocol doesn’t require an unnecessary number of transactions to

Proposed Solution

We propose solving for interoperable ETH by introducing a predeployed WETH contract that supports the ISuperchainERC20 interface. This contract, referred to as “Superchain WETH” for the remainder of this document, would be redeemable for the native asset on chains that use ETH as a native asset but would only be usable in its wrapped ERC-20 form on chains that use any other native asset.

Considerations

Dealing with existing the WETH predeploy

We have an existing WETH predeploy that is already used by a number of contracts on OP Mainnet and other OP Stack chains. Adding a new WETH predeploy would potentially fragment ETH liquidity in certain app-level protocols. We should carefully explore the impact of this fragmentation and determine if this impact justifies a fork that upgrades the existing WETH contract rather than introducing a new predeploy.

It is important to note that the existing WETH contract represents “wrapped native asset” and not “wrapped ETH” and any solution must account for this discrepancy. We want Superchain WETH to universally represent “wrapped ETH” on every chain. An ideal solution here would not create a massive diff between ETH and non-ETH chains.

Supplying ETH liquidity

Superchain WETH introduces the need to supply some sort of ETH liquidity that makes it possible to actually withdraw WETH into ETH. We can easily credit a WETH balance on a receiving chain but the receiving WETH contract will not actually have the ETH balance to facilitate the transfer into native ETH. We propose solving this issue by giving the Superchain WETH contract access to a very large (uint248 max) balance of ETH on each chain. Other projects (notably Scroll) have taken a similar strategy to handling ETH bridging as an alternative to minting ETH natively.

Allowing WETH to access a large ETH balance should not introduce any security concerns. WETH balances that allow ETH withdrawals would still be governed by the standard WETH logic. We must make sure to maintain the key invariant that the total amount of withdrawable WETH is exactly equal to the amount of deposited WETH.

We can implement this logic by either (1) crediting the WETH contract directly with a large ETH balance or (2) crediting some other contract (the ”Superchain Liqudiity” contract) with a large ETH balance and exclusively allowing WETH to tap into this contract. Crediting WETH directly is a simpler design but has the unintended side-effect of also creating an artificially large total WETH supply on paper because the standard WETH9 contract reads the total supply from address(this).balance. We need to consider whether we should modify WETH9 to compute the total supply from deposits and withdrawals or if we should introduce a separate Superchain Liquidity contract to sidestep this total supply issue.

Handling chains that use native assets other than ETH

As previously noted, we want Superchain WETH to represent “wrapped ETH” on all chains regardless of the native asset. This means that Superchain WETH must be able to distinguish between chains that use ETH as a native asset and chains that do not. Superchain WETH on ETH-native chains should be redeemable for the native asset while Superchain WETH on all other chains must remain in the ERC-20 form.

Streamlining user experience

An ideal user experience would mean that sending ETH from one chain to another feels just like sending ETH between two accounts on the same chain. We can mostly solve this at the wallet level but we should aim to keep the transfer process to a single transaction.

We can get pretty far by providing two ways to interact with Superchain WETH:

  1. Call a function sendETH that wraps the msg.value, transfers the WETH balance to the Superchain WETH contract on the receiving chain, and unwraps ETH to the recipient address if the receiving chain uses ETH as a native asset. ETH would remain in WETH form if the receiving chain uses a different native asset.
  2. Call a function sendERC20 that wraps the msg.value, transfers the WETH balance to the Superchain WETH contract on the receiving chain, and keeps the balance in WETH regardless of the native asset used by the receiving chain.

As usual, we could reduce this into a single function with toggles if desired:

function sendWETHTo(
    address recipient,
    uint256 amount,
    uint256 chainId,
    bool unwrap
)
    public
    payable
{
    // If amount is zero, send using WETH balance.
    // If amount is nonzero, send using msg.value.
    // Send message to recipient chain.
}

function relayWETHTo(
    address recipient,
    uint256 amount,
    bool unwrap
)
    public
{
    // Check that this is from the cross domain messenger.
    // Increase the recipient's balance.
    // If unwrap and this is a native ETH chain then perform unwrap.
}

@tynes
Copy link
Contributor Author

tynes commented Jun 20, 2024

The weth supply is not accurate because you can use selfdestruct to transfer ether to the contract without any execution (where it would usually mint weth). This would increase the totalSupply of weth without minting any erc20 tokens.

I still lean towards keeping the liquidity in its own contract to keep a clear boundary around the superchain ending bug of that ether being able to be removed somehow. The control flow of accessing this locked ether should be defined super clearly and it unlocks future modifications to the superchain weth predeploy in the future

@tynes
Copy link
Contributor Author

tynes commented Jun 20, 2024

This should be a proxied contract and only upgradable by the system transactions

@tynes
Copy link
Contributor Author

tynes commented Jun 20, 2024

Can we make this solution more generic? So it doesn't enshrine ether as deeply in the design

@tynes
Copy link
Contributor Author

tynes commented Jun 20, 2024

Should we use the mirror approach for this to solve the fragmentation issue?

@tynes
Copy link
Contributor Author

tynes commented Jun 21, 2024

After chatting with @smartcontracts about our constraints and optimization targets, we decided that we shouldn't focus on making the solution more generic or use the mirror approach. Right now there is extremely low usage of custom gas token chains, so it would be a premature optimization to make it more generic right now. We can solve the usecase for the chains that contribute the most revenue to the collective now (all ether as gas) then think about a more generic solution in the future

@skeletor-spaceman
Copy link
Contributor

skeletor-spaceman commented Jun 23, 2024

After chatting with @smartcontracts about our constraints and optimization targets, we decided that we shouldn't focus on making the solution more generic or use the mirror approach. Right now there is extremely low usage of custom gas token chains, so it would be a premature optimization to make it more generic right now. We can solve the usecase for the chains that contribute the most revenue to the collective now (all ether as gas) then think about a more generic solution in the future

What are the tradeoffs of not using the mirror contract and using a new custom pre-deploy to handle the WETH case ?

Could we get more insight into the reasoning behind the decision?

@smartcontracts
Copy link
Contributor

What are the tradeoffs of not using the mirror contract and using a new custom pre-deploy to handle the WETH case ?

I think the main reason not to use the mirror contract is because the chain contracts end up looking different if you're on a custom gas chain or not. I don't really love the idea of having some chains have a mirror contract while other chains just have this new Superchain WETH contract.

Mirroring would also require upgrading the existing WETH contract which requires a hardfork. Would be very annoying. Current approach works well enough, doesn't need a hardfork, and doesn't end up with different contracts on different chain types.

@smartcontracts
Copy link
Contributor

Approving but would like to see @skeletor-spaceman's response to the above before merging.

@skeletor-spaceman
Copy link
Contributor

Mirroring would also require upgrading the existing WETH contract which requires a hardfork

yeah, this is a big counter-point to the Mirror approach. Since the standard bridge just sends raw msg.value, and WETH is not used as a _isOptimismMintableERC20(...). where the internal mint on WETH, would pull from the "infinite" ETH pre-mine

with this in mind, and going back to one of the key-points above:

We should carefully explore the impact of this fragmentation and determine if this impact justifies a fork that upgrades the existing WETH contract rather than introducing a new predeploy.

I'm still a bit hesitant on why we would need to create a new "custom" pre-deploy that would behave almost exactly as any other superc20, for which we could leverage all the superc20 tooling and periphery for a better more comprehensive UX/DX. I'd love to avoid "contract/flow" fragmentation when sending tokens, native or not.

mirror is out of the question, we agree there. liquidity fragmentation is inevitable without a complex hard-fork that would modify the WETH pre-deploy for it to be able to handle ETH + custom gas tokens under the same address. (even if possible, is a mess)

but, why do we need superWETH to be a pre-deploy?

@smartcontracts
Copy link
Contributor

Got it, if the question is predeploy vs not, I have no strong opinions there. cc @tynes for input.

@tynes
Copy link
Contributor Author

tynes commented Jun 24, 2024

but, why do we need superWETH to be a pre-deploy?

The superchain erc20 spec includes the fact that the address is the same on all chains so making superchain weth be the same address on all chains as a predeploy is the most simple option. Since we want this on all op stack chains, the two options are predeploy or preinstall, I definitely lean towards predeploy since the existing weth aka wrapped native asset is a predeploy

for which we could leverage all the superc20 tooling and periphery for a better more comprehensive UX/DX.

The ABI on superchain weth will implement super erc20, so the tooling will still work. super erc20 is an interface, not all super erc20 tokens must be mirrors, superchain weth is an instance of a super erc20 that is meant to be weth on all chains, custom gas token or not

@tynes
Copy link
Contributor Author

tynes commented Jun 24, 2024

We need to be careful of pulling in too many features, ie making superchain weth support IOptimismMintableERC20 and allowing L1 weth to populate it. That seems like a nice to have feature in the future, we don't necessarily need that now

@skeletor-spaceman
Copy link
Contributor

@tynes agreed, my understanding was that if possible we'd want to keep the number of pre-deploys at a minimum.
But if it's easier to just make this a pre-deploy, I'm onboard.

@tynes tynes merged commit 1e1327b into main Jun 24, 2024
@tynes tynes deleted the interop-ether branch June 24, 2024 23:38
@mds1 mds1 mentioned this pull request Jun 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

6 participants