contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
  using SafeERC20 for IERC20;

  /********************************
   * External Contract References *
   ********************************/

  address public l2TokenBridge;

  // Maps L1 token to L2 token to balance of the L1 token deposited
  mapping(address => mapping(address => uint256)) public deposits;

  /***************
   * Constructor *
   ***************/

  // This contract lives behind a proxy, so the constructor parameters will go unused.
  constructor() CrossDomainEnabled(address(0)) {}

  /******************
   * Initialization *
   ******************/

  /**
   * @param _l1messenger L1 Messenger address being used for cross-chain communications.
   * @param _l2TokenBridge L2 standard bridge address.
   */
  function initialize(address _l1messenger, address _l2TokenBridge) public {
    require(messenger == address(0), 'Contract has already been initialized.');
    messenger = _l1messenger;
    l2TokenBridge = _l2TokenBridge;
  }

  /**************
   * Depositing *
   **************/

  /** @dev Modifier requiring sender to be EOA.  This check could be bypassed by a malicious
   *  contract via initcode, but it takes care of the user error we want to avoid.
   */
  modifier onlyEOA() {
    // Used to stop deposits from contracts (avoid accidentally lost tokens)
    require(!Address.isContract(msg.sender), 'Account not EOA');
    _;
  }

  /**
   * @dev This function can be called with no data
   * to deposit an amount of ETH to the caller's balance on L2.
   * Since the receive function doesn't take data, a conservative
   * default amount is forwarded to L2.
   */
  receive() external payable onlyEOA {
    _initiateETHDeposit(msg.sender, msg.sender, 1_300_000, bytes(''));
  }

  /**
   * @inheritdoc IL1StandardBridge
   */
  function depositETH(uint32 _l2Gas, bytes calldata _data)
    external
    payable
    onlyEOA
  {
    _initiateETHDeposit(msg.sender, msg.sender, _l2Gas, _data);
  }

  /**
   * @inheritdoc IL1StandardBridge
   */
  function depositETHTo(
    address _to,
    uint32 _l2Gas,
    bytes calldata _data
  ) external payable {
    _initiateETHDeposit(msg.sender, _to, _l2Gas, _data);
  }

  /**
   * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of
   * the deposit.
   * @param _from Account to pull the deposit from on L1.
   * @param _to Account to give the deposit to on L2.
   * @param _l2Gas Gas limit required to complete the deposit on L2.
   * @param _data Optional data to forward to L2. This data is provided
   *        solely as a convenience for external contracts. Aside from enforcing a maximum
   *        length, these contracts provide no guarantees about its content.
   */
  function _initiateETHDeposit(
    address _from,
    address _to,
    uint32 _l2Gas,
    bytes memory _data
  ) internal {
    // Construct calldata for finalizeDeposit call
    bytes memory message = abi.encodeWithSelector(
      IL2ERC20Bridge.finalizeDeposit.selector,
      address(0),
      Lib_PredeployAddresses.OVM_ETH,
      _from,
      _to,
      msg.value,
      _data
    );

    // Send calldata into L2
    sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

    emit ETHDepositInitiated(_from, _to, msg.value, _data);
  }

  /**
   * @inheritdoc IL1ERC20Bridge
   */
  function depositERC20(
    address _l1Token,
    address _l2Token,
    uint256 _amount,
    uint32 _l2Gas,
    bytes calldata _data
  ) external virtual onlyEOA {
    _initiateERC20Deposit(
      _l1Token,
      _l2Token,
      msg.sender,
      msg.sender,
      _amount,
      _l2Gas,
      _data
    );
  }

  /**
   * @inheritdoc IL1ERC20Bridge
   */
  function depositERC20To(
    address _l1Token,
    address _l2Token,
    address _to,
    uint256 _amount,
    uint32 _l2Gas,
    bytes calldata _data
  ) external virtual {
    _initiateERC20Deposit(
      _l1Token,
      _l2Token,
      msg.sender,
      _to,
      _amount,
      _l2Gas,
      _data
    );
  }

  /**
   * @dev Performs the logic for deposits by informing the L2 Deposited Token
   * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)
   *
   * @param _l1Token Address of the L1 ERC20 we are depositing
   * @param _l2Token Address of the L1 respective L2 ERC20
   * @param _from Account to pull the deposit from on L1
   * @param _to Account to give the deposit to on L2
   * @param _amount Amount of the ERC20 to deposit.
   * @param _l2Gas Gas limit required to complete the deposit on L2.
   * @param _data Optional data to forward to L2. This data is provided
   *        solely as a convenience for external contracts. Aside from enforcing a maximum
   *        length, these contracts provide no guarantees about its content.
   */
  function _initiateERC20Deposit(
    address _l1Token,
    address _l2Token,
    address _from,
    address _to,
    uint256 _amount,
    uint32 _l2Gas,
    bytes calldata _data
  ) internal {
    // When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future
    // withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if
    // _from is an EOA or address(0).
    IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);

    // Construct calldata for _l2Token.finalizeDeposit(_to, _amount)
    bytes memory message = abi.encodeWithSelector(
      IL2ERC20Bridge.finalizeDeposit.selector,
      _l1Token,
      _l2Token,
      _from,
      _to,
      _amount,
      _data
    );

    // Send calldata into L2
    sendCrossDomainMessage(l2TokenBridge, _l2Gas, message);

    deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] + _amount;

    emit ERC20DepositInitiated(_l1Token, _l2Token, _from, _to, _amount, _data);
  }

  /*************************
   * Cross-chain Functions *
   *************************/

  /**
   * @inheritdoc IL1StandardBridge
   */
  function finalizeETHWithdrawal(
    address _from,
    address _to,
    uint256 _amount,
    bytes calldata _data
  ) external onlyFromCrossDomainAccount(l2TokenBridge) {
    (bool success, ) = _to.call{value: _amount}(new bytes(0));
    require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');

    emit ETHWithdrawalFinalized(_from, _to, _amount, _data);
  }

  /**
   * @inheritdoc IL1ERC20Bridge
   */
  function finalizeERC20Withdrawal(
    address _l1Token,
    address _l2Token,
    address _from,
    address _to,
    uint256 _amount,
    bytes calldata _data
  ) external onlyFromCrossDomainAccount(l2TokenBridge) {
    deposits[_l1Token][_l2Token] = deposits[_l1Token][_l2Token] - _amount;

    // When a withdrawal is finalized on L1, the L1 Bridge transfers the funds to the withdrawer
    IERC20(_l1Token).safeTransfer(_to, _amount);

    emit ERC20WithdrawalFinalized(
      _l1Token,
      _l2Token,
      _from,
      _to,
      _amount,
      _data
    );
  }

  /*****************************
   * Temporary - Migrating ETH *
   *****************************/

  /**
   * @dev Adds ETH balance to the account. This is meant to allow for ETH
   * to be migrated from an old gateway to a new gateway.
   * NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the
   * old contract
   */
  function donateETH() external payable {}
}
/**
 * @title L1StandardBridge
 * @dev The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard
 * tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits
 * and listening to it for newly finalized withdrawals.
 *
 * Runtime target: EVM
 */
function depositERC20To(
  address _l1Token,
  address _l2Token,
  address _to,
  uint256 _amount,
  uint32 _l2Gas,
  bytes calldata _data
) external virtual {
  _initiateERC20Deposit(
    _l1Token,
    _l2Token,
    msg.sender,
    _to,
    _amount,
    _l2Gas,
    _data
  );
}

/**
 * @inheritdoc IL1ERC20Bridge
 */