Summary
The L1ERC20Bridge:: _approveFundsToAssetRouter function assumes that the approve function of ERC20 tokens returns a boolean value, as defined by the IERC20 interface. However, certain non-standard ERC20 tokens, such as USDT, do not return any value for their approve function. This mismatch leads to a revert whenever these tokens are used, preventing such tokens from being used for L1->L2 transactions.
Vulnerability Details
The L1ERC20Bridge:: deposit function is responsible for initiating a deposit by locking funds on the contract and sending a request to process an L2 transaction where tokens would be minted. During this process, the deposit function calls L1ERC20Bridge:: _approveFundsToAssetRouter to transfer ERC20 tokens from the depositor to the contract and approve them for use by the L1_ASSET_ROUTER. The L1ERC20Bridge:: _approveFundsToAssetRouter function is as follows:
 
    
    function _approveFundsToAssetRouter(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) {
        uint256 balanceBefore = _token.balanceOf(address(this));
        _token.safeTransferFrom(_from, address(this), _amount);
@>      bool success = _token.approve(address(L1_ASSET_ROUTER), _amount);
        if (!success) {
            revert ApprovalFailed();
        }
        uint256 balanceAfter = _token.balanceOf(address(this));
        return balanceAfter - balanceBefore;
    }
The contract uses the standard ERC20 interface for interacting with tokens, which requires the approve method to return a boolean value. However, for non-standard tokens like USDT, the approve method does not return a boolean value, this causes the approve function to revert during execution. That means users are unable to use non-standard ERC20 tokens for L1->L2 transactions via the L1ERC20Bridge contract.
Impact
This issue creates a denial of service (DoS) scenario for users attempting to perform L1->L2 transactions with non-standard ERC20 tokens that do not return a value for the approve function.
Tools Used
Manual
Recommendations
Use OpenZeppelin's SafeERC20 library to interact with ERC20 tokens. Replace the current approve logic with the following:
function deposit(
        address _l2Receiver,
        address _l1Token,
        uint256 _amount,
        uint256 _l2TxGasLimit,
        uint256 _l2TxGasPerPubdataByte,
        address _refundRecipient
    ) public payable nonReentrant returns (bytes32 l2TxHash) {
        if (_amount == 0) {
            // empty deposit amount
            revert EmptyDeposit();
        }
        if (_l1Token == ETH_TOKEN_ADDRESS) {
            revert ETHDepositNotSupported();
        }
        uint256 amount = _approveFundsToAssetRouter(msg.sender, IERC20(_l1Token), _amount);
        if (amount != _amount) {
            // The token has non-standard transfer logic
            revert TokensWithFeesNotSupported();
        }
        l2TxHash = L1_ASSET_ROUTER.depositLegacyErc20Bridge{value: msg.value}({
            _originalCaller: msg.sender,
            _l2Receiver: _l2Receiver,
            _l1Token: _l1Token,
            _amount: _amount,
            _l2TxGasLimit: _l2TxGasLimit,
            _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte,
            _refundRecipient: _refundRecipient
        });
        // clearing approval
-       bool success = IERC20(_l1Token).approve(address(L1_ASSET_ROUTER), 0);
+       bool success = IERC20(_l1Token).safeApprove(address(L1_ASSET_ROUTER), 0);
        if (!success) {
            revert ApprovalFailed();
        }
        depositAmount[msg.sender][_l1Token][l2TxHash] = _amount;
        emit DepositInitiated({
            l2DepositTxHash: l2TxHash,
            from: msg.sender,
            to: _l2Receiver,
            l1Token: _l1Token,
            amount: _amount
        });
    }
 /// @dev Transfers tokens from the depositor address to the native token vault address.
    /// @return The difference between the contract balance before and after the transferring of funds.
    function _approveFundsToAssetRouter(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) {
        uint256 balanceBefore = _token.balanceOf(address(this));
        _token.safeTransferFrom(_from, address(this), _amount);
+       bool success = _token.safeApprove(address(L1_ASSET_ROUTER), _amount);
-       bool success = _token.approve(address(L1_ASSET_ROUTER), _amount);
        if (!success) {
            revert ApprovalFailed();
        }
        uint256 balanceAfter = _token.balanceOf(address(this));
        return balanceAfter - balanceBefore;
    }