Skip to content

Commit

Permalink
fix: bring back price feeds
Browse files Browse the repository at this point in the history
  • Loading branch information
Van0k committed Mar 10, 2023
1 parent c0c3f58 commit 8e9b0c3
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 0 deletions.
38 changes: 38 additions & 0 deletions contracts/interfaces/ILPPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {IPriceFeedType} from "./IPriceFeedType.sol";

interface ILPPriceFeedEvents {
/// @dev Emits on updating the virtual price bounds
event NewLimiterParams(uint256 lowerBound, uint256 upperBound);
}

interface ILPPriceFeedExceptions {
/// @dev Thrown on returning a value that violates the current bounds
error ValueOutOfRangeException();

/// @dev Thrown on failing sanity checks when setting new bounds
error IncorrectLimitsException();
}

/// @title Interface for LP PriceFeeds with limiter
interface ILPPriceFeed is AggregatorV3Interface, IPriceFeedType, ILPPriceFeedEvents, ILPPriceFeedExceptions {
/// @dev Sets the lower and upper bounds for virtual price.
/// @param _lowerBound The new lower bound
/// @notice The upper bound is computed automatically
function setLimiter(uint256 _lowerBound) external;

/// @dev Returns the lower bound
function lowerBound() external view returns (uint256);

/// @dev Returns the upper bound
function upperBound() external view returns (uint256);

/// @dev Returns the pre-defined window between the lower and upper bounds
/// @notice In bp format
function delta() external view returns (uint256);
}
103 changes: 103 additions & 0 deletions contracts/oracles/BoundedPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV2V3Interface.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/PercentageMath.sol";
import {PriceFeedType, IPriceFeedType} from "../interfaces/IPriceFeedType.sol";

// EXCEPTIONS
import {NotImplementedException} from "../interfaces/IErrors.sol";

interface ChainlinkReadableAggregator {
function aggregator() external view returns (address);

function phaseAggregators(uint16 idx) external view returns (AggregatorV2V3Interface);

function phaseId() external view returns (uint16);
}

/// @title Price feed with an upper bound on price
/// @notice Used to limit prices on assets that should not rise above
/// a certain level, such as stablecoins and other pegged assets
contract BoundedPriceFeed is ChainlinkReadableAggregator, AggregatorV3Interface, IPriceFeedType {
/// @dev Chainlink price feed for the Vault's underlying
AggregatorV3Interface public immutable priceFeed;

/// @dev The upper bound on Chainlink price for the asset
int256 public immutable upperBound;

/// @dev Decimals of the returned result.
uint8 public immutable override decimals;

/// @dev Price feed description
string public override description;

uint256 public constant override version = 1;

PriceFeedType public constant override priceFeedType = PriceFeedType.BOUNDED_ORACLE;

bool public constant override skipPriceCheck = false;

/// @dev Constructor
/// @param _priceFeed Chainlink price feed to receive results from
/// @param _upperBound Initial upper bound for the Chainlink price
constructor(address _priceFeed, int256 _upperBound) {
priceFeed = AggregatorV3Interface(_priceFeed);
description = string(abi.encodePacked(priceFeed.description(), " Bounded"));
decimals = priceFeed.decimals();
upperBound = _upperBound;
}

/// @dev Implemented for compatibility, but reverts since Gearbox's price feeds
/// do not store historical data.
function getRoundData(uint80)
external
pure
virtual
override
returns (
uint80, // roundId,
int256, // answer,
uint256, // startedAt,
uint256, // updatedAt,
uint80 // answeredInRound
)
{
revert NotImplementedException(); // F:[LPF-2]
}

/// @dev Returns the value if it is below the upper bound, otherwise returns the upper bound
/// @param value Value to be checked and bounded
function _upperBoundValue(int256 value) internal view returns (int256) {
return (value > upperBound) ? upperBound : value;
}

/// @dev Returns the upper-bounded USD price of the token
function latestRoundData()
external
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
(roundId, answer, startedAt, updatedAt, answeredInRound) = priceFeed.latestRoundData(); // F:[OYPF-4]

answer = _upperBoundValue(answer);
}

