|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity =0.8.24; |
| 4 | + |
| 5 | +import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; |
| 6 | +import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; |
| 7 | +import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; |
| 8 | + |
| 9 | +import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol"; |
| 10 | +import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol"; |
| 11 | +import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol"; |
| 12 | +import {L2CustomERC20Gateway} from "./L2CustomERC20Gateway.sol"; |
| 13 | + |
| 14 | +/// @title L2ReverseCustomERC20Gateway |
| 15 | +/// @notice The `L2ReverseCustomERC20Gateway` is used to withdraw native ERC20 tokens on layer 2 and |
| 16 | +/// finalize deposit the tokens from layer 1. |
| 17 | +/// @dev The withdrawn ERC20 tokens are holed in this contract. On finalizing deposit, the corresponding |
| 18 | +/// token will be transferred to the recipient. |
| 19 | +contract L2ReverseCustomERC20Gateway is L2CustomERC20Gateway { |
| 20 | + using SafeERC20Upgradeable for IERC20Upgradeable; |
| 21 | + |
| 22 | + /********** |
| 23 | + * Errors * |
| 24 | + **********/ |
| 25 | + |
| 26 | + /// @dev Thrown when the message value is not zero. |
| 27 | + error ErrorNonzeroMsgValue(); |
| 28 | + |
| 29 | + /// @dev Thrown when the given l1 token address is zero. |
| 30 | + error ErrorL1TokenAddressIsZero(); |
| 31 | + |
| 32 | + /// @dev Thrown when the given l1 token address not match stored one. |
| 33 | + error ErrorL1TokenAddressMismatch(); |
| 34 | + |
| 35 | + /// @dev Thrown when no l1 token exists. |
| 36 | + error ErrorNoCorrespondingL1Token(); |
| 37 | + |
| 38 | + /// @dev Thrown when withdraw zero amount token. |
| 39 | + error ErrorWithdrawZeroAmount(); |
| 40 | + |
| 41 | + /*************** |
| 42 | + * Constructor * |
| 43 | + ***************/ |
| 44 | + |
| 45 | + /// @notice Constructor for `L2ReverseCustomERC20Gateway` implementation contract. |
| 46 | + /// |
| 47 | + /// @param _counterpart The address of `L1ReverseCustomERC20Gateway` contract in L1. |
| 48 | + /// @param _router The address of `L2GatewayRouter` contract in L2. |
| 49 | + /// @param _messenger The address of `L2ScrollMessenger` contract in L2. |
| 50 | + constructor( |
| 51 | + address _counterpart, |
| 52 | + address _router, |
| 53 | + address _messenger |
| 54 | + ) L2CustomERC20Gateway(_counterpart, _router, _messenger) { |
| 55 | + _disableInitializers(); |
| 56 | + } |
| 57 | + |
| 58 | + /***************************** |
| 59 | + * Public Mutating Functions * |
| 60 | + *****************************/ |
| 61 | + |
| 62 | + /// @inheritdoc IL2ERC20Gateway |
| 63 | + function finalizeDepositERC20( |
| 64 | + address _l1Token, |
| 65 | + address _l2Token, |
| 66 | + address _from, |
| 67 | + address _to, |
| 68 | + uint256 _amount, |
| 69 | + bytes calldata _data |
| 70 | + ) external payable override onlyCallByCounterpart nonReentrant { |
| 71 | + if (msg.value != 0) revert ErrorNonzeroMsgValue(); |
| 72 | + if (_l1Token == address(0)) revert ErrorL1TokenAddressIsZero(); |
| 73 | + if (_l1Token != tokenMapping[_l2Token]) revert ErrorL1TokenAddressMismatch(); |
| 74 | + |
| 75 | + IERC20Upgradeable(_l2Token).safeTransfer(_to, _amount); |
| 76 | + |
| 77 | + _doCallback(_to, _data); |
| 78 | + |
| 79 | + emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data); |
| 80 | + } |
| 81 | + |
| 82 | + /********************** |
| 83 | + * Internal Functions * |
| 84 | + **********************/ |
| 85 | + |
| 86 | + /// @inheritdoc L2ERC20Gateway |
| 87 | + function _withdraw( |
| 88 | + address _token, |
| 89 | + address _to, |
| 90 | + uint256 _amount, |
| 91 | + bytes memory _data, |
| 92 | + uint256 _gasLimit |
| 93 | + ) internal virtual override nonReentrant { |
| 94 | + address _l1Token = tokenMapping[_token]; |
| 95 | + if (_l1Token == address(0)) revert ErrorNoCorrespondingL1Token(); |
| 96 | + if (_amount == 0) revert ErrorWithdrawZeroAmount(); |
| 97 | + |
| 98 | + // 1. Extract real sender if this call is from L2GatewayRouter. |
| 99 | + address _from = _msgSender(); |
| 100 | + if (router == _from) { |
| 101 | + (_from, _data) = abi.decode(_data, (address, bytes)); |
| 102 | + } |
| 103 | + |
| 104 | + // 2. transfer token to this contract |
| 105 | + uint256 balance = IERC20Upgradeable(_token).balanceOf(address(this)); |
| 106 | + IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount); |
| 107 | + _amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balance; |
| 108 | + |
| 109 | + // 3. Generate message passed to L1ReverseCustomERC20Gateway. |
| 110 | + bytes memory _message = abi.encodeCall( |
| 111 | + IL1ERC20Gateway.finalizeWithdrawERC20, |
| 112 | + (_l1Token, _token, _from, _to, _amount, _data) |
| 113 | + ); |
| 114 | + |
| 115 | + // 4. send message to L2ScrollMessenger |
| 116 | + IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit); |
| 117 | + |
| 118 | + emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data); |
| 119 | + } |
| 120 | +} |
0 commit comments