Skip to content

Commit

Permalink
feat: pendle pt twap price feed
Browse files Browse the repository at this point in the history
  • Loading branch information
Van0k committed Aug 13, 2024
1 parent 9fa370b commit fba2782
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 6 deletions.
12 changes: 12 additions & 0 deletions contracts/interfaces/pendle/IPendleMarket.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.17;

interface IPendleMarket {
function observe(uint32[] calldata secondsAgos) external view returns (uint216[] memory);

function expiry() external view returns (uint256);

function readTokens() external view returns (address, address, address);
}
91 changes: 91 additions & 0 deletions contracts/oracles/pendle/PendleTWAPPTPriceFeed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;

import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {WAD, SECONDS_PER_YEAR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
import {PriceFeedType} from "@gearbox-protocol/sdk-gov/contracts/PriceFeedType.sol";

import {IPriceFeed} from "@gearbox-protocol/core-v2/contracts/interfaces/IPriceFeed.sol";
import {IPendleMarket} from "../../interfaces/pendle/IPendleMarket.sol";
import {PriceFeedValidationTrait} from "@gearbox-protocol/core-v3/contracts/traits/PriceFeedValidationTrait.sol";
import {SanityCheckTrait} from "@gearbox-protocol/core-v3/contracts/traits/SanityCheckTrait.sol";
import {PriceFeedParams} from "../PriceFeedParams.sol";
import {FixedPoint} from "../../libraries/FixedPoint.sol";
import {LogExpMath} from "../../libraries/LogExpMath.sol";

/// @title Pendle PT price feed based on Pendle market TWAPs
/// @notice The PT price is derived from the Pendle market's ln(impliedRate) TWAP:
/// 1) The average implied rate is computed as (ln(IR)_(now) - ln(IR)_(now - timeWindow)) / timeWindow;
/// 2) The PT to asset rate is computed as 1 / (e ^ (ln(IR)_avg * timeToExpiry / secondsPerYear));
/// 3) The PT price is ptToAssetRate * assetPrice;
contract PendleTWAPPTPriceFeed is IPriceFeed, PriceFeedValidationTrait, SanityCheckTrait {
uint256 public constant override version = 3_00;
PriceFeedType public constant override priceFeedType = PriceFeedType.PENDLE_PT_TWAP_ORACLE;
uint8 public constant override decimals = 8;
string public description;

/// @notice Indicates whether the consuming PriceOracle can skip the sanity checks. Set to `true`
/// since this price feed performs sanity checks locally
bool public constant override skipPriceCheck = true;

/// @notice Address of the pendle market where the PT is traded
address public immutable market;

/// @notice Timestamp of the market (and PT) expiry
uint256 public immutable expiry;

/// @notice Underlying price feed
address public immutable priceFeed;
uint32 public immutable stalenessPeriod;
bool public immutable skipCheck;

/// @notice The size of the TWAP observation window
uint32 public immutable twapWindow;

constructor(address _market, address _priceFeed, uint32 _stalenessPeriod, uint32 _twapWindow) {
market = _market;
expiry = IPendleMarket(_market).expiry();
priceFeed = _priceFeed;
stalenessPeriod = _stalenessPeriod;
skipCheck = _validatePriceFeed(priceFeed, stalenessPeriod);
twapWindow = _twapWindow;

(, address pt,) = IPendleMarket(_market).readTokens();

string memory ptName = IERC20Metadata(pt).name();

description = string(abi.encodePacked(ptName, " Pendle Market TWAP * ", IPriceFeed(priceFeed).description()));
}

/// @dev Gets the ln(impliedRate) from the market TWAP
function _getMarketLnImpliedRate() internal view returns (uint256) {
uint32[] memory secondAgos = new uint32[](2);
secondAgos[1] = twapWindow;

uint216[] memory cumulativeLIR = IPendleMarket(market).observe(secondAgos);

return (cumulativeLIR[0] - cumulativeLIR[1]) / twapWindow;
}

/// @dev Computes the PT to asset rate from the market implied rate TWAP
function _getPTToAssetRate() internal view returns (uint256) {
uint256 assetToPTRate =
uint256(LogExpMath.exp(int256(_getMarketLnImpliedRate() * (expiry - block.timestamp) / SECONDS_PER_YEAR)));

return FixedPoint.divDown(WAD, assetToPTRate);
}

/// @notice Returns the USD price of the PT token with 8 decimals and the last update timestamp
function latestRoundData() external view override returns (uint80, int256, uint256, uint256, uint80) {
int256 answer = _getValidatedPrice(priceFeed, stalenessPeriod, skipCheck);

if (expiry > block.timestamp) {
answer = int256(FixedPoint.mulDown(uint256(answer), _getPTToAssetRate()));
}

return (0, answer, 0, 0, 0);
}
}
34 changes: 33 additions & 1 deletion contracts/test/suites/PriceFeedDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
GenericLPPriceFeedData,
TheSamePriceFeedData,
BalancerLPPriceFeedData,
RedStonePriceFeedData
RedStonePriceFeedData,
PendlePriceFeedData
} from "@gearbox-protocol/sdk-gov/contracts/PriceFeedDataLive.sol";
import {PriceFeedConfig} from "@gearbox-protocol/core-v3/contracts/test/interfaces/ICreditConfig.sol";
import {PriceOracleV3} from "@gearbox-protocol/core-v3/contracts/core/PriceOracleV3.sol";
Expand All @@ -49,6 +50,7 @@ import {CompositePriceFeed} from "../../oracles/CompositePriceFeed.sol";
import {PriceFeedParams} from "../../oracles/PriceFeedParams.sol";
import {ZeroPriceFeed} from "../../oracles/ZeroPriceFeed.sol";
import {MellowLRTPriceFeed} from "../../oracles/mellow/MellowLRTPriceFeed.sol";
import {PendleTWAPPTPriceFeed} from "../../oracles/pendle/PendleTWAPPTPriceFeed.sol";

