diff --git a/contracts/oracles/SingleAssetLPPriceFeed.sol b/contracts/oracles/SingleAssetLPPriceFeed.sol index 8908959..403293b 100644 --- a/contracts/oracles/SingleAssetLPPriceFeed.sol +++ b/contracts/oracles/SingleAssetLPPriceFeed.sol @@ -14,10 +14,13 @@ abstract contract SingleAssetLPPriceFeed is LPPriceFeed { uint32 public immutable stalenessPeriod; bool public immutable skipCheck; - constructor(address addressProvider, address _lpToken, address _priceFeed, uint32 _stalenessPeriod) - LPPriceFeed(addressProvider, _lpToken, _lpToken) - nonZeroAddress(_priceFeed) - { + constructor( + address addressProvider, + address _lpToken, + address _lpContract, + address _priceFeed, + uint32 _stalenessPeriod + ) LPPriceFeed(addressProvider, _lpToken, _lpContract) nonZeroAddress(_priceFeed) { priceFeed = _priceFeed; stalenessPeriod = _stalenessPeriod; skipCheck = _validatePriceFeed(_priceFeed, _stalenessPeriod); diff --git a/contracts/oracles/aave/WrappedAaveV2PriceFeed.sol b/contracts/oracles/aave/WrappedAaveV2PriceFeed.sol index 2fcd308..78b54ca 100644 --- a/contracts/oracles/aave/WrappedAaveV2PriceFeed.sol +++ b/contracts/oracles/aave/WrappedAaveV2PriceFeed.sol @@ -14,7 +14,7 @@ contract WrappedAaveV2PriceFeed is SingleAssetLPPriceFeed { PriceFeedType public constant override priceFeedType = PriceFeedType.WRAPPED_AAVE_V2_ORACLE; constructor(address addressProvider, address _waToken, address _priceFeed, uint32 _stalenessPeriod) - SingleAssetLPPriceFeed(addressProvider, _waToken, _priceFeed, _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _waToken, _waToken, _priceFeed, _stalenessPeriod) { _initLimiter(); } diff --git a/contracts/oracles/compound/CompoundV2PriceFeed.sol b/contracts/oracles/compound/CompoundV2PriceFeed.sol index a60ca4b..1911f5a 100644 --- a/contracts/oracles/compound/CompoundV2PriceFeed.sol +++ b/contracts/oracles/compound/CompoundV2PriceFeed.sol @@ -14,7 +14,7 @@ contract CompoundV2PriceFeed is SingleAssetLPPriceFeed { PriceFeedType public constant override priceFeedType = PriceFeedType.COMPOUND_V2_ORACLE; constructor(address addressProvider, address _cToken, address _priceFeed, uint32 _stalenessPeriod) - SingleAssetLPPriceFeed(addressProvider, _cToken, _priceFeed, _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _cToken, _cToken, _priceFeed, _stalenessPeriod) { _initLimiter(); } diff --git a/contracts/oracles/curve/CurveUSDPriceFeed.sol b/contracts/oracles/curve/CurveUSDPriceFeed.sol new file mode 100644 index 0000000..31860bc --- /dev/null +++ b/contracts/oracles/curve/CurveUSDPriceFeed.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 +// Gearbox Protocol. Generalized leverage for DeFi protocols +// (c) Gearbox Foundation, 2023. +pragma solidity ^0.8.17; + +import {SingleAssetLPPriceFeed} from "../SingleAssetLPPriceFeed.sol"; +import {PriceFeedType} from "@gearbox-protocol/sdk/contracts/PriceFeedType.sol"; +import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol"; + +interface IPool { + function price_oracle() external view returns (uint256); +} + +/// @title crvUSD price feed +/// @notice Computes crvUSD price as product of crvUSD-USDC stableswap pool exchange rate and USDC price feed. +/// While crvUSD is not an LP token itself, the pricing logic is fairly similar, so existing infrastructure +/// is reused. Particularly, the same bounding mechanism is applied to the pool exchange rate. +contract CurveUSDPriceFeed is SingleAssetLPPriceFeed { + uint256 public constant override version = 3_00; + PriceFeedType public constant override priceFeedType = PriceFeedType.CURVE_USD_ORACLE; + + constructor(address addressProvider, address _crvUSD, address _pool, address _priceFeed, uint32 _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _crvUSD, _pool, _priceFeed, _stalenessPeriod) + { + _initLimiter(); + } + + function getLPExchangeRate() public view override returns (uint256) { + return IPool(lpContract).price_oracle(); + } + + function getScale() public pure override returns (uint256) { + return WAD; + } +} diff --git a/contracts/oracles/erc4626/ERC4626PriceFeed.sol b/contracts/oracles/erc4626/ERC4626PriceFeed.sol index 38503ca..9220e34 100644 --- a/contracts/oracles/erc4626/ERC4626PriceFeed.sol +++ b/contracts/oracles/erc4626/ERC4626PriceFeed.sol @@ -20,7 +20,7 @@ contract ERC4626PriceFeed is SingleAssetLPPriceFeed { uint256 immutable _assetUnit; constructor(address addressProvider, address _vault, address _assetPriceFeed, uint32 _stalenessPeriod) - SingleAssetLPPriceFeed(addressProvider, _vault, _assetPriceFeed, _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _vault, _vault, _assetPriceFeed, _stalenessPeriod) { _shareUnit = 10 ** IERC4626(_vault).decimals(); _assetUnit = 10 ** ERC20(IERC4626(_vault).asset()).decimals(); diff --git a/contracts/oracles/lido/WstETHPriceFeed.sol b/contracts/oracles/lido/WstETHPriceFeed.sol index e1f81c1..bb4fd35 100644 --- a/contracts/oracles/lido/WstETHPriceFeed.sol +++ b/contracts/oracles/lido/WstETHPriceFeed.sol @@ -14,7 +14,7 @@ contract WstETHPriceFeed is SingleAssetLPPriceFeed { PriceFeedType public constant override priceFeedType = PriceFeedType.WSTETH_ORACLE; constructor(address addressProvider, address _wstETH, address _priceFeed, uint32 _stalenessPeriod) - SingleAssetLPPriceFeed(addressProvider, _wstETH, _priceFeed, _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _wstETH, _wstETH, _priceFeed, _stalenessPeriod) { _initLimiter(); } diff --git a/contracts/oracles/yearn/YearnPriceFeed.sol b/contracts/oracles/yearn/YearnPriceFeed.sol index 20ecd84..a42b82f 100644 --- a/contracts/oracles/yearn/YearnPriceFeed.sol +++ b/contracts/oracles/yearn/YearnPriceFeed.sol @@ -16,7 +16,7 @@ contract YearnPriceFeed is SingleAssetLPPriceFeed { uint256 immutable _scale; constructor(address addressProvider, address _yVault, address _priceFeed, uint32 _stalenessPeriod) - SingleAssetLPPriceFeed(addressProvider, _yVault, _priceFeed, _stalenessPeriod) + SingleAssetLPPriceFeed(addressProvider, _yVault, _yVault, _priceFeed, _stalenessPeriod) { _scale = 10 ** IYVault(_yVault).decimals(); _initLimiter(); diff --git a/contracts/test/suites/PriceFeedDeployer.sol b/contracts/test/suites/PriceFeedDeployer.sol index 9ee1375..0386c19 100644 --- a/contracts/test/suites/PriceFeedDeployer.sol +++ b/contracts/test/suites/PriceFeedDeployer.sol @@ -16,6 +16,7 @@ import { SingeTokenPriceFeedData, CompositePriceFeedData, CurvePriceFeedData, + CrvUsdPriceFeedData, GenericLPPriceFeedData, TheSamePriceFeedData, RedStonePriceFeedData @@ -33,6 +34,7 @@ import {WstETHPriceFeed} from "../../oracles/lido/WstETHPriceFeed.sol"; import {CompositePriceFeed} from "../../oracles/CompositePriceFeed.sol"; import {BoundedPriceFeed} from "../../oracles/BoundedPriceFeed.sol"; +import {CurveUSDPriceFeed} from "../../oracles/curve/CurveUSDPriceFeed.sol"; import {CurveStableLPPriceFeed} from "../../oracles/curve/CurveStableLPPriceFeed.sol"; import {CurveCryptoLPPriceFeed} from "../../oracles/curve/CurveCryptoLPPriceFeed.sol"; @@ -83,8 +85,9 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { } } } + + // BOUNDED PRICE FEEDS { - // BOUNDED_PRICE_FEEDS BoundedPriceFeedData[] memory boundedPriceFeeds = boundedPriceFeedsByNetwork[chainId]; len = boundedPriceFeeds.length; unchecked { @@ -110,8 +113,9 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { } } } + + // COMPOSITE PRICE FEEDS { - // COMPOSITE_PRICE_FEEDS CompositePriceFeedData[] memory compositePriceFeeds = compositePriceFeedsByNetwork[chainId]; len = compositePriceFeeds.length; unchecked { @@ -147,6 +151,7 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { } } } + // ZERO PRICE FEEDS { SingeTokenPriceFeedData[] memory zeroPriceFeeds = zeroPriceFeedsByNetwork[chainId]; @@ -158,15 +163,43 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { address token = tokenTestSuite.addressOf(zeroPriceFeeds[i].token); if (token != address(0)) { setPriceFeed(token, zeroPF); - - vm.label(zeroPF, "ZERO PRICEFEED"); } } } + vm.label(zeroPF, "ZERO PRICEFEED"); + } + } + + // crvUSD PRICE FEEDS + { + CrvUsdPriceFeedData[] memory crvUSDPriceFeeds = crvUSDPriceFeedsByNetwork[chainId]; + len = crvUSDPriceFeeds.length; + unchecked { + for (uint256 i; i < len; ++i) { + Tokens t = crvUSDPriceFeeds[i].token; + address token = tokenTestSuite.addressOf(t); + if (token == address(0)) continue; + + address underlying = tokenTestSuite.addressOf(crvUSDPriceFeeds[i].underlying); + address pf = address( + new CurveUSDPriceFeed( + addressProvider, + token, + supportedContracts.addressOf(crvUSDPriceFeeds[i].pool), + priceFeeds[underlying], + stalenessPeriods[underlying] + ) + ); + + setPriceFeed(token, pf); + + string memory description = string(abi.encodePacked("PRICEFEED_", tokenTestSuite.symbols(t))); + vm.label(pf, description); + } } } - // CURVE PRICE FEEDS + // CURVE STABLE PRICE FEEDS { CurvePriceFeedData[] memory curvePriceFeeds = curvePriceFeedsByNetwork[chainId]; len = curvePriceFeeds.length; @@ -326,7 +359,7 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { } } - // WSTETH_PRICE_FEED + // wstETH PRICE FEED unchecked { Tokens t = wstethPriceFeedByNetwork[chainId].token; if (t != Tokens.NO_TOKEN) { @@ -379,6 +412,7 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive { } } } + // COMPOUND V2 PRICE FEEDS GenericLPPriceFeedData[] memory compoundV2PriceFeeds = compoundV2PriceFeedsByNetwork[chainId]; len = compoundV2PriceFeeds.length; diff --git a/package.json b/package.json index 66c1b1a..8b185cf 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "dependencies": { "@gearbox-protocol/core-v2": "^1.19.0-base.7", "@gearbox-protocol/core-v3": "^1.30.0", - "@gearbox-protocol/sdk": "^2.1.14", + "@gearbox-protocol/sdk": "^2.1.16", "@redstone-finance/evm-connector": "^0.2.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index e3c04c5..e09c127 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1062,10 +1062,10 @@ resolved "https://registry.yarnpkg.com/@gearbox-protocol/prettier-config/-/prettier-config-1.5.0.tgz#4df8e9fd2305fee6ab8c1417a02e31343836932a" integrity sha512-FUoprSsBdZyBjgxXCKL6mTkbeUJytaLzPJqIOoQpDmBRTX0seCc2o5I9PI9tySoRIlNnd/XXnKCXq1xHDEGbxw== -"@gearbox-protocol/sdk@^2.1.14": - version "2.1.14" - resolved "https://registry.yarnpkg.com/@gearbox-protocol/sdk/-/sdk-2.1.14.tgz#b19bfe1212ddf6f0964c45d95db297122994758c" - integrity sha512-GNTJiE3t2LVGxtUg1KLfKZtIUEL8opmtOzA0aDE1xgy/p8iuuQKU21OeLI9NT+1zBLK/yptr9wqqKtJ9bIv+XA== +"@gearbox-protocol/sdk@^2.1.16": + version "2.1.16" + resolved "https://registry.yarnpkg.com/@gearbox-protocol/sdk/-/sdk-2.1.16.tgz#68c996092cf645f750bc6e09d1685c1a8d50ba9d" + integrity sha512-u5JfNxu0j8Je0ymLFLnt2CPcXGq2WvrAMSDCcTC14tAlgowyj2zqxhYF+q8Px59D2mzwV+2MiUn/VgG6xaXCgw== dependencies: "@types/deep-eql" "^4.0.0" axios "^1.2.6"