DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

After entering the liquidation state, the user can still execute fillMarketOrder() to avoid being liquidated

Summary

After entering the liquidation state, the user can still execute fillMarketOrder() to reduce the position and avoid liquidation.

Vulnerability Details

in _fillOrder()
We will check if the order will go into liquidation after it is executed

function _fillOrder(
uint128 tradingAccountId,
uint128 marketId,
uint128 settlementConfigurationId,
SD59x18 sizeDeltaX18,
UD60x18 fillPriceX18
)
...
{
// calculate required initial & maintenance margin for this trade
// and account's unrealized PNL
(
UD60x18 requiredInitialMarginUsdX18,
UD60x18 requiredMaintenanceMarginUsdX18,
SD59x18 accountTotalUnrealizedPnlUsdX18
@> ) = tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, sizeDeltaX18);
// check maintenance margin if:
// 1) position is not increasing AND
// 2) existing position is being decreased in size
//
// when a position is under the higher initial margin requirement but over the
// lower maintenance margin requirement, we want to allow the trader to decrease
// their losing position size before they become subject to liquidation
//
// but if the trader is opening a new position or increasing the size
// of their existing position we want to ensure they satisfy the higher
// initial margin requirement
ctx.shouldUseMaintenanceMargin = !ctx.isIncreasing && !ctx.oldPositionSizeX18.isZero();
ctx.requiredMarginUsdX18 =
ctx.shouldUseMaintenanceMargin ? requiredMaintenanceMarginUsdX18 : requiredInitialMarginUsdX18;
// reverts if the trader can't satisfy the appropriate margin requirement
tradingAccount.validateMarginRequirement(
ctx.requiredMarginUsdX18,
tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18),
ctx.orderFeeUsdX18.add(ctx.settlementFeeUsdX18)
);
}

The above code uses tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, sizeDeltaX18); to calculate the requiredMaintenanceMarginUsdX18.

This assumes that :position.size = 1000, and that the user is already in liquidation.

But the user can still successfully close the postion by passing in fillOrder(sizeDeltaX18 = -1000).
Because getAccountMarginRequirementUsdAndUnrealizedPnlUsd(sizeDeltaX18 = -1000) inside will use the reduced position to calculate notionalValueX18 = sd59x18(position. size).add(sizeDeltaX18).abs().intoUD60x18().mul(markPrice);
The resulting requiredMarginUsdX18 will be small enough to close the position properly.

Impact

It's already in liquidation, so it should only be able to be liquidated or have collateral added to prevent it from being liquidated, it shouldn't be able to just close the position.

Tools Used

Recommendations

fillOrder() should be executed by checking the current state first.

function _fillOrder(
uint128 tradingAccountId,
uint128 marketId,
uint128 settlementConfigurationId,
SD59x18 sizeDeltaX18,
UD60x18 fillPriceX18
)
{
+ (, UD60x18 requiredMaintenanceMarginUsdX18, SD59x18 accountTotalUnrealizedPnlUsdX18) =
+ tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);
+
+ ctx.marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18);
+
+ require(!TradingAccount.isLiquidatable(requiredMaintenanceMarginUsdX18, ctx.marginBalanceUsdX18),"isLiquidatable");
Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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