From b889cb25b67dcdcf1b0e4c587aeb3c246d974e86 Mon Sep 17 00:00:00 2001 From: eboado Date: Fri, 29 May 2020 18:45:37 +0200 Subject: [PATCH] - Added contracts from v1 of the protocol. --- contracts/configuration/AddressStorage.sol | 15 + .../LendingPoolAddressesProvider.sol | 239 +++ .../LendingPoolAddressesProviderRegistry.sol | 86 + .../LendingPoolParametersProvider.sol | 54 + contracts/configuration/UintStorage.sol | 15 + contracts/fees/FeeProvider.sol | 52 + contracts/fees/MockKyberProxy.sol | 45 + contracts/fees/MockOneSplit.sol | 63 + contracts/fees/OneSplitAdapter.sol | 79 + contracts/fees/TokenDistributor.sol | 218 ++ .../flashloan/base/FlashLoanReceiverBase.sol | 52 + .../interfaces/IFlashLoanReceiver.sol | 13 + contracts/interfaces/IChainlinkAggregator.sol | 13 + contracts/interfaces/IERC20Detailed.sol | 10 + contracts/interfaces/IExchangeAdapter.sol | 24 + contracts/interfaces/IFeeProvider.sol | 12 + .../IKyberNetworkProxyInterface.sol | 23 + .../ILendingPoolAddressesProvider.sol | 45 + .../ILendingPoolAddressesProviderRegistry.sol | 16 + contracts/interfaces/ILendingRateOracle.sol | 19 + contracts/interfaces/IOneSplit.sol | 60 + contracts/interfaces/IPriceOracle.sol | 18 + contracts/interfaces/IPriceOracleGetter.sol | 16 + .../IReserveInterestRateStrategy.sol | 30 + contracts/interfaces/IUniswapExchange.sol | 9 + .../DefaultReserveInterestRateStrategy.sol | 203 ++ contracts/lendingpool/LendingPool.sol | 1008 +++++++++ .../lendingpool/LendingPoolConfigurator.sol | 451 +++++ contracts/lendingpool/LendingPoolCore.sol | 1802 +++++++++++++++++ .../lendingpool/LendingPoolDataProvider.sol | 482 +++++ .../LendingPoolLiquidationManager.sol | 360 ++++ contracts/libraries/CoreLibrary.sol | 440 ++++ contracts/libraries/EthAddressLib.sol | 13 + contracts/libraries/UintConstants.sol | 20 + contracts/libraries/WadRayMath.sol | 134 ++ .../AdminUpgradeabilityProxy.sol | 33 + .../BaseAdminUpgradeabilityProxy.sol | 121 ++ .../BaseUpgradeabilityProxy.sol | 65 + .../Initializable.sol | 63 + .../InitializableAdminUpgradeabilityProxy.sol | 36 + .../InitializableUpgradeabilityProxy.sol | 29 + .../openzeppelin-upgradeability/Proxy.sol | 72 + .../UpgradeabilityProxy.sol | 28 + .../VersionedInitializable.sol | 71 + .../misc/ChainlinkProxyPriceProvider.sol | 110 + contracts/misc/IERC20DetailedBytes.sol | 8 + contracts/misc/WalletBalanceProvider.sol | 103 + .../mocks/flashloan/MockFlashLoanReceiver.sol | 52 + .../CLAggregators/MockAggregatorBAT.sol | 8 + .../CLAggregators/MockAggregatorBUSD.sol | 8 + .../CLAggregators/MockAggregatorBase.sol | 17 + .../CLAggregators/MockAggregatorDAI.sol | 8 + .../CLAggregators/MockAggregatorKNC.sol | 8 + .../CLAggregators/MockAggregatorLEND.sol | 8 + .../CLAggregators/MockAggregatorLINK.sol | 8 + .../CLAggregators/MockAggregatorMANA.sol | 8 + .../CLAggregators/MockAggregatorMKR.sol | 8 + .../CLAggregators/MockAggregatorREP.sol | 8 + .../CLAggregators/MockAggregatorSNX.sol | 8 + .../CLAggregators/MockAggregatorSUSD.sol | 8 + .../CLAggregators/MockAggregatorTUSD.sol | 8 + .../MockAggregatorUNI_DAI_ETH.sol | 8 + .../MockAggregatorUNI_LEND_ETH.sol | 8 + .../MockAggregatorUNI_LINK_ETH.sol | 8 + .../MockAggregatorUNI_MKR_ETH.sol | 8 + .../MockAggregatorUNI_SETH_ETH.sol | 8 + .../MockAggregatorUNI_USDC_ETH.sol | 8 + .../CLAggregators/MockAggregatorUSD.sol | 8 + .../CLAggregators/MockAggregatorUSDC.sol | 8 + .../CLAggregators/MockAggregatorUSDT.sol | 8 + .../CLAggregators/MockAggregatorWBTC.sol | 8 + .../CLAggregators/MockAggregatorZRX.sol | 8 + .../mocks/oracle/ChainlinkUSDETHOracleI.sol | 7 + contracts/mocks/oracle/GenericOracleI.sol | 20 + .../mocks/oracle/IExtendedPriceAggregator.sol | 12 + contracts/mocks/oracle/LendingRateOracle.sol | 28 + contracts/mocks/oracle/PriceOracle.sol | 32 + contracts/mocks/tokens/MintableERC20.sol | 23 + contracts/mocks/tokens/MockBAT.sol | 9 + contracts/mocks/tokens/MockBUSD.sol | 8 + contracts/mocks/tokens/MockDAI.sol | 9 + contracts/mocks/tokens/MockKNC.sol | 8 + contracts/mocks/tokens/MockLEND.sol | 9 + contracts/mocks/tokens/MockLINK.sol | 8 + contracts/mocks/tokens/MockMANA.sol | 8 + contracts/mocks/tokens/MockMKR.sol | 8 + contracts/mocks/tokens/MockREP.sol | 8 + contracts/mocks/tokens/MockSNX.sol | 8 + contracts/mocks/tokens/MockSUSD.sol | 8 + contracts/mocks/tokens/MockTUSD.sol | 8 + contracts/mocks/tokens/MockUNI_DAI_ETH.sol | 8 + contracts/mocks/tokens/MockUNI_LEND_ETH.sol | 8 + contracts/mocks/tokens/MockUNI_LINK_ETH.sol | 8 + contracts/mocks/tokens/MockUNI_MKR_ETH.sol | 8 + contracts/mocks/tokens/MockUNI_SETH_ETH.sol | 8 + contracts/mocks/tokens/MockUNI_USDC_ETH.sol | 8 + contracts/mocks/tokens/MockUSD.sol | 8 + contracts/mocks/tokens/MockUSDC.sol | 8 + contracts/mocks/tokens/MockUSDT.sol | 8 + contracts/mocks/tokens/MockWBTC.sol | 8 + contracts/mocks/tokens/MockZRX.sol | 8 + .../upgradeability/MockLendingPoolCore.sol | 36 + contracts/tokenization/AToken.sol | 674 ++++++ contracts/tokenization/ERC20.sol | 305 +++ package-lock.json | 2 +- 105 files changed, 8456 insertions(+), 1 deletion(-) create mode 100644 contracts/configuration/AddressStorage.sol create mode 100644 contracts/configuration/LendingPoolAddressesProvider.sol create mode 100644 contracts/configuration/LendingPoolAddressesProviderRegistry.sol create mode 100644 contracts/configuration/LendingPoolParametersProvider.sol create mode 100644 contracts/configuration/UintStorage.sol create mode 100644 contracts/fees/FeeProvider.sol create mode 100644 contracts/fees/MockKyberProxy.sol create mode 100644 contracts/fees/MockOneSplit.sol create mode 100644 contracts/fees/OneSplitAdapter.sol create mode 100644 contracts/fees/TokenDistributor.sol create mode 100644 contracts/flashloan/base/FlashLoanReceiverBase.sol create mode 100644 contracts/flashloan/interfaces/IFlashLoanReceiver.sol create mode 100644 contracts/interfaces/IChainlinkAggregator.sol create mode 100644 contracts/interfaces/IERC20Detailed.sol create mode 100644 contracts/interfaces/IExchangeAdapter.sol create mode 100644 contracts/interfaces/IFeeProvider.sol create mode 100644 contracts/interfaces/IKyberNetworkProxyInterface.sol create mode 100644 contracts/interfaces/ILendingPoolAddressesProvider.sol create mode 100644 contracts/interfaces/ILendingPoolAddressesProviderRegistry.sol create mode 100644 contracts/interfaces/ILendingRateOracle.sol create mode 100644 contracts/interfaces/IOneSplit.sol create mode 100644 contracts/interfaces/IPriceOracle.sol create mode 100644 contracts/interfaces/IPriceOracleGetter.sol create mode 100644 contracts/interfaces/IReserveInterestRateStrategy.sol create mode 100644 contracts/interfaces/IUniswapExchange.sol create mode 100644 contracts/lendingpool/DefaultReserveInterestRateStrategy.sol create mode 100644 contracts/lendingpool/LendingPool.sol create mode 100644 contracts/lendingpool/LendingPoolConfigurator.sol create mode 100644 contracts/lendingpool/LendingPoolCore.sol create mode 100644 contracts/lendingpool/LendingPoolDataProvider.sol create mode 100644 contracts/lendingpool/LendingPoolLiquidationManager.sol create mode 100644 contracts/libraries/CoreLibrary.sol create mode 100644 contracts/libraries/EthAddressLib.sol create mode 100644 contracts/libraries/UintConstants.sol create mode 100644 contracts/libraries/WadRayMath.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/AdminUpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/BaseAdminUpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/BaseUpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/Initializable.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/InitializableUpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/Proxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/UpgradeabilityProxy.sol create mode 100644 contracts/libraries/openzeppelin-upgradeability/VersionedInitializable.sol create mode 100644 contracts/misc/ChainlinkProxyPriceProvider.sol create mode 100644 contracts/misc/IERC20DetailedBytes.sol create mode 100644 contracts/misc/WalletBalanceProvider.sol create mode 100644 contracts/mocks/flashloan/MockFlashLoanReceiver.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorBAT.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorBUSD.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorBase.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorDAI.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorKNC.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorLEND.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorLINK.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorMANA.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorMKR.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorREP.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorSNX.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorSUSD.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorTUSD.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_DAI_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LEND_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LINK_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_MKR_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_SETH_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_USDC_ETH.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUSD.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUSDC.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorUSDT.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorWBTC.sol create mode 100644 contracts/mocks/oracle/CLAggregators/MockAggregatorZRX.sol create mode 100644 contracts/mocks/oracle/ChainlinkUSDETHOracleI.sol create mode 100644 contracts/mocks/oracle/GenericOracleI.sol create mode 100644 contracts/mocks/oracle/IExtendedPriceAggregator.sol create mode 100644 contracts/mocks/oracle/LendingRateOracle.sol create mode 100644 contracts/mocks/oracle/PriceOracle.sol create mode 100644 contracts/mocks/tokens/MintableERC20.sol create mode 100644 contracts/mocks/tokens/MockBAT.sol create mode 100644 contracts/mocks/tokens/MockBUSD.sol create mode 100644 contracts/mocks/tokens/MockDAI.sol create mode 100644 contracts/mocks/tokens/MockKNC.sol create mode 100644 contracts/mocks/tokens/MockLEND.sol create mode 100644 contracts/mocks/tokens/MockLINK.sol create mode 100644 contracts/mocks/tokens/MockMANA.sol create mode 100644 contracts/mocks/tokens/MockMKR.sol create mode 100644 contracts/mocks/tokens/MockREP.sol create mode 100644 contracts/mocks/tokens/MockSNX.sol create mode 100644 contracts/mocks/tokens/MockSUSD.sol create mode 100644 contracts/mocks/tokens/MockTUSD.sol create mode 100644 contracts/mocks/tokens/MockUNI_DAI_ETH.sol create mode 100644 contracts/mocks/tokens/MockUNI_LEND_ETH.sol create mode 100644 contracts/mocks/tokens/MockUNI_LINK_ETH.sol create mode 100644 contracts/mocks/tokens/MockUNI_MKR_ETH.sol create mode 100644 contracts/mocks/tokens/MockUNI_SETH_ETH.sol create mode 100644 contracts/mocks/tokens/MockUNI_USDC_ETH.sol create mode 100644 contracts/mocks/tokens/MockUSD.sol create mode 100644 contracts/mocks/tokens/MockUSDC.sol create mode 100644 contracts/mocks/tokens/MockUSDT.sol create mode 100644 contracts/mocks/tokens/MockWBTC.sol create mode 100644 contracts/mocks/tokens/MockZRX.sol create mode 100644 contracts/mocks/upgradeability/MockLendingPoolCore.sol create mode 100644 contracts/tokenization/AToken.sol create mode 100644 contracts/tokenization/ERC20.sol diff --git a/contracts/configuration/AddressStorage.sol b/contracts/configuration/AddressStorage.sol new file mode 100644 index 00000000..9ec5182c --- /dev/null +++ b/contracts/configuration/AddressStorage.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +contract AddressStorage { + mapping(bytes32 => address) private addresses; + + function getAddress(bytes32 _key) public view returns (address) { + return addresses[_key]; + } + + function _setAddress(bytes32 _key, address _value) internal { + addresses[_key] = _value; + } + +} diff --git a/contracts/configuration/LendingPoolAddressesProvider.sol b/contracts/configuration/LendingPoolAddressesProvider.sol new file mode 100644 index 00000000..53dd8c10 --- /dev/null +++ b/contracts/configuration/LendingPoolAddressesProvider.sol @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol"; + +import "./AddressStorage.sol"; +import "../interfaces/ILendingPoolAddressesProvider.sol"; + +/** +* @title LendingPoolAddressesProvider contract +* @notice Is the main registry of the protocol. All the different components of the protocol are accessible +* through the addresses provider. +* @author Aave +**/ + +contract LendingPoolAddressesProvider is Ownable, ILendingPoolAddressesProvider, AddressStorage { + //events + event LendingPoolUpdated(address indexed newAddress); + event LendingPoolCoreUpdated(address indexed newAddress); + event LendingPoolParametersProviderUpdated(address indexed newAddress); + event LendingPoolManagerUpdated(address indexed newAddress); + event LendingPoolConfiguratorUpdated(address indexed newAddress); + event LendingPoolLiquidationManagerUpdated(address indexed newAddress); + event LendingPoolDataProviderUpdated(address indexed newAddress); + event EthereumAddressUpdated(address indexed newAddress); + event PriceOracleUpdated(address indexed newAddress); + event LendingRateOracleUpdated(address indexed newAddress); + event FeeProviderUpdated(address indexed newAddress); + event TokenDistributorUpdated(address indexed newAddress); + + event ProxyCreated(bytes32 id, address indexed newAddress); + + bytes32 private constant LENDING_POOL = "LENDING_POOL"; + bytes32 private constant LENDING_POOL_CORE = "LENDING_POOL_CORE"; + bytes32 private constant LENDING_POOL_CONFIGURATOR = "LENDING_POOL_CONFIGURATOR"; + bytes32 private constant LENDING_POOL_PARAMETERS_PROVIDER = "PARAMETERS_PROVIDER"; + bytes32 private constant LENDING_POOL_MANAGER = "LENDING_POOL_MANAGER"; + bytes32 private constant LENDING_POOL_LIQUIDATION_MANAGER = "LIQUIDATION_MANAGER"; + bytes32 private constant LENDING_POOL_FLASHLOAN_PROVIDER = "FLASHLOAN_PROVIDER"; + bytes32 private constant DATA_PROVIDER = "DATA_PROVIDER"; + bytes32 private constant ETHEREUM_ADDRESS = "ETHEREUM_ADDRESS"; + bytes32 private constant PRICE_ORACLE = "PRICE_ORACLE"; + bytes32 private constant LENDING_RATE_ORACLE = "LENDING_RATE_ORACLE"; + bytes32 private constant FEE_PROVIDER = "FEE_PROVIDER"; + bytes32 private constant WALLET_BALANCE_PROVIDER = "WALLET_BALANCE_PROVIDER"; + bytes32 private constant TOKEN_DISTRIBUTOR = "TOKEN_DISTRIBUTOR"; + + + /** + * @dev returns the address of the LendingPool proxy + * @return the lending pool proxy address + **/ + function getLendingPool() public override view returns (address) { + return getAddress(LENDING_POOL); + } + + + /** + * @dev updates the implementation of the lending pool + * @param _pool the new lending pool implementation + **/ + function setLendingPoolImpl(address _pool) public override onlyOwner { + updateImplInternal(LENDING_POOL, _pool); + emit LendingPoolUpdated(_pool); + } + + /** + * @dev returns the address of the LendingPoolCore proxy + * @return the lending pool core proxy address + */ + function getLendingPoolCore() public override view returns (address payable) { + address payable core = payable(getAddress(LENDING_POOL_CORE)); + return core; + } + + /** + * @dev updates the implementation of the lending pool core + * @param _lendingPoolCore the new lending pool core implementation + **/ + function setLendingPoolCoreImpl(address _lendingPoolCore) public override onlyOwner { + updateImplInternal(LENDING_POOL_CORE, _lendingPoolCore); + emit LendingPoolCoreUpdated(_lendingPoolCore); + } + + /** + * @dev returns the address of the LendingPoolConfigurator proxy + * @return the lending pool configurator proxy address + **/ + function getLendingPoolConfigurator() public override view returns (address) { + return getAddress(LENDING_POOL_CONFIGURATOR); + } + + /** + * @dev updates the implementation of the lending pool configurator + * @param _configurator the new lending pool configurator implementation + **/ + function setLendingPoolConfiguratorImpl(address _configurator) public override onlyOwner { + updateImplInternal(LENDING_POOL_CONFIGURATOR, _configurator); + emit LendingPoolConfiguratorUpdated(_configurator); + } + + /** + * @dev returns the address of the LendingPoolDataProvider proxy + * @return the lending pool data provider proxy address + */ + function getLendingPoolDataProvider() public override view returns (address) { + return getAddress(DATA_PROVIDER); + } + + /** + * @dev updates the implementation of the lending pool data provider + * @param _provider the new lending pool data provider implementation + **/ + function setLendingPoolDataProviderImpl(address _provider) public override onlyOwner { + updateImplInternal(DATA_PROVIDER, _provider); + emit LendingPoolDataProviderUpdated(_provider); + } + + /** + * @dev returns the address of the LendingPoolParametersProvider proxy + * @return the address of the Lending pool parameters provider proxy + **/ + function getLendingPoolParametersProvider() public override view returns (address) { + return getAddress(LENDING_POOL_PARAMETERS_PROVIDER); + } + + /** + * @dev updates the implementation of the lending pool parameters provider + * @param _parametersProvider the new lending pool parameters provider implementation + **/ + function setLendingPoolParametersProviderImpl(address _parametersProvider) public override onlyOwner { + updateImplInternal(LENDING_POOL_PARAMETERS_PROVIDER, _parametersProvider); + emit LendingPoolParametersProviderUpdated(_parametersProvider); + } + + /** + * @dev returns the address of the FeeProvider proxy + * @return the address of the Fee provider proxy + **/ + function getFeeProvider() public override view returns (address) { + return getAddress(FEE_PROVIDER); + } + + /** + * @dev updates the implementation of the FeeProvider proxy + * @param _feeProvider the new lending pool fee provider implementation + **/ + function setFeeProviderImpl(address _feeProvider) public override onlyOwner { + updateImplInternal(FEE_PROVIDER, _feeProvider); + emit FeeProviderUpdated(_feeProvider); + } + + /** + * @dev returns the address of the LendingPoolLiquidationManager. Since the manager is used + * through delegateCall within the LendingPool contract, the proxy contract pattern does not work properly hence + * the addresses are changed directly. + * @return the address of the Lending pool liquidation manager + **/ + + function getLendingPoolLiquidationManager() public override view returns (address) { + return getAddress(LENDING_POOL_LIQUIDATION_MANAGER); + } + + /** + * @dev updates the address of the Lending pool liquidation manager + * @param _manager the new lending pool liquidation manager address + **/ + function setLendingPoolLiquidationManager(address _manager) public override onlyOwner { + _setAddress(LENDING_POOL_LIQUIDATION_MANAGER, _manager); + emit LendingPoolLiquidationManagerUpdated(_manager); + } + + /** + * @dev the functions below are storing specific addresses that are outside the context of the protocol + * hence the upgradable proxy pattern is not used + **/ + + + function getLendingPoolManager() public override view returns (address) { + return getAddress(LENDING_POOL_MANAGER); + } + + function setLendingPoolManager(address _lendingPoolManager) public override onlyOwner { + _setAddress(LENDING_POOL_MANAGER, _lendingPoolManager); + emit LendingPoolManagerUpdated(_lendingPoolManager); + } + + function getPriceOracle() public override view returns (address) { + return getAddress(PRICE_ORACLE); + } + + function setPriceOracle(address _priceOracle) public override onlyOwner { + _setAddress(PRICE_ORACLE, _priceOracle); + emit PriceOracleUpdated(_priceOracle); + } + + function getLendingRateOracle() public override view returns (address) { + return getAddress(LENDING_RATE_ORACLE); + } + + function setLendingRateOracle(address _lendingRateOracle) public override onlyOwner { + _setAddress(LENDING_RATE_ORACLE, _lendingRateOracle); + emit LendingRateOracleUpdated(_lendingRateOracle); + } + + + function getTokenDistributor() public override view returns (address) { + return getAddress(TOKEN_DISTRIBUTOR); + } + + function setTokenDistributor(address _tokenDistributor) public override onlyOwner { + _setAddress(TOKEN_DISTRIBUTOR, _tokenDistributor); + emit TokenDistributorUpdated(_tokenDistributor); + } + + + /** + * @dev internal function to update the implementation of a specific component of the protocol + * @param _id the id of the contract to be updated + * @param _newAddress the address of the new implementation + **/ + function updateImplInternal(bytes32 _id, address _newAddress) internal { + address payable proxyAddress = address(uint160(getAddress(_id))); + + InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(proxyAddress); + bytes memory params = abi.encodeWithSignature("initialize(address)", address(this)); + + if (proxyAddress == address(0)) { + proxy = new InitializableAdminUpgradeabilityProxy(); + proxy.initialize(_newAddress, address(this), params); + _setAddress(_id, address(proxy)); + emit ProxyCreated(_id, address(proxy)); + } else { + proxy.upgradeToAndCall(_newAddress, params); + } + + } +} diff --git a/contracts/configuration/LendingPoolAddressesProviderRegistry.sol b/contracts/configuration/LendingPoolAddressesProviderRegistry.sol new file mode 100644 index 00000000..33cb1723 --- /dev/null +++ b/contracts/configuration/LendingPoolAddressesProviderRegistry.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/access/Ownable.sol"; +import "../interfaces/ILendingPoolAddressesProviderRegistry.sol"; + + + +/** +* @title LendingPoolAddressesProviderRegistry contract +* @notice contains the list of active addresses providers +* @author Aave +**/ + +contract LendingPoolAddressesProviderRegistry is Ownable, ILendingPoolAddressesProviderRegistry { + //events + event AddressesProviderRegistered(address indexed newAddress); + event AddressesProviderUnregistered(address indexed newAddress); + + mapping(address => uint256) addressesProviders; + address[] addressesProvidersList; + + /** + * @dev returns if an addressesProvider is registered or not + * @param _provider the addresses provider + * @return true if the addressesProvider is registered, false otherwise + **/ + function isAddressesProviderRegistered(address _provider) external override view returns(uint256) { + return addressesProviders[_provider]; + } + + /** + * @dev returns the list of active addressesProviders + * @return the list of addressesProviders + **/ + function getAddressesProvidersList() external override view returns(address[] memory) { + + uint256 maxLength = addressesProvidersList.length; + + address[] memory activeProviders = new address[](maxLength); + + for(uint256 i = 0; i 0){ + activeProviders[i] = addressesProvidersList[i]; + } + } + + return activeProviders; + } + + /** + * @dev adds a lending pool to the list of registered lending pools + * @param _provider the pool address to be registered + **/ + function registerAddressesProvider(address _provider, uint256 _id) public override onlyOwner { + addressesProviders[_provider] = _id; + addToAddressesProvidersListInternal(_provider); + emit AddressesProviderRegistered(_provider); + } + + /** + * @dev removes a lending pool from the list of registered lending pools + * @param _provider the pool address to be unregistered + **/ + function unregisterAddressesProvider(address _provider) public override onlyOwner { + require(addressesProviders[_provider] > 0, "Provider is not registered"); + addressesProviders[_provider] = 0; + emit AddressesProviderUnregistered(_provider); + } + + /** + * @dev adds to the list of the addresses providers, if it wasn't already added before + * @param _provider the pool address to be added + **/ + function addToAddressesProvidersListInternal(address _provider) internal { + + for(uint256 i = 0; i < addressesProvidersList.length; i++) { + + if(addressesProvidersList[i] == _provider){ + return; + } + } + + addressesProvidersList.push(_provider); + } +} diff --git a/contracts/configuration/LendingPoolParametersProvider.sol b/contracts/configuration/LendingPoolParametersProvider.sol new file mode 100644 index 00000000..d8484d48 --- /dev/null +++ b/contracts/configuration/LendingPoolParametersProvider.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; +import "./UintStorage.sol"; + +/** +* @title LendingPoolParametersProvider +* @author Aave +* @notice stores the configuration parameters of the Lending Pool contract +**/ + +contract LendingPoolParametersProvider is VersionedInitializable { + + uint256 private constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25; + uint256 private constant REBALANCE_DOWN_RATE_DELTA = (1e27)/5; + uint256 private constant FLASHLOAN_FEE_TOTAL = 9; + uint256 private constant FLASHLOAN_FEE_PROTOCOL = 3000; + + uint256 constant private DATA_PROVIDER_REVISION = 0x2; + + function getRevision() internal override pure returns(uint256) { + return DATA_PROVIDER_REVISION; + } + + /** + * @dev initializes the LendingPoolParametersProvider after it's added to the proxy + * @param _addressesProvider the address of the LendingPoolAddressesProvider + */ + function initialize(address _addressesProvider) public initializer { + } + /** + * @dev returns the maximum stable rate borrow size, in percentage of the available liquidity. + **/ + function getMaxStableRateBorrowSizePercent() external pure returns (uint256) { + return MAX_STABLE_RATE_BORROW_SIZE_PERCENT; + } + + /** + * @dev returns the delta between the current stable rate and the user stable rate at + * which the borrow position of the user will be rebalanced (scaled down) + **/ + function getRebalanceDownRateDelta() external pure returns (uint256) { + return REBALANCE_DOWN_RATE_DELTA; + } + + /** + * @dev returns the fee applied to a flashloan and the portion to redirect to the protocol, in basis points. + **/ + function getFlashLoanFeesInBips() external pure returns (uint256, uint256) { + return (FLASHLOAN_FEE_TOTAL, FLASHLOAN_FEE_PROTOCOL); + } +} diff --git a/contracts/configuration/UintStorage.sol b/contracts/configuration/UintStorage.sol new file mode 100644 index 00000000..d891bb94 --- /dev/null +++ b/contracts/configuration/UintStorage.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +contract UintStorage { + mapping(bytes32 => uint256) private uints; + + function getUint(bytes32 _key) public view returns (uint256) { + return uints[_key]; + } + + function _setUint(bytes32 _key, uint256 _value) internal { + uints[_key] = _value; + } + +} diff --git a/contracts/fees/FeeProvider.sol b/contracts/fees/FeeProvider.sol new file mode 100644 index 00000000..124f5869 --- /dev/null +++ b/contracts/fees/FeeProvider.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; +import "../interfaces/IFeeProvider.sol"; +import "../libraries/WadRayMath.sol"; + + +/** +* @title FeeProvider contract +* @notice Implements calculation for the fees applied by the protocol +* @author Aave +**/ +contract FeeProvider is IFeeProvider, VersionedInitializable { + using WadRayMath for uint256; + + // percentage of the fee to be calculated on the loan amount + uint256 public originationFeePercentage; + + + uint256 constant public FEE_PROVIDER_REVISION = 0x1; + + function getRevision() internal override pure returns(uint256) { + return FEE_PROVIDER_REVISION; + } + /** + * @dev initializes the FeeProvider after it's added to the proxy + * @param _addressesProvider the address of the LendingPoolAddressesProvider + */ + function initialize(address _addressesProvider) public initializer { + /// @notice origination fee is set as default as 25 basis points of the loan amount (0.0025%) + originationFeePercentage = 0.0025 * 1e18; + } + + /** + * @dev calculates the origination fee for every loan executed on the platform. + * @param _user can be used in the future to apply discount to the origination fee based on the + * _user account (eg. stake AAVE tokens in the lending pool, or deposit > 1M USD etc.) + * @param _amount the amount of the loan + **/ + function calculateLoanOriginationFee(address _user, uint256 _amount) external override view returns (uint256) { + return _amount.wadMul(originationFeePercentage); + } + + /** + * @dev returns the origination fee percentage + **/ + function getLoanOriginationFeePercentage() external override view returns (uint256) { + return originationFeePercentage; + } + +} diff --git a/contracts/fees/MockKyberProxy.sol b/contracts/fees/MockKyberProxy.sol new file mode 100644 index 00000000..3f8e4629 --- /dev/null +++ b/contracts/fees/MockKyberProxy.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +import "../libraries/EthAddressLib.sol"; +import "../mocks/tokens/MintableERC20.sol"; + +/// @title MockKyberProxy +/// @author Aave +/// @notice Mock contract to simulate the behaviour of the Kyber DEX +/// - Receives ETH/tokens +/// - Mints the tokenToBurn +/// - Sends back the tokenToBurn +contract MockKyberProxy { + using SafeERC20 for IERC20; + using SafeERC20 for MintableERC20; + + /// @notice The token which the msg.sender of tradeWithHint will burn + MintableERC20 public tokenToBurn; + + constructor(MintableERC20 _tokenToBurn) public { + tokenToBurn = _tokenToBurn; + } + + /// @notice Simulates the function with the same name on the Kyber Proxy contract + function tradeWithHint( + IERC20 _fromToken, + uint256 _amount, + IERC20 _toToken, + address _receiver, + uint256 _maxAmount, + uint minConversionRate, + address _referral, + bytes calldata _filtering + ) external payable returns(uint256) { + require(tokenToBurn.mint(1 ether), "TRADE_WITH_HINT. Reverted mint()"); + if (address(_fromToken) != EthAddressLib.ethAddress()) { + _fromToken.safeTransferFrom(msg.sender, address(this), _amount); + } + tokenToBurn.safeTransfer(msg.sender, 1 ether); + return 1 ether; + } +} \ No newline at end of file diff --git a/contracts/fees/MockOneSplit.sol b/contracts/fees/MockOneSplit.sol new file mode 100644 index 00000000..51e4a02b --- /dev/null +++ b/contracts/fees/MockOneSplit.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +import "../libraries/EthAddressLib.sol"; +import "../mocks/tokens/MintableERC20.sol"; + +import "../interfaces/IOneSplit.sol"; + +contract MockOneSplit is IOneSplit { + using SafeERC20 for IERC20; + using SafeERC20 for MintableERC20; + + MintableERC20 public tokenToBurn; + + constructor(MintableERC20 _tokenToBurn) public { + tokenToBurn = _tokenToBurn; + } + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags // 1 - Uniswap, 2 - Kyber, 4 - Bancor, 8 - Oasis, 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) + public + override + view + returns( + uint256 returnAmount, + uint256[] memory distribution // [Uniswap, Kyber, Bancor, Oasis] + ) { + return (0, new uint256[](0)); + } + + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 minReturn, + uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] + uint256 disableFlags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) public override payable { + } + + function goodSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 minReturn, + uint256 parts, + uint256 disableFlags + ) public override payable { + require(tokenToBurn.mint(10000 ether), "TRADE_WITH_HINT. Reverted mint()"); + if (address(fromToken) != EthAddressLib.ethAddress()) { + fromToken.safeTransferFrom(msg.sender, address(this), amount); + } + tokenToBurn.safeTransfer(msg.sender, 10000 ether); + } +} \ No newline at end of file diff --git a/contracts/fees/OneSplitAdapter.sol b/contracts/fees/OneSplitAdapter.sol new file mode 100644 index 00000000..b4773ac3 --- /dev/null +++ b/contracts/fees/OneSplitAdapter.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "../interfaces/IOneSplit.sol"; +import "../interfaces/IPriceOracleGetter.sol"; +import "../interfaces/IExchangeAdapter.sol"; + +/// @title OneSplitAdapter +/// @author Aave +/// @notice Implements the logic to exchange assets through 1Split +/// The hardcoded parameters are: +/// 0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476: ONE_SPLIT. The address of the 1Split exchange +/// 0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4: AAVE_PRICES_PROVIDER. Contract providing prices of the assets +/// in the Aave protocol, in token/ETH. +/// 512 : MULTI_PATH_ETH_FLAG. By using this flag on OneSplit, the swap sequence will introduce a step to ETH +/// in the middle, resulting in a sequence like FROM-to-ETH -> ETH-to-TO. +/// This is optimal for cases where the pair FROM/TO is not liquid enough in the +/// underlying exchanges used by OneSplit, reducing this way the slippage. +/// 10: SPLIT_PARTS. It defines in how many chunks the amount to swap will be splitted to then +/// divide the chunks amongst the underlying exchanges. +/// For example, using 10 as SPLIT_PARTS and having 4 underlying exchanges on 1Split, +/// the division amongst could look like [4,4,0,2]. + +contract OneSplitAdapter is IExchangeAdapter { + using SafeMath for uint256; + + event OneSplitAdapterSetup(address oneSplit, address priceOracle, uint256 splitParts); + + constructor() public { + emit OneSplitAdapterSetup(0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, 0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4, 10); + } + + /// @notice "Infinite" approval for all the tokens initialized + /// @param _tokens the list of token addresses to approve + function approveExchange(IERC20[] calldata _tokens) external override { + for (uint256 i = 0; i < _tokens.length; i++) { + if (address(_tokens[i]) != EthAddressLib.ethAddress()) { + _tokens[i].safeApprove(0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, UintConstants.maxUintMinus1()); + } + } + } + + /// @notice Exchanges _amount of _from token (or ETH) to _to token (or ETH) + /// - Uses EthAddressLib.ethAddress() as the reference on 1Split of ETH + /// @param _from The asset to exchange from + /// @param _to The asset to exchange to + /// @param _amount The amount to exchange + /// @param _maxSlippage Max slippage acceptable, taken into account after the goodSwap() + function exchange(address _from, address _to, uint256 _amount, uint256 _maxSlippage) external override returns(uint256) { + uint256 _value = (_from == EthAddressLib.ethAddress()) ? _amount : 0; + + uint256 _fromAssetPriceInWei = IPriceOracleGetter(0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4).getAssetPrice(_from); + uint256 _toAssetPriceInWei = IPriceOracleGetter(0x76B47460d7F7c5222cFb6b6A75615ab10895DDe4).getAssetPrice(_to); + uint256 _toBalanceBefore = IERC20(_to).balanceOf(address(this)); + + IOneSplit(0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476).goodSwap{value: _value}( + IERC20(_from), + IERC20(_to), + _amount, + 0, + 10, + 512 + ); + + uint256 _toReceivedAmount = IERC20(_to).balanceOf(address(this)).sub(_toBalanceBefore); + + require( + (_toAssetPriceInWei.mul(_toReceivedAmount).mul(100)) + .div(_fromAssetPriceInWei.mul(_amount)) >= (100 - _maxSlippage), + "INVALID_SLIPPAGE" + ); + + emit Exchange(_from, _to, 0x1814222fa8c8c1C1bf380e3BBFBd9De8657Da476, _amount, _toReceivedAmount); + return _toReceivedAmount; + } +} \ No newline at end of file diff --git a/contracts/fees/TokenDistributor.sol b/contracts/fees/TokenDistributor.sol new file mode 100644 index 00000000..0c6f6bbd --- /dev/null +++ b/contracts/fees/TokenDistributor.sol @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20Burnable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; +import "../interfaces/IKyberNetworkProxyInterface.sol"; +import "../interfaces/IExchangeAdapter.sol"; +import "../libraries/EthAddressLib.sol"; + + +/// @title TokenDistributor +/// @author Aave +/// @notice Receives tokens and manages the distribution amongst receivers +/// The usage is as follows: +/// - The distribution addresses and percentages are set up on construction +/// - The Kyber Proxy is approved for a list of tokens in construction, which will be later burnt +/// - At any moment, anyone can call distribute() with a list of token addresses in order to distribute +/// the accumulated token amounts and/or ETH in this contract to all the receivers with percentages +/// - If the address(0) is used as receiver, this contract will trade in Kyber to tokenToBurn (LEND) +/// and burn it (sending to address(0) the tokenToBurn) +contract TokenDistributor is ReentrancyGuard, VersionedInitializable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + struct Distribution { + address[] receivers; + uint256[] percentages; + } + + event DistributionUpdated(address[] receivers, uint256[] percentages); + event Distributed(address receiver, uint256 percentage, uint256 amount); + event Setup(address tokenToBurn, IExchangeAdapter exchangeAdapter, address _recipientBurn); + event Burn(uint256 amount); + + uint256 public constant IMPLEMENTATION_REVISION = 0x3; + + /// @notice DEPRECATED + uint256 public constant MAX_UINT = 2**256 - 1; + + /// @notice DEPRECATED + uint256 public constant MAX_UINT_MINUS_ONE = (2**256 - 1) - 1; + + /// @notice DEPRECATED + uint256 public constant MIN_CONVERSION_RATE = 1; + + /// @notice DEPRECATED + address public constant KYBER_ETH_MOCK_ADDRESS = address(0x00eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee); + + /// @notice Defines how tokens and ETH are distributed on each call to .distribute() + Distribution private distribution; + + /// @notice Instead of using 100 for percentages, higher base to have more precision in the distribution + uint256 public constant DISTRIBUTION_BASE = 10000; + + /// @notice DEPRECATED + IKyberNetworkProxyInterface public kyberProxy; + + /// @notice The address of the token to burn (LEND token) + address public tokenToBurn; + + /// @notice Address to send tokens to "burn". + /// Because of limitations on OZ ERC20, on dev it's needed to use the 0x00000...1 address instead of address(0) + /// So this param needs to be received on construction + address public recipientBurn; + + /// @notice Smart contract implementing the logic to interact with a particular exchange. + /// Will be called by DELEGATECALL + IExchangeAdapter public exchangeAdapter; + + /// @notice Called by the proxy when setting this contract as implementation + function initialize( + address _recipientBurn, + address _tokenToBurn, + IExchangeAdapter _exchangeAdapter, + address[] memory _receivers, + uint256[] memory _percentages, + IERC20[] memory _tokens + ) public initializer { + recipientBurn = _recipientBurn; + tokenToBurn = _tokenToBurn; + exchangeAdapter = _exchangeAdapter; + internalSetTokenDistribution(_receivers, _percentages); + approveExchange(_tokens); + emit Setup(_tokenToBurn, _exchangeAdapter, _recipientBurn); + } + + /// @notice In order to receive ETH transfers + receive() external payable {} + + /// @notice "Infinite" approval for all the tokens initialized + /// @param _tokens List of IERC20 to approve + function approveExchange(IERC20[] memory _tokens) public { + (bool _success, ) = address(exchangeAdapter).delegatecall( + abi.encodeWithSelector(exchangeAdapter.approveExchange.selector, _tokens) + ); + } + + /// @notice Distributes the whole balance of a list of _tokens balances in this contract + /// @param _tokens list of ERC20 tokens to distribute + function distribute(IERC20[] memory _tokens) public { + for (uint256 i = 0; i < _tokens.length; i++) { + uint256 _balanceToDistribute = (address(_tokens[i]) != EthAddressLib.ethAddress()) + ? _tokens[i].balanceOf(address(this)) + : address(this).balance; + if (_balanceToDistribute <= 0) { + continue; + } + + internalDistributeTokenWithAmount(_tokens[i], _balanceToDistribute); + } + } + + /// @notice Distributes specific amounts of a list of _tokens + /// @param _tokens list of ERC20 tokens to distribute + /// @param _amounts list of amounts to distribute per token + function distributeWithAmounts(IERC20[] memory _tokens, uint256[] memory _amounts) public { + for (uint256 i = 0; i < _tokens.length; i++) { + internalDistributeTokenWithAmount(_tokens[i], _amounts[i]); + } + } + + /// @notice Distributes specific total balance's percentages of a list of _tokens + /// @param _tokens list of ERC20 tokens to distribute + /// @param _percentages list of percentages to distribute per token + function distributeWithPercentages(IERC20[] memory _tokens, uint256[] memory _percentages) public { + for (uint256 i = 0; i < _tokens.length; i++) { + uint256 _amountToDistribute = (address(_tokens[i]) != EthAddressLib.ethAddress()) + ? _tokens[i].balanceOf(address(this)).mul(_percentages[i]).div(100) + : address(this).balance.mul(_percentages[i]).div(100); + if (_amountToDistribute <= 0) { + continue; + } + + internalDistributeTokenWithAmount(_tokens[i], _amountToDistribute); + } + } + + /// @notice Sets _receivers addresses with _percentages for each one + /// @param _receivers Array of addresses receiving a percentage of the distribution, both user addresses + /// or contracts + /// @param _percentages Array of percentages each _receivers member will get + function internalSetTokenDistribution(address[] memory _receivers, uint256[] memory _percentages) internal { + require(_receivers.length == _percentages.length, "Array lengths should be equal"); + + distribution = Distribution({receivers: _receivers, percentages: _percentages}); + emit DistributionUpdated(_receivers, _percentages); + } + + /// @notice Distributes a specific amount of a token owned by this contract + /// @param _token The ERC20 token to distribute + /// @param _amountToDistribute The specific amount to distribute + function internalDistributeTokenWithAmount(IERC20 _token, uint256 _amountToDistribute) internal { + address _tokenAddress = address(_token); + Distribution memory _distribution = distribution; + for (uint256 j = 0; j < _distribution.receivers.length; j++) { + uint256 _amount = _amountToDistribute.mul(_distribution.percentages[j]).div(DISTRIBUTION_BASE); + + //avoid transfers/burns of 0 tokens + if(_amount == 0){ + continue; + } + + if (_distribution.receivers[j] != address(0)) { + if (_tokenAddress != EthAddressLib.ethAddress()) { + _token.safeTransfer(_distribution.receivers[j], _amount); + } else { + //solium-disable-next-line + (bool _success,) = _distribution.receivers[j].call{value: _amount}(""); + require(_success, "Reverted ETH transfer"); + } + emit Distributed(_distribution.receivers[j], _distribution.percentages[j], _amount); + } else { + uint256 _amountToBurn = _amount; + // If the token to burn is already tokenToBurn, we don't trade, burning directly + if (_tokenAddress != tokenToBurn) { + (bool _success, bytes memory _result) = address(exchangeAdapter).delegatecall( + abi.encodeWithSelector( + exchangeAdapter.exchange.selector, + _tokenAddress, + tokenToBurn, + _amount, + 10 + ) + ); + require(_success, "ERROR_ON_EXCHANGE"); + _amountToBurn = abi.decode(_result, (uint256)); + } + internalBurn(_amountToBurn); + } + } + } + + /// @notice Internal function to send _amount of tokenToBurn to the 0x0 address + /// @param _amount The amount to burn + function internalBurn(uint256 _amount) internal { + require(IERC20(tokenToBurn).transfer(recipientBurn, _amount), "INTERNAL_BURN. Reverted transfer to recipientBurn address"); + emit Burn(_amount); + } + + /// @notice Returns the receivers and percentages of the contract Distribution + /// @return receivers array of addresses and percentages array on uints + function getDistribution() public view returns(address[] memory receivers, uint256[] memory percentages) { + receivers = distribution.receivers; + percentages = distribution.percentages; + } + + /// @notice Gets the revision number of the contract + /// @return The revision numeric reference + function getRevision() internal override pure returns (uint256) { + return IMPLEMENTATION_REVISION; + } + +} \ No newline at end of file diff --git a/contracts/flashloan/base/FlashLoanReceiverBase.sol b/contracts/flashloan/base/FlashLoanReceiverBase.sol new file mode 100644 index 00000000..b94b8976 --- /dev/null +++ b/contracts/flashloan/base/FlashLoanReceiverBase.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "../interfaces/IFlashLoanReceiver.sol"; +import "../../interfaces/ILendingPoolAddressesProvider.sol"; +import "../../libraries/EthAddressLib.sol"; + +abstract contract FlashLoanReceiverBase is IFlashLoanReceiver { + + using SafeERC20 for IERC20; + using SafeMath for uint256; + + ILendingPoolAddressesProvider public addressesProvider; + + constructor(ILendingPoolAddressesProvider _provider) public { + addressesProvider = _provider; + } + + receive() external payable {} + + function transferFundsBackToPoolInternal(address _reserve, uint256 _amount) internal { + + address payable core = addressesProvider.getLendingPoolCore(); + + transferInternal(core,_reserve, _amount); + } + + function transferInternal(address payable _destination, address _reserve, uint256 _amount) internal { + if(_reserve == EthAddressLib.ethAddress()) { + //solium-disable-next-line + _destination.call{value: _amount}(""); + return; + } + + IERC20(_reserve).safeTransfer(_destination, _amount); + + + } + + function getBalanceInternal(address _target, address _reserve) internal view returns(uint256) { + if(_reserve == EthAddressLib.ethAddress()) { + + return _target.balance; + } + + return IERC20(_reserve).balanceOf(_target); + + } +} \ No newline at end of file diff --git a/contracts/flashloan/interfaces/IFlashLoanReceiver.sol b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol new file mode 100644 index 00000000..9a6a2f7b --- /dev/null +++ b/contracts/flashloan/interfaces/IFlashLoanReceiver.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +* @title IFlashLoanReceiver interface +* @notice Interface for the Aave fee IFlashLoanReceiver. +* @author Aave +* @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract +**/ +interface IFlashLoanReceiver { + + function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external; +} diff --git a/contracts/interfaces/IChainlinkAggregator.sol b/contracts/interfaces/IChainlinkAggregator.sol new file mode 100644 index 00000000..1b09100f --- /dev/null +++ b/contracts/interfaces/IChainlinkAggregator.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface IChainlinkAggregator { + function latestAnswer() external view returns (int256); + function latestTimestamp() external view returns (uint256); + function latestRound() external view returns (uint256); + function getAnswer(uint256 roundId) external view returns (int256); + function getTimestamp(uint256 roundId) external view returns (uint256); + + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp); + event NewRound(uint256 indexed roundId, address indexed startedBy); +} \ No newline at end of file diff --git a/contracts/interfaces/IERC20Detailed.sol b/contracts/interfaces/IERC20Detailed.sol new file mode 100644 index 00000000..0c67bbbb --- /dev/null +++ b/contracts/interfaces/IERC20Detailed.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IERC20Detailed is IERC20 { + function name() external view returns(string memory); + function symbol() external view returns(string memory); + function decimals() external view returns(uint8); +} diff --git a/contracts/interfaces/IExchangeAdapter.sol b/contracts/interfaces/IExchangeAdapter.sol new file mode 100644 index 00000000..54343d58 --- /dev/null +++ b/contracts/interfaces/IExchangeAdapter.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; + +import "../libraries/EthAddressLib.sol"; +import "../libraries/UintConstants.sol"; + +interface IExchangeAdapter { + using SafeERC20 for IERC20; + + event Exchange( + address indexed from, + address indexed to, + address indexed platform, + uint256 fromAmount, + uint256 toAmount + ); + + function approveExchange(IERC20[] calldata _tokens) external; + + function exchange(address _from, address _to, uint256 _amount, uint256 _maxSlippage) external returns(uint256); +} \ No newline at end of file diff --git a/contracts/interfaces/IFeeProvider.sol b/contracts/interfaces/IFeeProvider.sol new file mode 100644 index 00000000..062d21c4 --- /dev/null +++ b/contracts/interfaces/IFeeProvider.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +* @title IFeeProvider interface +* @notice Interface for the Aave fee provider. +**/ + +interface IFeeProvider { + function calculateLoanOriginationFee(address _user, uint256 _amount) external view returns (uint256); + function getLoanOriginationFeePercentage() external view returns (uint256); +} diff --git a/contracts/interfaces/IKyberNetworkProxyInterface.sol b/contracts/interfaces/IKyberNetworkProxyInterface.sol new file mode 100644 index 00000000..761f35c0 --- /dev/null +++ b/contracts/interfaces/IKyberNetworkProxyInterface.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IKyberNetworkProxyInterface { + function maxGasPrice() external view returns(uint); + function getUserCapInWei(address user) external view returns(uint); + function getUserCapInTokenWei(address user, IERC20 token) external view returns(uint); + function enabled() external view returns(bool); + function info(bytes32 id) external view returns(uint); + function getExpectedRate(IERC20 src, IERC20 dest, uint srcQty) + external view returns (uint expectedRate, uint slippageRate); + function tradeWithHint( + IERC20 src, + uint srcAmount, + IERC20 dest, + address destAddress, + uint maxDestAmount, + uint minConversionRate, + address walletId, + bytes calldata hint) external payable returns(uint); +} \ No newline at end of file diff --git a/contracts/interfaces/ILendingPoolAddressesProvider.sol b/contracts/interfaces/ILendingPoolAddressesProvider.sol new file mode 100644 index 00000000..bc3abb21 --- /dev/null +++ b/contracts/interfaces/ILendingPoolAddressesProvider.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +@title ILendingPoolAddressesProvider interface +@notice provides the interface to fetch the LendingPoolCore address + */ + +interface ILendingPoolAddressesProvider { + + function getLendingPool() external view returns (address); + function setLendingPoolImpl(address _pool) external; + + function getLendingPoolCore() external view returns (address payable); + function setLendingPoolCoreImpl(address _lendingPoolCore) external; + + function getLendingPoolConfigurator() external view returns (address); + function setLendingPoolConfiguratorImpl(address _configurator) external; + + function getLendingPoolDataProvider() external view returns (address); + function setLendingPoolDataProviderImpl(address _provider) external; + + function getLendingPoolParametersProvider() external view returns (address); + function setLendingPoolParametersProviderImpl(address _parametersProvider) external; + + function getTokenDistributor() external view returns (address); + function setTokenDistributor(address _tokenDistributor) external; + + + function getFeeProvider() external view returns (address); + function setFeeProviderImpl(address _feeProvider) external; + + function getLendingPoolLiquidationManager() external view returns (address); + function setLendingPoolLiquidationManager(address _manager) external; + + function getLendingPoolManager() external view returns (address); + function setLendingPoolManager(address _lendingPoolManager) external; + + function getPriceOracle() external view returns (address); + function setPriceOracle(address _priceOracle) external; + + function getLendingRateOracle() external view returns (address); + function setLendingRateOracle(address _lendingRateOracle) external; + +} \ No newline at end of file diff --git a/contracts/interfaces/ILendingPoolAddressesProviderRegistry.sol b/contracts/interfaces/ILendingPoolAddressesProviderRegistry.sol new file mode 100644 index 00000000..505b5522 --- /dev/null +++ b/contracts/interfaces/ILendingPoolAddressesProviderRegistry.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +* @title ILendingPoolAddressesProvider interface +* @notice provides the interface to fetch the LendingPoolCore address +**/ + +interface ILendingPoolAddressesProviderRegistry { + + function getAddressesProvidersList() external view returns (address[] memory); + function isAddressesProviderRegistered(address _provider) external view returns (uint256); + + function registerAddressesProvider(address _provider, uint256 _id) external; + function unregisterAddressesProvider(address _provider) external; +} \ No newline at end of file diff --git a/contracts/interfaces/ILendingRateOracle.sol b/contracts/interfaces/ILendingRateOracle.sol new file mode 100644 index 00000000..d9a4bb73 --- /dev/null +++ b/contracts/interfaces/ILendingRateOracle.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +* @title ILendingRateOracle interface +* @notice Interface for the Aave borrow rate oracle. Provides the average market borrow rate to be used as a base for the stable borrow rate calculations +**/ + +interface ILendingRateOracle { + /** + @dev returns the market borrow rate in ray + **/ + function getMarketBorrowRate(address _asset) external view returns (uint256); + + /** + @dev sets the market borrow rate. Rate value must be in ray + **/ + function setMarketBorrowRate(address _asset, uint256 _rate) external; +} diff --git a/contracts/interfaces/IOneSplit.sol b/contracts/interfaces/IOneSplit.sol new file mode 100644 index 00000000..34fdb862 --- /dev/null +++ b/contracts/interfaces/IOneSplit.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + + +abstract contract IOneSplitView { + // disableFlags = FLAG_UNISWAP + FLAG_KYBER + ... + uint256 public constant FLAG_UNISWAP = 0x01; + uint256 public constant FLAG_KYBER = 0x02; + uint256 public constant FLAG_KYBER_UNISWAP_RESERVE = 0x100000000; // Turned off by default + uint256 public constant FLAG_KYBER_OASIS_RESERVE = 0x200000000; // Turned off by default + uint256 public constant FLAG_KYBER_BANCOR_RESERVE = 0x400000000; // Turned off by default + uint256 public constant FLAG_BANCOR = 0x04; + uint256 public constant FLAG_OASIS = 0x08; + uint256 public constant FLAG_COMPOUND = 0x10; + uint256 public constant FLAG_FULCRUM = 0x20; + uint256 public constant FLAG_CHAI = 0x40; + uint256 public constant FLAG_AAVE = 0x80; + uint256 public constant FLAG_SMART_TOKEN = 0x100; + uint256 public constant FLAG_MULTI_PATH_ETH = 0x200; // Turned off by default + uint256 public constant FLAG_BDAI = 0x400; + uint256 public constant FLAG_IEARN = 0x800; + + function getExpectedReturn( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 parts, + uint256 disableFlags // 1 - Uniswap, 2 - Kyber, 4 - Bancor, 8 - Oasis, 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) + public + virtual + view + returns( + uint256 returnAmount, + uint256[] memory distribution // [Uniswap, Kyber, Bancor, Oasis] + ); +} + + +abstract contract IOneSplit is IOneSplitView { + function swap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 minReturn, + uint256[] memory distribution, // [Uniswap, Kyber, Bancor, Oasis] + uint256 disableFlags // 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) public virtual payable; + + function goodSwap( + IERC20 fromToken, + IERC20 toToken, + uint256 amount, + uint256 minReturn, + uint256 parts, + uint256 disableFlags // 1 - Uniswap, 2 - Kyber, 4 - Bancor, 8 - Oasis, 16 - Compound, 32 - Fulcrum, 64 - Chai, 128 - Aave, 256 - SmartToken, 1024 - bDAI + ) public virtual payable; +} \ No newline at end of file diff --git a/contracts/interfaces/IPriceOracle.sol b/contracts/interfaces/IPriceOracle.sol new file mode 100644 index 00000000..31d4ccde --- /dev/null +++ b/contracts/interfaces/IPriceOracle.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/************ +@title IPriceOracle interface +@notice Interface for the Aave price oracle.*/ +interface IPriceOracle { + /*********** + @dev returns the asset price in ETH + */ + function getAssetPrice(address _asset) external view returns (uint256); + + /*********** + @dev sets the asset price, in wei + */ + function setAssetPrice(address _asset, uint256 _price) external; + +} diff --git a/contracts/interfaces/IPriceOracleGetter.sol b/contracts/interfaces/IPriceOracleGetter.sol new file mode 100644 index 00000000..7d1233f6 --- /dev/null +++ b/contracts/interfaces/IPriceOracleGetter.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +* @title IPriceOracleGetter interface +* @notice Interface for the Aave price oracle. +**/ + +interface IPriceOracleGetter { + /** + * @dev returns the asset price in ETH + * @param _asset the address of the asset + * @return the ETH price of the asset + **/ + function getAssetPrice(address _asset) external view returns (uint256); +} diff --git a/contracts/interfaces/IReserveInterestRateStrategy.sol b/contracts/interfaces/IReserveInterestRateStrategy.sol new file mode 100644 index 00000000..7108a90d --- /dev/null +++ b/contracts/interfaces/IReserveInterestRateStrategy.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +/** +@title IReserveInterestRateStrategyInterface interface +@notice Interface for the calculation of the interest rates. +*/ + +interface IReserveInterestRateStrategy { + + /** + * @dev returns the base variable borrow rate, in rays + */ + + function getBaseVariableBorrowRate() external view returns (uint256); + /** + * @dev calculates the liquidity, stable, and variable rates depending on the current utilization rate + * and the base parameters + * + */ + function calculateInterestRates( + address _reserve, + uint256 _utilizationRate, + uint256 _totalBorrowsStable, + uint256 _totalBorrowsVariable, + uint256 _averageStableBorrowRate) + external + view + returns (uint256 liquidityRate, uint256 stableBorrowRate, uint256 variableBorrowRate); +} diff --git a/contracts/interfaces/IUniswapExchange.sol b/contracts/interfaces/IUniswapExchange.sol new file mode 100644 index 00000000..e7b2a3f3 --- /dev/null +++ b/contracts/interfaces/IUniswapExchange.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface IUniswapExchange { + event TokenPurchase(address indexed buyer, uint256 indexed eth_sold, uint256 indexed tokens_bought); + event EthPurchase(address indexed buyer, uint256 indexed tokens_sold, uint256 indexed eth_bought); + event AddLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount); + event RemoveLiquidity(address indexed provider, uint256 indexed eth_amount, uint256 indexed token_amount); +} diff --git a/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol b/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol new file mode 100644 index 00000000..8a0b4c06 --- /dev/null +++ b/contracts/lendingpool/DefaultReserveInterestRateStrategy.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../interfaces/IReserveInterestRateStrategy.sol"; +import "../libraries/WadRayMath.sol"; +import "../configuration/LendingPoolAddressesProvider.sol"; +import "./LendingPoolCore.sol"; +import "../interfaces/ILendingRateOracle.sol"; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** +* @title DefaultReserveInterestRateStrategy contract +* @notice implements the calculation of the interest rates depending on the reserve parameters. +* @dev if there is need to update the calculation of the interest rates for a specific reserve, +* a new version of this contract will be deployed. +* @author Aave +**/ +contract DefaultReserveInterestRateStrategy is IReserveInterestRateStrategy { + using WadRayMath for uint256; + using SafeMath for uint256; + + + + /** + * @dev this constant represents the utilization rate at which the pool aims to obtain most competitive borrow rates + * expressed in ray + **/ + uint256 public constant OPTIMAL_UTILIZATION_RATE = 0.8 * 1e27; + + /** + * @dev this constant represents the excess utilization rate above the optimal. It's always equal to + * 1-optimal utilization rate. Added as a constant here for gas optimizations + * expressed in ray + **/ + + uint256 public constant EXCESS_UTILIZATION_RATE = 0.2 * 1e27; + + LendingPoolAddressesProvider public addressesProvider; + + + //base variable borrow rate when Utilization rate = 0. Expressed in ray + uint256 public baseVariableBorrowRate; + + //slope of the variable interest curve when utilization rate > 0 and <= OPTIMAL_UTILIZATION_RATE. Expressed in ray + uint256 public variableRateSlope1; + + //slope of the variable interest curve when utilization rate > OPTIMAL_UTILIZATION_RATE. Expressed in ray + uint256 public variableRateSlope2; + + //slope of the stable interest curve when utilization rate > 0 and <= OPTIMAL_UTILIZATION_RATE. Expressed in ray + uint256 public stableRateSlope1; + + //slope of the stable interest curve when utilization rate > OPTIMAL_UTILIZATION_RATE. Expressed in ray + uint256 public stableRateSlope2; + address public reserve; + + constructor( + address _reserve, + LendingPoolAddressesProvider _provider, + uint256 _baseVariableBorrowRate, + uint256 _variableRateSlope1, + uint256 _variableRateSlope2, + uint256 _stableRateSlope1, + uint256 _stableRateSlope2 + ) public { + addressesProvider = _provider; + baseVariableBorrowRate = _baseVariableBorrowRate; + variableRateSlope1 = _variableRateSlope1; + variableRateSlope2 = _variableRateSlope2; + stableRateSlope1 = _stableRateSlope1; + stableRateSlope2 = _stableRateSlope2; + reserve = _reserve; + } + + /** + @dev accessors + */ + + function getBaseVariableBorrowRate() external override view returns (uint256) { + return baseVariableBorrowRate; + } + + function getVariableRateSlope1() external view returns (uint256) { + return variableRateSlope1; + } + + function getVariableRateSlope2() external view returns (uint256) { + return variableRateSlope2; + } + + function getStableRateSlope1() external view returns (uint256) { + return stableRateSlope1; + } + + function getStableRateSlope2() external view returns (uint256) { + return stableRateSlope2; + } + + /** + * @dev calculates the interest rates depending on the available liquidity and the total borrowed. + * @param _reserve the address of the reserve + * @param _availableLiquidity the liquidity available in the reserve + * @param _totalBorrowsStable the total borrowed from the reserve a stable rate + * @param _totalBorrowsVariable the total borrowed from the reserve at a variable rate + * @param _averageStableBorrowRate the weighted average of all the stable rate borrows + * @return currentLiquidityRate calculated from the input parameters TODO: add description + * @return currentStableBorrowRate calculated from the input parameters TODO: add description + * @return currentVariableBorrowRate calculated from the input parameters TODO: add description + **/ + function calculateInterestRates( + address _reserve, + uint256 _availableLiquidity, + uint256 _totalBorrowsStable, + uint256 _totalBorrowsVariable, + uint256 _averageStableBorrowRate + ) + external + override + view + returns ( + uint256 currentLiquidityRate, + uint256 currentStableBorrowRate, + uint256 currentVariableBorrowRate + ) + { + uint256 totalBorrows = _totalBorrowsStable.add(_totalBorrowsVariable); + + uint256 utilizationRate = (totalBorrows == 0 && _availableLiquidity == 0) + ? 0 + : totalBorrows.rayDiv(_availableLiquidity.add(totalBorrows)); + + currentStableBorrowRate = ILendingRateOracle(addressesProvider.getLendingRateOracle()) + .getMarketBorrowRate(_reserve); + + if (utilizationRate > OPTIMAL_UTILIZATION_RATE) { + uint256 excessUtilizationRateRatio = utilizationRate + .sub(OPTIMAL_UTILIZATION_RATE) + .rayDiv(EXCESS_UTILIZATION_RATE); + + currentStableBorrowRate = currentStableBorrowRate.add(stableRateSlope1).add( + stableRateSlope2.rayMul(excessUtilizationRateRatio) + ); + + currentVariableBorrowRate = baseVariableBorrowRate.add(variableRateSlope1).add( + variableRateSlope2.rayMul(excessUtilizationRateRatio) + ); + } else { + currentStableBorrowRate = currentStableBorrowRate.add( + stableRateSlope1.rayMul( + utilizationRate.rayDiv( + OPTIMAL_UTILIZATION_RATE + ) + ) + ); + currentVariableBorrowRate = baseVariableBorrowRate.add( + utilizationRate.rayDiv(OPTIMAL_UTILIZATION_RATE).rayMul(variableRateSlope1) + ); + } + + currentLiquidityRate = getOverallBorrowRateInternal( + _totalBorrowsStable, + _totalBorrowsVariable, + currentVariableBorrowRate, + _averageStableBorrowRate + ) + .rayMul(utilizationRate); + + } + + /** + * @dev calculates the overall borrow rate as the weighted average between the total variable borrows and total stable borrows. + * @param _totalBorrowsStable the total borrowed from the reserve a stable rate + * @param _totalBorrowsVariable the total borrowed from the reserve at a variable rate + * @param _currentVariableBorrowRate the current variable borrow rate + * @param _currentAverageStableBorrowRate the weighted average of all the stable rate borrows + * @return the weighted averaged borrow rate + **/ + function getOverallBorrowRateInternal( + uint256 _totalBorrowsStable, + uint256 _totalBorrowsVariable, + uint256 _currentVariableBorrowRate, + uint256 _currentAverageStableBorrowRate + ) internal pure returns (uint256) { + uint256 totalBorrows = _totalBorrowsStable.add(_totalBorrowsVariable); + + if (totalBorrows == 0) return 0; + + uint256 weightedVariableRate = _totalBorrowsVariable.wadToRay().rayMul( + _currentVariableBorrowRate + ); + + uint256 weightedStableRate = _totalBorrowsStable.wadToRay().rayMul( + _currentAverageStableBorrowRate + ); + + uint256 overallBorrowRate = weightedVariableRate.add(weightedStableRate).rayDiv( + totalBorrows.wadToRay() + ); + + return overallBorrowRate; + } +} diff --git a/contracts/lendingpool/LendingPool.sol b/contracts/lendingpool/LendingPool.sol new file mode 100644 index 00000000..dc5ce58a --- /dev/null +++ b/contracts/lendingpool/LendingPool.sol @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; + +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../configuration/LendingPoolParametersProvider.sol"; +import "../tokenization/AToken.sol"; +import "../libraries/CoreLibrary.sol"; +import "../libraries/WadRayMath.sol"; +import "../interfaces/IFeeProvider.sol"; +import "../flashloan/interfaces/IFlashLoanReceiver.sol"; +import "./LendingPoolCore.sol"; +import "./LendingPoolDataProvider.sol"; +import "./LendingPoolLiquidationManager.sol"; +import "../libraries/EthAddressLib.sol"; + +/** +* @title LendingPool contract +* @notice Implements the actions of the LendingPool, and exposes accessory methods to fetch the users and reserve data +* @author Aave + **/ + +contract LendingPool is ReentrancyGuard, VersionedInitializable { + using SafeMath for uint256; + using WadRayMath for uint256; + using Address for address; + + LendingPoolAddressesProvider public addressesProvider; + LendingPoolCore public core; + LendingPoolDataProvider public dataProvider; + LendingPoolParametersProvider public parametersProvider; + IFeeProvider feeProvider; + + /** + * @dev emitted on deposit + * @param _reserve the address of the reserve + * @param _user the address of the user + * @param _amount the amount to be deposited + * @param _referral the referral number of the action + * @param _timestamp the timestamp of the action + **/ + event Deposit( + address indexed _reserve, + address indexed _user, + uint256 _amount, + uint16 indexed _referral, + uint256 _timestamp + ); + + /** + * @dev emitted during a redeem action. + * @param _reserve the address of the reserve + * @param _user the address of the user + * @param _amount the amount to be deposited + * @param _timestamp the timestamp of the action + **/ + event RedeemUnderlying( + address indexed _reserve, + address indexed _user, + uint256 _amount, + uint256 _timestamp + ); + + /** + * @dev emitted on borrow + * @param _reserve the address of the reserve + * @param _user the address of the user + * @param _amount the amount to be deposited + * @param _borrowRateMode the rate mode, can be either 1-stable or 2-variable + * @param _borrowRate the rate at which the user has borrowed + * @param _originationFee the origination fee to be paid by the user + * @param _borrowBalanceIncrease the balance increase since the last borrow, 0 if it's the first time borrowing + * @param _referral the referral number of the action + * @param _timestamp the timestamp of the action + **/ + event Borrow( + address indexed _reserve, + address indexed _user, + uint256 _amount, + uint256 _borrowRateMode, + uint256 _borrowRate, + uint256 _originationFee, + uint256 _borrowBalanceIncrease, + uint16 indexed _referral, + uint256 _timestamp + ); + + /** + * @dev emitted on repay + * @param _reserve the address of the reserve + * @param _user the address of the user for which the repay has been executed + * @param _repayer the address of the user that has performed the repay action + * @param _amountMinusFees the amount repaid minus fees + * @param _fees the fees repaid + * @param _borrowBalanceIncrease the balance increase since the last action + * @param _timestamp the timestamp of the action + **/ + event Repay( + address indexed _reserve, + address indexed _user, + address indexed _repayer, + uint256 _amountMinusFees, + uint256 _fees, + uint256 _borrowBalanceIncrease, + uint256 _timestamp + ); + + /** + * @dev emitted when a user performs a rate swap + * @param _reserve the address of the reserve + * @param _user the address of the user executing the swap + * @param _newRateMode the new interest rate mode + * @param _newRate the new borrow rate + * @param _borrowBalanceIncrease the balance increase since the last action + * @param _timestamp the timestamp of the action + **/ + event Swap( + address indexed _reserve, + address indexed _user, + uint256 _newRateMode, + uint256 _newRate, + uint256 _borrowBalanceIncrease, + uint256 _timestamp + ); + + /** + * @dev emitted when a user enables a reserve as collateral + * @param _reserve the address of the reserve + * @param _user the address of the user + **/ + event ReserveUsedAsCollateralEnabled(address indexed _reserve, address indexed _user); + + /** + * @dev emitted when a user disables a reserve as collateral + * @param _reserve the address of the reserve + * @param _user the address of the user + **/ + event ReserveUsedAsCollateralDisabled(address indexed _reserve, address indexed _user); + + /** + * @dev emitted when the stable rate of a user gets rebalanced + * @param _reserve the address of the reserve + * @param _user the address of the user for which the rebalance has been executed + * @param _newStableRate the new stable borrow rate after the rebalance + * @param _borrowBalanceIncrease the balance increase since the last action + * @param _timestamp the timestamp of the action + **/ + event RebalanceStableBorrowRate( + address indexed _reserve, + address indexed _user, + uint256 _newStableRate, + uint256 _borrowBalanceIncrease, + uint256 _timestamp + ); + + /** + * @dev emitted when a flashloan is executed + * @param _target the address of the flashLoanReceiver + * @param _reserve the address of the reserve + * @param _amount the amount requested + * @param _totalFee the total fee on the amount + * @param _protocolFee the part of the fee for the protocol + * @param _timestamp the timestamp of the action + **/ + event FlashLoan( + address indexed _target, + address indexed _reserve, + uint256 _amount, + uint256 _totalFee, + uint256 _protocolFee, + uint256 _timestamp + ); + + /** + * @dev these events are not emitted directly by the LendingPool + * but they are declared here as the LendingPoolLiquidationManager + * is executed using a delegateCall(). + * This allows to have the events in the generated ABI for LendingPool. + **/ + + /** + * @dev emitted when a borrow fee is liquidated + * @param _collateral the address of the collateral being liquidated + * @param _reserve the address of the reserve + * @param _user the address of the user being liquidated + * @param _feeLiquidated the total fee liquidated + * @param _liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee + * @param _timestamp the timestamp of the action + **/ + event OriginationFeeLiquidated( + address indexed _collateral, + address indexed _reserve, + address indexed _user, + uint256 _feeLiquidated, + uint256 _liquidatedCollateralForFee, + uint256 _timestamp + ); + + /** + * @dev emitted when a borrower is liquidated + * @param _collateral the address of the collateral being liquidated + * @param _reserve 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 _accruedBorrowInterest the amount of interest accrued by the borrower since the last action + * @param _liquidator the address of the liquidator + * @param _receiveAToken true if the liquidator wants to receive aTokens, false otherwise + * @param _timestamp the timestamp of the action + **/ + event LiquidationCall( + address indexed _collateral, + address indexed _reserve, + address indexed _user, + uint256 _purchaseAmount, + uint256 _liquidatedCollateralAmount, + uint256 _accruedBorrowInterest, + address _liquidator, + bool _receiveAToken, + uint256 _timestamp + ); + + /** + * @dev functions affected by this modifier can only be invoked by the + * aToken.sol contract + * @param _reserve the address of the reserve + **/ + modifier onlyOverlyingAToken(address _reserve) { + require( + msg.sender == core.getReserveATokenAddress(_reserve), + "The caller of this function can only be the aToken contract of this reserve" + ); + _; + } + + /** + * @dev functions affected by this modifier can only be invoked if the reserve is active + * @param _reserve the address of the reserve + **/ + modifier onlyActiveReserve(address _reserve) { + requireReserveActiveInternal(_reserve); + _; + } + + /** + * @dev functions affected by this modifier can only be invoked if the reserve is not freezed. + * A freezed reserve only allows redeems, repays, rebalances and liquidations. + * @param _reserve the address of the reserve + **/ + modifier onlyUnfreezedReserve(address _reserve) { + requireReserveNotFreezedInternal(_reserve); + _; + } + + /** + * @dev functions affected by this modifier can only be invoked if the provided _amount input parameter + * is not zero. + * @param _amount the amount provided + **/ + modifier onlyAmountGreaterThanZero(uint256 _amount) { + requireAmountGreaterThanZeroInternal(_amount); + _; + } + + uint256 public constant UINT_MAX_VALUE = uint256(-1); + + uint256 public constant LENDINGPOOL_REVISION = 0x2; + + function getRevision() internal override pure returns (uint256) { + return LENDINGPOOL_REVISION; + } + + /** + * @dev this function is invoked by the proxy contract when the LendingPool contract is added to the + * AddressesProvider. + * @param _addressesProvider the address of the LendingPoolAddressesProvider registry + **/ + function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer { + addressesProvider = _addressesProvider; + core = LendingPoolCore(addressesProvider.getLendingPoolCore()); + dataProvider = LendingPoolDataProvider(addressesProvider.getLendingPoolDataProvider()); + parametersProvider = LendingPoolParametersProvider( + addressesProvider.getLendingPoolParametersProvider() + ); + feeProvider = IFeeProvider(addressesProvider.getFeeProvider()); + } + + /** + * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens) + * is minted. + * @param _reserve the address of the reserve + * @param _amount the amount to be deposited + * @param _referralCode integrators are assigned a referral code and can potentially receive rewards. + **/ + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) + external + payable + nonReentrant + onlyActiveReserve(_reserve) + onlyUnfreezedReserve(_reserve) + onlyAmountGreaterThanZero(_amount) + { + AToken aToken = AToken(core.getReserveATokenAddress(_reserve)); + + bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0; + + core.updateStateOnDeposit(_reserve, msg.sender, _amount, isFirstDeposit); + + //minting AToken to user 1:1 with the specific exchange rate + aToken.mintOnDeposit(msg.sender, _amount); + + //transfer to the core contract + core.transferToReserve{value: msg.value}(_reserve, msg.sender, _amount); + + //solium-disable-next-line + emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp); + + } + + /** + * @dev Redeems the underlying amount of assets requested by _user. + * This function is executed by the overlying aToken contract in response to a redeem action. + * @param _reserve the address of the reserve + * @param _user the address of the user performing the action + * @param _amount the underlying amount to be redeemed + **/ + function redeemUnderlying( + address _reserve, + address payable _user, + uint256 _amount, + uint256 _aTokenBalanceAfterRedeem + ) + external + nonReentrant + onlyOverlyingAToken(_reserve) + onlyActiveReserve(_reserve) + onlyAmountGreaterThanZero(_amount) + { + uint256 currentAvailableLiquidity = core.getReserveAvailableLiquidity(_reserve); + require( + currentAvailableLiquidity >= _amount, + "There is not enough liquidity available to redeem" + ); + + core.updateStateOnRedeem(_reserve, _user, _amount, _aTokenBalanceAfterRedeem == 0); + + core.transferToUser(_reserve, _user, _amount); + + //solium-disable-next-line + emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp); + + } + + /** + * @dev data structures for local computations in the borrow() method. + */ + + struct BorrowLocalVars { + uint256 principalBorrowBalance; + uint256 currentLtv; + uint256 currentLiquidationThreshold; + uint256 borrowFee; + uint256 requestedBorrowAmountETH; + uint256 amountOfCollateralNeededETH; + uint256 userCollateralBalanceETH; + uint256 userBorrowBalanceETH; + uint256 userTotalFeesETH; + uint256 borrowBalanceIncrease; + uint256 currentReserveStableRate; + uint256 availableLiquidity; + uint256 reserveDecimals; + uint256 finalUserBorrowRate; + CoreLibrary.InterestRateMode rateMode; + bool healthFactorBelowThreshold; + } + + /** + * @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower + * already deposited enough collateral. + * @param _reserve the address of the reserve + * @param _amount the amount to be borrowed + * @param _interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE) + **/ + function borrow( + address _reserve, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode + ) + external + nonReentrant + onlyActiveReserve(_reserve) + onlyUnfreezedReserve(_reserve) + onlyAmountGreaterThanZero(_amount) + { + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + BorrowLocalVars memory vars; + + //check that the reserve is enabled for borrowing + require(core.isReserveBorrowingEnabled(_reserve), "Reserve is not enabled for borrowing"); + //validate interest rate mode + require( + uint256(CoreLibrary.InterestRateMode.VARIABLE) == _interestRateMode || + uint256(CoreLibrary.InterestRateMode.STABLE) == _interestRateMode, + "Invalid interest rate mode selected" + ); + + //cast the rateMode to coreLibrary.interestRateMode + vars.rateMode = CoreLibrary.InterestRateMode(_interestRateMode); + + //check that the amount is available in the reserve + vars.availableLiquidity = core.getReserveAvailableLiquidity(_reserve); + + require( + vars.availableLiquidity >= _amount, + "There is not enough liquidity available in the reserve" + ); + + ( + , + vars.userCollateralBalanceETH, + vars.userBorrowBalanceETH, + vars.userTotalFeesETH, + vars.currentLtv, + vars.currentLiquidationThreshold, + , + vars.healthFactorBelowThreshold + ) = dataProvider.calculateUserGlobalData(msg.sender); + + require(vars.userCollateralBalanceETH > 0, "The collateral balance is 0"); + + require( + !vars.healthFactorBelowThreshold, + "The borrower can already be liquidated so he cannot borrow more" + ); + + //calculating fees + vars.borrowFee = feeProvider.calculateLoanOriginationFee(msg.sender, _amount); + + require(vars.borrowFee > 0, "The amount to borrow is too small"); + + vars.amountOfCollateralNeededETH = dataProvider.calculateCollateralNeededInETH( + _reserve, + _amount, + vars.borrowFee, + vars.userBorrowBalanceETH, + vars.userTotalFeesETH, + vars.currentLtv + ); + + require( + vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH, + "There is not enough collateral to cover a new borrow" + ); + + /** + * Following conditions need to be met if the user is borrowing at a stable rate: + * 1. Reserve must be enabled for stable rate borrowing + * 2. Users cannot borrow from the reserve if their collateral is (mostly) the same currency + * they are borrowing, to prevent abuses. + * 3. Users will be able to borrow only a relatively small, configurable amount of the total + * liquidity + **/ + + if (vars.rateMode == CoreLibrary.InterestRateMode.STABLE) { + //check if the borrow mode is stable and if stable rate borrowing is enabled on this reserve + require( + core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, _amount), + "User cannot borrow the selected amount with a stable rate" + ); + + //calculate the max available loan size in stable rate mode as a percentage of the + //available liquidity + uint256 maxLoanPercent = parametersProvider.getMaxStableRateBorrowSizePercent(); + uint256 maxLoanSizeStable = vars.availableLiquidity.mul(maxLoanPercent).div(100); + + require( + _amount <= maxLoanSizeStable, + "User is trying to borrow too much liquidity at a stable rate" + ); + } + + //all conditions passed - borrow is accepted + (vars.finalUserBorrowRate, vars.borrowBalanceIncrease) = core.updateStateOnBorrow( + _reserve, + msg.sender, + _amount, + vars.borrowFee, + vars.rateMode + ); + + //if we reached this point, we can transfer + core.transferToUser(_reserve, msg.sender, _amount); + + emit Borrow( + _reserve, + msg.sender, + _amount, + _interestRateMode, + vars.finalUserBorrowRate, + vars.borrowFee, + vars.borrowBalanceIncrease, + _referralCode, + //solium-disable-next-line + block.timestamp + ); + } + + /** + * @notice repays a borrow on the specific reserve, for the specified amount (or for the whole amount, if uint256(-1) is specified). + * @dev the target user is defined by _onBehalfOf. If there is no repayment on behalf of another account, + * _onBehalfOf must be equal to msg.sender. + * @param _reserve the address of the reserve on which the user borrowed + * @param _amount the amount to repay, or uint256(-1) if the user wants to repay everything + * @param _onBehalfOf the address for which msg.sender is repaying. + **/ + + struct RepayLocalVars { + uint256 principalBorrowBalance; + uint256 compoundedBorrowBalance; + uint256 borrowBalanceIncrease; + bool isETH; + uint256 paybackAmount; + uint256 paybackAmountMinusFees; + uint256 currentStableRate; + uint256 originationFee; + } + + function repay(address _reserve, uint256 _amount, address payable _onBehalfOf) + external + payable + nonReentrant + onlyActiveReserve(_reserve) + onlyAmountGreaterThanZero(_amount) + { + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + RepayLocalVars memory vars; + + ( + vars.principalBorrowBalance, + vars.compoundedBorrowBalance, + vars.borrowBalanceIncrease + ) = core.getUserBorrowBalances(_reserve, _onBehalfOf); + + vars.originationFee = core.getUserOriginationFee(_reserve, _onBehalfOf); + vars.isETH = EthAddressLib.ethAddress() == _reserve; + + require(vars.compoundedBorrowBalance > 0, "The user does not have any borrow pending"); + + require( + _amount != UINT_MAX_VALUE || msg.sender == _onBehalfOf, + "To repay on behalf of an user an explicit amount to repay is needed." + ); + + //default to max amount + vars.paybackAmount = vars.compoundedBorrowBalance.add(vars.originationFee); + + if (_amount != UINT_MAX_VALUE && _amount < vars.paybackAmount) { + vars.paybackAmount = _amount; + } + + require( + !vars.isETH || msg.value >= vars.paybackAmount, + "Invalid msg.value sent for the repayment" + ); + + //if the amount is smaller than the origination fee, just transfer the amount to the fee destination address + if (vars.paybackAmount <= vars.originationFee) { + core.updateStateOnRepay( + _reserve, + _onBehalfOf, + 0, + vars.paybackAmount, + vars.borrowBalanceIncrease, + false + ); + + core.transferToFeeCollectionAddress{ value: vars.isETH ? vars.paybackAmount : 0 }( + _reserve, + _onBehalfOf, + vars.paybackAmount, + addressesProvider.getTokenDistributor() + ); + + emit Repay( + _reserve, + _onBehalfOf, + msg.sender, + 0, + vars.paybackAmount, + vars.borrowBalanceIncrease, + //solium-disable-next-line + block.timestamp + ); + return; + } + + vars.paybackAmountMinusFees = vars.paybackAmount.sub(vars.originationFee); + + core.updateStateOnRepay( + _reserve, + _onBehalfOf, + vars.paybackAmountMinusFees, + vars.originationFee, + vars.borrowBalanceIncrease, + vars.compoundedBorrowBalance == vars.paybackAmountMinusFees + ); + + //if the user didn't repay the origination fee, transfer the fee to the fee collection address + if(vars.originationFee > 0) { + core.transferToFeeCollectionAddress{ value: vars.isETH ? vars.originationFee : 0 }( + _reserve, + _onBehalfOf, + vars.originationFee, + addressesProvider.getTokenDistributor() + ); + } + + //sending the total msg.value if the transfer is ETH. + //the transferToReserve() function will take care of sending the + //excess ETH back to the caller + core.transferToReserve{ value: vars.isETH ? msg.value.sub(vars.originationFee) : 0 }( + _reserve, + msg.sender, + vars.paybackAmountMinusFees + ); + + emit Repay( + _reserve, + _onBehalfOf, + msg.sender, + vars.paybackAmountMinusFees, + vars.originationFee, + vars.borrowBalanceIncrease, + //solium-disable-next-line + block.timestamp + ); + } + + /** + * @dev borrowers can user this function to swap between stable and variable borrow rate modes. + * @param _reserve the address of the reserve on which the user borrowed + **/ + function swapBorrowRateMode(address _reserve) + external + nonReentrant + onlyActiveReserve(_reserve) + onlyUnfreezedReserve(_reserve) + { + (uint256 principalBorrowBalance, uint256 compoundedBorrowBalance, uint256 borrowBalanceIncrease) = core + .getUserBorrowBalances(_reserve, msg.sender); + + require( + compoundedBorrowBalance > 0, + "User does not have a borrow in progress on this reserve" + ); + + CoreLibrary.InterestRateMode currentRateMode = core.getUserCurrentBorrowRateMode( + _reserve, + msg.sender + ); + + if (currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) { + /** + * user wants to swap to stable, before swapping we need to ensure that + * 1. stable borrow rate is enabled on the reserve + * 2. user is not trying to abuse the reserve by depositing + * more collateral than he is borrowing, artificially lowering + * the interest rate, borrowing at variable, and switching to stable + **/ + require( + core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, compoundedBorrowBalance), + "User cannot borrow the selected amount at stable" + ); + } + + (CoreLibrary.InterestRateMode newRateMode, uint256 newBorrowRate) = core + .updateStateOnSwapRate( + _reserve, + msg.sender, + principalBorrowBalance, + compoundedBorrowBalance, + borrowBalanceIncrease, + currentRateMode + ); + + emit Swap( + _reserve, + msg.sender, + uint256(newRateMode), + newBorrowRate, + borrowBalanceIncrease, + //solium-disable-next-line + block.timestamp + ); + } + + /** + * @dev rebalances the stable interest rate of a user if current liquidity rate > user stable rate. + * this is regulated by Aave to ensure that the protocol is not abused, and the user is paying a fair + * rate. Anyone can call this function though. + * @param _reserve the address of the reserve + * @param _user the address of the user to be rebalanced + **/ + function rebalanceStableBorrowRate(address _reserve, address _user) + external + nonReentrant + onlyActiveReserve(_reserve) + { + (, uint256 compoundedBalance, uint256 borrowBalanceIncrease) = core.getUserBorrowBalances( + _reserve, + _user + ); + + //step 1: user must be borrowing on _reserve at a stable rate + require(compoundedBalance > 0, "User does not have any borrow for this reserve"); + + require( + core.getUserCurrentBorrowRateMode(_reserve, _user) == + CoreLibrary.InterestRateMode.STABLE, + "The user borrow is variable and cannot be rebalanced" + ); + + uint256 userCurrentStableRate = core.getUserCurrentStableBorrowRate(_reserve, _user); + uint256 liquidityRate = core.getReserveCurrentLiquidityRate(_reserve); + uint256 reserveCurrentStableRate = core.getReserveCurrentStableBorrowRate(_reserve); + uint256 rebalanceDownRateThreshold = reserveCurrentStableRate.rayMul( + WadRayMath.ray().add(parametersProvider.getRebalanceDownRateDelta()) + ); + + //step 2: we have two possible situations to rebalance: + + //1. user stable borrow rate is below the current liquidity rate. The loan needs to be rebalanced, + //as this situation can be abused (user putting back the borrowed liquidity in the same reserve to earn on it) + //2. user stable rate is above the market avg borrow rate of a certain delta, and utilization rate is low. + //In this case, the user is paying an interest that is too high, and needs to be rescaled down. + if ( + userCurrentStableRate < liquidityRate || + userCurrentStableRate > rebalanceDownRateThreshold + ) { + uint256 newStableRate = core.updateStateOnRebalance( + _reserve, + _user, + borrowBalanceIncrease + ); + + emit RebalanceStableBorrowRate( + _reserve, + _user, + newStableRate, + borrowBalanceIncrease, + //solium-disable-next-line + block.timestamp + ); + + return; + + } + + revert("Interest rate rebalance conditions were not met"); + } + + /** + * @dev allows depositors to enable or disable a specific deposit as collateral. + * @param _reserve the address of the reserve + * @param _useAsCollateral true if the user wants to user the deposit as collateral, false otherwise. + **/ + function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) + external + nonReentrant + onlyActiveReserve(_reserve) + onlyUnfreezedReserve(_reserve) + { + uint256 underlyingBalance = core.getUserUnderlyingAssetBalance(_reserve, msg.sender); + + require(underlyingBalance > 0, "User does not have any liquidity deposited"); + + require( + dataProvider.balanceDecreaseAllowed(_reserve, msg.sender, underlyingBalance), + "User deposit is already being used as collateral" + ); + + core.setUserUseReserveAsCollateral(_reserve, msg.sender, _useAsCollateral); + + if (_useAsCollateral) { + emit ReserveUsedAsCollateralEnabled(_reserve, msg.sender); + } else { + emit ReserveUsedAsCollateralDisabled(_reserve, msg.sender); + } + } + + /** + * @dev users can invoke this function to liquidate an undercollateralized position. + * @param _reserve the address of the collateral to liquidated + * @param _reserve 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 _reserve, + address _user, + uint256 _purchaseAmount, + bool _receiveAToken + ) external payable nonReentrant onlyActiveReserve(_reserve) onlyActiveReserve(_collateral) { + address liquidationManager = addressesProvider.getLendingPoolLiquidationManager(); + + //solium-disable-next-line + (bool success, bytes memory result) = liquidationManager.delegatecall( + abi.encodeWithSignature( + "liquidationCall(address,address,address,uint256,bool)", + _collateral, + _reserve, + _user, + _purchaseAmount, + _receiveAToken + ) + ); + require(success, "Liquidation call failed"); + + (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string)); + + if (returnCode != 0) { + //error found + revert(string(abi.encodePacked("Liquidation failed: ", returnMessage))); + } + } + + /** + * @dev allows smartcontracts to access the liquidity of the pool within one transaction, + * as long as the amount taken plus a fee is returned. NOTE 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 _receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface. + * @param _reserve the address of the principal reserve + * @param _amount the amount requested for this flashloan + **/ + function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params) + public + nonReentrant + onlyActiveReserve(_reserve) + onlyAmountGreaterThanZero(_amount) // TODO: remove + { + //check that the reserve has enough available liquidity + //we avoid using the getAvailableLiquidity() function in LendingPoolCore to save gas + uint256 availableLiquidityBefore = _reserve == EthAddressLib.ethAddress() + ? address(core).balance + : IERC20(_reserve).balanceOf(address(core)); + + require( + availableLiquidityBefore >= _amount, + "There is not enough liquidity available to borrow" + ); + + (uint256 totalFeeBips, uint256 protocolFeeBips) = parametersProvider + .getFlashLoanFeesInBips(); + //calculate amount fee + uint256 amountFee = _amount.mul(totalFeeBips).div(10000); + + //protocol fee is the part of the amountFee reserved for the protocol - the rest goes to depositors + uint256 protocolFee = amountFee.mul(protocolFeeBips).div(10000); + require( + amountFee > 0 && protocolFee > 0, + "The requested amount is too small for a flashLoan." + ); + + //get the FlashLoanReceiver instance + IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver); + + address payable userPayable = payable(_receiver); + + //transfer funds to the receiver + core.transferToUser(_reserve, userPayable, _amount); + + //execute action of the receiver + receiver.executeOperation(_reserve, _amount, amountFee, _params); + + //check that the actual balance of the core contract includes the returned amount + uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress() + ? address(core).balance + : IERC20(_reserve).balanceOf(address(core)); + + require( + availableLiquidityAfter == availableLiquidityBefore.add(amountFee), + "The actual balance of the protocol is inconsistent" + ); + + core.updateStateOnFlashLoan( + _reserve, + availableLiquidityBefore, + amountFee.sub(protocolFee), + protocolFee + ); + + //solium-disable-next-line + emit FlashLoan(_receiver, _reserve, _amount, amountFee, protocolFee, block.timestamp); + } + + /** + * @dev accessory functions to fetch data from the core contract + **/ + + function getReserveConfigurationData(address _reserve) + external + view + returns ( + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + address interestRateStrategyAddress, + bool usageAsCollateralEnabled, + bool borrowingEnabled, + bool stableBorrowRateEnabled, + bool isActive + ) + { + return dataProvider.getReserveConfigurationData(_reserve); + } + + function getReserveData(address _reserve) + external + view + returns ( + uint256 totalLiquidity, + uint256 availableLiquidity, + uint256 totalBorrowsStable, + uint256 totalBorrowsVariable, + uint256 liquidityRate, + uint256 variableBorrowRate, + uint256 stableBorrowRate, + uint256 averageStableBorrowRate, + uint256 utilizationRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex, + address aTokenAddress, + uint40 lastUpdateTimestamp + ) + { + return dataProvider.getReserveData(_reserve); + } + + function getUserAccountData(address _user) + external + view + returns ( + uint256 totalLiquidityETH, + uint256 totalCollateralETH, + uint256 totalBorrowsETH, + uint256 totalFeesETH, + uint256 availableBorrowsETH, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ) + { + return dataProvider.getUserAccountData(_user); + } + + function getUserReserveData(address _reserve, address _user) + external + view + returns ( + uint256 currentATokenBalance, + uint256 currentBorrowBalance, + uint256 principalBorrowBalance, + uint256 borrowRateMode, + uint256 borrowRate, + uint256 liquidityRate, + uint256 originationFee, + uint256 variableBorrowIndex, + uint256 lastUpdateTimestamp, + bool usageAsCollateralEnabled + ) + { + return dataProvider.getUserReserveData(_reserve, _user); + } + + function getReserves() external view returns (address[] memory) { + return core.getReserves(); + } + + /** + * @dev internal function to save on code size for the onlyActiveReserve modifier + **/ + function requireReserveActiveInternal(address _reserve) internal view { + require(core.getReserveIsActive(_reserve), "Action requires an active reserve"); + } + + /** + * @notice internal function to save on code size for the onlyUnfreezedReserve modifier + **/ + function requireReserveNotFreezedInternal(address _reserve) internal view { + require(!core.getReserveIsFreezed(_reserve), "Action requires an unfreezed reserve"); + } + + /** + * @notice internal function to save on code size for the onlyAmountGreaterThanZero modifier + **/ + function requireAmountGreaterThanZeroInternal(uint256 _amount) internal pure { + require(_amount > 0, "Amount must be greater than 0"); + } +} diff --git a/contracts/lendingpool/LendingPoolConfigurator.sol b/contracts/lendingpool/LendingPoolConfigurator.sol new file mode 100644 index 00000000..d93d305c --- /dev/null +++ b/contracts/lendingpool/LendingPoolConfigurator.sol @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "../interfaces/IERC20Detailed.sol"; +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; +import "../configuration/LendingPoolAddressesProvider.sol"; +import "./LendingPoolCore.sol"; +import "../tokenization/AToken.sol"; + +/** +* @title LendingPoolConfigurator contract +* @author Aave +* @notice Executes configuration methods on the LendingPoolCore contract. Allows to enable/disable reserves, +* and set different protocol parameters. +**/ + +contract LendingPoolConfigurator is VersionedInitializable { + using SafeMath for uint256; + + /** + * @dev emitted when a reserve is initialized. + * @param _reserve the address of the reserve + * @param _aToken the address of the overlying aToken contract + * @param _interestRateStrategyAddress the address of the interest rate strategy for the reserve + **/ + event ReserveInitialized( + address indexed _reserve, + address indexed _aToken, + address _interestRateStrategyAddress + ); + + /** + * @dev emitted when a reserve is removed. + * @param _reserve the address of the reserve + **/ + event ReserveRemoved( + address indexed _reserve + ); + + /** + * @dev emitted when borrowing is enabled on a reserve + * @param _reserve the address of the reserve + * @param _stableRateEnabled true if stable rate borrowing is enabled, false otherwise + **/ + event BorrowingEnabledOnReserve(address _reserve, bool _stableRateEnabled); + + /** + * @dev emitted when borrowing is disabled on a reserve + * @param _reserve the address of the reserve + **/ + event BorrowingDisabledOnReserve(address indexed _reserve); + + /** + * @dev emitted when a reserve is enabled as collateral. + * @param _reserve the address of the reserve + * @param _ltv the loan to value of the asset when used as collateral + * @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized + * @param _liquidationBonus the bonus liquidators receive to liquidate this asset + **/ + event ReserveEnabledAsCollateral( + address indexed _reserve, + uint256 _ltv, + uint256 _liquidationThreshold, + uint256 _liquidationBonus + ); + + /** + * @dev emitted when a reserve is disabled as collateral + * @param _reserve the address of the reserve + **/ + event ReserveDisabledAsCollateral(address indexed _reserve); + + /** + * @dev emitted when stable rate borrowing is enabled on a reserve + * @param _reserve the address of the reserve + **/ + event StableRateEnabledOnReserve(address indexed _reserve); + + /** + * @dev emitted when stable rate borrowing is disabled on a reserve + * @param _reserve the address of the reserve + **/ + event StableRateDisabledOnReserve(address indexed _reserve); + + /** + * @dev emitted when a reserve is activated + * @param _reserve the address of the reserve + **/ + event ReserveActivated(address indexed _reserve); + + /** + * @dev emitted when a reserve is deactivated + * @param _reserve the address of the reserve + **/ + event ReserveDeactivated(address indexed _reserve); + + /** + * @dev emitted when a reserve is freezed + * @param _reserve the address of the reserve + **/ + event ReserveFreezed(address indexed _reserve); + + /** + * @dev emitted when a reserve is unfreezed + * @param _reserve the address of the reserve + **/ + event ReserveUnfreezed(address indexed _reserve); + + /** + * @dev emitted when a reserve loan to value is updated + * @param _reserve the address of the reserve + * @param _ltv the new value for the loan to value + **/ + event ReserveBaseLtvChanged(address _reserve, uint256 _ltv); + + /** + * @dev emitted when a reserve liquidation threshold is updated + * @param _reserve the address of the reserve + * @param _threshold the new value for the liquidation threshold + **/ + event ReserveLiquidationThresholdChanged(address _reserve, uint256 _threshold); + + /** + * @dev emitted when a reserve liquidation bonus is updated + * @param _reserve the address of the reserve + * @param _bonus the new value for the liquidation bonus + **/ + event ReserveLiquidationBonusChanged(address _reserve, uint256 _bonus); + + /** + * @dev emitted when the reserve decimals are updated + * @param _reserve the address of the reserve + * @param _decimals the new decimals + **/ + event ReserveDecimalsChanged(address _reserve, uint256 _decimals); + + + /** + * @dev emitted when a reserve interest strategy contract is updated + * @param _reserve the address of the reserve + * @param _strategy the new address of the interest strategy contract + **/ + event ReserveInterestRateStrategyChanged(address _reserve, address _strategy); + + LendingPoolAddressesProvider public poolAddressesProvider; + /** + * @dev only the lending pool manager can call functions affected by this modifier + **/ + modifier onlyLendingPoolManager { + require( + poolAddressesProvider.getLendingPoolManager() == msg.sender, + "The caller must be a lending pool manager" + ); + _; + } + + uint256 public constant CONFIGURATOR_REVISION = 0x3; + + function getRevision() internal override pure returns (uint256) { + return CONFIGURATOR_REVISION; + } + + function initialize(LendingPoolAddressesProvider _poolAddressesProvider) public initializer { + poolAddressesProvider = _poolAddressesProvider; + } + + /** + * @dev initializes a reserve + * @param _reserve the address of the reserve to be initialized + * @param _underlyingAssetDecimals the decimals of the reserve underlying asset + * @param _interestRateStrategyAddress the address of the interest rate strategy contract for this reserve + **/ + function initReserve( + address _reserve, + uint8 _underlyingAssetDecimals, + address _interestRateStrategyAddress + ) external onlyLendingPoolManager { + IERC20Detailed asset = IERC20Detailed(_reserve); + + string memory aTokenName = string(abi.encodePacked("Aave Interest bearing ", asset.name())); + string memory aTokenSymbol = string(abi.encodePacked("a", asset.symbol())); + + initReserveWithData( + _reserve, + aTokenName, + aTokenSymbol, + _underlyingAssetDecimals, + _interestRateStrategyAddress + ); + + } + + /** + * @dev initializes a reserve using aTokenData provided externally (useful if the underlying ERC20 contract doesn't expose name or decimals) + * @param _reserve the address of the reserve to be initialized + * @param _aTokenName the name of the aToken contract + * @param _aTokenSymbol the symbol of the aToken contract + * @param _underlyingAssetDecimals the decimals of the reserve underlying asset + * @param _interestRateStrategyAddress the address of the interest rate strategy contract for this reserve + **/ + function initReserveWithData( + address _reserve, + string memory _aTokenName, + string memory _aTokenSymbol, + uint8 _underlyingAssetDecimals, + address _interestRateStrategyAddress + ) public onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + + AToken aTokenInstance = new AToken( + poolAddressesProvider, + _reserve, + _underlyingAssetDecimals, + _aTokenName, + _aTokenSymbol + ); + core.initReserve( + _reserve, + address(aTokenInstance), + _underlyingAssetDecimals, + _interestRateStrategyAddress + ); + + emit ReserveInitialized( + _reserve, + address(aTokenInstance), + _interestRateStrategyAddress + ); + } + + /** + * @dev removes the last added reserve in the list of the reserves + * @param _reserveToRemove the address of the reserve + **/ + function removeLastAddedReserve( address _reserveToRemove) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.removeLastAddedReserve(_reserveToRemove); + emit ReserveRemoved(_reserveToRemove); + } + + /** + * @dev enables borrowing on a reserve + * @param _reserve the address of the reserve + * @param _stableBorrowRateEnabled true if stable borrow rate needs to be enabled by default on this reserve + **/ + function enableBorrowingOnReserve(address _reserve, bool _stableBorrowRateEnabled) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.enableBorrowingOnReserve(_reserve, _stableBorrowRateEnabled); + emit BorrowingEnabledOnReserve(_reserve, _stableBorrowRateEnabled); + } + + /** + * @dev disables borrowing on a reserve + * @param _reserve the address of the reserve + **/ + function disableBorrowingOnReserve(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.disableBorrowingOnReserve(_reserve); + + emit BorrowingDisabledOnReserve(_reserve); + } + + /** + * @dev enables a reserve to be used as collateral + * @param _reserve the address of the reserve + * @param _baseLTVasCollateral the loan to value of the asset when used as collateral + * @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized + * @param _liquidationBonus the bonus liquidators receive to liquidate this asset + **/ + function enableReserveAsCollateral( + address _reserve, + uint256 _baseLTVasCollateral, + uint256 _liquidationThreshold, + uint256 _liquidationBonus + ) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.enableReserveAsCollateral( + _reserve, + _baseLTVasCollateral, + _liquidationThreshold, + _liquidationBonus + ); + emit ReserveEnabledAsCollateral( + _reserve, + _baseLTVasCollateral, + _liquidationThreshold, + _liquidationBonus + ); + } + + /** + * @dev disables a reserve as collateral + * @param _reserve the address of the reserve + **/ + function disableReserveAsCollateral(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.disableReserveAsCollateral(_reserve); + + emit ReserveDisabledAsCollateral(_reserve); + } + + /** + * @dev enable stable rate borrowing on a reserve + * @param _reserve the address of the reserve + **/ + function enableReserveStableBorrowRate(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.enableReserveStableBorrowRate(_reserve); + + emit StableRateEnabledOnReserve(_reserve); + } + + /** + * @dev disable stable rate borrowing on a reserve + * @param _reserve the address of the reserve + **/ + function disableReserveStableBorrowRate(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.disableReserveStableBorrowRate(_reserve); + + emit StableRateDisabledOnReserve(_reserve); + } + + /** + * @dev activates a reserve + * @param _reserve the address of the reserve + **/ + function activateReserve(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.activateReserve(_reserve); + + emit ReserveActivated(_reserve); + } + + /** + * @dev deactivates a reserve + * @param _reserve the address of the reserve + **/ + function deactivateReserve(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + require(core.getReserveTotalLiquidity(_reserve) == 0, "The liquidity of the reserve needs to be 0"); + core.deactivateReserve(_reserve); + + emit ReserveDeactivated(_reserve); + } + + /** + * @dev freezes a reserve. A freezed reserve doesn't accept any new deposit, borrow or rate swap, but can accept repayments, liquidations, rate rebalances and redeems + * @param _reserve the address of the reserve + **/ + function freezeReserve(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.freezeReserve(_reserve); + + emit ReserveFreezed(_reserve); + } + + /** + * @dev unfreezes a reserve + * @param _reserve the address of the reserve + **/ + function unfreezeReserve(address _reserve) external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.unfreezeReserve(_reserve); + + emit ReserveUnfreezed(_reserve); + } + + /** + * @dev emitted when a reserve loan to value is updated + * @param _reserve the address of the reserve + * @param _ltv the new value for the loan to value + **/ + function setReserveBaseLTVasCollateral(address _reserve, uint256 _ltv) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.setReserveBaseLTVasCollateral(_reserve, _ltv); + emit ReserveBaseLtvChanged(_reserve, _ltv); + } + + /** + * @dev updates the liquidation threshold of a reserve. + * @param _reserve the address of the reserve + * @param _threshold the new value for the liquidation threshold + **/ + function setReserveLiquidationThreshold(address _reserve, uint256 _threshold) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.setReserveLiquidationThreshold(_reserve, _threshold); + emit ReserveLiquidationThresholdChanged(_reserve, _threshold); + } + + /** + * @dev updates the liquidation bonus of a reserve + * @param _reserve the address of the reserve + * @param _bonus the new value for the liquidation bonus + **/ + function setReserveLiquidationBonus(address _reserve, uint256 _bonus) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.setReserveLiquidationBonus(_reserve, _bonus); + emit ReserveLiquidationBonusChanged(_reserve, _bonus); + } + + /** + * @dev updates the reserve decimals + * @param _reserve the address of the reserve + * @param _decimals the new number of decimals + **/ + function setReserveDecimals(address _reserve, uint256 _decimals) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.setReserveDecimals(_reserve, _decimals); + emit ReserveDecimalsChanged(_reserve, _decimals); + } + + /** + * @dev sets the interest rate strategy of a reserve + * @param _reserve the address of the reserve + * @param _rateStrategyAddress the new address of the interest strategy contract + **/ + function setReserveInterestRateStrategyAddress(address _reserve, address _rateStrategyAddress) + external + onlyLendingPoolManager + { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.setReserveInterestRateStrategyAddress(_reserve, _rateStrategyAddress); + emit ReserveInterestRateStrategyChanged(_reserve, _rateStrategyAddress); + } + + /** + * @dev refreshes the lending pool core configuration to update the cached address + **/ + function refreshLendingPoolCoreConfiguration() external onlyLendingPoolManager { + LendingPoolCore core = LendingPoolCore(poolAddressesProvider.getLendingPoolCore()); + core.refreshConfiguration(); + } +} diff --git a/contracts/lendingpool/LendingPoolCore.sol b/contracts/lendingpool/LendingPoolCore.sol new file mode 100644 index 00000000..7a65e788 --- /dev/null +++ b/contracts/lendingpool/LendingPoolCore.sol @@ -0,0 +1,1802 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; + +import "../libraries/CoreLibrary.sol"; +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../interfaces/ILendingRateOracle.sol"; +import "../interfaces/IReserveInterestRateStrategy.sol"; +import "../libraries/WadRayMath.sol"; +import "../tokenization/AToken.sol"; +import "../libraries/EthAddressLib.sol"; + +/** +* @title LendingPoolCore contract +* @author Aave +* @notice Holds the state of the lending pool and all the funds deposited +* @dev NOTE: The core does not enforce security checks on the update of the state +* (eg, updateStateOnBorrow() does not enforce that borrowed is enabled on the reserve). +* The check that an action can be performed is a duty of the overlying LendingPool contract. +**/ + +contract LendingPoolCore is VersionedInitializable { + using SafeMath for uint256; + using WadRayMath for uint256; + using CoreLibrary for CoreLibrary.ReserveData; + using CoreLibrary for CoreLibrary.UserReserveData; + using SafeERC20 for IERC20; + using Address for address payable; + + + /** + * @dev DEPRECATED: This event was used in previous LendingPoolCore implementations, and it has been replaced by ReserveDataUpdated() + * @param reserve the address of the reserve + * @param liquidityRate the new liquidity rate + * @param stableBorrowRate the new stable borrow rate + * @param variableBorrowRate the new variable borrow rate + * @param liquidityIndex the new liquidity index + * @param variableBorrowIndex the new variable borrow index + **/ + event ReserveUpdated( + address indexed reserve, + uint256 liquidityRate, + uint256 stableBorrowRate, + uint256 variableBorrowRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex + ); + + + /** + * @dev Emitted when the state of a reserve is updated + * @dev NOTE: This event replaces the Deprecated ReserveUpdated() event, which didn't emit the average stable borrow rate + * @param reserve the address of the reserve + * @param liquidityRate the new liquidity rate + * @param stableBorrowRate the new stable borrow rate + * @param averageStableBorrowRate the new average stable borrow rate + * @param variableBorrowRate the new variable borrow rate + * @param liquidityIndex the new liquidity index + * @param variableBorrowIndex the new variable borrow index + **/ + event ReserveDataUpdated( + address indexed reserve, + uint256 liquidityRate, + uint256 stableBorrowRate, + uint256 averageStableBorrowRate, + uint256 variableBorrowRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex + ); + + address public lendingPoolAddress; + + LendingPoolAddressesProvider public addressesProvider; + + /** + * @dev only lending pools can use functions affected by this modifier + **/ + modifier onlyLendingPool { + require(lendingPoolAddress == msg.sender, "The caller must be a lending pool contract"); + _; + } + + /** + * @dev only lending pools configurator can use functions affected by this modifier + **/ + modifier onlyLendingPoolConfigurator { + require( + addressesProvider.getLendingPoolConfigurator() == msg.sender, + "The caller must be a lending pool configurator contract" + ); + _; + } + + mapping(address => CoreLibrary.ReserveData) internal reserves; + mapping(address => mapping(address => CoreLibrary.UserReserveData)) internal usersReserveData; + + address[] public reservesList; + + uint256 private constant CORE_REVISION = 0x7; + + /** + * @dev returns the revision number of the contract + **/ + function getRevision() internal virtual override pure returns (uint256) { + return CORE_REVISION; + } + + /** + * @dev initializes the Core contract, invoked upon registration on the AddressesProvider + * @param _addressesProvider the addressesProvider contract + **/ + + function initialize(LendingPoolAddressesProvider _addressesProvider) public virtual initializer { + addressesProvider = _addressesProvider; + refreshConfigInternal(); + } + + /** + * @dev updates the state of the core as a result of a deposit action + * @param _reserve the address of the reserve in which the deposit is happening + * @param _user the address of the the user depositing + * @param _amount the amount being deposited + * @param _isFirstDeposit true if the user is depositing for the first time + **/ + + function updateStateOnDeposit( + address _reserve, + address _user, + uint256 _amount, + bool _isFirstDeposit + ) external onlyLendingPool { + reserves[_reserve].updateCumulativeIndexes(); + updateReserveInterestRatesAndTimestampInternal(_reserve, _amount, 0); + + if (_isFirstDeposit) { + //if this is the first deposit of the user, we configure the deposit as enabled to be used as collateral + setUserUseReserveAsCollateral(_reserve, _user, true); + } + } + + /** + * @dev updates the state of the core as a result of a redeem action + * @param _reserve the address of the reserve in which the redeem is happening + * @param _user the address of the the user redeeming + * @param _amountRedeemed the amount being redeemed + * @param _userRedeemedEverything true if the user is redeeming everything + **/ + function updateStateOnRedeem( + address _reserve, + address _user, + uint256 _amountRedeemed, + bool _userRedeemedEverything + ) external onlyLendingPool { + //compound liquidity and variable borrow interests + reserves[_reserve].updateCumulativeIndexes(); + updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountRedeemed); + + //if user redeemed everything the useReserveAsCollateral flag is reset + if (_userRedeemedEverything) { + setUserUseReserveAsCollateral(_reserve, _user, false); + } + } + + /** + * @dev updates the state of the core as a result of a flashloan action + * @param _reserve the address of the reserve in which the flashloan is happening + * @param _income the income of the protocol as a result of the action + **/ + function updateStateOnFlashLoan( + address _reserve, + uint256 _availableLiquidityBefore, + uint256 _income, + uint256 _protocolFee + ) external onlyLendingPool { + transferFlashLoanProtocolFeeInternal(_reserve, _protocolFee); + + //compounding the cumulated interest + reserves[_reserve].updateCumulativeIndexes(); + + uint256 totalLiquidityBefore = _availableLiquidityBefore.add( + getReserveTotalBorrows(_reserve) + ); + + //compounding the received fee into the reserve + reserves[_reserve].cumulateToLiquidityIndex(totalLiquidityBefore, _income); + + //refresh interest rates + updateReserveInterestRatesAndTimestampInternal(_reserve, _income, 0); + } + + /** + * @dev updates the state of the core as a consequence of a borrow action. + * @param _reserve the address of the reserve on which the user is borrowing + * @param _user the address of the borrower + * @param _amountBorrowed the new amount borrowed + * @param _borrowFee the fee on the amount borrowed + * @param _rateMode the borrow rate mode (stable, variable) + * @return the new borrow rate for the user + **/ + function updateStateOnBorrow( + address _reserve, + address _user, + uint256 _amountBorrowed, + uint256 _borrowFee, + CoreLibrary.InterestRateMode _rateMode + ) external onlyLendingPool returns (uint256, uint256) { + // getting the previous borrow data of the user + (uint256 principalBorrowBalance, , uint256 balanceIncrease) = getUserBorrowBalances( + _reserve, + _user + ); + + updateReserveStateOnBorrowInternal( + _reserve, + _user, + principalBorrowBalance, + balanceIncrease, + _amountBorrowed, + _rateMode + ); + + updateUserStateOnBorrowInternal( + _reserve, + _user, + _amountBorrowed, + balanceIncrease, + _borrowFee, + _rateMode + ); + + updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountBorrowed); + + return (getUserCurrentBorrowRate(_reserve, _user), balanceIncrease); + } + + /** + * @dev updates the state of the core as a consequence of a repay action. + * @param _reserve the address of the reserve on which the user is repaying + * @param _user the address of the borrower + * @param _paybackAmountMinusFees the amount being paid back minus fees + * @param _originationFeeRepaid the fee on the amount that is being repaid + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _repaidWholeLoan true if the user is repaying the whole loan + **/ + + function updateStateOnRepay( + address _reserve, + address _user, + uint256 _paybackAmountMinusFees, + uint256 _originationFeeRepaid, + uint256 _balanceIncrease, + bool _repaidWholeLoan + ) external onlyLendingPool { + updateReserveStateOnRepayInternal( + _reserve, + _user, + _paybackAmountMinusFees, + _balanceIncrease + ); + updateUserStateOnRepayInternal( + _reserve, + _user, + _paybackAmountMinusFees, + _originationFeeRepaid, + _balanceIncrease, + _repaidWholeLoan + ); + + updateReserveInterestRatesAndTimestampInternal(_reserve, _paybackAmountMinusFees, 0); + } + + /** + * @dev updates the state of the core as a consequence of a swap rate action. + * @param _reserve the address of the reserve on which the user is repaying + * @param _user the address of the borrower + * @param _principalBorrowBalance the amount borrowed by the user + * @param _compoundedBorrowBalance the amount borrowed plus accrued interest + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _currentRateMode the current interest rate mode for the user + **/ + function updateStateOnSwapRate( + address _reserve, + address _user, + uint256 _principalBorrowBalance, + uint256 _compoundedBorrowBalance, + uint256 _balanceIncrease, + CoreLibrary.InterestRateMode _currentRateMode + ) external onlyLendingPool returns (CoreLibrary.InterestRateMode, uint256) { + updateReserveStateOnSwapRateInternal( + _reserve, + _user, + _principalBorrowBalance, + _compoundedBorrowBalance, + _currentRateMode + ); + + CoreLibrary.InterestRateMode newRateMode = updateUserStateOnSwapRateInternal( + _reserve, + _user, + _balanceIncrease, + _currentRateMode + ); + + updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0); + + return (newRateMode, getUserCurrentBorrowRate(_reserve, _user)); + } + + /** + * @dev updates the state of the core as a consequence of a liquidation action. + * @param _principalReserve the address of the principal reserve that is being repaid + * @param _collateralReserve the address of the collateral reserve that is being liquidated + * @param _user the address of the borrower + * @param _amountToLiquidate the amount being repaid by the liquidator + * @param _collateralToLiquidate the amount of collateral being liquidated + * @param _feeLiquidated the amount of origination fee being liquidated + * @param _liquidatedCollateralForFee the amount of collateral equivalent to the origination fee + bonus + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _liquidatorReceivesAToken true if the liquidator will receive aTokens, false otherwise + **/ + function updateStateOnLiquidation( + address _principalReserve, + address _collateralReserve, + address _user, + uint256 _amountToLiquidate, + uint256 _collateralToLiquidate, + uint256 _feeLiquidated, + uint256 _liquidatedCollateralForFee, + uint256 _balanceIncrease, + bool _liquidatorReceivesAToken + ) external onlyLendingPool { + updatePrincipalReserveStateOnLiquidationInternal( + _principalReserve, + _user, + _amountToLiquidate, + _balanceIncrease + ); + + updateCollateralReserveStateOnLiquidationInternal( + _collateralReserve + ); + + updateUserStateOnLiquidationInternal( + _principalReserve, + _user, + _amountToLiquidate, + _feeLiquidated, + _balanceIncrease + ); + + updateReserveInterestRatesAndTimestampInternal(_principalReserve, _amountToLiquidate, 0); + + if (!_liquidatorReceivesAToken) { + updateReserveInterestRatesAndTimestampInternal( + _collateralReserve, + 0, + _collateralToLiquidate.add(_liquidatedCollateralForFee) + ); + } + + } + + /** + * @dev updates the state of the core as a consequence of a stable rate rebalance + * @param _reserve the address of the principal reserve where the user borrowed + * @param _user the address of the borrower + * @param _balanceIncrease the accrued interest on the borrowed amount + * @return the new stable rate for the user + **/ + function updateStateOnRebalance(address _reserve, address _user, uint256 _balanceIncrease) + external + onlyLendingPool + returns (uint256) + { + updateReserveStateOnRebalanceInternal(_reserve, _user, _balanceIncrease); + + //update user data and rebalance the rate + updateUserStateOnRebalanceInternal(_reserve, _user, _balanceIncrease); + updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0); + return usersReserveData[_user][_reserve].stableBorrowRate; + } + + /** + * @dev enables or disables a reserve as collateral + * @param _reserve the address of the principal reserve where the user deposited + * @param _user the address of the depositor + * @param _useAsCollateral true if the depositor wants to use the reserve as collateral + **/ + function setUserUseReserveAsCollateral(address _reserve, address _user, bool _useAsCollateral) + public + onlyLendingPool + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + user.useAsCollateral = _useAsCollateral; + } + + /** + * @notice ETH/token transfer functions + **/ + + /** + * @dev fallback function enforces that the caller is a contract, to support flashloan transfers + **/ + receive() external payable { + //only contracts can send ETH to the core + require(msg.sender.isContract(), "Only contracts can send ether to the Lending pool core"); + + } + + /** + * @dev transfers to the user a specific amount from the reserve. + * @param _reserve the address of the reserve where the transfer is happening + * @param _user the address of the user receiving the transfer + * @param _amount the amount being transferred + **/ + function transferToUser(address _reserve, address payable _user, uint256 _amount) + external + onlyLendingPool + { + if (_reserve != EthAddressLib.ethAddress()) { + IERC20(_reserve).safeTransfer(_user, _amount); + } else { + //solium-disable-next-line + (bool result, ) = _user.call{value: _amount, gas: 50000}(""); + require(result, "Transfer of ETH failed"); + } + } + + /** + * @dev transfers the protocol fees to the fees collection address + * @param _token the address of the token being transferred + * @param _user the address of the user from where the transfer is happening + * @param _amount the amount being transferred + * @param _destination the fee receiver address + **/ + + function transferToFeeCollectionAddress( + address _token, + address _user, + uint256 _amount, + address _destination + ) external payable onlyLendingPool { + address payable feeAddress = address(uint160(_destination)); //cast the address to payable + + if (_token != EthAddressLib.ethAddress()) { + require( + msg.value == 0, + "User is sending ETH along with the ERC20 transfer. Check the value attribute of the transaction" + ); + IERC20(_token).safeTransferFrom(_user, feeAddress, _amount); + } else { + require(msg.value >= _amount, "The amount and the value sent to deposit do not match"); + //solium-disable-next-line + (bool result, ) = feeAddress.call{ value: _amount, gas: 50000}(""); + require(result, "Transfer of ETH failed"); + } + } + + /** + * @dev transfers the fees to the fees collection address in the case of liquidation + * @param _token the address of the token being transferred + * @param _amount the amount being transferred + * @param _destination the fee receiver address + **/ + function liquidateFee( + address _token, + uint256 _amount, + address _destination + ) external payable onlyLendingPool { + address payable feeAddress = address(uint160(_destination)); //cast the address to payable + require( + msg.value == 0, + "Fee liquidation does not require any transfer of value" + ); + + if (_token != EthAddressLib.ethAddress()) { + IERC20(_token).safeTransfer(feeAddress, _amount); + } else { + //solium-disable-next-line + (bool result, ) = feeAddress.call{ value: _amount, gas: 50000}(""); + require(result, "Transfer of ETH failed"); + } + } + + /** + * @dev transfers an amount from a user to the destination reserve + * @param _reserve the address of the reserve where the amount is being transferred + * @param _user the address of the user from where the transfer is happening + * @param _amount the amount being transferred + **/ + function transferToReserve(address _reserve, address payable _user, uint256 _amount) + external + payable + onlyLendingPool + { + if (_reserve != EthAddressLib.ethAddress()) { + require(msg.value == 0, "User is sending ETH along with the ERC20 transfer."); + IERC20(_reserve).safeTransferFrom(_user, address(this), _amount); + + } else { + require(msg.value >= _amount, "The amount and the value sent to deposit do not match"); + + if (msg.value > _amount) { + //send back excess ETH + uint256 excessAmount = msg.value.sub(_amount); + //solium-disable-next-line + (bool result, ) = _user.call{ value: _amount, gas: 50000}(""); + require(result, "Transfer of ETH failed"); + } + } + } + + /** + * @notice data access functions + **/ + + /** + * @dev returns the basic data (balances, fee accrued, reserve enabled/disabled as collateral) + * needed to calculate the global account data in the LendingPoolDataProvider + * @param _reserve the address of the reserve + * @param _user the address of the user + * @return the user deposited balance, the principal borrow balance, the fee, and if the reserve is enabled as collateral or not + **/ + function getUserBasicReserveData(address _reserve, address _user) + external + view + returns (uint256, uint256, uint256, bool) + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + uint256 underlyingBalance = getUserUnderlyingAssetBalance(_reserve, _user); + + if (user.principalBorrowBalance == 0) { + return (underlyingBalance, 0, 0, user.useAsCollateral); + } + + return ( + underlyingBalance, + user.getCompoundedBorrowBalance(reserve), + user.originationFee, + user.useAsCollateral + ); + } + + /** + * @dev checks if a user is allowed to borrow at a stable rate + * @param _reserve the reserve address + * @param _user the user + * @param _amount the amount the the user wants to borrow + * @return true if the user is allowed to borrow at a stable rate, false otherwise + **/ + + function isUserAllowedToBorrowAtStable(address _reserve, address _user, uint256 _amount) + external + view + returns (bool) + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + if (!reserve.isStableBorrowRateEnabled) return false; + + return + !user.useAsCollateral || + !reserve.usageAsCollateralEnabled || + _amount > getUserUnderlyingAssetBalance(_reserve, _user); + } + + /** + * @dev gets the underlying asset balance of a user based on the corresponding aToken balance. + * @param _reserve the reserve address + * @param _user the user address + * @return the underlying deposit balance of the user + **/ + + function getUserUnderlyingAssetBalance(address _reserve, address _user) + public + view + returns (uint256) + { + AToken aToken = AToken(reserves[_reserve].aTokenAddress); + return aToken.balanceOf(_user); + + } + + /** + * @dev gets the interest rate strategy contract address for the reserve + * @param _reserve the reserve address + * @return the address of the interest rate strategy contract + **/ + function getReserveInterestRateStrategyAddress(address _reserve) public view returns (address) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.interestRateStrategyAddress; + } + + /** + * @dev gets the aToken contract address for the reserve + * @param _reserve the reserve address + * @return the address of the aToken contract + **/ + + function getReserveATokenAddress(address _reserve) public view returns (address) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.aTokenAddress; + } + + /** + * @dev gets the available liquidity in the reserve. The available liquidity is the balance of the core contract + * @param _reserve the reserve address + * @return the available liquidity + **/ + function getReserveAvailableLiquidity(address _reserve) public view returns (uint256) { + uint256 balance = 0; + + if (_reserve == EthAddressLib.ethAddress()) { + balance = address(this).balance; + } else { + balance = IERC20(_reserve).balanceOf(address(this)); + } + return balance; + } + + /** + * @dev gets the total liquidity in the reserve. The total liquidity is the balance of the core contract + total borrows + * @param _reserve the reserve address + * @return the total liquidity + **/ + function getReserveTotalLiquidity(address _reserve) public view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return getReserveAvailableLiquidity(_reserve).add(reserve.getTotalBorrows()); + } + + /** + * @dev gets the normalized income of the reserve. a value of 1e27 means there is no income. A value of 2e27 means there + * there has been 100% income. + * @param _reserve the reserve address + * @return the reserve normalized income + **/ + function getReserveNormalizedIncome(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.getNormalizedIncome(); + } + + /** + * @dev gets the reserve total borrows + * @param _reserve the reserve address + * @return the total borrows (stable + variable) + **/ + function getReserveTotalBorrows(address _reserve) public view returns (uint256) { + return reserves[_reserve].getTotalBorrows(); + } + + /** + * @dev gets the reserve total borrows stable + * @param _reserve the reserve address + * @return the total borrows stable + **/ + function getReserveTotalBorrowsStable(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.totalBorrowsStable; + } + + /** + * @dev gets the reserve total borrows variable + * @param _reserve the reserve address + * @return the total borrows variable + **/ + + function getReserveTotalBorrowsVariable(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.totalBorrowsVariable; + } + + /** + * @dev gets the reserve liquidation threshold + * @param _reserve the reserve address + * @return the reserve liquidation threshold + **/ + + function getReserveLiquidationThreshold(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.liquidationThreshold; + } + + /** + * @dev gets the reserve liquidation bonus + * @param _reserve the reserve address + * @return the reserve liquidation bonus + **/ + + function getReserveLiquidationBonus(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.liquidationBonus; + } + + /** + * @dev gets the reserve current variable borrow rate. Is the base variable borrow rate if the reserve is empty + * @param _reserve the reserve address + * @return the reserve current variable borrow rate + **/ + + function getReserveCurrentVariableBorrowRate(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + if (reserve.currentVariableBorrowRate == 0) { + return + IReserveInterestRateStrategy(reserve.interestRateStrategyAddress) + .getBaseVariableBorrowRate(); + } + return reserve.currentVariableBorrowRate; + } + + /** + * @dev gets the reserve current stable borrow rate. Is the market rate if the reserve is empty + * @param _reserve the reserve address + * @return the reserve current stable borrow rate + **/ + + function getReserveCurrentStableBorrowRate(address _reserve) public view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + ILendingRateOracle oracle = ILendingRateOracle(addressesProvider.getLendingRateOracle()); + + if (reserve.currentStableBorrowRate == 0) { + //no stable rate borrows yet + return oracle.getMarketBorrowRate(_reserve); + } + + return reserve.currentStableBorrowRate; + } + + /** + * @dev gets the reserve average stable borrow rate. The average stable rate is the weighted average + * of all the loans taken at stable rate. + * @param _reserve the reserve address + * @return the reserve current average borrow rate + **/ + function getReserveCurrentAverageStableBorrowRate(address _reserve) + external + view + returns (uint256) + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.currentAverageStableBorrowRate; + } + + /** + * @dev gets the reserve liquidity rate + * @param _reserve the reserve address + * @return the reserve liquidity rate + **/ + function getReserveCurrentLiquidityRate(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.currentLiquidityRate; + } + + /** + * @dev gets the reserve liquidity cumulative index + * @param _reserve the reserve address + * @return the reserve liquidity cumulative index + **/ + function getReserveLiquidityCumulativeIndex(address _reserve) external view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.lastLiquidityCumulativeIndex; + } + + /** + * @dev gets the reserve variable borrow index + * @param _reserve the reserve address + * @return the reserve variable borrow index + **/ + function getReserveVariableBorrowsCumulativeIndex(address _reserve) + external + view + returns (uint256) + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.lastVariableBorrowCumulativeIndex; + } + + /** + * @dev this function aggregates the configuration parameters of the reserve. + * It's used in the LendingPoolDataProvider specifically to save gas, and avoid + * multiple external contract calls to fetch the same data. + * @param _reserve the reserve address + * @return the reserve decimals + * @return the base ltv as collateral + * @return the liquidation threshold + * @return if the reserve is used as collateral or not + **/ + function getReserveConfiguration(address _reserve) + external + view + returns (uint256, uint256, uint256, bool) + { + uint256 decimals; + uint256 baseLTVasCollateral; + uint256 liquidationThreshold; + bool usageAsCollateralEnabled; + + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + decimals = reserve.decimals; + baseLTVasCollateral = reserve.baseLTVasCollateral; + liquidationThreshold = reserve.liquidationThreshold; + usageAsCollateralEnabled = reserve.usageAsCollateralEnabled; + + return (decimals, baseLTVasCollateral, liquidationThreshold, usageAsCollateralEnabled); + } + + /** + * @dev returns the decimals of the reserve + * @param _reserve the reserve address + * @return the reserve decimals + **/ + function getReserveDecimals(address _reserve) external view returns (uint256) { + return reserves[_reserve].decimals; + } + + /** + * @dev returns true if the reserve is enabled for borrowing + * @param _reserve the reserve address + * @return true if the reserve is enabled for borrowing, false otherwise + **/ + + function isReserveBorrowingEnabled(address _reserve) external view returns (bool) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.borrowingEnabled; + } + + /** + * @dev returns true if the reserve is enabled as collateral + * @param _reserve the reserve address + * @return true if the reserve is enabled as collateral, false otherwise + **/ + + function isReserveUsageAsCollateralEnabled(address _reserve) external view returns (bool) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.usageAsCollateralEnabled; + } + + /** + * @dev returns true if the stable rate is enabled on reserve + * @param _reserve the reserve address + * @return true if the stable rate is enabled on reserve, false otherwise + **/ + function getReserveIsStableBorrowRateEnabled(address _reserve) external view returns (bool) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.isStableBorrowRateEnabled; + } + + /** + * @dev returns true if the reserve is active + * @param _reserve the reserve address + * @return true if the reserve is active, false otherwise + **/ + function getReserveIsActive(address _reserve) external view returns (bool) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.isActive; + } + + /** + * @notice returns if a reserve is freezed + * @param _reserve the reserve for which the information is needed + * @return true if the reserve is freezed, false otherwise + **/ + + function getReserveIsFreezed(address _reserve) external view returns (bool) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + return reserve.isFreezed; + } + + /** + * @notice returns the timestamp of the last action on the reserve + * @param _reserve the reserve for which the information is needed + * @return timestamp the last updated timestamp of the reserve + **/ + + function getReserveLastUpdate(address _reserve) external view returns (uint40 timestamp) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + timestamp = reserve.lastUpdateTimestamp; + } + + /** + * @dev returns the utilization rate U of a specific reserve + * @param _reserve the reserve for which the information is needed + * @return the utilization rate in ray + **/ + + function getReserveUtilizationRate(address _reserve) public view returns (uint256) { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + uint256 totalBorrows = reserve.getTotalBorrows(); + + if (totalBorrows == 0) { + return 0; + } + + uint256 availableLiquidity = getReserveAvailableLiquidity(_reserve); + + return totalBorrows.rayDiv(availableLiquidity.add(totalBorrows)); + } + + /** + * @return the array of reserves configured on the core + **/ + function getReserves() external view returns (address[] memory) { + return reservesList; + } + + /** + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return true if the user has chosen to use the reserve as collateral, false otherwise + **/ + function isUserUseReserveAsCollateralEnabled(address _reserve, address _user) + external + view + returns (bool) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + return user.useAsCollateral; + } + + /** + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return the origination fee for the user + **/ + function getUserOriginationFee(address _reserve, address _user) + external + view + returns (uint256) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + return user.originationFee; + } + + /** + * @dev users with no loans in progress have NONE as borrow rate mode + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return the borrow rate mode for the user, + **/ + + function getUserCurrentBorrowRateMode(address _reserve, address _user) + public + view + returns (CoreLibrary.InterestRateMode) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + if (user.principalBorrowBalance == 0) { + return CoreLibrary.InterestRateMode.NONE; + } + + return + user.stableBorrowRate > 0 + ? CoreLibrary.InterestRateMode.STABLE + : CoreLibrary.InterestRateMode.VARIABLE; + } + + /** + * @dev gets the current borrow rate of the user + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return the borrow rate for the user, + **/ + function getUserCurrentBorrowRate(address _reserve, address _user) + internal + view + returns (uint256) + { + CoreLibrary.InterestRateMode rateMode = getUserCurrentBorrowRateMode(_reserve, _user); + + if (rateMode == CoreLibrary.InterestRateMode.NONE) { + return 0; + } + + return + rateMode == CoreLibrary.InterestRateMode.STABLE + ? usersReserveData[_user][_reserve].stableBorrowRate + : reserves[_reserve].currentVariableBorrowRate; + } + + /** + * @dev the stable rate returned is 0 if the user is borrowing at variable or not borrowing at all + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return the user stable rate + **/ + function getUserCurrentStableBorrowRate(address _reserve, address _user) + external + view + returns (uint256) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + return user.stableBorrowRate; + } + + /** + * @dev calculates and returns the borrow balances of the user + * @param _reserve the address of the reserve + * @param _user the address of the user + * @return the principal borrow balance, the compounded balance and the balance increase since the last borrow/repay/swap/rebalance + **/ + + function getUserBorrowBalances(address _reserve, address _user) + public + view + returns (uint256, uint256, uint256) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + if (user.principalBorrowBalance == 0) { + return (0, 0, 0); + } + + uint256 principal = user.principalBorrowBalance; + uint256 compoundedBalance = CoreLibrary.getCompoundedBorrowBalance( + user, + reserves[_reserve] + ); + return (principal, compoundedBalance, compoundedBalance.sub(principal)); + } + + /** + * @dev the variable borrow index of the user is 0 if the user is not borrowing or borrowing at stable + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return the variable borrow index for the user + **/ + + function getUserVariableBorrowCumulativeIndex(address _reserve, address _user) + external + view + returns (uint256) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + return user.lastVariableBorrowCumulativeIndex; + } + + /** + * @dev the variable borrow index of the user is 0 if the user is not borrowing or borrowing at stable + * @param _reserve the address of the reserve for which the information is needed + * @param _user the address of the user for which the information is needed + * @return timestamp the variable borrow index for the user + **/ + + function getUserLastUpdate(address _reserve, address _user) + external + view + returns (uint256 timestamp) + { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + timestamp = user.lastUpdateTimestamp; + } + + /** + * @dev updates the lending pool core configuration + **/ + function refreshConfiguration() external onlyLendingPoolConfigurator { + refreshConfigInternal(); + } + + /** + * @dev initializes a reserve + * @param _reserve the address of the reserve + * @param _aTokenAddress the address of the overlying aToken contract + * @param _decimals the decimals of the reserve currency + * @param _interestRateStrategyAddress the address of the interest rate strategy contract + **/ + function initReserve( + address _reserve, + address _aTokenAddress, + uint256 _decimals, + address _interestRateStrategyAddress + ) external onlyLendingPoolConfigurator { + reserves[_reserve].init(_aTokenAddress, _decimals, _interestRateStrategyAddress); + addReserveToListInternal(_reserve); + + } + + + + /** + * @dev removes the last added reserve in the reservesList array + * @param _reserveToRemove the address of the reserve + **/ + function removeLastAddedReserve(address _reserveToRemove) + external onlyLendingPoolConfigurator { + + address lastReserve = reservesList[reservesList.length-1]; + + require(lastReserve == _reserveToRemove, "Reserve being removed is different than the reserve requested"); + + //as we can't check if totalLiquidity is 0 (since the reserve added might not be an ERC20) we at least check that there is nothing borrowed + require(getReserveTotalBorrows(lastReserve) == 0, "Cannot remove a reserve with liquidity deposited"); + + reserves[lastReserve].isActive = false; + reserves[lastReserve].aTokenAddress = address(0); + reserves[lastReserve].decimals = 0; + reserves[lastReserve].lastLiquidityCumulativeIndex = 0; + reserves[lastReserve].lastVariableBorrowCumulativeIndex = 0; + reserves[lastReserve].borrowingEnabled = false; + reserves[lastReserve].usageAsCollateralEnabled = false; + reserves[lastReserve].baseLTVasCollateral = 0; + reserves[lastReserve].liquidationThreshold = 0; + reserves[lastReserve].liquidationBonus = 0; + reserves[lastReserve].interestRateStrategyAddress = address(0); + + reservesList.pop(); + } + + /** + * @dev updates the address of the interest rate strategy contract + * @param _reserve the address of the reserve + * @param _rateStrategyAddress the address of the interest rate strategy contract + **/ + + function setReserveInterestRateStrategyAddress(address _reserve, address _rateStrategyAddress) + external + onlyLendingPoolConfigurator + { + reserves[_reserve].interestRateStrategyAddress = _rateStrategyAddress; + } + + /** + * @dev enables borrowing on a reserve. Also sets the stable rate borrowing + * @param _reserve the address of the reserve + * @param _stableBorrowRateEnabled true if the stable rate needs to be enabled, false otherwise + **/ + + function enableBorrowingOnReserve(address _reserve, bool _stableBorrowRateEnabled) + external + onlyLendingPoolConfigurator + { + reserves[_reserve].enableBorrowing(_stableBorrowRateEnabled); + } + + /** + * @dev disables borrowing on a reserve + * @param _reserve the address of the reserve + **/ + + function disableBorrowingOnReserve(address _reserve) external onlyLendingPoolConfigurator { + reserves[_reserve].disableBorrowing(); + } + + /** + * @dev enables a reserve to be used as collateral + * @param _reserve the address of the reserve + **/ + function enableReserveAsCollateral( + address _reserve, + uint256 _baseLTVasCollateral, + uint256 _liquidationThreshold, + uint256 _liquidationBonus + ) external onlyLendingPoolConfigurator { + reserves[_reserve].enableAsCollateral( + _baseLTVasCollateral, + _liquidationThreshold, + _liquidationBonus + ); + } + + /** + * @dev disables a reserve to be used as collateral + * @param _reserve the address of the reserve + **/ + function disableReserveAsCollateral(address _reserve) external onlyLendingPoolConfigurator { + reserves[_reserve].disableAsCollateral(); + } + + /** + * @dev enable the stable borrow rate mode on a reserve + * @param _reserve the address of the reserve + **/ + function enableReserveStableBorrowRate(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.isStableBorrowRateEnabled = true; + } + + /** + * @dev disable the stable borrow rate mode on a reserve + * @param _reserve the address of the reserve + **/ + function disableReserveStableBorrowRate(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.isStableBorrowRateEnabled = false; + } + + /** + * @dev activates a reserve + * @param _reserve the address of the reserve + **/ + function activateReserve(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + require( + reserve.lastLiquidityCumulativeIndex > 0 && + reserve.lastVariableBorrowCumulativeIndex > 0, + "Reserve has not been initialized yet" + ); + reserve.isActive = true; + } + + /** + * @dev deactivates a reserve + * @param _reserve the address of the reserve + **/ + function deactivateReserve(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.isActive = false; + } + + /** + * @notice allows the configurator to freeze the reserve. + * A freezed reserve does not allow any action apart from repay, redeem, liquidationCall, rebalance. + * @param _reserve the address of the reserve + **/ + function freezeReserve(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.isFreezed = true; + } + + /** + * @notice allows the configurator to unfreeze the reserve. A unfreezed reserve allows any action to be executed. + * @param _reserve the address of the reserve + **/ + function unfreezeReserve(address _reserve) external onlyLendingPoolConfigurator { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.isFreezed = false; + } + + /** + * @notice allows the configurator to update the loan to value of a reserve + * @param _reserve the address of the reserve + * @param _ltv the new loan to value + **/ + function setReserveBaseLTVasCollateral(address _reserve, uint256 _ltv) + external + onlyLendingPoolConfigurator + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.baseLTVasCollateral = _ltv; + } + + /** + * @notice allows the configurator to update the liquidation threshold of a reserve + * @param _reserve the address of the reserve + * @param _threshold the new liquidation threshold + **/ + function setReserveLiquidationThreshold(address _reserve, uint256 _threshold) + external + onlyLendingPoolConfigurator + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.liquidationThreshold = _threshold; + } + + /** + * @notice allows the configurator to update the liquidation bonus of a reserve + * @param _reserve the address of the reserve + * @param _bonus the new liquidation bonus + **/ + function setReserveLiquidationBonus(address _reserve, uint256 _bonus) + external + onlyLendingPoolConfigurator + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.liquidationBonus = _bonus; + } + + /** + * @notice allows the configurator to update the reserve decimals + * @param _reserve the address of the reserve + * @param _decimals the decimals of the reserve + **/ + function setReserveDecimals(address _reserve, uint256 _decimals) + external + onlyLendingPoolConfigurator + { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + reserve.decimals = _decimals; + } + + /** + * @notice internal functions + **/ + + /** + * @dev updates the state of a reserve as a consequence of a borrow action. + * @param _reserve the address of the reserve on which the user is borrowing + * @param _user the address of the borrower + * @param _principalBorrowBalance the previous borrow balance of the borrower before the action + * @param _balanceIncrease the accrued interest of the user on the previous borrowed amount + * @param _amountBorrowed the new amount borrowed + * @param _rateMode the borrow rate mode (stable, variable) + **/ + + function updateReserveStateOnBorrowInternal( + address _reserve, + address _user, + uint256 _principalBorrowBalance, + uint256 _balanceIncrease, + uint256 _amountBorrowed, + CoreLibrary.InterestRateMode _rateMode + ) internal { + reserves[_reserve].updateCumulativeIndexes(); + + //increasing reserve total borrows to account for the new borrow balance of the user + //NOTE: Depending on the previous borrow mode, the borrows might need to be switched from variable to stable or vice versa + + updateReserveTotalBorrowsByRateModeInternal( + _reserve, + _user, + _principalBorrowBalance, + _balanceIncrease, + _amountBorrowed, + _rateMode + ); + } + + /** + * @dev updates the state of a user as a consequence of a borrow action. + * @param _reserve the address of the reserve on which the user is borrowing + * @param _user the address of the borrower + * @param _amountBorrowed the amount borrowed + * @param _balanceIncrease the accrued interest of the user on the previous borrowed amount + * @param _rateMode the borrow rate mode (stable, variable) + **/ + + function updateUserStateOnBorrowInternal( + address _reserve, + address _user, + uint256 _amountBorrowed, + uint256 _balanceIncrease, + uint256 _fee, + CoreLibrary.InterestRateMode _rateMode + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + if (_rateMode == CoreLibrary.InterestRateMode.STABLE) { + //stable + //reset the user variable index, and update the stable rate + user.stableBorrowRate = reserve.currentStableBorrowRate; + user.lastVariableBorrowCumulativeIndex = 0; + } else if (_rateMode == CoreLibrary.InterestRateMode.VARIABLE) { + //variable + //reset the user stable rate, and store the new borrow index + user.stableBorrowRate = 0; + user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex; + } else { + revert("Invalid borrow rate mode"); + } + //increase the principal borrows and the origination fee + user.principalBorrowBalance = user.principalBorrowBalance.add(_amountBorrowed).add( + _balanceIncrease + ); + user.originationFee = user.originationFee.add(_fee); + + //solium-disable-next-line + user.lastUpdateTimestamp = uint40(block.timestamp); + + } + + /** + * @dev updates the state of the reserve as a consequence of a repay action. + * @param _reserve the address of the reserve on which the user is repaying + * @param _user the address of the borrower + * @param _paybackAmountMinusFees the amount being paid back minus fees + * @param _balanceIncrease the accrued interest on the borrowed amount + **/ + + function updateReserveStateOnRepayInternal( + address _reserve, + address _user, + uint256 _paybackAmountMinusFees, + uint256 _balanceIncrease + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode(_reserve, _user); + + //update the indexes + reserves[_reserve].updateCumulativeIndexes(); + + //compound the cumulated interest to the borrow balance and then subtracting the payback amount + if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) { + reserve.increaseTotalBorrowsStableAndUpdateAverageRate( + _balanceIncrease, + user.stableBorrowRate + ); + reserve.decreaseTotalBorrowsStableAndUpdateAverageRate( + _paybackAmountMinusFees, + user.stableBorrowRate + ); + } else { + reserve.increaseTotalBorrowsVariable(_balanceIncrease); + reserve.decreaseTotalBorrowsVariable(_paybackAmountMinusFees); + } + } + + /** + * @dev updates the state of the user as a consequence of a repay action. + * @param _reserve the address of the reserve on which the user is repaying + * @param _user the address of the borrower + * @param _paybackAmountMinusFees the amount being paid back minus fees + * @param _originationFeeRepaid the fee on the amount that is being repaid + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _repaidWholeLoan true if the user is repaying the whole loan + **/ + function updateUserStateOnRepayInternal( + address _reserve, + address _user, + uint256 _paybackAmountMinusFees, + uint256 _originationFeeRepaid, + uint256 _balanceIncrease, + bool _repaidWholeLoan + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + //update the user principal borrow balance, adding the cumulated interest and then subtracting the payback amount + user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease).sub( + _paybackAmountMinusFees + ); + user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex; + + //if the balance decrease is equal to the previous principal (user is repaying the whole loan) + //and the rate mode is stable, we reset the interest rate mode of the user + if (_repaidWholeLoan) { + user.stableBorrowRate = 0; + user.lastVariableBorrowCumulativeIndex = 0; + } + user.originationFee = user.originationFee.sub(_originationFeeRepaid); + + //solium-disable-next-line + user.lastUpdateTimestamp = uint40(block.timestamp); + + } + + /** + * @dev updates the state of the user as a consequence of a swap rate action. + * @param _reserve the address of the reserve on which the user is performing the rate swap + * @param _user the address of the borrower + * @param _principalBorrowBalance the the principal amount borrowed by the user + * @param _compoundedBorrowBalance the principal amount plus the accrued interest + * @param _currentRateMode the rate mode at which the user borrowed + **/ + function updateReserveStateOnSwapRateInternal( + address _reserve, + address _user, + uint256 _principalBorrowBalance, + uint256 _compoundedBorrowBalance, + CoreLibrary.InterestRateMode _currentRateMode + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + //compounding reserve indexes + reserve.updateCumulativeIndexes(); + + if (_currentRateMode == CoreLibrary.InterestRateMode.STABLE) { + uint256 userCurrentStableRate = user.stableBorrowRate; + + //swap to variable + reserve.decreaseTotalBorrowsStableAndUpdateAverageRate( + _principalBorrowBalance, + userCurrentStableRate + ); //decreasing stable from old principal balance + reserve.increaseTotalBorrowsVariable(_compoundedBorrowBalance); //increase variable borrows + } else if (_currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) { + //swap to stable + uint256 currentStableRate = reserve.currentStableBorrowRate; + reserve.decreaseTotalBorrowsVariable(_principalBorrowBalance); + reserve.increaseTotalBorrowsStableAndUpdateAverageRate( + _compoundedBorrowBalance, + currentStableRate + ); + + } else { + revert("Invalid rate mode received"); + } + } + + /** + * @dev updates the state of the user as a consequence of a swap rate action. + * @param _reserve the address of the reserve on which the user is performing the swap + * @param _user the address of the borrower + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _currentRateMode the current rate mode of the user + **/ + + function updateUserStateOnSwapRateInternal( + address _reserve, + address _user, + uint256 _balanceIncrease, + CoreLibrary.InterestRateMode _currentRateMode + ) internal returns (CoreLibrary.InterestRateMode) { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + CoreLibrary.InterestRateMode newMode = CoreLibrary.InterestRateMode.NONE; + + if (_currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) { + //switch to stable + newMode = CoreLibrary.InterestRateMode.STABLE; + user.stableBorrowRate = reserve.currentStableBorrowRate; + user.lastVariableBorrowCumulativeIndex = 0; + } else if (_currentRateMode == CoreLibrary.InterestRateMode.STABLE) { + newMode = CoreLibrary.InterestRateMode.VARIABLE; + user.stableBorrowRate = 0; + user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex; + } else { + revert("Invalid interest rate mode received"); + } + //compounding cumulated interest + user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease); + //solium-disable-next-line + user.lastUpdateTimestamp = uint40(block.timestamp); + + return newMode; + } + + /** + * @dev updates the state of the principal reserve as a consequence of a liquidation action. + * @param _principalReserve the address of the principal reserve that is being repaid + * @param _user the address of the borrower + * @param _amountToLiquidate the amount being repaid by the liquidator + * @param _balanceIncrease the accrued interest on the borrowed amount + **/ + + function updatePrincipalReserveStateOnLiquidationInternal( + address _principalReserve, + address _user, + uint256 _amountToLiquidate, + uint256 _balanceIncrease + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_principalReserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_principalReserve]; + + //update principal reserve data + reserve.updateCumulativeIndexes(); + + CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode( + _principalReserve, + _user + ); + + if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) { + //increase the total borrows by the compounded interest + reserve.increaseTotalBorrowsStableAndUpdateAverageRate( + _balanceIncrease, + user.stableBorrowRate + ); + + //decrease by the actual amount to liquidate + reserve.decreaseTotalBorrowsStableAndUpdateAverageRate( + _amountToLiquidate, + user.stableBorrowRate + ); + + } else { + //increase the total borrows by the compounded interest + reserve.increaseTotalBorrowsVariable(_balanceIncrease); + + //decrease by the actual amount to liquidate + reserve.decreaseTotalBorrowsVariable(_amountToLiquidate); + } + + } + + /** + * @dev updates the state of the collateral reserve as a consequence of a liquidation action. + * @param _collateralReserve the address of the collateral reserve that is being liquidated + **/ + function updateCollateralReserveStateOnLiquidationInternal( + address _collateralReserve + ) internal { + //update collateral reserve + reserves[_collateralReserve].updateCumulativeIndexes(); + + } + + /** + * @dev updates the state of the user being liquidated as a consequence of a liquidation action. + * @param _reserve the address of the principal reserve that is being repaid + * @param _user the address of the borrower + * @param _amountToLiquidate the amount being repaid by the liquidator + * @param _feeLiquidated the amount of origination fee being liquidated + * @param _balanceIncrease the accrued interest on the borrowed amount + **/ + function updateUserStateOnLiquidationInternal( + address _reserve, + address _user, + uint256 _amountToLiquidate, + uint256 _feeLiquidated, + uint256 _balanceIncrease + ) internal { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + //first increase by the compounded interest, then decrease by the liquidated amount + user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease).sub( + _amountToLiquidate + ); + + if ( + getUserCurrentBorrowRateMode(_reserve, _user) == CoreLibrary.InterestRateMode.VARIABLE + ) { + user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex; + } + + if(_feeLiquidated > 0){ + user.originationFee = user.originationFee.sub(_feeLiquidated); + } + + //solium-disable-next-line + user.lastUpdateTimestamp = uint40(block.timestamp); + } + + /** + * @dev updates the state of the reserve as a consequence of a stable rate rebalance + * @param _reserve the address of the principal reserve where the user borrowed + * @param _user the address of the borrower + * @param _balanceIncrease the accrued interest on the borrowed amount + **/ + + function updateReserveStateOnRebalanceInternal( + address _reserve, + address _user, + uint256 _balanceIncrease + ) internal { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + + reserve.updateCumulativeIndexes(); + + reserve.increaseTotalBorrowsStableAndUpdateAverageRate( + _balanceIncrease, + user.stableBorrowRate + ); + + } + + /** + * @dev updates the state of the user as a consequence of a stable rate rebalance + * @param _reserve the address of the principal reserve where the user borrowed + * @param _user the address of the borrower + * @param _balanceIncrease the accrued interest on the borrowed amount + **/ + + function updateUserStateOnRebalanceInternal( + address _reserve, + address _user, + uint256 _balanceIncrease + ) internal { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease); + user.stableBorrowRate = reserve.currentStableBorrowRate; + + //solium-disable-next-line + user.lastUpdateTimestamp = uint40(block.timestamp); + } + + /** + * @dev updates the state of the user as a consequence of a stable rate rebalance + * @param _reserve the address of the principal reserve where the user borrowed + * @param _user the address of the borrower + * @param _balanceIncrease the accrued interest on the borrowed amount + * @param _amountBorrowed the accrued interest on the borrowed amount + **/ + function updateReserveTotalBorrowsByRateModeInternal( + address _reserve, + address _user, + uint256 _principalBalance, + uint256 _balanceIncrease, + uint256 _amountBorrowed, + CoreLibrary.InterestRateMode _newBorrowRateMode + ) internal { + CoreLibrary.InterestRateMode previousRateMode = getUserCurrentBorrowRateMode( + _reserve, + _user + ); + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + if (previousRateMode == CoreLibrary.InterestRateMode.STABLE) { + CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve]; + reserve.decreaseTotalBorrowsStableAndUpdateAverageRate( + _principalBalance, + user.stableBorrowRate + ); + } else if (previousRateMode == CoreLibrary.InterestRateMode.VARIABLE) { + reserve.decreaseTotalBorrowsVariable(_principalBalance); + } + + uint256 newPrincipalAmount = _principalBalance.add(_balanceIncrease).add(_amountBorrowed); + if (_newBorrowRateMode == CoreLibrary.InterestRateMode.STABLE) { + reserve.increaseTotalBorrowsStableAndUpdateAverageRate( + newPrincipalAmount, + reserve.currentStableBorrowRate + ); + } else if (_newBorrowRateMode == CoreLibrary.InterestRateMode.VARIABLE) { + reserve.increaseTotalBorrowsVariable(newPrincipalAmount); + } else { + revert("Invalid new borrow rate mode"); + } + } + + /** + * @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl. + * Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information. + * @param _reserve the address of the reserve to be updated + * @param _liquidityAdded the amount of liquidity added to the protocol (deposit or repay) in the previous action + * @param _liquidityTaken the amount of liquidity taken from the protocol (redeem or borrow) + **/ + + function updateReserveInterestRatesAndTimestampInternal( + address _reserve, + uint256 _liquidityAdded, + uint256 _liquidityTaken + ) internal virtual { + CoreLibrary.ReserveData storage reserve = reserves[_reserve]; + + uint256 currentAvgStableRate = reserve.currentAverageStableBorrowRate; + + (uint256 newLiquidityRate, uint256 newStableRate, uint256 newVariableRate) = IReserveInterestRateStrategy( + reserve + .interestRateStrategyAddress + ) + .calculateInterestRates( + _reserve, + getReserveAvailableLiquidity(_reserve).add(_liquidityAdded).sub(_liquidityTaken), + reserve.totalBorrowsStable, + reserve.totalBorrowsVariable, + currentAvgStableRate + ); + + reserve.currentLiquidityRate = newLiquidityRate; + reserve.currentStableBorrowRate = newStableRate; + reserve.currentVariableBorrowRate = newVariableRate; + + //solium-disable-next-line + reserve.lastUpdateTimestamp = uint40(block.timestamp); + + emit ReserveDataUpdated( + _reserve, + newLiquidityRate, + newStableRate, + currentAvgStableRate, + newVariableRate, + reserve.lastLiquidityCumulativeIndex, + reserve.lastVariableBorrowCumulativeIndex + ); + } + + /** + * @dev transfers to the protocol fees of a flashloan to the fees collection address + * @param _token the address of the token being transferred + * @param _amount the amount being transferred + **/ + + function transferFlashLoanProtocolFeeInternal(address _token, uint256 _amount) internal { + address payable receiver = payable(addressesProvider.getTokenDistributor()); + + if (_token != EthAddressLib.ethAddress()) { + IERC20(_token).safeTransfer(receiver, _amount); + } else { + //solium-disable-next-line + (bool result, ) = receiver.call{ value: _amount }(""); + require(result, "Transfer to token distributor failed"); + } + } + + /** + * @dev updates the internal configuration of the core + **/ + function refreshConfigInternal() internal { + lendingPoolAddress = addressesProvider.getLendingPool(); + } + + /** + * @dev adds a reserve to the array of the reserves address + **/ + function addReserveToListInternal(address _reserve) internal { + bool reserveAlreadyAdded = false; + for (uint256 i = 0; i < reservesList.length; i++) + if (reservesList[i] == _reserve) { + reserveAlreadyAdded = true; + } + if (!reserveAlreadyAdded) reservesList.push(_reserve); + } + +} diff --git a/contracts/lendingpool/LendingPoolDataProvider.sol b/contracts/lendingpool/LendingPoolDataProvider.sol new file mode 100644 index 00000000..d1c7318b --- /dev/null +++ b/contracts/lendingpool/LendingPoolDataProvider.sol @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; + +import "../libraries/CoreLibrary.sol"; +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../libraries/WadRayMath.sol"; +import "../interfaces/IPriceOracleGetter.sol"; +import "../interfaces/IFeeProvider.sol"; +import "../tokenization/AToken.sol"; + +import "./LendingPoolCore.sol"; + +/** +* @title LendingPoolDataProvider contract +* @author Aave +* @notice Implements functions to fetch data from the core, and aggregate them in order to allow computation +* on the compounded balances and the account balances in ETH +**/ +contract LendingPoolDataProvider is VersionedInitializable { + using SafeMath for uint256; + using WadRayMath for uint256; + + LendingPoolCore public core; + LendingPoolAddressesProvider public addressesProvider; + + /** + * @dev specifies the health factor threshold at which the user position is liquidated. + * 1e18 by default, if the health factor drops below 1e18, the loan can be liquidated. + **/ + uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; + + uint256 public constant DATA_PROVIDER_REVISION = 0x1; + + function getRevision() internal override pure returns (uint256) { + return DATA_PROVIDER_REVISION; + } + + function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer { + addressesProvider = _addressesProvider; + core = LendingPoolCore(_addressesProvider.getLendingPoolCore()); + } + + /** + * @dev struct to hold calculateUserGlobalData() local computations + **/ + struct UserGlobalDataLocalVars { + uint256 reserveUnitPrice; + uint256 tokenUnit; + uint256 compoundedLiquidityBalance; + uint256 compoundedBorrowBalance; + uint256 reserveDecimals; + uint256 baseLtv; + uint256 liquidationThreshold; + uint256 originationFee; + bool usageAsCollateralEnabled; + bool userUsesReserveAsCollateral; + address currentReserve; + } + + /** + * @dev calculates the user data across the reserves. + * this includes the total liquidity/collateral/borrow balances in ETH, + * the average Loan To Value, the average Liquidation Ratio, and the Health factor. + * @param _user the address of the user + * @return totalLiquidityBalanceETH of the user in ETH + * @return totalCollateralBalanceETH of the user in ETH + * @return totalBorrowBalanceETH of the user in ETH + * @return totalFeesETH of the user in ETH + * @return currentLtv average Ltv + * @return currentLiquidationThreshold liquidation threshold + * @return healthFactor health factor + * @return healthFactorBelowThreshold is the health factor below threshold + **/ + function calculateUserGlobalData(address _user) + public + view + returns ( + uint256 totalLiquidityBalanceETH, + uint256 totalCollateralBalanceETH, + uint256 totalBorrowBalanceETH, + uint256 totalFeesETH, + uint256 currentLtv, + uint256 currentLiquidationThreshold, + uint256 healthFactor, + bool healthFactorBelowThreshold + ) + { + IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); + + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + UserGlobalDataLocalVars memory vars; + + address[] memory reserves = core.getReserves(); + + for (uint256 i = 0; i < reserves.length; i++) { + vars.currentReserve = reserves[i]; + + ( + vars.compoundedLiquidityBalance, + vars.compoundedBorrowBalance, + vars.originationFee, + vars.userUsesReserveAsCollateral + ) = core.getUserBasicReserveData(vars.currentReserve, _user); + + if (vars.compoundedLiquidityBalance == 0 && vars.compoundedBorrowBalance == 0) { + continue; + } + + //fetch reserve data + ( + vars.reserveDecimals, + vars.baseLtv, + vars.liquidationThreshold, + vars.usageAsCollateralEnabled + ) = core.getReserveConfiguration(vars.currentReserve); + + vars.tokenUnit = 10 ** vars.reserveDecimals; + vars.reserveUnitPrice = oracle.getAssetPrice(vars.currentReserve); + + //liquidity and collateral balance + if (vars.compoundedLiquidityBalance > 0) { + uint256 liquidityBalanceETH = vars + .reserveUnitPrice + .mul(vars.compoundedLiquidityBalance) + .div(vars.tokenUnit); + totalLiquidityBalanceETH = totalLiquidityBalanceETH.add(liquidityBalanceETH); + + if (vars.usageAsCollateralEnabled && vars.userUsesReserveAsCollateral) { + totalCollateralBalanceETH = totalCollateralBalanceETH.add(liquidityBalanceETH); + currentLtv = currentLtv.add(liquidityBalanceETH.mul(vars.baseLtv)); + currentLiquidationThreshold = currentLiquidationThreshold.add( + liquidityBalanceETH.mul(vars.liquidationThreshold) + ); + } + } + + if (vars.compoundedBorrowBalance > 0) { + totalBorrowBalanceETH = totalBorrowBalanceETH.add( + vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit) + ); + totalFeesETH = totalFeesETH.add( + vars.originationFee.mul(vars.reserveUnitPrice).div(vars.tokenUnit) + ); + } + } + + currentLtv = totalCollateralBalanceETH > 0 ? currentLtv.div(totalCollateralBalanceETH) : 0; + currentLiquidationThreshold = totalCollateralBalanceETH > 0 + ? currentLiquidationThreshold.div(totalCollateralBalanceETH) + : 0; + + healthFactor = calculateHealthFactorFromBalancesInternal( + totalCollateralBalanceETH, + totalBorrowBalanceETH, + totalFeesETH, + currentLiquidationThreshold + ); + healthFactorBelowThreshold = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD; + + } + + struct balanceDecreaseAllowedLocalVars { + uint256 decimals; + uint256 collateralBalanceETH; + uint256 borrowBalanceETH; + uint256 totalFeesETH; + uint256 currentLiquidationThreshold; + uint256 reserveLiquidationThreshold; + uint256 amountToDecreaseETH; + uint256 collateralBalancefterDecrease; + uint256 liquidationThresholdAfterDecrease; + uint256 healthFactorAfterDecrease; + bool reserveUsageAsCollateralEnabled; + } + + /** + * @dev check if a specific balance decrease is allowed (i.e. doesn't bring the user borrow position health factor under 1e18) + * @param _reserve the address of the reserve + * @param _user the address of the user + * @param _amount the amount to decrease + * @return true if the decrease of the balance is allowed + **/ + + function balanceDecreaseAllowed(address _reserve, address _user, uint256 _amount) + external + view + returns (bool) + { + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + balanceDecreaseAllowedLocalVars memory vars; + + ( + vars.decimals, + , + vars.reserveLiquidationThreshold, + vars.reserveUsageAsCollateralEnabled + ) = core.getReserveConfiguration(_reserve); + + if ( + !vars.reserveUsageAsCollateralEnabled || + !core.isUserUseReserveAsCollateralEnabled(_reserve, _user) + ) { + return true; //if reserve is not used as collateral, no reasons to block the transfer + } + + ( + , + vars.collateralBalanceETH, + vars.borrowBalanceETH, + vars.totalFeesETH, + , + vars.currentLiquidationThreshold, + , + + ) = calculateUserGlobalData(_user); + + if (vars.borrowBalanceETH == 0) { + return true; //no borrows - no reasons to block the transfer + } + + IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); + + vars.amountToDecreaseETH = oracle.getAssetPrice(_reserve).mul(_amount).div( + 10 ** vars.decimals + ); + + vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub( + vars.amountToDecreaseETH + ); + + //if there is a borrow, there can't be 0 collateral + if (vars.collateralBalancefterDecrease == 0) { + return false; + } + + vars.liquidationThresholdAfterDecrease = vars + .collateralBalanceETH + .mul(vars.currentLiquidationThreshold) + .sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold)) + .div(vars.collateralBalancefterDecrease); + + uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalancesInternal( + vars.collateralBalancefterDecrease, + vars.borrowBalanceETH, + vars.totalFeesETH, + vars.liquidationThresholdAfterDecrease + ); + + return healthFactorAfterDecrease > HEALTH_FACTOR_LIQUIDATION_THRESHOLD; + + } + + /** + * @notice calculates the amount of collateral needed in ETH to cover a new borrow. + * @param _reserve the reserve from which the user wants to borrow + * @param _amount the amount the user wants to borrow + * @param _fee the fee for the amount that the user needs to cover + * @param _userCurrentBorrowBalanceTH the current borrow balance of the user (before the borrow) + * @param _userCurrentLtv the average ltv of the user given his current collateral + * @return the total amount of collateral in ETH to cover the current borrow balance + the new amount + fee + **/ + function calculateCollateralNeededInETH( + address _reserve, + uint256 _amount, + uint256 _fee, + uint256 _userCurrentBorrowBalanceTH, + uint256 _userCurrentFeesETH, + uint256 _userCurrentLtv + ) external view returns (uint256) { + uint256 reserveDecimals = core.getReserveDecimals(_reserve); + + IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle()); + + uint256 requestedBorrowAmountETH = oracle + .getAssetPrice(_reserve) + .mul(_amount.add(_fee)) + .div(10 ** reserveDecimals); //price is in ether + + //add the current already borrowed amount to the amount requested to calculate the total collateral needed. + uint256 collateralNeededInETH = _userCurrentBorrowBalanceTH + .add(_userCurrentFeesETH) + .add(requestedBorrowAmountETH) + .mul(100) + .div(_userCurrentLtv); //LTV is calculated in percentage + + return collateralNeededInETH; + + } + + /** + * @dev calculates the equivalent amount in ETH that an user can borrow, depending on the available collateral and the + * average Loan To Value. + * @param collateralBalanceETH the total collateral balance + * @param borrowBalanceETH the total borrow balance + * @param totalFeesETH the total fees + * @param ltv the average loan to value + * @return the amount available to borrow in ETH for the user + **/ + + function calculateAvailableBorrowsETHInternal( + uint256 collateralBalanceETH, + uint256 borrowBalanceETH, + uint256 totalFeesETH, + uint256 ltv + ) internal view returns (uint256) { + uint256 availableBorrowsETH = collateralBalanceETH.mul(ltv).div(100); //ltv is in percentage + + if (availableBorrowsETH < borrowBalanceETH) { + return 0; + } + + availableBorrowsETH = availableBorrowsETH.sub(borrowBalanceETH.add(totalFeesETH)); + //calculate fee + uint256 borrowFee = IFeeProvider(addressesProvider.getFeeProvider()) + .calculateLoanOriginationFee(msg.sender, availableBorrowsETH); + return availableBorrowsETH.sub(borrowFee); + } + + /** + * @dev calculates the health factor from the corresponding balances + * @param collateralBalanceETH the total collateral balance in ETH + * @param borrowBalanceETH the total borrow balance in ETH + * @param totalFeesETH the total fees in ETH + * @param liquidationThreshold the avg liquidation threshold + **/ + function calculateHealthFactorFromBalancesInternal( + uint256 collateralBalanceETH, + uint256 borrowBalanceETH, + uint256 totalFeesETH, + uint256 liquidationThreshold + ) internal pure returns (uint256) { + if (borrowBalanceETH == 0) return uint256(-1); + + return + (collateralBalanceETH.mul(liquidationThreshold).div(100)).wadDiv( + borrowBalanceETH.add(totalFeesETH) + ); + } + + /** + * @dev returns the health factor liquidation threshold + **/ + function getHealthFactorLiquidationThreshold() public pure returns (uint256) { + return HEALTH_FACTOR_LIQUIDATION_THRESHOLD; + } + + /** + * @dev accessory functions to fetch data from the lendingPoolCore + **/ + function getReserveConfigurationData(address _reserve) + external + view + returns ( + uint256 ltv, + uint256 liquidationThreshold, + uint256 liquidationBonus, + address rateStrategyAddress, + bool usageAsCollateralEnabled, + bool borrowingEnabled, + bool stableBorrowRateEnabled, + bool isActive + ) + { + (, ltv, liquidationThreshold, usageAsCollateralEnabled) = core.getReserveConfiguration( + _reserve + ); + stableBorrowRateEnabled = core.getReserveIsStableBorrowRateEnabled(_reserve); + borrowingEnabled = core.isReserveBorrowingEnabled(_reserve); + isActive = core.getReserveIsActive(_reserve); + liquidationBonus = core.getReserveLiquidationBonus(_reserve); + + rateStrategyAddress = core.getReserveInterestRateStrategyAddress(_reserve); + } + + function getReserveData(address _reserve) + external + view + returns ( + uint256 totalLiquidity, + uint256 availableLiquidity, + uint256 totalBorrowsStable, + uint256 totalBorrowsVariable, + uint256 liquidityRate, + uint256 variableBorrowRate, + uint256 stableBorrowRate, + uint256 averageStableBorrowRate, + uint256 utilizationRate, + uint256 liquidityIndex, + uint256 variableBorrowIndex, + address aTokenAddress, + uint40 lastUpdateTimestamp + ) + { + totalLiquidity = core.getReserveTotalLiquidity(_reserve); + availableLiquidity = core.getReserveAvailableLiquidity(_reserve); + totalBorrowsStable = core.getReserveTotalBorrowsStable(_reserve); + totalBorrowsVariable = core.getReserveTotalBorrowsVariable(_reserve); + liquidityRate = core.getReserveCurrentLiquidityRate(_reserve); + variableBorrowRate = core.getReserveCurrentVariableBorrowRate(_reserve); + stableBorrowRate = core.getReserveCurrentStableBorrowRate(_reserve); + averageStableBorrowRate = core.getReserveCurrentAverageStableBorrowRate(_reserve); + utilizationRate = core.getReserveUtilizationRate(_reserve); + liquidityIndex = core.getReserveLiquidityCumulativeIndex(_reserve); + variableBorrowIndex = core.getReserveVariableBorrowsCumulativeIndex(_reserve); + aTokenAddress = core.getReserveATokenAddress(_reserve); + lastUpdateTimestamp = core.getReserveLastUpdate(_reserve); + } + + function getUserAccountData(address _user) + external + view + returns ( + uint256 totalLiquidityETH, + uint256 totalCollateralETH, + uint256 totalBorrowsETH, + uint256 totalFeesETH, + uint256 availableBorrowsETH, + uint256 currentLiquidationThreshold, + uint256 ltv, + uint256 healthFactor + ) + { + ( + totalLiquidityETH, + totalCollateralETH, + totalBorrowsETH, + totalFeesETH, + ltv, + currentLiquidationThreshold, + healthFactor, + + ) = calculateUserGlobalData(_user); + + availableBorrowsETH = calculateAvailableBorrowsETHInternal( + totalCollateralETH, + totalBorrowsETH, + totalFeesETH, + ltv + ); + } + + function getUserReserveData(address _reserve, address _user) + external + view + returns ( + uint256 currentATokenBalance, + uint256 currentBorrowBalance, + uint256 principalBorrowBalance, + uint256 borrowRateMode, + uint256 borrowRate, + uint256 liquidityRate, + uint256 originationFee, + uint256 variableBorrowIndex, + uint256 lastUpdateTimestamp, + bool usageAsCollateralEnabled + ) + { + currentATokenBalance = AToken(core.getReserveATokenAddress(_reserve)).balanceOf(_user); + CoreLibrary.InterestRateMode mode = core.getUserCurrentBorrowRateMode(_reserve, _user); + (principalBorrowBalance, currentBorrowBalance, ) = core.getUserBorrowBalances( + _reserve, + _user + ); + + //default is 0, if mode == CoreLibrary.InterestRateMode.NONE + if (mode == CoreLibrary.InterestRateMode.STABLE) { + borrowRate = core.getUserCurrentStableBorrowRate(_reserve, _user); + } else if (mode == CoreLibrary.InterestRateMode.VARIABLE) { + borrowRate = core.getReserveCurrentVariableBorrowRate(_reserve); + } + + borrowRateMode = uint256(mode); + liquidityRate = core.getReserveCurrentLiquidityRate(_reserve); + originationFee = core.getUserOriginationFee(_reserve, _user); + variableBorrowIndex = core.getUserVariableBorrowCumulativeIndex(_reserve, _user); + lastUpdateTimestamp = core.getUserLastUpdate(_reserve, _user); + usageAsCollateralEnabled = core.isUserUseReserveAsCollateralEnabled(_reserve, _user); + } +} diff --git a/contracts/lendingpool/LendingPoolLiquidationManager.sol b/contracts/lendingpool/LendingPoolLiquidationManager.sol new file mode 100644 index 00000000..a5f9bea7 --- /dev/null +++ b/contracts/lendingpool/LendingPoolLiquidationManager.sol @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +import "../libraries/openzeppelin-upgradeability/VersionedInitializable.sol"; + +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../configuration/LendingPoolParametersProvider.sol"; +import "../tokenization/AToken.sol"; +import "../libraries/CoreLibrary.sol"; +import "../libraries/WadRayMath.sol"; +import "./LendingPoolCore.sol"; +import "./LendingPoolDataProvider.sol"; +import "../interfaces/IPriceOracleGetter.sol"; + +/** +* @title LendingPoolLiquidationManager contract +* @author Aave +* @notice Implements the liquidation function. +**/ +contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializable { + using SafeMath for uint256; + using WadRayMath for uint256; + using Address for address; + + LendingPoolAddressesProvider public addressesProvider; + LendingPoolCore core; + LendingPoolDataProvider dataProvider; + LendingPoolParametersProvider parametersProvider; + IFeeProvider feeProvider; + address ethereumAddress; + + uint256 constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 50; + + /** + * @dev emitted when a borrow fee is liquidated + * @param _collateral the address of the collateral being liquidated + * @param _reserve the address of the reserve + * @param _user the address of the user being liquidated + * @param _feeLiquidated the total fee liquidated + * @param _liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee + * @param _timestamp the timestamp of the action + **/ + event OriginationFeeLiquidated( + address indexed _collateral, + address indexed _reserve, + address indexed _user, + uint256 _feeLiquidated, + uint256 _liquidatedCollateralForFee, + uint256 _timestamp + ); + + /** + * @dev emitted when a borrower is liquidated + * @param _collateral the address of the collateral being liquidated + * @param _reserve 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 _accruedBorrowInterest the amount of interest accrued by the borrower since the last action + * @param _liquidator the address of the liquidator + * @param _receiveAToken true if the liquidator wants to receive aTokens, false otherwise + * @param _timestamp the timestamp of the action + **/ + event LiquidationCall( + address indexed _collateral, + address indexed _reserve, + address indexed _user, + uint256 _purchaseAmount, + uint256 _liquidatedCollateralAmount, + uint256 _accruedBorrowInterest, + address _liquidator, + bool _receiveAToken, + uint256 _timestamp + ); + + 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 userCompoundedBorrowBalance; + uint256 borrowBalanceIncrease; + uint256 maxPrincipalAmountToLiquidate; + uint256 actualAmountToLiquidate; + uint256 liquidationRatio; + uint256 maxAmountCollateralToLiquidate; + uint256 originationFee; + uint256 feeLiquidated; + uint256 liquidatedCollateralForFee; + CoreLibrary.InterestRateMode borrowRateMode; + uint256 userStableRate; + bool isCollateralEnabled; + bool healthFactorBelowThreshold; + } + + /** + * @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 _reserve the address of the collateral to liquidated + * @param _reserve 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 _reserve, + address _user, + uint256 _purchaseAmount, + bool _receiveAToken + ) external payable returns (uint256, string memory) { + // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables + LiquidationCallLocalVars memory vars; + + (, , , , , , , vars.healthFactorBelowThreshold) = dataProvider.calculateUserGlobalData( + _user + ); + + if (!vars.healthFactorBelowThreshold) { + return ( + uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD), + "Health factor is not below the threshold" + ); + } + + vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(_collateral, _user); + + //if _user hasn't deposited this specific collateral, nothing can be liquidated + if (vars.userCollateralBalance == 0) { + return ( + uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE), + "Invalid collateral to liquidate" + ); + } + + vars.isCollateralEnabled = + core.isReserveUsageAsCollateralEnabled(_collateral) && + core.isUserUseReserveAsCollateralEnabled(_collateral, _user); + + //if _collateral isn't enabled as collateral by _user, it cannot be liquidated + if (!vars.isCollateralEnabled) { + return ( + uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED), + "The collateral chosen cannot be liquidated" + ); + } + + //if the user hasn't borrowed the specific currency defined by _reserve, it cannot be liquidated + (, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core + .getUserBorrowBalances(_reserve, _user); + + if (vars.userCompoundedBorrowBalance == 0) { + return ( + uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED), + "User did not borrow the specified currency" + ); + } + + //all clear - calculate the max principal amount that can be liquidated + vars.maxPrincipalAmountToLiquidate = vars + .userCompoundedBorrowBalance + .mul(LIQUIDATION_CLOSE_FACTOR_PERCENT) + .div(100); + + vars.actualAmountToLiquidate = _purchaseAmount > vars.maxPrincipalAmountToLiquidate + ? vars.maxPrincipalAmountToLiquidate + : _purchaseAmount; + + (uint256 maxCollateralToLiquidate, uint256 principalAmountNeeded) = calculateAvailableCollateralToLiquidate( + _collateral, + _reserve, + vars.actualAmountToLiquidate, + vars.userCollateralBalance + ); + + vars.originationFee = core.getUserOriginationFee(_reserve, _user); + + //if there is a fee to liquidate, calculate the maximum amount of fee that can be liquidated + if (vars.originationFee > 0) { + ( + vars.liquidatedCollateralForFee, + vars.feeLiquidated + ) = calculateAvailableCollateralToLiquidate( + _collateral, + _reserve, + vars.originationFee, + vars.userCollateralBalance.sub(maxCollateralToLiquidate) + ); + } + + //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 (principalAmountNeeded < vars.actualAmountToLiquidate) { + vars.actualAmountToLiquidate = principalAmountNeeded; + } + + //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve + if (!_receiveAToken) { + uint256 currentAvailableCollateral = core.getReserveAvailableLiquidity(_collateral); + if (currentAvailableCollateral < maxCollateralToLiquidate) { + return ( + uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY), + "There isn't enough liquidity available to liquidate" + ); + } + } + + core.updateStateOnLiquidation( + _reserve, + _collateral, + _user, + vars.actualAmountToLiquidate, + maxCollateralToLiquidate, + vars.feeLiquidated, + vars.liquidatedCollateralForFee, + vars.borrowBalanceIncrease, + _receiveAToken + ); + + AToken collateralAtoken = AToken(core.getReserveATokenAddress(_collateral)); + + //if liquidator reclaims the aToken, he receives the equivalent atoken amount + if (_receiveAToken) { + collateralAtoken.transferOnLiquidation(_user, msg.sender, maxCollateralToLiquidate); + } else { + //otherwise receives the underlying asset + //burn the equivalent amount of atoken + collateralAtoken.burnOnLiquidation(_user, maxCollateralToLiquidate); + core.transferToUser(_collateral, msg.sender, maxCollateralToLiquidate); + } + + //transfers the principal currency to the pool + core.transferToReserve{value: msg.value}(_reserve, msg.sender, vars.actualAmountToLiquidate); + + if (vars.feeLiquidated > 0) { + //if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of + //aTokens of the user + collateralAtoken.burnOnLiquidation(_user, vars.liquidatedCollateralForFee); + + //then liquidate the fee by transferring it to the fee collection address + core.liquidateFee( + _collateral, + vars.liquidatedCollateralForFee, + addressesProvider.getTokenDistributor() + ); + + emit OriginationFeeLiquidated( + _collateral, + _reserve, + _user, + vars.feeLiquidated, + vars.liquidatedCollateralForFee, + //solium-disable-next-line + block.timestamp + ); + + } + emit LiquidationCall( + _collateral, + _reserve, + _user, + vars.actualAmountToLiquidate, + maxCollateralToLiquidate, + vars.borrowBalanceIncrease, + msg.sender, + _receiveAToken, + //solium-disable-next-line + block.timestamp + ); + + return (uint256(LiquidationErrors.NO_ERROR), "No errors"); + } + + struct AvailableCollateralToLiquidateLocalVars { + uint256 userCompoundedBorrowBalance; + uint256 liquidationBonus; + uint256 collateralPrice; + uint256 principalCurrencyPrice; + uint256 maxAmountCollateralToLiquidate; + uint256 principalDecimals; + uint256 collateralDecimals; + } + + /** + * @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 _collateral the collateral to be liquidated + * @param _principal 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( + address _collateral, + address _principal, + uint256 _purchaseAmount, + uint256 _userCollateralBalance + ) internal view returns (uint256 collateralAmount, uint256 principalAmountNeeded) { + collateralAmount = 0; + 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(_collateral); + vars.principalCurrencyPrice = oracle.getAssetPrice(_principal); + vars.liquidationBonus = core.getReserveLiquidationBonus(_collateral); + vars.principalDecimals = core.getReserveDecimals(_principal); + vars.collateralDecimals = core.getReserveDecimals(_collateral); + + //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)) + .mul(vars.liquidationBonus) + .div(100); + + if (vars.maxAmountCollateralToLiquidate > _userCollateralBalance) { + collateralAmount = _userCollateralBalance; + principalAmountNeeded = vars + .collateralPrice + .mul(collateralAmount) + .mul(10 ** vars.principalDecimals) + .div(vars.principalCurrencyPrice.mul(10 ** vars.collateralDecimals)) + .mul(100) + .div(vars.liquidationBonus); + } else { + collateralAmount = vars.maxAmountCollateralToLiquidate; + principalAmountNeeded = _purchaseAmount; + } + + return (collateralAmount, principalAmountNeeded); + } +} diff --git a/contracts/libraries/CoreLibrary.sol b/contracts/libraries/CoreLibrary.sol new file mode 100644 index 00000000..f4b8631f --- /dev/null +++ b/contracts/libraries/CoreLibrary.sol @@ -0,0 +1,440 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "./WadRayMath.sol"; + +/** +* @title CoreLibrary library +* @author Aave +* @notice Defines the data structures of the reserves and the user data +**/ +library CoreLibrary { + using SafeMath for uint256; + using WadRayMath for uint256; + + enum InterestRateMode {NONE, STABLE, VARIABLE} + + uint256 internal constant SECONDS_PER_YEAR = 365 days; + + struct UserReserveData { + //principal amount borrowed by the user. + uint256 principalBorrowBalance; + //cumulated variable borrow index for the user. Expressed in ray + uint256 lastVariableBorrowCumulativeIndex; + //origination fee cumulated by the user + uint256 originationFee; + // stable borrow rate at which the user has borrowed. Expressed in ray + uint256 stableBorrowRate; + uint40 lastUpdateTimestamp; + //defines if a specific deposit should or not be used as a collateral in borrows + bool useAsCollateral; + } + + struct ReserveData { + /** + * @dev refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties. + **/ + //the liquidity index. Expressed in ray + uint256 lastLiquidityCumulativeIndex; + //the current supply rate. Expressed in ray + uint256 currentLiquidityRate; + //the total borrows of the reserve at a stable rate. Expressed in the currency decimals + uint256 totalBorrowsStable; + //the total borrows of the reserve at a variable rate. Expressed in the currency decimals + uint256 totalBorrowsVariable; + //the current variable borrow rate. Expressed in ray + uint256 currentVariableBorrowRate; + //the current stable borrow rate. Expressed in ray + uint256 currentStableBorrowRate; + //the current average stable borrow rate (weighted average of all the different stable rate loans). Expressed in ray + uint256 currentAverageStableBorrowRate; + //variable borrow index. Expressed in ray + uint256 lastVariableBorrowCumulativeIndex; + //the ltv of the reserve. Expressed in percentage (0-100) + uint256 baseLTVasCollateral; + //the liquidation threshold of the reserve. Expressed in percentage (0-100) + uint256 liquidationThreshold; + //the liquidation bonus of the reserve. Expressed in percentage + uint256 liquidationBonus; + //the decimals of the reserve asset + uint256 decimals; + /** + * @dev address of the aToken representing the asset + **/ + address aTokenAddress; + /** + * @dev address of the interest rate strategy contract + **/ + address interestRateStrategyAddress; + uint40 lastUpdateTimestamp; + // borrowingEnabled = true means users can borrow from this reserve + bool borrowingEnabled; + // usageAsCollateralEnabled = true means users can use this reserve as collateral + bool usageAsCollateralEnabled; + // isStableBorrowRateEnabled = true means users can borrow at a stable rate + bool isStableBorrowRateEnabled; + // isActive = true means the reserve has been activated and properly configured + bool isActive; + // isFreezed = true means the reserve only allows repays and redeems, but not deposits, new borrowings or rate swap + bool isFreezed; + } + + /** + * @dev returns the ongoing normalized income for the reserve. + * a value of 1e27 means there is no income. As time passes, the income is accrued. + * A value of 2*1e27 means that the income of the reserve is double the initial amount. + * @param _reserve the reserve object + * @return the normalized income. expressed in ray + **/ + function getNormalizedIncome(CoreLibrary.ReserveData storage _reserve) + internal + view + returns (uint256) + { + uint256 cumulated = calculateLinearInterest( + _reserve + .currentLiquidityRate, + _reserve + .lastUpdateTimestamp + ) + .rayMul(_reserve.lastLiquidityCumulativeIndex); + + return cumulated; + + } + + /** + * @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for + * a formal specification. + * @param _self the reserve object + **/ + function updateCumulativeIndexes(ReserveData storage _self) internal { + uint256 totalBorrows = getTotalBorrows(_self); + + if (totalBorrows > 0) { + //only cumulating if there is any income being produced + uint256 cumulatedLiquidityInterest = calculateLinearInterest( + _self.currentLiquidityRate, + _self.lastUpdateTimestamp + ); + + _self.lastLiquidityCumulativeIndex = cumulatedLiquidityInterest.rayMul( + _self.lastLiquidityCumulativeIndex + ); + + uint256 cumulatedVariableBorrowInterest = calculateCompoundedInterest( + _self.currentVariableBorrowRate, + _self.lastUpdateTimestamp + ); + _self.lastVariableBorrowCumulativeIndex = cumulatedVariableBorrowInterest.rayMul( + _self.lastVariableBorrowCumulativeIndex + ); + } + } + + /** + * @dev accumulates a predefined amount of asset to the reserve as a fixed, one time income. Used for example to accumulate + * the flashloan fee to the reserve, and spread it through the depositors. + * @param _self the reserve object + * @param _totalLiquidity the total liquidity available in the reserve + * @param _amount the amount to accomulate + **/ + function cumulateToLiquidityIndex( + ReserveData storage _self, + uint256 _totalLiquidity, + uint256 _amount + ) internal { + uint256 amountToLiquidityRatio = _amount.wadToRay().rayDiv(_totalLiquidity.wadToRay()); + + uint256 cumulatedLiquidity = amountToLiquidityRatio.add(WadRayMath.ray()); + + _self.lastLiquidityCumulativeIndex = cumulatedLiquidity.rayMul( + _self.lastLiquidityCumulativeIndex + ); + } + + /** + * @dev initializes a reserve + * @param _self the reserve object + * @param _aTokenAddress the address of the overlying atoken contract + * @param _decimals the number of decimals of the underlying asset + * @param _interestRateStrategyAddress the address of the interest rate strategy contract + **/ + function init( + ReserveData storage _self, + address _aTokenAddress, + uint256 _decimals, + address _interestRateStrategyAddress + ) external { + require(_self.aTokenAddress == address(0), "Reserve has already been initialized"); + + if (_self.lastLiquidityCumulativeIndex == 0) { + //if the reserve has not been initialized yet + _self.lastLiquidityCumulativeIndex = WadRayMath.ray(); + } + + if (_self.lastVariableBorrowCumulativeIndex == 0) { + _self.lastVariableBorrowCumulativeIndex = WadRayMath.ray(); + } + + _self.aTokenAddress = _aTokenAddress; + _self.decimals = _decimals; + + _self.interestRateStrategyAddress = _interestRateStrategyAddress; + _self.isActive = true; + _self.isFreezed = false; + + } + + /** + * @dev enables borrowing on a reserve + * @param _self the reserve object + * @param _stableBorrowRateEnabled true if the stable borrow rate must be enabled by default, false otherwise + **/ + function enableBorrowing(ReserveData storage _self, bool _stableBorrowRateEnabled) external { + require(_self.borrowingEnabled == false, "Reserve is already enabled"); + + _self.borrowingEnabled = true; + _self.isStableBorrowRateEnabled = _stableBorrowRateEnabled; + + } + + /** + * @dev disables borrowing on a reserve + * @param _self the reserve object + **/ + function disableBorrowing(ReserveData storage _self) external { + _self.borrowingEnabled = false; + } + + /** + * @dev enables a reserve to be used as collateral + * @param _self the reserve object + * @param _baseLTVasCollateral the loan to value of the asset when used as collateral + * @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized + * @param _liquidationBonus the bonus liquidators receive to liquidate this asset + **/ + function enableAsCollateral( + ReserveData storage _self, + uint256 _baseLTVasCollateral, + uint256 _liquidationThreshold, + uint256 _liquidationBonus + ) external { + require( + _self.usageAsCollateralEnabled == false, + "Reserve is already enabled as collateral" + ); + + _self.usageAsCollateralEnabled = true; + _self.baseLTVasCollateral = _baseLTVasCollateral; + _self.liquidationThreshold = _liquidationThreshold; + _self.liquidationBonus = _liquidationBonus; + + if (_self.lastLiquidityCumulativeIndex == 0) + _self.lastLiquidityCumulativeIndex = WadRayMath.ray(); + + } + + /** + * @dev disables a reserve as collateral + * @param _self the reserve object + **/ + function disableAsCollateral(ReserveData storage _self) external { + _self.usageAsCollateralEnabled = false; + } + + + + /** + * @dev calculates the compounded borrow balance of a user + * @param _self the userReserve object + * @param _reserve the reserve object + * @return the user compounded borrow balance + **/ + function getCompoundedBorrowBalance( + CoreLibrary.UserReserveData storage _self, + CoreLibrary.ReserveData storage _reserve + ) internal view returns (uint256) { + if (_self.principalBorrowBalance == 0) return 0; + + uint256 principalBorrowBalanceRay = _self.principalBorrowBalance.wadToRay(); + uint256 compoundedBalance = 0; + uint256 cumulatedInterest = 0; + + if (_self.stableBorrowRate > 0) { + cumulatedInterest = calculateCompoundedInterest( + _self.stableBorrowRate, + _self.lastUpdateTimestamp + ); + } else { + //variable interest + cumulatedInterest = calculateCompoundedInterest( + _reserve + .currentVariableBorrowRate, + _reserve + .lastUpdateTimestamp + ) + .rayMul(_reserve.lastVariableBorrowCumulativeIndex) + .rayDiv(_self.lastVariableBorrowCumulativeIndex); + } + + compoundedBalance = principalBorrowBalanceRay.rayMul(cumulatedInterest).rayToWad(); + + if (compoundedBalance == _self.principalBorrowBalance) { + //solium-disable-next-line + if (_self.lastUpdateTimestamp != block.timestamp) { + //no interest cumulation because of the rounding - we add 1 wei + //as symbolic cumulated interest to avoid interest free loans. + + return _self.principalBorrowBalance.add(1 wei); + } + } + + return compoundedBalance; + } + + /** + * @dev increases the total borrows at a stable rate on a specific reserve and updates the + * average stable rate consequently + * @param _reserve the reserve object + * @param _amount the amount to add to the total borrows stable + * @param _rate the rate at which the amount has been borrowed + **/ + function increaseTotalBorrowsStableAndUpdateAverageRate( + ReserveData storage _reserve, + uint256 _amount, + uint256 _rate + ) internal { + uint256 previousTotalBorrowStable = _reserve.totalBorrowsStable; + //updating reserve borrows stable + _reserve.totalBorrowsStable = _reserve.totalBorrowsStable.add(_amount); + + //update the average stable rate + //weighted average of all the borrows + uint256 weightedLastBorrow = _amount.wadToRay().rayMul(_rate); + uint256 weightedPreviousTotalBorrows = previousTotalBorrowStable.wadToRay().rayMul( + _reserve.currentAverageStableBorrowRate + ); + + _reserve.currentAverageStableBorrowRate = weightedLastBorrow + .add(weightedPreviousTotalBorrows) + .rayDiv(_reserve.totalBorrowsStable.wadToRay()); + } + + /** + * @dev decreases the total borrows at a stable rate on a specific reserve and updates the + * average stable rate consequently + * @param _reserve the reserve object + * @param _amount the amount to substract to the total borrows stable + * @param _rate the rate at which the amount has been repaid + **/ + function decreaseTotalBorrowsStableAndUpdateAverageRate( + ReserveData storage _reserve, + uint256 _amount, + uint256 _rate + ) internal { + require(_reserve.totalBorrowsStable >= _amount, "Invalid amount to decrease"); + + uint256 previousTotalBorrowStable = _reserve.totalBorrowsStable; + + //updating reserve borrows stable + _reserve.totalBorrowsStable = _reserve.totalBorrowsStable.sub(_amount); + + if (_reserve.totalBorrowsStable == 0) { + _reserve.currentAverageStableBorrowRate = 0; //no income if there are no stable rate borrows + return; + } + + //update the average stable rate + //weighted average of all the borrows + uint256 weightedLastBorrow = _amount.wadToRay().rayMul(_rate); + uint256 weightedPreviousTotalBorrows = previousTotalBorrowStable.wadToRay().rayMul( + _reserve.currentAverageStableBorrowRate + ); + + require( + weightedPreviousTotalBorrows >= weightedLastBorrow, + "The amounts to subtract don't match" + ); + + _reserve.currentAverageStableBorrowRate = weightedPreviousTotalBorrows + .sub(weightedLastBorrow) + .rayDiv(_reserve.totalBorrowsStable.wadToRay()); + } + + /** + * @dev increases the total borrows at a variable rate + * @param _reserve the reserve object + * @param _amount the amount to add to the total borrows variable + **/ + function increaseTotalBorrowsVariable(ReserveData storage _reserve, uint256 _amount) internal { + _reserve.totalBorrowsVariable = _reserve.totalBorrowsVariable.add(_amount); + } + + /** + * @dev decreases the total borrows at a variable rate + * @param _reserve the reserve object + * @param _amount the amount to substract to the total borrows variable + **/ + function decreaseTotalBorrowsVariable(ReserveData storage _reserve, uint256 _amount) internal { + require( + _reserve.totalBorrowsVariable >= _amount, + "The amount that is being subtracted from the variable total borrows is incorrect" + ); + _reserve.totalBorrowsVariable = _reserve.totalBorrowsVariable.sub(_amount); + } + + /** + * @dev function to calculate the interest using a linear interest rate formula + * @param _rate the interest rate, in ray + * @param _lastUpdateTimestamp the timestamp of the last update of the interest + * @return the interest rate linearly accumulated during the timeDelta, in ray + **/ + + function calculateLinearInterest(uint256 _rate, uint40 _lastUpdateTimestamp) + internal + view + returns (uint256) + { + //solium-disable-next-line + uint256 timeDifference = block.timestamp.sub(uint256(_lastUpdateTimestamp)); + + uint256 timeDelta = timeDifference.wadToRay().rayDiv(SECONDS_PER_YEAR.wadToRay()); + + return _rate.rayMul(timeDelta).add(WadRayMath.ray()); + } + + /** + * @dev function to calculate the interest using a compounded interest rate formula + * @param _rate the interest rate, in ray + * @param _lastUpdateTimestamp the timestamp of the last update of the interest + * @return the interest rate compounded during the timeDelta, in ray + **/ + function calculateCompoundedInterest(uint256 _rate, uint40 _lastUpdateTimestamp) + internal + view + returns (uint256) + { + //solium-disable-next-line + uint256 timeDifference = block.timestamp.sub(uint256(_lastUpdateTimestamp)); + + uint256 ratePerSecond = _rate.div(SECONDS_PER_YEAR); + + return ratePerSecond.add(WadRayMath.ray()).rayPow(timeDifference); + } + + /** + * @dev returns the total borrows on the reserve + * @param _reserve the reserve object + * @return the total borrows (stable + variable) + **/ + function getTotalBorrows(CoreLibrary.ReserveData storage _reserve) + internal + view + returns (uint256) + { + return _reserve.totalBorrowsStable.add(_reserve.totalBorrowsVariable); + } + +} diff --git a/contracts/libraries/EthAddressLib.sol b/contracts/libraries/EthAddressLib.sol new file mode 100644 index 00000000..07d485f9 --- /dev/null +++ b/contracts/libraries/EthAddressLib.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +library EthAddressLib { + + /** + * @dev returns the address used within the protocol to identify ETH + * @return the address assigned to ETH + */ + function ethAddress() internal pure returns(address) { + return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + } +} \ No newline at end of file diff --git a/contracts/libraries/UintConstants.sol b/contracts/libraries/UintConstants.sol new file mode 100644 index 00000000..67497035 --- /dev/null +++ b/contracts/libraries/UintConstants.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +library UintConstants { + /** + * @dev returns max uint256 + * @return max uint256 + */ + function maxUint() internal pure returns(uint256) { + return uint256(-1); + } + + /** + * @dev returns max uint256-1 + * @return max uint256-1 + */ + function maxUintMinus1() internal pure returns(uint256) { + return uint256(-1) - 1; + } +} \ No newline at end of file diff --git a/contracts/libraries/WadRayMath.sol b/contracts/libraries/WadRayMath.sol new file mode 100644 index 00000000..45c9793e --- /dev/null +++ b/contracts/libraries/WadRayMath.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +/** +* @title WadRayMath library +* @author Aave +* @dev Provides mul and div function for wads (decimal numbers with 18 digits precision) and rays (decimals with 27 digits) +**/ + +library WadRayMath { + using SafeMath for uint256; + + uint256 internal constant WAD = 1e18; + uint256 internal constant halfWAD = WAD / 2; + + uint256 internal constant RAY = 1e27; + uint256 internal constant halfRAY = RAY / 2; + + uint256 internal constant WAD_RAY_RATIO = 1e9; + + /** + * @return one ray, 1e27 + **/ + function ray() internal pure returns (uint256) { + return RAY; + } + + /** + * @return one wad, 1e18 + **/ + + function wad() internal pure returns (uint256) { + return WAD; + } + + /** + * @return half ray, 1e27/2 + **/ + function halfRay() internal pure returns (uint256) { + return halfRAY; + } + + /** + * @return half ray, 1e18/2 + **/ + function halfWad() internal pure returns (uint256) { + return halfWAD; + } + + /** + * @dev multiplies two wad, rounding half up to the nearest wad + * @param a wad + * @param b wad + * @return the result of a*b, in wad + **/ + function wadMul(uint256 a, uint256 b) internal pure returns (uint256) { + return halfWAD.add(a.mul(b)).div(WAD); + } + + /** + * @dev divides two wad, rounding half up to the nearest wad + * @param a wad + * @param b wad + * @return the result of a/b, in wad + **/ + function wadDiv(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 halfB = b / 2; + + return halfB.add(a.mul(WAD)).div(b); + } + + /** + * @dev multiplies two ray, rounding half up to the nearest ray + * @param a ray + * @param b ray + * @return the result of a*b, in ray + **/ + function rayMul(uint256 a, uint256 b) internal pure returns (uint256) { + return halfRAY.add(a.mul(b)).div(RAY); + } + + /** + * @dev divides two ray, rounding half up to the nearest ray + * @param a ray + * @param b ray + * @return the result of a/b, in ray + **/ + function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 halfB = b / 2; + + return halfB.add(a.mul(RAY)).div(b); + } + + /** + * @dev casts ray down to wad + * @param a ray + * @return a casted to wad, rounded half up to the nearest wad + **/ + function rayToWad(uint256 a) internal pure returns (uint256) { + uint256 halfRatio = WAD_RAY_RATIO / 2; + + return halfRatio.add(a).div(WAD_RAY_RATIO); + } + + /** + * @dev convert wad up to ray + * @param a wad + * @return a converted in ray + **/ + function wadToRay(uint256 a) internal pure returns (uint256) { + return a.mul(WAD_RAY_RATIO); + } + + /** + * @dev calculates base^exp. The code uses the ModExp precompile + * @return z base^exp, in ray + */ + //solium-disable-next-line + function rayPow(uint256 x, uint256 n) internal pure returns (uint256 z) { + + z = n % 2 != 0 ? x : RAY; + + for (n /= 2; n != 0; n /= 2) { + x = rayMul(x, x); + + if (n % 2 != 0) { + z = rayMul(z, x); + } + } + } + +} diff --git a/contracts/libraries/openzeppelin-upgradeability/AdminUpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/AdminUpgradeabilityProxy.sol new file mode 100644 index 00000000..9ba027b1 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/AdminUpgradeabilityProxy.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./BaseAdminUpgradeabilityProxy.sol"; + +/** + * @title AdminUpgradeabilityProxy + * @dev Extends from BaseAdminUpgradeabilityProxy with a constructor for + * initializing the implementation, admin, and init data. + */ +contract AdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, UpgradeabilityProxy { + /** + * Contract constructor. + * @param _logic address of the initial implementation. + * @param _admin Address of the proxy administrator. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + constructor(address _logic, address _admin, bytes memory _data) public payable UpgradeabilityProxy(_logic, _data) { + assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); + _setAdmin(_admin); + } + + /** + * @dev Only fall back when the sender is not the admin. + */ + function _willFallback() internal override(BaseAdminUpgradeabilityProxy, Proxy) { + BaseAdminUpgradeabilityProxy._willFallback(); + } + +} diff --git a/contracts/libraries/openzeppelin-upgradeability/BaseAdminUpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/BaseAdminUpgradeabilityProxy.sol new file mode 100644 index 00000000..bdff5011 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/BaseAdminUpgradeabilityProxy.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./UpgradeabilityProxy.sol"; + +/** + * @title BaseAdminUpgradeabilityProxy + * @dev This contract combines an upgradeability proxy with an authorization + * mechanism for administrative tasks. + * All external functions in this contract must be guarded by the + * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity + * feature proposal that would enable this to be done automatically. + */ +contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy { + /** + * @dev Emitted when the administration has been transferred. + * @param previousAdmin Address of the previous admin. + * @param newAdmin Address of the new admin. + */ + event AdminChanged(address previousAdmin, address newAdmin); + + /** + * @dev Storage slot with the admin of the contract. + * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + + /** + * @dev Modifier to check whether the `msg.sender` is the admin. + * If it is, it will run the function. Otherwise, it will delegate the call + * to the implementation. + */ + modifier ifAdmin() { + if (msg.sender == _admin()) { + _; + } else { + _fallback(); + } + } + + /** + * @return The address of the proxy admin. + */ + function admin() external ifAdmin returns (address) { + return _admin(); + } + + /** + * @return The address of the implementation. + */ + function implementation() external ifAdmin returns (address) { + return _implementation(); + } + + /** + * @dev Changes the admin of the proxy. + * Only the current admin can call this function. + * @param newAdmin Address to transfer proxy administration to. + */ + function changeAdmin(address newAdmin) external ifAdmin { + require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address"); + emit AdminChanged(_admin(), newAdmin); + _setAdmin(newAdmin); + } + + /** + * @dev Upgrade the backing implementation of the proxy. + * Only the admin can call this function. + * @param newImplementation Address of the new implementation. + */ + function upgradeTo(address newImplementation) external ifAdmin { + _upgradeTo(newImplementation); + } + + /** + * @dev Upgrade the backing implementation of the proxy and call a function + * on the new implementation. + * This is useful to initialize the proxied contract. + * @param newImplementation Address of the new implementation. + * @param data Data to send as msg.data in the low level call. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + */ + function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin { + _upgradeTo(newImplementation); + (bool success, ) = newImplementation.delegatecall(data); + require(success); + } + + /** + * @return adm The admin slot. + */ + function _admin() internal view returns (address adm) { + bytes32 slot = ADMIN_SLOT; + //solium-disable-next-line + assembly { + adm := sload(slot) + } + } + + /** + * @dev Sets the address of the proxy admin. + * @param newAdmin Address of the new proxy admin. + */ + function _setAdmin(address newAdmin) internal { + bytes32 slot = ADMIN_SLOT; + //solium-disable-next-line + assembly { + sstore(slot, newAdmin) + } + } + + /** + * @dev Only fall back when the sender is not the admin. + */ + function _willFallback() internal override virtual { + require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin"); + super._willFallback(); + } +} diff --git a/contracts/libraries/openzeppelin-upgradeability/BaseUpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/BaseUpgradeabilityProxy.sol new file mode 100644 index 00000000..21347927 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/BaseUpgradeabilityProxy.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./Proxy.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @title BaseUpgradeabilityProxy + * @dev This contract implements a proxy that allows to change the + * implementation address to which it will delegate. + * Such a change is called an implementation upgrade. + */ +contract BaseUpgradeabilityProxy is Proxy { + /** + * @dev Emitted when the implementation is upgraded. + * @param implementation Address of the new implementation. + */ + event Upgraded(address indexed implementation); + + /** + * @dev Storage slot with the address of the current implementation. + * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is + * validated in the constructor. + */ + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + /** + * @dev Returns the current implementation. + * @return impl Address of the current implementation + */ + function _implementation() internal override view returns (address impl) { + bytes32 slot = IMPLEMENTATION_SLOT; + //solium-disable-next-line + assembly { + impl := sload(slot) + } + } + + /** + * @dev Upgrades the proxy to a new implementation. + * @param newImplementation Address of the new implementation. + */ + function _upgradeTo(address newImplementation) internal { + _setImplementation(newImplementation); + emit Upgraded(newImplementation); + } + + /** + * @dev Sets the implementation address of the proxy. + * @param newImplementation Address of the new implementation. + */ + function _setImplementation(address newImplementation) internal { + require( + Address.isContract(newImplementation), + "Cannot set a proxy implementation to a non-contract address" + ); + + bytes32 slot = IMPLEMENTATION_SLOT; + + //solium-disable-next-line + assembly { + sstore(slot, newImplementation) + } + } +} diff --git a/contracts/libraries/openzeppelin-upgradeability/Initializable.sol b/contracts/libraries/openzeppelin-upgradeability/Initializable.sol new file mode 100644 index 00000000..3c7dd122 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/Initializable.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity >=0.4.24 <0.7.0; + +/** + * @title Initializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + */ +contract Initializable { + /** + * @dev Indicates that the contract has been initialized. + */ + bool private initialized; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + initialized = true; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + //solium-disable-next-line + assembly { + cs := extcodesize(address()) + } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} diff --git a/contracts/libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol new file mode 100644 index 00000000..47e57418 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/InitializableAdminUpgradeabilityProxy.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./BaseAdminUpgradeabilityProxy.sol"; +import "./InitializableUpgradeabilityProxy.sol"; + +/** + * @title InitializableAdminUpgradeabilityProxy + * @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for + * initializing the implementation, admin, and init data. + */ +contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy { + /** + * Contract initializer. + * @param _logic address of the initial implementation. + * @param _admin Address of the proxy administrator. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + function initialize(address _logic, address _admin, bytes memory _data) public payable { + require(_implementation() == address(0)); + InitializableUpgradeabilityProxy.initialize(_logic, _data); + assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); + _setAdmin(_admin); + } + + /** + * @dev Only fall back when the sender is not the admin. + */ + function _willFallback() internal override(BaseAdminUpgradeabilityProxy, Proxy) { + BaseAdminUpgradeabilityProxy._willFallback(); + } + +} diff --git a/contracts/libraries/openzeppelin-upgradeability/InitializableUpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/InitializableUpgradeabilityProxy.sol new file mode 100644 index 00000000..bbb67159 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/InitializableUpgradeabilityProxy.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./BaseUpgradeabilityProxy.sol"; + +/** + * @title InitializableUpgradeabilityProxy + * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing + * implementation and init data. + */ +contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy { + /** + * @dev Contract initializer. + * @param _logic Address of the initial implementation. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + function initialize(address _logic, bytes memory _data) public payable { + require(_implementation() == address(0)); + assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); + _setImplementation(_logic); + if (_data.length > 0) { + (bool success, ) = _logic.delegatecall(_data); + require(success); + } + } +} diff --git a/contracts/libraries/openzeppelin-upgradeability/Proxy.sol b/contracts/libraries/openzeppelin-upgradeability/Proxy.sol new file mode 100644 index 00000000..05fc5a47 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/Proxy.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.0; + +/** + * @title Proxy + * @dev Implements delegation of calls to other contracts, with proper + * forwarding of return values and bubbling of failures. + * It defines a fallback function that delegates all calls to the address + * returned by the abstract _implementation() internal function. + */ +abstract contract Proxy { + /** + * @dev Fallback function. + * Implemented entirely in `_fallback`. + */ + receive() external payable { + _fallback(); + } + + /** + * @return The Address of the implementation. + */ + function _implementation() internal virtual view returns (address); + + /** + * @dev Delegates execution to an implementation contract. + * This is a low level function that doesn't return to its internal call site. + * It will return to the external caller whatever the implementation returns. + * @param implementation Address to delegate. + */ + function _delegate(address implementation) internal { + //solium-disable-next-line + assembly { + // Copy msg.data. We take full control of memory in this inline assembly + // block because it will not return to Solidity code. We overwrite the + // Solidity scratch pad at memory position 0. + calldatacopy(0, 0, calldatasize()) + + // Call the implementation. + // out and outsize are 0 because we don't know the size yet. + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // Copy the returned data. + returndatacopy(0, 0, returndatasize()) + + switch result + // delegatecall returns 0 on error. + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } + + /** + * @dev Function that is run as the first thing in the fallback function. + * Can be redefined in derived contracts to add functionality. + * Redefinitions must call super._willFallback(). + */ + function _willFallback() internal virtual {} + + /** + * @dev fallback implementation. + * Extracted to enable manual triggering. + */ + function _fallback() internal { + _willFallback(); + _delegate(_implementation()); + } +} diff --git a/contracts/libraries/openzeppelin-upgradeability/UpgradeabilityProxy.sol b/contracts/libraries/openzeppelin-upgradeability/UpgradeabilityProxy.sol new file mode 100644 index 00000000..0c63ffc6 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/UpgradeabilityProxy.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./BaseUpgradeabilityProxy.sol"; + +/** + * @title UpgradeabilityProxy + * @dev Extends BaseUpgradeabilityProxy with a constructor for initializing + * implementation and init data. + */ +contract UpgradeabilityProxy is BaseUpgradeabilityProxy { + /** + * @dev Contract constructor. + * @param _logic Address of the initial implementation. + * @param _data Data to send as msg.data to the implementation to initialize the proxied contract. + * It should include the signature and the parameters of the function to be called, as described in + * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding. + * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped. + */ + constructor(address _logic, bytes memory _data) public payable { + assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); + _setImplementation(_logic); + if (_data.length > 0) { + (bool success, ) = _logic.delegatecall(_data); + require(success); + } + } +} diff --git a/contracts/libraries/openzeppelin-upgradeability/VersionedInitializable.sol b/contracts/libraries/openzeppelin-upgradeability/VersionedInitializable.sol new file mode 100644 index 00000000..e0dff3e5 --- /dev/null +++ b/contracts/libraries/openzeppelin-upgradeability/VersionedInitializable.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.8; + +/** + * @title VersionedInitializable + * + * @dev Helper contract to support initializer functions. To use it, replace + * the constructor with a function that has the `initializer` modifier. + * WARNING: Unlike constructors, initializer functions must be manually + * invoked. This applies both to deploying an Initializable contract, as well + * as extending an Initializable contract via inheritance. + * WARNING: When used with inheritance, manual care must be taken to not invoke + * a parent initializer twice, or ensure that all initializers are idempotent, + * because this is not dealt with automatically as with constructors. + * + * @author Aave, inspired by the OpenZeppelin Initializable contract + */ +abstract contract VersionedInitializable { + /** + * @dev Indicates that the contract has been initialized. + */ + uint256 private lastInitializedRevision = 0; + + /** + * @dev Indicates that the contract is in the process of being initialized. + */ + bool private initializing; + + /** + * @dev Modifier to use in the initializer function of a contract. + */ + modifier initializer() { + uint256 revision = getRevision(); + require(initializing || isConstructor() || revision > lastInitializedRevision, "Contract instance has already been initialized"); + + bool isTopLevelCall = !initializing; + if (isTopLevelCall) { + initializing = true; + lastInitializedRevision = revision; + } + + _; + + if (isTopLevelCall) { + initializing = false; + } + } + + /// @dev returns the revision number of the contract. + /// Needs to be defined in the inherited class as a constant. + function getRevision() internal virtual pure returns(uint256); + + + /// @dev Returns true if and only if the function is running in the constructor + function isConstructor() private view returns (bool) { + // extcodesize checks the size of the code stored in an address, and + // address returns the current address. Since the code is still not + // deployed when running a constructor, any checks on its code size will + // yield zero, making it an effective way to detect if a contract is + // under construction or not. + uint256 cs; + //solium-disable-next-line + assembly { + cs := extcodesize(address()) + } + return cs == 0; + } + + // Reserved storage space to allow for layout changes in the future. + uint256[50] private ______gap; +} diff --git a/contracts/misc/ChainlinkProxyPriceProvider.sol b/contracts/misc/ChainlinkProxyPriceProvider.sol new file mode 100644 index 00000000..e2c6d8fa --- /dev/null +++ b/contracts/misc/ChainlinkProxyPriceProvider.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +import "../interfaces/IPriceOracleGetter.sol"; +import "../interfaces/IChainlinkAggregator.sol"; +import "../libraries/EthAddressLib.sol"; + +/// @title ChainlinkProxyPriceProvider +/// @author Aave +/// @notice Proxy smart contract to get the price of an asset from a price source, with Chainlink Aggregator +/// smart contracts as primary option +/// - If the returned price by a Chainlink aggregator is <= 0, the call is forwarded to a fallbackOracle +/// - Owned by the Aave governance system, allowed to add sources for assets, replace them +/// and change the fallbackOracle +contract ChainlinkProxyPriceProvider is IPriceOracleGetter, Ownable { + + event AssetSourceUpdated(address indexed asset, address indexed source); + event FallbackOracleUpdated(address indexed fallbackOracle); + + mapping(address => IChainlinkAggregator) private assetsSources; + IPriceOracleGetter private fallbackOracle; + + /// @notice Constructor + /// @param _assets The addresses of the assets + /// @param _sources The address of the source of each asset + /// @param _fallbackOracle The address of the fallback oracle to use if the data of an + /// aggregator is not consistent + constructor(address[] memory _assets, address[] memory _sources, address _fallbackOracle) public { + internalSetFallbackOracle(_fallbackOracle); + internalSetAssetsSources(_assets, _sources); + } + + /// @notice External function called by the Aave governance to set or replace sources of assets + /// @param _assets The addresses of the assets + /// @param _sources The address of the source of each asset + function setAssetSources(address[] calldata _assets, address[] calldata _sources) external onlyOwner { + internalSetAssetsSources(_assets, _sources); + } + + /// @notice Sets the fallbackOracle + /// - Callable only by the Aave governance + /// @param _fallbackOracle The address of the fallbackOracle + function setFallbackOracle(address _fallbackOracle) external onlyOwner { + internalSetFallbackOracle(_fallbackOracle); + } + + /// @notice Internal function to set the sources for each asset + /// @param _assets The addresses of the assets + /// @param _sources The address of the source of each asset + function internalSetAssetsSources(address[] memory _assets, address[] memory _sources) internal { + require(_assets.length == _sources.length, "INCONSISTENT_PARAMS_LENGTH"); + for (uint256 i = 0; i < _assets.length; i++) { + assetsSources[_assets[i]] = IChainlinkAggregator(_sources[i]); + emit AssetSourceUpdated(_assets[i], _sources[i]); + } + } + + /// @notice Internal function to set the fallbackOracle + /// @param _fallbackOracle The address of the fallbackOracle + function internalSetFallbackOracle(address _fallbackOracle) internal { + fallbackOracle = IPriceOracleGetter(_fallbackOracle); + emit FallbackOracleUpdated(_fallbackOracle); + } + + /// @notice Gets an asset price by address + /// @param _asset The asset address + function getAssetPrice(address _asset) public override view returns(uint256) { + IChainlinkAggregator source = assetsSources[_asset]; + if (_asset == EthAddressLib.ethAddress()) { + return 1 ether; + } else { + // If there is no registered source for the asset, call the fallbackOracle + if (address(source) == address(0)) { + return IPriceOracleGetter(fallbackOracle).getAssetPrice(_asset); + } else { + int256 _price = IChainlinkAggregator(source).latestAnswer(); + if (_price > 0) { + return uint256(_price); + } else { + return IPriceOracleGetter(fallbackOracle).getAssetPrice(_asset); + } + } + } + } + + /// @notice Gets a list of prices from a list of assets addresses + /// @param _assets The list of assets addresses + function getAssetsPrices(address[] calldata _assets) external view returns(uint256[] memory) { + uint256[] memory prices = new uint256[](_assets.length); + for (uint256 i = 0; i < _assets.length; i++) { + prices[i] = getAssetPrice(_assets[i]); + } + return prices; + } + + /// @notice Gets the address of the source for an asset address + /// @param _asset The address of the asset + /// @return address The address of the source + function getSourceOfAsset(address _asset) external view returns(address) { + return address(assetsSources[_asset]); + } + + /// @notice Gets the address of the fallback oracle + /// @return address The addres of the fallback oracle + function getFallbackOracle() external view returns(address) { + return address(fallbackOracle); + } +} \ No newline at end of file diff --git a/contracts/misc/IERC20DetailedBytes.sol b/contracts/misc/IERC20DetailedBytes.sol new file mode 100644 index 00000000..cc22b8d7 --- /dev/null +++ b/contracts/misc/IERC20DetailedBytes.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +contract IERC20DetailedBytes { + bytes32 public name; + bytes32 public symbol; + uint256 public decimals; +} diff --git a/contracts/misc/WalletBalanceProvider.sol b/contracts/misc/WalletBalanceProvider.sol new file mode 100644 index 00000000..36c28bb4 --- /dev/null +++ b/contracts/misc/WalletBalanceProvider.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/utils/Address.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../lendingpool/LendingPoolCore.sol"; +import "../libraries/EthAddressLib.sol"; + + +/** +* @title WalletBalanceProvider contract +* @author Aave, influenced by https://github.com/wbobeirne/eth-balance-checker/blob/master/contracts/BalanceChecker.sol +* @notice Implements a logic of getting multiple tokens balance for one user address +* @dev NOTE: THIS CONTRACT IS NOT USED WITHIN THE AAVE PROTOCOL. It's an accessory contract used to reduce the number of calls +* towards the blockchain from the Aave backend. +**/ +contract WalletBalanceProvider { + + using Address for address; + + LendingPoolAddressesProvider provider; + + constructor(LendingPoolAddressesProvider _provider) public { + + provider = _provider; + + } + /** + @dev Fallback function, don't accept any ETH + **/ + receive() external payable { + revert("WalletBalanceProvider does not accept payments"); + } + + /** + @dev Check the token balance of a wallet in a token contract + + Returns the balance of the token for user. Avoids possible errors: + - return 0 on non-contract address + **/ + function balanceOf(address _user, address _token) public view returns (uint256) { + // check if token is actually a contract + if (_token.isContract()) { + return IERC20(_token).balanceOf(_user); + } else { + return 0; + } + } + + /// @notice Fetches, for a list of _users and _tokens (ETH included with mock address), the balances + /// @param _users The list of users + /// @param _tokens The list of tokens + /// @return And array with the concatenation of, for each user, his/her balances + function batchBalanceOf(address[] memory _users, address[] memory _tokens) public view returns (uint256[] memory) { + uint256[] memory balances = new uint256[](_users.length * _tokens.length); + + for (uint256 i = 0; i < _users.length; i++) { + for (uint256 j = 0; j < _tokens.length; j++) { + uint256 _offset = i * _tokens.length; + if (_tokens[j] == EthAddressLib.ethAddress()) { + balances[_offset + j] = _users[i].balance; // ETH balance + } else { + if (!_tokens[j].isContract()) { + revert("INVALID_TOKEN"); + } else { + balances[_offset + j] = balanceOf(_users[i], _tokens[j]); + } + } + } + } + + return balances; + } + + + /** + @dev provides balances of user wallet for all reserves available on the pool + */ + function getUserWalletBalances(address _user) public view returns (address[] memory, uint256[] memory) { + + LendingPoolCore core = LendingPoolCore(provider.getLendingPoolCore()); + + address[] memory reserves = core.getReserves(); + + uint256[] memory balances = new uint256[](reserves.length); + + for (uint256 j = 0; j < reserves.length; j++) { + if(!core.getReserveIsActive(reserves[j])){ + balances[j] = 0; + continue; + } + if (reserves[j] != EthAddressLib.ethAddress()) { + balances[j] = balanceOf(_user, reserves[j]); + } else { + balances[j] = _user.balance; // ETH balance + } + } + + return (reserves, balances); + } +} \ No newline at end of file diff --git a/contracts/mocks/flashloan/MockFlashLoanReceiver.sol b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol new file mode 100644 index 00000000..688d507f --- /dev/null +++ b/contracts/mocks/flashloan/MockFlashLoanReceiver.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "@openzeppelin/contracts/math/SafeMath.sol"; + +import "../../flashloan/base/FlashLoanReceiverBase.sol"; +import "../tokens/MintableERC20.sol"; + +contract MockFlashLoanReceiver is FlashLoanReceiverBase { + + using SafeMath for uint256; + event ExecutedWithFail(address _reserve, uint256 _amount, uint256 _fee); + event ExecutedWithSuccess(address _reserve, uint256 _amount, uint256 _fee); + + + bool failExecution = false; + + constructor(ILendingPoolAddressesProvider _provider) FlashLoanReceiverBase(_provider) public { + } + + function setFailExecutionTransfer(bool _fail) public { + failExecution = _fail; + } + + function executeOperation( + address _reserve, + uint256 _amount, + uint256 _fee, + bytes memory _params) public override { + //mint to this contract the specific amount + MintableERC20 token = MintableERC20(_reserve); + + + //check the contract has the specified balance + require(_amount <= getBalanceInternal(address(this), _reserve), "Invalid balance for the contract"); + + if(failExecution) { + emit ExecutedWithFail(_reserve, _amount, _fee); + return; + } + + //execution does not fail - mint tokens and return them to the _destination + //note: if the reserve is eth, the mock contract must receive at least _fee ETH before calling executeOperation + + if(_reserve != EthAddressLib.ethAddress()) { + token.mint(_fee); + } + //returning amount + fee to the destination + transferFundsBackToPoolInternal(_reserve, _amount.add(_fee)); + emit ExecutedWithSuccess(_reserve, _amount, _fee); + } +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorBAT.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorBAT.sol new file mode 100644 index 00000000..9fb2722d --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorBAT.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorBAT is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorBUSD.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorBUSD.sol new file mode 100644 index 00000000..cfdfc6db --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorBUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorBUSD is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorBase.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorBase.sol new file mode 100644 index 00000000..d0c86d64 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorBase.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +contract MockAggregatorBase { + int256 private _latestAnswer; + + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp); + + constructor (int256 _initialAnswer) public { + _latestAnswer = _initialAnswer; + emit AnswerUpdated(_initialAnswer, 0, now); + } + + function latestAnswer() external view returns (int256) { + return _latestAnswer; + } +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorDAI.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorDAI.sol new file mode 100644 index 00000000..f808e4c5 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorDAI.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorDAI is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorKNC.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorKNC.sol new file mode 100644 index 00000000..4eae34df --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorKNC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorKNC is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorLEND.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorLEND.sol new file mode 100644 index 00000000..66a27d06 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorLEND.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorLEND is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorLINK.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorLINK.sol new file mode 100644 index 00000000..eb16f529 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorLINK.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorLINK is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorMANA.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorMANA.sol new file mode 100644 index 00000000..c6b6efbf --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorMANA.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorMANA is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorMKR.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorMKR.sol new file mode 100644 index 00000000..c73c5099 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorMKR.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorMKR is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorREP.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorREP.sol new file mode 100644 index 00000000..a0bcb6ec --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorREP.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorREP is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorSNX.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorSNX.sol new file mode 100644 index 00000000..8e0d3cde --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorSNX.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorSNX is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorSUSD.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorSUSD.sol new file mode 100644 index 00000000..f52cd3f6 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorSUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorSUSD is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorTUSD.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorTUSD.sol new file mode 100644 index 00000000..5f96af10 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorTUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorTUSD is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_DAI_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_DAI_ETH.sol new file mode 100644 index 00000000..e9526a39 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_DAI_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_DAI_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LEND_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LEND_ETH.sol new file mode 100644 index 00000000..33515c00 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LEND_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_LEND_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LINK_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LINK_ETH.sol new file mode 100644 index 00000000..9115914e --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_LINK_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_LINK_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_MKR_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_MKR_ETH.sol new file mode 100644 index 00000000..821bf83a --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_MKR_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_MKR_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_SETH_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_SETH_ETH.sol new file mode 100644 index 00000000..815c928f --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_SETH_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_SETH_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_USDC_ETH.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_USDC_ETH.sol new file mode 100644 index 00000000..9225d1de --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUNI_USDC_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUNI_USDC_ETH is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUSD.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSD.sol new file mode 100644 index 00000000..83745952 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUSD is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDC.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDC.sol new file mode 100644 index 00000000..2ac9d729 --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUSDC is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDT.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDT.sol new file mode 100644 index 00000000..8f9889df --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorUSDT.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorUSDT is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorWBTC.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorWBTC.sol new file mode 100644 index 00000000..05535c8d --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorWBTC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorWBTC is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/CLAggregators/MockAggregatorZRX.sol b/contracts/mocks/oracle/CLAggregators/MockAggregatorZRX.sol new file mode 100644 index 00000000..5831bd7e --- /dev/null +++ b/contracts/mocks/oracle/CLAggregators/MockAggregatorZRX.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./MockAggregatorBase.sol"; + +contract MockAggregatorZRX is MockAggregatorBase { + constructor (int256 _initialAnswer) public MockAggregatorBase(_initialAnswer) {} +} \ No newline at end of file diff --git a/contracts/mocks/oracle/ChainlinkUSDETHOracleI.sol b/contracts/mocks/oracle/ChainlinkUSDETHOracleI.sol new file mode 100644 index 00000000..71ef7f99 --- /dev/null +++ b/contracts/mocks/oracle/ChainlinkUSDETHOracleI.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface ChainlinkUSDETHOracleI { + event AnswerUpdated(int256 indexed current, uint256 indexed answerId); +} + diff --git a/contracts/mocks/oracle/GenericOracleI.sol b/contracts/mocks/oracle/GenericOracleI.sol new file mode 100644 index 00000000..ffb28e0e --- /dev/null +++ b/contracts/mocks/oracle/GenericOracleI.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface GenericOracleI { + // ganache + event AssetPriceUpdated(address _asset, uint256 _price, uint256 timestamp); + event EthPriceUpdated(uint256 _price, uint256 timestamp); + + // kovan + event ProphecySubmitted( + address indexed _sybil, + address indexed _asset, + uint96 _sybilProphecy, + uint96 _oracleProphecy + ); + + function getAssetPrice(address _asset) external view returns(uint256); + function getEthUsdPrice() external view returns(uint256); +} + diff --git a/contracts/mocks/oracle/IExtendedPriceAggregator.sol b/contracts/mocks/oracle/IExtendedPriceAggregator.sol new file mode 100644 index 00000000..2222f134 --- /dev/null +++ b/contracts/mocks/oracle/IExtendedPriceAggregator.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +interface IExtendedPriceAggregator { + event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 timestamp); + + function getToken() external view returns (address); + function getTokenType() external view returns (uint256); + function getPlatformId() external view returns (uint256); + function getSubTokens() external view returns(address[] memory); + function latestAnswer() external view returns (int256); +} \ No newline at end of file diff --git a/contracts/mocks/oracle/LendingRateOracle.sol b/contracts/mocks/oracle/LendingRateOracle.sol new file mode 100644 index 00000000..f2de3276 --- /dev/null +++ b/contracts/mocks/oracle/LendingRateOracle.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../../interfaces/ILendingRateOracle.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract LendingRateOracle is ILendingRateOracle, Ownable { + + mapping(address => uint256) borrowRates; + mapping(address => uint256) liquidityRates; + + + function getMarketBorrowRate(address _asset) external override view returns(uint256) { + return borrowRates[_asset]; + } + + function setMarketBorrowRate(address _asset, uint256 _rate) external override onlyOwner { + borrowRates[_asset] = _rate; + } + + function getMarketLiquidityRate(address _asset) external view returns(uint256) { + return liquidityRates[_asset]; + } + + function setMarketLiquidityRate(address _asset, uint256 _rate) external onlyOwner { + liquidityRates[_asset] = _rate; + } +} \ No newline at end of file diff --git a/contracts/mocks/oracle/PriceOracle.sol b/contracts/mocks/oracle/PriceOracle.sol new file mode 100644 index 00000000..fc707678 --- /dev/null +++ b/contracts/mocks/oracle/PriceOracle.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../../interfaces/IPriceOracle.sol"; + + +contract PriceOracle is IPriceOracle { + + mapping(address => uint256) prices; + uint256 ethPriceUsd; + + event AssetPriceUpdated(address _asset, uint256 _price, uint256 timestamp); + event EthPriceUpdated(uint256 _price, uint256 timestamp); + + function getAssetPrice(address _asset) external override view returns(uint256) { + return prices[_asset]; + } + + function setAssetPrice(address _asset, uint256 _price) external override { + prices[_asset] = _price; + emit AssetPriceUpdated(_asset, _price, block.timestamp); + } + + function getEthUsdPrice() external view returns(uint256) { + return ethPriceUsd; + } + + function setEthUsdPrice(uint256 _price) external { + ethPriceUsd = _price; + emit EthPriceUpdated(_price, block.timestamp); + } +} \ No newline at end of file diff --git a/contracts/mocks/tokens/MintableERC20.sol b/contracts/mocks/tokens/MintableERC20.sol new file mode 100644 index 00000000..44c35014 --- /dev/null +++ b/contracts/mocks/tokens/MintableERC20.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../../tokenization/ERC20.sol"; + +/** + * @title ERC20Mintable + * @dev ERC20 minting logic + */ +contract MintableERC20 is ERC20 { + constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol) public { + _setupDecimals(decimals); + } + /** + * @dev Function to mint tokens + * @param value The amount of tokens to mint. + * @return A boolean that indicates if the operation was successful. + */ + function mint(uint256 value) public returns (bool) { + _mint(msg.sender, value); + return true; + } +} diff --git a/contracts/mocks/tokens/MockBAT.sol b/contracts/mocks/tokens/MockBAT.sol new file mode 100644 index 00000000..65121471 --- /dev/null +++ b/contracts/mocks/tokens/MockBAT.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + + +contract MockBAT is MintableERC20("BAT", "Basic Attention Token", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockBUSD.sol b/contracts/mocks/tokens/MockBUSD.sol new file mode 100644 index 00000000..61fb8650 --- /dev/null +++ b/contracts/mocks/tokens/MockBUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockBUSD is MintableERC20("BUSD", "Binance USD", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockDAI.sol b/contracts/mocks/tokens/MockDAI.sol new file mode 100644 index 00000000..bbbc0a5e --- /dev/null +++ b/contracts/mocks/tokens/MockDAI.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + + +contract MockDAI is MintableERC20("DAI", "DAI", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockKNC.sol b/contracts/mocks/tokens/MockKNC.sol new file mode 100644 index 00000000..c06f3dbe --- /dev/null +++ b/contracts/mocks/tokens/MockKNC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockKNC is MintableERC20("KNC", "Kyber Network", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockLEND.sol b/contracts/mocks/tokens/MockLEND.sol new file mode 100644 index 00000000..97e8c683 --- /dev/null +++ b/contracts/mocks/tokens/MockLEND.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + + +contract MockLEND is MintableERC20("LEND", "LEND", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockLINK.sol b/contracts/mocks/tokens/MockLINK.sol new file mode 100644 index 00000000..3904807c --- /dev/null +++ b/contracts/mocks/tokens/MockLINK.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockLINK is MintableERC20("LINK", "ChainLink", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockMANA.sol b/contracts/mocks/tokens/MockMANA.sol new file mode 100644 index 00000000..e76cfdea --- /dev/null +++ b/contracts/mocks/tokens/MockMANA.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockMANA is MintableERC20("MANA", "Decentraland", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockMKR.sol b/contracts/mocks/tokens/MockMKR.sol new file mode 100644 index 00000000..bacb4e10 --- /dev/null +++ b/contracts/mocks/tokens/MockMKR.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockMKR is MintableERC20("MKR", "Maker", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockREP.sol b/contracts/mocks/tokens/MockREP.sol new file mode 100644 index 00000000..78550e04 --- /dev/null +++ b/contracts/mocks/tokens/MockREP.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockREP is MintableERC20("REP", "Augur", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockSNX.sol b/contracts/mocks/tokens/MockSNX.sol new file mode 100644 index 00000000..f1b554f1 --- /dev/null +++ b/contracts/mocks/tokens/MockSNX.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockSNX is MintableERC20("SNX", "SNX", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockSUSD.sol b/contracts/mocks/tokens/MockSUSD.sol new file mode 100644 index 00000000..c273d723 --- /dev/null +++ b/contracts/mocks/tokens/MockSUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockSUSD is MintableERC20("SUSD", "Synthetix USD", 6) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockTUSD.sol b/contracts/mocks/tokens/MockTUSD.sol new file mode 100644 index 00000000..d2368fe7 --- /dev/null +++ b/contracts/mocks/tokens/MockTUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockTUSD is MintableERC20("TUSD", "TrueUSD", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_DAI_ETH.sol b/contracts/mocks/tokens/MockUNI_DAI_ETH.sol new file mode 100644 index 00000000..fdf56713 --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_DAI_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_DAI_ETH is MintableERC20("UNI_DAI_ETH", "UNI_DAI_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_LEND_ETH.sol b/contracts/mocks/tokens/MockUNI_LEND_ETH.sol new file mode 100644 index 00000000..9eda60d6 --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_LEND_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_LEND_ETH is MintableERC20("UNI_LEND_ETH", "UNI_LEND_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_LINK_ETH.sol b/contracts/mocks/tokens/MockUNI_LINK_ETH.sol new file mode 100644 index 00000000..08cd3ffc --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_LINK_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_LINK_ETH is MintableERC20("UNI_LINK_ETH", "UNI_LINK_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_MKR_ETH.sol b/contracts/mocks/tokens/MockUNI_MKR_ETH.sol new file mode 100644 index 00000000..e32c4da6 --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_MKR_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_MKR_ETH is MintableERC20("UNI_MKR_ETH", "UNI_MKR_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_SETH_ETH.sol b/contracts/mocks/tokens/MockUNI_SETH_ETH.sol new file mode 100644 index 00000000..dd9d3bd5 --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_SETH_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_SETH_ETH is MintableERC20("UNI_SETH_ETH", "UNI_SETH_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUNI_USDC_ETH.sol b/contracts/mocks/tokens/MockUNI_USDC_ETH.sol new file mode 100644 index 00000000..a563d16e --- /dev/null +++ b/contracts/mocks/tokens/MockUNI_USDC_ETH.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUNI_USDC_ETH is MintableERC20("UNI_USDC_ETH", "UNI_USDC_ETH", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUSD.sol b/contracts/mocks/tokens/MockUSD.sol new file mode 100644 index 00000000..7fe5407e --- /dev/null +++ b/contracts/mocks/tokens/MockUSD.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUSD is MintableERC20("USD", "USD", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUSDC.sol b/contracts/mocks/tokens/MockUSDC.sol new file mode 100644 index 00000000..58a487f8 --- /dev/null +++ b/contracts/mocks/tokens/MockUSDC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUSDC is MintableERC20("USDC", "USD Coin", 6) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockUSDT.sol b/contracts/mocks/tokens/MockUSDT.sol new file mode 100644 index 00000000..6cbb8685 --- /dev/null +++ b/contracts/mocks/tokens/MockUSDT.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockUSDT is MintableERC20("USDT", "USDT Coin", 6) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockWBTC.sol b/contracts/mocks/tokens/MockWBTC.sol new file mode 100644 index 00000000..d5a7b4ee --- /dev/null +++ b/contracts/mocks/tokens/MockWBTC.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockWBTC is MintableERC20("WBTC", "WBTC Coin", 18) {} \ No newline at end of file diff --git a/contracts/mocks/tokens/MockZRX.sol b/contracts/mocks/tokens/MockZRX.sol new file mode 100644 index 00000000..13098d7c --- /dev/null +++ b/contracts/mocks/tokens/MockZRX.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + + +import "./MintableERC20.sol"; + + +contract MockZRX is MintableERC20("ZRX", "0x Coin", 18) {} \ No newline at end of file diff --git a/contracts/mocks/upgradeability/MockLendingPoolCore.sol b/contracts/mocks/upgradeability/MockLendingPoolCore.sol new file mode 100644 index 00000000..9555184b --- /dev/null +++ b/contracts/mocks/upgradeability/MockLendingPoolCore.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "../../configuration/LendingPoolAddressesProvider.sol"; +import "../../lendingpool/LendingPoolCore.sol"; + +/************************************************************************************* +* @title MockLendingPoolCore contract +* @author Aave +* @notice This is a mock contract to test upgradeability of the AddressProvider + *************************************************************************************/ + +contract MockLendingPoolCore is LendingPoolCore { + + event ReserveUpdatedFromMock(uint256 indexed revision); + + uint256 constant private CORE_REVISION = 0x8; + + function getRevision() internal override pure returns(uint256) { + return CORE_REVISION; + } + + function initialize(LendingPoolAddressesProvider _addressesProvider) public override initializer { + addressesProvider = _addressesProvider; + refreshConfigInternal(); + } + + function updateReserveInterestRatesAndTimestampInternal(address _reserve, uint256 _liquidityAdded, uint256 _liquidityTaken) + internal override + { + super.updateReserveInterestRatesAndTimestampInternal(_reserve, _liquidityAdded, _liquidityTaken); + + emit ReserveUpdatedFromMock(getRevision()); + + } +} diff --git a/contracts/tokenization/AToken.sol b/contracts/tokenization/AToken.sol new file mode 100644 index 00000000..dad35478 --- /dev/null +++ b/contracts/tokenization/AToken.sol @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.6.8; + +import "./ERC20.sol"; + +import "../configuration/LendingPoolAddressesProvider.sol"; +import "../lendingpool/LendingPool.sol"; +import "../lendingpool/LendingPoolDataProvider.sol"; +import "../lendingpool/LendingPoolCore.sol"; +import "../libraries/WadRayMath.sol"; + +/** + * @title Aave ERC20 AToken + * + * @dev Implementation of the interest bearing token for the DLP protocol. + * @author Aave + */ +contract AToken is ERC20 { + using WadRayMath for uint256; + + uint256 public constant UINT_MAX_VALUE = uint256(-1); + + /** + * @dev emitted after the redeem action + * @param _from the address performing the redeem + * @param _value the amount to be redeemed + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _fromIndex the last index of the user + **/ + event Redeem( + address indexed _from, + uint256 _value, + uint256 _fromBalanceIncrease, + uint256 _fromIndex + ); + + /** + * @dev emitted after the mint action + * @param _from the address performing the mint + * @param _value the amount to be minted + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _fromIndex the last index of the user + **/ + event MintOnDeposit( + address indexed _from, + uint256 _value, + uint256 _fromBalanceIncrease, + uint256 _fromIndex + ); + + /** + * @dev emitted during the liquidation action, when the liquidator reclaims the underlying + * asset + * @param _from the address from which the tokens are being burned + * @param _value the amount to be burned + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _fromIndex the last index of the user + **/ + event BurnOnLiquidation( + address indexed _from, + uint256 _value, + uint256 _fromBalanceIncrease, + uint256 _fromIndex + ); + + /** + * @dev emitted during the transfer action + * @param _from the address from which the tokens are being transferred + * @param _to the adress of the destination + * @param _value the amount to be minted + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _toBalanceIncrease the cumulated balance since the last update of the destination + * @param _fromIndex the last index of the user + * @param _toIndex the last index of the liquidator + **/ + event BalanceTransfer( + address indexed _from, + address indexed _to, + uint256 _value, + uint256 _fromBalanceIncrease, + uint256 _toBalanceIncrease, + uint256 _fromIndex, + uint256 _toIndex + ); + + /** + * @dev emitted when the accumulation of the interest + * by an user is redirected to another user + * @param _from the address from which the interest is being redirected + * @param _to the adress of the destination + * @param _fromBalanceIncrease the cumulated balance since the last update of the user + * @param _fromIndex the last index of the user + **/ + event InterestStreamRedirected( + address indexed _from, + address indexed _to, + uint256 _redirectedBalance, + uint256 _fromBalanceIncrease, + uint256 _fromIndex + ); + + /** + * @dev emitted when the redirected balance of an user is being updated + * @param _targetAddress the address of which the balance is being updated + * @param _targetBalanceIncrease the cumulated balance since the last update of the target + * @param _targetIndex the last index of the user + * @param _redirectedBalanceAdded the redirected balance being added + * @param _redirectedBalanceRemoved the redirected balance being removed + **/ + event RedirectedBalanceUpdated( + address indexed _targetAddress, + uint256 _targetBalanceIncrease, + uint256 _targetIndex, + uint256 _redirectedBalanceAdded, + uint256 _redirectedBalanceRemoved + ); + + event InterestRedirectionAllowanceChanged( + address indexed _from, + address indexed _to + ); + + address public underlyingAssetAddress; + + mapping (address => uint256) private userIndexes; + mapping (address => address) private interestRedirectionAddresses; + mapping (address => uint256) private redirectedBalances; + mapping (address => address) private interestRedirectionAllowances; + + LendingPoolAddressesProvider private addressesProvider; + LendingPoolCore private core; + LendingPool private pool; + LendingPoolDataProvider private dataProvider; + + modifier onlyLendingPool { + require( + msg.sender == address(pool), + "The caller of this function must be a lending pool" + ); + _; + } + + modifier whenTransferAllowed(address _from, uint256 _amount) { + require(isTransferAllowed(_from, _amount), "Transfer cannot be allowed."); + _; + } + + constructor( + LendingPoolAddressesProvider _addressesProvider, + address _underlyingAsset, + uint8 _underlyingAssetDecimals, + string memory _name, + string memory _symbol + ) public ERC20(_name, _symbol) { + _setupDecimals(_underlyingAssetDecimals); + addressesProvider = _addressesProvider; + core = LendingPoolCore(addressesProvider.getLendingPoolCore()); + pool = LendingPool(addressesProvider.getLendingPool()); + dataProvider = LendingPoolDataProvider(addressesProvider.getLendingPoolDataProvider()); + underlyingAssetAddress = _underlyingAsset; + } + + /** + * @notice ERC20 implementation internal function backing transfer() and transferFrom() + * @dev validates the transfer before allowing it. NOTE: This is not standard ERC20 behavior + **/ + function _transfer(address _from, address _to, uint256 _amount) internal override whenTransferAllowed(_from, _amount) { + + executeTransferInternal(_from, _to, _amount); + } + + + /** + * @dev redirects the interest generated to a target address. + * when the interest is redirected, the user balance is added to + * the recepient redirected balance. + * @param _to the address to which the interest will be redirected + **/ + function redirectInterestStream(address _to) external { + redirectInterestStreamInternal(msg.sender, _to); + } + + /** + * @dev redirects the interest generated by _from to a target address. + * when the interest is redirected, the user balance is added to + * the recepient redirected balance. The caller needs to have allowance on + * the interest redirection to be able to execute the function. + * @param _from the address of the user whom interest is being redirected + * @param _to the address to which the interest will be redirected + **/ + function redirectInterestStreamOf(address _from, address _to) external { + require( + msg.sender == interestRedirectionAllowances[_from], + "Caller is not allowed to redirect the interest of the user" + ); + redirectInterestStreamInternal(_from,_to); + } + + /** + * @dev gives allowance to an address to execute the interest redirection + * on behalf of the caller. + * @param _to the address to which the interest will be redirected. Pass address(0) to reset + * the allowance. + **/ + function allowInterestRedirectionTo(address _to) external { + require(_to != msg.sender, "User cannot give allowance to himself"); + interestRedirectionAllowances[msg.sender] = _to; + emit InterestRedirectionAllowanceChanged( + msg.sender, + _to + ); + } + + /** + * @dev redeems aToken for the underlying asset + * @param _amount the amount being redeemed + **/ + function redeem(uint256 _amount) external { + + require(_amount > 0, "Amount to redeem needs to be > 0"); + + //cumulates the balance of the user + (, + uint256 currentBalance, + uint256 balanceIncrease, + uint256 index) = cumulateBalanceInternal(msg.sender); + + uint256 amountToRedeem = _amount; + + //if amount is equal to uint(-1), the user wants to redeem everything + if(_amount == UINT_MAX_VALUE){ + amountToRedeem = currentBalance; + } + + require(amountToRedeem <= currentBalance, "User cannot redeem more than the available balance"); + + //check that the user is allowed to redeem the amount + require(isTransferAllowed(msg.sender, amountToRedeem), "Transfer cannot be allowed."); + + //if the user is redirecting his interest towards someone else, + //we update the redirected balance of the redirection address by adding the accrued interest, + //and removing the amount to redeem + updateRedirectedBalanceOfRedirectionAddressInternal(msg.sender, balanceIncrease, amountToRedeem); + + // burns tokens equivalent to the amount requested + _burn(msg.sender, amountToRedeem); + + bool userIndexReset = false; + //reset the user data if the remaining balance is 0 + if(currentBalance.sub(amountToRedeem) == 0){ + userIndexReset = resetDataOnZeroBalanceInternal(msg.sender); + } + + // executes redeem of the underlying asset + pool.redeemUnderlying( + underlyingAssetAddress, + msg.sender, + amountToRedeem, + currentBalance.sub(amountToRedeem) + ); + + emit Redeem(msg.sender, amountToRedeem, balanceIncrease, userIndexReset ? 0 : index); + } + + /** + * @dev mints token in the event of users depositing the underlying asset into the lending pool + * only lending pools can call this function + * @param _account the address receiving the minted tokens + * @param _amount the amount of tokens to mint + */ + function mintOnDeposit(address _account, uint256 _amount) external onlyLendingPool { + + //cumulates the balance of the user + (, + , + uint256 balanceIncrease, + uint256 index) = cumulateBalanceInternal(_account); + + //if the user is redirecting his interest towards someone else, + //we update the redirected balance of the redirection address by adding the accrued interest + //and the amount deposited + updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease.add(_amount), 0); + + //mint an equivalent amount of tokens to cover the new deposit + _mint(_account, _amount); + + emit MintOnDeposit(_account, _amount, balanceIncrease, index); + } + + /** + * @dev burns token in the event of a borrow being liquidated, in case the liquidators reclaims the underlying asset + * Transfer of the liquidated asset is executed by the lending pool contract. + * only lending pools can call this function + * @param _account the address from which burn the aTokens + * @param _value the amount to burn + **/ + function burnOnLiquidation(address _account, uint256 _value) external onlyLendingPool { + + //cumulates the balance of the user being liquidated + (,uint256 accountBalance,uint256 balanceIncrease,uint256 index) = cumulateBalanceInternal(_account); + + //adds the accrued interest and substracts the burned amount to + //the redirected balance + updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease, _value); + + //burns the requested amount of tokens + _burn(_account, _value); + + bool userIndexReset = false; + //reset the user data if the remaining balance is 0 + if(accountBalance.sub(_value) == 0){ + userIndexReset = resetDataOnZeroBalanceInternal(_account); + } + + emit BurnOnLiquidation(_account, _value, balanceIncrease, userIndexReset ? 0 : index); + } + + /** + * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken + * only lending pools can call this function + * @param _from the address from which transfer the aTokens + * @param _to the destination address + * @param _value the amount to transfer + **/ + function transferOnLiquidation(address _from, address _to, uint256 _value) external onlyLendingPool { + + //being a normal transfer, the Transfer() and BalanceTransfer() are emitted + //so no need to emit a specific event here + executeTransferInternal(_from, _to, _value); + } + + /** + * @dev calculates the balance of the user, which is the + * principal balance + interest generated by the principal balance + interest generated by the redirected balance + * @param _user the user for which the balance is being calculated + * @return the total balance of the user + **/ + function balanceOf(address _user) public override view returns(uint256) { + + //current principal balance of the user + uint256 currentPrincipalBalance = super.balanceOf(_user); + //balance redirected by other users to _user for interest rate accrual + uint256 redirectedBalance = redirectedBalances[_user]; + + if(currentPrincipalBalance == 0 && redirectedBalance == 0){ + return 0; + } + //if the _user is not redirecting the interest to anybody, accrues + //the interest for himself + + if(interestRedirectionAddresses[_user] == address(0)){ + + //accruing for himself means that both the principal balance and + //the redirected balance partecipate in the interest + return calculateCumulatedBalanceInternal( + _user, + currentPrincipalBalance.add(redirectedBalance) + ) + .sub(redirectedBalance); + } + else { + //if the user redirected the interest, then only the redirected + //balance generates interest. In that case, the interest generated + //by the redirected balance is added to the current principal balance. + return currentPrincipalBalance.add( + calculateCumulatedBalanceInternal( + _user, + redirectedBalance + ) + .sub(redirectedBalance) + ); + } + } + + /** + * @dev returns the principal balance of the user. The principal balance is the last + * updated stored balance, which does not consider the perpetually accruing interest. + * @param _user the address of the user + * @return the principal balance of the user + **/ + function principalBalanceOf(address _user) external view returns(uint256) { + return super.balanceOf(_user); + } + + + /** + * @dev calculates the total supply of the specific aToken + * since the balance of every single user increases over time, the total supply + * does that too. + * @return the current total supply + **/ + function totalSupply() public override view returns(uint256) { + + uint256 currentSupplyPrincipal = super.totalSupply(); + + if(currentSupplyPrincipal == 0){ + return 0; + } + + return currentSupplyPrincipal + .wadToRay() + .rayMul(core.getReserveNormalizedIncome(underlyingAssetAddress)) + .rayToWad(); + } + + + /** + * @dev Used to validate transfers before actually executing them. + * @param _user address of the user to check + * @param _amount the amount to check + * @return true if the _user can transfer _amount, false otherwise + **/ + function isTransferAllowed(address _user, uint256 _amount) public view returns (bool) { + return dataProvider.balanceDecreaseAllowed(underlyingAssetAddress, _user, _amount); + } + + /** + * @dev returns the last index of the user, used to calculate the balance of the user + * @param _user address of the user + * @return the last user index + **/ + function getUserIndex(address _user) external view returns(uint256) { + return userIndexes[_user]; + } + + + /** + * @dev returns the address to which the interest is redirected + * @param _user address of the user + * @return 0 if there is no redirection, an address otherwise + **/ + function getInterestRedirectionAddress(address _user) external view returns(address) { + return interestRedirectionAddresses[_user]; + } + + /** + * @dev returns the redirected balance of the user. The redirected balance is the balance + * redirected by other accounts to the user, that is accrueing interest for him. + * @param _user address of the user + * @return the total redirected balance + **/ + function getRedirectedBalance(address _user) external view returns(uint256) { + return redirectedBalances[_user]; + } + + /** + * @dev accumulates the accrued interest of the user to the principal balance + * @param _user the address of the user for which the interest is being accumulated + * @return the previous principal balance, the new principal balance, the balance increase + * and the new user index + **/ + function cumulateBalanceInternal(address _user) + internal + returns(uint256, uint256, uint256, uint256) { + + uint256 previousPrincipalBalance = super.balanceOf(_user); + + //calculate the accrued interest since the last accumulation + uint256 balanceIncrease = balanceOf(_user).sub(previousPrincipalBalance); + //mints an amount of tokens equivalent to the amount accumulated + _mint(_user, balanceIncrease); + //updates the user index + uint256 index = userIndexes[_user] = core.getReserveNormalizedIncome(underlyingAssetAddress); + return ( + previousPrincipalBalance, + previousPrincipalBalance.add(balanceIncrease), + balanceIncrease, + index + ); + } + + /** + * @dev updates the redirected balance of the user. If the user is not redirecting his + * interest, nothing is executed. + * @param _user the address of the user for which the interest is being accumulated + * @param _balanceToAdd the amount to add to the redirected balance + * @param _balanceToRemove the amount to remove from the redirected balance + **/ + function updateRedirectedBalanceOfRedirectionAddressInternal( + address _user, + uint256 _balanceToAdd, + uint256 _balanceToRemove + ) internal { + + address redirectionAddress = interestRedirectionAddresses[_user]; + //if there isn't any redirection, nothing to be done + if(redirectionAddress == address(0)){ + return; + } + + //compound balances of the redirected address + (,,uint256 balanceIncrease, uint256 index) = cumulateBalanceInternal(redirectionAddress); + + //updating the redirected balance + redirectedBalances[redirectionAddress] = redirectedBalances[redirectionAddress] + .add(_balanceToAdd) + .sub(_balanceToRemove); + + //if the interest of redirectionAddress is also being redirected, we need to update + //the redirected balance of the redirection target by adding the balance increase + address targetOfRedirectionAddress = interestRedirectionAddresses[redirectionAddress]; + + if(targetOfRedirectionAddress != address(0)){ + redirectedBalances[targetOfRedirectionAddress] = redirectedBalances[targetOfRedirectionAddress].add(balanceIncrease); + } + + emit RedirectedBalanceUpdated( + redirectionAddress, + balanceIncrease, + index, + _balanceToAdd, + _balanceToRemove + ); + } + + /** + * @dev calculate the interest accrued by _user on a specific balance + * @param _user the address of the user for which the interest is being accumulated + * @param _balance the balance on which the interest is calculated + * @return the interest rate accrued + **/ + function calculateCumulatedBalanceInternal( + address _user, + uint256 _balance + ) internal view returns (uint256) { + return _balance + .wadToRay() + .rayMul(core.getReserveNormalizedIncome(underlyingAssetAddress)) + .rayDiv(userIndexes[_user]) + .rayToWad(); + } + + /** + * @dev executes the transfer of aTokens, invoked by both _transfer() and + * transferOnLiquidation() + * @param _from the address from which transfer the aTokens + * @param _to the destination address + * @param _value the amount to transfer + **/ + function executeTransferInternal( + address _from, + address _to, + uint256 _value + ) internal { + + require(_value > 0, "Transferred amount needs to be greater than zero"); + + //cumulate the balance of the sender + (, + uint256 fromBalance, + uint256 fromBalanceIncrease, + uint256 fromIndex + ) = cumulateBalanceInternal(_from); + + //cumulate the balance of the receiver + (, + , + uint256 toBalanceIncrease, + uint256 toIndex + ) = cumulateBalanceInternal(_to); + + //if the sender is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and removes the amount + //being transferred + updateRedirectedBalanceOfRedirectionAddressInternal(_from, fromBalanceIncrease, _value); + + //if the receiver is redirecting his interest towards someone else, + //adds to the redirected balance the accrued interest and the amount + //being transferred + updateRedirectedBalanceOfRedirectionAddressInternal(_to, toBalanceIncrease.add(_value), 0); + + //performs the transfer + super._transfer(_from, _to, _value); + + bool fromIndexReset = false; + //reset the user data if the remaining balance is 0 + if(fromBalance.sub(_value) == 0){ + fromIndexReset = resetDataOnZeroBalanceInternal(_from); + } + + emit BalanceTransfer( + _from, + _to, + _value, + fromBalanceIncrease, + toBalanceIncrease, + fromIndexReset ? 0 : fromIndex, + toIndex + ); + } + + /** + * @dev executes the redirection of the interest from one address to another. + * immediately after redirection, the destination address will start to accrue interest. + * @param _from the address from which transfer the aTokens + * @param _to the destination address + **/ + function redirectInterestStreamInternal( + address _from, + address _to + ) internal { + + address currentRedirectionAddress = interestRedirectionAddresses[_from]; + + require(_to != currentRedirectionAddress, "Interest is already redirected to the user"); + + //accumulates the accrued interest to the principal + (uint256 previousPrincipalBalance, + uint256 fromBalance, + uint256 balanceIncrease, + uint256 fromIndex) = cumulateBalanceInternal(_from); + + require(fromBalance > 0, "Interest stream can only be redirected if there is a valid balance"); + + //if the user is already redirecting the interest to someone, before changing + //the redirection address we substract the redirected balance of the previous + //recipient + if(currentRedirectionAddress != address(0)){ + updateRedirectedBalanceOfRedirectionAddressInternal(_from,0, previousPrincipalBalance); + } + + //if the user is redirecting the interest back to himself, + //we simply set to 0 the interest redirection address + if(_to == _from) { + interestRedirectionAddresses[_from] = address(0); + emit InterestStreamRedirected( + _from, + address(0), + fromBalance, + balanceIncrease, + fromIndex + ); + return; + } + + //first set the redirection address to the new recipient + interestRedirectionAddresses[_from] = _to; + + //adds the user balance to the redirected balance of the destination + updateRedirectedBalanceOfRedirectionAddressInternal(_from,fromBalance,0); + + emit InterestStreamRedirected( + _from, + _to, + fromBalance, + balanceIncrease, + fromIndex + ); + } + + /** + * @dev function to reset the interest stream redirection and the user index, if the + * user has no balance left. + * @param _user the address of the user + * @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value + **/ + function resetDataOnZeroBalanceInternal(address _user) internal returns(bool) { + + //if the user has 0 principal balance, the interest stream redirection gets reset + interestRedirectionAddresses[_user] = address(0); + + //emits a InterestStreamRedirected event to notify that the redirection has been reset + emit InterestStreamRedirected(_user, address(0),0,0,0); + + //if the redirected balance is also 0, we clear up the user index + if(redirectedBalances[_user] == 0){ + userIndexes[_user] = 0; + return true; + } + else{ + return false; + } + } +} diff --git a/contracts/tokenization/ERC20.sol b/contracts/tokenization/ERC20.sol new file mode 100644 index 00000000..a9d09dac --- /dev/null +++ b/contracts/tokenization/ERC20.sol @@ -0,0 +1,305 @@ +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/GSN/Context.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20MinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin guidelines: functions revert instead + * of returning `false` on failure. This behavior is nonetheless conventional + * and does not conflict with the expectations of ERC20 applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +contract ERC20 is Context, IERC20 { + using SafeMath for uint256; + using Address for address; + + mapping (address => uint256) private _balances; + + mapping (address => mapping (address => uint256)) private _allowances; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + /** + * @dev Sets the values for {name} and {symbol}, initializes {decimals} with + * a default value of 18. + * + * To select a different value for {decimals}, use {_setupDecimals}. + * + * All three of these values are immutable: they can only be set once during + * construction. + */ + constructor (string memory name, string memory symbol) public { + _name = name; + _symbol = symbol; + _decimals = 18; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5,05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is + * called. + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account]; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(_msgSender(), recipient, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) public view virtual override returns (uint256) { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + _approve(_msgSender(), spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}; + * + * Requirements: + * - `sender` and `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + * - the caller must have allowance for ``sender``'s tokens of at least + * `amount`. + */ + function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) { + _transfer(sender, recipient, amount); + _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue)); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) { + _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero")); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer(address sender, address recipient, uint256 amount) internal virtual { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _beforeTokenTransfer(sender, recipient, amount); + + _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance"); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a {Transfer} event with `from` set to the zero address. + * + * Requirements + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: mint to the zero address"); + + _beforeTokenTransfer(address(0), account, amount); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a {Transfer} event with `to` set to the zero address. + * + * Requirements + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 amount) internal virtual { + require(account != address(0), "ERC20: burn from the zero address"); + + _beforeTokenTransfer(account, address(0), amount); + + _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); + _totalSupply = _totalSupply.sub(amount); + emit Transfer(account, address(0), amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens. + * + * This is internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve(address owner, address spender, uint256 amount) internal virtual { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Sets {decimals} to a value other than the default one of 18. + * + * WARNING: This function should only be called from the constructor. Most + * applications that interact with token contracts will not expect + * {decimals} to ever change, and may work incorrectly if it does. + */ + function _setupDecimals(uint8 decimals_) internal { + _decimals = decimals_; + } + + /** + * @dev Hook that is called before any transfer of tokens. This includes + * minting and burning. + * + * Calling conditions: + * + * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens + * will be to transferred to `to`. + * - when `from` is zero, `amount` tokens will be minted for `to`. + * - when `to` is zero, `amount` of ``from``'s tokens will be burned. + * - `from` and `to` are never both zero. + * + * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. + */ + function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { } +} diff --git a/package-lock.json b/package-lock.json index 5f24f217..40bee6ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "name-here", + "name": "protocol-v2", "version": "1.0.0", "lockfileVersion": 1, "requires": true,