Skip to content

Commit

Permalink
feat: add SuperchainERC20 baseline (#11675)
Browse files Browse the repository at this point in the history
* feat: add superchain erc20 baseline (#37)

* feat: add superchain erc20 baseline

* feat: make superchain ERC20 simpler

* fix: small version fix and tests

* test: fix test name

* test: remove unused import

* feat: making baseline abstract

* fix: interfaces to comply with the new interface checker

* fix: import paths and empty line

* fix: lint line

---------

Co-authored-by: 0xng <[email protected]>
Co-authored-by: 0xng <[email protected]>
  • Loading branch information
3 people authored Sep 16, 2024
1 parent 7efa4e7 commit 9968aa3
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 104 deletions.
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": "0x4fd71b5352b78d51d39625b6defa77a75be53067b32f3cba86bd17a46917adf9",
"sourceCodeHash": "0xad3934ea533544b3c130c80be26201354af85f9166cb2ce54d96e5e383ebb5c1"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0x99ce8095b23c124850d866cbc144fee6cee05dbc6bb5d83acadfe00b90cf42c7",
Expand Down
72 changes: 15 additions & 57 deletions packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,17 @@
pragma solidity 0.8.25;

import { IOptimismSuperchainERC20Extension } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
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 { ERC20 } from "@solady/tokens/ERC20.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();

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

/// @custom:proxied true
/// @title OptimismSuperchainERC20
/// @notice OptimismSuperchainERC20 is a standard extension of the base ERC20 token contract that unifies ERC20 token
Expand All @@ -31,10 +21,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 @@ -57,7 +50,7 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
}

/// @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,7 +85,7 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
external
initializer
{
OptimismSuperchainERC20Metadata storage _storage = _getMetadataStorage();
OptimismSuperchainERC20Metadata storage _storage = _getStorage();
_storage.remoteToken = _remoteToken;
_storage.name = _name;
_storage.symbol = _symbol;
Expand Down Expand Up @@ -121,54 +114,19 @@ 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;
return _getStorage().remoteToken;
}

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

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

/// @notice Returns the number of decimals used to get its user representation.
Expand All @@ -178,7 +136,7 @@ contract OptimismSuperchainERC20 is IOptimismSuperchainERC20Extension, ERC20, IS
/// 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().decimals;
}

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

import { ISuperchainERC20Extensions, ISuperchainERC20Errors } 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";

/// @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, ISuperchainERC20Errors, ERC20 {
/// @notice Address of the L2ToL2CrossDomainMessenger Predeploy.
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

/// @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 (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);
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ISuperchainERC20Extensions } from "./ISuperchainERC20.sol";
import { IERC20Solady } from "src/dependency/interfaces/IERC20Solady.sol";
import { ISuperchainERC20Extensions, ISuperchainERC20Errors } from "./ISuperchainERC20.sol";

/// @title IOptimismSuperchainERC20Extension
/// @notice This interface is available on the OptimismSuperchainERC20 contract.
/// We declare it as a separate interface so that it can be used in
/// custom implementations of SuperchainERC20.
interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions {
interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions, ISuperchainERC20Errors {
/// @notice Emitted whenever tokens are minted for an account.
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
Expand All @@ -34,5 +34,5 @@ interface IOptimismSuperchainERC20Extension is ISuperchainERC20Extensions {
}

/// @title IOptimismSuperchainERC20
/// @notice Combines the ERC20 interface with the OptimismSuperchainERC20Extension interface.
interface IOptimismSuperchainERC20 is IERC20, IOptimismSuperchainERC20Extension { }
/// @notice Combines Solady's ERC20 interface with the OptimismSuperchainERC20Extension interface.
interface IOptimismSuperchainERC20 is IERC20Solady, IOptimismSuperchainERC20Extension { }
21 changes: 18 additions & 3 deletions packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Solady } from "src/dependency/interfaces/IERC20Solady.sol";

/// @title ISuperchainERC20Extensions
/// @notice Interface for the extensions to the ERC20 standard that are used by SuperchainERC20.
Expand Down Expand Up @@ -35,6 +35,21 @@ interface ISuperchainERC20Extensions {
function relayERC20(address _from, address _to, uint256 _amount) external;
}

/// @title ISuperchainERC20Errors
/// @notice Interface containing the errors added in the SuperchainERC20 implementation.
interface ISuperchainERC20Errors {
/// @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 perform an operation and the account is the zero address.
error ZeroAddress();
}

/// @title ISuperchainERC20
/// @notice Combines the ERC20 interface with the SuperchainERC20Extensions interface.
interface ISuperchainERC20 is IERC20, ISuperchainERC20Extensions { }
/// @notice Combines Solady's ERC20 interface with the SuperchainERC20Extensions interface.
interface ISuperchainERC20 is IERC20Solady, ISuperchainERC20Extensions, ISuperchainERC20Errors { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20Solady {
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();

/// @dev The allowance has overflowed.
error AllowanceOverflow();

/// @dev The allowance has underflowed.
error AllowanceUnderflow();

/// @dev Insufficient balance.
error InsufficientBalance();

/// @dev Insufficient allowance.
error InsufficientAllowance();

/// @dev The permit is invalid.
error InvalidPermit();

/// @dev The permit has expired.
error PermitExpired();

/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);

/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);

/// @dev Returns the name of the token.
function name() external view returns (string memory);

/// @dev Returns the symbol of the token.
function symbol() external view returns (string memory);

/// @dev Returns the decimals places of the token.
function decimals() external view returns (uint8);

/// @dev Returns the amount of tokens in existence.
function totalSupply() external view returns (uint256 result);

/// @dev Returns the amount of tokens owned by `owner`
function balanceOf(address owner) external view returns (uint256 result);

/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender) external view returns (uint256 result);

/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) external returns (bool);

/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) external returns (bool);

/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) external returns (bool);

/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) external view returns (uint256 result);

/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external;

/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
function DOMAIN_SEPARATOR() external view returns (bytes32 result);
}
Loading

0 comments on commit 9968aa3

Please sign in to comment.