Skip to content

Commit

Permalink
feat: SuperchainWETHWrapper contract
Browse files Browse the repository at this point in the history
  • Loading branch information
tremarkley committed Sep 23, 2024
1 parent 551af9f commit e62e3d1
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 17 deletions.
8 changes: 8 additions & 0 deletions contracts/script/DeployL2PeripheryContracts.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.25;

import {Script, console} from "forge-std/Script.sol";
import {L2NativeSuperchainERC20} from "../src/L2NativeSuperchainERC20.sol";
import {SuperchainETHWrapper} from "../src/SuperchainETHWrapper.sol";

contract DeployL2PeripheryContracts is Script {
/// @notice Used for tracking the next address to deploy a periphery contract at.
Expand Down Expand Up @@ -31,6 +32,7 @@ contract DeployL2PeripheryContracts is Script {

function run() public broadcast {
deployL2NativeSuperchainERC20();
deploySuperchainETHWrapper();
}

function deployL2NativeSuperchainERC20() public {
Expand All @@ -39,6 +41,12 @@ contract DeployL2PeripheryContracts is Script {
console.log("Deployed L2NativeSuperchainERC20 at address: ", deploymentAddress);
}

function deploySuperchainETHWrapper() public {
address _superchainETHWrapperContract = address(new SuperchainETHWrapper{salt: _salt()}());
address deploymentAddress = deployAtNextDeploymentAddress(_superchainETHWrapperContract.code);
console.log("Deployed SuperchainETHWrapper at address: ", deploymentAddress);
}

function deployAtNextDeploymentAddress(bytes memory newRuntimeBytecode)
internal
returns (address _deploymentAddr)
Expand Down
101 changes: 101 additions & 0 deletions contracts/src/SuperchainETHWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {Unauthorized} from "@contracts-bedrock/libraries/errors/CommonErrors.sol";
import {Predeploys} from "@contracts-bedrock/libraries/Predeploys.sol";
import {SafeCall} from "@contracts-bedrock//libraries/SafeCall.sol";
import {IL2ToL2CrossDomainMessenger} from "@contracts-bedrock/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import {ISuperchainERC20Extensions} from "@contracts-bedrock/L2/interfaces/ISuperchainERC20.sol";
import {IWETH} from "@contracts-bedrock/universal/interfaces/IWETH.sol";

interface IL2ToL2CrossDomainMessengerMissing {
function successfulMessages(bytes32 _msgHash) external view returns (bool);
function messageNonce() external view returns (uint256);
}


/**
* @notice Thrown when the relay of SuperchainWETH has not succeeded.
* @dev This error is triggered if the SuperchainWETH relay through the L2ToL2CrossDomainMessenger
* has not completed successfully successful.
*/
error RelaySuperchainWETHNotSuccessful();

/**
* @title SuperchainETHWrapper
* @notice This contract facilitates sending ETH across chains within the Superchain by wrapping ETH into SuperchainWETH,
* relaying the wrapped asset to another chain, and then unwrapping it back to ETH on the destination chain.
* @dev The contract integrates with the SuperchainWETH contract for wrapping and unwrapping ETH, and uses the L2ToL2CrossDomainMessenger
* for relaying the wrapped ETH between chains.
*/
contract SuperchainETHWrapper {
/**
* @dev Emitted when ETH is received by the contract.
* @param from The address that sent ETH.
* @param value The amount of ETH received.
*/
event LogReceived(address from, uint256 value);

// Fallback function to receive ETH
receive() external payable {
emit LogReceived(msg.sender, msg.value);
}

/**
* @notice Unwraps SuperchainWETH into native ETH and sends it to a specified destination address.
* @dev This function is called after receiving a message from another chain. It checks the message relay status, unwraps WETH to ETH,
* and calls the destination address with the unwrapped ETH and the provided calldata.
* @param _relayERC20MsgHash The hash of the relayed ERC20 message.
* @param _dst The destination address on the receiving chain.
* @param _wad The amount of SuperchainWETH to unwrap to ETH.
* @param _calldata Data to be executed on the destination address.
*/
function unwrapAndCall(bytes32 _relayERC20MsgHash, address _dst, uint256 _wad, bytes memory _calldata) external {
// Receive message from other chain.
IL2ToL2CrossDomainMessenger messenger = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
if (msg.sender != address(messenger)) revert Unauthorized();
if (messenger.crossDomainMessageSender() != address(this)) revert Unauthorized();

if (
IL2ToL2CrossDomainMessengerMissing(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).successfulMessages(
_relayERC20MsgHash
) == false
) {
revert RelaySuperchainWETHNotSuccessful();
}

IWETH(Predeploys.SUPERCHAIN_WETH).withdraw(_wad);
SafeCall.call(_dst, _wad, _calldata);
}

/**
* @notice Wraps ETH into SuperchainWETH and sends it to another chain.
* @dev This function wraps the sent ETH into SuperchainWETH, computes the relay message hash, and relays the message to the destination chain.
* @param _dst The destination address on the receiving chain.
* @param _chainId The ID of the destination chain.
* @param _calldata Data to be executed on the destination address.
*/
function sendETH(address _dst, uint256 _chainId, bytes memory _calldata) public payable {
IWETH(Predeploys.SUPERCHAIN_WETH).deposit{value: msg.value}();

bytes32 relayERC20MessageHash = keccak256(
abi.encode(
_chainId,
block.chainid,
IL2ToL2CrossDomainMessengerMissing(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).messageNonce(),
Predeploys.SUPERCHAIN_WETH,
Predeploys.SUPERCHAIN_WETH,
abi.encodeCall(
ISuperchainERC20Extensions(Predeploys.SUPERCHAIN_WETH).relayERC20,
(address(this), address(this), msg.value)
)
)
);
ISuperchainERC20Extensions(Predeploys.SUPERCHAIN_WETH).sendERC20(address(this), msg.value, _chainId);
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).sendMessage({
_destination: _chainId,
_target: address(this),
_message: abi.encodeCall(this.unwrapAndCall, (relayERC20MessageHash, _dst, msg.value, _calldata))
});
}
}
27 changes: 10 additions & 17 deletions interop/relayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"math/big"
"sync"

"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum-optimism/optimism/op-service/tasks"
Expand Down Expand Up @@ -57,31 +56,30 @@ func (r *L2ToL2MessageRelayer) Start(indexer *L2ToL2MessageIndexer, clients map[

for destinationChainID, client := range r.clients {
r.tasks.Go(func() error {
var mu sync.Mutex
sentMessageCh := make(chan *L2ToL2MessageStoreEntry)
unsubscribe, err := r.l2ToL2MessageIndexer.SubscribeSentMessageToDestination(destinationChainID, sentMessageCh)

if err != nil {
return fmt.Errorf("failed to subscribe to sent message events: %w", err)
}

transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(destinationChainID)))
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

crossL2Inbox, err := bindings.NewCrossL2InboxTransactor(predeploys.CrossL2InboxAddr, client)
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

for {
select {
case <-r.tasksCtx.Done():
unsubscribe()
close(sentMessageCh)
return nil
case sentMessage := <-sentMessageCh:
transactor, err := bind.NewKeyedTransactorWithChainID(privateKey, big.NewInt(int64(destinationChainID)))
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

crossL2Inbox, err := bindings.NewCrossL2InboxTransactor(predeploys.CrossL2InboxAddr, client)
if err != nil {
return fmt.Errorf("failed to create transactor: %w", err)
}

identifier := sentMessage.Identifier()
msg := sentMessage.Message()
calldata, err := msg.EventData()
Expand All @@ -105,15 +103,10 @@ func (r *L2ToL2MessageRelayer) Start(indexer *L2ToL2MessageIndexer, clients map[
}
// Pad gas by 33%.
paddedGas := (gasEstimate / 3) + gasEstimate
mu.Lock()
defer mu.Unlock()
originalTransactorGasLimit := transactor.GasLimit
transactor.GasLimit = paddedGas

if _, err := crossL2Inbox.ExecuteMessage(transactor, *identifier, predeploys.L2toL2CrossDomainMessengerAddr, calldata); err != nil {
return fmt.Errorf("failed to execute message: %w", err)
}
transactor.GasLimit = originalTransactorGasLimit
}
}

Expand Down

0 comments on commit e62e3d1

Please sign in to comment.