Tadle

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

Withdrawal of collateral (native ETH) can be locked forever - if a caller (`msg.sender`) is a multi-sig or smart contract wallet

Summary

Native ETH to be withdrawn (via the transfer() method in the TokenManager#withdraw()) will be locked forever in the TokenManager contract, leading to loss of user (msg.sender)'s collateral (native ETH).

Vulnerability Details

When a user would like to withdraw their deposited-collateral (native ETH) from the CapitalPool contract, the user would call the TokenManager#withdraw().

Within the TokenManager#withdraw(), the following steps would be proceeded:

/**
* @notice Withdraw
* @dev Caller must be owner
* @param _tokenAddress Token address
* @param _tokenBalanceType Token balance type
*/
function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
uint256 claimAbleAmount = userTokenBalanceMap[_msgSender()][
_tokenAddress
][_tokenBalanceType];
...
address capitalPoolAddr = tadleFactory.relatedContracts(
RelatedContractLibraries.CAPITAL_POOL
);
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( ///<------- @audit
wrappedNativeToken,
capitalPoolAddr,
address(this),
claimAbleAmount,
capitalPoolAddr
);
IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount); ////<------- @audit - WETH# withdraw()
payable(msg.sender).transfer(claimAbleAmount); ///<------- @audit - Native ETH would be transferred from the TokenManager contract to the caller (msg.sender)

However, within the TokenManager#withdraw(), the transfer() method of the native ETH would be used - when the the claimAbleAmount of the native ETH-withdrawn would be transferred from the TokenManager contract (address(this)) to a caller (msg.sender) during the step 3/ above:
(NOTE:This caller (msg.sender) would be an end user like an EOA or a smart contract wallet)
https://github.com/Cyfrin/2024-08-tadle/blob/main/src/core/TokenManager.sol#L169

payable(msg.sender).transfer(claimAbleAmount);

This is problematic because the transfer() method of the native ETH would only forward 2300 gas, which is not enough for the recipient to execute any non-trivial logic in a receive() or fallback(). Especially, it is not enough for the smart contract wallets like (Gnosis) Safes to receive funds, which require > 6k gas for the call to reach the implementation contract.

In this case (the step 3/ above), if a caller (msg.sender) would be a multisig or smart contract wallet like Safe that has the receive() requiring >2300 gas, their subsequent TokenManager#withdraw() call will fail permanently.
As a result, native ETH to be withdrawn (via the transfer() method in the TokenManager#withdraw()) will be locked forever in the TokenManager contract, leading to loss of user (msg.sender)'s collateral (native ETH).

(Reference: A similar finding in the past C4 contest is here)

Impact

  • Native ETH to be withdrawn (via the transfer() method in the TokenManager#withdraw()) will be locked forever in the TokenManager contract, leading to loss of user (msg.sender)'s collateral (native ETH).

Tools Used

  • Foundry

Recommendations

Within the TokenManager#withdraw(), consider using the low-level call method of the native ETH - instead of using the transfer() method of the native ETH like this:

function withdraw(
address _tokenAddress,
TokenBalanceType _tokenBalanceType
) external whenNotPaused {
...
_transfer(
wrappedNativeToken,
capitalPoolAddr,
address(this),
claimAbleAmount,
capitalPoolAddr
);
IWrappedNativeToken(wrappedNativeToken).withdraw(claimAbleAmount);
- payable(msg.sender).transfer(claimAbleAmount);
+ (bool success, ) = payable(msg.sender).call{ value: claimAbleAmount }("");
+ require(success, "native ETH transfer failed");
...
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.