From a0fdeed91cf572ab37f3fd36de02003b8c2f658e Mon Sep 17 00:00:00 2001 From: Joey Santoro Date: Sat, 1 Jan 2022 16:55:03 -0800 Subject: [PATCH 1/3] factory oracle --- .../external/curve/ICurveFactoryRegistry.sol | 7 ++ .../CurveFactoryLpTokenPriceOracle.sol | 93 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 contracts/external/curve/ICurveFactoryRegistry.sol create mode 100644 contracts/oracles/CurveFactoryLpTokenPriceOracle.sol diff --git a/contracts/external/curve/ICurveFactoryRegistry.sol b/contracts/external/curve/ICurveFactoryRegistry.sol new file mode 100644 index 0000000..b9832f2 --- /dev/null +++ b/contracts/external/curve/ICurveFactoryRegistry.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.6.12; + +interface ICurveRegistry { + function get_n_coins(address lp) external view returns (uint); + function get_coins(address pool) external view returns (address[4] memory); +} diff --git a/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol b/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol new file mode 100644 index 0000000..fd2eaf7 --- /dev/null +++ b/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.6.12; + +import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + +import "../external/compound/PriceOracle.sol"; +import "../external/compound/CToken.sol"; +import "../external/compound/CErc20.sol"; + +import "../external/curve/ICurveFactoryRegistry.sol"; +import "../external/curve/ICurvePool.sol"; + +import "./BasePriceOracle.sol"; + +/** + * @title CurveFactoryLpTokenPriceOracle + * @author David Lucid (https://github.com/davidlucid) + * @notice CurveFactoryLpTokenPriceOracle is a price oracle for Curve LP tokens (using the sender as a root oracle). + * @dev Implements the `PriceOracle` interface used by Fuse pools (and Compound v2). + */ +contract CurveFactoryLpTokenPriceOracle is PriceOracle, BasePriceOracle { + using SafeMathUpgradeable for uint256; + + /** + * @notice Get the LP token price price for an underlying token address. + * @param underlying The underlying token address for which to get the price (set to zero address for ETH). + * @return Price denominated in ETH (scaled by 1e18). + */ + function price(address underlying) external override view returns (uint) { + return _price(underlying); + } + + /** + * @notice Returns the price in ETH of the token underlying `cToken`. + * @dev Implements the `PriceOracle` interface for Fuse pools (and Compound v2). + * @return Price in ETH of the token underlying `cToken`, scaled by `10 ** (36 - underlyingDecimals)`. + */ + function getUnderlyingPrice(CToken cToken) external override view returns (uint) { + address underlying = CErc20(address(cToken)).underlying(); + // Comptroller needs prices to be scaled by 1e(36 - decimals) + // Since `_price` returns prices scaled by 18 decimals, we must scale them by 1e(36 - 18 - decimals) + return _price(underlying).mul(1e18).div(10 ** uint256(ERC20Upgradeable(underlying).decimals())); + } + + /** + * @dev Fetches the fair LP token/ETH price from Curve, with 18 decimals of precision. + * Source: https://github.com/AlphaFinanceLab/homora-v2/blob/master/contracts/oracle/CurveOracle.sol + * @param pool pool LP token + */ + function _price(address pool) internal view returns (uint) { + address[] memory tokens = underlyingTokens[pool]; + require(tokens.length != 0, "LP token is not registered."); + uint256 minPx = uint256(-1); + uint256 n = tokens.length; + + for (uint256 i = 0; i < n; i++) { + address ulToken = tokens[i]; + uint256 tokenPx = BasePriceOracle(msg.sender).price(ulToken); + if (tokenPx < minPx) minPx = tokenPx; + } + + require(minPx != uint256(-1), "No minimum underlying token price found."); + return minPx.mul(ICurvePool(pool).get_virtual_price()).div(1e18); // Use min underlying token prices + } + + /** + * @dev The Curve registry. + */ + ICurveFactoryRegistry public constant registry = ICurveFactoryRegistry(0xB9fC157394Af804a3578134A6585C0dc9cc990d4); + + /** + * @dev Maps Curve LP token addresses to underlying token addresses. + */ + mapping(address => address[]) public underlyingTokens; + + /** + * @dev Maps Curve LP token addresses to pool addresses. + */ + mapping(address => address) public poolOf; + + /** + * @dev Register the pool given LP token address and set the pool info. + * Source: https://github.com/AlphaFinanceLab/homora-v2/blob/master/contracts/oracle/CurveOracle.sol + * @param pool pool LP token + */ + function registerPool(address pool) external { + uint n = registry.get_n_coins(pool); + require(n != 0, "n"); + address[4] memory tokens = registry.get_coins(pool); + for (uint256 i = 0; i < n; i++) underlyingTokens[pool].push(tokens[i]); + } +} From 5b0804c0075981de35357a75478f1f2f6a55982b Mon Sep 17 00:00:00 2001 From: Joey Santoro Date: Sat, 1 Jan 2022 17:08:29 -0800 Subject: [PATCH 2/3] factory --- contracts/external/curve/ICurveFactoryRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/external/curve/ICurveFactoryRegistry.sol b/contracts/external/curve/ICurveFactoryRegistry.sol index b9832f2..8ae95e2 100644 --- a/contracts/external/curve/ICurveFactoryRegistry.sol +++ b/contracts/external/curve/ICurveFactoryRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity 0.6.12; -interface ICurveRegistry { +interface ICurveFactoryRegistry { function get_n_coins(address lp) external view returns (uint); function get_coins(address pool) external view returns (address[4] memory); } From 9542b3ee106ba0c0b65a66a2bfddcc5b1a7cf531 Mon Sep 17 00:00:00 2001 From: sriyantra <85423375+sriyantra@users.noreply.github.com> Date: Tue, 22 Feb 2022 11:19:00 -0800 Subject: [PATCH 3/3] check for metapool coins --- contracts/external/curve/ICurveFactoryRegistry.sol | 1 + contracts/oracles/CurveFactoryLpTokenPriceOracle.sol | 1 + 2 files changed, 2 insertions(+) diff --git a/contracts/external/curve/ICurveFactoryRegistry.sol b/contracts/external/curve/ICurveFactoryRegistry.sol index 8ae95e2..96f0ccd 100644 --- a/contracts/external/curve/ICurveFactoryRegistry.sol +++ b/contracts/external/curve/ICurveFactoryRegistry.sol @@ -4,4 +4,5 @@ pragma solidity 0.6.12; interface ICurveFactoryRegistry { function get_n_coins(address lp) external view returns (uint); function get_coins(address pool) external view returns (address[4] memory); + function get_meta_n_coins(address pool) external view returns (uint, uint); } diff --git a/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol b/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol index fd2eaf7..4573cfe 100644 --- a/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol +++ b/contracts/oracles/CurveFactoryLpTokenPriceOracle.sol @@ -86,6 +86,7 @@ contract CurveFactoryLpTokenPriceOracle is PriceOracle, BasePriceOracle { */ function registerPool(address pool) external { uint n = registry.get_n_coins(pool); + if (n == 0) (n, ) = registry.get_meta_n_coins(pool); require(n != 0, "n"); address[4] memory tokens = registry.get_coins(pool); for (uint256 i = 0; i < n; i++) underlyingTokens[pool].push(tokens[i]);