Skip to content
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

feat: introduce OptimismSuperchainERC20 #9

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@
"sourceCodeHash": "0x1f14aafab2cb15970cccedb461b72218fca8afa6ffd0ac696a9e28ff1415a068"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0xd7418787c42d9bfdc85f7810deb0f65a014edace04a04f81af218940f0c32a54",
"sourceCodeHash": "0xfd07d6685da601129bd854f79e91363baf9d93d28868add9ca7a0698111158a1"
"initCodeHash": "0x9b057e90bc62b4cd175c685e5317f70cbcb2448258466dc36c563203aceb7fd4",
"sourceCodeHash": "0x0b91f534e4ba21e947fe3f62a5399ae9aa9deb1ea9cfd77a20c7fc95e1c739cd"
},
"src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0xb94145f571e92ee615c6fe903b6568e8aac5fe760b6b65148ffc45d2fb0f5433",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,11 @@
},
{
"inputs": [
{
"internalType": "address",
"name": "_from",
"type": "address"
},
{
"internalType": "address",
"name": "_to",
Expand Down Expand Up @@ -460,6 +465,12 @@
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
Expand All @@ -471,6 +482,12 @@
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "source",
"type": "uint256"
}
],
"name": "RelayedERC20",
Expand Down Expand Up @@ -500,7 +517,7 @@
{
"indexed": false,
"internalType": "uint256",
"name": "chainId",
"name": "destination",
"type": "uint256"
}
],
Expand Down
25 changes: 14 additions & 11 deletions packages/contracts-bedrock/src/L2/IOptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ interface IOptimismSuperchainERC20 {
event Burn(address indexed account, uint256 amount);

/// @notice Emitted whenever tokens are sent to another chain.
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Amount of tokens sent.
/// @param chainId Chain ID of the destination chain.
event SentERC20(address indexed from, address indexed to, uint256 amount, uint256 chainId);
/// @param from Address of the sender.
/// @param to Address of the recipient.
/// @param amount Amount of tokens sent.
/// @param destination Chain ID of the destination chain.
event SentERC20(address indexed from, address indexed to, uint256 amount, uint256 destination);

/// @notice Emitted whenever tokens are successfully relayed on this chain.
/// @param to Address of the recipient.
/// @param amount Amount of tokens relayed.
event RelayedERC20(address indexed to, uint256 amount);
/// @param from Address of the sender.
0xng marked this conversation as resolved.
Show resolved Hide resolved
/// @param to Address of the recipient.
/// @param amount Amount of tokens relayed.
/// @param source Chain ID of the source chain.
event RelayedERC20(address indexed from, address indexed to, uint256 amount, uint256 source);

/// @notice Allows the L2StandardBridge to mint tokens.
/// @param _to Address to mint tokens to.
Expand All @@ -45,7 +47,8 @@ interface IOptimismSuperchainERC20 {
function sendERC20(address _to, uint256 _amount, uint256 _chainId) external;

/// @notice Relays tokens received from another chain.
/// @param _to Address to relay tokens to.
/// @param _amount Amount of tokens to relay.
function relayERC20(address _to, uint256 _amount) external;
/// @param _from Address of the sender.
/// @param _to Address to relay tokens to.
/// @param _amount Amount of tokens to relay.
function relayERC20(address _from, address _to, uint256 _amount) external;
}
8 changes: 5 additions & 3 deletions packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,27 +100,29 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20, ERC20, ISemver {

_burn(msg.sender, _amount);

bytes memory _message = abi.encodeCall(this.relayERC20, (_to, _amount));
bytes memory _message = abi.encodeCall(this.relayERC20, (msg.sender, _to, _amount));
IL2ToL2CrossDomainMessenger(MESSENGER).sendMessage(_chainId, address(this), _message);

emit SentERC20(msg.sender, _to, _amount, _chainId);
}

/// @notice Relays tokens received from another chain.
/// @param _from Address of the sender.
/// @param _to Address to relay tokens to.
/// @param _amount Amount of tokens to relay.
function relayERC20(address _to, uint256 _amount) external {
function relayERC20(address _from, address _to, uint256 _amount) external {
if (_to == address(0)) revert ZeroAddress();

if (msg.sender != MESSENGER) revert CallerNotL2ToL2CrossDomainMessenger();

if (IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSender() != address(this)) {
revert InvalidCrossDomainSender();
}
uint256 _source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();

_mint(_to, _amount);

emit RelayedERC20(_to, _amount);
emit RelayedERC20(_from, _to, _amount, _source);
}

/// @notice Returns the number of decimals used to get its user representation.
Expand Down
22 changes: 15 additions & 7 deletions packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ contract OptimismSuperchainERC20Test is Test {
emit IOptimismSuperchainERC20.SentERC20(_sender, _to, _amount, _chainId);

// Mock the call over the `sendMessage` function and expect it to be called properly
bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_to, _amount));
bytes memory _message = abi.encodeCall(superchainERC20.relayERC20, (_sender, _to, _amount));
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(
Expand Down Expand Up @@ -218,7 +218,7 @@ contract OptimismSuperchainERC20Test is Test {

// Call the `relayERC20` function with the non-messenger caller
vm.prank(_caller);
superchainERC20.relayERC20(_to, _amount);
superchainERC20.relayERC20(_caller, _to, _amount);
}

/// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not
Expand All @@ -245,7 +245,7 @@ contract OptimismSuperchainERC20Test is Test {

// Call the `relayERC20` function with the sender caller
vm.prank(MESSENGER);
superchainERC20.relayERC20(_to, _amount);
superchainERC20.relayERC20(_crossDomainMessageSender, _to, _amount);
}

/// @notice Tests the `relayERC20` function reverts when the `_to` address is the zero address.
Expand All @@ -262,11 +262,12 @@ contract OptimismSuperchainERC20Test is Test {

// Call the `relayERC20` function with the zero address
vm.prank(MESSENGER);
superchainERC20.relayERC20({ _to: ZERO_ADDRESS, _amount: _amount });
superchainERC20.relayERC20({ _from: ZERO_ADDRESS, _to: ZERO_ADDRESS, _amount: _amount });
}

/// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayedERC20` event.
function testFuzz_relayERC20_succeeds(address _to, uint256 _amount) public {
function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public {
vm.assume(_from != ZERO_ADDRESS);
vm.assume(_to != ZERO_ADDRESS);

// Mock the call over the `crossDomainMessageSender` function setting the same address as value
Expand All @@ -276,6 +277,13 @@ contract OptimismSuperchainERC20Test is Test {
abi.encode(address(superchainERC20))
);

// Mock the call over the `crossDomainMessageSource` function setting the same address as value
_mockAndExpect(
MESSENGER,
abi.encodeWithSelector(IL2ToL2CrossDomainMessenger.crossDomainMessageSource.selector),
abi.encode(_source)
);

// Get the total supply and balance of `_to` before the relay to compare later on the assertions
uint256 _totalSupplyBefore = superchainERC20.totalSupply();
uint256 _toBalanceBefore = superchainERC20.balanceOf(_to);
Expand All @@ -286,11 +294,11 @@ contract OptimismSuperchainERC20Test is Test {

// Look for the emit of the `RelayedERC20` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
emit IOptimismSuperchainERC20.RelayedERC20(_to, _amount);
emit IOptimismSuperchainERC20.RelayedERC20(_from, _to, _amount, _source);

// Call the `relayERC20` function with the messenger caller
vm.prank(MESSENGER);
superchainERC20.relayERC20(_to, _amount);
superchainERC20.relayERC20(_from, _to, _amount);

// Check the total supply and balance of `_to` after the relay were updated correctly
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore + _amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract OptimismSuperchainERC20_User is StdUtils {
}

/// @notice Relay a message from another chain.
function relayMessage() public {
function relayMessage(uint256 _source) public {
// Make sure there are unrelayed messages.
if (unrelayed.length == 0) {
return;
Expand All @@ -89,10 +89,17 @@ contract OptimismSuperchainERC20_User is StdUtils {
abi.encode(address(superchainERC20))
);

// Simulate the cross-domain message source to any chain.
vm.mockCall(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageSource, ()),
abi.encode(_source)
);

// Prank the relayERC20 function.
// Balance will just go back to our own account.
vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
try superchainERC20.relayERC20(address(this), message.amount) {
try superchainERC20.relayERC20(address(this), address(this), message.amount) {
// Success.
} catch {
failed = true;
Expand Down