Tadle

Tadle
DeFi
30,000 USDC
View results
Submission Details
Severity: low
Invalid

Withdrawals can be locked forever if recipient is a contract

Summary

The low level transfer function in TokenManager::withdraw() can lead to failed withdrawals if the caller is a contract (for example Gnosis wallet or a smart contract wallet).

Vulnerability Details

The TokenManagercontract allows users to withdraw their deposited tokens. In case the withdrawn token is a wrapped form of the native token, the withdraw()function sends the ETH using the low-level transfer() function (line 169):

/**
* @notice Withdraw
* @dev Caller must be owner
* @param _tokenAddress Token address
* @param _tokenBalanceType Token balance type
*/
function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
...
if (_tokenAddress == wrappedNativeToken) {
/**
* @dev token is native token
* @dev transfer from capital pool to msg sender
* @dev withdraw native token to token manager contract
* @dev transfer native token to msg sender
*/
_transfer(
wrappedNativeToken,
capitalPoolAddr,
address(this),
claimAbleAmount,
capitalPoolAddr
);
IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount);
payable(msg.sender).transfer(claimAbleAmount);
...
}

The transfer() only forwards 2300 gas, which is not enough for the recipient to execute any non-trivial logic in a receive() or fallback function.

And because only the depositor address can withdraw the locked tokens, this can lead to the deposited tokens being locked forever if the depositor is a smart contract instead of EOA.

Impact

In case the withdrawn token is wrapped native token, if a user callsTokenManager::withdraw() from a contract account like a multisig or smart contract wallet that has a receive() function requiring >2300 gas, the withdraw call will fail permanently.

The withdrawn ETH will be locked forever, leading to loss of funds.

Tools Used

Manual review.

Recommendations

Use call() instead of transfer() to send ETH in claim():

(bool success, ) = payable(msg.sender).call{value: claimAbleAmount}("");
require(success, "Native token transfer failed");

This forwards all available gas and allows contract recipients to execute arbitrary logic.

Updates

Lead Judging Commences

0xnevi Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
Assigned finding tags:

[invalid] finding-TokenManager-withdraw-transfer-2300-gas

Invalid, known issues [Medium-2](https://github.com/Cyfrin/2024-08-tadle/issues/1)

Support

FAQs

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