The CapitalPool::approve
function is intended to approve the tokenAddr
for maximum allowance to the tokenManager
. However, in the TokenManager::_transfer
function, an incorrect argument address(this)
is passed to ICapitalPool(_capitalPoolAddr).approve
, which should actually be _token
. This mistake causes the approval to revert unexpectedly. Furthermore, the TokenManager::withdraw
function fails to call approve when _tokenAddress != wrappedNativeToken
when the allowance is 0, potentially leading to issues when CapitalPool::approve
has not been called for this _tokenAddress
before.
Issue 1: Incorrect Argument in TokenManager::_transfer
The CapitalPool::approve
function receives tokenAddr
and calls tokenAddr.approve(tokenManager, type(uint256).max)
:
However, in the TokenManager::_transfer
function, the following code calls ICapitalPool(_capitalPoolAddr).approve(address(this))
:
In this code, the address(this)
argument passed to approve
is incorrect; it should be _token
instead. Passing address(this)
as the argument results in the approval being called on the wrong address, which will not allow the tokenManager
to spend tokens as intended. This mistake causes the approval process to fail and the transaction to revert unexpectedly.
The PoC is shown below:
The log shows the approve
call fails since there is no approve
in the contract TokenManager
.
Issue 2: Missing Allowance Check and Approval Call in TokenManager::withdraw
In the TokenManager::withdraw
function, the approve
function is not called when _tokenAddress != wrappedNativeToken
. The code directly attempts to transfer tokens from the capitalPoolAddr
without checking if the CapitalPool::approve
has been called for the specific _tokenAddress
to have sufficient allowance.
Incorrect Input Parameter: The incorrect input parameter address(this)
in the TokenManager::_transfer
function causes the approval process to fail, leading to unexpected reverts during token transfers.
Missing Allowance Check: The lack of an allowance check in the TokenManager::withdraw
function for tokens that are not wrappedNativeToken
can result in failed withdrawals due to insufficient allowance, especially if CapitalPool::approve
has not been called for the specific token.
Manual, Foundry
Modify the TokenManager::_transfer
function to pass the correct _token
parameter to the approve
function, ensuring that the approval is correctly set up for the intended token:
Ensure that the approve
function is also called within TokenManager::withdraw
when _tokenAddress != wrappedNativeToken
. This can be done by checking the allowance and approving the necessary amount before performing the token transfer.
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.