withdraw()
is used to transfer tokens from CapitalPool.sol to the user. However, _transfer()
uses an incorrect address for the approval, causing the transaction to always revert. As a result, the funds remain permanently stuck in the contract.
withdraw()
calls _transfer when _tokenAddress == wrappedNativeToken
.
As shown, when the from address is _capitalPoolAddr
and the allowance is 0, the contract attempts to call capitalPool to approve TokenManager.sol
to spend tokens on its behalf. The issue arises because it's setting address(this)
instead of _token
.
For clarity, we can refer to the approve()
function in CapitalPool.sol
.
The parameter for the approve()
should be the token address, not the contract that needs approval.
As you can see the address of the TokenManager is obtained via tadleFactory.relatedContracts(RelatedContractLibraries.TOKEN_MANAGER)
, and then the approve()
is called on the token.
The issue with the current implementation is that it attempts to call approve()
on TokenManager.sol
, which does not have an approve()
function. This leads to the transaction always reverting with the custom error ApproveFailed()
, resulting in tokens being permanently stuck in CapitalPool.sol
.
##POC
To run the POC copy the following code into PreMarkets.t.sol
.
The transaction will always revert, causing the funds to remain permanently stuck in the contract and resulting in a loss of funds.
Manual review.
To resolve this issue, modify _transfer()
as follows.
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. The argument up in the air is since the approval function `approve` was made permisionless, the `if` block within the internal `_transfer()` function will never be invoked if somebody beforehand calls approval for the TokenManager for the required token, so 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.
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.