diff --git a/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol b/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol index e530a9a4..540b2a14 100644 --- a/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol +++ b/contracts/protocol/lendingpool/LendingPoolCollateralManager.sol @@ -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, diff --git a/contracts/protocol/libraries/logic/ValidationLogic.sol b/contracts/protocol/libraries/logic/ValidationLogic.sol index 9c7ea35a..795f01fb 100644 --- a/contracts/protocol/libraries/logic/ValidationLogic.sol +++ b/contracts/protocol/libraries/logic/ValidationLogic.sol @@ -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 diff --git a/test-suites/test-aave/uniswapAdapters.flashLiquidation.spec.ts b/test-suites/test-aave/uniswapAdapters.flashLiquidation.spec.ts index cc161b4b..836b7b19 100644 --- a/test-suites/test-aave/uniswapAdapters.flashLiquidation.spec.ts +++ b/test-suites/test-aave/uniswapAdapters.flashLiquidation.spec.ts @@ -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(