diff --git a/contracts/interfaces/IChainlinkAggregator.sol b/contracts/interfaces/IChainlinkAggregator.sol index 4b75788d..75686371 100644 --- a/contracts/interfaces/IChainlinkAggregator.sol +++ b/contracts/interfaces/IChainlinkAggregator.sol @@ -2,6 +2,8 @@ pragma solidity 0.6.12; interface IChainlinkAggregator { + function decimals() external view returns (uint8); + function latestAnswer() external view returns (int256); function latestTimestamp() external view returns (uint256); @@ -14,4 +16,4 @@ interface IChainlinkAggregator { 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/misc/UiPoolDataProviderV2.sol b/contracts/misc/UiPoolDataProviderV2.sol new file mode 100644 index 00000000..bb561683 --- /dev/null +++ b/contracts/misc/UiPoolDataProviderV2.sol @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {IERC20Detailed} from '../dependencies/openzeppelin/contracts/IERC20Detailed.sol'; +import {ILendingPoolAddressesProvider} from '../interfaces/ILendingPoolAddressesProvider.sol'; +import {IUiPoolDataProviderV2} from './interfaces/IUiPoolDataProviderV2.sol'; +import {ILendingPool} from '../interfaces/ILendingPool.sol'; +import {IAaveOracle} from './interfaces/IAaveOracle.sol'; +import {IAToken} from '../interfaces/IAToken.sol'; +import {IVariableDebtToken} from '../interfaces/IVariableDebtToken.sol'; +import {IStableDebtToken} from '../interfaces/IStableDebtToken.sol'; +import {WadRayMath} from '../protocol/libraries/math/WadRayMath.sol'; +import {ReserveConfiguration} from '../protocol/libraries/configuration/ReserveConfiguration.sol'; +import {UserConfiguration} from '../protocol/libraries/configuration/UserConfiguration.sol'; +import {DataTypes} from '../protocol/libraries/types/DataTypes.sol'; +import {IChainlinkAggregator} from '../interfaces/IChainlinkAggregator.sol'; +import {DefaultReserveInterestRateStrategy} from '../protocol/lendingpool/DefaultReserveInterestRateStrategy.sol'; +import {IERC20DetailedBytes} from './interfaces/IERC20DetailedBytes.sol'; + +contract UiPoolDataProviderV2 is IUiPoolDataProviderV2 { + using WadRayMath for uint256; + using ReserveConfiguration for DataTypes.ReserveConfigurationMap; + using UserConfiguration for DataTypes.UserConfigurationMap; + + IChainlinkAggregator public immutable networkBaseTokenPriceInUsdProxyAggregator; + IChainlinkAggregator public immutable marketReferenceCurrencyPriceInUsdProxyAggregator; + uint256 public constant ETH_CURRENCY_UNIT = 1 ether; + address public constant MKRAddress = 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2; + + constructor( + IChainlinkAggregator _networkBaseTokenPriceInUsdProxyAggregator, + IChainlinkAggregator _marketReferenceCurrencyPriceInUsdProxyAggregator + ) public { + networkBaseTokenPriceInUsdProxyAggregator = _networkBaseTokenPriceInUsdProxyAggregator; + marketReferenceCurrencyPriceInUsdProxyAggregator = _marketReferenceCurrencyPriceInUsdProxyAggregator; + } + + function getInterestRateStrategySlopes(DefaultReserveInterestRateStrategy interestRateStrategy) + internal + view + returns ( + uint256, + uint256, + uint256, + uint256 + ) + { + return ( + interestRateStrategy.variableRateSlope1(), + interestRateStrategy.variableRateSlope2(), + interestRateStrategy.stableRateSlope1(), + interestRateStrategy.stableRateSlope2() + ); + } + + function getReservesList(ILendingPoolAddressesProvider provider) + public + view + override + returns (address[] memory) + { + ILendingPool lendingPool = ILendingPool(provider.getLendingPool()); + return lendingPool.getReservesList(); + } + + function getReservesData(ILendingPoolAddressesProvider provider) + public + view + override + returns (AggregatedReserveData[] memory, BaseCurrencyInfo memory) + { + IAaveOracle oracle = IAaveOracle(provider.getPriceOracle()); + ILendingPool lendingPool = ILendingPool(provider.getLendingPool()); + address[] memory reserves = lendingPool.getReservesList(); + AggregatedReserveData[] memory reservesData = new AggregatedReserveData[](reserves.length); + + for (uint256 i = 0; i < reserves.length; i++) { + AggregatedReserveData memory reserveData = reservesData[i]; + reserveData.underlyingAsset = reserves[i]; + + // reserve current state + DataTypes.ReserveData memory baseData = lendingPool.getReserveData( + reserveData.underlyingAsset + ); + reserveData.liquidityIndex = baseData.liquidityIndex; + reserveData.variableBorrowIndex = baseData.variableBorrowIndex; + reserveData.liquidityRate = baseData.currentLiquidityRate; + reserveData.variableBorrowRate = baseData.currentVariableBorrowRate; + reserveData.stableBorrowRate = baseData.currentStableBorrowRate; + reserveData.lastUpdateTimestamp = baseData.lastUpdateTimestamp; + reserveData.aTokenAddress = baseData.aTokenAddress; + reserveData.stableDebtTokenAddress = baseData.stableDebtTokenAddress; + reserveData.variableDebtTokenAddress = baseData.variableDebtTokenAddress; + reserveData.interestRateStrategyAddress = baseData.interestRateStrategyAddress; + reserveData.priceInMarketReferenceCurrency = oracle.getAssetPrice( + reserveData.underlyingAsset + ); + + reserveData.availableLiquidity = IERC20Detailed(reserveData.underlyingAsset).balanceOf( + reserveData.aTokenAddress + ); + ( + reserveData.totalPrincipalStableDebt, + , + reserveData.averageStableRate, + reserveData.stableDebtLastUpdateTimestamp + ) = IStableDebtToken(reserveData.stableDebtTokenAddress).getSupplyData(); + reserveData.totalScaledVariableDebt = IVariableDebtToken(reserveData.variableDebtTokenAddress) + .scaledTotalSupply(); + + if (address(reserveData.underlyingAsset) == address(MKRAddress)) { + bytes32 symbol = IERC20DetailedBytes(reserveData.underlyingAsset).symbol(); + reserveData.symbol = bytes32ToString(symbol); + } else { + reserveData.symbol = IERC20Detailed(reserveData.underlyingAsset).symbol(); + } + + ( + reserveData.baseLTVasCollateral, + reserveData.reserveLiquidationThreshold, + reserveData.reserveLiquidationBonus, + reserveData.decimals, + reserveData.reserveFactor + ) = baseData.configuration.getParamsMemory(); + ( + reserveData.isActive, + reserveData.isFrozen, + reserveData.borrowingEnabled, + reserveData.stableBorrowRateEnabled + ) = baseData.configuration.getFlagsMemory(); + reserveData.usageAsCollateralEnabled = reserveData.baseLTVasCollateral != 0; + ( + reserveData.variableRateSlope1, + reserveData.variableRateSlope2, + reserveData.stableRateSlope1, + reserveData.stableRateSlope2 + ) = getInterestRateStrategySlopes( + DefaultReserveInterestRateStrategy(reserveData.interestRateStrategyAddress) + ); + } + + BaseCurrencyInfo memory baseCurrencyInfo; + baseCurrencyInfo.networkBaseTokenPriceInUsd = networkBaseTokenPriceInUsdProxyAggregator + .latestAnswer(); + baseCurrencyInfo.networkBaseTokenPriceDecimals = networkBaseTokenPriceInUsdProxyAggregator + .decimals(); + + try oracle.BASE_CURRENCY_UNIT() returns (uint256 baseCurrencyUnit) { + if (ETH_CURRENCY_UNIT == baseCurrencyUnit) { + baseCurrencyInfo.marketReferenceCurrencyUnit = ETH_CURRENCY_UNIT; + baseCurrencyInfo + .marketReferenceCurrencyPriceInUsd = marketReferenceCurrencyPriceInUsdProxyAggregator + .latestAnswer(); + } else { + baseCurrencyInfo.marketReferenceCurrencyUnit = baseCurrencyUnit; + baseCurrencyInfo.marketReferenceCurrencyPriceInUsd = int256(baseCurrencyUnit); + } + } catch ( + bytes memory /*lowLevelData*/ + ) { + baseCurrencyInfo.marketReferenceCurrencyUnit = ETH_CURRENCY_UNIT; + baseCurrencyInfo + .marketReferenceCurrencyPriceInUsd = marketReferenceCurrencyPriceInUsdProxyAggregator + .latestAnswer(); + } + + return (reservesData, baseCurrencyInfo); + } + + function getUserReservesData(ILendingPoolAddressesProvider provider, address user) + external + view + override + returns (UserReserveData[] memory) + { + ILendingPool lendingPool = ILendingPool(provider.getLendingPool()); + address[] memory reserves = lendingPool.getReservesList(); + DataTypes.UserConfigurationMap memory userConfig = lendingPool.getUserConfiguration(user); + + UserReserveData[] memory userReservesData = new UserReserveData[]( + user != address(0) ? reserves.length : 0 + ); + + for (uint256 i = 0; i < reserves.length; i++) { + DataTypes.ReserveData memory baseData = lendingPool.getReserveData(reserves[i]); + + // user reserve data + userReservesData[i].underlyingAsset = reserves[i]; + userReservesData[i].scaledATokenBalance = IAToken(baseData.aTokenAddress).scaledBalanceOf( + user + ); + userReservesData[i].usageAsCollateralEnabledOnUser = userConfig.isUsingAsCollateral(i); + + if (userConfig.isBorrowing(i)) { + userReservesData[i].scaledVariableDebt = IVariableDebtToken( + baseData.variableDebtTokenAddress + ).scaledBalanceOf(user); + userReservesData[i].principalStableDebt = IStableDebtToken(baseData.stableDebtTokenAddress) + .principalBalanceOf(user); + if (userReservesData[i].principalStableDebt != 0) { + userReservesData[i].stableBorrowRate = IStableDebtToken(baseData.stableDebtTokenAddress) + .getUserStableRate(user); + userReservesData[i].stableBorrowLastUpdateTimestamp = IStableDebtToken( + baseData.stableDebtTokenAddress + ).getUserLastUpdated(user); + } + } + } + + return (userReservesData); + } + + function bytes32ToString(bytes32 _bytes32) public pure returns (string memory) { + uint8 i = 0; + while (i < 32 && _bytes32[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); + } +} \ No newline at end of file diff --git a/contracts/misc/interfaces/IAaveOracle.sol b/contracts/misc/interfaces/IAaveOracle.sol new file mode 100644 index 00000000..3ecc82b6 --- /dev/null +++ b/contracts/misc/interfaces/IAaveOracle.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +/** + * @title IAaveOracle interface + * @notice Interface for the Aave oracle. + **/ + +interface IAaveOracle { + function BASE_CURRENCY() external view returns (address); // if usd returns 0x0, if eth returns weth address + function BASE_CURRENCY_UNIT() external view returns (uint256); + + /*********** + @dev returns the asset price in ETH + */ + function getAssetPrice(address asset) external view returns (uint256); +} \ No newline at end of file diff --git a/contracts/misc/interfaces/IERC20DetailedBytes.sol b/contracts/misc/interfaces/IERC20DetailedBytes.sol index de91e288..8c47df16 100644 --- a/contracts/misc/interfaces/IERC20DetailedBytes.sol +++ b/contracts/misc/interfaces/IERC20DetailedBytes.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: agpl-3.0 pragma solidity 0.6.12; -contract IERC20DetailedBytes { - bytes32 public name; - bytes32 public symbol; - uint256 public decimals; -} +import {IERC20} from '../../dependencies/openzeppelin/contracts/IERC20.sol'; + +interface IERC20DetailedBytes is IERC20 { + function name() external view returns (bytes32); + + function symbol() external view returns (bytes32); + + function decimals() external view returns (uint8); +} \ No newline at end of file diff --git a/contracts/misc/interfaces/IUiPoolDataProviderV2.sol b/contracts/misc/interfaces/IUiPoolDataProviderV2.sol new file mode 100644 index 00000000..a286f9cc --- /dev/null +++ b/contracts/misc/interfaces/IUiPoolDataProviderV2.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import {ILendingPoolAddressesProvider} from '../../interfaces/ILendingPoolAddressesProvider.sol'; + +interface IUiPoolDataProviderV2 { + struct AggregatedReserveData { + address underlyingAsset; + string name; + string symbol; + uint256 decimals; + uint256 baseLTVasCollateral; + uint256 reserveLiquidationThreshold; + uint256 reserveLiquidationBonus; + uint256 reserveFactor; + bool usageAsCollateralEnabled; + bool borrowingEnabled; + bool stableBorrowRateEnabled; + bool isActive; + bool isFrozen; + // base data + uint128 liquidityIndex; + uint128 variableBorrowIndex; + uint128 liquidityRate; + uint128 variableBorrowRate; + uint128 stableBorrowRate; + uint40 lastUpdateTimestamp; + address aTokenAddress; + address stableDebtTokenAddress; + address variableDebtTokenAddress; + address interestRateStrategyAddress; + // + uint256 availableLiquidity; + uint256 totalPrincipalStableDebt; + uint256 averageStableRate; + uint256 stableDebtLastUpdateTimestamp; + uint256 totalScaledVariableDebt; + uint256 priceInMarketReferenceCurrency; + uint256 variableRateSlope1; + uint256 variableRateSlope2; + uint256 stableRateSlope1; + uint256 stableRateSlope2; + } + + struct UserReserveData { + address underlyingAsset; + uint256 scaledATokenBalance; + bool usageAsCollateralEnabledOnUser; + uint256 stableBorrowRate; + uint256 scaledVariableDebt; + uint256 principalStableDebt; + uint256 stableBorrowLastUpdateTimestamp; + } + + struct BaseCurrencyInfo { + uint256 marketReferenceCurrencyUnit; + int256 marketReferenceCurrencyPriceInUsd; + int256 networkBaseTokenPriceInUsd; + uint8 networkBaseTokenPriceDecimals; + } + + function getReservesList(ILendingPoolAddressesProvider provider) + external + view + returns (address[] memory); + + function getReservesData(ILendingPoolAddressesProvider provider) + external + view + returns ( + AggregatedReserveData[] memory, + BaseCurrencyInfo memory + ); + + function getUserReservesData(ILendingPoolAddressesProvider provider, address user) + external + view + returns ( + UserReserveData[] memory + ); +} \ No newline at end of file diff --git a/helper-hardhat-config.ts b/helper-hardhat-config.ts index c66da367..9a8fb434 100644 --- a/helper-hardhat-config.ts +++ b/helper-hardhat-config.ts @@ -58,7 +58,7 @@ export const NETWORKS_RPC_URL: iParamsPerNetwork = { }; export const NETWORKS_DEFAULT_GAS: iParamsPerNetwork = { - [eEthereumNetwork.kovan]: 1 * GWEI, + [eEthereumNetwork.kovan]: 3 * GWEI, [eEthereumNetwork.ropsten]: 65 * GWEI, [eEthereumNetwork.main]: 65 * GWEI, [eEthereumNetwork.coverage]: 65 * GWEI, diff --git a/helpers/constants.ts b/helpers/constants.ts index fe743fa2..6720a7b9 100644 --- a/helpers/constants.ts +++ b/helpers/constants.ts @@ -75,3 +75,23 @@ export const MOCK_CHAINLINK_AGGREGATORS_PRICES = { WAVAX: oneEther.multipliedBy('0.006051936629').toFixed(), USD: '5848466240000000', }; + +export const chainlinkAggregatorProxy = { + main: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + kovan: '0x9326BFA02ADD2366b30bacB125260Af641031331', + matic: '0xAB594600376Ec9fD91F8e885dADF0CE036862dE0', + mumbai: '0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada', + avalanche: '0x0A77230d17318075983913bC2145DB16C7366156', + fuji: '0x5498BB86BC934c8D34FDA08E81D444153d0D06aD', + tenderly: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', +}; + +export const chainlinkEthUsdAggregatorProxy = { + main: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', + kovan: '0x9326BFA02ADD2366b30bacB125260Af641031331', + matic: '0xF9680D99D6C9589e2a93a78A04A279e509205945', + mumbai: '0x0715A7794a1dc8e42615F059dD6e406A6594651A', + avalanche: '0x976B3D034E162d8bD72D6b9C989d545b839003b0', + fuji: '0x86d67c3D38D2bCeE722E601025C25a575021c6EA', + tenderly: '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419', +}; diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 13c64a78..2d29e3f2 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -51,6 +51,7 @@ import { WETH9MockedFactory, WETHGatewayFactory, FlashLiquidationAdapterFactory, + UiPoolDataProviderV2Factory, } from '../types'; import { withSaveAndVerify, @@ -69,6 +70,21 @@ import { LendingPoolLibraryAddresses } from '../types/LendingPoolFactory'; import { UiPoolDataProvider } from '../types'; import { eNetwork } from './types'; +export const deployUiPoolDataProviderV2 = async ( + chainlinkAggregatorProxy: string, + chainlinkEthUsdAggregatorProxy: string, + verify?: boolean +) => + withSaveAndVerify( + await new UiPoolDataProviderV2Factory(await getFirstSigner()).deploy( + chainlinkAggregatorProxy, + chainlinkEthUsdAggregatorProxy + ), + eContractid.UiPoolDataProvider, + [chainlinkAggregatorProxy, chainlinkEthUsdAggregatorProxy], + verify + ); + export const deployUiPoolDataProvider = async ( [incentivesController, aaveOracle]: [tEthereumAddress, tEthereumAddress], verify?: boolean diff --git a/helpers/contracts-helpers.ts b/helpers/contracts-helpers.ts index dc786128..8548eba0 100644 --- a/helpers/contracts-helpers.ts +++ b/helpers/contracts-helpers.ts @@ -192,7 +192,10 @@ export const getOptionalParamAddressPerNetwork = ( return getParamPerNetwork(param, network); }; -export const getParamPerPool = ({ proto, amm, matic, avalanche }: iParamsPerPool, pool: AavePools) => { +export const getParamPerPool = ( + { proto, amm, matic, avalanche }: iParamsPerPool, + pool: AavePools +) => { switch (pool) { case AavePools.proto: return proto; diff --git a/helpers/types.ts b/helpers/types.ts index babd113e..3f1e82d6 100644 --- a/helpers/types.ts +++ b/helpers/types.ts @@ -84,6 +84,7 @@ export enum eContractid { StableAndVariableTokensHelper = 'StableAndVariableTokensHelper', ATokensAndRatesHelper = 'ATokensAndRatesHelper', UiPoolDataProvider = 'UiPoolDataProvider', + UiPoolDataProviderV2 = 'UiPoolDataProviderV2', WETHGateway = 'WETHGateway', WETH = 'WETH', WETHMocked = 'WETHMocked', diff --git a/package.json b/package.json index dfe0b2d4..e6b68832 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "matic:deployUIProvider": "hardhat --network matic deploy-UiPoolDataProvider", "mumbai:deployUIProvider": "hardhat --network mumbai deploy-UiPoolDataProvider", "fuji:deployUIProvider": "hardhat --network fuji deploy-UiPoolDataProvider", + "dev:deployUIProviderV2": "hardhat --network kovan deploy-UiPoolDataProviderV2 --verify", "dev:deployUniswapRepayAdapter": "hardhat --network kovan deploy-UniswapRepayAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c", "dev:UniswapLiquiditySwapAdapter": "hardhat --network kovan deploy-UniswapLiquiditySwapAdapter --provider 0x88757f2f99175387aB4C6a4b3067c77A695b0349 --router 0xfcd87315f0e4067070ade8682fcdbc3006631441 --weth 0xd0a1e359811322d97991e03f863a0c30c2cf029c", "main:deployUniswapRepayAdapter": "hardhat --network main deploy-UniswapRepayAdapter --provider 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5 --router 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D --weth 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", diff --git a/tasks/deployments/deploy-UiPoolDataProviderV2.ts b/tasks/deployments/deploy-UiPoolDataProviderV2.ts new file mode 100644 index 00000000..6c0e0262 --- /dev/null +++ b/tasks/deployments/deploy-UiPoolDataProviderV2.ts @@ -0,0 +1,34 @@ +import { task } from 'hardhat/config'; +import { eContractid } from '../../helpers/types'; +import { deployUiPoolDataProviderV2 } from '../../helpers/contracts-deployments'; +import { chainlinkAggregatorProxy, chainlinkEthUsdAggregatorProxy } from '../../helpers/constants'; + +task(`deploy-${eContractid.UiPoolDataProviderV2}`, `Deploys the UiPoolDataProviderV2 contract`) + .addFlag('verify', 'Verify UiPoolDataProviderV2 contract via Etherscan API.') + .setAction(async ({ verify }, localBRE) => { + await localBRE.run('set-DRE'); + if (!localBRE.network.config.chainId) { + throw new Error('INVALID_CHAIN_ID'); + } + + console.log( + `\n- UiPoolDataProviderV2 price aggregator: ${ + chainlinkAggregatorProxy[localBRE.network.name] + }` + ); + console.log( + `\n- UiPoolDataProviderV2 eth/usd price aggregator: ${ + chainlinkAggregatorProxy[localBRE.network.name] + }` + ); + console.log(`\n- UiPoolDataProviderV2 deployment`); + + const UiPoolDataProviderV2 = await deployUiPoolDataProviderV2( + chainlinkAggregatorProxy[localBRE.network.name], + chainlinkEthUsdAggregatorProxy[localBRE.network.name], + verify + ); + + console.log('UiPoolDataProviderV2 deployed at:', UiPoolDataProviderV2.address); + console.log(`\tFinished UiPoolDataProvider deployment`); + });