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: add IERC7802 #123

Merged
merged 3 commits into from
Nov 1, 2024
Merged
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
14 changes: 7 additions & 7 deletions packages/contracts-bedrock/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@
"sourceCodeHash": "0xa76133db7f449ae742f9ba988ad86ccb5672475f61298b9fefe411b63b63e9f6"
},
"src/L2/OptimismSuperchainERC20.sol": {
"initCodeHash": "0x24d85d246858d1aff78ae86c614dd0dc0f63b3326b2b662e3462c3a6f9b7965e",
"sourceCodeHash": "0xcb705d26e63e733051c8bd442ea69ce637a00c16d646ccc37b687b20941366fe"
"initCodeHash": "0xd72d397efe7c3629b378f43d74763ca0709806009cd91c6692b4f3f11c28d1cb",
"sourceCodeHash": "0x0819c9411a155dca592d19b60c4176954202e4fe5d632a4ffbf88d465461252c"
},
"src/L2/OptimismSuperchainERC20Beacon.sol": {
"initCodeHash": "0x23dba3ceb9e58646695c306996c9e15251ac79acc6339c1a93d10a4c79da6dab",
Expand All @@ -125,15 +125,15 @@
},
"src/L2/SuperchainERC20.sol": {
"initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
"sourceCodeHash": "0xba47f404e66e010ce417410476f26c704f2be4ce584cb79210bc5536a82ddb1f"
"sourceCodeHash": "0x7a81d66f7eefdecc8d9c3c015aae3dbe3b353976dad69424375f72abac3b4c21"
},
"src/L2/SuperchainTokenBridge.sol": {
"initCodeHash": "0xef7590c30630a75f105384e339e52758569c25a5aa0a5934c521e004b8f86220",
"sourceCodeHash": "0x4f539e9d9096d31e861982b8f751fa2d7de0849590523375cf92e175294d1036"
"initCodeHash": "0xc3bec105a862965a0a2dfe0a786ddcbd38624f63b59f40b3f25665a6c7f53002",
"sourceCodeHash": "0x0e3a2bf9eff4ad3c7b174622678a78b86cb542930f49d7e96a8938c0c31faa51"
},
"src/L2/SuperchainWETH.sol": {
"initCodeHash": "0xc72cb486b815a65daa8bd5d0af8c965b6708cf8caf03de0a18023a63a6e6c604",
"sourceCodeHash": "0x39fff1d4702a2fec3dcba29c7f9604eabf20d32e9c5bf4377edeb620624aa467"
"initCodeHash": "0xfaa8529acd18f9b182eb97247ad95b3c3b7a60760c91f098b452a4527d9e2af7",
"sourceCodeHash": "0x51f45d94871090b5b7ad1c07fa9a8523cfa2593e1eb5cfac0627476ef36ada30"
},
"src/L2/WETH.sol": {
"initCodeHash": "0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@
"name": "InvalidCrossDomainSender",
"type": "error"
},
{
"inputs": [],
"name": "InvalidERC7802",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
Expand Down
19 changes: 19 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,25 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "_interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
Expand Down
7 changes: 3 additions & 4 deletions packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity 0.8.25;

import { IOptimismSuperchainERC20 } from "src/L2/interfaces/IOptimismSuperchainERC20.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC165 } from "@openzeppelin/contracts-v5/utils/introspection/ERC165.sol";
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { Initializable } from "@openzeppelin/contracts-v5/proxy/utils/Initializable.sol";
import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol";
Expand All @@ -16,7 +15,7 @@ import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol
/// OptimismSuperchainERC20 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 SuperchainERC20, Initializable, ERC165 {
contract OptimismSuperchainERC20 is SuperchainERC20, Initializable {
/// @notice Emitted whenever tokens are minted for an account.
/// @param to Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
Expand Down Expand Up @@ -59,8 +58,8 @@ contract OptimismSuperchainERC20 is SuperchainERC20, Initializable, ERC165 {
}

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.8
string public constant override version = "1.0.0-beta.8";
/// @custom:semver 1.0.0-beta.9
string public constant override version = "1.0.0-beta.9";

/// @notice Constructs the OptimismSuperchainERC20 contract.
constructor() {
Expand Down
14 changes: 10 additions & 4 deletions packages/contracts-bedrock/src/L2/SuperchainERC20.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC7802 } from "src/L2/interfaces/IERC7802.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ERC20 } from "@solady-v0.0.245/tokens/ERC20.sol";
import { Unauthorized } from "src/libraries/errors/CommonErrors.sol";
import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.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. This construction allows the SuperchainTokenBridge to
/// burn and mint tokens.
abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver {
abstract contract SuperchainERC20 is ERC20, ERC165, IERC7802, ISemver {

Choose a reason for hiding this comment

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

cant we make the IERC7802 include IERC165?

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.4
/// @custom:semver 1.0.0-beta.5
function version() external view virtual returns (string memory) {
return "1.0.0-beta.4";
return "1.0.0-beta.5";
}

/// @notice Allows the SuperchainTokenBridge to mint tokens.
Expand All @@ -39,4 +40,9 @@ abstract contract SuperchainERC20 is ERC20, ICrosschainERC20, ISemver {

emit CrosschainBurn(_from, _amount);
}

/// @inheritdoc ERC165
function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
0xDiscotech marked this conversation as resolved.
Show resolved Hide resolved
return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId);
}
}
13 changes: 11 additions & 2 deletions packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ZeroAddress, Unauthorized } from "src/libraries/errors/CommonErrors.sol

