Clumsy Pink Otter
High
Some accounts using Intents to trade might be liquidated while healthy or be unliquidatable while being unhealthy.
When user trades using signed intents, pending order is created using the price specified by the user. Upon settlement, there is some pnl realized due to difference of intent price and market price. The issue is that this difference is added to account only when checking margin to add pending order, but when protection (liquidation) is validated, collateral doesn't include pending intent's pnl from the difference of intent and market price. As such, user can be unfairly liquidated if price moves against him but he has enough collateral (with pnl from pending intent orders). If there is a loss from intent price difference, then this loss is not added when checking liquidation either, thus liquidation will revert as if account is healthy.
When protection is validated, only account's collateral is checked in InvariantLib.validate
:
https://github.com/sherlock-audit/2025-01-perennial-v2-4-update/blob/main/perennial-v2/packages/core/contracts/libs/InvariantLib.sol#L127
Note that when position is opened, intent price adjustment is added to account's collateral: https://github.com/sherlock-audit/2025-01-perennial-v2-4-update/blob/main/perennial-v2/packages/core/contracts/libs/InvariantLib.sol#L92
- User account is close to liquidation
- Position is opened or closed via Intent with intent price far enough from the latest oracle price (far enough - meaning the difference in price can make account safe)
- Price moves against the user
None.
Happens by itself (unfair user liquidation) or can be crafted by the user (avoid liquidation).
- Margin ratio is 0.5%, maintenence ratio is 0.3%
- Trading fee is 0.1%
- User has
collateral = 350
and settled position oflong = 1000
at the latest oracle price of100
(account margin ratio is350 / (1000 * 100) = 0.35%
, very close to liquidation, but still healthy) - User signs intent to partially close position of the size
500
at the price of101
. - The intent is sent on-chain and user now has pending position of
long = 500
. His additional profit from the intent price is500 * (101 - 100) = 500
. - Liquidator commits unrequested price of
99.9
- User's collateral is now
350 + 1000 * (99.9 - 100) = 250
, however since the intent is guaranteed to be executed at the price of101
, his additional profit from the intent makes the expected margin ratio for liquidation reason =(250 + 500) / (1000 * 100) = 0.75%
, so account should be safe. - However, liquidator immediately liquidates user account, because this additional profit from the intent is ignored.
- Besides liquidation fee, upon settlement, user pays fees to close the position from liquidation, which is
500 * 100 * 0.1% = 50
, so the final user collateral is 700 instead of 750. - User was unfairly liquidated (as his account was healthy) and user has lost
50 / 750 = 6.7%
of his funds.
- User can be unfairly and unexpectedly liquidated and lose 1%+ of his funds (because this happens only to users with low collateral and the pnl from intent price difference will mostly be small enough, the user loss relative to collateral will be above 1%).
Calculate sum of price adjustments for all pending guarantees (from latest + 1 to current), and add it to collateral when validating if account can be protected (liquidated).
Additionally, since intent updates are guaranteed and can not be invalidated, consider adding intent position updates to latest position when calculating the account health for liquidation reasons to make more fair liquidations. For example, when position is closed using intent (as in the example in the Attack Path):
- latest = 1000
- pending (intent) = -500 Use position of size (1000 - 500 = 500) to calculate health when liquidating, so min collateral should be below 150 instead of 300 to be able to liquidate the account.