Part 2

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

Incorrect `AutoDeleverageFactor`.

Summary

The calculation of AutoDeleverageFactor is incorrect.

Vulnerability Details

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Market.sol#L174

function getAutoDeleverageFactor(
...
) ...
{ ...
UD60x18 unscaledDeleverageFactor = Math.min(marketDebtRatio, autoDeleverageEndThresholdX18).sub(
autoDeleverageStartThresholdX18
).div(autoDeleverageEndThresholdX18.sub(autoDeleverageStartThresholdX18));
// finally, raise to the power scale
174: autoDeleverageFactorX18 = unscaledDeleverageFactor.pow(autoDeleverageExponentZX18);
}

As we can see, when marketRatio >= ADL start threshold, the smaller debt, the smaller autoDeleverageFactor.
For example:
When marketDebtRatio = autoDeleverageStartThresholdX18, the calcuatated AutoDeleverageFactor is zero.
When marketDebtRatio = autoDeleverageEndThresholdX18, the calcuatated AutoDeleverageFactor is one.

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/branches/CreditDelegationBranch.sol#L159

function getAdjustedProfitForMarketId(
...
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
// if the market's auto deleverage system is triggered, it assumes marketTotalDebtUsdX18 > 0
adjustedProfitUsdX18 =
159: market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(adjustedProfitUsdX18);
}

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/perpetuals/branches/SettlementBranch.sol#L499

if (ctx.pnlUsdX18.gt(SD59x18_ZERO)) {
IMarketMakingEngine marketMakingEngine = IMarketMakingEngine(perpsEngineConfiguration.marketMakingEngine);
ctx.marginToAddX18 =
499: marketMakingEngine.getAdjustedProfitForMarketId(marketId, ctx.pnlUsdX18.intoUD60x18().intoUint256());
tradingAccount.deposit(perpsEngineConfiguration.usdToken, ctx.marginToAddX18);
// mint settlement tokens credited to trader; tokens are minted to
// address(this) since they have been credited to the trader's margin
marketMakingEngine.withdrawUsdTokenFromMarket(marketId, ctx.marginToAddX18.intoUint256());
}

As a result, Traders receive their full pnls when marketDebtRatio = autoDeleverageEndThresholdX18,but none when marketDebtRatio = autoDeleverageStartThresholdX18.

Impact

Traders do not receive their PnLs at all when the state enters the ADL state.

Recommendations

https://github.com/Cyfrin/2025-01-zaros-part-2/blob/main/src/market-making/leaves/Market.sol#L174

function getAutoDeleverageFactor(
...
) ...
{ ...
UD60x18 unscaledDeleverageFactor = Math.min(marketDebtRatio, autoDeleverageEndThresholdX18).sub(
autoDeleverageStartThresholdX18
).div(autoDeleverageEndThresholdX18.sub(autoDeleverageStartThresholdX18));
// finally, raise to the power scale
-174: autoDeleverageFactorX18 = unscaledDeleverageFactor.pow(autoDeleverageExponentZX18);
+174: autoDeleverageFactorX18 = UD60x18_UNIT.sub(unscaledDeleverageFactor).pow(autoDeleverageExponentZX18);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

getAutoDeleverageFactor inverts the ADL behavior causing wrong profit reductions when market enters deleveraging state

Support

FAQs

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