// Interfaces
import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/interfaces/IL2ToL2CrossDomainMessenger.sol";

/// @custom:proxied true
Expand All @@ -20,6 +21,9 @@ contract SuperchainTokenBridge {
/// SuperchainTokenBridge.
error InvalidCrossDomainSender();

/// @notice Thrown when attempting to use a token that does not implement the ERC7802 interface.
error InvalidERC7802();

/// @notice Emitted when tokens are sent from one chain to another.
/// @param token Address of the token sent.
/// @param from Address of the sender.
Expand All @@ -42,8 +46,8 @@ contract SuperchainTokenBridge {
address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.2
string public constant version = "1.0.0-beta.2";
/// @custom:semver 1.0.0-beta.3
string public constant version = "1.0.0-beta.3";

/// @notice Sends tokens to a target address on another chain.
/// @dev Tokens are burned on the source chain.
Expand All @@ -63,6 +67,8 @@ contract SuperchainTokenBridge {
{
if (_to == address(0)) revert ZeroAddress();

if (!IERC165(_token).supportsInterface(type(IERC7802).interfaceId)) revert InvalidERC7802();

ISuperchainERC20(_token).crosschainBurn(msg.sender, _amount);

bytes memory message = abi.encodeCall(this.relayERC20, (_token, msg.sender, _to, _amount));
Expand All @@ -80,8 +86,11 @@ contract SuperchainTokenBridge {
function relayERC20(address _token, address _from, address _to, uint256 _amount) external {
if (msg.sender != MESSENGER) revert Unauthorized();

if (!IERC165(_token).supportsInterface(type(IERC7802).interfaceId)) revert InvalidERC7802();

(address crossDomainMessageSender, uint256 source) =
IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageContext();

if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender();

ISuperchainERC20(_token).crosschainMint(_to, _amount);
Expand Down
14 changes: 10 additions & 4 deletions packages/contracts-bedrock/src/L2/SuperchainWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";
import { IL1Block } from "src/L2/interfaces/IL1Block.sol";
import { IETHLiquidity } from "src/L2/interfaces/IETHLiquidity.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC7802 } from "src/L2/interfaces/IERC7802.sol";
import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol";

/// @custom:proxied true
Expand All @@ -21,10 +22,10 @@ import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErro
/// @notice SuperchainWETH is a version of WETH that can be freely transfrered between chains
/// within the superchain. SuperchainWETH can be converted into native ETH on chains that
/// do not use a custom gas token.
contract SuperchainWETH is WETH98, ICrosschainERC20, ISemver {
contract SuperchainWETH is WETH98, ERC165, IERC7802, ISemver {
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.9";
/// @custom:semver 1.0.0-beta.10
string public constant version = "1.0.0-beta.10";

/// @inheritdoc WETH98
function deposit() public payable override {
Expand Down Expand Up @@ -91,4 +92,9 @@ contract SuperchainWETH is WETH98, ICrosschainERC20, ISemver {

emit CrosschainBurn(_from, _amount);
}

/// @inheritdoc ERC165
function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
0xDiscotech marked this conversation as resolved.
Show resolved Hide resolved
return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// @title ICrosschainERC20
import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol";

/// @title IERC7802
/// @notice Defines the interface for crosschain ERC20 transfers.
interface ICrosschainERC20 {
interface IERC7802 is IERC165 {

Choose a reason for hiding this comment

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

if you include it here, do you need to include in the implementation contract as well?

Copy link
Member Author

@agusduha agusduha Nov 1, 2024

Choose a reason for hiding this comment

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

This one is for the interface to enforce it to be included when extending from it

The other one is an abstract contract that has the OZ implementation, it particularly declares supporting the IERC165 interface

One solution is to stop inheriting from ERC165 but manually add the interface support to each contract in the supportInterface function

/// @notice Emitted when a crosschain transfer mints tokens.
/// @param to Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
pragma solidity ^0.8.0;

// Interfaces
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC7802 } from "src/L2/interfaces/IERC7802.sol";
import { IERC20Solady as IERC20 } from "src/vendor/interfaces/IERC20Solady.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";

/// @title ISuperchainERC20
/// @notice This interface is available on the SuperchainERC20 contract.
/// @dev This interface is needed for the abstract SuperchainERC20 implementation but is not part of the standard
interface ISuperchainERC20 is ICrosschainERC20, IERC20, ISemver {
interface ISuperchainERC20 is IERC7802, IERC20, ISemver {
error Unauthorized();

function supportsInterface(bytes4 _interfaceId) external view returns (bool);

function __constructor__() external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface ISuperchainTokenBridge is ISemver {
error ZeroAddress();
error Unauthorized();
error InvalidCrossDomainSender();
error InvalidERC7802();

event SendERC20(
address indexed token, address indexed from, address indexed to, uint256 amount, uint256 destination
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
pragma solidity ^0.8.0;

import { IWETH98 } from "src/universal/interfaces/IWETH98.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC7802 } from "src/L2/interfaces/IERC7802.sol";
import { ISemver } from "src/universal/interfaces/ISemver.sol";

interface ISuperchainWETH is IWETH98, ICrosschainERC20, ISemver {
interface ISuperchainWETH is IWETH98, IERC7802, ISemver {
error Unauthorized();
error NotCustomGasToken();

function balanceOf(address src) external view returns (uint256);
function withdraw(uint256 _amount) external;
function supportsInterface(bytes4 _interfaceId) external view returns (bool);

function __constructor__() external;
}
20 changes: 17 additions & 3 deletions packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IERC20Solady as IERC20 } from "src/vendor/interfaces/IERC20Solady.sol";

// Target contract
import { SuperchainERC20 } from "src/L2/SuperchainERC20.sol";
import { ICrosschainERC20 } from "src/L2/interfaces/ICrosschainERC20.sol";
import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol";
import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol";
import { MockSuperchainERC20Implementation } from "test/mocks/SuperchainERC20Implementation.sol";

Expand Down Expand Up @@ -62,7 +62,7 @@ contract SuperchainERC20Test is Test {

// Look for the emit of the `CrosschainMint` event
vm.expectEmit(address(superchainERC20));
emit ICrosschainERC20.CrosschainMint(_to, _amount);
emit IERC7802.CrosschainMint(_to, _amount);

// Call the `mint` function with the bridge caller
vm.prank(SUPERCHAIN_TOKEN_BRIDGE);
Expand Down Expand Up @@ -105,7 +105,7 @@ contract SuperchainERC20Test is Test {

// Look for the emit of the `CrosschainBurn` event
vm.expectEmit(address(superchainERC20));
emit ICrosschainERC20.CrosschainBurn(_from, _amount);
emit IERC7802.CrosschainBurn(_from, _amount);

// Call the `burn` function with the bridge caller
vm.prank(SUPERCHAIN_TOKEN_BRIDGE);
Expand All @@ -115,4 +115,18 @@ contract SuperchainERC20Test is Test {
assertEq(superchainERC20.totalSupply(), _totalSupplyBefore - _amount);
assertEq(superchainERC20.balanceOf(_from), _fromBalanceBefore - _amount);
}

/// @notice Tests that the `supportsInterface` function returns true for the `IERC7802` interface.
function test_supportInterface_succeeds() public view {
assertTrue(superchainERC20.supportsInterface(type(IERC165).interfaceId));
assertTrue(superchainERC20.supportsInterface(type(IERC7802).interfaceId));
}

/// @notice Tests that the `supportsInterface` function returns false for any other interface than the
/// `IERC7802` one.
function testFuzz_supportInterface_returnFalse(bytes4 _interfaceId) public view {
vm.assume(_interfaceId != type(IERC165).interfaceId);
vm.assume(_interfaceId != type(IERC7802).interfaceId);
assertFalse(superchainERC20.supportsInterface(_interfaceId));
}
}
Loading