Users can deposit their tokens however they cannot take back them because even through the withdraw function exists in TokenManager contract, it will only transfer the wrapped native tokens and ERC20 tokens from CapitalPool contract to TokenManager contract. In case users want to get back their funds they won't be able to perform it because there is no any alternative withdrawing function in the protocol contracts.This means any deposited token will be locked in the protocol forever.
As the following code snippet from TokenManager.sol::withdraw() method demonstrates withdraw function won't be useful in case any user wants to get back their tokens :
As it can be clearly seen,native tokens will directly send to TokenManager and the case will be same for other tokens as well because _safe_transfer_from() will simply make a low level call to the transferFrom function of ERC20.This function requires preApproval for sending funds but core contracts have only one approve function in CapitalPool.sol and it approves all of the issued token to the TokenManager contract. Since there is no any alternative method in the protocol contracts, the users won't be able to withdraw their funds.
This issue can be reproduced by simply changing and calling test_ask_turbo_chain() function as below:
Users won't be able to withdraw their tokens once the the tokens were deposited to the protocol which means their tokens will be locked there forever and only available to them inside the protocol. Since they cannot transfer their deposited tokens back to their eoa addresses, it will make the same effect with loosing their funds for the users in case they need to spent it in another protocol or simply convert it to a real world fiat currency.
Manual Analysis and Foundry
Adding a mapping objects which tracks user balances per capital and a withdraw function in the CapitalPool contract can fix this issue.
Mapping object can be like the following example mapping(address => mapping(address=>uint256)) public balanceOfUser`
The withdraw function can include following check `if(balanceOfUser[msg.sender][token]==0){revert("User didn't deposit any funds")}` with a reentrancy guard modifier or well implemented cei pattern as well. This way protocol can be saved against both attacking methods :
Stealing funds with reentrant call on withdraw function
Stealing funds as simply calling withdraw function without depositing the issued token.
This approach can ensure and enforce both security and user experience in the same time.
This issue's severity has similar reasonings to #252, whereby If we consider the correct permissioned implementation for the `approve()` function within `CapitalPool.sol`, this would be a critical severity issue, because the withdrawal of funds will be permanently blocked and must be rescued by the admin via the `Rescuable.sol` contract, given it will always revert [here](https://github.com/Cyfrin/2024-08-tadle/blob/04fd8634701697184a3f3a5558b41c109866e5f8/src/core/CapitalPool.sol#L36-L38) when attempting to call a non-existent function selector `approve` within the TokenManager contract. Similarly, the argument here is the approval function `approve` was made permisionless, so if somebody beforehand calls approval for the TokenManager for the required token, the transfer will infact not revert when a withdrawal is invoked. I will leave open for escalation discussions, but based on my first point, I believe high severity is appropriate. It also has a slightly different root cause and fix whereby an explicit approval needs to be provided before a call to `_safe_transfer_from()`, if not, the alternative `_transfer()` function should be used to provide an approval, assuming a fix was implemented for issue #252
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.