Tadle

Tadle
DeFiFoundry
27,750 USDC
View results
Submission Details
Severity: low
Invalid

The Recipent using multi-signature wallets cannot withdraw ETH.

Summary

In the TokenManager.sol#withdraw() function, if _tokenAddress is ETH, the low-level transfer() function is used to transfer wrappedNativeToken, so users using multi-signature wallets cannot withdraw assets.

Vulnerability Details

When _tokenAddress is wrappedNativeToken, the TokenManager.sol#withdraw() function sends ETH from the CapitalPool to the caller (msg.sender) using the low-level transfer() function:

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);
} else {
... ... // token is ERC20
}
}

However, transfer() only forwards 2300 gas, which is not enough for the recipient to execute any non-trivial logic in a receive() or fallback function. Even if the call were to reach the implementation contract and fire an event, it would require more than 6,000 gas.

If a user calls withdraw() on a contract account, such as a multi-signature or smart contract wallet with a receive() function requiring >2300 gas, the call will fail permanently. The ETH you wish to withdraw will be permanently locked in the CapitalPool contract, resulting in loss of funds.

Proof of Concept:

  • Alice calls withdraw() on the multi-signature wallet contract to withdraw 10 ETH worth.

  • The multi-signature wallet contract is msg.sender, payable(msg.sender).transfer(claimAbleAmount) is called, and ETH is transferred to the multi-signature wallet contract.

  • The receive() function of the multi-signature wallet contract attempts to transfer the received ETH to Alice, which uses >2300 gas.

  • Due to lack of gas, the multisig's receive() will fail and the transaction will be reverted.

  • Now the 10 ETH is permanently locked in the CapitalPool contract, so there is no way for Alice to claim it.

Impact

User are not able to claim withdraw request in the worst case

Tools Used

Manual Review

Recommendations

Using call() instead of transfer():

(bool success, ) = payable(msg.sender).call{value: claimableAmount}("");
require(success, "ETH transfer failed");
Updates

Lead Judging Commences

0xnevi Lead Judge about 1 year 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.