An account isLiquidatable
if the account's current required margin maintenance (before this trade) i.e (previousRequiredMaintenanceMarginUsdX18
) is greater than the account's margin balance i.e (marginBalanceUsdX18
).
During createMarketOrder()
, this is checked in simulateTrade()
to prevent liquidatable accounts
from trading.
However, in _fillOrder()
, this is not checked. The issue here is that there is a possiblity that the window between the time when an order
was created and the current time when it is being filled, prices
may have flactuated and therefore marginBalanceUsdX18
may have changed with repect to previousRequiredMaintenanceMarginUsdX18
.
createMarketOrder()
invokes simulateTrade()
which performs the following check:
Notice that it uses unrealized PNL to calculate & output account's margin balance(marginBalanceUsdX18
) by calling tradingAccount.getMarginBalanceUsd()
:
The getMarginBalanceUsd()
function iterates over every collateral the account has deposited and calls getPrice()
to calculate the collateral's "effective" balance and finally returns the margin balance of the account in usd.
Now, getPrice()
returns the price of the given margin collateral type by calling ChainlinkUtil.getPrice()
. With such oracle query, prices always fluctuate.
The problem however lies in how this whole process works.
A user creates an order
at time t
. During this time, previousRequiredMaintenanceMarginUsdX18 = x
and marginBalanceUsdX18 = x+2
At time t+3
the filler tries to fill this order. However, the prices have flactuated since the time this order was created.
At this time of filling order, previousRequiredMaintenanceMarginUsdX18 = x+1
and marginBalanceUsdX18 = x
. Therefore, this account isLiquidatable
.
However, in SettlementBranch:_fillOrder()
, this check is not performed at all. The function assumes that once the isLiquidatable
state is checked during order creation, it will remain the same throughout which is not the case. Note that one account can have multiple open positions in different markets and all these positions rely on the users margin balance.
There is a chance that these position may be in losses during this time of filling an order meaning that the remaining marginBalanceUsdX18
could have dropped with respect to previousRequiredMaintenanceMarginUsdX18
.
In createMarketOrder()
function, the following two checks are done in order:
Check that account's margin balance is greater than current required margin maintenance (before this trade) via TradingAccount.isLiquidatable()
in simulateTrade()
Check that the trader can satisfy the appropriate margin requirement via tradingAccount.validateMarginRequirement()
In SettlementBranch:fillMarketOrder()
however, only the second check is done here.
Without checking the liquidation status, liquidatable
accounts might engage in trades which according to protocol, should not be allowed. Such accounts should be liquidated.
Manual Review
Perform a check to ensure that liquidatable accounts
are prevented from trading:
In _fillOrder()
, add this check:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.