From c4560bdb9a03408aed01d7470b5fe134075bfefc Mon Sep 17 00:00:00 2001 From: agusduha Date: Fri, 1 Nov 2024 11:34:56 -0300 Subject: [PATCH 1/3] feat: add IERC7802 --- packages/contracts-bedrock/semver-lock.json | 14 ++--- .../snapshots/abi/SuperchainTokenBridge.json | 5 ++ .../snapshots/abi/SuperchainWETH.json | 19 +++++++ .../src/L2/OptimismSuperchainERC20.sol | 7 ++- .../src/L2/SuperchainERC20.sol | 14 +++-- .../src/L2/SuperchainTokenBridge.sol | 13 ++++- .../src/L2/SuperchainWETH.sol | 14 +++-- .../{ICrosschainERC20.sol => IERC7802.sol} | 6 ++- .../src/L2/interfaces/ISuperchainERC20.sol | 6 ++- .../L2/interfaces/ISuperchainTokenBridge.sol | 1 + .../src/L2/interfaces/ISuperchainWETH.sol | 5 +- .../test/L2/SuperchainERC20.t.sol | 20 ++++++-- .../test/L2/SuperchainTokenBridge.t.sol | 51 ++++++++++++++++++- .../test/L2/SuperchainWETH.t.sol | 15 ++++++ 14 files changed, 158 insertions(+), 32 deletions(-) rename packages/contracts-bedrock/src/L2/interfaces/{ICrosschainERC20.sol => IERC7802.sol} (89%) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 1cee134ca19..78d1e07fd7f 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -108,8 +108,8 @@ "sourceCodeHash": "0xa76133db7f449ae742f9ba988ad86ccb5672475f61298b9fefe411b63b63e9f6" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0x24d85d246858d1aff78ae86c614dd0dc0f63b3326b2b662e3462c3a6f9b7965e", - "sourceCodeHash": "0xcb705d26e63e733051c8bd442ea69ce637a00c16d646ccc37b687b20941366fe" + "initCodeHash": "0xd72d397efe7c3629b378f43d74763ca0709806009cd91c6692b4f3f11c28d1cb", + "sourceCodeHash": "0x0819c9411a155dca592d19b60c4176954202e4fe5d632a4ffbf88d465461252c" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { "initCodeHash": "0x23dba3ceb9e58646695c306996c9e15251ac79acc6339c1a93d10a4c79da6dab", @@ -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", diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json index 36358db1b30..a75bb2ba723 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainTokenBridge.json @@ -153,6 +153,11 @@ "name": "InvalidCrossDomainSender", "type": "error" }, + { + "inputs": [], + "name": "InvalidERC7802", + "type": "error" + }, { "inputs": [], "name": "Unauthorized", diff --git a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json index fe53a4cbfb5..32d84df7d88 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperchainWETH.json @@ -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", diff --git a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol index 1e7cdb4a719..c323d8b7577 100644 --- a/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/OptimismSuperchainERC20.sol @@ -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"; @@ -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. @@ -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() { diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index f18f91ad241..53f5838d31a 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -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 { /// @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. @@ -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) { + return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId); + } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index 284beb79ec0..01b8c5f567a 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -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 @@ -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. @@ -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. @@ -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)); @@ -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); diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index e03b689ba15..f312e8c66e2 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -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 @@ -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 { @@ -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) { + return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId); + } } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/IERC7802.sol similarity index 89% rename from packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol rename to packages/contracts-bedrock/src/L2/interfaces/IERC7802.sol index 9fbfe8c65f3..469230e822d 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ICrosschainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/IERC7802.sol @@ -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 { /// @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. diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol index ce066a27b88..029b13d5520 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainERC20.sol @@ -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; } diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol index f2a61d02d55..af9d7d8d841 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainTokenBridge.sol @@ -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 diff --git a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol index 1935b4d5b0b..fa10b237dbb 100644 --- a/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/interfaces/ISuperchainWETH.sol @@ -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; } diff --git a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol index f814c06c76d..8910b79f52b 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainERC20.t.sol @@ -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"; @@ -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); @@ -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); @@ -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)); + } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index 2ff6cecfd12..f0015eca8f5 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -13,6 +13,7 @@ import { ISuperchainTokenBridge } from "src/L2/interfaces/ISuperchainTokenBridge import { ISuperchainERC20 } from "src/L2/interfaces/ISuperchainERC20.sol"; import { IOptimismSuperchainERC20Factory } from "src/L2/interfaces/IOptimismSuperchainERC20Factory.sol"; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC7802 } from "src/L2/interfaces/IERC7802.sol"; /// @title SuperchainTokenBridgeTest /// @notice Contract for testing the SuperchainTokenBridge contract. @@ -60,6 +61,31 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { superchainTokenBridge.sendERC20(address(superchainERC20), ZERO_ADDRESS, _amount, _chainId); } + /// @notice Tests the `sendERC20` function reverts when the `token` does not support the IERC7802 interface. + function testFuzz_sendERC20_notSupportedIERC7802_reverts( + address _token, + address _sender, + address _to, + uint256 _amount, + uint256 _chainId + ) + public + { + vm.assume(_to != ZERO_ADDRESS); + + // Mock the call over the `supportsInterface` function to return false + vm.mockCall( + _token, abi.encodeCall(ISuperchainERC20.supportsInterface, (type(IERC7802).interfaceId)), abi.encode(false) + ); + + // Expect the revert with `InvalidERC7802` selector + vm.expectRevert(ISuperchainTokenBridge.InvalidERC7802.selector); + + // Call the `sendERC20` function + vm.prank(_sender); + superchainTokenBridge.sendERC20(_token, _to, _amount, _chainId); + } + /// @notice Tests the `sendERC20` function burns the sender tokens, sends the message, and emits the `SendERC20` /// event. function testFuzz_sendERC20_succeeds( @@ -134,10 +160,31 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { superchainTokenBridge.relayERC20(_token, _caller, _to, _amount); } + /// @notice Tests the `relayERC20` function reverts when the `token` does not support the IERC7802 interface. + function testFuzz_relayERC20_notSupportedIERC7802_reverts( + address _token, + address _crossDomainMessageSender, + address _to, + uint256 _amount + ) + public + { + // Mock the call over the `supportsInterface` function to return false + vm.mockCall( + _token, abi.encodeCall(ISuperchainERC20.supportsInterface, (type(IERC7802).interfaceId)), abi.encode(false) + ); + + // Expect the revert with `InvalidERC7802` selector + vm.expectRevert(ISuperchainTokenBridge.InvalidERC7802.selector); + + // Call the `relayERC20` function with the messenger caller + vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + superchainTokenBridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); + } + /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not /// the same SuperchainTokenBridge. function testFuzz_relayERC20_notCrossDomainSender_reverts( - address _token, address _crossDomainMessageSender, uint256 _source, address _to, @@ -159,7 +206,7 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { // Call the `relayERC20` function with the sender caller vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainTokenBridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); + superchainTokenBridge.relayERC20(address(superchainERC20), _crossDomainMessageSender, _to, _amount); } /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index 9a65544dc7e..6b23dbfcd2b 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -12,6 +12,7 @@ import { Preinstalls } from "src/libraries/Preinstalls.sol"; // Interfaces 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"; /// @title SuperchainWETH_Test /// @notice Contract for testing the SuperchainWETH contract. @@ -457,4 +458,18 @@ contract SuperchainWETH_Test is CommonTest { // Assert assertEq(superchainWeth.balanceOf(_user), _wad); } + + /// @notice Tests that the `supportsInterface` function returns true for the `IERC7802` interface. + function test_supportInterface_succeeds() public view { + assertTrue(superchainWeth.supportsInterface(type(IERC165).interfaceId)); + assertTrue(superchainWeth.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(superchainWeth.supportsInterface(_interfaceId)); + } } From dd61f72f53b90801c69cba8dc2ebcc9abbd5a36c Mon Sep 17 00:00:00 2001 From: agusduha Date: Fri, 1 Nov 2024 12:21:24 -0300 Subject: [PATCH 2/3] fix: token address fuzz error --- packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index f0015eca8f5..ce535389007 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -72,6 +72,7 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { public { vm.assume(_to != ZERO_ADDRESS); + assumeAddressIsNot(_token, AddressType.Precompile, AddressType.ForgeAddress); // Mock the call over the `supportsInterface` function to return false vm.mockCall( @@ -169,6 +170,8 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { ) public { + assumeAddressIsNot(_token, AddressType.Precompile, AddressType.ForgeAddress); + // Mock the call over the `supportsInterface` function to return false vm.mockCall( _token, abi.encodeCall(ISuperchainERC20.supportsInterface, (type(IERC7802).interfaceId)), abi.encode(false) From d05548fa63380784ade0708b2c05ffe7b2a3b2b8 Mon Sep 17 00:00:00 2001 From: agusduha Date: Fri, 1 Nov 2024 16:10:35 -0300 Subject: [PATCH 3/3] feat: remove ERC165 contract inheritance --- packages/contracts-bedrock/semver-lock.json | 12 +++++----- .../src/L2/SuperchainERC20.sol | 11 ++++----- .../src/L2/SuperchainTokenBridge.sol | 2 -- .../src/L2/SuperchainWETH.sol | 11 ++++----- .../test/L2/SuperchainTokenBridge.t.sol | 24 ------------------- 5 files changed, 16 insertions(+), 44 deletions(-) diff --git a/packages/contracts-bedrock/semver-lock.json b/packages/contracts-bedrock/semver-lock.json index 78d1e07fd7f..2b09f99cade 100644 --- a/packages/contracts-bedrock/semver-lock.json +++ b/packages/contracts-bedrock/semver-lock.json @@ -108,7 +108,7 @@ "sourceCodeHash": "0xa76133db7f449ae742f9ba988ad86ccb5672475f61298b9fefe411b63b63e9f6" }, "src/L2/OptimismSuperchainERC20.sol": { - "initCodeHash": "0xd72d397efe7c3629b378f43d74763ca0709806009cd91c6692b4f3f11c28d1cb", + "initCodeHash": "0x1c7ffba96b28d8c92e1e15515286cbb392b1d0b165c65756b06d5c79587d0590", "sourceCodeHash": "0x0819c9411a155dca592d19b60c4176954202e4fe5d632a4ffbf88d465461252c" }, "src/L2/OptimismSuperchainERC20Beacon.sol": { @@ -125,15 +125,15 @@ }, "src/L2/SuperchainERC20.sol": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", - "sourceCodeHash": "0x7a81d66f7eefdecc8d9c3c015aae3dbe3b353976dad69424375f72abac3b4c21" + "sourceCodeHash": "0x1f6e8972082c03da692e1273eefc59c9a2b5a316aaf4f81b952a4a6899072a81" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0xc3bec105a862965a0a2dfe0a786ddcbd38624f63b59f40b3f25665a6c7f53002", - "sourceCodeHash": "0x0e3a2bf9eff4ad3c7b174622678a78b86cb542930f49d7e96a8938c0c31faa51" + "initCodeHash": "0x1cd2afdae6dd1b6ebc17f1d529e7d74c9b8b21b02db8589b8e389e2d5523d775", + "sourceCodeHash": "0x617aa994f659c5d8ebd54128d994f86f5b175ceca095b024b8524a7898e8ae62" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0xfaa8529acd18f9b182eb97247ad95b3c3b7a60760c91f098b452a4527d9e2af7", - "sourceCodeHash": "0x51f45d94871090b5b7ad1c07fa9a8523cfa2593e1eb5cfac0627476ef36ada30" + "initCodeHash": "0xc2cb1c20157498a62859c5868f037070087838e2986b9e3ba8d065ecb50214b7", + "sourceCodeHash": "0xee35d5d54444ca8df211db964c72dd374e73cc311a3430b40c95dd60dd0b86aa" }, "src/L2/WETH.sol": { "initCodeHash": "0x17ea1b1c5d5a622d51c2961fde886a5498de63584e654ed1d69ee80dddbe0b17", diff --git a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol index 53f5838d31a..eaac6078ce6 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainERC20.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainERC20.sol @@ -1,18 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import { IERC7802 } from "src/L2/interfaces/IERC7802.sol"; +import { IERC7802, IERC165 } 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, ERC165, IERC7802, ISemver { +abstract contract SuperchainERC20 is ERC20, IERC7802, ISemver { /// @notice Semantic version. /// @custom:semver 1.0.0-beta.5 function version() external view virtual returns (string memory) { @@ -41,8 +40,8 @@ abstract contract SuperchainERC20 is ERC20, ERC165, IERC7802, ISemver { emit CrosschainBurn(_from, _amount); } - /// @inheritdoc ERC165 - function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId); + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC165).interfaceId; } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index 01b8c5f567a..104f8ef9504 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -86,8 +86,6 @@ 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(); diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index f312e8c66e2..0baecab1705 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -12,8 +12,7 @@ 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 { IERC7802 } from "src/L2/interfaces/IERC7802.sol"; -import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IERC7802, IERC165 } from "src/L2/interfaces/IERC7802.sol"; import { Unauthorized, NotCustomGasToken } from "src/libraries/errors/CommonErrors.sol"; /// @custom:proxied true @@ -22,7 +21,7 @@ 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, ERC165, IERC7802, ISemver { +contract SuperchainWETH is WETH98, IERC7802, ISemver { /// @notice Semantic version. /// @custom:semver 1.0.0-beta.10 string public constant version = "1.0.0-beta.10"; @@ -93,8 +92,8 @@ contract SuperchainWETH is WETH98, ERC165, IERC7802, ISemver { emit CrosschainBurn(_from, _amount); } - /// @inheritdoc ERC165 - function supportsInterface(bytes4 _interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { - return _interfaceId == type(IERC7802).interfaceId || super.supportsInterface(_interfaceId); + /// @inheritdoc IERC165 + function supportsInterface(bytes4 _interfaceId) public view virtual returns (bool) { + return _interfaceId == type(IERC7802).interfaceId || _interfaceId == type(IERC165).interfaceId; } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index ce535389007..1dc01ba0008 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -161,30 +161,6 @@ contract SuperchainTokenBridgeTest is Bridge_Initializer { superchainTokenBridge.relayERC20(_token, _caller, _to, _amount); } - /// @notice Tests the `relayERC20` function reverts when the `token` does not support the IERC7802 interface. - function testFuzz_relayERC20_notSupportedIERC7802_reverts( - address _token, - address _crossDomainMessageSender, - address _to, - uint256 _amount - ) - public - { - assumeAddressIsNot(_token, AddressType.Precompile, AddressType.ForgeAddress); - - // Mock the call over the `supportsInterface` function to return false - vm.mockCall( - _token, abi.encodeCall(ISuperchainERC20.supportsInterface, (type(IERC7802).interfaceId)), abi.encode(false) - ); - - // Expect the revert with `InvalidERC7802` selector - vm.expectRevert(ISuperchainTokenBridge.InvalidERC7802.selector); - - // Call the `relayERC20` function with the messenger caller - vm.prank(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - superchainTokenBridge.relayERC20(_token, _crossDomainMessageSender, _to, _amount); - } - /// @notice Tests the `relayERC20` function reverts when the `crossDomainMessageSender` that sent the message is not /// the same SuperchainTokenBridge. function testFuzz_relayERC20_notCrossDomainSender_reverts(