Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: high
Invalid

Improper Auto‐Deleveraging Factor Calculation


Summary and Impact

The vulnerability lies in the calculation of the auto‐deleverage (ADL) factor within the market module. When a market’s debt ratio exceeds or equals the configured end threshold, the ADL factor is computed as exactly 1.0—resulting in no reduction of a trader’s profit (i.e. no P&L adjustment) even under conditions of extreme debt. This miscalculation permits full minting of USDz, regardless of over‐leverage, and thereby undermines the collateralization invariants of the protocol. As a consequence, unbacked USDz could be minted, exposing the system to significant financial risk and potential insolvency.


Vulnerability Details

In‐Depth Explanation

In the vulnerable section of Market.sol, the ADL factor is computed as follows:

function getAutoDeleverageFactor(
Data storage self,
UD60x18 delegatedCreditUsdX18,
SD59x18 totalDebtUsdX18
)
internal
view
returns (UD60x18 autoDeleverageFactorX18)
{
SD59x18 sdDelegatedCreditUsdX18 = delegatedCreditUsdX18.intoSD59x18();
if (sdDelegatedCreditUsdX18.lte(totalDebtUsdX18) || sdDelegatedCreditUsdX18.isZero()) {
return UD60x18_UNIT;
}
// calculates the market ratio
UD60x18 marketDebtRatio = totalDebtUsdX18.div(sdDelegatedCreditUsdX18).intoUD60x18();
// cache the auto deleverage parameters as UD60x18
UD60x18 autoDeleverageStartThresholdX18 = ud60x18(self.autoDeleverageStartThreshold);
UD60x18 autoDeleverageEndThresholdX18 = ud60x18(self.autoDeleverageEndThreshold);
UD60x18 autoDeleverageExponentZX18 = ud60x18(self.autoDeleverageExponentZ);
// first, calculate the unscaled delevarage factor
UD60x18 unscaledDeleverageFactor = Math.min(marketDebtRatio, autoDeleverageEndThresholdX18).sub(
autoDeleverageStartThresholdX18
).div(autoDeleverageEndThresholdX18.sub(autoDeleverageStartThresholdX18));
// finally, raise to the power scale
autoDeleverageFactorX18 = unscaledDeleverageFactor.pow(autoDeleverageExponentZX18);
}

What’s Wrong:

  • Clamping Behavior:
    When the market debt ratio (total debt divided by delegated credit) is at or above the autoDeleverageEndThresholdX18, the Math.min operation forces the value to be exactly the end threshold. Thus:

    • If:
      marketDebtRatio >= autoDeleverageEndThresholdX18
      then:
      Math.min(marketDebtRatio, autoDeleverageEndThresholdX18) = autoDeleverageEndThresholdX18

  • Resulting Calculation:
    This makes the numerator:

    autoDeleverageEndThresholdX18 - autoDeleverageStartThresholdX18

    and the denominator is identical:

    autoDeleverageEndThresholdX18 - autoDeleverageStartThresholdX18

    Hence, the unscaled factor equals 1.0. Raising 1.0 to any exponent still yields 1.0, meaning no reduction in the computed profit occurs even when the market is severely overleveraged.

  • Impact:
    As a result, the system mints USDz at 100% of a trader’s profit, ignoring the risk associated with excessive market debt. This violates the intended safety mechanism and directly jeopardizes the protocol’s collateralization, potentially leading to insolvency.

Simulation Example

Scenario:

  • Delegated Credit: 1.0 USD (represented as 1e18 in fixed‑point arithmetic)

  • Total Debt: 1.2 USD (1.2e18)

  • ADL Parameters:

    • Auto-Deleverage Start Threshold: 0.5e18

    • Auto-Deleverage End Threshold: 1.0e18

    • Auto-Deleverage Exponent: 2

Calculation Steps:

  1. Market Debt Ratio:
    Total Debt / Delegated Credit = 1.2e18 / 1.0e18 = 1.2
    Clamped to 1.0 due to the min() function.

  2. Unscaled Factor:
    (1.0 − 0.5) / (1.0 − 0.5) = 0.5 / 0.5 = 1.0

  3. Final ADL Factor:
    1.0^2 = 1.0

Despite being in an overleveraged state, the ADL factor remains 1.0—resulting in full USDz minting for profitable trades. This directly contravenes the protocol’s safety mechanism by not reducing P&L under high debt conditions.

Test Code Snippet

Below is a critical excerpt from our Foundry test that reproduces the calculation:

function testAutoDeleverageFactorWhenHighDebt() public {
// Scenario: delegatedCredit = 1.0e18, totalDebt = 1.2e18.
UD60x18 delegatedCredit = ud60x18(1e18);
SD59x18 totalDebt = sd59x18(1.2e18);
UD60x18 factor = testMarket.getAutoDeleverageFactor(delegatedCredit, totalDebt);
// Expected: factor should be 1.0 (i.e., no reduction in profit).
assertEq(factor.intoUint256(), UD60x18_UNIT.intoUint256());
}

This test confirms that under extreme debt conditions the computed factor does not reduce the profit (remains at 1.0), enabling the protocol to mint unbacked USDz.


Tools Used

  • Manual Review

  • Foundry


Recommendations

  • Capping the Factor:
    Modify the logic such that when marketDebtRatio >= autoDeleverageEndThresholdX18, the factor is capped at a value slightly less than 1.0 (e.g., 1.0 - ε, where ε is a small constant).


Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.