🍶Liquidations in the Borrowing Module

Tutorial to participate in liquidations in Angle Borrowing Module

The health of Angle Protocol borrowing module is dependent on the health of the positions (or vaults) within the system, also known as the health factor. When the health factor of a vault is below 1, anyone can make a liquidate call to the VaultManager contract where the vault is, paying back a portion of the debt owed by the vault and receiving discounted collateral in return.

This incentivises third parties to participate in the health of the overall protocol, by acting on their own interest (to receive the discounted collateral) and as a result, ensure AgTokens in circulation from this module are sufficiently collateralised.

This repo contains an implementation example of how to liquidate in the Borrowing module.

What's similar/different with Angle Borrowing Module Liquidations?

Liquidations are quite-common in DeFi. There are however some peculiarities within the Angle system liquidators should have in mind before engaging with the Angle Protocol:

  • It is possible to liquidate multiple positions/vaults in just one transaction with Angle. If different positions of the same VaultManager can be liquidated, then both of them can liquidated in one transaction with just one stablecoin transfer from the liquidator and collateral transfer to it.

  • Liquidations have been designed to be capital-efficient meaning that you can liquidate with no upfront capital: discounted collateral is given first to the liquidator which can in the same transaction swap it to stablecoins to then repay the debt. This is however an optional feature and liquidators could proceed as they normally do in other protocols.

  • The protocol proposes two different liquidation interfaces for liquidators: one that lets them specify a contract address and data to process a swap if needed, and one which enables them to proceed without this logic. The two different options correspond to the two different liquidate functions in the VaultManager contract.

  • Discount given to liquidators is not fixed and is a dynamic variable of the health ratio of a vault. Basically the smaller a health ratio, the bigger the discount of a liquidator is.

For more details on the Angle Borrowing Module smart contracts and on the contracts affected by liquidation, you can look at this page on the module architecture here and on VaultManager contracts here. All VaultManager addresses can be obtained here.

0. Prerequisites

When making a liquidate() call, you must:

  • Know the vaults (i.e. the uint256 defining the ID of vaults) which health factor are below 1.

  • Know the VaultManager (that is the contract corresponding to a collateral type-stablecoin pair in Angle Borrowing Module) in which liquidation should take place: this is the contract in which you will have to call the liquidate function

  • Choose if you want to swap the collateral obtained from liquidations to stablecoins using some sort of Swapper contract or directly bring the stablecoins to repay. In the first case, you may want to look at our guide here to see how to use Swapper contract in your Angle execution flow.

  • Know the amount of stablecoins you want to repay in these vaults. There are several ways in which you can estimate this. The optimal way is to rely on the function checkLiquidation defined in each VaultManager contract. When called on a given vaultID that is liquidable, this function returns several elements which can be used in a subsequent liquidation call:

    • maxStablecoinAmountToRepay: Maximum stablecoin amount that can be repaid by liquidators upon liquidating the vault

    • maxCollateralAmountToGive: This is the amount of collateral you would receive if you were to repay maxStablecoinAmountToRepay

    • thresholdRepayAmount: For some vaults, if liquidators were to liquidate a certain portion of their debt, then these vaults may end up with a "dusty" amount of debt of collateral in it. The protocol prevents liquidators from putting vaults in situations like that. As such, a non-null thresholdRepayAmount means that you can repay an amount of the debt either smaller to this thresholdRepayAmount or exactly equal to the maxStablecoinAmountToRepay

    • discount: Discount you will get on the oracle value price of the collateral

    • currentDebt: Total amount of debt in the vault

These values may vary in time, as such if you're running a smart contract you may want to embed your call to checkLiquidation in the same transaction as the liquidate call. If you're not sure about the maxStablecoinAmountToRepay, the protocol will round the amount value you pass in the liquidation function to the maxStablecoinAmountToRepay if it is superior to the thresholdRepayAmount in case it is not null or to the maxStablecoinAmountToRepay.

1. Getting vaults to liquidate

