44pragma solidity ^ 0.8.17 ;
55
66import {ILPPriceFeed} from "../interfaces/ILPPriceFeed.sol " ;
7- import {AbstractPriceFeed} from "./AbstractPriceFeed.sol " ;
87import {ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol " ;
98import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol " ;
109import {ACLNonReentrantTrait} from "@gearbox-protocol/core-v3/contracts/traits/ACLNonReentrantTrait.sol " ;
10+ import {PriceFeedValidationTrait} from "@gearbox-protocol/core-v3/contracts/traits/PriceFeedValidationTrait.sol " ;
1111import {IPriceOracleV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol " ;
12+ import {IUpdatablePriceFeed} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceFeed.sol " ;
1213import {
1314 IAddressProviderV3, AP_PRICE_ORACLE
1415} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol " ;
@@ -19,14 +20,26 @@ import {
1920 IncorrectLimitsException
2021} from "@gearbox-protocol/core-v3/contracts/interfaces/IExceptions.sol " ;
2122
23+ /// @dev Window size in bps, used to compute upper bound given lower bound
24+ uint256 constant WINDOW_SIZE = 200 ;
25+
26+ /// @dev Buffer size in bps, used to compute new lower bound given current exchange rate
27+ uint256 constant BUFFER_SIZE = 20 ;
28+
2229/// @title LP price feed
2330/// @notice Abstract contract for LP token price feeds.
2431/// It is assumed that the price of an LP token is the product of its exchange rate and some aggregate function
2532/// of underlying tokens prices. This contract simplifies creation of such price feeds and provides standard
2633/// validation of the LP token exchange rate that protects against price manipulation.
27- abstract contract LPPriceFeed is ILPPriceFeed , AbstractPriceFeed , ACLNonReentrantTrait {
28- /// @notice Address provider contract
29- address public immutable override addressProvider;
34+ abstract contract LPPriceFeed is ILPPriceFeed , ACLNonReentrantTrait , PriceFeedValidationTrait {
35+ /// @notice Answer precision (always 8 decimals for USD price feeds)
36+ uint8 public constant override decimals = 8 ;
37+
38+ /// @notice Indicates that price oracle can skip checks for this price feed's answers
39+ bool public constant override skipPriceCheck = true ;
40+
41+ /// @notice Price oracle contract
42+ address public immutable override priceOracle;
3043
3144 /// @notice LP token for which the prices are computed
3245 address public immutable override lpToken;
@@ -37,9 +50,6 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
3750 /// @notice Lower bound for the LP token exchange rate
3851 uint256 public override lowerBound;
3952
40- /// @notice Window size in bps, used to compute upper bound given lower bound
41- uint256 public constant override delta = 2_00 ;
42-
4353 /// @notice Whether permissionless bounds update is allowed
4454 bool public override updateBoundsAllowed;
4555
@@ -52,7 +62,7 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
5262 nonZeroAddress (_lpToken)
5363 nonZeroAddress (_lpContract)
5464 {
55- addressProvider = _addressProvider;
65+ priceOracle = IAddressProviderV3 ( _addressProvider). getAddressOrRevert (AP_PRICE_ORACLE, 3_00 ) ;
5666 lpToken = _lpToken;
5767 lpContract = _lpContract;
5868 }
@@ -73,7 +83,7 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
7383 uint256 lb = lowerBound;
7484 if (exchangeRate < lb) revert ValueOutOfRangeException ();
7585
76- uint256 ub = _upperBound (lb);
86+ uint256 ub = _calcUpperBound (lb);
7787 if (exchangeRate > ub) exchangeRate = ub;
7888
7989 (answer, updatedAt) = getAggregatePrice ();
@@ -83,7 +93,7 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
8393
8494 /// @notice Upper bound for the LP token exchange rate
8595 function upperBound () external view returns (uint256 ) {
86- return _upperBound (lowerBound);
96+ return _calcUpperBound (lowerBound);
8797 }
8898
8999 /// @notice Returns aggregate price of underlying tokens
@@ -98,11 +108,6 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
98108 /// @dev Must be implemented by derived price feeds
99109 function getScale () public view virtual override returns (uint256 scale );
100110
101- /// @dev Computes upper bound as `lowerBound * (1 + delta)`
102- function _upperBound (uint256 lb ) internal pure returns (uint256 ) {
103- return (lb * (PERCENTAGE_FACTOR + delta)) / PERCENTAGE_FACTOR;
104- }
105-
106111 // ------------- //
107112 // CONFIGURATION //
108113 // ------------- //
@@ -116,39 +121,51 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
116121
117122 /// @notice Sets new lower and upper bounds for the LP token exchange rate
118123 /// @param newLowerBound New lower bound value
119- /// @dev New upper bound value is computed as `newLowerBound * (1 + delta)`
120124 function setLimiter (uint256 newLowerBound ) external override controllerOnly {
121125 _setLimiter (newLowerBound, getLPExchangeRate ());
122126 }
123127
124128 /// @notice Permissionlessly updates LP token's exchange rate bounds using answer from the reserve price feed.
125- /// The lower bound is set to the induced reserve exchange rate (with small downside buffer).
126- function updateBounds () external override {
129+ /// Lower bound is set to the induced reserve exchange rate (with small buffer for downside movement).
130+ /// @param updateData Data to update the reserve price feed with before querying its answer if it is updatable
131+ function updateBounds (bytes calldata updateData ) external override {
127132 if (! updateBoundsAllowed) return ;
128133
129- address priceOracle = IAddressProviderV3 (addressProvider).getAddressOrRevert (AP_PRICE_ORACLE, 3_00 );
130- address reserveFeed = IPriceOracleV3 (priceOracle).priceFeedsRaw (lpToken, true );
134+ address reserveFeed = IPriceOracleV3 (priceOracle).priceFeedsRaw ({token: lpToken, reserve: true });
135+ try IUpdatablePriceFeed (reserveFeed).updatable () returns (bool updatable ) {
136+ if (updatable) IUpdatablePriceFeed (reserveFeed).updatePrice (updateData);
137+ } catch {}
131138
132- (, int256 reserveAnswer ,,,) = ILPPriceFeed (reserveFeed). latestRoundData ( );
139+ uint256 reserveAnswer = IPriceOracleV3 (priceOracle). getPriceRaw ({token: lpToken, reserve: true } );
133140 (int256 price ,) = getAggregatePrice ();
134- uint256 reserveExchangeRate = uint256 (reserveAnswer * int256 ( getScale ()) / price);
141+ uint256 reserveExchangeRate = uint256 (reserveAnswer * getScale () / uint256 ( price) );
135142
136- _setLimiter (reserveExchangeRate * 998 / 1000 , getLPExchangeRate ());
143+ _setLimiter (_calcLowerBound ( reserveExchangeRate) , getLPExchangeRate ());
137144 }
138145
139- /// @dev Sets lower bound to the current LP token exhcange rate (with small downside buffer )
140- /// @dev Derived price feeds MUST call this in the constructor after initializing all the
141- /// state variables needed for exchange rate calculation
146+ /// @dev Sets lower bound to the current LP token exhcange rate (with small buffer for downside movement )
147+ /// @dev Derived price feeds MUST call this in the constructor after initializing all the state variables
148+ /// needed for exchange rate calculation
142149 function _initLimiter () internal {
143150 uint256 exchangeRate = getLPExchangeRate ();
144- _setLimiter (exchangeRate * 998 / 1000 , exchangeRate);
151+ _setLimiter (_calcLowerBound ( exchangeRate) , exchangeRate);
145152 }
146153
147154 /// @dev `setLimiter` implementation: sets new bounds, ensures that current value is within them, emits event
148155 function _setLimiter (uint256 lower , uint256 current ) internal {
149- uint256 upper = _upperBound (lower);
156+ uint256 upper = _calcUpperBound (lower);
150157 if (lower == 0 || current < lower || current > upper) revert IncorrectLimitsException ();
151158 lowerBound = lower;
152159 emit SetBounds (lower, upper);
153160 }
161+
162+ /// @dev Computes upper bound as `_lowerBound * (1 + WINDOW_SIZE)`
163+ function _calcUpperBound (uint256 _lowerBound ) internal pure returns (uint256 ) {
164+ return _lowerBound * (PERCENTAGE_FACTOR + WINDOW_SIZE) / PERCENTAGE_FACTOR;
165+ }
166+
167+ /// @dev Computes lower bound as `exchangeRate * (1 - BUFFER_SIZE)`
168+ function _calcLowerBound (uint256 exchangeRate ) internal pure returns (uint256 ) {
169+ return exchangeRate * (PERCENTAGE_FACTOR - BUFFER_SIZE) / PERCENTAGE_FACTOR;
170+ }
154171}
0 commit comments