/// @dev Returns the current phase's aggregator address
function aggregator() external view returns (address) {
return ChainlinkReadableAggregator(address(priceFeed)).aggregator();
}

/// @dev Returns a phase aggregator by index
function phaseAggregators(uint16 idx) external view returns (AggregatorV2V3Interface) {
return ChainlinkReadableAggregator(address(priceFeed)).phaseAggregators(idx);
}

function phaseId() external view returns (uint16) {
return ChainlinkReadableAggregator(address(priceFeed)).phaseId();
}
}
86 changes: 86 additions & 0 deletions contracts/oracles/CompositePriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import {PriceFeedChecker} from "./PriceFeedChecker.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/PercentageMath.sol";
import {PriceFeedType, IPriceFeedType} from "../interfaces/IPriceFeedType.sol";

// EXCEPTIONS
import {NotImplementedException} from "../interfaces/IErrors.sol";

/// @title Price feed that composes an base asset-denominated price feed with a USD one
/// @notice Used for better price tracking for correlated assets (such as stETH or WBTC) or on networks where
/// only feeds for the native tokens exist
contract CompositePriceFeed is PriceFeedChecker, AggregatorV3Interface, IPriceFeedType {
/// @dev Chainlink base asset price feed for the target asset
AggregatorV3Interface public immutable targetToBasePriceFeed;

/// @dev Chainlink Base asset / USD price feed
AggregatorV3Interface public immutable baseToUsdPriceFeed;

/// @dev Decimals of the returned result.
uint8 public immutable override decimals;

/// @dev 10 ^ Decimals of Target / Base price feed, to divide the product of answers
int256 public immutable answerDenominator;

/// @dev Price feed description
string public override description;

uint256 public constant override version = 1;

PriceFeedType public constant override priceFeedType = PriceFeedType.COMPOSITE_ORACLE;

bool public constant override skipPriceCheck = true;

/// @dev Constructor
/// @param _targetToBasePriceFeed Base asset price feed for target asset
/// @param _baseToUsdPriceFeed USD price feed for base asset
constructor(address _targetToBasePriceFeed, address _baseToUsdPriceFeed) {
targetToBasePriceFeed = AggregatorV3Interface(_targetToBasePriceFeed);
baseToUsdPriceFeed = AggregatorV3Interface(_baseToUsdPriceFeed);
description = string(abi.encodePacked(targetToBasePriceFeed.description(), " to USD Composite"));
decimals = baseToUsdPriceFeed.decimals();
answerDenominator = int256(10 ** targetToBasePriceFeed.decimals());
}

/// @dev Implemented for compatibility, but reverts since Gearbox's price feeds
/// do not store historical data.
function getRoundData(uint80)
external
pure
virtual
override
returns (
uint80, // roundId,
int256, // answer,
uint256, // startedAt,
uint256, // updatedAt,
uint80 // answeredInRound
)
{
revert NotImplementedException();
}

/// @dev Returns the composite USD-denominated price of the asset, computed as (Target / base rate * base / USD rate)
function latestRoundData()
external
view
override
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
(uint80 roundId0, int256 answer0, uint256 startedAt0, uint256 updatedAt0, uint80 answeredInRound0) =
targetToBasePriceFeed.latestRoundData();

_checkAnswer(roundId0, answer0, updatedAt0, answeredInRound0);

(roundId, answer, startedAt, updatedAt, answeredInRound) = baseToUsdPriceFeed.latestRoundData();

_checkAnswer(roundId, answer, updatedAt, answeredInRound);

answer = (answer0 * answer) / answerDenominator;
}
}
107 changes: 107 additions & 0 deletions contracts/oracles/LPPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {ILPPriceFeed} from "../interfaces/ILPPriceFeed.sol";
import {PriceFeedChecker} from "./PriceFeedChecker.sol";
import {ACLNonReentrantTrait} from "../core/ACLNonReentrantTrait.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/PercentageMath.sol";

// EXCEPTIONS
import {NotImplementedException} from "../interfaces/IErrors.sol";

