├── .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 |
--------------------------------------------------------------------------------