Risks Control Model

Details of how liquidation works in DeFiner

Parameters

  1. _liquidator: address of the liquidator.

  2. _borrower: address of the borrower, also the account which will be liquidated

  3. _borrowedToken: address of the borrowed token

  4. _collateralToken: address of the collateral token

Design Overview

By calling this function, the liquidator repays the borrower's loan if the borrower is liquidatable and also ends up buying the borrower's collateral at a 5% discounted price. This also resets the borrower's LTV back to what it was initially. The tokens that the liquidator uses to liquidate the borrower's account should be deposited in DeFiner.

By calling this function, the liquidator repays the borrower's loan if the borrower is liquidatable and also ends up buying the borrower's collateral at a 5% discounted price. This resets the borrower's LTV back to what it was initially. The tokens that the liquidator uses to liquidate the borrower's account should be deposited in DeFiner.

In order to be liquidatable, an account's LTV should be greater than 85%, since there is a high risk that this account will not repay it's debt once the LTV goes above 85%.

In order to be liquidatable, an account's LTV should be greater than 85%, since there is a high risk that this account will not repay it's debt once the LTV goes above 85%.

If all conditions are met, the liquidate function majorly executed the following operations:

  1. Liquidator deposits collateral tokens equivalent to the payAmount calculated

  2. Withdraws borrowed tokens equivalent to the repayAmount to the liquidator

  3. Withdraws collateral tokens equivalent to the payAmount.

  4. Repays the borrower's loan.

Please refer to the pseudocode given below for more details on how these calculations are performed.

Examples:

Terminology used:

CBB: Current borrow balance = principle + accrued interest

LDR: Liquidation Discount ratio: The discount ratio the liquidator will get when buying other's assets during the liquidation process.

CCV: Current Collateral Value = Collateral price * Collateral Amount

UAAL: User asset at Liquidation: The maximum collateral that can be liquidated or swapped.

BP: Borrow Power of borrower

ILTV: Initial LTV ratio of collateral token: 0.6 currently for most tokens

We have to make sure that:

Before liquidation:

(Assuming 1 DAI = 1 USDT = $1) USDT Price drops by 65% after user deposits, setting LTV to 92%

  1. Full liquidation

(Assuming 1 USDT = 1 DAI = $1) Collateral price drops to 65% after User 1 borrows

User1:

  1. Deposits: 100 USDT

  2. Loans: 60 DAI

  3. Collateral price drops to 65%, new collateral value = $65

  4. LTV: 0.92, liquidatable

  5. New borrow power = initial BP * %age price drop = 60 * 0.65 = 39

User2:

  1. Deposits 200 DAI

  2. It calls liquidate(user1, DAIAdress, USDTAddress)

Explanation

The maximum amount of DAI that user2 can transfer to user1 is 200 since it doesn't have any borrows.

Since DAI's price is 1, and 60 < 200, so user2 is able to pay the maximum value, which is 60 DAI. This is called full liquidation.

After Liquidation: User 1:

  1. Deposits: 5 USDT

  2. Borrows: 0

  3. User is not liquidatable

User 2:

  1. Deposits: 95 USDT

2. Partial Liquidation

Before Liquidation:

User1:

  1. Deposits: 100 USDT

  2. Loans: 60 DAI

  3. Collateral price drops to 65%, new collateral value = $65

  4. LTV: 0.92, liquidatable

  5. borrow power = initial BP * %age price drop = 60 * 0.65 = 39

User2:

  1. Deposits 50 DAI

  2. It calls liquidate(user1, DAIAdress, USDTAddress)

Explanation:

Here, UAAL is computed the same way as the previous example.

But here user2 only has 50DAI, which worth $50 and 50 < 60 so user2 can't be swapped to the maximum value UAAL. That way, user2 only pays 50 DAI and user1 will pay 50 / 0.95 = 52.6 USDC.

After liquidation

User 1:

  1. Deposits: 100 - 50/0.95 = 47.4 USDT

  2. Borrows: 60-50 = 10DAI

  3. LTV: 10 / 47.4 = 0.21, not liquidatable

User 2:

  1. Deposits: 50/0.95 = 52.6 USDC

Notice here although user2 doesn't fully liquidate user1, user1 is not liquidatable after liquidation. This is because there is a gap between the initial borrow LTV and the LTV to become liquidatable.

Pseudocode

