Skip to content

Commit

Permalink
feat: add permissionless updateBounds
Browse files Browse the repository at this point in the history
  • Loading branch information
lekhovitsky committed Aug 6, 2023
1 parent 5df4f90 commit ffc589e
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 10 deletions.
8 changes: 8 additions & 0 deletions contracts/interfaces/ILPPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@ import {IPriceFeed} from "./IPriceFeed.sol";
interface ILPPriceFeedEvents {
/// @notice Emitted when new LP token exchange rate bounds are set
event SetBounds(uint256 lowerBound, uint256 upperBound);

/// @notice Emitted when permissionless bounds update is allowed or forbidden
event SetUpdateBoundsAllowed(bool allowed);
}

/// @title LP price feed interface
interface ILPPriceFeed is IPriceFeed, ILPPriceFeedEvents {
function addressProvider() external view returns (address);

function lpToken() external view returns (address);
function lpContract() external view returns (address);

function lowerBound() external view returns (uint256);
function upperBound() external view returns (uint256);
function delta() external view returns (uint256);
function updateBoundsAllowed() external view returns (bool);

function getAggregatePrice() external view returns (int256 answer, uint256 updatedAt);
function getLPExchangeRate() external view returns (uint256 exchangeRate);
Expand All @@ -27,5 +33,7 @@ interface ILPPriceFeed is IPriceFeed, ILPPriceFeedEvents {
// CONFIGURATION //
// ------------- //

function setUpdateBoundsAllowed(bool allowed) external;
function setLimiter(uint256 newLowerBound) external;
function updateBounds() external;
}
55 changes: 45 additions & 10 deletions contracts/oracles/LPPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {AbstractPriceFeed} from "./AbstractPriceFeed.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
import {ACLNonReentrantTrait} from "@gearbox-protocol/core-v3/contracts/traits/ACLNonReentrantTrait.sol";
import {IPriceOracleV3} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol";
import {
IAddressProviderV3, AP_PRICE_ORACLE
} from "@gearbox-protocol/core-v3/contracts/interfaces/IAddressProviderV3.sol";

// EXCEPTIONS
import {
Expand All @@ -21,6 +25,9 @@ import {
/// of underlying tokens prices. This contract simplifies creation of such price feeds and provides standard
/// validation of the LP token exchange rate that protects against price manipulation.
abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentrantTrait {
/// @notice Address provider contract
address public immutable override addressProvider;

/// @notice LP token for which the prices are computed
address public immutable override lpToken;

Expand All @@ -33,6 +40,9 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
/// @notice Window size in bps, used to compute upper bound given lower bound
uint256 public constant override delta = 2_00;

/// @notice Whether permissionless bounds update is allowed
bool public override updateBoundsAllowed;

/// @notice Constructor
/// @param _addressProvider Address provider contract address
/// @param _lpToken LP token for which the prices are computed
Expand All @@ -42,6 +52,7 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
nonZeroAddress(_lpToken)
nonZeroAddress(_lpContract)
{
addressProvider = _addressProvider;
lpToken = _lpToken;
lpContract = _lpContract;
}
Expand Down Expand Up @@ -96,24 +107,48 @@ abstract contract LPPriceFeed is ILPPriceFeed, AbstractPriceFeed, ACLNonReentran
// CONFIGURATION //
// ------------- //

/// @notice Allows or forbids permissionless bounds update
function setUpdateBoundsAllowed(bool allowed) external override configuratorOnly {
if (updateBoundsAllowed == allowed) return;
updateBoundsAllowed = allowed;
emit SetUpdateBoundsAllowed(allowed);
}

/// @notice Sets new lower and upper bounds for the LP token exchange rate
/// @param newLowerBound New lower bound value
/// @dev New upper bound value is computed as `newLowerBound * (1 + delta)`
function setLimiter(uint256 newLowerBound) external override controllerOnly {
uint256 exchangeRate = getLPExchangeRate();
if (newLowerBound == 0 || exchangeRate < newLowerBound || exchangeRate > _upperBound(newLowerBound)) {
revert IncorrectLimitsException();
}
lowerBound = newLowerBound;
emit SetBounds(newLowerBound, _upperBound(newLowerBound));
_setLimiter(newLowerBound, getLPExchangeRate());
}

/// @notice Permissionlessly updates LP token's exchange rate bounds using answer from the reserve price feed.
/// The lower bound is set to the induced reserve exchange rate (with small downside buffer).
function updateBounds() external override {
if (!updateBoundsAllowed) return;

address priceOracle = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_PRICE_ORACLE, 3_00);
address reserveFeed = IPriceOracleV3(priceOracle).priceFeedsRaw(lpToken, true);

(, int256 reserveAnswer,,,) = ILPPriceFeed(reserveFeed).latestRoundData();
(int256 price,) = getAggregatePrice();
uint256 reserveExchangeRate = uint256(reserveAnswer * int256(getScale()) / price);

_setLimiter(reserveExchangeRate * 998 / 1000, getLPExchangeRate());
}

/// @dev Inititalizes bounds such that lower bound is the current LP token exhcange rate
/// @dev Sets lower bound to the current LP token exhcange rate (with small downside buffer)
/// @dev Derived price feeds MUST call this in the constructor after initializing all the
/// state variables needed for exchange rate calculation
function _initLimiter() internal {
uint256 newLowerBound = getLPExchangeRate();
lowerBound = newLowerBound;
emit SetBounds(newLowerBound, _upperBound(newLowerBound));
uint256 exchangeRate = getLPExchangeRate();
_setLimiter(exchangeRate * 998 / 1000, exchangeRate);
}

/// @dev `setLimiter` implementation: sets new bounds, ensures that current value is within them, emits event
function _setLimiter(uint256 lower, uint256 current) internal {
uint256 upper = _upperBound(lower);
if (lower == 0 || current < lower || current > upper) revert IncorrectLimitsException();
lowerBound = lower;
emit SetBounds(lower, upper);
}
}
2 changes: 2 additions & 0 deletions contracts/oracles/curve/CurveCryptoLPPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ contract CurveCryptoLPPriceFeed is LPPriceFeed {
skipCheck0 = _validatePriceFeed(priceFeed0, stalenessPeriod0);
skipCheck1 = _validatePriceFeed(priceFeed1, stalenessPeriod1);
skipCheck2 = nCoins == 3 ? _validatePriceFeed(priceFeed2, stalenessPeriod2) : false;

_initLimiter();
}

function getAggregatePrice() public view override returns (int256 answer, uint256 updatedAt) {
Expand Down
2 changes: 2 additions & 0 deletions contracts/oracles/curve/CurveStableLPPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ contract CurveStableLPPriceFeed is LPPriceFeed {
priceFeedType = nCoins == 2
? PriceFeedType.CURVE_2LP_ORACLE
: (nCoins == 3 ? PriceFeedType.CURVE_3LP_ORACLE : PriceFeedType.CURVE_4LP_ORACLE);

_initLimiter();
}

function getAggregatePrice() public view override returns (int256 answer, uint256 updatedAt) {
Expand Down

0 comments on commit ffc589e

Please sign in to comment.