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

View File

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

View File

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