Era

ZKsync
FoundryLayer 2
500,000 USDC
View results
Submission Details
Severity: medium
Valid

Lack of Compatibility with Non-standard ERC20 Tokens in `L1ERC20Bridge:: _approveFundsToAssetRouter` Leads to Denial of Service (DoS)

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:

/// @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.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;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

`L1ERC20Bridge` Uses Unsafe Approvals - USDT won't work

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.