mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
Merge branch 'fix/35-isActive-liquidations' into 'master'
Fixes #35 Closes #35 See merge request aave-tech/protocol-v2!43
This commit is contained in:
commit
155d249abf
|
@ -430,6 +430,7 @@ contract LendingPool is VersionedInitializable, ILendingPool {
|
|||
uint256 purchaseAmount,
|
||||
bool receiveAToken
|
||||
) external override {
|
||||
|
||||
address liquidationManager = _addressesProvider.getLendingPoolLiquidationManager();
|
||||
|
||||
//solium-disable-next-line
|
||||
|
|
|
@ -21,6 +21,7 @@ import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
|||
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/SafeERC20.sol';
|
||||
import {ISwapAdapter} from '../interfaces/ISwapAdapter.sol';
|
||||
import {Errors} from '../libraries/helpers/Errors.sol';
|
||||
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
|
||||
|
||||
/**
|
||||
* @title LendingPoolLiquidationManager contract
|
||||
|
@ -89,15 +90,6 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 swappedCollateralAmount
|
||||
);
|
||||
|
||||
enum LiquidationErrors {
|
||||
NO_ERROR,
|
||||
NO_COLLATERAL_AVAILABLE,
|
||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||
CURRRENCY_NOT_BORROWED,
|
||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||
NOT_ENOUGH_LIQUIDITY
|
||||
}
|
||||
|
||||
struct LiquidationCallLocalVars {
|
||||
uint256 userCollateralBalance;
|
||||
uint256 userStableDebt;
|
||||
|
@ -113,6 +105,9 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 healthFactor;
|
||||
IAToken collateralAtoken;
|
||||
bool isCollateralEnabled;
|
||||
address principalAToken;
|
||||
uint256 errorCode;
|
||||
string errorMsg;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,8 +134,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
uint256 purchaseAmount,
|
||||
bool receiveAToken
|
||||
) external returns (uint256, string memory) {
|
||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||
|
||||
LiquidationCallLocalVars memory vars;
|
||||
|
@ -153,43 +148,29 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
addressesProvider.getPriceOracle()
|
||||
);
|
||||
|
||||
if (vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
return (
|
||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||
|
||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||
|
||||
vars.isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!vars.isCollateralEnabled) {
|
||||
return (
|
||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
|
||||
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
||||
user,
|
||||
principalReserve
|
||||
);
|
||||
|
||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateLiquidationCall(
|
||||
collateralReserve,
|
||||
principalReserve,
|
||||
userConfig,
|
||||
vars.healthFactor,
|
||||
vars.userStableDebt,
|
||||
vars.userVariableDebt
|
||||
);
|
||||
|
||||
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||
return (vars.errorCode, vars.errorMsg);
|
||||
}
|
||||
|
||||
//all clear - calculate the max principal amount that can be liquidated
|
||||
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
||||
|
||||
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
||||
|
||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
||||
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
||||
);
|
||||
|
@ -225,7 +206,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
);
|
||||
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
||||
return (
|
||||
uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||
uint256(Errors.LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
||||
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
||||
);
|
||||
}
|
||||
|
@ -292,7 +273,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
receiveAToken
|
||||
);
|
||||
|
||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -315,9 +296,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
address receiver,
|
||||
bytes calldata params
|
||||
) external returns (uint256, string memory) {
|
||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
||||
|
||||
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
||||
UserConfiguration.Map storage userConfig = usersConfig[user];
|
||||
|
||||
LiquidationCallLocalVars memory vars;
|
||||
|
@ -330,36 +310,20 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
addressesProvider.getPriceOracle()
|
||||
);
|
||||
|
||||
if (
|
||||
msg.sender != user && vars.healthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||
) {
|
||||
return (
|
||||
uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
if (msg.sender != user) {
|
||||
vars.isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!vars.isCollateralEnabled) {
|
||||
return (
|
||||
uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
||||
|
||||
if (vars.userStableDebt == 0 && vars.userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateRepayWithCollateral(
|
||||
collateralReserve,
|
||||
debtReserve,
|
||||
userConfig,
|
||||
user,
|
||||
vars.healthFactor,
|
||||
vars.userStableDebt,
|
||||
vars.userVariableDebt
|
||||
);
|
||||
|
||||
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
||||
return (vars.errorCode, vars.errorMsg);
|
||||
}
|
||||
|
||||
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt);
|
||||
|
@ -398,7 +362,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
||||
}
|
||||
|
||||
address principalAToken = debtReserve.aTokenAddress;
|
||||
vars.principalAToken = debtReserve.aTokenAddress;
|
||||
|
||||
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
||||
ISwapAdapter(receiver).executeOperation(
|
||||
|
@ -411,8 +375,8 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
|
||||
//updating debt reserve
|
||||
debtReserve.updateCumulativeIndexesAndTimestamp();
|
||||
debtReserve.updateInterestRates(principal, principalAToken, vars.actualAmountToLiquidate, 0);
|
||||
IERC20(principal).transferFrom(receiver, principalAToken, vars.actualAmountToLiquidate);
|
||||
debtReserve.updateInterestRates(principal, vars.principalAToken, vars.actualAmountToLiquidate, 0);
|
||||
IERC20(principal).transferFrom(receiver, vars.principalAToken, vars.actualAmountToLiquidate);
|
||||
|
||||
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
||||
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(
|
||||
|
@ -444,7 +408,7 @@ contract LendingPoolLiquidationManager is VersionedInitializable {
|
|||
vars.maxCollateralToLiquidate
|
||||
);
|
||||
|
||||
return (uint256(LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
struct AvailableCollateralToLiquidateLocalVars {
|
||||
|
|
|
@ -74,4 +74,14 @@ library Errors {
|
|||
string public constant MULTIPLICATION_OVERFLOW = '44';
|
||||
string public constant ADDITION_OVERFLOW = '45';
|
||||
string public constant DIVISION_BY_ZERO = '46';
|
||||
|
||||
enum LiquidationErrors {
|
||||
NO_ERROR,
|
||||
NO_COLLATERAL_AVAILABLE,
|
||||
COLLATERAL_CANNOT_BE_LIQUIDATED,
|
||||
CURRRENCY_NOT_BORROWED,
|
||||
HEALTH_FACTOR_ABOVE_THRESHOLD,
|
||||
NOT_ENOUGH_LIQUIDITY,
|
||||
NO_ACTIVE_RESERVE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import {ReserveConfiguration} from '../configuration/ReserveConfiguration.sol';
|
|||
import {UserConfiguration} from '../configuration/UserConfiguration.sol';
|
||||
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
||||
import {Errors} from '../helpers/Errors.sol';
|
||||
import {Helpers} from '../helpers/Helpers.sol';
|
||||
|
||||
/**
|
||||
* @title ReserveLogic library
|
||||
|
@ -329,4 +330,116 @@ library ValidationLogic {
|
|||
require(premium > 0, Errors.REQUESTED_AMOUNT_TOO_SMALL);
|
||||
require(mode <= uint256(ReserveLogic.InterestRateMode.VARIABLE), Errors.INVALID_FLASHLOAN_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates the liquidationCall() action
|
||||
* @param collateralReserve The reserve data of the collateral
|
||||
* @param principalReserve The reserve data of the principal
|
||||
* @param userConfig The user configuration
|
||||
* @param userHealthFactor The user's health factor
|
||||
* @param userStableDebt Total stable debt balance of the user
|
||||
* @param userVariableDebt Total variable debt balance of the user
|
||||
**/
|
||||
function validateLiquidationCall(
|
||||
ReserveLogic.ReserveData storage collateralReserve,
|
||||
ReserveLogic.ReserveData storage principalReserve,
|
||||
UserConfiguration.Map storage userConfig,
|
||||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
}
|
||||
|
||||
if (userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!isCollateralEnabled) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
|
||||
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Validates the repayWithCollateral() action
|
||||
* @param collateralReserve The reserve data of the collateral
|
||||
* @param principalReserve The reserve data of the principal
|
||||
* @param userConfig The user configuration
|
||||
* @param user The address of the user
|
||||
* @param userHealthFactor The user's health factor
|
||||
* @param userStableDebt Total stable debt balance of the user
|
||||
* @param userVariableDebt Total variable debt balance of the user
|
||||
**/
|
||||
function validateRepayWithCollateral(
|
||||
ReserveLogic.ReserveData storage collateralReserve,
|
||||
ReserveLogic.ReserveData storage principalReserve,
|
||||
UserConfiguration.Map storage userConfig,
|
||||
address user,
|
||||
uint256 userHealthFactor,
|
||||
uint256 userStableDebt,
|
||||
uint256 userVariableDebt
|
||||
) internal view returns(uint256, string memory) {
|
||||
if ( !collateralReserve.configuration.getActive() || !principalReserve.configuration.getActive()) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.NO_ACTIVE_RESERVE),
|
||||
Errors.NO_ACTIVE_RESERVE
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
msg.sender != user && userHealthFactor >= GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD
|
||||
) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
|
||||
Errors.HEALTH_FACTOR_NOT_BELOW_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
if (msg.sender != user) {
|
||||
bool isCollateralEnabled =
|
||||
collateralReserve.configuration.getLiquidationThreshold() > 0 &&
|
||||
userConfig.isUsingAsCollateral(collateralReserve.id);
|
||||
|
||||
//if collateral isn't enabled as collateral by user, it cannot be liquidated
|
||||
if (!isCollateralEnabled) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
|
||||
Errors.COLLATERAL_CANNOT_BE_LIQUIDATED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (userStableDebt == 0 && userVariableDebt == 0) {
|
||||
return (
|
||||
uint256(Errors.LiquidationErrors.CURRRENCY_NOT_BORROWED),
|
||||
Errors.SPECIFIED_CURRENCY_NOT_BORROWED_BY_USER
|
||||
);
|
||||
}
|
||||
|
||||
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {makeSuite} from './helpers/make-suite';
|
|||
import {ProtocolErrors, RateMode} from '../helpers/types';
|
||||
import {calcExpectedStableDebtTokenBalance} from './helpers/utils/calculations';
|
||||
import {getUserData} from './helpers/utils/helpers';
|
||||
import {parseEther} from 'ethers/lib/utils';
|
||||
|
||||
const chai = require('chai');
|
||||
|
||||
|
@ -23,6 +24,26 @@ makeSuite('LendingPool liquidation - liquidator receiving the underlying asset',
|
|||
BigNumber.config({DECIMAL_PLACES: 20, ROUNDING_MODE: BigNumber.ROUND_HALF_UP});
|
||||
});
|
||||
|
||||
it("It's not possible to liquidate on a non-active collateral or a non active principal", async () => {
|
||||
const {configurator, weth, pool, users, dai} = testEnv;
|
||||
const user = users[1];
|
||||
await configurator.deactivateReserve(weth.address);
|
||||
|
||||
await expect(
|
||||
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(weth.address);
|
||||
|
||||
await configurator.deactivateReserve(dai.address);
|
||||
|
||||
await expect(
|
||||
pool.liquidationCall(weth.address, dai.address, user.address, parseEther('1000'), false)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(dai.address);
|
||||
});
|
||||
|
||||
it('LIQUIDATION - Deposits WETH, borrows DAI', async () => {
|
||||
const {dai, weth, users, pool, oracle} = testEnv;
|
||||
const depositor = users[0];
|
||||
|
|
|
@ -39,6 +39,44 @@ export const expectRepayWithCollateralEvent = (
|
|||
};
|
||||
|
||||
makeSuite('LendingPool. repayWithCollateral()', (testEnv: TestEnv) => {
|
||||
it("It's not possible to repayWithCollateral() on a non-active collateral or a non active principal", async () => {
|
||||
const {configurator, weth, pool, users, dai, mockSwapAdapter} = testEnv;
|
||||
const user = users[1];
|
||||
await configurator.deactivateReserve(weth.address);
|
||||
|
||||
await expect(
|
||||
pool
|
||||
.connect(user.signer)
|
||||
.repayWithCollateral(
|
||||
weth.address,
|
||||
dai.address,
|
||||
user.address,
|
||||
parseEther('100'),
|
||||
mockSwapAdapter.address,
|
||||
'0x'
|
||||
)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(weth.address);
|
||||
|
||||
await configurator.deactivateReserve(dai.address);
|
||||
|
||||
await expect(
|
||||
pool
|
||||
.connect(user.signer)
|
||||
.repayWithCollateral(
|
||||
weth.address,
|
||||
dai.address,
|
||||
user.address,
|
||||
parseEther('100'),
|
||||
mockSwapAdapter.address,
|
||||
'0x'
|
||||
)
|
||||
).to.be.revertedWith('2');
|
||||
|
||||
await configurator.activateReserve(dai.address);
|
||||
});
|
||||
|
||||
it('User 1 provides some liquidity for others to borrow', async () => {
|
||||
const {pool, weth, dai, usdc, deployer} = testEnv;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user