Only vaults that have a health factor below 1 can be liquidated. There are multiple ways you can get the health factor of a vault.

The easiest way to identify liquidable vaults is to call in a given VaultManager contract checkLiquidation for all existing vaults (you may simply iterate over the vaultIDCount variable to get all these vaults). Since this just requires repeatedly calling a view function, it does not incur any cost.

2. Executing the liquidation call

As explained above, There are two functions that you can call to liquidate:

    function liquidate(
        uint256[] memory vaultIDs,
        uint256[] memory amounts,
        address from,
        address to
    ) external returns (LiquidatorData memory)

And:

    function liquidate(
        uint256[] memory vaultIDs,
        uint256[] memory amounts,
        address from,
        address to,
        address who,
        bytes memory data
    ) public returns (LiquidatorData memory liqData)

Upon choosing the amount of debt of a vault you want to repay, you have the possibility to specify a from address from which stablecoins should be taken. This address should have approved for the stablecoin the address used to trigger the liquidation. You can also specify a to address to which collateral will be sent.

In the second option, you can specify a who contract as well as some data to swap the obtained collateral for stablecoins (or just a portion of it to minimize slippage) efficiently.

3. Processing liquidations result

If you are using a smart contract to liquidate, then you may want to process the results obtained from the liquidation you performed. You basically get different returned values:

  • stablecoinAmountToReceive: This is the amount of stablecoins you have given for the liquidation

  • collateralAmountToGive: Current amount of collateral you've gotten from the contract

Note that these returned values are denominated from a protocol perspective and they could be named differently. The contract also returns some utility values which may be less useful to the liquidator:

  • badDebtFromLiquidation: Bad debt accrued across the liquidation process

  • oracleValue: Oracle value at which the liquidation took place

  • newInterestAccumulator: Value of the interestAccumulator at the time of the call (this is a value used to track vaults outstanding debt)

4. Setting up a bot

Depending on your environment, preferred programming tools and languages, your bot should:

  • Ensure it has enough (or access to enough) funds when liquidating or that it is able to correctly swap the collateral obtained for stablecoins

  • Calculate the profitability of liquidating loans vs gas costs, taking into account the most lucrative collateral to liquidate.

  • Ensure it has access to the latest protocol data.

  • Have the usual fail safes and security you'd expect for any production service.

  • Calculating profitability vs gas cost.

  • Be able to estimate slippage in the case where collateral is swapped for stablecoins (or used to mint stablecoins).

Appendix

How is the health factor computed?

The health factor of a vault is computed from the vault's collateral amount (in stablecoin value) multiplied by the current collateral factor divided by the vault's current debt. This debt can be obtained on-chain by querying the function: getVaultDebt.

The forumula for the health factor is then the following:

Health Factor=Collateral Amount (in Stablecoin Value)×Collateral FactorDebt\texttt{Health Factor} = \frac{\texttt{Collateral Amount (in Stablecoin Value)}\times \texttt{Collateral Factor}}{\texttt{Debt}}

To get the necessary values, you can do the following:

const collateralAmount = (await vaultManager.vaultData(vaultID))
  .collateralAmount
const oracleValue = await(await vaultManager.oracle()).read()
const collatBase = await collateral.decimals()
const collateralFactor = await vaultManager.collateralFactor()
const collateralAmountInStablecoinValue = collateralAmount
  .mul(oracleValue)
  .div(10 ** collatBase)
const debt = await vaultManager.getVaultDebt(vaultID)
const healthFactor = collateralAmountInStablecoinValue
  .mul(collateralFactor)
  .div(debt)

How is the liquidation discount determined?

The liquidation discount given to liquidators is a function of the health factor of the liquidated vault.

Discount=k(1Health Factor)\texttt{Discount} = k(1-\texttt{Health Factor})

This discount is always capped by a maximum discount value defined in each VaultManager contract.

Price oracles

Angle Protocol uses Chainlink as a price oracle. You may find the list of oracle contracts used here.

Last updated