import {IWAToken} from "../../interfaces/aave/IWAToken.sol";
import {IBalancerStablePool} from "../../interfaces/balancer/IBalancerStablePool.sol";
Expand Down Expand Up @@ -708,6 +710,36 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive {
}
}

// PENDLE PT PRICE FEEDS
PendlePriceFeedData[] memory pendlePTPriceFeeds = pendlePriceFeedsByNetwork[chainId];
len = pendlePTPriceFeeds.length;
unchecked {
for (uint256 i; i < len; ++i) {
Tokens t = pendlePTPriceFeeds[i].token;
address token = tokenTestSuite.addressOf(t);

if (token == address(0)) {
continue;
}

address underlying = tokenTestSuite.addressOf(pendlePTPriceFeeds[i].underlying);

address pf = address(
new PendleTWAPPTPriceFeed(
pendlePTPriceFeeds[i].market,
priceFeeds[underlying],
stalenessPeriods[underlying],
pendlePTPriceFeeds[i].twapWindow
)
);

setPriceFeed(token, pf, pendlePTPriceFeeds[i].trusted, pendlePTPriceFeeds[i].reserve);

string memory description = string(abi.encodePacked("PRICEFEED_", tokenTestSuite.symbols(t)));
vm.label(pf, description);
}
}

priceFeedConfigLength = priceFeedConfig.length;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@gearbox-protocol/core-v3": "^1.47.1",
"@gearbox-protocol/eslint-config": "^1.6.1",
"@gearbox-protocol/prettier-config": "^1.5.0",
"@gearbox-protocol/sdk-gov": "^2.13.5",
"@gearbox-protocol/sdk-gov": "^2.15.0",
"@openzeppelin/contracts": "4.9.3",
"@redstone-finance/evm-connector": "0.2.5",
"@typechain/ethers-v5": "^10.1.0",
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -993,10 +993,10 @@
resolved "https://registry.yarnpkg.com/@gearbox-protocol/prettier-config/-/prettier-config-1.5.0.tgz#4df8e9fd2305fee6ab8c1417a02e31343836932a"
integrity sha512-FUoprSsBdZyBjgxXCKL6mTkbeUJytaLzPJqIOoQpDmBRTX0seCc2o5I9PI9tySoRIlNnd/XXnKCXq1xHDEGbxw==

"@gearbox-protocol/sdk-gov@^2.13.5":
version "2.13.5"
resolved "https://registry.yarnpkg.com/@gearbox-protocol/sdk-gov/-/sdk-gov-2.13.5.tgz#86f5a307d60ac7dd71f0b2de0c2457a81d1b007b"
integrity sha512-6852ID0FRor/PauMhRGP4ejvT+gsKuXc1ENzzCVcDGWJozne3r8C6yf1x2VjnDKMPEbykM3iYOHDvhK4SiE80g==
"@gearbox-protocol/sdk-gov@^2.15.0":
version "2.15.0"
resolved "https://registry.yarnpkg.com/@gearbox-protocol/sdk-gov/-/sdk-gov-2.15.0.tgz#3ed7da0154b170fa5360f4dbb24fd8c98a67443b"
integrity sha512-AfSgKf+tSkpNT2a8f+yWX14pfXQXg82BU9aJcW2mTEEAT7b1CJg3jl8DQLneO2y3YtqnyGlQpv2m4pYtrUqAaQ==
dependencies:
ethers "6.12.1"
humanize-duration-ts "^2.1.1"
Expand Down

0 comments on commit fba2782

Please sign in to comment.