Reserve requirement and fees checked before withdrawal
Description
When withdrawing from Tranche, the utilization ratio is checked in _withdraw
so that the withdraw does not cause the protocol to become insolvent:
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual override {
require(utilizationRatio() < withdrawThreshold,
"UTILIZATION_RATIO_MAX");
uint256 fee = getWithdrawalFeesRaw(assets);
super._withdraw(caller, receiver, owner, assets, shares);
However, the call to utilizationRatio()
calls super.totalAssets()
, which checks the current assets of the contract, and that has not changed yet. Similarly, getWithdrawalFeesRaw
uses the current assets in the contract, and only later in super._withdraw
do the assets get transferred out.
This means that the utilization ratio and withdrawal fees are calculated on the prewithdrawal state, not the postwithdrawal state. The same thing happens with the balancing fee on deposit.
Impact
As long as the utilization ratio is currently not violated, a share owner can withdraw any amount, including an amount that would leave the utilization ratio violated after the USDC moves out of the Tranche. Here is a proof-of-concept (POC) output:
Start
- Junior reserved = 0 actual = 0
- Senior reserved = 0 actual = 0
LP provides liquidity
- Junior reserved = 0 actual = 995024875621
- Junior util ratio % = 0
- Senior reserved = 0 actual = 1000000000000
- Senior util ratio % = 0
Traders open market longs
- Junior reserved = 92609441060 actual = 995024875621
- Junior util ratio % = 9
- Senior reserved = 49866622110 actual = 1000000000000
- Senior util ratio % = 4
LP withdraws liquidity
- Junior reserved = 92609441060 actual = 4777375621
- Junior util ratio % = 1938
- Senior reserved = 49866622110 actual = 9752500000
- Senior util ratio % = 511
At the end of the POC, the utilization ratio for the tranches were 1938% and 511%, when it should never be above 100%. This means the protocol has become insolvent and cannot pay all trader profits or return trader collateral.
Additionally, even if the utilization ratio is currently violated, a share owner can still withdraw any amount by first depositing a flash loan that brings the utilization ratio back under the threshold and then withdrawing both the flash loan and the amount they wanted to withdraw.
The balancing fees on deposit and withdrawal fees on withdrawal can similarly be dodged with a flash loan.
Recommendations
Ensure all relevant quantities used in these checks are calculated on post-action balances.
Remediation
This issue has been acknowledged by Avantis Labs, Inc., and a fix was implemented in commit 2c45e310↗.