โ Liquidation
Intro
In this section, we simulate a borrow->liquidation scenario:
Step 1: supply
Step 2: borrow max
Step 3: wait few blocks and let
borrowed_balance > supplied_balance * collateral_factor
-> leads to liquidationStep 4: liquidate
Code:
Setup
Set Comptroller, borrowed token and borrowed cToken:
Comptroller public comptroller = Comptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
IERC20 public tokenBorrow;
CErc20 public cTokenBorrow;
event Log(string message, uint val);
constructor(address _tokenBorrow, address _cTokenBorrow) {
tokenBorrow = IERC20(_tokenBorrow);
cTokenBorrow = CErc20(_cTokenBorrow);
}
Close Factor
What is "close factor"? Quote from doc:
The percent, ranging from 0% to 100%, of a liquidatable accountโs borrow that can be repaid in a single liquidate transaction. If a user has multiple borrowed assets, the closeFactor applies to any single borrowed asset, not the aggregated value of a userโs outstanding borrowing.
Close factor can be queried via comptroller.closeFactorMantissa()
:
// close factor
function getCloseFactor() external view returns (uint) {
return comptroller.closeFactorMantissa();
}
Liquidation Incentive
What is "liquidation incentive"? Quote from doc:
The additional collateral given to liquidators as an incentive to perform liquidation of underwater accounts. A portion of this is given to the collateral cToken reserves as determined by the seize share. The seize share is assumed to be 0 if the cToken does not have a
protocolSeizeShareMantissa
constant. For example, if the liquidation incentive is 1.08, and the collateralโs seize share is 1.028, liquidators receive an extra 5.2% of the borrowerโs collateral for every unit they close, and the remaining 2.8% is added to the cTokenโs reserves.
Liquidation incentive can be queried via comptroller.liquidationIncentiveMantissa()
:
// liquidation incentive
function getLiquidationIncentive() external view returns (uint) {
return comptroller.liquidationIncentiveMantissa();
}
Liquidate
To compute the amount of the collateral that we can liquidate, call comptroller .liquidateCalculateSeizeTokens()
:
// get amount of collateral to be liquidated
function getAmountToBeLiquidated(
address _cTokenBorrowed,
address _cTokenCollateral,
uint _actualRepayAmount
) external view returns (uint) {
/*
* Get the exchange rate and calculate the number of collateral tokens to seize:
* seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral
* seizeTokens = seizeAmount / exchangeRate
* = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate)
*/
(uint error, uint cTokenCollateralAmount) = comptroller
.liquidateCalculateSeizeTokens(
_cTokenBorrowed,
_cTokenCollateral,
_actualRepayAmount
);
require(error == 0, "error");
return cTokenCollateralAmount;
}
Finally let's implement the high-level liquidate()
function:
// liquidate
function liquidate(
address _borrower,
uint _repayAmount,
address _cTokenCollateral
) external {
// Transfer the fund from user's wallet to this contract
tokenBorrow.transferFrom(msg.sender, address(this), _repayAmount);
// Approve the cTokenBorrow contract to spend this fund
tokenBorrow.approve(address(cTokenBorrow), _repayAmount);
// Call cTokenBorrow.liquidateBorrow() to liquidate
require(
cTokenBorrow.liquidateBorrow(_borrower, _repayAmount, _cTokenCollateral) == 0,
"liquidate failed"
);
}
Test
We supply WBTC and borrow DAI.
Test case:
it("should liquidate", async () => {
// used for debugging
let tx
let snap
// supply
await tokenSupply.approve(testCompound.address, SUPPLY_AMOUNT, {
from: SUPPLY_WHALE
})
tx = await testCompound.supply(SUPPLY_AMOUNT, {
from: SUPPLY_WHALE,
})
snap = await snapshot(testCompound, liquidator)
console.log(`--- supplied ---`)
console.log(`col factor: ${snap.colFactor} %`)
console.log(`supplied: ${snap.supplied}`)
// enter market
tx = await testCompound.enterMarket({
from: accounts[0]
})
// borrow
const { liquidity } = await testCompound.getAccountLiquidity()
const price = await testCompound.getPriceFeed(C_TOKEN_BORROW)
const maxBorrow = liquidity.mul(pow(10, BORROW_DECIMALS)).div(price)
// NOTE: tweak borrow amount if borrow fails
const borrowAmount = maxBorrow.mul(new BN(9997)).div(new BN(10000))
console.log(`--- entered market ---`)
console.log(`liquidity: $ ${liquidity.div(pow(10, 18))}`)
console.log(`price: $ ${price.div(pow(10, 18))}`)
console.log(`max borrow: ${maxBorrow.div(pow(10, 18))}`)
console.log(`borrow amount: ${borrowAmount.div(pow(10, 18))}`)
tx = await testCompound.borrow(borrowAmount, {
from: accounts[0]
})
snap = await snapshot(testCompound, liquidator)
console.log(`--- borrowed ---`)
console.log(`liquidity: $ ${snap.liquidity}`)
console.log(`borrowed: ${snap.borrowed}`)
// accrue interest on borrow
const block = await web3.eth.getBlockNumber()
// NOTE: tweak this to increase borrowed amount
await time.advanceBlockTo(block + 10000)
// send any tx to Compound to update liquidity and shortfall
await testCompound.getBorrowBalance()
snap = await snapshot(testCompound, liquidator)
console.log(`--- after some blocks... ---`)
console.log(`liquidity: $ ${snap.liquidity}`)
console.log(`shortfall: $ ${snap.shortfall}`)
console.log(`borrowed: ${snap.borrowed}`)
// liquidate
const closeFactor = await liquidator.getCloseFactor()
const repayAmount = (await testCompound.getBorrowBalance.call()).mul(closeFactor).div(pow(10, 18))
const liqBal = await tokenBorrow.balanceOf(LIQUIDATOR)
console.log(`liquidator balance: ${liqBal.div(pow(10, BORROW_DECIMALS))}`)
assert(liqBal.gte(repayAmount), "bal < repay")
const amountToBeLiquidated = await liquidator.getAmountToBeLiquidated(C_TOKEN_BORROW, C_TOKEN_SUPPLY, repayAmount)
console.log(
`amount to be liquidated (cToken collateral): ${amountToBeLiquidated.div(pow(10, SUPPLY_DECIMALS - 2)) / 100}`
)
await tokenBorrow.approve(liquidator.address, repayAmount, {
from: LIQUIDATOR
})
tx = await liquidator.liquidate(testCompound.address, repayAmount, C_TOKEN_SUPPLY, {
from: LIQUIDATOR,
})
snap = await snapshot(testCompound, liquidator)
console.log(`--- liquidated ---`)
console.log(`close factor: ${snap.closeFactor} %`)
console.log(`liquidation incentive: ${snap.incentive}`)
console.log(`supplied: ${snap.supplied}`)
console.log(`liquidity: $ ${snap.liquidity}`)
console.log(`shortfall: $ ${snap.shortfall}`)
console.log(`borrowed: ${snap.borrowed}`)
console.log(`liquidated: ${snap.liquidated}`)
/* memo
c = 31572
r = c * 0.65 * 0.5
b = 1
i = 1.08
r * i * b / c
*/
})
Last updated
Was this helpful?