feat: added ability to liquidate the full loan

This commit is contained in:
The3D 2021-06-28 13:47:44 +02:00
parent 5a33536b5f
commit 96cb9b3d50
3 changed files with 45 additions and 42 deletions

View File

@ -18,9 +18,10 @@ import {PercentageMath} from '../libraries/math/PercentageMath.sol';
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
import {Errors} from '../libraries/helpers/Errors.sol';
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
import {DataTypes} from '../libraries/types/DataTypes.sol';
import {LendingPoolStorage} from './LendingPoolStorage.sol';
import "hardhat/console.sol";
/**
* @title LendingPoolCollateralManager contract
* @author Aave
@ -39,7 +40,10 @@ contract LendingPoolCollateralManager is
using PercentageMath for uint256;
using ReserveLogic for DataTypes.ReserveCache;
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
uint256 public constant STD_LIQUIDATION_CLOSE_FACTOR = 5000;
uint256 public constant MAX_LIQUIDATION_CLOSE_FACTOR = 10000;
uint256 public constant CLOSE_FACTOR_HF_THRESHOLD = 0.95 * 1e18;
struct LiquidationCallLocalVars {
uint256 userCollateralBalance;
@ -54,6 +58,7 @@ contract LendingPoolCollateralManager is
uint256 debtAmountNeeded;
uint256 healthFactor;
uint256 liquidatorPreviousATokenBalance;
uint256 closeFactor;
IAToken collateralAtoken;
IPriceOracleGetter oracle;
bool isCollateralEnabled;
@ -96,14 +101,10 @@ contract LendingPoolCollateralManager is
LiquidationCallLocalVars memory vars;
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
vars.oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
vars.oracle = IPriceOracleGetter(_addressesProvider.getPriceOracle());
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve,
debtReserveCache,
vars.userStableDebt.add(vars.userVariableDebt),
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
user,
_reserves,
userConfig,
@ -112,6 +113,14 @@ contract LendingPoolCollateralManager is
address(vars.oracle)
);
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
collateralReserve,
debtReserveCache,
vars.userStableDebt.add(vars.userVariableDebt),
userConfig,
vars.healthFactor
);
if (Errors.CollateralManagerErrors(vars.errorCode) != Errors.CollateralManagerErrors.NO_ERROR) {
return (vars.errorCode, vars.errorMsg);
}
@ -120,14 +129,24 @@ contract LendingPoolCollateralManager is
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
vars.closeFactor = vars.healthFactor > CLOSE_FACTOR_HF_THRESHOLD
? STD_LIQUIDATION_CLOSE_FACTOR
: MAX_LIQUIDATION_CLOSE_FACTOR;
vars.maxLiquidatableDebt = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
LIQUIDATION_CLOSE_FACTOR_PERCENT
vars.closeFactor
);
console.log("close factor is ", vars.closeFactor);
vars.actualDebtToLiquidate = debtToCover > vars.maxLiquidatableDebt
? vars.maxLiquidatableDebt
: debtToCover;
console.log("actual debt to liquidate: ", vars.actualDebtToLiquidate);
console.log("Debt to cover", debtToCover);
(
vars.maxCollateralToLiquidate,
vars.debtAmountNeeded
@ -163,25 +182,30 @@ contract LendingPoolCollateralManager is
}
debtReserve.updateState(debtReserveCache);
console.log("Updated debt reserve state");
if (vars.userVariableDebt >= vars.actualDebtToLiquidate) {
console.log("Burning variable debt");
IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user,
vars.actualDebtToLiquidate,
debtReserveCache.nextVariableBorrowIndex
);
console.log("variable debt burned");
debtReserveCache.refreshDebt(0, 0, 0, vars.actualDebtToLiquidate);
debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0);
} else {
// If the user doesn't have variable debt, no need to try to burn variable debt tokens
if (vars.userVariableDebt > 0) {
console.log("Burning variable debt");
IVariableDebtToken(debtReserveCache.variableDebtTokenAddress).burn(
user,
vars.userVariableDebt,
debtReserveCache.nextVariableBorrowIndex
);
}
IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn(
console.log("variable debt burned, burning stable");
IStableDebtToken(debtReserveCache.stableDebtTokenAddress).burn(
user,
vars.actualDebtToLiquidate.sub(vars.userVariableDebt)
);
@ -191,6 +215,7 @@ contract LendingPoolCollateralManager is
0,
vars.userVariableDebt
);
console.log("stable debt burned");
debtReserve.updateInterestRates(debtReserveCache, debtAsset, vars.actualDebtToLiquidate, 0);
}
@ -230,6 +255,7 @@ contract LendingPoolCollateralManager is
emit ReserveUsedAsCollateralDisabled(collateralAsset, user);
}
console.log("transferring debt");
// Transfers the debt asset being repaid to the aToken, where the liquidity is kept
IERC20(debtAsset).safeTransferFrom(
msg.sender,
@ -237,6 +263,8 @@ contract LendingPoolCollateralManager is
vars.actualDebtToLiquidate
);
console.log("debt transferred");
emit LiquidationCall(
collateralAsset,
debtAsset,

View File

@ -417,7 +417,6 @@ library ValidationLogic {
}
struct ValidateLiquidationCallLocalVars {
uint256 healthFactor;
bool collateralReserveActive;
bool collateralReservePaused;
bool principalReserveActive;
@ -431,23 +430,15 @@ library ValidationLogic {
* @param principalReserveCache The cached reserve data of the principal
* @param userConfig The user configuration
* @param totalDebt Total debt balance of the user
* @param user The address of the user being liquidated
* @param reservesData The mapping of the reserves data
* @param userConfig The user configuration mapping
* @param reserves The list of the reserves
* @param reservesCount The number of reserves in the list
* @param oracle The address of the price oracle
* @param healthFactor The health factor of the loan
**/
function validateLiquidationCall(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory principalReserveCache,
uint256 totalDebt,
address user,
mapping(address => DataTypes.ReserveData) storage reservesData,
DataTypes.UserConfigurationMap storage userConfig,
mapping(uint256 => address) storage reserves,
uint256 reservesCount,
address oracle
DataTypes.UserConfigurationMap memory userConfig,
uint256 healthFactor
) internal view returns (uint256, string memory) {
ValidateLiquidationCallLocalVars memory vars;
@ -468,17 +459,8 @@ library ValidationLogic {
if (vars.collateralReservePaused || vars.principalReservePaused) {
return (uint256(Errors.CollateralManagerErrors.PAUSED_RESERVE), Errors.VL_RESERVE_PAUSED);
}
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
user,
reservesData,
userConfig,
reserves,
reservesCount,
oracle
);
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
if (healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
return (
uint256(Errors.CollateralManagerErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
Errors.LPCM_HEALTH_FACTOR_NOT_BELOW_THRESHOLD

View File

@ -627,12 +627,9 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
const liquidator = users[3];
const borrower = users[1];
const liquidatorWethBalanceBefore = await weth.balanceOf(liquidator.address);
const collateralPrice = await oracle.getAssetPrice(weth.address);
const principalPrice = await oracle.getAssetPrice(dai.address);
const daiReserveDataBefore = await helpersContract.getReserveData(dai.address);
const ethReserveDataBefore = await helpersContract.getReserveData(weth.address);
const userReserveDataBefore = await getUserData(
pool,
helpersContract,
@ -646,7 +643,7 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
const principalDecimals = (
await helpersContract.getReserveConfigurationData(dai.address)
).decimals.toString();
const amountToLiquidate = userReserveDataBefore.currentStableDebt.div(2).toFixed(0);
const amountToLiquidate = userReserveDataBefore.currentStableDebt.toFixed(0);
const extraAmount = new BigNumber(amountToLiquidate).times('1.15').toFixed(0);
const expectedCollateralLiquidated = new BigNumber(principalPrice.toString())
@ -660,10 +657,6 @@ makeSuite('Uniswap adapters', (testEnv: TestEnv) => {
.div(100)
.decimalPlaces(0, BigNumber.ROUND_DOWN);
const flashLoanDebt = new BigNumber(amountToLiquidate.toString())
.multipliedBy(1.0009)
.toFixed(0);
// Set how much ETH will be sold and swapped for DAI at Uniswap mock
await (
await mockUniswapRouter.setAmountToSwap(