mirror of
https://github.com/Instadapp/aave-protocol-v2.git
synced 2024-07-29 21:47:30 +00:00
1030 lines
35 KiB
Solidity
1030 lines
35 KiB
Solidity
// SPDX-License-Identifier: agpl-3.0
|
|
pragma solidity 0.6.12;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import {SafeMath} from '../../dependencies/openzeppelin/contracts/SafeMath.sol';
|
|
import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol';
|
|
import {IERC20WithPermit} from '../../interfaces/IERC20WithPermit.sol';
|
|
import {SafeERC20} from '../../dependencies/openzeppelin/contracts/SafeERC20.sol';
|
|
import {Address} from '../../dependencies/openzeppelin/contracts/Address.sol';
|
|
import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol';
|
|
import {IAToken} from '../../interfaces/IAToken.sol';
|
|
import {IVariableDebtToken} from '../../interfaces/IVariableDebtToken.sol';
|
|
import {IFlashLoanReceiver} from '../../flashloan/interfaces/IFlashLoanReceiver.sol';
|
|
import {IPriceOracleGetter} from '../../interfaces/IPriceOracleGetter.sol';
|
|
import {IStableDebtToken} from '../../interfaces/IStableDebtToken.sol';
|
|
import {ILendingPool} from '../../interfaces/ILendingPool.sol';
|
|
import {VersionedInitializable} from '../libraries/aave-upgradeability/VersionedInitializable.sol';
|
|
import {Helpers} from '../libraries/helpers/Helpers.sol';
|
|
import {Errors} from '../libraries/helpers/Errors.sol';
|
|
import {WadRayMath} from '../libraries/math/WadRayMath.sol';
|
|
import {PercentageMath} from '../libraries/math/PercentageMath.sol';
|
|
import {ReserveLogic} from '../libraries/logic/ReserveLogic.sol';
|
|
import {GenericLogic} from '../libraries/logic/GenericLogic.sol';
|
|
import {ValidationLogic} from '../libraries/logic/ValidationLogic.sol';
|
|
import {ReserveConfiguration} from '../libraries/configuration/ReserveConfiguration.sol';
|
|
import {UserConfiguration} from '../libraries/configuration/UserConfiguration.sol';
|
|
import {DataTypes} from '../libraries/types/DataTypes.sol';
|
|
import {LendingPoolStorage} from './LendingPoolStorage.sol';
|
|
|
|
/**
|
|
* @title LendingPool contract
|
|
* @dev Main point of interaction with an Aave protocol's market
|
|
* - Users can:
|
|
* # Deposit
|
|
* # Withdraw
|
|
* # Borrow
|
|
* # Repay
|
|
* # Swap their loans between variable and stable rate
|
|
* # Enable/disable their deposits as collateral rebalance stable rate borrow positions
|
|
* # Liquidate positions
|
|
* # Execute Flash Loans
|
|
* - To be covered by a proxy contract, owned by the LendingPoolAddressesProvider of the specific market
|
|
* - All admin functions are callable by the LendingPoolConfigurator contract defined also in the
|
|
* LendingPoolAddressesProvider
|
|
* @author Aave
|
|
**/
|
|
contract LendingPool is VersionedInitializable, ILendingPool, LendingPoolStorage {
|
|
using SafeMath for uint256;
|
|
using WadRayMath for uint256;
|
|
using PercentageMath for uint256;
|
|
using SafeERC20 for IERC20;
|
|
|
|
uint256 public constant LENDINGPOOL_REVISION = 0x2;
|
|
|
|
modifier whenNotPaused() {
|
|
_whenNotPaused();
|
|
_;
|
|
}
|
|
|
|
modifier onlyLendingPoolConfigurator() {
|
|
_onlyLendingPoolConfigurator();
|
|
_;
|
|
}
|
|
|
|
function _whenNotPaused() internal view {
|
|
require(!_paused, Errors.LP_IS_PAUSED);
|
|
}
|
|
|
|
function _onlyLendingPoolConfigurator() internal view {
|
|
require(
|
|
_addressesProvider.getLendingPoolConfigurator() == msg.sender,
|
|
Errors.LP_CALLER_NOT_LENDING_POOL_CONFIGURATOR
|
|
);
|
|
}
|
|
|
|
function getRevision() internal pure override returns (uint256) {
|
|
return LENDINGPOOL_REVISION;
|
|
}
|
|
|
|
/**
|
|
* @dev Function is invoked by the proxy contract when the LendingPool contract is added to the
|
|
* LendingPoolAddressesProvider of the market.
|
|
* - Caching the address of the LendingPoolAddressesProvider in order to reduce gas consumption
|
|
* on subsequent operations
|
|
* @param provider The address of the LendingPoolAddressesProvider
|
|
**/
|
|
function initialize(ILendingPoolAddressesProvider provider) public initializer {
|
|
_addressesProvider = provider;
|
|
_maxStableRateBorrowSizePercent = 2500;
|
|
_flashLoanPremiumTotal = 9;
|
|
_maxNumberOfReserves = 128;
|
|
}
|
|
|
|
/**
|
|
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
|
|
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
|
|
* @param asset The address of the underlying asset to deposit
|
|
* @param amount The amount to be deposited
|
|
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
|
|
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
|
|
* is a different wallet
|
|
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
|
* 0 if the action is executed directly by the user, without any middle-man
|
|
**/
|
|
function deposit(
|
|
address asset,
|
|
uint256 amount,
|
|
address onBehalfOf,
|
|
uint16 referralCode
|
|
) external override whenNotPaused {
|
|
_executeDeposit(asset, amount, onBehalfOf, referralCode);
|
|
}
|
|
|
|
/**
|
|
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
|
|
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
|
|
* @param asset The address of the underlying asset to withdraw
|
|
* @param amount The underlying amount to be withdrawn
|
|
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
|
|
* @param to Address that will receive the underlying, same as msg.sender if the user
|
|
* wants to receive it on his own wallet, or a different address if the beneficiary is a
|
|
* different wallet
|
|
* @return The final amount withdrawn
|
|
**/
|
|
function withdraw(
|
|
address asset,
|
|
uint256 amount,
|
|
address to
|
|
) external override whenNotPaused returns (uint256) {
|
|
return _executeWithdraw(asset, amount, to);
|
|
}
|
|
|
|
/**
|
|
* @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
|
|
* already deposited enough collateral, or he was given enough allowance by a credit delegator on the
|
|
* corresponding debt token (StableDebtToken or VariableDebtToken)
|
|
* - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
|
|
* and 100 stable/variable debt tokens, depending on the `interestRateMode`
|
|
* @param asset The address of the underlying asset to borrow
|
|
* @param amount The amount to be borrowed
|
|
* @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
|
|
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
|
* 0 if the action is executed directly by the user, without any middle-man
|
|
* @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself
|
|
* calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
|
|
* if he has been given credit delegation allowance
|
|
**/
|
|
function borrow(
|
|
address asset,
|
|
uint256 amount,
|
|
uint256 interestRateMode,
|
|
uint16 referralCode,
|
|
address onBehalfOf
|
|
) external override whenNotPaused {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
_executeBorrow(
|
|
ExecuteBorrowParams(
|
|
asset,
|
|
msg.sender,
|
|
onBehalfOf,
|
|
amount,
|
|
interestRateMode,
|
|
reserve.aTokenAddress,
|
|
referralCode,
|
|
true
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
|
|
* - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address
|
|
* @param asset The address of the borrowed underlying asset previously borrowed
|
|
* @param amount The amount to repay
|
|
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
|
|
* @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
|
|
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
|
|
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
|
|
* other borrower whose debt should be removed
|
|
* @return The final amount repaid
|
|
**/
|
|
function repay(
|
|
address asset,
|
|
uint256 amount,
|
|
uint256 rateMode,
|
|
address onBehalfOf
|
|
) external override whenNotPaused returns (uint256) {
|
|
return _executeRepay(asset, amount, rateMode, onBehalfOf);
|
|
}
|
|
|
|
/**
|
|
* @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa
|
|
* @param asset The address of the underlying asset borrowed
|
|
* @param rateMode The rate mode that the user wants to swap to
|
|
**/
|
|
function swapBorrowRateMode(address asset, uint256 rateMode) external override whenNotPaused {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(msg.sender, reserve);
|
|
|
|
DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode);
|
|
|
|
ValidationLogic.validateSwapRateMode(
|
|
reserve,
|
|
_usersConfig[msg.sender],
|
|
stableDebt,
|
|
variableDebt,
|
|
interestRateMode
|
|
);
|
|
|
|
reserve.updateState();
|
|
|
|
if (interestRateMode == DataTypes.InterestRateMode.STABLE) {
|
|
IStableDebtToken(reserve.stableDebtTokenAddress).burn(msg.sender, stableDebt);
|
|
IVariableDebtToken(reserve.variableDebtTokenAddress).mint(
|
|
msg.sender,
|
|
msg.sender,
|
|
stableDebt,
|
|
reserve.variableBorrowIndex
|
|
);
|
|
} else {
|
|
IVariableDebtToken(reserve.variableDebtTokenAddress).burn(
|
|
msg.sender,
|
|
variableDebt,
|
|
reserve.variableBorrowIndex
|
|
);
|
|
IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
|
msg.sender,
|
|
msg.sender,
|
|
variableDebt,
|
|
reserve.currentStableBorrowRate
|
|
);
|
|
}
|
|
|
|
reserve.updateInterestRates(asset, reserve.aTokenAddress, 0, 0);
|
|
|
|
emit Swap(asset, msg.sender, rateMode);
|
|
}
|
|
|
|
/**
|
|
* @notice Deposit with transfer approval of asset to be deposited done via permit function
|
|
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
|
|
* @param asset The address of the underlying asset to deposit
|
|
* @param amount The amount to be deposited
|
|
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
|
|
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
|
|
* is a different wallet
|
|
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
|
* 0 if the action is executed directly by the user, without any middle-man
|
|
* @param permitV V parameter of ERC712 permit sig
|
|
* @param permitR R parameter of ERC712 permit sig
|
|
* @param permitS S parameter of ERC712 permit sig
|
|
**/
|
|
function depositWithPermit(
|
|
address asset,
|
|
uint256 amount,
|
|
address onBehalfOf,
|
|
uint16 referralCode,
|
|
uint256 deadline,
|
|
uint8 permitV,
|
|
bytes32 permitR,
|
|
bytes32 permitS
|
|
) external override {
|
|
IERC20WithPermit(asset).permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS);
|
|
_executeDeposit(asset, amount, onBehalfOf, referralCode);
|
|
}
|
|
|
|
/**
|
|
* @notice Repay with transfer approval of asset to be repaid done via permit function
|
|
* see: https://eips.ethereum.org/EIPS/eip-2612 and https://eips.ethereum.org/EIPS/eip-713
|
|
* @param asset The address of the borrowed underlying asset previously borrowed
|
|
* @param amount The amount to repay
|
|
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
|
|
* @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
|
|
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
|
|
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
|
|
* other borrower whose debt should be removed
|
|
* @param permitV V parameter of ERC712 permit sig
|
|
* @param permitR R parameter of ERC712 permit sig
|
|
* @param permitS S parameter of ERC712 permit sig
|
|
* @return The final amount repaid
|
|
**/
|
|
function repayWithPermit(
|
|
address asset,
|
|
uint256 amount,
|
|
uint256 rateMode,
|
|
address onBehalfOf,
|
|
uint256 deadline,
|
|
uint8 permitV,
|
|
bytes32 permitR,
|
|
bytes32 permitS
|
|
) external override returns (uint256) {
|
|
IERC20WithPermit(asset).permit(msg.sender, address(this), amount, deadline, permitV, permitR, permitS);
|
|
return _executeRepay(asset, amount, rateMode, onBehalfOf);
|
|
}
|
|
|
|
/**
|
|
* @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
|
|
* - Users can be rebalanced if the following conditions are satisfied:
|
|
* 1. Usage ratio is above 95%
|
|
* 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
|
|
* borrowed at a stable rate and depositors are not earning enough
|
|
* @param asset The address of the underlying asset borrowed
|
|
* @param user The address of the user to be rebalanced
|
|
**/
|
|
function rebalanceStableBorrowRate(address asset, address user) external override whenNotPaused {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
IERC20 stableDebtToken = IERC20(reserve.stableDebtTokenAddress);
|
|
IERC20 variableDebtToken = IERC20(reserve.variableDebtTokenAddress);
|
|
address aTokenAddress = reserve.aTokenAddress;
|
|
|
|
uint256 stableDebt = IERC20(stableDebtToken).balanceOf(user);
|
|
|
|
ValidationLogic.validateRebalanceStableBorrowRate(
|
|
reserve,
|
|
asset,
|
|
stableDebtToken,
|
|
variableDebtToken,
|
|
aTokenAddress
|
|
);
|
|
|
|
reserve.updateState();
|
|
|
|
IStableDebtToken(address(stableDebtToken)).burn(user, stableDebt);
|
|
IStableDebtToken(address(stableDebtToken)).mint(
|
|
user,
|
|
user,
|
|
stableDebt,
|
|
reserve.currentStableBorrowRate
|
|
);
|
|
|
|
reserve.updateInterestRates(asset, aTokenAddress, 0, 0);
|
|
|
|
emit RebalanceStableBorrowRate(asset, user);
|
|
}
|
|
|
|
/**
|
|
* @dev Allows depositors to enable/disable a specific deposited asset as collateral
|
|
* @param asset The address of the underlying asset deposited
|
|
* @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise
|
|
**/
|
|
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral)
|
|
external
|
|
override
|
|
whenNotPaused
|
|
{
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
ValidationLogic.validateSetUseReserveAsCollateral(
|
|
reserve,
|
|
asset,
|
|
useAsCollateral,
|
|
_reserves,
|
|
_usersConfig[msg.sender],
|
|
_reservesList,
|
|
_reservesCount,
|
|
_addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
_usersConfig[msg.sender].setUsingAsCollateral(reserve.id, useAsCollateral);
|
|
|
|
if (useAsCollateral) {
|
|
emit ReserveUsedAsCollateralEnabled(asset, msg.sender);
|
|
} else {
|
|
emit ReserveUsedAsCollateralDisabled(asset, msg.sender);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
|
|
* - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
|
|
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
|
|
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
|
|
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
|
|
* @param user The address of the borrower getting liquidated
|
|
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
|
|
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
|
|
* to receive the underlying collateral asset directly
|
|
**/
|
|
function liquidationCall(
|
|
address collateralAsset,
|
|
address debtAsset,
|
|
address user,
|
|
uint256 debtToCover,
|
|
bool receiveAToken
|
|
) external override whenNotPaused {
|
|
address collateralManager = _addressesProvider.getLendingPoolCollateralManager();
|
|
|
|
//solium-disable-next-line
|
|
(bool success, bytes memory result) =
|
|
collateralManager.delegatecall(
|
|
abi.encodeWithSignature(
|
|
'liquidationCall(address,address,address,uint256,bool)',
|
|
collateralAsset,
|
|
debtAsset,
|
|
user,
|
|
debtToCover,
|
|
receiveAToken
|
|
)
|
|
);
|
|
|
|
require(success, Errors.LP_LIQUIDATION_CALL_FAILED);
|
|
|
|
(uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
|
|
|
|
require(returnCode == 0, string(abi.encodePacked(returnMessage)));
|
|
}
|
|
|
|
struct FlashLoanLocalVars {
|
|
IFlashLoanReceiver receiver;
|
|
address oracle;
|
|
uint256 i;
|
|
address currentAsset;
|
|
address currentATokenAddress;
|
|
uint256 currentAmount;
|
|
uint256 currentPremium;
|
|
uint256 currentAmountPlusPremium;
|
|
address debtToken;
|
|
}
|
|
|
|
/**
|
|
* @dev Allows smartcontracts to access the liquidity of the pool within one transaction,
|
|
* as long as the amount taken plus a fee is returned.
|
|
* IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration.
|
|
* For further details please visit https://developers.aave.com
|
|
* @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface
|
|
* @param assets The addresses of the assets being flash-borrowed
|
|
* @param amounts The amounts amounts being flash-borrowed
|
|
* @param modes Types of the debt to open if the flash loan is not returned:
|
|
* 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
|
|
* 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
|
|
* 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
|
|
* @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2
|
|
* @param params Variadic packed params to pass to the receiver as extra information
|
|
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
|
|
* 0 if the action is executed directly by the user, without any middle-man
|
|
**/
|
|
function flashLoan(
|
|
address receiverAddress,
|
|
address[] calldata assets,
|
|
uint256[] calldata amounts,
|
|
uint256[] calldata modes,
|
|
address onBehalfOf,
|
|
bytes calldata params,
|
|
uint16 referralCode
|
|
) external override whenNotPaused {
|
|
FlashLoanLocalVars memory vars;
|
|
|
|
ValidationLogic.validateFlashloan(assets, amounts);
|
|
|
|
address[] memory aTokenAddresses = new address[](assets.length);
|
|
uint256[] memory premiums = new uint256[](assets.length);
|
|
|
|
vars.receiver = IFlashLoanReceiver(receiverAddress);
|
|
|
|
for (vars.i = 0; vars.i < assets.length; vars.i++) {
|
|
aTokenAddresses[vars.i] = _reserves[assets[vars.i]].aTokenAddress;
|
|
|
|
premiums[vars.i] = amounts[vars.i].mul(_flashLoanPremiumTotal).div(10000);
|
|
|
|
IAToken(aTokenAddresses[vars.i]).transferUnderlyingTo(receiverAddress, amounts[vars.i]);
|
|
}
|
|
|
|
require(
|
|
vars.receiver.executeOperation(assets, amounts, premiums, msg.sender, params),
|
|
Errors.LP_INVALID_FLASH_LOAN_EXECUTOR_RETURN
|
|
);
|
|
|
|
for (vars.i = 0; vars.i < assets.length; vars.i++) {
|
|
vars.currentAsset = assets[vars.i];
|
|
vars.currentAmount = amounts[vars.i];
|
|
vars.currentPremium = premiums[vars.i];
|
|
vars.currentATokenAddress = aTokenAddresses[vars.i];
|
|
vars.currentAmountPlusPremium = vars.currentAmount.add(vars.currentPremium);
|
|
|
|
if (DataTypes.InterestRateMode(modes[vars.i]) == DataTypes.InterestRateMode.NONE) {
|
|
_reserves[vars.currentAsset].updateState();
|
|
_reserves[vars.currentAsset].cumulateToLiquidityIndex(
|
|
IERC20(vars.currentATokenAddress).totalSupply(),
|
|
vars.currentPremium
|
|
);
|
|
_reserves[vars.currentAsset].updateInterestRates(
|
|
vars.currentAsset,
|
|
vars.currentATokenAddress,
|
|
vars.currentAmountPlusPremium,
|
|
0
|
|
);
|
|
|
|
IERC20(vars.currentAsset).safeTransferFrom(
|
|
receiverAddress,
|
|
vars.currentATokenAddress,
|
|
vars.currentAmountPlusPremium
|
|
);
|
|
} else {
|
|
// If the user chose to not return the funds, the system checks if there is enough collateral and
|
|
// eventually opens a debt position
|
|
_executeBorrow(
|
|
ExecuteBorrowParams(
|
|
vars.currentAsset,
|
|
msg.sender,
|
|
onBehalfOf,
|
|
vars.currentAmount,
|
|
modes[vars.i],
|
|
vars.currentATokenAddress,
|
|
referralCode,
|
|
false
|
|
)
|
|
);
|
|
}
|
|
emit FlashLoan(
|
|
receiverAddress,
|
|
msg.sender,
|
|
vars.currentAsset,
|
|
vars.currentAmount,
|
|
vars.currentPremium,
|
|
referralCode
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the state and configuration of the reserve
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @return The state of the reserve
|
|
**/
|
|
function getReserveData(address asset)
|
|
external
|
|
view
|
|
override
|
|
returns (DataTypes.ReserveData memory)
|
|
{
|
|
return _reserves[asset];
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the user account data across all the reserves
|
|
* @param user The address of the user
|
|
* @return totalCollateralETH the total collateral in ETH of the user
|
|
* @return totalDebtETH the total debt in ETH of the user
|
|
* @return availableBorrowsETH the borrowing power left of the user
|
|
* @return currentLiquidationThreshold the liquidation threshold of the user
|
|
* @return ltv the loan to value of the user
|
|
* @return healthFactor the current health factor of the user
|
|
**/
|
|
function getUserAccountData(address user)
|
|
external
|
|
view
|
|
override
|
|
returns (
|
|
uint256 totalCollateralETH,
|
|
uint256 totalDebtETH,
|
|
uint256 availableBorrowsETH,
|
|
uint256 currentLiquidationThreshold,
|
|
uint256 ltv,
|
|
uint256 healthFactor
|
|
)
|
|
{
|
|
(
|
|
totalCollateralETH,
|
|
totalDebtETH,
|
|
ltv,
|
|
currentLiquidationThreshold,
|
|
healthFactor
|
|
) = GenericLogic.calculateUserAccountData(
|
|
user,
|
|
_reserves,
|
|
_usersConfig[user],
|
|
_reservesList,
|
|
_reservesCount,
|
|
_addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
availableBorrowsETH = GenericLogic.calculateAvailableBorrowsETH(
|
|
totalCollateralETH,
|
|
totalDebtETH,
|
|
ltv
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the configuration of the reserve
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @return The configuration of the reserve
|
|
**/
|
|
function getConfiguration(address asset)
|
|
external
|
|
view
|
|
override
|
|
returns (DataTypes.ReserveConfigurationMap memory)
|
|
{
|
|
return _reserves[asset].configuration;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the configuration of the user across all the reserves
|
|
* @param user The user address
|
|
* @return The configuration of the user
|
|
**/
|
|
function getUserConfiguration(address user)
|
|
external
|
|
view
|
|
override
|
|
returns (DataTypes.UserConfigurationMap memory)
|
|
{
|
|
return _usersConfig[user];
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the normalized income per unit of asset
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @return The reserve's normalized income
|
|
*/
|
|
function getReserveNormalizedIncome(address asset)
|
|
external
|
|
view
|
|
virtual
|
|
override
|
|
returns (uint256)
|
|
{
|
|
return _reserves[asset].getNormalizedIncome();
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the normalized variable debt per unit of asset
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @return The reserve normalized variable debt
|
|
*/
|
|
function getReserveNormalizedVariableDebt(address asset)
|
|
external
|
|
view
|
|
override
|
|
returns (uint256)
|
|
{
|
|
return _reserves[asset].getNormalizedDebt();
|
|
}
|
|
|
|
/**
|
|
* @dev Returns if the LendingPool is paused
|
|
*/
|
|
function paused() external view override returns (bool) {
|
|
return _paused;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the list of the initialized reserves
|
|
**/
|
|
function getReservesList() external view override returns (address[] memory) {
|
|
address[] memory _activeReserves = new address[](_reservesCount);
|
|
|
|
for (uint256 i = 0; i < _reservesCount; i++) {
|
|
_activeReserves[i] = _reservesList[i];
|
|
}
|
|
return _activeReserves;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the cached LendingPoolAddressesProvider connected to this contract
|
|
**/
|
|
function getAddressesProvider() external view override returns (ILendingPoolAddressesProvider) {
|
|
return _addressesProvider;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the percentage of available liquidity that can be borrowed at once at stable rate
|
|
*/
|
|
function MAX_STABLE_RATE_BORROW_SIZE_PERCENT() public view returns (uint256) {
|
|
return _maxStableRateBorrowSizePercent;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the fee on flash loans
|
|
*/
|
|
function FLASHLOAN_PREMIUM_TOTAL() public view returns (uint256) {
|
|
return _flashLoanPremiumTotal;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the maximum number of reserves supported to be listed in this LendingPool
|
|
*/
|
|
function MAX_NUMBER_RESERVES() public view returns (uint256) {
|
|
return _maxNumberOfReserves;
|
|
}
|
|
|
|
/**
|
|
* @dev Validates and finalizes an aToken transfer
|
|
* - Only callable by the overlying aToken of the `asset`
|
|
* @param asset The address of the underlying asset of the aToken
|
|
* @param from The user from which the aTokens are transferred
|
|
* @param to The user receiving the aTokens
|
|
* @param amount The amount being transferred/withdrawn
|
|
* @param balanceFromBefore The aToken balance of the `from` user before the transfer
|
|
* @param balanceToBefore The aToken balance of the `to` user before the transfer
|
|
*/
|
|
function finalizeTransfer(
|
|
address asset,
|
|
address from,
|
|
address to,
|
|
uint256 amount,
|
|
uint256 balanceFromBefore,
|
|
uint256 balanceToBefore
|
|
) external override whenNotPaused {
|
|
require(msg.sender == _reserves[asset].aTokenAddress, Errors.LP_CALLER_MUST_BE_AN_ATOKEN);
|
|
|
|
ValidationLogic.validateTransfer(
|
|
from,
|
|
_reserves,
|
|
_usersConfig[from],
|
|
_reservesList,
|
|
_reservesCount,
|
|
_addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
uint256 reserveId = _reserves[asset].id;
|
|
|
|
if (from != to) {
|
|
if (balanceFromBefore.sub(amount) == 0) {
|
|
DataTypes.UserConfigurationMap storage fromConfig = _usersConfig[from];
|
|
fromConfig.setUsingAsCollateral(reserveId, false);
|
|
emit ReserveUsedAsCollateralDisabled(asset, from);
|
|
}
|
|
|
|
if (balanceToBefore == 0 && amount != 0) {
|
|
DataTypes.UserConfigurationMap storage toConfig = _usersConfig[to];
|
|
toConfig.setUsingAsCollateral(reserveId, true);
|
|
emit ReserveUsedAsCollateralEnabled(asset, to);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev Initializes a reserve, activating it, assigning an aToken and debt tokens and an
|
|
* interest rate strategy
|
|
* - Only callable by the LendingPoolConfigurator contract
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @param aTokenAddress The address of the aToken that will be assigned to the reserve
|
|
* @param stableDebtAddress The address of the StableDebtToken that will be assigned to the reserve
|
|
* @param aTokenAddress The address of the VariableDebtToken that will be assigned to the reserve
|
|
* @param interestRateStrategyAddress The address of the interest rate strategy contract
|
|
**/
|
|
function initReserve(
|
|
address asset,
|
|
address aTokenAddress,
|
|
address stableDebtAddress,
|
|
address variableDebtAddress,
|
|
address interestRateStrategyAddress
|
|
) external override onlyLendingPoolConfigurator {
|
|
require(Address.isContract(asset), Errors.LP_NOT_CONTRACT);
|
|
_reserves[asset].init(
|
|
aTokenAddress,
|
|
stableDebtAddress,
|
|
variableDebtAddress,
|
|
interestRateStrategyAddress
|
|
);
|
|
_addReserveToList(asset);
|
|
}
|
|
|
|
/**
|
|
* @dev Updates the address of the interest rate strategy contract
|
|
* - Only callable by the LendingPoolConfigurator contract
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @param rateStrategyAddress The address of the interest rate strategy contract
|
|
**/
|
|
function setReserveInterestRateStrategyAddress(address asset, address rateStrategyAddress)
|
|
external
|
|
override
|
|
onlyLendingPoolConfigurator
|
|
{
|
|
_reserves[asset].interestRateStrategyAddress = rateStrategyAddress;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets the configuration bitmap of the reserve as a whole
|
|
* - Only callable by the LendingPoolConfigurator contract
|
|
* @param asset The address of the underlying asset of the reserve
|
|
* @param configuration The new configuration bitmap
|
|
**/
|
|
function setConfiguration(address asset, uint256 configuration)
|
|
external
|
|
override
|
|
onlyLendingPoolConfigurator
|
|
{
|
|
_reserves[asset].configuration.data = configuration;
|
|
}
|
|
|
|
/**
|
|
* @dev Set the _pause state of a reserve
|
|
* - Only callable by the LendingPoolConfigurator contract
|
|
* @param val `true` to pause the reserve, `false` to un-pause it
|
|
*/
|
|
function setPause(bool val) external override onlyLendingPoolConfigurator {
|
|
_paused = val;
|
|
if (_paused) {
|
|
emit Paused();
|
|
} else {
|
|
emit Unpaused();
|
|
}
|
|
}
|
|
|
|
struct ExecuteBorrowParams {
|
|
address asset;
|
|
address user;
|
|
address onBehalfOf;
|
|
uint256 amount;
|
|
uint256 interestRateMode;
|
|
address aTokenAddress;
|
|
uint16 referralCode;
|
|
bool releaseUnderlying;
|
|
}
|
|
|
|
function _executeBorrow(ExecuteBorrowParams memory vars) internal {
|
|
DataTypes.ReserveData storage reserve = _reserves[vars.asset];
|
|
DataTypes.UserConfigurationMap storage userConfig = _usersConfig[vars.onBehalfOf];
|
|
|
|
address oracle = _addressesProvider.getPriceOracle();
|
|
|
|
uint256 amountInETH =
|
|
IPriceOracleGetter(oracle).getAssetPrice(vars.asset).mul(vars.amount).div(
|
|
10**reserve.configuration.getDecimals()
|
|
);
|
|
|
|
ValidationLogic.validateBorrow(
|
|
vars.asset,
|
|
reserve,
|
|
vars.onBehalfOf,
|
|
vars.amount,
|
|
amountInETH,
|
|
vars.interestRateMode,
|
|
_maxStableRateBorrowSizePercent,
|
|
_reserves,
|
|
userConfig,
|
|
_reservesList,
|
|
_reservesCount,
|
|
oracle
|
|
);
|
|
|
|
reserve.updateState();
|
|
|
|
uint256 currentStableRate = 0;
|
|
|
|
bool isFirstBorrowing = false;
|
|
if (DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE) {
|
|
currentStableRate = reserve.currentStableBorrowRate;
|
|
|
|
isFirstBorrowing = IStableDebtToken(reserve.stableDebtTokenAddress).mint(
|
|
vars.user,
|
|
vars.onBehalfOf,
|
|
vars.amount,
|
|
currentStableRate
|
|
);
|
|
} else {
|
|
isFirstBorrowing = IVariableDebtToken(reserve.variableDebtTokenAddress).mint(
|
|
vars.user,
|
|
vars.onBehalfOf,
|
|
vars.amount,
|
|
reserve.variableBorrowIndex
|
|
);
|
|
}
|
|
|
|
if (isFirstBorrowing) {
|
|
userConfig.setBorrowing(reserve.id, true);
|
|
}
|
|
|
|
reserve.updateInterestRates(
|
|
vars.asset,
|
|
vars.aTokenAddress,
|
|
0,
|
|
vars.releaseUnderlying ? vars.amount : 0
|
|
);
|
|
|
|
if (vars.releaseUnderlying) {
|
|
IAToken(vars.aTokenAddress).transferUnderlyingTo(vars.user, vars.amount);
|
|
}
|
|
|
|
emit Borrow(
|
|
vars.asset,
|
|
vars.user,
|
|
vars.onBehalfOf,
|
|
vars.amount,
|
|
vars.interestRateMode,
|
|
DataTypes.InterestRateMode(vars.interestRateMode) == DataTypes.InterestRateMode.STABLE
|
|
? currentStableRate
|
|
: reserve.currentVariableBorrowRate,
|
|
vars.referralCode
|
|
);
|
|
}
|
|
|
|
function _executeDeposit(
|
|
address asset,
|
|
uint256 amount,
|
|
address onBehalfOf,
|
|
uint16 referralCode
|
|
) internal {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
ValidationLogic.validateDeposit(reserve, amount);
|
|
|
|
address aToken = reserve.aTokenAddress;
|
|
|
|
reserve.updateState();
|
|
reserve.updateInterestRates(asset, aToken, amount, 0);
|
|
|
|
IERC20(asset).safeTransferFrom(msg.sender, aToken, amount);
|
|
|
|
bool isFirstDeposit = IAToken(aToken).mint(onBehalfOf, amount, reserve.liquidityIndex);
|
|
|
|
if (isFirstDeposit) {
|
|
_usersConfig[onBehalfOf].setUsingAsCollateral(reserve.id, true);
|
|
emit ReserveUsedAsCollateralEnabled(asset, onBehalfOf);
|
|
}
|
|
|
|
emit Deposit(asset, msg.sender, onBehalfOf, amount, referralCode);
|
|
}
|
|
|
|
function _executeWithdraw(
|
|
address asset,
|
|
uint256 amount,
|
|
address to
|
|
) internal returns (uint256) {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
address aToken = reserve.aTokenAddress;
|
|
|
|
uint256 userBalance = IAToken(aToken).balanceOf(msg.sender);
|
|
|
|
uint256 amountToWithdraw = amount;
|
|
|
|
if (amount == type(uint256).max) {
|
|
amountToWithdraw = userBalance;
|
|
}
|
|
|
|
ValidationLogic.validateWithdraw(
|
|
asset,
|
|
amountToWithdraw,
|
|
userBalance,
|
|
_reserves,
|
|
_usersConfig[msg.sender],
|
|
_reservesList,
|
|
_reservesCount,
|
|
_addressesProvider.getPriceOracle()
|
|
);
|
|
|
|
reserve.updateState();
|
|
|
|
reserve.updateInterestRates(asset, aToken, 0, amountToWithdraw);
|
|
|
|
if (amountToWithdraw == userBalance) {
|
|
_usersConfig[msg.sender].setUsingAsCollateral(reserve.id, false);
|
|
emit ReserveUsedAsCollateralDisabled(asset, msg.sender);
|
|
}
|
|
|
|
IAToken(aToken).burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex);
|
|
|
|
emit Withdraw(asset, msg.sender, to, amountToWithdraw);
|
|
|
|
return amountToWithdraw;
|
|
}
|
|
|
|
function _executeRepay(
|
|
address asset,
|
|
uint256 amount,
|
|
uint256 rateMode,
|
|
address onBehalfOf
|
|
) internal returns (uint256) {
|
|
DataTypes.ReserveData storage reserve = _reserves[asset];
|
|
|
|
(uint256 stableDebt, uint256 variableDebt) = Helpers.getUserCurrentDebt(onBehalfOf, reserve);
|
|
|
|
DataTypes.InterestRateMode interestRateMode = DataTypes.InterestRateMode(rateMode);
|
|
|
|
ValidationLogic.validateRepay(
|
|
reserve,
|
|
amount,
|
|
interestRateMode,
|
|
onBehalfOf,
|
|
stableDebt,
|
|
variableDebt
|
|
);
|
|
|
|
uint256 paybackAmount =
|
|
interestRateMode == DataTypes.InterestRateMode.STABLE ? stableDebt : variableDebt;
|
|
|
|
if (amount < paybackAmount) {
|
|
paybackAmount = amount;
|
|
}
|
|
|
|
reserve.updateState();
|
|
|
|
if (interestRateMode == DataTypes.InterestRateMode.STABLE) {
|
|
IStableDebtToken(reserve.stableDebtTokenAddress).burn(onBehalfOf, paybackAmount);
|
|
} else {
|
|
IVariableDebtToken(reserve.variableDebtTokenAddress).burn(
|
|
onBehalfOf,
|
|
paybackAmount,
|
|
reserve.variableBorrowIndex
|
|
);
|
|
}
|
|
|
|
address aToken = reserve.aTokenAddress;
|
|
reserve.updateInterestRates(asset, aToken, paybackAmount, 0);
|
|
|
|
if (stableDebt.add(variableDebt).sub(paybackAmount) == 0) {
|
|
_usersConfig[onBehalfOf].setBorrowing(reserve.id, false);
|
|
}
|
|
|
|
IERC20(asset).safeTransferFrom(msg.sender, aToken, paybackAmount);
|
|
|
|
IAToken(aToken).handleRepayment(msg.sender, paybackAmount);
|
|
|
|
emit Repay(asset, onBehalfOf, msg.sender, paybackAmount);
|
|
|
|
return paybackAmount;
|
|
}
|
|
|
|
function _addReserveToList(address asset) internal {
|
|
uint256 reservesCount = _reservesCount;
|
|
|
|
require(reservesCount < _maxNumberOfReserves, Errors.LP_NO_MORE_RESERVES_ALLOWED);
|
|
|
|
bool reserveAlreadyAdded = _reserves[asset].id != 0 || _reservesList[0] == asset;
|
|
|
|
if (!reserveAlreadyAdded) {
|
|
_reserves[asset].id = uint8(reservesCount);
|
|
_reservesList[reservesCount] = asset;
|
|
|
|
_reservesCount = reservesCount + 1;
|
|
}
|
|
}
|
|
}
|