mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
602 lines
21 KiB
Solidity
602 lines
21 KiB
Solidity
// SPDX-License-Identifier: agpl-3.0
|
|
pragma solidity ^0.6.8;
|
|
|
|
import {SafeMath} from '@openzeppelin/contracts/math/SafeMath.sol';
|
|
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
|
|
import {Pausable} from '@openzeppelin/contracts/utils/Pausable.sol';
|
|
import {
|
|
VersionedInitializable
|
|
} from '../libraries/openzeppelin-upgradeability/VersionedInitializable.sol';
|
|
import {LendingPoolAddressesProvider} from '../configuration/LendingPoolAddressesProvider.sol';
|
|
import {IAToken} from '../tokenization/interfaces/IAToken.sol';
|
|
import {IStableDebtToken} from '../tokenization/interfaces/IStableDebtToken.sol';
|
|
import {IVariableDebtToken} from '../tokenization/interfaces/IVariableDebtToken.sol';
|
|
import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol';
|
|
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
|
|
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
|
|
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
|
|
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
|
|
import {Helpers} from '../libraries/helpers/Helpers.sol';
|
|
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
|
|
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
|
|
* @author Aave
|
|
* @notice Implements the liquidation function.
|
|
* @dev LendingPoolLiquidationManager inherits Pausable from OpenZeppelin to have the same storage layout as LendingPool
|
|
**/
|
|
contract LendingPoolLiquidationManager is VersionedInitializable, Pausable {
|
|
using SafeERC20 for IERC20;
|
|
using SafeMath for uint256;
|
|
using WadRayMath for uint256;
|
|
using PercentageMath for uint256;
|
|
using ReserveLogic for ReserveLogic.ReserveData;
|
|
using ReserveConfiguration for ReserveConfiguration.Map;
|
|
using UserConfiguration for UserConfiguration.Map;
|
|
|
|
// IMPORTANT The storage layout of the LendingPool is reproduced here because this contract
|
|
// is gonna be used through DELEGATECALL
|
|
|
|
LendingPoolAddressesProvider internal addressesProvider;
|
|
|
|
mapping(address => ReserveLogic.ReserveData) internal reserves;
|
|
mapping(address => UserConfiguration.Map) internal usersConfig;
|
|
mapping(address => mapping(address => mapping(address => uint256))) internal _borrowAllowance;
|
|
|
|
address[] internal reservesList;
|
|
|
|
bool internal _flashLiquidationLocked;
|
|
|
|
uint256 internal constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 5000;
|
|
|
|
/**
|
|
* @dev emitted when a borrower is liquidated
|
|
* @param collateral the address of the collateral being liquidated
|
|
* @param principal the address of the reserve
|
|
* @param user the address of the user being liquidated
|
|
* @param purchaseAmount the total amount liquidated
|
|
* @param liquidatedCollateralAmount the amount of collateral being liquidated
|
|
* @param liquidator the address of the liquidator
|
|
* @param receiveAToken true if the liquidator wants to receive aTokens, false otherwise
|
|
**/
|
|
event LiquidationCall(
|
|
address indexed collateral,
|
|
address indexed principal,
|
|
address indexed user,
|
|
uint256 purchaseAmount,
|
|
uint256 liquidatedCollateralAmount,
|
|
address liquidator,
|
|
bool receiveAToken
|
|
);
|
|
|
|
/**
|
|
@dev emitted when a borrower/liquidator repays with the borrower's collateral
|
|
@param collateral the address of the collateral being swapped to repay
|
|
@param principal the address of the reserve of the debt
|
|
@param user the borrower's address
|
|
@param liquidator the address of the liquidator, same as the one of the borrower on self-repayment
|
|
@param principalAmount the amount of the debt finally covered
|
|
@param swappedCollateralAmount the amount of collateral finally swapped
|
|
*/
|
|
event RepaidWithCollateral(
|
|
address indexed collateral,
|
|
address indexed principal,
|
|
address indexed user,
|
|
address liquidator,
|
|
uint256 principalAmount,
|
|
uint256 swappedCollateralAmount
|
|
);
|
|
|
|
struct LiquidationCallLocalVars {
|
|
uint256 userCollateralBalance;
|
|
uint256 userStableDebt;
|
|
uint256 userVariableDebt;
|
|
uint256 maxPrincipalAmountToLiquidate;
|
|
uint256 actualAmountToLiquidate;
|
|
uint256 liquidationRatio;
|
|
uint256 maxAmountCollateralToLiquidate;
|
|
ReserveLogic.InterestRateMode borrowRateMode;
|
|
uint256 userStableRate;
|
|
uint256 maxCollateralToLiquidate;
|
|
uint256 principalAmountNeeded;
|
|
uint256 healthFactor;
|
|
IAToken collateralAtoken;
|
|
bool isCollateralEnabled;
|
|
address principalAToken;
|
|
uint256 errorCode;
|
|
string errorMsg;
|
|
}
|
|
|
|
struct SwapLiquidityLocalVars {
|
|
uint256 healthFactor;
|
|
uint256 amountToReceive;
|
|
uint256 userBalanceBefore;
|
|
IAToken fromReserveAToken;
|
|
IAToken toReserveAToken;
|
|
uint256 errorCode;
|
|
string errorMsg;
|
|
}
|
|
|
|
struct AvailableCollateralToLiquidateLocalVars {
|
|
uint256 userCompoundedBorrowBalance;
|
|
uint256 liquidationBonus;
|
|
uint256 collateralPrice;
|
|
uint256 principalCurrencyPrice;
|
|
uint256 maxAmountCollateralToLiquidate;
|
|
uint256 principalDecimals;
|
|
uint256 collateralDecimals;
|
|
}
|
|
|
|
/**
|
|
* @dev as the contract extends the VersionedInitializable contract to match the state
|
|
* of the LendingPool contract, the getRevision() function is needed.
|
|
*/
|
|
function getRevision() internal override pure returns (uint256) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @dev users can invoke this function to liquidate an undercollateralized position.
|
|
* @param collateral the address of the collateral to liquidated
|
|
* @param principal the address of the principal reserve
|
|
* @param user the address of the borrower
|
|
* @param purchaseAmount the amount of principal that the liquidator wants to repay
|
|
* @param receiveAToken true if the liquidators wants to receive the aTokens, false if
|
|
* he wants to receive the underlying asset directly
|
|
**/
|
|
function liquidationCall(
|
|
address collateral,
|
|
address principal,
|
|
address user,
|
|
uint256 purchaseAmount,
|
|
bool receiveAToken
|
|
) external returns (uint256, string memory) {
|
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
|
ReserveLogic.ReserveData storage principalReserve = reserves[principal];
|
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
|
|
|
LiquidationCallLocalVars memory vars;
|
|
|
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
|
user,
|
|
reserves,
|
|
usersConfig[user],
|
|
reservesList,
|
|
addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
//if the user hasn't borrowed the specific currency defined by asset, it cannot be liquidated
|
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(
|
|
user,
|
|
principalReserve
|
|
);
|
|
|
|
(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);
|
|
}
|
|
|
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
|
|
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
|
|
|
vars.maxPrincipalAmountToLiquidate = vars.userStableDebt.add(vars.userVariableDebt).percentMul(
|
|
LIQUIDATION_CLOSE_FACTOR_PERCENT
|
|
);
|
|
|
|
vars.actualAmountToLiquidate = purchaseAmount > vars.maxPrincipalAmountToLiquidate
|
|
? vars.maxPrincipalAmountToLiquidate
|
|
: purchaseAmount;
|
|
|
|
(
|
|
vars.maxCollateralToLiquidate,
|
|
vars.principalAmountNeeded
|
|
) = calculateAvailableCollateralToLiquidate(
|
|
collateralReserve,
|
|
principalReserve,
|
|
collateral,
|
|
principal,
|
|
vars.actualAmountToLiquidate,
|
|
vars.userCollateralBalance
|
|
);
|
|
|
|
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
|
|
//of collateral to cover the actual amount that is being liquidated, hence we liquidate
|
|
//a smaller amount
|
|
|
|
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) {
|
|
vars.actualAmountToLiquidate = vars.principalAmountNeeded;
|
|
}
|
|
|
|
//if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
|
|
if (!receiveAToken) {
|
|
uint256 currentAvailableCollateral = IERC20(collateral).balanceOf(
|
|
address(vars.collateralAtoken)
|
|
);
|
|
if (currentAvailableCollateral < vars.maxCollateralToLiquidate) {
|
|
return (
|
|
uint256(Errors.LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
|
|
Errors.NOT_ENOUGH_LIQUIDITY_TO_LIQUIDATE
|
|
);
|
|
}
|
|
}
|
|
|
|
//update the principal reserve
|
|
principalReserve.updateCumulativeIndexesAndTimestamp();
|
|
principalReserve.updateInterestRates(
|
|
principal,
|
|
principalReserve.aTokenAddress,
|
|
vars.actualAmountToLiquidate,
|
|
0
|
|
);
|
|
|
|
if (vars.userVariableDebt >= vars.actualAmountToLiquidate) {
|
|
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(
|
|
user,
|
|
vars.actualAmountToLiquidate
|
|
);
|
|
} else {
|
|
IVariableDebtToken(principalReserve.variableDebtTokenAddress).burn(
|
|
user,
|
|
vars.userVariableDebt
|
|
);
|
|
IStableDebtToken(principalReserve.stableDebtTokenAddress).burn(
|
|
user,
|
|
vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
|
|
);
|
|
}
|
|
|
|
//if liquidator reclaims the aToken, he receives the equivalent atoken amount
|
|
if (receiveAToken) {
|
|
vars.collateralAtoken.transferOnLiquidation(user, msg.sender, vars.maxCollateralToLiquidate);
|
|
} else {
|
|
//otherwise receives the underlying asset
|
|
|
|
//updating collateral reserve
|
|
collateralReserve.updateCumulativeIndexesAndTimestamp();
|
|
collateralReserve.updateInterestRates(
|
|
collateral,
|
|
address(vars.collateralAtoken),
|
|
0,
|
|
vars.maxCollateralToLiquidate
|
|
);
|
|
|
|
//burn the equivalent amount of atoken
|
|
vars.collateralAtoken.burn(
|
|
user,
|
|
msg.sender,
|
|
vars.maxCollateralToLiquidate,
|
|
collateralReserve.liquidityIndex
|
|
);
|
|
}
|
|
|
|
//transfers the principal currency to the aToken
|
|
IERC20(principal).safeTransferFrom(
|
|
msg.sender,
|
|
principalReserve.aTokenAddress,
|
|
vars.actualAmountToLiquidate
|
|
);
|
|
|
|
emit LiquidationCall(
|
|
collateral,
|
|
principal,
|
|
user,
|
|
vars.actualAmountToLiquidate,
|
|
vars.maxCollateralToLiquidate,
|
|
msg.sender,
|
|
receiveAToken
|
|
);
|
|
|
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
|
}
|
|
|
|
/**
|
|
* @dev flashes the underlying collateral on an user to swap for the owed asset and repay
|
|
* - Both the owner of the position and other liquidators can execute it.
|
|
* - The owner can repay with his collateral at any point, no matter the health factor.
|
|
* - Other liquidators can only use this function below 1 HF. To liquidate 50% of the debt > HF 0.98 or the whole below.
|
|
* @param collateral The address of the collateral asset.
|
|
* @param principal The address of the owed asset.
|
|
* @param user Address of the borrower.
|
|
* @param principalAmount Amount of the debt to repay.
|
|
* @param receiver Address of the contract receiving the collateral to swap.
|
|
* @param params Variadic bytes param to pass with extra information to the receiver
|
|
**/
|
|
function repayWithCollateral(
|
|
address collateral,
|
|
address principal,
|
|
address user,
|
|
uint256 principalAmount,
|
|
address receiver,
|
|
bytes calldata params
|
|
) external returns (uint256, string memory) {
|
|
ReserveLogic.ReserveData storage collateralReserve = reserves[collateral];
|
|
ReserveLogic.ReserveData storage debtReserve = reserves[principal];
|
|
UserConfiguration.Map storage userConfig = usersConfig[user];
|
|
|
|
LiquidationCallLocalVars memory vars;
|
|
|
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
|
user,
|
|
reserves,
|
|
usersConfig[user],
|
|
reservesList,
|
|
addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
(vars.userStableDebt, vars.userVariableDebt) = Helpers.getUserCurrentDebt(user, debtReserve);
|
|
|
|
(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);
|
|
|
|
vars.actualAmountToLiquidate = principalAmount > vars.maxPrincipalAmountToLiquidate
|
|
? vars.maxPrincipalAmountToLiquidate
|
|
: principalAmount;
|
|
|
|
vars.collateralAtoken = IAToken(collateralReserve.aTokenAddress);
|
|
vars.userCollateralBalance = vars.collateralAtoken.balanceOf(user);
|
|
|
|
(
|
|
vars.maxCollateralToLiquidate,
|
|
vars.principalAmountNeeded
|
|
) = calculateAvailableCollateralToLiquidate(
|
|
collateralReserve,
|
|
debtReserve,
|
|
collateral,
|
|
principal,
|
|
vars.actualAmountToLiquidate,
|
|
vars.userCollateralBalance
|
|
);
|
|
|
|
//if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
|
|
//of collateral to cover the actual amount that is being liquidated, hence we liquidate
|
|
//a smaller amount
|
|
if (vars.principalAmountNeeded < vars.actualAmountToLiquidate) {
|
|
vars.actualAmountToLiquidate = vars.principalAmountNeeded;
|
|
}
|
|
//updating collateral reserve indexes
|
|
collateralReserve.updateCumulativeIndexesAndTimestamp();
|
|
|
|
vars.collateralAtoken.burn(
|
|
user,
|
|
receiver,
|
|
vars.maxCollateralToLiquidate,
|
|
collateralReserve.liquidityIndex
|
|
);
|
|
|
|
if (vars.userCollateralBalance == vars.maxCollateralToLiquidate) {
|
|
usersConfig[user].setUsingAsCollateral(collateralReserve.id, false);
|
|
}
|
|
|
|
vars.principalAToken = debtReserve.aTokenAddress;
|
|
|
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
|
ISwapAdapter(receiver).executeOperation(
|
|
collateral,
|
|
principal,
|
|
vars.maxCollateralToLiquidate,
|
|
address(this),
|
|
params
|
|
);
|
|
|
|
//updating debt reserve
|
|
debtReserve.updateCumulativeIndexesAndTimestamp();
|
|
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(
|
|
user,
|
|
vars.actualAmountToLiquidate
|
|
);
|
|
} else {
|
|
IVariableDebtToken(debtReserve.variableDebtTokenAddress).burn(user, vars.userVariableDebt);
|
|
IStableDebtToken(debtReserve.stableDebtTokenAddress).burn(
|
|
user,
|
|
vars.actualAmountToLiquidate.sub(vars.userVariableDebt)
|
|
);
|
|
}
|
|
|
|
//updating collateral reserve
|
|
collateralReserve.updateInterestRates(
|
|
collateral,
|
|
address(vars.collateralAtoken),
|
|
0,
|
|
vars.maxCollateralToLiquidate
|
|
);
|
|
|
|
emit RepaidWithCollateral(
|
|
collateral,
|
|
principal,
|
|
user,
|
|
msg.sender,
|
|
vars.actualAmountToLiquidate,
|
|
vars.maxCollateralToLiquidate
|
|
);
|
|
|
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
|
}
|
|
|
|
/**
|
|
* @dev Allows an user to release one of his assets deposited in the protocol, even if it is used as collateral, to swap for another.
|
|
* - It's not possible to release one asset to swap for the same
|
|
* @param receiverAddress The address of the contract receiving the funds. The receiver should implement the ISwapAdapter interface
|
|
* @param fromAsset Asset to swap from
|
|
* @param toAsset Asset to swap to
|
|
* @param params a bytes array to be sent (if needed) to the receiver contract with extra data
|
|
**/
|
|
function swapLiquidity(
|
|
address receiverAddress,
|
|
address fromAsset,
|
|
address toAsset,
|
|
uint256 amountToSwap,
|
|
bytes calldata params
|
|
) external returns (uint256, string memory) {
|
|
ReserveLogic.ReserveData storage fromReserve = reserves[fromAsset];
|
|
ReserveLogic.ReserveData storage toReserve = reserves[toAsset];
|
|
|
|
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
|
SwapLiquidityLocalVars memory vars;
|
|
|
|
(vars.errorCode, vars.errorMsg) = ValidationLogic.validateSwapLiquidity(
|
|
fromReserve,
|
|
toReserve,
|
|
fromAsset,
|
|
toAsset
|
|
);
|
|
|
|
if (Errors.LiquidationErrors(vars.errorCode) != Errors.LiquidationErrors.NO_ERROR) {
|
|
return (vars.errorCode, vars.errorMsg);
|
|
}
|
|
|
|
vars.fromReserveAToken = IAToken(fromReserve.aTokenAddress);
|
|
vars.toReserveAToken = IAToken(toReserve.aTokenAddress);
|
|
|
|
fromReserve.updateCumulativeIndexesAndTimestamp();
|
|
toReserve.updateCumulativeIndexesAndTimestamp();
|
|
|
|
if (vars.fromReserveAToken.balanceOf(msg.sender) == amountToSwap) {
|
|
usersConfig[msg.sender].setUsingAsCollateral(fromReserve.id, false);
|
|
}
|
|
|
|
fromReserve.updateInterestRates(fromAsset, address(vars.fromReserveAToken), 0, amountToSwap);
|
|
|
|
vars.fromReserveAToken.burn(
|
|
msg.sender,
|
|
receiverAddress,
|
|
amountToSwap,
|
|
fromReserve.liquidityIndex
|
|
);
|
|
// Notifies the receiver to proceed, sending as param the underlying already transferred
|
|
ISwapAdapter(receiverAddress).executeOperation(
|
|
fromAsset,
|
|
toAsset,
|
|
amountToSwap,
|
|
address(this),
|
|
params
|
|
);
|
|
|
|
vars.amountToReceive = IERC20(toAsset).balanceOf(receiverAddress);
|
|
if (vars.amountToReceive != 0) {
|
|
IERC20(toAsset).transferFrom(
|
|
receiverAddress,
|
|
address(vars.toReserveAToken),
|
|
vars.amountToReceive
|
|
);
|
|
vars.toReserveAToken.mint(msg.sender, vars.amountToReceive, toReserve.liquidityIndex);
|
|
toReserve.updateInterestRates(
|
|
toAsset,
|
|
address(vars.toReserveAToken),
|
|
vars.amountToReceive,
|
|
0
|
|
);
|
|
}
|
|
|
|
(, , , , vars.healthFactor) = GenericLogic.calculateUserAccountData(
|
|
msg.sender,
|
|
reserves,
|
|
usersConfig[msg.sender],
|
|
reservesList,
|
|
addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
if (vars.healthFactor < GenericLogic.HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
|
|
return (
|
|
uint256(Errors.LiquidationErrors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD),
|
|
Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
|
|
);
|
|
}
|
|
|
|
return (uint256(Errors.LiquidationErrors.NO_ERROR), Errors.NO_ERRORS);
|
|
}
|
|
|
|
/**
|
|
* @dev calculates how much of a specific collateral can be liquidated, given
|
|
* a certain amount of principal currency. This function needs to be called after
|
|
* all the checks to validate the liquidation have been performed, otherwise it might fail.
|
|
* @param collateralAddress the collateral to be liquidated
|
|
* @param principalAddress the principal currency to be liquidated
|
|
* @param purchaseAmount the amount of principal being liquidated
|
|
* @param userCollateralBalance the collatera balance for the specific collateral asset of the user being liquidated
|
|
* @return collateralAmount the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor)
|
|
* @return principalAmountNeeded the purchase amount
|
|
**/
|
|
function calculateAvailableCollateralToLiquidate(
|
|
ReserveLogic.ReserveData storage collateralReserve,
|
|
ReserveLogic.ReserveData storage principalReserve,
|
|
address collateralAddress,
|
|
address principalAddress,
|
|
uint256 purchaseAmount,
|
|
uint256 userCollateralBalance
|
|
) internal view returns (uint256, uint256) {
|
|
uint256 collateralAmount = 0;
|
|
uint256 principalAmountNeeded = 0;
|
|
IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
|
|
|
|
// Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
|
|
AvailableCollateralToLiquidateLocalVars memory vars;
|
|
|
|
vars.collateralPrice = oracle.getAssetPrice(collateralAddress);
|
|
vars.principalCurrencyPrice = oracle.getAssetPrice(principalAddress);
|
|
|
|
(, , vars.liquidationBonus, vars.collateralDecimals) = collateralReserve
|
|
.configuration
|
|
.getParams();
|
|
vars.principalDecimals = principalReserve.configuration.getDecimals();
|
|
|
|
//this is the maximum possible amount of the selected collateral that can be liquidated, given the
|
|
//max amount of principal currency that is available for liquidation.
|
|
vars.maxAmountCollateralToLiquidate = vars
|
|
.principalCurrencyPrice
|
|
.mul(purchaseAmount)
|
|
.mul(10**vars.collateralDecimals)
|
|
.div(vars.collateralPrice.mul(10**vars.principalDecimals))
|
|
.percentMul(vars.liquidationBonus);
|
|
|
|
if (vars.maxAmountCollateralToLiquidate > userCollateralBalance) {
|
|
collateralAmount = userCollateralBalance;
|
|
principalAmountNeeded = vars
|
|
.collateralPrice
|
|
.mul(collateralAmount)
|
|
.mul(10**vars.principalDecimals)
|
|
.div(vars.principalCurrencyPrice.mul(10**vars.collateralDecimals))
|
|
.percentDiv(vars.liquidationBonus);
|
|
} else {
|
|
collateralAmount = vars.maxAmountCollateralToLiquidate;
|
|
principalAmountNeeded = purchaseAmount;
|
|
}
|
|
return (collateralAmount, principalAmountNeeded);
|
|
}
|
|
}
|