require(isAccountLiquidatable(_borrower);

if (liquidator has borrows){
    require(Liquidator's borrow value < Liquidator's borrow power;
}

uint tokenBalLiquidator = get deposit balance of Liquidator;

uint tokenBalBorrowedUser = get borrow balance of Borrower;

uint borrowedTokenAmountForLiquidation = tokenBalLiquidator.min(tokenBalBorrowedUser)

uint borrowerCollateralVal = getDepositBalanceCurrent(_collateralToken, _borrower);

uint collateralLTV = 60%;

uint totalBorrowwValBorwer = getBorrowETH(_borrower);
uint borrowPowerBorrower = getBorrowPower(borrower);
uint liquidationDiscountRatio = 95%;

uint limiRepaymentVal = (totalBorrowwValBorwer - borrowPowerBorrower) / (liquidationDiscountRatio - collateralLTV);

uint collateralTokenValueForLiquidation = limiRepaymentVal.min(tokenBalLiquidator);

uint liquidationVal = collateralTokenValueForLiquidation.min(borrowedTokenAmountForLiquidation * borrowedTokenPrice / liquidationDiscountRatio);

uint repaymentAmount = (liquidationVal * borrowTokenDivisor) / borrowTokenPrice;
uint payAmount = (repaymentAmount * liquidateTokenDivisor * borrowTokenPrice) / (borrowTokenDivisor * liquidationDiscountRatio * liquidateTokenPrice);

deposit(_liquidator, _collateralToken, payAmount);
_withdrawLiquidate(_liquidator, _borrowedToken, repaymentAmount);
_withdrawLiquidate(_borrower, _collateralToken, payAmount);
repay(_borrower, _borrowedToken, repaymentAmount);

return (repaymentAmount, payAmount);

Source Code

function liquidate(
        address _liquidator,
        address _borrower,
        address _borrowedToken,
        address _collateralToken
    ) external onlyAuthorized returns (uint256, uint256) {
        initCollateralFlag(_liquidator);
        initCollateralFlag(_borrower);
        require(isAccountLiquidatable(_borrower), "borrower is not liquidatable");

        // It is required that the liquidator doesn't exceed it's borrow power.
        // if liquidator has any borrows, then only check for borrowPower condition
        Account storage liquidateAcc = accounts[_liquidator];
        if (liquidateAcc.borrowBitmap > 0) {
            require(getBorrowETH(_liquidator) < getBorrowPower(_liquidator), "No extra funds used for liquidation");
        }

        LiquidationVars memory vars;

        ITokenRegistry tokenRegistry = globalConfig.tokenInfoRegistry();

        // _borrowedToken balance of the liquidator (deposit balance)
        vars.targetTokenBalance = getDepositBalanceCurrent(_borrowedToken, _liquidator);
        require(vars.targetTokenBalance > 0, "amount must be > 0");

        // _borrowedToken balance of the borrower (borrow balance)
        vars.targetTokenBalanceBorrowed = getBorrowBalanceCurrent(_borrowedToken, _borrower);
        require(vars.targetTokenBalanceBorrowed > 0, "borrower not own any debt token");

        // _borrowedToken available for liquidation
        uint256 borrowedTokenAmountForLiquidation = vars.targetTokenBalance.min(vars.targetTokenBalanceBorrowed);

        // _collateralToken balance of the borrower (deposit balance)
        vars.liquidateTokenBalance = getDepositBalanceCurrent(_collateralToken, _borrower);

        uint256 targetTokenDivisor;
        (, targetTokenDivisor, vars.targetTokenPrice, vars.borrowTokenLTV) = tokenRegistry.getTokenInfoFromAddress(
            _borrowedToken
        );

        uint256 liquidateTokendivisor;
        uint256 collateralLTV;
        (, liquidateTokendivisor, vars.liquidateTokenPrice, collateralLTV) = tokenRegistry.getTokenInfoFromAddress(
            _collateralToken
        );

        // _collateralToken to purchase so that borrower's balance matches its borrow power
        vars.totalBorrow = getBorrowETH(_borrower);
        vars.borrowPower = getBorrowPower(_borrower);
        vars.liquidationDiscountRatio = globalConfig.liquidationDiscountRatio();
        vars.limitRepaymentValue = vars.totalBorrow.sub(vars.borrowPower).mul(100).div(
            vars.liquidationDiscountRatio.sub(collateralLTV)
        );

        uint256 collateralTokenValueForLiquidation = vars.limitRepaymentValue.min(
            vars.liquidateTokenBalance.mul(vars.liquidateTokenPrice).div(liquidateTokendivisor)
        );

        uint256 liquidationValue = collateralTokenValueForLiquidation.min(
            borrowedTokenAmountForLiquidation.mul(vars.targetTokenPrice).mul(100).div(targetTokenDivisor).div(
                vars.liquidationDiscountRatio
            )
        );

        vars.repayAmount = liquidationValue.mul(vars.liquidationDiscountRatio).mul(targetTokenDivisor).div(100).div(
            vars.targetTokenPrice
        );
        vars.payAmount = vars.repayAmount.mul(liquidateTokendivisor).mul(100).mul(vars.targetTokenPrice);
        vars.payAmount = vars.payAmount.div(targetTokenDivisor).div(vars.liquidationDiscountRatio).div(
            vars.liquidateTokenPrice
        );

        deposit(_liquidator, _collateralToken, vars.payAmount);
        _withdrawLiquidate(_liquidator, _borrowedToken, vars.repayAmount);
        _withdrawLiquidate(_borrower, _collateralToken, vars.payAmount);
        repay(_borrower, _borrowedToken, vars.repayAmount);

        return (vars.repayAmount, vars.payAmount);
    }

Last updated