From f4d3111d2f7e0f38252579afceb08f5921650c7e Mon Sep 17 00:00:00 2001 From: tre Date: Tue, 29 Oct 2024 12:01:41 -0700 Subject: [PATCH] interop: interoperable ether transfers --- protocol/interoperable-ether-transfers.md | 154 ++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 protocol/interoperable-ether-transfers.md diff --git a/protocol/interoperable-ether-transfers.md b/protocol/interoperable-ether-transfers.md new file mode 100644 index 00000000..be3ba1a9 --- /dev/null +++ b/protocol/interoperable-ether-transfers.md @@ -0,0 +1,154 @@ +# Purpose + +This document exists to align on a design for simplifying the process for sending `ETH` between two interoperable chains. + +# Summary + +New functionality is introduced to the `SuperchainWETH` contract that enables it to accept `ETH` and facilitate cross-chain `ETH` transfers to a specified recipient. + +# Problem Statement + Context + +Currently, L2-to-L2 `ETH` transfers between two interoperable chains require four separate transactions: + +1. Wrap `ETH` to `SuperchainWETH`. +2. Call `SuperchainTokenBridge#SendERC20` to burn `SuperchainWETH` on source chain and relay a message to the destination chain that mints `SuperchainWETH` to the recipient. +3. Execute the message on the destination chain, triggering `SuperchainTokenBridge#RelayERC20` to mint `SuperchainWETH` to the recipient. +4. Unwrap the received `SuperchainWETH` to `ETH`. + +The goal is to reduce the transaction count from four to two, enabling users to send `ETH` to a destination chain directly: + +1. Burn `ETH` on source chain and relay a message to destination chain that mints `ETH` to recipient on destination chain. +2. Execute the relayed message on the destination chain that mints `ETH` to the recipient. + +# Proposed Solution + +Add two new functions to the `SuperchainWETH` contract: `SendETH` and `RelayETH`. + +## `SendETH` + +The `SendETH` function combines the first two transactions as follows: + +1. Burns `ETH` within the `ETHLiquidity` contract equivalent to the `ETH` sent. +2. Sends a message to the destination chain encoding a call to `RelayETH`. + +## `RelayETH` + +The `RelayETH` function combines the last two transactions as follows: + +1. Mints the specified `_amount` of `ETH` from the `ETHLiquidity` contract. +2. Sends the minted `ETH` to the specified recipient. + +## Contract changes + +```solidity +/// @notice Sends ETH to some target address on another chain. +/// @param _to Address to send tokens to. +/// @param _chainId Chain ID of the destination chain. +function sendETH(address _to, uint256 _chainId) public payable { + if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { + revert NotCustomGasToken(); + } + + // Burn to ETHLiquidity contract. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: msg.value }(); + + // Send message to other chain. + IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({ + _destination: _chainId, + _target: address(this), + _message: abi.encodeCall(this.relayETH, (msg.sender, _to, msg.value)) + }); + + // Emit event. + emit SendETH(msg.sender, _to, msg.value, _chainId); +} + +/// @notice Relays ETH received from another chain. +/// @param _from Address of the msg.sender of sendETH on the source chain. +/// @param _to Address to relay tokens to. +/// @param _amount Amount of tokens to relay. +function relayETH(address _from, address _to, uint256 _amount) external { + // Receive message from other chain. + IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger(); + if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender(); + if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { + revert NotCustomGasToken(); + } + + // Mint from ETHLiquidity contract. + IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount); + + // Get source chain ID. + uint256 source = messenger.crossDomainMessageSource(); + + new SafeSend{ value: _amount }(payable(_to)); + + // Emit event. + emit RelayETH(_from, _to, _amount, source); +} +``` + +Since the `SuperchainWETH` contract already has permissions to mint and burn `ETH` with the `ETHLiquidity` contract no changes to the `ETHLiquidity` contract are necessary. + +A potential drawback of this approach is that it creates a second entrypoint outside of the `SuperchainTokenBridge` for performing asset transfers. However, there will already be alternative etnrypoints for asset transfers for any `SuperchainERC20` tokens that implement their own custom bridging. + +## Considerations + +## Custom Gas Token Chains + +To simplify the solution, custom gas token chains will not be supported and must follow the original four-transaction flow, which includes wrapping and unwrapping `SuperchainWETH`. + +# Open Questions + +- **Naming**: Should `SuperchainWETH` be renamed now that it also handles native `ETH` transfers? +- **Long-term improvements**: Could this functionality eventually extend to custom gas token chains? +- **Rollbacks**: How would rollbacks be handled in this implementation? + +# Alternatives Considered + +## Integrate `ETH` transfer into `SuperchainTokenBridge` + +One alternative is to add two new functions to the `SuperchainTokenBridge` contract: `SendETH` and `RelayETH`. + +```solidity +/// @notice Sends ETH to a target address on another chain. +/// @param _to Address to send ETH to. +/// @param _amount Amount of ETH to send. +/// @param _chainId Chain ID of the destination chain. +/// @return msgHash_ Hash of the message sent. +function sendETH(address _to, uint256 _amount, uint256 _chainId) external payable returns (bytes32 msgHash_) { + if (_to == address(0)) revert ZeroAddress(); + + ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)).deposit{ value: msg.value }(); + ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)).crosschainBurn(msg.sender, msg.value); + + bytes memory message = abi.encodeCall(this.relayETH, (msg.sender, _to, _amount)); + msgHash_ = IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), message); + + emit SendERC20(Predeploys.SUPERCHAIN_WETH, msg.sender, _to, _amount, _chainId); +} + +/// @notice Relays ETH received from another chain. +/// @param _from Address of the msg.sender of sendETH on the source chain. +/// @param _to Address to relay ETH to. +/// @param _amount Amount of ETH to relay. +function relayETH(address _from, address _to, uint256 _amount) external { + if (msg.sender != MESSENGER) revert Unauthorized(); + + (address crossDomainMessageSender, uint256 source) = + IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageContext(); + if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); + + ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)).crosschainMint(address(this), _amount); + ISuperchainWETH(payable(Predeploys.SUPERCHAIN_WETH)).withdraw(_amount); + + new SafeSend{ value: _amount }(payable(_to)); + + emit RelayERC20(Predeploys.SUPERCHAIN_WETH, _from, _to, _amount, source); +} +``` + +The advantage of this solution is that `SuperchainTokenBridge`would handle both `ETH` transfers and `SuperchainERC20` transfers, simplifying developer integrations. + +The downside of this approach is that it creates changes to a highly sensitive contract. `SuperchainTokenBridge` has permissions to `mint` and `burn` standard `SuperchainERC20` tokens, so updates must be treated with extreme caution. By going with the proposed solution of adding this to `SuperchainWETH`, security risks are limited to `SuperchainWETH` and do not affect other `SuperchainERC20` tokens. \ No newline at end of file