├── .prettierignore ├── .gitignore ├── docs ├── Weighted Geometric Mean Math.pdf ├── Final Specification Uniswap V2 Price Provider.pdf ├── Specification Balancer Shared Pool Price Provider.pdf └── LICENSE.md ├── contracts └── lp-oracle-contracts │ ├── interfaces │ ├── IUniswapV2Factory.sol │ ├── IPriceOracle.sol │ ├── BPool.sol │ ├── IGUniPool.sol │ ├── IUniswapV2Pair.sol │ └── IExtendedAggregator.sol │ ├── mock │ ├── Token.sol │ ├── UniswapV2FactoryMock.sol │ ├── PriceOracleMock.sol │ └── UniswapV2PairMock.sol │ ├── misc │ ├── SafeMath.sol │ ├── Math.sol │ ├── BConst.sol │ └── BNum.sol │ └── aggregators │ ├── GUniPriceProvider.sol │ ├── UniswapV2PriceProvider.sol │ └── BalancerSharedPoolPriceProvider.sol ├── tasks ├── set-hre.ts └── deploy.ts ├── tsconfig.json ├── .prettierrc ├── README.md ├── scripts ├── misc-helpers.ts ├── deploy-price-aggregators │ ├── uniswap.ts │ └── balancer.ts └── config.ts ├── package.json └── hardhat.config.ts /.prettierignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | cache 3 | node_modules 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | secrets.json 2 | node_modules 3 | artifacts 4 | cache 5 | typechain -------------------------------------------------------------------------------- /docs/Weighted Geometric Mean Math.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aave/price-aggregators/HEAD/docs/Weighted Geometric Mean Math.pdf -------------------------------------------------------------------------------- /docs/Final Specification Uniswap V2 Price Provider.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aave/price-aggregators/HEAD/docs/Final Specification Uniswap V2 Price Provider.pdf -------------------------------------------------------------------------------- /docs/Specification Balancer Shared Pool Price Provider.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aave/price-aggregators/HEAD/docs/Specification Balancer Shared Pool Price Provider.pdf -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/IUniswapV2Factory.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | 3 | pragma solidity 0.6.12; 4 | 5 | interface IUniswapV2Factory { 6 | function feeTo() external view returns (address); 7 | } 8 | -------------------------------------------------------------------------------- /tasks/set-hre.ts: -------------------------------------------------------------------------------- 1 | import { task } from 'hardhat/config'; 2 | import { setHRE } from '../scripts/misc-helpers'; 3 | 4 | task(`set-hre`, `Inits the DRE, to have access to all the plugins' objects`).setAction( 5 | async (_, _HRE) => { 6 | await setHRE(_HRE); 7 | return _HRE; 8 | } 9 | ); 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true 9 | }, 10 | "include": ["./tasks", "./test"], 11 | "files": ["hardhat.config.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "trailingComma": "es5", 4 | "semi": true, 5 | "singleQuote": true, 6 | "tabWidth": 2, 7 | "overrides": [ 8 | { 9 | "files": "*.sol", 10 | "options": { 11 | "semi": true, 12 | "printWidth": 100 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/mock/Token.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 5 | 6 | contract Token is ERC20 { 7 | constructor(uint8 decimals) public ERC20('Test', 'TST') { 8 | _setupDecimals(decimals); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AAVE <-> AMM Price providers 2 | 3 | This repository contains contracts that enable the AAVE protocol to read prices for the following AMM tokens: 4 | - Balancer 5 | - Uniswap v2 6 | 7 | ## Security 8 | Both Balancer and Uniswap v2 price providers have been audited by Consensys Diligence, with the report [here](https://consensys.net/diligence/audits/2020/08/aave-balancer-and-uniswap-v2-price-providers/) -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/IPriceOracle.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | /************ 5 | @title IPriceOracle interface 6 | @notice Interface for the Aave price oracle.*/ 7 | interface IPriceOracle { 8 | /*********** 9 | @dev returns the asset price in ETH 10 | */ 11 | function getAssetPrice(address _asset) external view returns (uint256); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/mock/UniswapV2FactoryMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | import '../interfaces/IUniswapV2Factory.sol'; 5 | 6 | contract UniswapV2FactoryMock is IUniswapV2Factory { 7 | address _feeTo; 8 | 9 | constructor(address __feeTo) public { 10 | _feeTo = __feeTo; 11 | } 12 | 13 | function feeTo() external view override returns (address) { 14 | return _feeTo; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/BPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | interface BPool { 5 | function getFinalTokens() external view returns (address[] memory tokens); 6 | 7 | function getNormalizedWeight(address token) external view returns (uint256); 8 | 9 | function getBalance(address token) external view returns (uint256); 10 | 11 | function totalSupply() external view returns (uint256); 12 | } 13 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/IGUniPool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | pragma solidity =0.6.12; 3 | 4 | interface IGUniPool { 5 | function token0() external view returns (address); 6 | function token1() external view returns (address); 7 | function getUnderlyingBalancesAtPrice(uint160) external view returns (uint256, uint256); 8 | function getUnderlyingBalances() external view returns (uint256, uint256); 9 | function totalSupply() external view returns (uint256); 10 | } -------------------------------------------------------------------------------- /docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Aave 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU Affero General Public License as 5 | published by the Free Software Foundation, either version 3 of the 6 | License, or any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html) 12 | for more details 13 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/mock/PriceOracleMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | import '../interfaces/IPriceOracle.sol'; 5 | 6 | contract PriceOracleMock is IPriceOracle { 7 | mapping(address => uint256) private prices; 8 | 9 | constructor(address[] memory _tokens, uint256[] memory _prices) public { 10 | for (uint256 i = 0; i < _tokens.length; i++) { 11 | prices[_tokens[i]] = _prices[i]; 12 | } 13 | } 14 | 15 | function getAssetPrice(address _asset) external view override returns (uint256) { 16 | return prices[_asset]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/IUniswapV2Pair.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | interface IUniswapV2Pair { 5 | function totalSupply() external view returns (uint256); 6 | 7 | function token0() external view returns (address); 8 | 9 | function token1() external view returns (address); 10 | 11 | function getReserves() 12 | external 13 | view 14 | returns ( 15 | uint112 reserve0, 16 | uint112 reserve1, 17 | uint32 blockTimestampLast 18 | ); 19 | 20 | function kLast() external view returns (uint256); 21 | 22 | function factory() external view returns (address); 23 | } 24 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/misc/SafeMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | // a library for performing overflow-safe math, courtesy of DappHub (https://github.com/dapphub/ds-math) 5 | 6 | library SafeMath { 7 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 8 | require((z = x + y) >= x, 'ds-math-add-overflow'); 9 | } 10 | 11 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 12 | require((z = x - y) <= x, 'ds-math-sub-underflow'); 13 | } 14 | 15 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 16 | require(y == 0 || (z = x * y) / y == x, 'ds-math-mul-overflow'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/misc-helpers.ts: -------------------------------------------------------------------------------- 1 | import { HardhatRuntimeEnvironment } from 'hardhat/types'; 2 | 3 | export let HRE: HardhatRuntimeEnvironment = {} as HardhatRuntimeEnvironment; 4 | 5 | export const setHRE = (_HRE: HardhatRuntimeEnvironment) => { 6 | HRE = _HRE; 7 | }; 8 | 9 | export async function delay(ms: number) { 10 | return new Promise((resolve) => setTimeout(resolve, ms)); 11 | } 12 | 13 | export async function verifyContract(_address: string, contractPath: string, args: any) { 14 | let count = 0; 15 | let maxTries = 5; 16 | while (true) { 17 | try { 18 | console.log('Verifying contract at', _address); 19 | await HRE.run('verify:verify', { 20 | address: _address, 21 | constructorArguments: args, 22 | contract: contractPath, //"contracts/lp-oracle-contracts/mock/Token.sol:Token", 23 | }); 24 | return; 25 | } catch (error) { 26 | if (++count == maxTries) { 27 | console.log( 28 | 'Failed to verify contract at path %s at address %s, error: %s', 29 | _address, 30 | error 31 | ); 32 | } 33 | throw 'Verification failed'; 34 | } 35 | await delay(5000); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tasks/deploy.ts: -------------------------------------------------------------------------------- 1 | import { setBptAggs } from '../scripts/deploy-price-aggregators/balancer'; 2 | import { setUniAggs } from '../scripts/deploy-price-aggregators/uniswap'; 3 | import { task } from 'hardhat/config'; 4 | 5 | const { signerAddress } = require('../secrets.json'); 6 | 7 | task('deploy-price-aggregators', 'Deploy price aggregators', async (_, hre) => { 8 | // Make HRE be available in other modules 9 | await hre.run('set-hre'); 10 | 11 | // Uncomment pool names you dont want to deploy due deployment re-runs 12 | const balancerDeployList = ['BptWBTCWETH', 'BptBALWETH']; 13 | const uniswapDeployList = [ 14 | 'UniDAIWETH', 15 | 'UniWBTCWETH', 16 | 'UniAAVEWETH', 17 | 'UniBATWETH', 18 | 'UniDAIUSDC', 19 | 'UniCRVWETH', 20 | 'UniLINKWETH', 21 | 'UniMKRWETH', 22 | 'UniRENWETH', 23 | 'UniSNXWETH', 24 | 'UniUNIWETH', 25 | 'UniUSDCWETH', 26 | 'UniWBTCUSDC', 27 | 'UniYFIWETH', 28 | ]; 29 | 30 | if (!signerAddress) { 31 | console.error( 32 | "Missing signer address. Please re-run command and provide an address to 'signerAddress' field at secrets.json." 33 | ); 34 | } 35 | 36 | const signer = hre.ethers.provider.getSigner(signerAddress); 37 | 38 | await setUniAggs(uniswapDeployList, signer); 39 | await setBptAggs(balancerDeployList, signer); 40 | }); 41 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/interfaces/IExtendedAggregator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | 3 | pragma solidity ^0.6.12; 4 | 5 | interface IExtendedAggregator { 6 | enum TokenType {Invalid, Simple, Complex} 7 | 8 | enum PlatformId {Invalid, Simple, Uniswap, Balancer, GUni} 9 | 10 | /** 11 | * @dev Returns the LP shares token 12 | * @return address of the LP shares token 13 | */ 14 | function getToken() external view returns (address); 15 | 16 | /** 17 | * @dev Returns token type for categorization 18 | * @return uint256 1 = Simple (Native or plain ERC20 tokens like DAI), 2 = Complex (LP Tokens, Staked tokens) 19 | */ 20 | function getTokenType() external pure returns (TokenType); 21 | 22 | /** 23 | * @dev Returns the number of tokens that composes the LP shares 24 | * @return address[] memory of token addresses 25 | */ 26 | function getSubTokens() external view returns (address[] memory); 27 | 28 | /** 29 | * @dev Returns the platform id to categorize the price aggregator 30 | * @return uint256 1 = Uniswap, 2 = Balancer 31 | */ 32 | function getPlatformId() external pure returns (PlatformId); 33 | 34 | /** 35 | * @dev Returns the latest price 36 | * @return int256 price 37 | */ 38 | function latestAnswer() external view returns (int256); 39 | } 40 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/mock/UniswapV2PairMock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | import '../interfaces/IUniswapV2Pair.sol'; 5 | 6 | contract UniswapV2PairMock is IUniswapV2Pair { 7 | address _factory; 8 | address _token_0; 9 | address _token_1; 10 | uint112 _reserve0; 11 | uint112 _reserve1; 12 | uint256 _supply; 13 | uint256 _kLast; 14 | 15 | constructor( 16 | address __factory, 17 | address __token_0, 18 | address __token_1, 19 | uint112 __reserve0, 20 | uint112 __reserve1, 21 | uint256 __supply, 22 | uint256 __kLast 23 | ) public { 24 | _factory = __factory; 25 | _token_0 = __token_0; 26 | _token_1 = __token_1; 27 | _reserve0 = __reserve0; 28 | _reserve1 = __reserve1; 29 | _supply = __supply; 30 | _kLast = __kLast; 31 | } 32 | 33 | function totalSupply() external view override returns (uint256) { 34 | return _supply; 35 | } 36 | 37 | function token0() external view override returns (address) { 38 | return _token_0; 39 | } 40 | 41 | function token1() external view override returns (address) { 42 | return _token_1; 43 | } 44 | 45 | function getReserves() 46 | external 47 | view 48 | override 49 | returns ( 50 | uint112, 51 | uint112, 52 | uint32 53 | ) 54 | { 55 | return (_reserve0, _reserve1, uint32(block.timestamp)); 56 | } 57 | 58 | function kLast() external view override returns (uint256) { 59 | return _kLast; 60 | } 61 | 62 | function factory() external view override returns (address) { 63 | return _factory; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/misc/Math.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | 4 | // a library for performing various math operations 5 | 6 | library Math { 7 | uint256 public constant BONE = 10**18; 8 | uint256 public constant TWO_BONES = 2 * 10**18; 9 | 10 | /** 11 | * @notice Returns the square root of an uint256 x using the Babylonian method 12 | * @param y The number to calculate the sqrt from 13 | * @param bone True when y has 18 decimals 14 | */ 15 | function bsqrt(uint256 y, bool bone) internal pure returns (uint256 z) { 16 | if (y > 3) { 17 | z = y; 18 | uint256 x = y / 2 + 1; 19 | while (x < z) { 20 | z = x; 21 | if (bone) { 22 | x = (bdiv(y, x) + x) / 2; 23 | } else { 24 | x = (y / x + x) / 2; 25 | } 26 | } 27 | } else if (y != 0) { 28 | z = 1; 29 | } 30 | } 31 | 32 | function bmul( 33 | uint256 a, 34 | uint256 b //Bone mul 35 | ) internal pure returns (uint256) { 36 | uint256 c0 = a * b; 37 | require(a == 0 || c0 / a == b, 'ERR_MUL_OVERFLOW'); 38 | uint256 c1 = c0 + (BONE / 2); 39 | require(c1 >= c0, 'ERR_MUL_OVERFLOW'); 40 | uint256 c2 = c1 / BONE; 41 | return c2; 42 | } 43 | 44 | function bdiv( 45 | uint256 a, 46 | uint256 b //Bone div 47 | ) internal pure returns (uint256) { 48 | require(b != 0, 'ERR_DIV_ZERO'); 49 | uint256 c0 = a * BONE; 50 | require(a == 0 || c0 / a == BONE, 'ERR_DIV_INTERNAL'); // bmul overflow 51 | uint256 c1 = c0 + (b / 2); 52 | require(c1 >= c0, 'ERR_DIV_INTERNAL'); // badd require 53 | uint256 c2 = c1 / b; 54 | return c2; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "price-aggregators", 3 | "version": "1.0.0", 4 | "description": "Deployment scripts for Aave price aggregators", 5 | "dependencies": { 6 | "@openzeppelin/contracts": "^3.3.0", 7 | "@typechain/ethers-v5": "^5.0.0", 8 | "decimal.js": "^10.2.1", 9 | "hardhat-typechain": "^0.3.4", 10 | "ts-generator": "^0.1.1", 11 | "typechain": "^4.0.1" 12 | }, 13 | "devDependencies": { 14 | "@nomiclabs/hardhat-ethers": "^2.0.2", 15 | "@nomiclabs/hardhat-etherscan": "^2.0.1", 16 | "@nomiclabs/hardhat-waffle": "^2.0.0", 17 | "@types/chai": "^4.2.14", 18 | "@types/mocha": "^8.2.0", 19 | "@types/node": "^14.14.21", 20 | "chai": "^4.2.0", 21 | "ethereum-waffle": "^3.2.1", 22 | "ethers": "^5.0.23", 23 | "hardhat": "^2.0.6", 24 | "husky": "^5.1.3", 25 | "prettier": "^2.2.1", 26 | "prettier-plugin-solidity": "^1.0.0-beta.6", 27 | "pretty-quick": "^3.1.0", 28 | "tmp-promise": "^3.0.2", 29 | "ts-node": "^9.1.1", 30 | "typescript": "^4.1.3" 31 | }, 32 | "scripts": { 33 | "compile": "SKIP_LOAD=true npx hardhat compile", 34 | "deploy-mainnet": "npm run compile && npx hardhat --network mainnet deploy-price-aggregators", 35 | "deploy-tenderly": "npm run compile && npx hardhat --network tenderly deploy-price-aggregators", 36 | "deploy-kovan": "npm run compile && npx hardhat --network kovan deploy-price-aggregators", 37 | "prettier:check": "npx prettier -c 'contracts/**/*.sol' 'scripts/**/*.ts'", 38 | "prettier:write": "prettier --write 'contracts/**/*.sol' 'scripts/**/*.ts'" 39 | }, 40 | "husky": { 41 | "hooks": { 42 | "pre-commit": "pretty-quick --staged --pattern 'contracts/**/*.sol' --pattern 'scripts/**/*.ts'" 43 | } 44 | }, 45 | "author": "Aave", 46 | "license": "AGPL-3.0-only" 47 | } 48 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/misc/BConst.sol: -------------------------------------------------------------------------------- 1 | // This program is free software: you can redistribute it and/or modify 2 | // it under the terms of the GNU General Public License as published by 3 | // the Free Software Foundation, either version 3 of the License, or 4 | // (at your option) any later version. 5 | 6 | // This program is distributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | // GNU General Public License for more details. 10 | 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | 14 | // From // From https://github.com/balancer-labs/balancer-core/blob/master/contracts/BConst.sol 15 | 16 | // SPDX-License-Identifier: GPL-3.0-or-later 17 | pragma solidity 0.6.12; 18 | 19 | contract BConst { 20 | uint256 public constant BONE = 10**18; 21 | 22 | uint256 public constant MIN_BOUND_TOKENS = 2; 23 | uint256 public constant MAX_BOUND_TOKENS = 8; 24 | 25 | uint256 public constant MIN_FEE = BONE / 10**6; 26 | uint256 public constant MAX_FEE = BONE / 10; 27 | uint256 public constant EXIT_FEE = 0; 28 | 29 | uint256 public constant MIN_WEIGHT = BONE; 30 | uint256 public constant MAX_WEIGHT = BONE * 50; 31 | uint256 public constant MAX_TOTAL_WEIGHT = BONE * 50; 32 | uint256 public constant MIN_BALANCE = BONE / 10**12; 33 | 34 | uint256 public constant INIT_POOL_SUPPLY = BONE * 100; 35 | 36 | uint256 public constant MIN_BPOW_BASE = 1 wei; 37 | uint256 public constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; 38 | uint256 public constant BPOW_PRECISION = BONE / 10**10; 39 | 40 | uint256 public constant MAX_IN_RATIO = BONE / 2; 41 | uint256 public constant MAX_OUT_RATIO = (BONE / 3) + 1 wei; 42 | } 43 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import '@nomiclabs/hardhat-waffle'; 4 | import '@nomiclabs/hardhat-etherscan'; 5 | import '@nomiclabs/hardhat-ethers'; 6 | import 'hardhat-typechain'; 7 | 8 | // Import HRE task 9 | import './tasks/set-hre'; 10 | import './tasks/deploy'; 11 | 12 | const { ffmnemonic, alchemyProjectId, etherscanKey, infuraProjectId } = require('./secrets.json'); 13 | 14 | // Prevent to load scripts before compilation and typechain 15 | const SKIP_LOAD = process.env.SKIP_LOAD === 'true'; 16 | if (!SKIP_LOAD) { 17 | const tasksPath = path.join(__dirname, 'tasks'); 18 | fs.readdirSync(tasksPath) 19 | .filter((pth) => pth.includes('.ts')) 20 | .forEach((task) => { 21 | require(`${tasksPath}/${task}`); 22 | }); 23 | } 24 | 25 | export default { 26 | solidity: { 27 | version: '0.6.12', 28 | settings: { 29 | optimizer: { 30 | enabled: true, 31 | runs: 200, 32 | }, 33 | }, 34 | }, 35 | networks: { 36 | mainnet: { 37 | url: `https://eth-mainnet.alchemyapi.io/v2/${alchemyProjectId}`, 38 | gasPrice: 71000000000, 39 | accounts: { mnemonic: ffmnemonic }, 40 | }, 41 | matic: { 42 | url: `https://rpc-mainnet.matic.network`, 43 | accounts: { mnemonic: ffmnemonic }, 44 | }, 45 | mumbai: { 46 | url: `https://rpc-mumbai.maticvigil.com/`, 47 | accounts: { mnemonic: ffmnemonic }, 48 | }, 49 | hardhat: { 50 | forking: { 51 | url: `https://eth-mainnet.alchemyapi.io/v2/${alchemyProjectId}`, 52 | blockNumber: 11689738, 53 | }, 54 | }, 55 | tenderly: { 56 | url: `https://rpc.tenderly.co/fork/f551ad0a-1daf-47c0-b091-73beef4a43bb`, 57 | }, 58 | kovan: { 59 | url: `https://eth-kovan.alchemyapi.io/v2/${alchemyProjectId}`, 60 | accounts: { mnemonic: ffmnemonic }, 61 | chainId: 42, 62 | }, 63 | }, 64 | etherscan: { 65 | apiKey: etherscanKey, 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /scripts/deploy-price-aggregators/uniswap.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from 'ethers'; 2 | import { UniswapV2PriceProvider__factory } from '../../typechain'; 3 | import '@nomiclabs/hardhat-ethers'; 4 | import { AAVE_ORACLE, MAX_PRICE_DEVIATION, uniswapMarkets } from '../config'; 5 | import { exit } from 'process'; 6 | import { verifyContract } from '../misc-helpers'; 7 | 8 | export async function setUniAggs(deployList: string[], signer: Signer) { 9 | const uniswapPools = uniswapMarkets.filter(({ name }) => { 10 | return deployList.includes(name); 11 | }); 12 | 13 | if (uniswapPools.length !== deployList.length) { 14 | console.error('Deploy list mismatch, check names at scripts/config.ts'); 15 | return exit(1); 16 | } 17 | 18 | const aggregatorAddresses: string[] = []; 19 | 20 | for (let i = 0; i < uniswapPools.length; i++) { 21 | const uniAggregator = await new UniswapV2PriceProvider__factory(signer).deploy( 22 | uniswapPools[i].address, 23 | uniswapPools[i].peg, 24 | uniswapPools[i].decimals, 25 | AAVE_ORACLE, 26 | MAX_PRICE_DEVIATION 27 | ); 28 | console.log('- Deploying UNI-V2', uniswapPools[i].address); 29 | await uniAggregator.deployTransaction.wait(7); 30 | console.log( 31 | `- Deployed Uni-V2 aggregator ${uniswapPools[i].name} for pair %s at address %s`, 32 | uniswapPools[i].address, 33 | uniAggregator.address 34 | ); 35 | aggregatorAddresses.push(uniAggregator.address); 36 | 37 | await verifyContract( 38 | uniAggregator.address, 39 | 'contracts/lp-oracle-contracts/aggregators/UniswapV2PriceProvider.sol:UniswapV2PriceProvider', 40 | [ 41 | uniswapPools[i].address, 42 | uniswapPools[i].peg, 43 | uniswapPools[i].decimals, 44 | AAVE_ORACLE, 45 | MAX_PRICE_DEVIATION, 46 | ] 47 | ); 48 | } 49 | console.log('- Uniswap Addresses'); 50 | aggregatorAddresses.forEach((address, i) => { 51 | console.log(uniswapPools[i].name, address); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /scripts/deploy-price-aggregators/balancer.ts: -------------------------------------------------------------------------------- 1 | import Decimal from 'decimal.js'; 2 | import { BigNumberish, Signer } from 'ethers'; 3 | import { exit } from 'process'; 4 | import { BalancerSharedPoolPriceProvider__factory } from '../../typechain'; 5 | import { balancerMarkets, AAVE_ORACLE, MAX_PRICE_DEVIATION } from '../config'; 6 | import { verifyContract } from '../misc-helpers'; 7 | 8 | export async function setBptAggs(deployList: string[], signer: Signer) { 9 | const balancerPools = balancerMarkets.filter(({ name }) => { 10 | return deployList.includes(name); 11 | }); 12 | 13 | if (balancerPools.length !== deployList.length) { 14 | console.error('Deploy list mismatch, check names at scripts/config.ts'); 15 | return exit(1); 16 | } 17 | let aggregatorAddresses: string[] = []; 18 | const ether = '1000000000000000000'; 19 | 20 | for (let i = 0; i < balancerPools.length; i++) { 21 | const w1 = balancerPools[i].weights[0]; 22 | const w2 = balancerPools[i].weights[1]; 23 | const factor1 = new Decimal(w1).pow(w1); 24 | const factor2 = new Decimal(w2).pow(w2); 25 | const divisor = factor1.mul(factor2); 26 | const k = new Decimal(ether).div(divisor).toFixed(0); 27 | 28 | let matrix: BigNumberish[][] = []; 29 | 30 | for (let i = 1; i <= 20; i++) { 31 | matrix.push([ 32 | new Decimal(10).pow(i).times(ether).toFixed(0), 33 | new Decimal(10).pow(i).pow(w1).times(ether).toFixed(0), 34 | new Decimal(10).pow(i).pow(w2).times(ether).toFixed(0), 35 | ]); 36 | } 37 | 38 | const bptAggregator = await new BalancerSharedPoolPriceProvider__factory(signer).deploy( 39 | balancerPools[i].address, 40 | balancerPools[i].peg, 41 | balancerPools[i].decimals, 42 | AAVE_ORACLE, 43 | MAX_PRICE_DEVIATION, 44 | k, 45 | '100000000', 46 | matrix 47 | ); 48 | console.log('- Deploying BPT aggregator', balancerPools[i].address); 49 | await bptAggregator.deployTransaction.wait(7); 50 | console.log( 51 | '- Deployed BPT aggregator for pair %s at address %s', 52 | balancerPools[i].address, 53 | bptAggregator.address 54 | ); 55 | aggregatorAddresses.push(bptAggregator.address); 56 | 57 | await verifyContract( 58 | bptAggregator.address, 59 | 'contracts/lp-oracle-contracts/aggregators/BalancerSharedPoolPriceProvider.sol:BalancerSharedPoolPriceProvider', 60 | [ 61 | balancerPools[i].address, 62 | balancerPools[i].peg, 63 | balancerPools[i].decimals, 64 | AAVE_ORACLE, 65 | MAX_PRICE_DEVIATION, 66 | k, 67 | '100000000', 68 | matrix, 69 | ] 70 | ); 71 | } 72 | 73 | console.log('- Balancer Addresses'); 74 | aggregatorAddresses.forEach((address, i) => { 75 | console.log(balancerPools[i].name, address); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /scripts/config.ts: -------------------------------------------------------------------------------- 1 | export interface BalancerPoolInfo { 2 | name: string; 3 | address: string; 4 | weights: number[]; 5 | peg: boolean[]; 6 | decimals: number[]; 7 | } 8 | 9 | export interface UniswapPoolInfo { 10 | name: string; 11 | address: string; 12 | peg: boolean[]; 13 | decimals: number[]; 14 | } 15 | export const MAX_PRICE_DEVIATION = '50000000000000000'; 16 | 17 | export const AAVE_ORACLE = '0xA50ba011c48153De246E5192C8f9258A2ba79Ca9'; 18 | 19 | export const balancerMarkets = [ 20 | { 21 | name: 'BptWBTCWETH', 22 | address: '0x1efF8aF5D577060BA4ac8A29A13525bb0Ee2A3D5', 23 | peg: [true, false], 24 | decimals: [18, 8], 25 | weights: [0.5, 0.5], 26 | }, 27 | { 28 | name: 'BptBALWETH', 29 | address: '0x59A19D8c652FA0284f44113D0ff9aBa70bd46fB4', 30 | peg: [false, true], 31 | decimals: [18, 18], 32 | weights: [0.8, 0.2], 33 | }, 34 | ]; 35 | 36 | export const uniswapMarkets: UniswapPoolInfo[] = [ 37 | { 38 | name: 'UniDAIWETH', 39 | address: '0xa478c2975ab1ea89e8196811f51a7b7ade33eb11', 40 | peg: [false, true], 41 | decimals: [18, 18], 42 | }, 43 | { 44 | name: 'UniWBTCWETH', 45 | address: '0xBb2b8038a1640196FbE3e38816F3e67Cba72D940', 46 | peg: [false, true], 47 | decimals: [8, 18], 48 | }, 49 | { 50 | name: 'UniAAVEWETH', 51 | address: '0xDFC14d2Af169B0D36C4EFF567Ada9b2E0CAE044f', 52 | peg: [false, true], 53 | decimals: [18, 18], 54 | }, 55 | { 56 | name: 'UniBATWETH', 57 | address: '0xB6909B960DbbE7392D405429eB2b3649752b4838', 58 | peg: [false, true], 59 | decimals: [18, 18], 60 | }, 61 | { 62 | name: 'UniDAIUSDC', 63 | address: '0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5', 64 | peg: [false, false], 65 | decimals: [18, 6], 66 | }, 67 | { 68 | name: 'UniCRVWETH', 69 | address: '0x3dA1313aE46132A397D90d95B1424A9A7e3e0fCE', 70 | peg: [true, false], 71 | decimals: [18, 18], 72 | }, 73 | { 74 | name: 'UniLINKWETH', 75 | address: '0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974', 76 | peg: [false, true], 77 | decimals: [18, 18], 78 | }, 79 | { 80 | name: 'UniMKRWETH', 81 | address: '0xC2aDdA861F89bBB333c90c492cB837741916A225', 82 | peg: [false, true], 83 | decimals: [18, 18], 84 | }, 85 | { 86 | name: 'UniRENWETH', 87 | address: '0x8Bd1661Da98EBDd3BD080F0bE4e6d9bE8cE9858c', 88 | peg: [false, true], 89 | decimals: [18, 18], 90 | }, 91 | { 92 | name: 'UniSNXWETH', 93 | address: '0x43AE24960e5534731Fc831386c07755A2dc33D47', 94 | peg: [false, true], 95 | decimals: [18, 18], 96 | }, 97 | { 98 | name: 'UniUNIWETH', 99 | address: '0xd3d2E2692501A5c9Ca623199D38826e513033a17', 100 | peg: [false, true], 101 | decimals: [18, 18], 102 | }, 103 | { 104 | name: 'UniUSDCWETH', 105 | address: '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', 106 | peg: [false, true], 107 | decimals: [6, 18], 108 | }, 109 | { 110 | name: 'UniWBTCUSDC', 111 | address: '0x004375Dff511095CC5A197A54140a24eFEF3A416', 112 | peg: [false, false], 113 | decimals: [8, 6], 114 | }, 115 | { 116 | name: 'UniYFIWETH', 117 | address: '0x2fDbAdf3C4D5A8666Bc06645B8358ab803996E28', 118 | peg: [false, true], 119 | decimals: [18, 18], 120 | }, 121 | ]; 122 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/misc/BNum.sol: -------------------------------------------------------------------------------- 1 | // This program is free software: you can redistribute it and/or modify 2 | // it under the terms of the GNU General Public License as published by 3 | // the Free Software Foundation, either version 3 of the License, or 4 | // (at your option) any later version. 5 | 6 | // This program is distributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | // GNU General Public License for more details. 10 | 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | 14 | // From https://github.com/balancer-labs/balancer-core/blob/master/contracts/BNum.sol 15 | // SPDX-License-Identifier: GPL-3.0-or-later 16 | 17 | pragma solidity 0.6.12; 18 | 19 | import './BConst.sol'; 20 | 21 | contract BNum is BConst { 22 | function btoi(uint256 a) internal pure returns (uint256) { 23 | return a / BONE; 24 | } 25 | 26 | function bfloor(uint256 a) internal pure returns (uint256) { 27 | return btoi(a) * BONE; 28 | } 29 | 30 | function badd(uint256 a, uint256 b) internal pure returns (uint256) { 31 | uint256 c = a + b; 32 | require(c >= a, 'ERR_ADD_OVERFLOW'); 33 | return c; 34 | } 35 | 36 | function bsub(uint256 a, uint256 b) internal pure returns (uint256) { 37 | (uint256 c, bool flag) = bsubSign(a, b); 38 | require(!flag, 'ERR_SUB_UNDERFLOW'); 39 | return c; 40 | } 41 | 42 | function bsubSign(uint256 a, uint256 b) internal pure returns (uint256, bool) { 43 | if (a >= b) { 44 | return (a - b, false); 45 | } else { 46 | return (b - a, true); 47 | } 48 | } 49 | 50 | function bmul(uint256 a, uint256 b) internal pure returns (uint256) { 51 | uint256 c0 = a * b; 52 | require(a == 0 || c0 / a == b, 'ERR_MUL_OVERFLOW'); 53 | uint256 c1 = c0 + (BONE / 2); 54 | require(c1 >= c0, 'ERR_MUL_OVERFLOW'); 55 | uint256 c2 = c1 / BONE; 56 | return c2; 57 | } 58 | 59 | function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { 60 | require(b != 0, 'ERR_DIV_ZERO'); 61 | uint256 c0 = a * BONE; 62 | require(a == 0 || c0 / a == BONE, 'ERR_DIV_INTERNAL'); // bmul overflow 63 | uint256 c1 = c0 + (b / 2); 64 | require(c1 >= c0, 'ERR_DIV_INTERNAL'); // badd require 65 | uint256 c2 = c1 / b; 66 | return c2; 67 | } 68 | 69 | // DSMath.wpow 70 | function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { 71 | uint256 z = n % 2 != 0 ? a : BONE; 72 | 73 | for (n /= 2; n != 0; n /= 2) { 74 | a = bmul(a, a); 75 | 76 | if (n % 2 != 0) { 77 | z = bmul(z, a); 78 | } 79 | } 80 | return z; 81 | } 82 | 83 | // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). 84 | // Use `bpowi` for `b^e` and `bpowK` for k iterations 85 | // of approximation of b^0.w 86 | function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { 87 | require(base >= MIN_BPOW_BASE, 'ERR_BPOW_BASE_TOO_LOW'); 88 | require(base <= MAX_BPOW_BASE, 'ERR_BPOW_BASE_TOO_HIGH'); 89 | 90 | uint256 whole = bfloor(exp); 91 | uint256 remain = bsub(exp, whole); 92 | 93 | uint256 wholePow = bpowi(base, btoi(whole)); 94 | 95 | if (remain == 0) { 96 | return wholePow; 97 | } 98 | 99 | uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); 100 | return bmul(wholePow, partialResult); 101 | } 102 | 103 | function bpowApprox( 104 | uint256 base, 105 | uint256 exp, 106 | uint256 precision 107 | ) internal pure returns (uint256) { 108 | // term 0: 109 | uint256 a = exp; 110 | (uint256 x, bool xneg) = bsubSign(base, BONE); 111 | uint256 term = BONE; 112 | uint256 sum = term; 113 | bool negative = false; 114 | 115 | // term(k) = numer / denom 116 | // = (product(a - i - 1, i=1-->k) * x^k) / (k!) 117 | // each iteration, multiply previous term by (a-(k-1)) * x / k 118 | // continue until term is less than precision 119 | for (uint256 i = 1; term >= precision; i++) { 120 | uint256 bigK = i * BONE; 121 | (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); 122 | term = bmul(term, bmul(c, x)); 123 | term = bdiv(term, bigK); 124 | if (term == 0) break; 125 | 126 | if (xneg) negative = !negative; 127 | if (cneg) negative = !negative; 128 | if (negative) { 129 | sum = bsub(sum, term); 130 | } else { 131 | sum = badd(sum, term); 132 | } 133 | } 134 | 135 | return sum; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/aggregators/GUniPriceProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// GUniPriceProvider.sol 4 | 5 | // based heavily on GUniLPOracle.sol from MakerDAO 6 | // found here: https://github.com/makerdao/univ3-lp-oracle/blob/master/src/GUniLPOracle.sol 7 | // Copyright (C) 2017-2020 Maker Ecosystem Growth Holdings, INC. 8 | 9 | // This program is free software: you can redistribute it and/or modify 10 | // it under the terms of the GNU Affero General Public License as published by 11 | // the Free Software Foundation, either version 3 of the License, or 12 | // (at your option) any later version. 13 | // 14 | // This program is distributed in the hope that it will be useful, 15 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | // GNU Affero General Public License for more details. 18 | // 19 | // You should have received a copy of the GNU Affero General Public License 20 | // along with this program. If not, see . 21 | 22 | /////////////////////////////////////////////////////// 23 | // // 24 | // Methodology for Calculating LP Token Price // 25 | // // 26 | /////////////////////////////////////////////////////// 27 | 28 | // We derive the sqrtPriceX96 via Chainlink Oracles to prevent price manipulation in the pool: 29 | // 30 | // p0 = price of token0 in USD (18 decimal precision) 31 | // p1 = price of token1 in USD (18 decimal precision) 32 | // UNITS_0 = decimals of token0 33 | // UNITS_1 = decimals of token1 34 | // 35 | // token1/token0 = (p0 / 10^UNITS_0) / (p1 / 10^UNITS_1) [price ratio, Uniswap format] 36 | // = (p0 * 10^UNITS_1) / (p1 * 10^UNITS_0) 37 | // 38 | // sqrtPriceX96 = sqrt(token1/token0) * 2^96 [From Uniswap's definition] 39 | // = sqrt((p0 * 10^UNITS_1) / (p1 * 10^UNITS_0)) * 2^96 40 | // = sqrt((p0 * 10^UNITS_1) / (p1 * 10^UNITS_0)) * 2^48 * 2^48 41 | // = sqrt((p0 * 10^UNITS_1 * 2^96) / (p1 * 10^UNITS_0)) * 2^48 42 | // 43 | // Once we have the sqrtPriceX96 we can use that to compute the fair reserves for each token. 44 | // This part may be slightly subjective depending on the implementation, 45 | // but we expect token to provide something like getUnderlyingBalancesAtPrice(uint160 sqrtPriceX96) 46 | // which will forward our oracle derived `sqrtPriceX96` 47 | // to Uniswap's LiquidityAmounts.getAmountsForLiquidity(...) 48 | // This function will return the fair reserves for each token. 49 | // Vendor-specific logic is then used to tack any uninvested fees on top of those amounts. 50 | // 51 | // Once we have the fair reserves and the prices we can compute the token price by: 52 | // 53 | // Token Price = TVL / Token Supply 54 | // = (r0 * p0 + r1 * p1) / totalSupply 55 | 56 | 57 | pragma solidity =0.6.12; 58 | 59 | import {IExtendedAggregator} from "../interfaces/IExtendedAggregator.sol"; 60 | import {IGUniPool} from "../interfaces/IGUniPool.sol"; 61 | 62 | contract GUniPriceProvider is IExtendedAggregator { 63 | // solhint-disable private-vars-leading-underscore, var-name-mixedcase 64 | uint256 private immutable UNIT_0; 65 | uint256 private immutable UNIT_1; 66 | uint256 private immutable TO_WAD_0; 67 | uint256 private immutable TO_WAD_1; 68 | uint256 private immutable TO_WAD_ORACLE_0; 69 | uint256 private immutable TO_WAD_ORACLE_1; 70 | 71 | address public immutable pool; 72 | address public immutable priceFeed0; 73 | address public immutable priceFeed1; 74 | 75 | constructor(address _pool, address _feed0, address _feed1) public { 76 | uint256 dec0 = uint256(IExtendedAggregator(IGUniPool(_pool).token0()).decimals()); 77 | require(dec0 <= 18, "token0-dec-gt-18"); 78 | UNIT_0 = 10 ** dec0; 79 | TO_WAD_0 = 10 ** (18 - dec0); 80 | uint256 dec1 = uint256(IExtendedAggregator(IGUniPool(_pool).token1()).decimals()); 81 | require(dec1 <= 18, "token1-dec-gt-18"); 82 | UNIT_1 = 10 ** dec1; 83 | TO_WAD_1 = 10 ** (18 - dec1); 84 | uint256 decOracle0 = uint256(IExtendedAggregator(_feed0).decimals()); 85 | require(decOracle0 <= 18, "oracle0-dec-gt-18"); 86 | TO_WAD_ORACLE_0 = 10 ** (18 - decOracle0); 87 | uint256 decOracle1 = uint256(IExtendedAggregator(_feed1).decimals()); 88 | require(decOracle1 <= 18, "oracle1-dec-gt-18"); 89 | TO_WAD_ORACLE_1 = 10 ** (18 - decOracle1); 90 | pool = _pool; 91 | priceFeed0 = _feed0; 92 | priceFeed1 = _feed1; 93 | } 94 | 95 | function latestAnswer() external view override returns (int256) { 96 | // All Oracle prices are priced with 18 decimals against USD 97 | uint256 p0 = _getWADPrice(true); // Query token0 price from oracle (WAD) 98 | uint256 p1 = _getWADPrice(false); // Query token1 price from oracle (WAD) 99 | uint160 sqrtPriceX96 = 100 | _toUint160(_sqrt(_mul(_mul(p0, UNIT_1), (1 << 96)) / (_mul(p1, UNIT_0))) << 48); 101 | 102 | // Get balances of the tokens in the pool 103 | (uint256 r0, uint256 r1) = IGUniPool(pool).getUnderlyingBalancesAtPrice(sqrtPriceX96); 104 | require(r0 > 0 || r1 > 0, "invalid-balances"); 105 | uint256 totalSupply = IGUniPool(pool).totalSupply(); 106 | // Protect against precision errors with dust-levels of collateral 107 | require(totalSupply >= 1e9, "total-supply-too-small"); 108 | 109 | // Add the total value of each token together and divide by totalSupply to get unit price 110 | uint256 preq = _add( 111 | _mul(p0, _mul(r0, TO_WAD_0)), 112 | _mul(p1, _mul(r1, TO_WAD_1)) 113 | ) / totalSupply; 114 | 115 | return int256(preq); 116 | } 117 | 118 | function getToken() external view override returns (address) { 119 | return pool; 120 | } 121 | 122 | function getSubTokens() external view override returns (address[] memory) { 123 | address[] memory arr = new address[](2); 124 | arr[0] = IGUniPool(pool).token0(); 125 | arr[1] = IGUniPool(pool).token1(); 126 | return arr; 127 | } 128 | 129 | function getPlatformId() external pure override returns (IExtendedAggregator.PlatformId) { 130 | return IExtendedAggregator.PlatformId.GUni; 131 | } 132 | 133 | function getTokenType() external pure override returns (IExtendedAggregator.TokenType) { 134 | return IExtendedAggregator.TokenType.Complex; 135 | } 136 | 137 | function decimals() external pure override returns (uint8) { 138 | return 18; 139 | } 140 | 141 | function _getWADPrice(bool isToken0) 142 | internal 143 | view 144 | returns (uint256) 145 | { 146 | int256 price = IExtendedAggregator(isToken0 ? priceFeed0 : priceFeed1).latestAnswer(); 147 | require(price > 0, "negative-price"); 148 | return _mul(uint256(price), isToken0 ? TO_WAD_ORACLE_0 : TO_WAD_ORACLE_1); 149 | } 150 | 151 | function _add(uint256 _x, uint256 _y) internal pure returns (uint256 z) { 152 | require((z = _x + _y) >= _x, "add-overflow"); 153 | } 154 | function _sub(uint256 _x, uint256 _y) internal pure returns (uint256 z) { 155 | require((z = _x - _y) <= _x, "sub-underflow"); 156 | } 157 | function _mul(uint256 _x, uint256 _y) internal pure returns (uint256 z) { 158 | require(_y == 0 || (z = _x * _y) / _y == _x, "mul-overflow"); 159 | } 160 | function _toUint160(uint256 x) internal pure returns (uint160 z) { 161 | require((z = uint160(x)) == x, "uint160-overflow"); 162 | } 163 | 164 | // solhint-disable-next-line max-line-length 165 | // FROM https://github.com/abdk-consulting/abdk-libraries-solidity/blob/16d7e1dd8628dfa2f88d5dadab731df7ada70bdd/ABDKMath64x64.sol#L687 166 | // solhint-disable-next-line code-complexity 167 | function _sqrt(uint256 _x) private pure returns (uint128) { 168 | if (_x == 0) return 0; 169 | else { 170 | uint256 xx = _x; 171 | uint256 r = 1; 172 | if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; } 173 | if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; } 174 | if (xx >= 0x100000000) { xx >>= 32; r <<= 16; } 175 | if (xx >= 0x10000) { xx >>= 16; r <<= 8; } 176 | if (xx >= 0x100) { xx >>= 8; r <<= 4; } 177 | if (xx >= 0x10) { xx >>= 4; r <<= 2; } 178 | if (xx >= 0x8) { r <<= 1; } 179 | r = (r + _x / r) >> 1; 180 | r = (r + _x / r) >> 1; 181 | r = (r + _x / r) >> 1; 182 | r = (r + _x / r) >> 1; 183 | r = (r + _x / r) >> 1; 184 | r = (r + _x / r) >> 1; 185 | r = (r + _x / r) >> 1; // Seven iterations should be enough 186 | uint256 r1 = _x / r; 187 | return uint128 (r < r1 ? r : r1); 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/aggregators/UniswapV2PriceProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../interfaces/IUniswapV2Pair.sol"; 6 | import "../interfaces/IUniswapV2Factory.sol"; 7 | import "../interfaces/IPriceOracle.sol"; 8 | import "../interfaces/IExtendedAggregator.sol"; 9 | import "../misc/SafeMath.sol"; 10 | import "../misc/Math.sol"; 11 | 12 | /** @title UniswapV2PriceProvider 13 | * @notice Price provider for a Uniswap V2 pair token 14 | * It calculates the price using Chainlink as an external price source and the pair's tokens reserves using the weighted arithmetic mean formula. 15 | * If there is a price deviation, instead of the reserves, it uses a weighted geometric mean with the constant invariant K. 16 | */ 17 | 18 | contract UniswapV2PriceProvider is IExtendedAggregator { 19 | using SafeMath for uint256; 20 | 21 | IUniswapV2Pair public immutable pair; 22 | address[] public tokens; 23 | bool[] public isPeggedToEth; 24 | uint8[] public decimals; 25 | IPriceOracle immutable priceOracle; 26 | uint256 public immutable maxPriceDeviation; 27 | 28 | /** 29 | * UniswapV2PriceProvider constructor. 30 | * @param _pair Uniswap V2 pair address. 31 | * @param _isPeggedToEth For each token, true if it is pegged to ETH. 32 | * @param _decimals Number of decimals for each token. 33 | * @param _priceOracle Aave price oracle. 34 | * @param _maxPriceDeviation Threshold of spot prices deviation: 10ˆ16 represents a 1% deviation. 35 | */ 36 | constructor( 37 | IUniswapV2Pair _pair, 38 | bool[] memory _isPeggedToEth, 39 | uint8[] memory _decimals, 40 | IPriceOracle _priceOracle, 41 | uint256 _maxPriceDeviation 42 | ) public { 43 | require(_isPeggedToEth.length == 2, "ERR_INVALID_PEGGED_LENGTH"); 44 | require(_decimals.length == 2, "ERR_INVALID_DECIMALS_LENGTH"); 45 | require( 46 | _decimals[0] <= 18 && _decimals[1] <= 18, 47 | "ERR_INVALID_DECIMALS" 48 | ); 49 | require( 50 | address(_priceOracle) != address(0), 51 | "ERR_INVALID_PRICE_PROVIDER" 52 | ); 53 | require(_maxPriceDeviation < Math.BONE, "ERR_INVALID_PRICE_DEVIATION"); 54 | 55 | pair = _pair; 56 | //Get tokens 57 | tokens.push(_pair.token0()); 58 | tokens.push(_pair.token1()); 59 | isPeggedToEth = _isPeggedToEth; 60 | decimals = _decimals; 61 | priceOracle = _priceOracle; 62 | maxPriceDeviation = _maxPriceDeviation; 63 | } 64 | 65 | /** 66 | * Returns the token balance in ethers by multiplying its reserves with its price in ethers. 67 | * @param index Token index. 68 | * @param reserve Token reserves. 69 | */ 70 | function getEthBalanceByToken(uint256 index, uint112 reserve) 71 | internal 72 | view 73 | returns (uint256) 74 | { 75 | uint256 pi = 76 | isPeggedToEth[index] 77 | ? Math.BONE 78 | : uint256(priceOracle.getAssetPrice(tokens[index])); 79 | require(pi > 0, "ERR_NO_ORACLE_PRICE"); 80 | uint256 missingDecimals = uint256(18).sub(decimals[index]); 81 | uint256 bi = uint256(reserve).mul(10**(missingDecimals)); 82 | return Math.bmul(bi, pi); 83 | } 84 | 85 | /** 86 | * Returns true if there is a price deviation. 87 | * @param ethTotal_0 Total eth for token 0. 88 | * @param ethTotal_1 Total eth for token 1. 89 | */ 90 | function hasDeviation(uint256 ethTotal_0, uint256 ethTotal_1) 91 | internal 92 | view 93 | returns (bool) 94 | { 95 | //Check for a price deviation 96 | uint256 price_deviation = Math.bdiv(ethTotal_0, ethTotal_1); 97 | if ( 98 | price_deviation > (Math.BONE.add(maxPriceDeviation)) || 99 | price_deviation < (Math.BONE.sub(maxPriceDeviation)) 100 | ) { 101 | return true; 102 | } 103 | price_deviation = Math.bdiv(ethTotal_1, ethTotal_0); 104 | if ( 105 | price_deviation > (Math.BONE.add(maxPriceDeviation)) || 106 | price_deviation < (Math.BONE.sub(maxPriceDeviation)) 107 | ) { 108 | return true; 109 | } 110 | return false; 111 | } 112 | 113 | /** 114 | * Calculates the price of the pair token using the formula of arithmetic mean. 115 | * @param ethTotal_0 Total eth for token 0. 116 | * @param ethTotal_1 Total eth for token 1. 117 | */ 118 | function getArithmeticMean(uint256 ethTotal_0, uint256 ethTotal_1) 119 | internal 120 | view 121 | returns (uint256) 122 | { 123 | uint256 totalEth = ethTotal_0 + ethTotal_1; 124 | return Math.bdiv(totalEth, getTotalSupplyAtWithdrawal()); 125 | } 126 | 127 | /** 128 | * Calculates the price of the pair token using the formula of weighted geometric mean. 129 | * @param ethTotal_0 Total eth for token 0. 130 | * @param ethTotal_1 Total eth for token 1. 131 | */ 132 | function getWeightedGeometricMean(uint256 ethTotal_0, uint256 ethTotal_1) 133 | internal 134 | view 135 | returns (uint256) 136 | { 137 | uint256 square = Math.bsqrt(Math.bmul(ethTotal_0, ethTotal_1), true); 138 | return 139 | Math.bdiv( 140 | Math.bmul(Math.TWO_BONES, square), 141 | getTotalSupplyAtWithdrawal() 142 | ); 143 | } 144 | 145 | /** 146 | * Returns Uniswap V2 pair total supply at the time of withdrawal. 147 | */ 148 | function getTotalSupplyAtWithdrawal() 149 | private 150 | view 151 | returns (uint256 totalSupply) 152 | { 153 | totalSupply = pair.totalSupply(); 154 | address feeTo = 155 | IUniswapV2Factory(IUniswapV2Pair(pair).factory()).feeTo(); 156 | bool feeOn = feeTo != address(0); 157 | if (feeOn) { 158 | uint256 kLast = IUniswapV2Pair(pair).kLast(); 159 | if (kLast != 0) { 160 | (uint112 reserve_0, uint112 reserve_1, ) = pair.getReserves(); 161 | uint256 rootK = 162 | Math.bsqrt(uint256(reserve_0).mul(reserve_1), false); 163 | uint256 rootKLast = Math.bsqrt(kLast, false); 164 | if (rootK > rootKLast) { 165 | uint256 numerator = totalSupply.mul(rootK.sub(rootKLast)); 166 | uint256 denominator = rootK.mul(5).add(rootKLast); 167 | uint256 liquidity = numerator / denominator; 168 | totalSupply = totalSupply.add(liquidity); 169 | } 170 | } 171 | } 172 | } 173 | 174 | /** 175 | * Returns Uniswap V2 pair address. 176 | */ 177 | function getPair() external view returns (IUniswapV2Pair) { 178 | return pair; 179 | } 180 | 181 | /** 182 | * Returns all tokens. 183 | */ 184 | function getTokens() external view returns (address[] memory) { 185 | return tokens; 186 | } 187 | 188 | /** 189 | * @dev Returns the LP shares token 190 | * @return address of the LP shares token 191 | */ 192 | function getToken() external view override returns (address) { 193 | return address(pair); 194 | } 195 | 196 | /** 197 | * @dev Returns token type for categorization 198 | * @return uint256 1 = Simple (Native or plain ERC20 tokens like DAI), 2 = Complex (LP Tokens, Staked tokens) 199 | */ 200 | function getTokenType() 201 | external 202 | pure 203 | override 204 | returns (IExtendedAggregator.TokenType) 205 | { 206 | return IExtendedAggregator.TokenType.Complex; 207 | } 208 | 209 | /** 210 | * @dev Returns the number of tokens that composes the LP shares 211 | * @return address[] memory of token addresses 212 | */ 213 | function getSubTokens() external view override returns (address[] memory) { 214 | return tokens; 215 | } 216 | 217 | /** 218 | * @dev Returns the platform id to categorize the price aggregator 219 | * @return uint256 1 = Uniswap, 2 = Balancer 220 | */ 221 | function getPlatformId() 222 | external 223 | pure 224 | override 225 | returns (IExtendedAggregator.PlatformId) 226 | { 227 | return IExtendedAggregator.PlatformId.Uniswap; 228 | } 229 | 230 | /** 231 | * @dev Returns the pair's token price. 232 | * It calculates the price using Chainlink as an external price source and the pair's tokens reserves using the arithmetic mean formula. 233 | * If there is a price deviation, instead of the reserves, it uses a weighted geometric mean with constant invariant K. 234 | * @return int256 price 235 | */ 236 | function latestAnswer() external view override returns (int256) { 237 | //Get token reserves in ethers 238 | (uint112 reserve_0, uint112 reserve_1, ) = pair.getReserves(); 239 | uint256 ethTotal_0 = getEthBalanceByToken(0, reserve_0); 240 | uint256 ethTotal_1 = getEthBalanceByToken(1, reserve_1); 241 | 242 | if (hasDeviation(ethTotal_0, ethTotal_1)) { 243 | //Calculate the weighted geometric mean 244 | return int256(getWeightedGeometricMean(ethTotal_0, ethTotal_1)); 245 | } else { 246 | //Calculate the arithmetic mean 247 | return int256(getArithmeticMean(ethTotal_0, ethTotal_1)); 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /contracts/lp-oracle-contracts/aggregators/BalancerSharedPoolPriceProvider.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: agpl-3.0 2 | pragma solidity 0.6.12; 3 | pragma experimental ABIEncoderV2; 4 | 5 | import "../interfaces/BPool.sol"; 6 | import "../interfaces/IExtendedAggregator.sol"; 7 | import "../interfaces/IPriceOracle.sol"; 8 | import "../misc/BNum.sol"; 9 | 10 | /** @title BalancerSharedPoolPriceProvider 11 | * @notice Price provider for a balancer pool token 12 | * It calculates the price using Chainlink as an external price source and the pool's tokens balances using the weighted arithmetic mean formula. 13 | * If there is a price deviation, instead of the balances, it uses a weighted geometric mean with the token's weights and constant value function V. 14 | */ 15 | 16 | contract BalancerSharedPoolPriceProvider is BNum, IExtendedAggregator { 17 | BPool public pool; 18 | address[] public tokens; 19 | uint256[] public weights; 20 | bool[] public isPeggedToEth; 21 | uint8[] public decimals; 22 | IPriceOracle public priceOracle; 23 | uint256 public immutable maxPriceDeviation; 24 | uint256 internal immutable K; 25 | uint256 internal immutable powerPrecision; 26 | uint256[][] internal approximationMatrix; 27 | 28 | /** 29 | * BalancerSharedPoolPriceProvider constructor. 30 | * @param _pool Balancer pool address. 31 | * @param _isPeggedToEth For each token, true if it is pegged to ETH (token order determined by pool.getFinalTokens()). 32 | * @param _decimals Number of decimals for each token (token order determined by pool.getFinalTokens()). 33 | * @param _priceOracle Aave price oracle. 34 | * @param _maxPriceDeviation Threshold of spot prices deviation: 10ˆ16 represents a 1% deviation. 35 | * @param _K //Constant K = 1 / (w1ˆw1 * .. * wn^wn) 36 | * @param _powerPrecision //Precision for power math function. 37 | * @param _approximationMatrix //Approximation matrix for gas optimization. 38 | */ 39 | constructor( 40 | BPool _pool, 41 | bool[] memory _isPeggedToEth, 42 | uint8[] memory _decimals, 43 | IPriceOracle _priceOracle, 44 | uint256 _maxPriceDeviation, 45 | uint256 _K, 46 | uint256 _powerPrecision, 47 | uint256[][] memory _approximationMatrix 48 | ) public { 49 | pool = _pool; 50 | //Get token list 51 | tokens = pool.getFinalTokens(); //This already checks for pool finalized 52 | uint256 length = tokens.length; 53 | //Validate contructor params 54 | require(length >= 2 && length <= 3, "ERR_INVALID_POOL_TOKENS_NUMBER"); 55 | require(_isPeggedToEth.length == length, "ERR_INVALID_PEGGED_LENGTH"); 56 | require(_decimals.length == length, "ERR_INVALID_DECIMALS_LENGTH"); 57 | for (uint8 i = 0; i < length; i++) { 58 | require(_decimals[i] <= 18, "ERR_INVALID_DECIMALS"); 59 | } 60 | require( 61 | _approximationMatrix.length == 0 || 62 | _approximationMatrix[0].length == length + 1, 63 | "ERR_INVALID_APPROX_MATRIX" 64 | ); 65 | require(_maxPriceDeviation < BONE, "ERR_INVALID_PRICE_DEVIATION"); 66 | require( 67 | _powerPrecision >= 1 && _powerPrecision <= BONE, 68 | "ERR_INVALID_POWER_PRECISION" 69 | ); 70 | require( 71 | address(_priceOracle) != address(0), 72 | "ERR_INVALID_PRICE_PROVIDER" 73 | ); 74 | //Get token normalized weights 75 | for (uint8 i = 0; i < length; i++) { 76 | weights.push(pool.getNormalizedWeight(tokens[i])); 77 | } 78 | isPeggedToEth = _isPeggedToEth; 79 | decimals = _decimals; 80 | priceOracle = _priceOracle; 81 | maxPriceDeviation = _maxPriceDeviation; 82 | K = _K; 83 | powerPrecision = _powerPrecision; 84 | approximationMatrix = _approximationMatrix; 85 | } 86 | 87 | /** 88 | * Returns the token balance in ethers by multiplying its balance with its price in ethers. 89 | * @param index Token index. 90 | */ 91 | function getEthBalanceByToken(uint256 index) 92 | internal 93 | view 94 | returns (uint256) 95 | { 96 | uint256 pi = 97 | isPeggedToEth[index] 98 | ? BONE 99 | : uint256(priceOracle.getAssetPrice(tokens[index])); 100 | require(pi > 0, "ERR_NO_ORACLE_PRICE"); 101 | uint256 missingDecimals = 18 - decimals[index]; 102 | uint256 bi = 103 | bmul(pool.getBalance(tokens[index]), BONE * 10**(missingDecimals)); 104 | return bmul(bi, pi); 105 | } 106 | 107 | /** 108 | * Using the matrix approximation, returns a near base and exponentiation result, for num ^ weights[index] 109 | * @param index Token index. 110 | * @param num Base to approximate. 111 | */ 112 | function getClosestBaseAndExponetation(uint256 index, uint256 num) 113 | internal 114 | view 115 | returns (uint256, uint256) 116 | { 117 | uint256 length = approximationMatrix.length; 118 | uint256 k = index + 1; 119 | for (uint8 i = 0; i < length; i++) { 120 | if (approximationMatrix[i][0] >= num) { 121 | return (approximationMatrix[i][0], approximationMatrix[i][k]); 122 | } 123 | } 124 | return (0, 0); 125 | } 126 | 127 | /** 128 | * Returns true if there is a price deviation. 129 | * @param ethTotals Balance of each token in ethers. 130 | */ 131 | function hasDeviation(uint256[] memory ethTotals) 132 | internal 133 | view 134 | returns (bool) 135 | { 136 | //Check for a price deviation 137 | uint256 length = tokens.length; 138 | for (uint8 i = 0; i < length; i++) { 139 | for (uint8 o = 0; o < length; o++) { 140 | if (i != o) { 141 | uint256 price_deviation = 142 | bdiv( 143 | bdiv(ethTotals[i], weights[i]), 144 | bdiv(ethTotals[o], weights[o]) 145 | ); 146 | if ( 147 | price_deviation > (BONE + maxPriceDeviation) || 148 | price_deviation < (BONE - maxPriceDeviation) 149 | ) { 150 | return true; 151 | } 152 | } 153 | } 154 | } 155 | return false; 156 | } 157 | 158 | /** 159 | * Calculates the price of the pool token using the formula of weighted arithmetic mean. 160 | * @param ethTotals Balance of each token in ethers. 161 | */ 162 | function getArithmeticMean(uint256[] memory ethTotals) 163 | internal 164 | view 165 | returns (uint256) 166 | { 167 | uint256 totalEth = 0; 168 | uint256 length = tokens.length; 169 | for (uint8 i = 0; i < length; i++) { 170 | totalEth = badd(totalEth, ethTotals[i]); 171 | } 172 | return bdiv(totalEth, pool.totalSupply()); 173 | } 174 | 175 | /** 176 | * Returns the weighted token balance in ethers by calculating the balance in ether of the token to the power of its weight. 177 | * @param index Token index. 178 | */ 179 | function getWeightedEthBalanceByToken(uint256 index, uint256 ethTotal) 180 | internal 181 | view 182 | returns (uint256) 183 | { 184 | uint256 weight = weights[index]; 185 | (uint256 base, uint256 result) = 186 | getClosestBaseAndExponetation(index, ethTotal); 187 | if (base == 0 || ethTotal < MAX_BPOW_BASE) { 188 | if (ethTotal < MAX_BPOW_BASE) { 189 | return bpowApprox(ethTotal, weight, powerPrecision); 190 | } else { 191 | return 192 | bmul( 193 | ethTotal, 194 | bpowApprox( 195 | bdiv(BONE, ethTotal), 196 | (BONE - weight), 197 | powerPrecision 198 | ) 199 | ); 200 | } 201 | } else { 202 | return 203 | bmul( 204 | result, 205 | bpowApprox(bdiv(ethTotal, base), weight, powerPrecision) 206 | ); 207 | } 208 | } 209 | 210 | /** 211 | * Calculates the price of the pool token using the formula of weighted geometric mean. 212 | * @param ethTotals Balance of each token in ethers. 213 | */ 214 | function getWeightedGeometricMean(uint256[] memory ethTotals) 215 | internal 216 | view 217 | returns (uint256) 218 | { 219 | uint256 mult = BONE; 220 | uint256 length = tokens.length; 221 | for (uint256 i = 0; i < length; i++) { 222 | mult = bmul(mult, getWeightedEthBalanceByToken(i, ethTotals[i])); 223 | } 224 | return bdiv(bmul(mult, K), pool.totalSupply()); 225 | } 226 | 227 | /** 228 | * Returns Balancer pool address. 229 | */ 230 | function getPool() external view returns (BPool) { 231 | return pool; 232 | } 233 | 234 | /** 235 | * Returns all tokens's weights. 236 | */ 237 | function getWeights() external view returns (uint256[] memory) { 238 | return weights; 239 | } 240 | 241 | /** 242 | * @dev Returns token type for categorization 243 | * @return uint256 1 = Simple (Native or plain ERC20 tokens like DAI), 2 = Complex (LP Tokens, Staked tokens) 244 | */ 245 | function getTokenType() 246 | external 247 | pure 248 | override 249 | returns (IExtendedAggregator.TokenType) 250 | { 251 | return IExtendedAggregator.TokenType.Complex; 252 | } 253 | 254 | /** 255 | * @dev Returns the number of tokens that composes the LP shares 256 | * @return address[] memory of token addresses 257 | */ 258 | function getSubTokens() external view override returns (address[] memory) { 259 | return tokens; 260 | } 261 | 262 | /** 263 | * @dev Returns the LP shares token 264 | * @return address of the LP shares token 265 | */ 266 | function getToken() external view override returns (address) { 267 | return address(pool); 268 | } 269 | 270 | /** 271 | * @dev Returns the platform id to categorize the price aggregator 272 | * @return uint256 1 = Uniswap, 2 = Balancer 273 | */ 274 | function getPlatformId() 275 | external 276 | pure 277 | override 278 | returns (IExtendedAggregator.PlatformId) 279 | { 280 | return IExtendedAggregator.PlatformId.Balancer; 281 | } 282 | 283 | /** 284 | * @dev Returns the pool's token price. 285 | * It calculates the price using Chainlink as an external price source and the pool's tokens balances using the weighted arithmetic mean formula. 286 | * If there is a price deviation, instead of the balances, it uses a weighted geometric mean with the token's weights and constant value function V. 287 | * @return int256 price 288 | */ 289 | function latestAnswer() external view override returns (int256) { 290 | //Get token balances in ethers 291 | uint256[] memory ethTotals = new uint256[](tokens.length); 292 | uint256 length = tokens.length; 293 | for (uint256 i = 0; i < length; i++) { 294 | ethTotals[i] = getEthBalanceByToken(i); 295 | } 296 | 297 | if (hasDeviation(ethTotals)) { 298 | //Calculate the weighted geometric mean 299 | return int256(getWeightedGeometricMean(ethTotals)); 300 | } else { 301 | //Calculate the weighted arithmetic mean 302 | return int256(getArithmeticMean(ethTotals)); 303 | } 304 | } 305 | } 306 | --------------------------------------------------------------------------------