Commit 72eb3116 authored by tre's avatar tre Committed by GitHub

feat(protoype): enable superchainWETH native transfers (#12710)

parent 98a1d928
...@@ -143,6 +143,53 @@ ...@@ -143,6 +143,53 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "_from",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_amount",
"type": "uint256"
}
],
"name": "relayETH",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_chainId",
"type": "uint256"
}
],
"name": "sendETH",
"outputs": [
{
"internalType": "bytes32",
"name": "msgHash_",
"type": "bytes32"
}
],
"stateMutability": "payable",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -361,6 +408,68 @@ ...@@ -361,6 +408,68 @@
"name": "Deposit", "name": "Deposit",
"type": "event" "type": "event"
}, },
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "source",
"type": "uint256"
}
],
"name": "RelayETH",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "destination",
"type": "uint256"
}
],
"name": "SendETH",
"type": "event"
},
{ {
"anonymous": false, "anonymous": false,
"inputs": [ "inputs": [
...@@ -405,6 +514,11 @@ ...@@ -405,6 +514,11 @@
"name": "Withdrawal", "name": "Withdrawal",
"type": "event" "type": "event"
}, },
{
"inputs": [],
"name": "InvalidCrossDomainSender",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "NotCustomGasToken", "name": "NotCustomGasToken",
...@@ -414,5 +528,10 @@ ...@@ -414,5 +528,10 @@
"inputs": [], "inputs": [],
"name": "Unauthorized", "name": "Unauthorized",
"type": "error" "type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
} }
] ]
\ No newline at end of file
...@@ -128,8 +128,8 @@ ...@@ -128,8 +128,8 @@
"sourceCodeHash": "0x617aa994f659c5d8ebd54128d994f86f5b175ceca095b024b8524a7898e8ae62" "sourceCodeHash": "0x617aa994f659c5d8ebd54128d994f86f5b175ceca095b024b8524a7898e8ae62"
}, },
"src/L2/SuperchainWETH.sol": { "src/L2/SuperchainWETH.sol": {
"initCodeHash": "0x90aad5698e09994909331dd9665d99a8d5a53e45ba792bf47e4c2efbd48f7699", "initCodeHash": "0xac4cab1ee7f0a5de8362ddc37e1de92e816aee3fdd4e31c0ba0fd9b03568f0f0",
"sourceCodeHash": "0x35f0ffcfa027f736b496f3fd2640c043648a49ce325083486ce27f63bfec6d08" "sourceCodeHash": "0x1fc6439f7a5fac2e264d5e057593844d252cdaa12c2b1eaa2fb08cbb911a9f35"
}, },
"src/L2/WETH.sol": { "src/L2/WETH.sol": {
"initCodeHash": "0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17", "initCodeHash": "0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17",
......
...@@ -5,16 +5,18 @@ pragma solidity 0.8.15; ...@@ -5,16 +5,18 @@ pragma solidity 0.8.15;
import { WETH98 } from "src/universal/WETH98.sol"; import { WETH98 } from "src/universal/WETH98.sol";
// Libraries // Libraries
import { NotCustomGasToken, Unauthorized, ZeroAddress } from "src/libraries/errors/CommonErrors.sol";
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { SafeSend } from "src/universal/SafeSend.sol";
// Interfaces // Interfaces
import { ISemver } from "src/universal/interfaces/ISemver.sol"; import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { IL1Block } from "src/L2/interfaces/IL1Block.sol"; import { IL1Block } from "src/L2/interfaces/IL1Block.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol"; import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
/// @custom:proxied true /// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000024 /// @custom:predeploy 0x4200000000000000000000000000000000000024
...@@ -23,9 +25,26 @@ import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErro ...@@ -23,9 +25,26 @@ import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErro
/// within the superchain. SuperchainWETH can be converted into native ETH on chains that /// within the superchain. SuperchainWETH can be converted into native ETH on chains that
/// do not use a custom gas token. /// do not use a custom gas token.
contract SuperchainWETH is WETH98, IERC7802, ISemver { contract SuperchainWETH is WETH98, IERC7802, ISemver {
/// @notice Thrown when attempting to relay a message and the cross domain message sender is not SuperchainWETH.
error InvalidCrossDomainSender();
/// @notice Emitted when ETH is sent from one chain to another.
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Amount of ETH sent.
/// @param destination Chain ID of the destination chain.
event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination);
/// @notice Emitted whenever ETH is successfully relayed on this chain.
/// @param from Address of the msg.sender of sendETH on the source chain.
/// @param to Address of the recipient.
/// @param amount Amount of ETH relayed.
/// @param source Chain ID of the source chain.
event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source);
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.11 /// @custom:semver 1.0.0-beta.12
string public constant version = "1.0.0-beta.11"; string public constant version = "1.0.0-beta.12";
/// @inheritdoc WETH98 /// @inheritdoc WETH98
function deposit() public payable override { function deposit() public payable override {
...@@ -69,8 +88,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver { ...@@ -69,8 +88,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
_mint(_to, _amount); _mint(_to, _amount);
// Mint from ETHLiquidity contract. // Withdraw from ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
// NOTE: 'mint' will soon change to 'withdraw'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount); IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount);
} }
...@@ -85,8 +105,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver { ...@@ -85,8 +105,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
_burn(_from, _amount); _burn(_from, _amount);
// Burn to ETHLiquidity contract. // Deposit to ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) { if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
// NOTE: 'burn' will soon change to 'deposit'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: _amount }(); IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: _amount }();
} }
...@@ -98,4 +119,53 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver { ...@@ -98,4 +119,53 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).interfaceId
|| _interfaceId == type(IERC165).interfaceId; || _interfaceId == type(IERC165).interfaceId;
} }
/// @notice Sends ETH to some target address on another chain.
/// @param _to Address to send ETH to.
/// @param _chainId Chain ID of the destination chain.
/// @return msgHash_ Hash of the message sent.
function sendETH(address _to, uint256 _chainId) external payable returns (bytes32 msgHash_) {
if (_to == address(0)) revert ZeroAddress();
if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
revert NotCustomGasToken();
}
// NOTE: 'burn' will soon change to 'deposit'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: msg.value }();
msgHash_ = 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 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 ETH to.
/// @param _amount Amount of ETH to relay.
function relayETH(address _from, address _to, uint256 _amount) external {
if (msg.sender != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert Unauthorized();
(address crossDomainMessageSender, uint256 source) =
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageContext();
if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender();
if (IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
// Since ETH is not the native asset on custom gas token chains, send SuperchainWETH to the recipient.
_mint(_to, _amount);
} else {
// NOTE: 'mint' will soon change to 'withdraw'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount);
// This is a forced ETH send to the recipient, the recipient should NOT expect to be called.
new SafeSend{ value: _amount }(payable(_to));
}
emit RelayETH(_from, _to, _amount, source);
}
} }
...@@ -8,10 +8,18 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol"; ...@@ -8,10 +8,18 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol";
interface ISuperchainWETH is IWETH98, IERC7802, ISemver { interface ISuperchainWETH is IWETH98, IERC7802, ISemver {
error Unauthorized(); error Unauthorized();
error NotCustomGasToken(); error NotCustomGasToken();
error InvalidCrossDomainSender();
error ZeroAddress();
event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination);
event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source);
function balanceOf(address src) external view returns (uint256); function balanceOf(address src) external view returns (uint256);
function withdraw(uint256 _amount) external; function withdraw(uint256 _amount) external;
function supportsInterface(bytes4 _interfaceId) external view returns (bool); function supportsInterface(bytes4 _interfaceId) external view returns (bool);
function sendETH(address _to, uint256 _chainId) external payable returns (bytes32 msgHash_);
function relayETH(address _from, address _to, uint256 _amount) external;
function __constructor__() external; function __constructor__() external;
} }
...@@ -6,7 +6,7 @@ import { CommonTest } from "test/setup/CommonTest.sol"; ...@@ -6,7 +6,7 @@ import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries // Libraries
import { Predeploys } from "src/libraries/Predeploys.sol"; import { Predeploys } from "src/libraries/Predeploys.sol";
import { NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; import { NotCustomGasToken, Unauthorized, ZeroAddress } from "src/libraries/errors/CommonErrors.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol"; import { Preinstalls } from "src/libraries/Preinstalls.sol";
// Interfaces // Interfaces
...@@ -14,6 +14,7 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol"; ...@@ -14,6 +14,7 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol"; import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol"; import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
/// @title SuperchainWETH_Test /// @title SuperchainWETH_Test
/// @notice Contract for testing the SuperchainWETH contract. /// @notice Contract for testing the SuperchainWETH contract.
...@@ -33,6 +34,10 @@ contract SuperchainWETH_Test is CommonTest { ...@@ -33,6 +34,10 @@ contract SuperchainWETH_Test is CommonTest {
/// @notice Emitted when a crosschain transfer burns tokens. /// @notice Emitted when a crosschain transfer burns tokens.
event CrosschainBurn(address indexed from, uint256 amount, address indexed sender); event CrosschainBurn(address indexed from, uint256 amount, address indexed sender);
event SendETH(address indexed from, address indexed to, uint256 amount, uint256 destination);
event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source);
address internal constant ZERO_ADDRESS = address(0); address internal constant ZERO_ADDRESS = address(0);
/// @notice Test setup. /// @notice Test setup.
...@@ -475,4 +480,206 @@ contract SuperchainWETH_Test is CommonTest { ...@@ -475,4 +480,206 @@ contract SuperchainWETH_Test is CommonTest {
vm.assume(_interfaceId != type(IERC20).interfaceId); vm.assume(_interfaceId != type(IERC20).interfaceId);
assertFalse(superchainWeth.supportsInterface(_interfaceId)); assertFalse(superchainWeth.supportsInterface(_interfaceId));
} }
/// @notice Tests the `sendETH` function reverts when the address `_to` is zero.
function testFuzz_sendETH_zeroAddressTo_reverts(address _sender, uint256 _amount, uint256 _chainId) public {
// Expect the revert with `ZeroAddress` selector
vm.expectRevert(ZeroAddress.selector);
vm.deal(_sender, _amount);
vm.prank(_sender);
// Call the `sendETH` function with the zero address as `_to`
superchainWeth.sendETH{ value: _amount }(ZERO_ADDRESS, _chainId);
}
/// @notice Tests the `sendETH` function burns the sender ETH, sends the message, and emits the `SendETH`
/// event.
function testFuzz_sendETH_fromNonCustomGasTokenChain_succeeds(
address _sender,
address _to,
uint256 _amount,
uint256 _chainId,
bytes32 _msgHash
)
external
{
// Assume
vm.assume(_sender != ZERO_ADDRESS);
vm.assume(_to != ZERO_ADDRESS);
_amount = bound(_amount, 0, type(uint248).max - 1);
// Arrange
vm.deal(_sender, _amount);
_mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false));
// Get the total balance of `_sender` before the send to compare later on the assertions
uint256 _senderBalanceBefore = _sender.balance;
// Look for the emit of the `SendETH` event
vm.expectEmit(address(superchainWeth));
emit SendETH(_sender, _to, _amount, _chainId);
// Expect the call to the `burn` function in the `ETHLiquidity` contract
vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.burn, ()), 1);
// Mock the call over the `sendMessage` function and expect it to be called properly
bytes memory _message = abi.encodeCall(superchainWeth.relayETH, (_sender, _to, _amount));
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeCall(IL2ToL2CrossDomainMessenger.sendMessage, (_chainId, address(superchainWeth), _message)),
abi.encode(_msgHash)
);
// Call the `sendETH` function
vm.prank(_sender);
bytes32 _returnedMsgHash = superchainWeth.sendETH{ value: _amount }(_to, _chainId);
// Check the message hash was generated correctly
assertEq(_msgHash, _returnedMsgHash);
// Check the total supply and balance of `_sender` after the send were updated correctly
assertEq(_sender.balance, _senderBalanceBefore - _amount);
}
/// @notice Tests the `sendETH` function reverts when called on a custom gas token chain.
function testFuzz_sendETH_fromCustomGasTokenChain_fails(
address _sender,
address _to,
uint256 _amount,
uint256 _chainId
)
external
{
// Assume
vm.assume(_sender != ZERO_ADDRESS);
vm.assume(_to != ZERO_ADDRESS);
_amount = bound(_amount, 0, type(uint248).max - 1);
// Arrange
vm.deal(_sender, _amount);
_mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true));
// Call the `sendETH` function
vm.prank(_sender);
vm.expectRevert(NotCustomGasToken.selector);
superchainWeth.sendETH{ value: _amount }(_to, _chainId);
}
/// @notice Tests the `relayETH` function reverts when the caller is not the L2ToL2CrossDomainMessenger.
function testFuzz_relayETH_notMessenger_reverts(address _caller, address _to, uint256 _amount) public {
// Ensure the caller is not the messenger
vm.assume(_caller != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
// Expect the revert with `Unauthorized` selector
vm.expectRevert(Unauthorized.selector);
// Call the `relayETH` function with the non-messenger caller
vm.prank(_caller);
superchainWeth.relayETH(_caller, _to, _amount);
}
/// @notice Tests the `relayETH` function reverts when the `crossDomainMessageSender` that sent the message is not
/// the same SuperchainWETH.
function testFuzz_relayETH_notCrossDomainSender_reverts(
address _crossDomainMessageSender,
uint256 _source,
address _to,
uint256 _amount
)
public
{
vm.assume(_crossDomainMessageSender != address(superchainWeth));
// Mock the call over the `crossDomainMessageContext` function setting a wrong sender
vm.mockCall(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()),
abi.encode(_crossDomainMessageSender, _source)
);
// Expect the revert with `InvalidCrossDomainSender` selector
vm.expectRevert(ISuperchainWETH.InvalidCrossDomainSender.selector);
// Call the `relayETH` function with the sender caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainWeth.relayETH(_crossDomainMessageSender, _to, _amount);
}
/// @notice Tests the `relayETH` function succeeds and sends SuperchainWETH to the recipient on a custom gas token
/// chain.
function testFuzz_relayETH_fromCustomGasTokenChain_succeeds(
address _from,
address _to,
uint256 _amount,
uint256 _source
)
public
{
// Assume
vm.assume(_to != ZERO_ADDRESS);
_amount = bound(_amount, 0, type(uint248).max - 1);
// Get the balance of `_to` before the mint to compare later on the assertions
uint256 _toBalanceBefore = superchainWeth.balanceOf(_to);
// Look for the emit of the `Transfer` event
vm.expectEmit(address(superchainWeth));
emit Transfer(ZERO_ADDRESS, _to, _amount);
// Look for the emit of the `RelayETH` event
vm.expectEmit(address(superchainWeth));
emit RelayETH(_from, _to, _amount, _source);
// Arrange
_mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(true));
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()),
abi.encode(address(superchainWeth), _source)
);
// Expect to not call the `mint` function in the `ETHLiquidity` contract
vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 0);
// Act
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainWeth.relayETH(_from, _to, _amount);
// Check the total supply and balance of `_to` after the mint were updated correctly
assertEq(superchainWeth.balanceOf(_to), _toBalanceBefore + _amount);
assertEq(superchainWeth.totalSupply(), 0);
assertEq(address(superchainWeth).balance, 0);
}
/// @notice Tests the `relayETH` function relays the proper amount of ETH and emits the `RelayETH` event.
function testFuzz_relayETH_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public {
// Assume
vm.assume(_to != ZERO_ADDRESS);
assumePayable(_to);
_amount = bound(_amount, 0, type(uint248).max - 1);
// Arrange
vm.deal(address(superchainWeth), _amount);
vm.deal(Predeploys.ETH_LIQUIDITY, _amount);
_mockAndExpect(address(l1Block), abi.encodeCall(l1Block.isCustomGasToken, ()), abi.encode(false));
_mockAndExpect(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()),
abi.encode(address(superchainWeth), _source)
);
uint256 _toBalanceBefore = _to.balance;
// Look for the emit of the `RelayETH` event
vm.expectEmit(address(superchainWeth));
emit RelayETH(_from, _to, _amount, _source);
// Expect the call to the `mint` function in the `ETHLiquidity` contract
vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 1);
// Call the `RelayETH` function with the messenger caller
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
superchainWeth.relayETH(_from, _to, _amount);
assertEq(_to.balance, _toBalanceBefore + _amount);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment