From 7d51b97b27c01b34ff1aa1012599bc2056939584 Mon Sep 17 00:00:00 2001 From: David Racero Date: Thu, 27 May 2021 12:03:19 +0200 Subject: [PATCH] feat: Added AaveOracleV2 with custom quote asset. Support AaveOracleV2 in deployment scripts. --- contracts/misc/AaveOracleV2.sol | 125 ++++++++++++++++++++++++++ helpers/contracts-deployments.ts | 14 ++- helpers/init-helpers.ts | 1 - tasks/dev/4_oracles.ts | 4 +- tasks/full/3_oracles.ts | 4 +- test-suites/test-aave/__setup.spec.ts | 6 +- test-suites/test-amm/__setup.spec.ts | 8 +- 7 files changed, 152 insertions(+), 10 deletions(-) create mode 100644 contracts/misc/AaveOracleV2.sol diff --git a/contracts/misc/AaveOracleV2.sol b/contracts/misc/AaveOracleV2.sol new file mode 100644 index 00000000..8af3b08e --- /dev/null +++ b/contracts/misc/AaveOracleV2.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity 0.6.12; + +import {Ownable} from '../dependencies/openzeppelin/contracts/Ownable.sol'; +import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol'; + +import {IPriceOracleGetter} from '../interfaces/IPriceOracleGetter.sol'; +import {IChainlinkAggregator} from '../interfaces/IChainlinkAggregator.sol'; +import {SafeERC20} from '../dependencies/openzeppelin/contracts/SafeERC20.sol'; + +/// @title AaveOracleV2 +/// @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 AaveOracleV2 is IPriceOracleGetter, Ownable { + using SafeERC20 for IERC20; + + event QuoteCurrencySet(address indexed quoteCurrency, uint256 quoteUnit); + event AssetSourceUpdated(address indexed asset, address indexed source); + event FallbackOracleUpdated(address indexed fallbackOracle); + + mapping(address => IChainlinkAggregator) private assetsSources; + IPriceOracleGetter private _fallbackOracle; + address public immutable QUOTE_CURRENCY; + uint256 public immutable QUOTE_CURRENCY_UNIT; + + /// @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, + address quoteCurrency, + uint256 quoteCurrencyUnits + ) public { + _setFallbackOracle(fallbackOracle); + _setAssetsSources(assets, sources); + QUOTE_CURRENCY = quoteCurrency; + QUOTE_CURRENCY_UNIT = quoteCurrencyUnits; + emit QuoteCurrencySet(quoteCurrency, quoteCurrencyUnits); + } + + /// @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 + { + _setAssetsSources(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 { + _setFallbackOracle(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 _setAssetsSources(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 _setFallbackOracle(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 view override returns (uint256) { + IChainlinkAggregator source = assetsSources[asset]; + + if (asset == QUOTE_CURRENCY) { + return QUOTE_CURRENCY_UNIT; + } else if (address(source) == address(0)) { + return _fallbackOracle.getAssetPrice(asset); + } else { + int256 price = IChainlinkAggregator(source).latestAnswer(); + if (price > 0) { + return uint256(price); + } else { + return _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); + } +} diff --git a/helpers/contracts-deployments.ts b/helpers/contracts-deployments.ts index 9572a648..e7122ac9 100644 --- a/helpers/contracts-deployments.ts +++ b/helpers/contracts-deployments.ts @@ -52,6 +52,7 @@ import { RewardsTokenFactory, RewardsATokenMockFactory, CurveRewardsAwareATokenFactory, + AaveOracleV2Factory, } from '../types'; import { withSaveAndVerify, @@ -225,7 +226,7 @@ export const deployMockAggregator = async (price: tStringTokenSmallUnits, verify ); export const deployAaveOracle = async ( - args: [tEthereumAddress[], tEthereumAddress[], tEthereumAddress, tEthereumAddress, string], + args: [tEthereumAddress[], tEthereumAddress[], tEthereumAddress, tEthereumAddress], verify?: boolean ) => withSaveAndVerify( @@ -235,6 +236,17 @@ export const deployAaveOracle = async ( verify ); +export const deployAaveOracleV2 = async ( + args: [tEthereumAddress[], tEthereumAddress[], tEthereumAddress, tEthereumAddress, string], + verify?: boolean +) => + withSaveAndVerify( + await new AaveOracleV2Factory(await getFirstSigner()).deploy(...args), + eContractid.AaveOracle, + args, + verify + ); + export const deployLendingPoolCollateralManager = async (verify?: boolean) => { const collateralManagerImpl = await new LendingPoolCollateralManagerFactory( await getFirstSigner() diff --git a/helpers/init-helpers.ts b/helpers/init-helpers.ts index 0d006beb..4dee6fa0 100644 --- a/helpers/init-helpers.ts +++ b/helpers/init-helpers.ts @@ -143,7 +143,6 @@ export const initReservesByHelper = async ( console.log(`- Reserves initialization in ${chunkedInitInputParams.length} txs`); for (let chunkIndex = 0; chunkIndex < chunkedInitInputParams.length; chunkIndex++) { - console.log(chunkedInitInputParams[chunkIndex]); const tx3 = await waitForTx( await configurator.batchInitReserve(chunkedInitInputParams[chunkIndex]) ); diff --git a/tasks/dev/4_oracles.ts b/tasks/dev/4_oracles.ts index 38228e5b..44a657af 100644 --- a/tasks/dev/4_oracles.ts +++ b/tasks/dev/4_oracles.ts @@ -1,7 +1,7 @@ import { task } from 'hardhat/config'; import { deployPriceOracle, - deployAaveOracle, + deployAaveOracleV2, deployLendingRateOracle, } from '../../helpers/contracts-deployments'; import { @@ -64,7 +64,7 @@ task('dev:deploy-oracles', 'Deploy oracles for dev environment') OracleQuoteCurrency ); - await deployAaveOracle( + await deployAaveOracleV2( [ tokens, aggregators, diff --git a/tasks/full/3_oracles.ts b/tasks/full/3_oracles.ts index 9c29cd4a..b3868250 100644 --- a/tasks/full/3_oracles.ts +++ b/tasks/full/3_oracles.ts @@ -1,6 +1,6 @@ import { task } from 'hardhat/config'; import { getParamPerNetwork } from '../../helpers/contracts-helpers'; -import { deployAaveOracle, deployLendingRateOracle } from '../../helpers/contracts-deployments'; +import { deployAaveOracleV2, deployLendingRateOracle } from '../../helpers/contracts-deployments'; import { setInitialMarketRatesInRatesOracleByHelper } from '../../helpers/oracles-helpers'; import { ICommonConfiguration, eNetwork, SymbolMap } from '../../helpers/types'; import { waitForTx, notFalsyOrZeroAddress } from '../../helpers/misc-utils'; @@ -59,7 +59,7 @@ task('full:deploy-oracles', 'Deploy oracles for dev enviroment') if (notFalsyOrZeroAddress(aaveOracleAddress)) { aaveOracle = await await getAaveOracle(aaveOracleAddress); } else { - aaveOracle = await deployAaveOracle( + aaveOracle = await deployAaveOracleV2( [ tokens, aggregators, diff --git a/test-suites/test-aave/__setup.spec.ts b/test-suites/test-aave/__setup.spec.ts index 69afa212..cadd9c53 100644 --- a/test-suites/test-aave/__setup.spec.ts +++ b/test-suites/test-aave/__setup.spec.ts @@ -12,7 +12,7 @@ import { deployLendingPoolConfigurator, deployLendingPool, deployPriceOracle, - deployAaveOracle, + deployAaveOracleV2, deployLendingPoolCollateralManager, deployMockFlashLoanReceiver, deployWalletBalancerProvider, @@ -200,6 +200,8 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { STAKE: mockTokens.STAKE.address, xSUSHI: mockTokens.xSUSHI.address, REW: mockTokens.REW.address, + 'a3CRV-gauge': ZERO_ADDRESS, + 'saCRV-gauge': ZERO_ADDRESS, }, fallbackOracle ); @@ -226,7 +228,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { config.OracleQuoteCurrency ); - await deployAaveOracle([ + await deployAaveOracleV2([ tokens, aggregators, fallbackOracle.address, diff --git a/test-suites/test-amm/__setup.spec.ts b/test-suites/test-amm/__setup.spec.ts index 407ec844..97db5d4e 100644 --- a/test-suites/test-amm/__setup.spec.ts +++ b/test-suites/test-amm/__setup.spec.ts @@ -12,7 +12,7 @@ import { deployLendingPoolConfigurator, deployLendingPool, deployPriceOracle, - deployAaveOracle, + deployAaveOracleV2, deployLendingPoolCollateralManager, deployMockFlashLoanReceiver, deployWalletBalancerProvider, @@ -199,6 +199,10 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { WMATIC: mockTokens.WMATIC.address, USD: USD_ADDRESS, STAKE: mockTokens.STAKE.address, + 'a3CRV-gauge': ZERO_ADDRESS, + 'saCRV-gauge': ZERO_ADDRESS, + xSUSHI: ZERO_ADDRESS, + REW: ZERO_ADDRESS, }, fallbackOracle ); @@ -226,7 +230,7 @@ const buildTestEnv = async (deployer: Signer, secondaryWallet: Signer) => { config.OracleQuoteCurrency ); - await deployAaveOracle([ + await deployAaveOracleV2([ tokens, aggregators, fallbackOracle.address,