/// @title Abstract PriceFeed for an LP token
/// @notice For most pools/vaults, the LP token price depends on Chainlink prices of pool assets and the pool's
/// internal exchange rate.
abstract contract LPPriceFeed is ILPPriceFeed, PriceFeedChecker, ACLNonReentrantTrait {
/// @dev The lower bound for the contract's token-to-underlying exchange rate.
/// @notice Used to protect against LP token / share price manipulation.
uint256 public lowerBound;

/// @dev Window size in PERCENTAGE format. Upper bound = lowerBound * (1 + delta)
uint256 public immutable delta;

/// @dev Decimals of the returned result.
uint8 public constant override decimals = 8;

/// @dev Price feed description
string public override description;

/// @dev Constructor
/// @param addressProvider Address of address provier which is use for getting ACL
/// @param _delta Pre-defined window in PERCENTAGE FORMAT which is allowed for SC value
/// @param _description Price feed description
constructor(address addressProvider, uint256 _delta, string memory _description)
ACLNonReentrantTrait(addressProvider)
{
description = _description; // F:[LPF-1]
delta = _delta; // F:[LPF-1]
}

/// @dev Implemented for compatibility, but reverts since Gearbox's price feeds
/// do not store historical data.
function getRoundData(uint80)
external
pure
virtual
override
returns (
uint80, // roundId,
int256, // answer,
uint256, // startedAt,
uint256, // updatedAt,
uint80 // answeredInRound
)
{
revert NotImplementedException(); // F:[LPF-2]
}

/// @dev Checks that value is in range [lowerBound; upperBound],
/// Reverts if below lowerBound and returns min(value, upperBound)
/// @param value Value to be checked and bounded
function _checkAndUpperBoundValue(uint256 value) internal view returns (uint256) {
uint256 lb = lowerBound;
if (value < lb) revert ValueOutOfRangeException(); // F:[LPF-3]

uint256 uBound = _upperBound(lb);

return (value > uBound) ? uBound : value;
}

/// @dev Updates the bounds for the exchange rate value
/// @param _lowerBound The new lower bound (the upper bound is computed dynamically)
/// from the lower bound
function setLimiter(uint256 _lowerBound)
external
override
configuratorOnly // F:[LPF-4]
{
_setLimiter(_lowerBound); // F:[LPF-4,5]
}

/// @dev IMPLEMENTATION: setLimiter
function _setLimiter(uint256 _lowerBound) internal {
if (_lowerBound == 0 || !_checkCurrentValueInBounds(_lowerBound, _upperBound(_lowerBound))) {
revert IncorrectLimitsException();
} // F:[LPF-4]

lowerBound = _lowerBound; // F:[LPF-5]
emit NewLimiterParams(lowerBound, _upperBound(_lowerBound)); // F:[LPF-5]
}

function _upperBound(uint256 lb) internal view returns (uint256) {
return (lb * (PERCENTAGE_FACTOR + delta)) / PERCENTAGE_FACTOR; // F:[LPF-5]
}

/// @dev Returns the upper bound, calculated based on the lower bound
function upperBound() external view returns (uint256) {
return _upperBound(lowerBound); // F:[LPF-5]
}

function _checkCurrentValueInBounds(uint256 _lowerBound, uint256 _upperBound)
internal
view
virtual
returns (bool);
}
16 changes: 16 additions & 0 deletions contracts/oracles/PriceFeedChecker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: BUSL-1.1
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Holdings, 2022
pragma solidity ^0.8.10;

import {IPriceOracleV2Exceptions} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceOracle.sol";

/// @title Sanity checker for Chainlink price feed results
contract PriceFeedChecker is IPriceOracleV2Exceptions {
function _checkAnswer(uint80 roundID, int256 price, uint256 updatedAt, uint80 answeredInRound) internal pure {
if (price <= 0) revert ZeroPriceException(); // F:[PO-5]
if (answeredInRound < roundID || updatedAt == 0) {
revert ChainPriceStaleException();
} // F:[PO-5]
}
}
Loading

0 comments on commit 8e9b0c3

Please sign in to comment.