From 64dc6d40e659673cb5c6ef3e6e564c74f7dacd5f Mon Sep 17 00:00:00 2001 From: agusduha Date: Mon, 5 Aug 2024 11:32:25 -0300 Subject: [PATCH] feat: add L2 standrad bridge interop contract --- .../src/L2/L2StandardBridgeInterop.sol | 96 +++++++++++++++++++ .../src/libraries/Predeploys.sol | 7 +- 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol new file mode 100644 index 000000000000..ad4a378a5fda --- /dev/null +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import { Predeploys } from "src/libraries/Predeploys.sol"; +import { L2StandardBridge } from "./L2StandardBridge.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/// @notice Thrown when the decimals of the tokens are not the same. +error InvalidDecimals(); + +/// @notice Thrown when the legacy address is not found in the OptimismMintableERC20Factory. +error InvalidLegacyAddress(); + +/// @notice Thrown when the SuperchainERC20 address is not found in the SuperchainERC20Factory. +error InvalidSuperchainAddress(); + +/// @notice Thrown when the remote addresses of the tokens are not the same. +error InvalidTokenPair(); + +// TODO: Use OptimismMintableERC20Factory contract instead of interface +interface IOptimismMintableERC20Factory { + function deployments(address) external view returns (address); +} + +// TODO: Move to a separate file +interface ISuperchainERC20Factory { + function deployments(address) external view returns (address); +} + +// TODO: Use an existing interface with `mint` and `burn`? +interface MintableAndBurnable is IERC20 { + function mint(address, uint256) external; + function burn(address, uint256) external; +} + +/// @custom:proxied +/// @custom:predeploy 0x4200000000000000000000000000000000000010 +/// @title L2StandardBridgeInterop +/// @notice The L2StandardBridgeInterop is an extension of the L2StandardBridge that allows for +/// the conversion of tokens between legacy tokens (OptimismMintableERC20 or StandardL2ERC20) +/// and SuperchainERC20 tokens. +contract L2StandardBridgeInterop is L2StandardBridge { + /// @notice Emitted when a conversion is made. + /// @param from The token being converted from. + /// @param to The token being converted to. + /// @param caller The caller of the conversion. + /// @param amount The amount of tokens being converted. + event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); + + /// @notice Converts `amount` of `from` token to `to` token. + /// @param _from The token being converted from. + /// @param _to The token being converted to. + /// @param _amount The amount of tokens being converted. + function convert(address _from, address _to, uint256 _amount) external { + _validatePair(_from, _to); + + MintableAndBurnable(_from).burn(msg.sender, _amount); + MintableAndBurnable(_to).mint(msg.sender, _amount); + + emit Converted(_from, _to, msg.sender, _amount); + } + + /// @notice Validates the pair of tokens. + /// @param _from The token being converted from. + /// @param _to The token being converted to. + function _validatePair(address _from, address _to) internal view { + // 1. Decimals check + if (IERC20Metadata(_from).decimals() != IERC20Metadata(_to).decimals()) revert InvalidDecimals(); + + // Order tokens for factory validation + if (_isOptimismMintableERC20(_from)) { + _validateFactories(_from, _to); + } else { + _validateFactories(_to, _from); + } + } + + /// @notice Validates that the tokens are deployed by the correct factory. + /// @param _legacyAddr The legacy token address (OptimismMintableERC20 or StandardL2ERC20). + /// @param _superAddr The SuperchainERC20 address. + function _validateFactories(address _legacyAddr, address _superAddr) internal view { + // 2. Valid legacy check + address _legacyRemoteToken = + IOptimismMintableERC20Factory(Predeploys.OPTIMISM_MINTABLE_ERC20_FACTORY).deployments(_legacyAddr); + if (_legacyRemoteToken == address(0)) revert InvalidLegacyAddress(); + + // 3. Valid SuperchainERC20 check + address _superRemoteToken = + ISuperchainERC20Factory(Predeploys.OPTIMISM_SUPERCHAIN_ERC20_FACTORY).deployments(_superAddr); + if (_superRemoteToken == address(0)) revert InvalidSuperchainAddress(); + + // 4. Same remote address check + if (_legacyRemoteToken != _superRemoteToken) revert InvalidTokenPair(); + } +} diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index 0aece54898d3..65d019a45711 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -95,6 +95,9 @@ library Predeploys { /// @notice Address of the ETHLiquidity predeploy. address internal constant ETH_LIQUIDITY = 0x4200000000000000000000000000000000000025; + /// @notice Address of the OptimismSuperchainERC20Factory predeploy. + address internal constant OPTIMISM_SUPERCHAIN_ERC20_FACTORY = 0x4200000000000000000000000000000000000026; + /// @notice Returns the name of the predeploy at the given address. function getName(address _addr) internal pure returns (string memory out_) { require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); @@ -123,6 +126,7 @@ library Predeploys { if (_addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) return "L2ToL2CrossDomainMessenger"; if (_addr == SUPERCHAIN_WETH) return "SuperchainWETH"; if (_addr == ETH_LIQUIDITY) return "ETHLiquidity"; + if (_addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY) return "OptimismSuperchainERC20Factory"; revert("Predeploys: unnamed predeploy"); } @@ -140,7 +144,8 @@ library Predeploys { || _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) - || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY); + || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) + || (_useInterop && _addr == OPTIMISM_SUPERCHAIN_ERC20_FACTORY); } function isPredeployNamespace(address _addr) internal pure returns (bool) {