Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
72eb3116
Unverified
Commit
72eb3116
authored
Nov 26, 2024
by
tre
Committed by
GitHub
Nov 26, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(protoype): enable superchainWETH native transfers (#12710)
parent
98a1d928
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
412 additions
and
8 deletions
+412
-8
SuperchainWETH.json
packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json
+119
-0
semver-lock.json
packages/contracts-bedrock/snapshots/semver-lock.json
+2
-2
SuperchainWETH.sol
packages/contracts-bedrock/src/L2/SuperchainWETH.sol
+75
-5
ISuperchainWETH.sol
...s/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol
+8
-0
SuperchainWETH.t.sol
packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol
+208
-1
No files found.
packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json
View file @
72eb3116
...
...
@@ -143,6 +143,53 @@
"stateMutability"
:
"view"
,
"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"
:
[
{
...
...
@@ -361,6 +408,68 @@
"name"
:
"Deposit"
,
"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
,
"inputs"
:
[
...
...
@@ -405,6 +514,11 @@
"name"
:
"Withdrawal"
,
"type"
:
"event"
},
{
"inputs"
:
[],
"name"
:
"InvalidCrossDomainSender"
,
"type"
:
"error"
},
{
"inputs"
:
[],
"name"
:
"NotCustomGasToken"
,
...
...
@@ -414,5 +528,10 @@
"inputs"
:
[],
"name"
:
"Unauthorized"
,
"type"
:
"error"
},
{
"inputs"
:
[],
"name"
:
"ZeroAddress"
,
"type"
:
"error"
}
]
\ No newline at end of file
packages/contracts-bedrock/snapshots/semver-lock.json
View file @
72eb3116
...
...
@@ -128,8 +128,8 @@
"sourceCodeHash"
:
"0x617aa994f659c5d8ebd54128d994f86f5b175ceca095b024b8524a7898e8ae62"
},
"src/L2/SuperchainWETH.sol"
:
{
"initCodeHash"
:
"0x
90aad5698e09994909331dd9665d99a8d5a53e45ba792bf47e4c2efbd48f7699
"
,
"sourceCodeHash"
:
"0x
35f0ffcfa027f736b496f3fd2640c043648a49ce325083486ce27f63bfec6d08
"
"initCodeHash"
:
"0x
ac4cab1ee7f0a5de8362ddc37e1de92e816aee3fdd4e31c0ba0fd9b03568f0f0
"
,
"sourceCodeHash"
:
"0x
1fc6439f7a5fac2e264d5e057593844d252cdaa12c2b1eaa2fb08cbb911a9f35
"
},
"src/L2/WETH.sol"
:
{
"initCodeHash"
:
"0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17"
,
...
...
packages/contracts-bedrock/src/L2/SuperchainWETH.sol
View file @
72eb3116
...
...
@@ -5,16 +5,18 @@ pragma solidity 0.8.15;
import { WETH98 } from "src/universal/WETH98.sol";
// Libraries
import { NotCustomGasToken, Unauthorized, ZeroAddress } from "src/libraries/errors/CommonErrors.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { SafeSend } from "src/universal/SafeSend.sol";
// Interfaces
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 { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";
/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000024
...
...
@@ -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
/// do not use a custom gas token.
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.
/// @custom:semver 1.0.0-beta.1
1
string public constant version = "1.0.0-beta.1
1
";
/// @custom:semver 1.0.0-beta.1
2
string public constant version = "1.0.0-beta.1
2
";
/// @inheritdoc WETH98
function deposit() public payable override {
...
...
@@ -69,8 +88,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
_mint(_to, _amount);
//
Mint
from ETHLiquidity contract.
//
Withdraw
from ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
// NOTE: 'mint' will soon change to 'withdraw'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).mint(_amount);
}
...
...
@@ -85,8 +105,9 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
_burn(_from, _amount);
//
Burn
to ETHLiquidity contract.
//
Deposit
to ETHLiquidity contract.
if (!IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).isCustomGasToken()) {
// NOTE: 'burn' will soon change to 'deposit'.
IETHLiquidity(Predeploys.ETH_LIQUIDITY).burn{ value: _amount }();
}
...
...
@@ -98,4 +119,53 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver {
return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC20).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);
}
}
packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol
View file @
72eb3116
...
...
@@ -8,10 +8,18 @@ import { ISemver } from "src/universal/interfaces/ISemver.sol";
interface ISuperchainWETH is IWETH98, IERC7802, ISemver {
error Unauthorized();
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 withdraw(uint256 _amount) external;
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;
}
packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol
View file @
72eb3116
...
...
@@ -6,7 +6,7 @@ import { CommonTest } from "test/setup/CommonTest.sol";
// Libraries
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";
// Interfaces
...
...
@@ -14,6 +14,7 @@ import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ISuperchainWETH } from "src/L2/interfaces/ISuperchainWETH.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
/// @title SuperchainWETH_Test
/// @notice Contract for testing the SuperchainWETH contract.
...
...
@@ -33,6 +34,10 @@ contract SuperchainWETH_Test is CommonTest {
/// @notice Emitted when a crosschain transfer burns tokens.
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);
/// @notice Test setup.
...
...
@@ -475,4 +480,206 @@ contract SuperchainWETH_Test is CommonTest {
vm.assume(_interfaceId != type(IERC20).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);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment