-
Notifications
You must be signed in to change notification settings - Fork 25
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
Conversation
This should include the following options:
It should also cover how custom gas token impacts things |
This comment was marked as outdated.
This comment was marked as outdated.
Adds a design doc for what interoperable ether can look like.
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 |
There was a problem hiding this comment.
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` |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
function burn() external payable { | ||
if (msg.sender != weth) revert Unauthorized(); | ||
} | ||
|
||
function source(uint256 amount) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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
- 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
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`. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
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 ProblemsETH 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 ETHNot 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 complexitySupport 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 functionalityAny 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 experienceAn 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 SolutionWe propose solving for interoperable ETH by introducing a predeployed WETH contract that supports the ConsiderationsDealing with existing the WETH predeployWe 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 liquiditySuperchain 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 Handling chains that use native assets other than ETHAs 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 experienceAn 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:
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.
} |
The weth supply is not accurate because you can use 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 |
This should be a proxied contract and only upgradable by the system transactions |
Can we make this solution more generic? So it doesn't enshrine ether as deeply in the design |
Should we use the mirror approach for this to solve the fragmentation issue? |
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? |
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. |
Approving but would like to see @skeletor-spaceman's response to the above before merging. |
yeah, this is a big counter-point to the Mirror approach. Since the standard bridge just sends raw with this in mind, and going back to one of the key-points above:
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? |
Got it, if the question is predeploy vs not, I have no strong opinions there. cc @tynes for input. |
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
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 |
We need to be careful of pulling in too many features, ie making superchain weth support |
@tynes agreed, my understanding was that if possible we'd want to keep the number of pre-deploys at a minimum. |
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