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

Fix/baseline conflicts #52

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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 @@ -116,8 +116,8 @@
"sourceCodeHash": "0x4b806cc85cead74c8df34ab08f4b6c6a95a1a387a335ec8a7cb2de4ea4e1cf41"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0xdd16dbc0ccbbac53ec2d4273f06334960a907a9f20b7c40300833227ee31d0de",
"sourceCodeHash": "0x910d43a17800df64dbc104f69ef1f900ca761cec4949c01d1c1126fde5268349"
"initCodeHash": "0x5158735abf860249f9449ab8e1cb60f4183c701d08ff9a9a3066575d5e26617a",
"sourceCodeHash": "0x9cc730bedbd1adc6422fc186910d4f47d124701a7a17fa33cf2ad3626ce2e684"
},
"src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0x2e6551705e493bacba8cffe22e564d5c401ae5bb02577a5424e0d32784e13e74",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,11 @@
"name": "TotalSupplyOverflow",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
Comment on lines +632 to +636

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines are repeated below, were them autogenerated? Are we sure we need them or could be removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is autogenerated, but if this is repeated then we may have the error in both the SuperchainERC20 and the OptimismSuperchainERC20, will check and remove accordingly

{
"inputs": [],
"name": "ZeroAddress",
Expand Down
92 changes: 13 additions & 79 deletions packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,10 @@ import { ERC20 } from "@solady/tokens/ERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol";
import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol";

/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
/// L2ToL2CrossDomainMessenger.
error CallerNotL2ToL2CrossDomainMessenger();

/// @notice Thrown when attempting to relay a message and the cross domain message sender is not this
/// OptimismSuperchainERC20.
error InvalidCrossDomainSender();

/// @notice Thrown when attempting to mint or burn tokens and the function caller is not the StandardBridge.
error OnlyBridge();

Expand All @@ -31,10 +24,13 @@ error ZeroAddress();
/// token, turning it fungible and interoperable across the superchain. Likewise, it also enables the inverse
/// conversion path.
/// Moreover, it builds on top of the L2ToL2CrossDomainMessenger for both replay protection and domain binding.
contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, ISemver, Initializable, ERC165 {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy.
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

contract OptimismSuperchainERC20 is
IOptimismSuperchainERC20Extension,
SuperchainERC20,
ISemver,
Initializable,
ERC165
{
/// @notice Address of the StandardBridge Predeploy.
address internal constant BRIDGE = Predeploys.L2_STANDARD_BRIDGE;

Expand All @@ -48,16 +44,10 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
struct OptimismSuperchainERC20Metadata {
/// @notice Address of the corresponding version of this token on the remote chain.
address remoteToken;
Comment on lines 44 to 46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just have a storage slot for the address instead of defining a struct for that contains only 1 field?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that can be done at well, i actually like it better, good suggestion

/// @notice Name of the token
string name;
/// @notice Symbol of the token
string symbol;
/// @notice Decimals of the token
uint8 decimals;
}

/// @notice Returns the storage for the OptimismSuperchainERC20Metadata.
function _getMetadataStorage() private pure returns (OptimismSuperchainERC20Metadata storage _storage) {
function _getStorage() private pure returns (OptimismSuperchainERC20Metadata storage _storage) {
assembly {
_storage.slot := OPTIMISM_SUPERCHAIN_ERC20_METADATA_SLOT
}
Expand Down Expand Up @@ -92,11 +82,10 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
external
initializer
{
OptimismSuperchainERC20Metadata storage _storage = _getMetadataStorage();
_setMetadataStorage(_name, _symbol, _decimals);

OptimismSuperchainERC20Metadata storage _storage = _getStorage();
_storage.remoteToken = _remoteToken;
_storage.name = _name;
_storage.symbol = _symbol;
_storage.decimals = _decimals;
}

/// @notice Allows the L2StandardBridge to mint tokens.
Expand All @@ -121,64 +110,9 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
emit Burn(_from, _amount);
}

/// @notice Sends tokens to some target address on another chain.
/// @param _to Address to send tokens to.
/// @param _amount Amount of tokens to send.
/// @param _chainId Chain ID of the destination chain.
function sendERC20(address _to, uint256 _amount, uint256 _chainId) external {
if (_to == address(0)) revert ZeroAddress();

_burn(msg.sender, _amount);

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

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

/// @notice Relays tokens received from another chain.
/// @param _from Address of the msg.sender of sendERC20 on the source chain.
/// @param _to Address to relay tokens to.
/// @param _amount Amount of tokens to relay.
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 RelayERC20(_from, _to, _amount, source);
}

/// @notice Returns the address of the corresponding version of this token on the remote chain.
function remoteToken() public view override returns (address) {
return _getMetadataStorage().remoteToken;
}

/// @notice Returns the name of the token.
function name() public view virtual override returns (string memory) {
return _getMetadataStorage().name;
}

/// @notice Returns the symbol of the token.
function symbol() public view virtual override returns (string memory) {
return _getMetadataStorage().symbol;
}

/// @notice Returns the number of decimals used to get its user representation.
/// For example, if `decimals` equals `2`, a balance of `505` tokens should
/// be displayed to a user as `5.05` (`505 / 10 ** 2`).
/// NOTE: This information is only used for _display_ purposes: it in
/// no way affects any of the arithmetic of the contract, including
/// {IERC20-balanceOf} and {IERC20-transfer}.
function decimals() public view override returns (uint8) {
return _getMetadataStorage().decimals;
return _getStorage().remoteToken;
}

/// @notice ERC165 interface check function.
Expand Down
115 changes: 115 additions & 0 deletions packages/contracts-bedrock/src/L2/SuperchainERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol";
import { ERC20 } from "@solady/tokens/ERC20.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";

/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not
/// L2ToL2CrossDomainMessenger.
error CallerNotL2ToL2CrossDomainMessenger();

/// @notice Thrown when attempting to relay a message and the cross domain message sender is not this SuperchainERC20.
error InvalidCrossDomainSender();

/// @notice Thrown when attempting to mint or burn tokens and the account is the zero address.
error ZeroAddress();

/// @title SuperchainERC20
/// @notice SuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token
/// bridging to make it fungible across the Superchain. It builds on top of the L2ToL2CrossDomainMessenger for
/// both replay protection and domain binding.
abstract contract SuperchainERC20 is ISuperchainERC20Extensions, ERC20 {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy.
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

/// @notice Storage slot that the SuperchainERC20Metadata struct is stored at.
/// keccak256(abi.encode(uint256(keccak256("superchainERC20.metadata")) - 1)) & ~bytes32(uint256(0xff));
bytes32 internal constant SUPERCHAIN_ERC20_METADATA_SLOT =
0xd17d6ca6a839692cc315581e57453e7dbbeba09485cfb8c48daa1d1181778600;

/// @notice Storage struct for the SuperchainERC20 metadata.
/// @custom:storage-location erc7201:superchainERC20.metadata
struct SuperchainERC20Metadata {
/// @notice Name of the token
string name;
/// @notice Symbol of the token
string symbol;
/// @notice Decimals of the token
uint8 decimals;
}

/// @notice Returns the storage for the SuperchainERC20Metadata.
function _getMetadataStorage() private pure returns (SuperchainERC20Metadata storage _storage) {
assembly {
_storage.slot := SUPERCHAIN_ERC20_METADATA_SLOT
}
}

/// @notice Sets the storage for the SuperchainERC20Metadata.
/// @param _name Name of the token.
/// @param _symbol Symbol of the token.
/// @param _decimals Decimals of the token.
function _setMetadataStorage(string memory _name, string memory _symbol, uint8 _decimals) internal {
SuperchainERC20Metadata storage _storage = _getMetadataStorage();
_storage.name = _name;
_storage.symbol = _symbol;
_storage.decimals = _decimals;
}

/// @notice Sends tokens to some target address on another chain.
/// @param _to Address to send tokens to.
/// @param _amount Amount of tokens to send.
/// @param _chainId Chain ID of the destination chain.
function sendERC20(address _to, uint256 _amount, uint256 _chainId) external virtual {
if (_to == address(0)) revert ZeroAddress();

_burn(msg.sender, _amount);

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

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this check redundant? the _to == address(0) is already checked on sendERC20. Since only messages initiated in sendERC20 can bypass the following checks, this might be removed, even if gas-wise its very cheap.


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

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

uint256 source = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageSource();

_mint(_to, _amount);

emit RelayERC20(_from, _to, _amount, source);
}

/// @notice Returns the name of the token.
function name() public view virtual override returns (string memory) {
return _getMetadataStorage().name;
}

/// @notice Returns the symbol of the token.
function symbol() public view virtual override returns (string memory) {
return _getMetadataStorage().symbol;
}

/// @notice Returns the number of decimals used to get its user representation.
/// For example, if `decimals` equals `2`, a balance of `505` tokens should
/// be displayed to a user as `5.05` (`505 / 10 ** 2`).
/// NOTE: This information is only used for _display_ purposes: it in
/// no way affects any of the arithmetic of the contract, including
/// {IERC20-balanceOf} and {IERC20-transfer}.
function decimals() public view virtual override returns (uint8) {
return _getMetadataStorage().decimals;
}
}
28 changes: 16 additions & 12 deletions packages/contracts-bedrock/test/L2/OptimismSuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ import { IERC165 } from "@openzeppelin/contracts-v5/utils/introspection/IERC165.

// Target contract
import {
OptimismSuperchainERC20,
IOptimismSuperchainERC20Extension,
OptimismSuperchainERC20, IOptimismSuperchainERC20Extension, OnlyBridge
} from "src/L2/OptimismSuperchainERC20.sol";

import {
CallerNotL2ToL2CrossDomainMessenger,
InvalidCrossDomainSender,
OnlyBridge,
SuperchainERC20,
ISuperchainERC20Extensions,
ZeroAddress
} from "src/L2/OptimismSuperchainERC20.sol";
} from "src/L2/SuperchainERC20.sol";

import { ISuperchainERC20Extensions } from "src/L2/interfaces/ISuperchainERC20.sol";

/// @title OptimismSuperchainERC20Test
Expand Down Expand Up @@ -127,11 +131,11 @@ contract OptimismSuperchainERC20Test is Test {
uint256 _toBalanceBefore = superchainERC20.balanceOf(_to);

// Look for the emit of the `Transfer` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the true, true, true, true doesn't alter the way the cheatcode checks the emitted args are done properly and in order?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's the same as if they were there, it's shorthand way of writing it

emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount);

// Look for the emit of the `Mint` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit IOptimismSuperchainERC20Extension.Mint(_to, _amount);

// Call the `mint` function with the bridge caller
Expand Down Expand Up @@ -180,11 +184,11 @@ contract OptimismSuperchainERC20Test is Test {
uint256 _fromBalanceBefore = superchainERC20.balanceOf(_from);

// Look for the emit of the `Transfer` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(_from, ZERO_ADDRESS, _amount);

// Look for the emit of the `Burn` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit IOptimismSuperchainERC20Extension.Burn(_from, _amount);

// Call the `burn` function with the bridge caller
Expand Down Expand Up @@ -222,11 +226,11 @@ contract OptimismSuperchainERC20Test is Test {
uint256 _senderBalanceBefore = superchainERC20.balanceOf(_sender);

// Look for the emit of the `Transfer` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(_sender, ZERO_ADDRESS, _amount);

// Look for the emit of the `SendERC20` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit ISuperchainERC20Extensions.SendERC20(_sender, _to, _amount, _chainId);

// Mock the call over the `sendMessage` function and expect it to be called properly
Expand Down Expand Up @@ -330,11 +334,11 @@ contract OptimismSuperchainERC20Test is Test {
uint256 _toBalanceBefore = superchainERC20.balanceOf(_to);

// Look for the emit of the `Transfer` event
vm.expectEmit(true, true, true, true, address(superchainERC20));
vm.expectEmit(address(superchainERC20));
emit IERC20.Transfer(ZERO_ADDRESS, _to, _amount);

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

// Call the `relayERC20` function with the messenger caller
Expand Down
Loading