// SPDX-License-Identifier: MIT pragma solidity =0.8.24; import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol"; import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol"; import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol"; import {IScrollERC20Upgradeable} from "../../libraries/token/IScrollERC20Upgradeable.sol"; import {ScrollStandardERC20} from "../../libraries/token/ScrollStandardERC20.sol"; import {IScrollStandardERC20Factory} from "../../libraries/token/IScrollStandardERC20Factory.sol"; import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol"; /// @title L2StandardERC20Gateway /// @notice The `L2StandardERC20Gateway` is used to withdraw standard ERC20 tokens on layer 2 and /// finalize deposit the tokens from layer 1. /// @dev The withdrawn ERC20 tokens will be burned directly. On finalizing deposit, the corresponding /// token will be minted and transferred to the recipient. Any ERC20 that requires non-standard functionality /// should use a separate gateway. contract L2StandardERC20Gateway is L2ERC20Gateway { using AddressUpgradeable for address; /************* * Constants * *************/ /// @notice The address of ScrollStandardERC20Factory. address public immutable tokenFactory; /************* * Variables * *************/ /// @notice Mapping from l2 token address to l1 token address. mapping(address => address) private tokenMapping; /// @dev The storage slot used as ScrollStandardERC20Factory contract, which is deprecated now. address private __tokenFactory; /*************** * Constructor * ***************/ /// @notice Constructor for `L2StandardERC20Gateway` implementation contract. /// /// @param _counterpart The address of `L1StandardERC20Gateway` contract in L1. /// @param _router The address of `L2GatewayRouter` contract in L2. /// @param _messenger The address of `L2ScrollMessenger` contract in L2. /// @param _tokenFactory The address of `ScrollStandardERC20Factory` contract in L2. constructor( address _counterpart, address _router, address _messenger, address _tokenFactory ) ScrollGatewayBase(_counterpart, _router, _messenger) { if (_router == address(0) || _tokenFactory == address(0)) revert ErrorZeroAddress(); _disableInitializers(); tokenFactory = _tokenFactory; } /// @notice Initialize the storage of L2StandardERC20Gateway. /// /// @dev The parameters `_counterpart`, `_router`, `_messenger` and `_tokenFactory` are no longer used. /// /// @param _counterpart The address of `L1StandardERC20Gateway` contract in L1. /// @param _router The address of `L2GatewayRouter` contract in L2. /// @param _messenger The address of `L2ScrollMessenger` contract in L2. function initialize( address _counterpart, address _router, address _messenger, address /*_tokenFactory*/ ) external initializer { ScrollGatewayBase._initialize(_counterpart, _router, _messenger); } /************************* * Public View Functions * *************************/ /// @inheritdoc IL2ERC20Gateway function getL1ERC20Address(address _l2Token) external view override returns (address) { return tokenMapping[_l2Token]; } /// @inheritdoc IL2ERC20Gateway function getL2ERC20Address(address _l1Token) public view override returns (address) { return IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress(address(this), _l1Token); } /***************************** * Public Mutating Functions * *****************************/ /// @inheritdoc IL2ERC20Gateway function finalizeDepositERC20( address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes memory _data ) external payable override onlyCallByCounterpart nonReentrant { require(msg.value == 0, "nonzero msg.value"); require(_l1Token != address(0), "token address cannot be 0"); { // avoid stack too deep address _expectedL2Token = IScrollStandardERC20Factory(tokenFactory).computeL2TokenAddress( address(this), _l1Token ); require(_l2Token == _expectedL2Token, "l2 token mismatch"); } bool _hasMetadata; (_hasMetadata, _data) = abi.decode(_data, (bool, bytes)); bytes memory _deployData; bytes memory _callData; if (_hasMetadata) { (_callData, _deployData) = abi.decode(_data, (bytes, bytes)); } else { require(tokenMapping[_l2Token] == _l1Token, "token mapping mismatch"); _callData = _data; } if (!_l2Token.isContract()) { // first deposit, update mapping tokenMapping[_l2Token] = _l1Token; _deployL2Token(_deployData, _l1Token); } IScrollERC20Upgradeable(_l2Token).mint(_to, _amount); _doCallback(_to, _callData); emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _callData); } /********************** * Internal Functions * **********************/ /// @inheritdoc L2ERC20Gateway function _withdraw( address _token, address _to, uint256 _amount, bytes memory _data, uint256 _gasLimit ) internal virtual override nonReentrant { require(_amount > 0, "withdraw zero amount"); // 1. Extract real sender if this call is from L2GatewayRouter. address _from = _msgSender(); if (router == _from) { (_from, _data) = abi.decode(_data, (address, bytes)); } address _l1Token = tokenMapping[_token]; require(_l1Token != address(0), "no corresponding l1 token"); // 2. Burn token. IScrollERC20Upgradeable(_token).burn(_from, _amount); // 3. Generate message passed to L1StandardERC20Gateway. bytes memory _message = abi.encodeCall( IL1ERC20Gateway.finalizeWithdrawERC20, (_l1Token, _token, _from, _to, _amount, _data) ); // 4. send message to L2ScrollMessenger IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit); emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data); } function _deployL2Token(bytes memory _deployData, address _l1Token) internal { address _l2Token = IScrollStandardERC20Factory(tokenFactory).deployL2Token(address(this), _l1Token); (string memory _symbol, string memory _name, uint8 _decimals) = abi.decode( _deployData, (string, string, uint8) ); ScrollStandardERC20(_l2Token).initialize(_name, _symbol, _decimals, address(this), _l1Token); } }