Summary
In OrderBranch, function simulateTradehas incorrect liquidation.
Vulnerability Details
function simulateTradeis called by function createMarketOrder. The function simulateTradehas checks which ensure that the creation of market order will fail if tradingAccountis liquidatable after the ordersettlement. However, the condition being used is wrong.
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/OrderBranch.sol#L123-L137
(requiredInitialMarginUsdX18, requiredMaintenanceMarginUsdX18, ctx.accountTotalUnrealizedPnlUsdX18) =
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, ctx.sizeDeltaX18);
marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(ctx.accountTotalUnrealizedPnlUsdX18);
{
(, ctx.previousRequiredMaintenanceMarginUsdX18,) =
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);
if (TradingAccount.isLiquidatable(ctx.previousRequiredMaintenanceMarginUsdX18, marginBalanceUsdX18)) {
revert Errors.AccountIsLiquidatable(tradingAccountId);
}
}
The check is used to ensure that previousRequiredMaintenanceMarginUsdX18(before order settlement) is less than marginBalanceUsdX18to prevent liquidatable accounts from trading as mentioned in comments. However, marginBalanceUsdX18is calculated considering the sizeDeltaonce the order is settled. This is not correct way because it should calculate marginBalanceUsdX18only considering current state of tradingAccountand not based on sizeDeltaof created order.
Impact
marginBalanceUsdX18calculated may not be accurate because it considers the sizeDeltaand not current state of account.
Tools Used
Manual
Recommendations
The correct code is as follows(added the comments where changes is made):
(requiredInitialMarginUsdX18, requiredMaintenanceMarginUsdX18, ctx.accountTotalUnrealizedPnlUsdX18) =\
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(marketId, ctx.sizeDeltaX18);
{
(, ctx.previousRequiredMaintenanceMarginUsdX18, accountTotalUnrealizedPnlUsdX18) =
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);
marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18);
if (TradingAccount.isLiquidatable(ctx.previousRequiredMaintenanceMarginUsdX18, marginBalanceUsdX18)) {
revert Errors.AccountIsLiquidatable(tradingAccountId);
}
}