The isLong
flag for a position is not reset to false
during liquidation, preventing users from opening long positions
Description
During an administrative liquidation, the isLong
flag of a position is not reset to false
if the amount
of that position becomes zero.
function liquidatePosition(Position storage self, uint256 quoteTraded, uint256 baseTraded)
internal
returns (PositionLiquidateResult memory result)
{
// [...]
if (position.amount == 0) {
result.close = true;
delete margin;
}
//[...]
}
Following such a liquidation, the account associated with this position may post sell limit orders. If a user then attempts to open a long position, their buy order could match one of these sell limit orders. This match triggers an attempt to update the seller's position by calling the _updatePosition
function.
Within the _updatePosition
function, if the side
parameter is Side.SELL
and the position’s isLong
flag is true
, the _close
function is called. The _close
function subsequently fails because the position's amount
is zero.
function _updatePosition(Position memory self, Side side, uint256 quoteTraded, uint256 baseTraded, uint256 leverage)
private
pure
returns (PositionUpdateResult memory result)
{
bool openLong = side == Side.BUY && (self.isLong || self.amount == 0);
bool openShort = side == Side.SELL && !self.isLong; // isLong at default value implies at least open, so no need to check amount
Position memory position = self;
if (openLong || openShort) {
int256 marginDelta = _open(position, side, quoteTraded, baseTraded, leverage, result.oiDelta);
result.marginDelta = marginDelta;
result.collateralDelta = marginDelta;
} else {
result = _close(position, side, quoteTraded, baseTraded, leverage);
}
}
Consequently, this issue prevents users from successfully opening certain long positions.
Impact
A malicious actor can exploit this vulnerability to prevent users from opening new long positions by performing the following actions:
They can open a long position with the minimum-allowable amount and maximum leverage, then wait for an administrator to liquidate this position when it becomes liquidatable.
After the position is liquidated, they can repeatedly post sell limit orders for a minimal amount, targeting the best ask price. Since these orders cannot be filled when matched, this action blocks other users from opening new long positions.
Recommendations
We recommend resetting the isLong
flag to false
for a position if its amount
becomes zero during liquidation.
Remediation
This issue has been acknowledged by Liquid Labs, Inc., and a fix was implemented in commit a969476a↗.