Assessment reports>Perennial>High findings>High-volatility ticks can cause bank run due to negative liquidations
Category: Business Logic

High-volatility ticks can cause bank run due to negative liquidations

High Severity
High Impact
Low Likelihood

Description

The liquidation mechanism in Market.sol calculates the maintenance (minimum collateral) and liquidation fee for a given position as follows:

function liquidationFee(
    Position memory self,
    OracleVersion memory latestVersion,
    RiskParameter memory riskParameter
) internal pure returns (UFixed6) {
    return maintenance(self, latestVersion, riskParameter)
        .mul(riskParameter.liquidationFee)
        .min(riskParameter.maxLiquidationFee)
        .max(riskParameter.minLiquidationFee);
}
function maintenance(
    Position memory self,
    OracleVersion memory latestVersion,
    RiskParameter memory riskParameter
) internal pure returns (UFixed6) {
    if (magnitude(self).isZero()) return UFixed6Lib.ZERO;
    return magnitude(self)
        .mul(latestVersion.price.abs())
        .mul(riskParameter.maintenance)
        .max(riskParameter.minMaintenance);
}

Since the liquidation fee is not constrained to be less than the collateral, a high-volatility tick can cause the liquidation fee to exceed the deposited collateral. When this happens, the liquidation itself will cause the position to end with negative collateral. So, if a user opens a position with collateral very close to maintenance, the position can then be self-liquidated for more than the deposited collateral following a volatile tick.

Impact

We created a proof of concept (POC) for this bug (section ). In this POC, we demonstrate a scenario where the first depositor can self-liquidate the position for more than their deposit, effectively stealing other users' funds and making the market insolvent.

An excerpt of the POC output is shown below:

User deposits collateral

Deposited collateral: 1000000000

Volatile click changes price to 1.5
Position liquidated

collateral after liquidation: -1001000000
token earned by liquidator: 1001000000

attack successful

It is to be noted that although an organic bank run scenario is possible, it does require a fairly volatile tick from the oracle under appropriate tuning parameters.

For example, for a power two oracle with riskParameter.liquidationFee = 0.5, we would need a 48% price change between two subsequent oracle ticks. With riskParameter.liquidationFee = 0.7, the required volatility is 18%. These values, while feasible, are still rare in practice.

There are two other possible exploitation scenarios.

  1. It may be used as a backdoor by a malicious oracle operator to drain the market relying on it.

  2. It may lead to a malicious user trying to intentionally exploit this as an infinite money glitch by opening a number of positions and self-liquidating them. However, such a user would need to anticipate an incoming volatile tick.

Recommendations

A permanent fix would require liquidations to be capped at the total deposited assets of a user. However, the current Perennial design does not track the total deposit for an account, so implementing that would require a considerable amount of rewrites. For now, this possibility should be minimized via appropriate parameter tuning on a per-market level.

Remediation

Zellic © 2023Back to top ↑