Liquidation

Liquidation

Liquidations are an integral part of any money market protocol. Fundamentally, they work simply by having liquidators repay unhealthy debts and withdraw backing collateral with a bonus as incentive. However, this is complicated by the fact that Mirage has to preserve information about healthy positions.

How liquidation works

In a typical non-confidential money market protocol, liquidators would index all collateral and debt data off-chain and search for the most profitable opportunity. Unfortunately, this efficient means of unhealthy position discovery is impossible for Mirage, as publicly available debt data are not associated with lenders.

A reference liquidator implementation will be open source after Mirage mainnet launch to lower the barrier of running a liquidator.

Identifying unhealthy positions

Mirage only exposes data for users with unhealthy debt positions. Since it's impossible to know ahead of time which lenders are unhealthy, the only way to discovery unhealthy positions would be to first index the complete list of lender addresses, and call isUserUndercollateralized() on those addresses until true is returned.

Despite the inherent inefficiency, it's possible to optimize this process by using a Multicall contract, and also prioritizing lenders with the most collateral. With a sufficiently decentralized liquidator community, the health of Mirage can be properly maintained.

Fetching unhealthy position data

Once an undercollateralized position is identified, liquidators can invoke the getUserDeposit() and getUndercollateralizedUserDebt() functions to fetch user collateral and debt data, respectively. Combined with token price data fetched from the oracle, a complete liquidation transaction can be constructed.

Liquidation limit and incentives

When a user's debt position becomes unhealthy, a liquidator can repay a certain amount of the user's debts in exchange for the backing collateral. Mirage imposes a limit on the proportion of debt value against the user's total debt value that can be repaid in a single liquidate call. This is known as the liquidation limit. The latest liquidation limit value can be obtained by calling the liquidationLimit() function on the Mirage contract.

When a liquidation happens, the liquidator will be rewarded with their collateral of choice. First, the base amount is determined by simply calculating the amount of collateral token that would be of the same value as the debt amount repaid. Then, a proportion known as liquidation bonus is added to the base amount to arrive at a final amount.

The liquidation bonus setting is per-collateral, meaning that certain tokens could have a higher liquidation bonus than others, and thus be preferred by liquidators.

Example

Assume the current price of ETH is 1,000 USD; and the price of USDC is 1 USD. The collateral ratio of ETH is configured to be 0.8. The global liquidation limit is set to 0.3, whereas the liquidation bonus of USDC is 0.1. The following events happen:

  • Alice deposits 1 ETH, which gives her 800 USD worth of borrowing power. She then takes out a loan of 410 USDC.

  • Then, the price of ETH drops to 500 USD. Alice's total borrowing power becomes 400 USD, which is now lower than her debt value.

  • Bob discovers Alice's unhealthy position, as her position is no longer confidential due to being undercollateralized.

  • Bob could repay up to 123 USDC in a single transaction, but he decides to only liquidate 100 USDC due to the lack of liquidity. Bob calls liquidate() to repay 100 USDC of Alice's debt, and receives 0.22 ETH from Alice's collateral.

  • Now Alice has 0.78 ETH left in collateral, which has a borrowing power of 312 USD. She only has 310 USDC in debt, so her position is healthy again, and hence no longer exposed by the protocol.

Zero leakage of healthy positions

As detailed in this page, liquidations in money market protocols could be susceptible to side-channel attacks. Despite this, Mirage is able to achieve zero leakage on healthy positions, while still allowing liquidations to function as expected.

This is achieved by making the 3 liquidation-related functions, namely isUserUndercollateralized(), getUndercollateralizedUserDebt(), and liquidate(), either completed constant-gas, or constant-gas-reverted. Check out this page for more details.