Assessment reports>Avantis>Critical findings>Reserve requirement and fees checked before withdrawal
Category: Business Logic

Reserve requirement and fees checked before withdrawal

Critical Severity
Critical Impact
High Likelihood

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.

Zellic © 2025Back to top ↑