Era

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

Double Deduction of Funds in _bridgeBurnNativeToken and _depositFunds Causes Potential User Loss

Summary

The _bridgeBurnNativeToken function calls transferFundsToNTV to transfer funds from the _originalCaller, and then calls _depositFunds, which results in the same funds being deducted twice from the user's account. This causes the user to be charged more than intended, leading to potential loss of funds and malfunctioning of the bridge operation.

Vulnerability Details

  • The _originalCaller is charged twice for the same transaction. First, funds are transferred to the Native Token Vault (NTV) via the transferFundsToNTV function. Then, the same funds are again transferred from the _originalCaller using the _depositFunds function.

  • This double deduction occurs without proper checks, leading to an unintended financial loss for the user and unexpected behavior in the transaction process.

First, when transferFundsToNTV transfers funds to the Native Token Vault (NTV).
Then, when _depositFunds is called, funds are transferred from the _originalCaller again, resulting in double the intended deduction.

function _bridgeBurnNativeToken(
uint256 _chainId,
bytes32 _assetId,
address _originalCaller,
bool _depositChecked,
bytes calldata _data
) internal virtual returns (bytes memory _bridgeMintData) {
(uint256 _depositAmount, address _receiver) = abi.decode(_data, (uint256, address));
uint256 amount;
address nativeToken = tokenAddress[_assetId];
// ETH token handling
if (_assetId == BASE_TOKEN_ASSET_ID) {
amount = msg.value;
if (_depositAmount == 0) {
_depositAmount = amount;
}
_handleChainBalanceIncrease(_chainId, _assetId, amount, true);
if (_depositAmount != amount) {
revert ValueMismatch(_depositAmount, amount);
}
} else {
// ERC20 token handling
if (msg.value != 0) {
revert NonEmptyMsgValue();
}
amount = _depositAmount;
_handleChainBalanceIncrease(_chainId, _assetId, amount, true);
// Check if the deposit amount is valid
if (!_depositChecked) {
uint256 expectedDepositAmount = _depositFunds(_originalCaller, IERC20(nativeToken), _depositAmount);
if (amount != expectedDepositAmount) {
revert TokensWithFeesNotSupported();
}
}
}
if (amount == 0) {
revert EmptyDeposit();
}
bytes memory erc20Metadata = getERC20Getters(nativeToken, originChainId[_assetId]);
_bridgeMintData = DataEncoding.encodeBridgeMintData({
_originalCaller: _originalCaller,
_remoteReceiver: _receiver,
_originToken: nativeToken,
_amount: amount,
_erc20Metadata: erc20Metadata
});
emit BridgeBurn({
chainId: _chainId,
assetId: _assetId,
sender: _originalCaller,
receiver: _receiver,
amount: amount
});
}
function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal virtual returns (uint256) {
uint256 balanceBefore = _token.balanceOf(address(this));
// Transfer the funds
_token.safeTransferFrom(_from, address(this), _amount);
uint256 balanceAfter = _token.balanceOf(address(this));
return balanceAfter - balanceBefore;
}
function transferFundsToNTV(
bytes32 _assetId,
uint256 _amount,
address _originalCaller
) external onlyNativeTokenVault returns (bool) {
address l1TokenAddress = INativeTokenVault(address(nativeTokenVault)).tokenAddress(_assetId);
if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) {
return false;
}
IERC20 l1Token = IERC20(l1TokenAddress);
bool weCanTransfer = false;
// Check if allowance is enough for transfer
if (l1Token.allowance(address(legacyBridge), address(this)) >= _amount) {
_originalCaller = address(legacyBridge);
weCanTransfer = true;
} else if (
l1Token.allowance(_originalCaller, address(this)) >= _amount &&
l1Token.allowance(_originalCaller, address(nativeTokenVault)) < _amount
) {
weCanTransfer = true;
}
if (weCanTransfer) {
l1Token.safeTransferFrom(_originalCaller, address(nativeTokenVault), _amount);
return true;
}
return false;
}

Impact

The transferFundsToNTV function and _depositFunds function both transfer funds from the _originalCaller address without ensuring that only one of these transfers is needed. This results in duplicate transfers of the same amount, causing financial discrepancies.

Tools Used

Recommendations

Add a condition to check if the funds were already transferred via transferFundsToNTV. If the funds have already been transferred, _depositFunds should not be called again for the same amount.

Updates

Lead Judging Commences

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

Double spending of funds when bridging “bridgedToken”

Support

FAQs

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