Part 2

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

Incorrect deleverage amount calculation in `_fillOrder()`

Summary

The getAdjustedProfitForMarketId() function uses stale credit capacity data leading to incorrect profit calculations.

Vulnerability Details

In _fillOrder(), when calculating the margin to add for positive PnL positions, the code uses outdated credit capacity values:

File: SettlementBranch.sol
494: // if trader's old position had positive pnl then credit that to the trader
495: if (ctx.pnlUsdX18.gt(SD59x18_ZERO)) {
496: IMarketMakingEngine marketMakingEngine = IMarketMakingEngine(perpsEngineConfiguration.marketMakingEngine);
497:
498: ctx.marginToAddX18 =
499: marketMakingEngine.getAdjustedProfitForMarketId(marketId, ctx.pnlUsdX18.intoUD60x18().intoUint256()); //@audit stale data
500:
501: tradingAccount.deposit(perpsEngineConfiguration.usdToken, ctx.marginToAddX18);
502:
503: // mint settlement tokens credited to trader; tokens are minted to
504: // address(this) since they have been credited to the trader's margin
505: marketMakingEngine.withdrawUsdTokenFromMarket(marketId, ctx.marginToAddX18.intoUint256());
506: }

It's because getAdjustedProfitForMarketId() returns stale credit capacity without updating the credit capacity.

function getAdjustedProfitForMarketId(
uint128 marketId,
uint256 profitUsd
)
public
view
returns (UD60x18 adjustedProfitUsdX18)
{
// load the market's data storage pointer & cache total debt
Market.Data storage market = Market.loadLive(marketId);
SD59x18 marketTotalDebtUsdX18 = market.getTotalDebt();
// caches the market's delegated credit & credit capacity
UD60x18 delegatedCreditUsdX18 = market.getTotalDelegatedCreditUsd();
SD59x18 creditCapacityUsdX18 = Market.getCreditCapacityUsd(delegatedCreditUsdX18, marketTotalDebtUsdX18);
// if the credit capacity is less than or equal to zero then
// the total debt has already taken all the delegated credit
if (creditCapacityUsdX18.lte(SD59x18_ZERO)) {
revert Errors.InsufficientCreditCapacity(marketId, creditCapacityUsdX18.intoInt256());
}
// uint256 -> UD60x18; output default case when market not in Auto Deleverage state
adjustedProfitUsdX18 = ud60x18(profitUsd);
// we don't need to add `profitUsd` as it's assumed to be part of the total debt
// NOTE: If we don't return the adjusted profit in this if branch, we assume marketTotalDebtUsdX18 is positive
if (market.isAutoDeleverageTriggered(delegatedCreditUsdX18, marketTotalDebtUsdX18)) {
// if the market's auto deleverage system is triggered, it assumes marketTotalDebtUsdX18 > 0
adjustedProfitUsdX18 =
market.getAutoDeleverageFactor(delegatedCreditUsdX18, marketTotalDebtUsdX18).mul(adjustedProfitUsdX18);
}
}

Impact

The discrepancy between previewed and actual withdrawn amounts can lead to incorrect internal accounting and potential protocol insolvency.

Recommendations

The recalculateVaultsCreditCapacity() should be called before calculating adjustedProfitUsdX18 in the _fillOrder() function.

Updates

Lead Judging Commences

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

Support

FAQs

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