Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: pendle v2 oracle #78

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions src/adapter/pendle/PendleOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {IPPrincipalToken} from "@pendle/core-v2/interfaces/IPPrincipalToken.sol"
import {IPPYLpOracle} from "@pendle/core-v2/interfaces/IPPYLpOracle.sol";
import {IStandardizedYield} from "@pendle/core-v2/interfaces/IStandardizedYield.sol";
import {PendlePYOracleLib} from "@pendle/core-v2/oracles/PendlePYOracleLib.sol";
import {PendleLpOracleLib} from "@pendle/core-v2/oracles/PendleLpOracleLib.sol";
import {BaseAdapter, Errors, IPriceOracle} from "../BaseAdapter.sol";
import {ScaleUtils, Scale} from "../../lib/ScaleUtils.sol";

Expand Down Expand Up @@ -36,12 +37,13 @@ contract PendleOracle is BaseAdapter {
Scale internal immutable scale;

/// @notice Deploy a PendleOracle.
/// @dev The oracle can price PT/SY and PT/Asset. Whether to use SY or Asset depends on the underlying.
/// @dev The oracle can price Pendle PT,LP to SY,Asset. Whether to use SY or Asset depends on the underlying.
/// Consult https://docs.pendle.finance/Developers/Contracts/StandardizedYield#standard-sys for more information.
/// Before deploying this adapter ensure that the oracle is initialized and the observation buffer is filled.
/// Note that this adapter allows specifing any `quote` as the underlying asset.
/// @param _pendleOracle The address of the PendlePYLpOracle contract. Used only in the constructor.
/// @param _pendleMarket The address of the Pendle market.
/// @param _base The address of the PT.
/// @param _base The address of the PT or LP token.
/// @param _quote The address of the SY token or the underlying asset.
/// @param _twapWindow The desired length of the twap window.
constructor(address _pendleOracle, address _pendleMarket, address _base, address _quote, uint32 _twapWindow) {
Expand All @@ -59,19 +61,21 @@ contract PendleOracle is BaseAdapter {

(IStandardizedYield sy, IPPrincipalToken pt,) = IPMarket(_pendleMarket).readTokens();

// Base must be PT
if (_base != address(pt)) revert Errors.PriceOracle_InvalidConfiguration();

// Quote must be SY or Asset.
if (_quote == address(sy)) {
getRate = PendlePYOracleLib.getPtToSyRate;
} else {
(, address asset,) = sy.assetInfo();
if (_quote == asset) {
// Note: we allow using any asset # to any token.
if (_base == address(pt)) {
if (_quote == address(sy)) {
getRate = PendlePYOracleLib.getPtToSyRate;
} else {
getRate = PendlePYOracleLib.getPtToAssetRate;
}
} else if (_base == _pendleMarket) {
if (_quote == address(sy)) {
getRate = PendleLpOracleLib.getLpToSyRate;
} else {
revert Errors.PriceOracle_InvalidConfiguration();
getRate = PendleLpOracleLib.getLpToAssetRate;
}
} else {
revert Errors.PriceOracle_InvalidConfiguration();
}

pendleMarket = _pendleMarket;
Expand Down
1 change: 0 additions & 1 deletion test/adapter/AdapterHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ contract AdapterHelper is Test {
FeedReturnsExpoTooLow,
FeedReturnsExpoTooHigh,
Constructor_BaseNotPt,
Constructor_QuoteNotSyOrAsset,
Constructor_CardinalityTooSmall,
Constructor_TooFewObservations,
Constructor_NoPool,
Expand Down
136 changes: 136 additions & 0 deletions test/adapter/pendle/PendleOracle.fork.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,39 @@ contract PendleOracleForkTest is ForkTest {
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market is active. 1 LP-sUSDe0924 = 1.8960 SY-sUSDe.
function test_GetQuote_ActiveMarket_sUSDe0924_LP_SY() public {
PendleOracle oracle = new PendleOracle(
PENDLE_ORACLE, PENDLE_SUSDE_0924_MARKET, PENDLE_SUSDE_0924_MARKET, PENDLE_SUSDE_0924_SY, 15 minutes
);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_SUSDE_0924_MARKET, PENDLE_SUSDE_0924_SY);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_SUSDE_0924_MARKET, PENDLE_SUSDE_0924_SY);
assertApproxEqRel(outAmount, 1.896e18, REL_PRECISION);
assertEq(outAmount1000, outAmount * 1000);

uint256 outAmountInv = oracle.getQuote(outAmount, PENDLE_SUSDE_0924_SY, PENDLE_SUSDE_0924_MARKET);
assertEq(outAmountInv, 1e18);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, PENDLE_SUSDE_0924_SY, PENDLE_SUSDE_0924_MARKET);
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market is active. 1 LP-sUSDe0924 = 2.0890 USDe.
function test_GetQuote_ActiveMarket_sUSDe0924_LP_Asset() public {
PendleOracle oracle =
new PendleOracle(PENDLE_ORACLE, PENDLE_SUSDE_0924_MARKET, PENDLE_SUSDE_0924_MARKET, USDE, 15 minutes);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_SUSDE_0924_MARKET, USDE);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_SUSDE_0924_MARKET, USDE);
assertApproxEqRel(outAmount, 2.089e18, REL_PRECISION);
assertEq(outAmount1000, outAmount * 1000);

uint256 outAmountInv = oracle.getQuote(outAmount, USDE, PENDLE_SUSDE_0924_MARKET);
assertEq(outAmountInv, 1e18);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, USDE, PENDLE_SUSDE_0924_MARKET);
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market is active. 1 PT-eBTC1224 = 0.9849 SY-eBTC. Oracle has no slippage.
function test_GetQuote_ActiveMarket_eBTC1224_PT_SY() public {
PendleOracle oracle = new PendleOracle(
Expand Down Expand Up @@ -114,6 +147,39 @@ contract PendleOracleForkTest is ForkTest {
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market is active. 1 LP-eBTC1224 = 1.9698 SY-eBTC.
function test_GetQuote_ActiveMarket_eBTC1224_LP_SY() public {
PendleOracle oracle = new PendleOracle(
PENDLE_ORACLE, PENDLE_EBTC_1224_MARKET, PENDLE_EBTC_1224_MARKET, PENDLE_EBTC_1224_SY, 15 minutes
);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_EBTC_1224_MARKET, PENDLE_EBTC_1224_SY);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_EBTC_1224_MARKET, PENDLE_EBTC_1224_SY);
assertApproxEqRel(outAmount, 1.9698e8, REL_PRECISION);
assertApproxEqRel(outAmount1000, outAmount * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, PENDLE_EBTC_1224_SY, PENDLE_EBTC_1224_MARKET);
assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, PENDLE_EBTC_1224_SY, PENDLE_EBTC_1224_MARKET);
assertApproxEqRel(outAmountInv1000, 1000e18, REL_PRECISION);
}

/// @dev This market is active. 1 LP-eBTC1224 = 1.9836 eBTC.
function test_GetQuote_ActiveMarket_eBTC1224_LP_Asset() public {
PendleOracle oracle =
new PendleOracle(PENDLE_ORACLE, PENDLE_EBTC_1224_MARKET, PENDLE_EBTC_1224_MARKET, EBTC, 15 minutes);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_EBTC_1224_MARKET, EBTC);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_EBTC_1224_MARKET, EBTC);
assertApproxEqRel(outAmount, 1.9836e8, REL_PRECISION);
assertApproxEqRel(outAmount1000, outAmount * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, EBTC, PENDLE_EBTC_1224_MARKET);
assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, EBTC, PENDLE_EBTC_1224_MARKET);
assertApproxEqRel(outAmountInv1000, 1000e18, REL_PRECISION);
}

/// @dev This market is active. 1 PT-LBTC1224 = 0.9802 SY-LBTC. Oracle has no slippage.
function test_GetQuote_ActiveMarket_LBTC1224_PT_SY() public {
PendleOracle oracle = new PendleOracle(
Expand Down Expand Up @@ -147,6 +213,45 @@ contract PendleOracleForkTest is ForkTest {
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market is active. 1 LP-LBTC1224 = 2.0775 SY-LBTC.
function test_GetQuote_ActiveMarket_LBTC1224_LP_SY() public {
PendleOracle oracle = new PendleOracle(
PENDLE_ORACLE,
PENDLE_CORN_LBTC_1224_MARKET,
PENDLE_CORN_LBTC_1224_MARKET,
PENDLE_CORN_LBTC_1224_SY,
15 minutes
);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_CORN_LBTC_1224_MARKET, PENDLE_CORN_LBTC_1224_SY);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_CORN_LBTC_1224_MARKET, PENDLE_CORN_LBTC_1224_SY);
assertApproxEqRel(outAmount, 2.0775e8, REL_PRECISION);
assertApproxEqRel(outAmount1000, outAmount * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, PENDLE_CORN_LBTC_1224_SY, PENDLE_CORN_LBTC_1224_MARKET);
assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION);
uint256 outAmountInv1000 =
oracle.getQuote(outAmount1000, PENDLE_CORN_LBTC_1224_SY, PENDLE_CORN_LBTC_1224_MARKET);
assertApproxEqRel(outAmountInv1000, 1000e18, REL_PRECISION);
}

/// @dev This market is active. 1 LP-LBTC1224 = 2.0775 WBTC.
function test_GetQuote_ActiveMarket_LBTC1224_LP_Asset() public {
PendleOracle oracle = new PendleOracle(
PENDLE_ORACLE, PENDLE_CORN_LBTC_1224_MARKET, PENDLE_CORN_LBTC_1224_MARKET, WBTC, 15 minutes
);

uint256 outAmount = oracle.getQuote(1e18, PENDLE_CORN_LBTC_1224_MARKET, WBTC);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_CORN_LBTC_1224_MARKET, WBTC);
assertApproxEqRel(outAmount, 2.0775e8, REL_PRECISION);
assertApproxEqRel(outAmount1000, outAmount * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, WBTC, PENDLE_CORN_LBTC_1224_MARKET);
assertApproxEqRel(outAmountInv, 1e18, REL_PRECISION);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, WBTC, PENDLE_CORN_LBTC_1224_MARKET);
assertApproxEqRel(outAmountInv1000, 1000e18, REL_PRECISION);
}

/// @dev This market has expired, so 1 PT = 0.95712 SY-weETH without slippage.
function test_GetQuote_ExpiredMarket_eETH0624_PT_SY() public {
PendleOracle oracle = new PendleOracle(
Expand Down Expand Up @@ -178,6 +283,37 @@ contract PendleOracleForkTest is ForkTest {
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market has expired, so 1 LP = 2.0058 SY-weETH without slippage.
function test_GetQuote_ExpiredMarket_eETH0624_LP_SY() public {
PendleOracle oracle = new PendleOracle(
PENDLE_ORACLE, PENDLE_EETH_0624_MARKET, PENDLE_EETH_0624_MARKET, PENDLE_EETH_0624_SY, 15 minutes
);
uint256 outAmount = oracle.getQuote(1e18, PENDLE_EETH_0624_MARKET, PENDLE_EETH_0624_SY);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_EETH_0624_MARKET, PENDLE_EETH_0624_SY);
assertApproxEqRel(outAmount, 2.0058e18, REL_PRECISION);
assertApproxEqRel(outAmount1000, 2.0058e18 * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, PENDLE_EETH_0624_SY, PENDLE_EETH_0624_MARKET);
assertEq(outAmountInv, 1e18);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, PENDLE_EETH_0624_SY, PENDLE_EETH_0624_MARKET);
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market has expired, so 1 LP = 2.1031 eETH without slippage.
function test_GetQuote_ExpiredMarket_eETH0624_LP_Asset() public {
PendleOracle oracle =
new PendleOracle(PENDLE_ORACLE, PENDLE_EETH_0624_MARKET, PENDLE_EETH_0624_MARKET, EETH, 15 minutes);
uint256 outAmount = oracle.getQuote(1e18, PENDLE_EETH_0624_MARKET, EETH);
uint256 outAmount1000 = oracle.getQuote(1000e18, PENDLE_EETH_0624_MARKET, EETH);
assertApproxEqRel(outAmount, 2.1031e18, REL_PRECISION);
assertApproxEqRel(outAmount1000, 2.1031e18 * 1000, REL_PRECISION);

uint256 outAmountInv = oracle.getQuote(outAmount, EETH, PENDLE_EETH_0624_MARKET);
assertEq(outAmountInv, 1e18);
uint256 outAmountInv1000 = oracle.getQuote(outAmount1000, EETH, PENDLE_EETH_0624_MARKET);
assertEq(outAmountInv1000, 1000e18);
}

/// @dev This market's oracle buffer is not initialized, so deployment should revert.
function test_Constructor_OracleBufferNotInitialized() public {
// Oracle does not support 15 minutes.
Expand Down
6 changes: 0 additions & 6 deletions test/adapter/pendle/PendleOracle.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ contract PendleOracleTest is PendleOracleHelper {
setUpState(s);
}

function test_Constructor_RevertsWhen_Constructor_QuoteNotSyOrAsset(FuzzableState memory s) public {
setBehavior(Behavior.Constructor_QuoteNotSyOrAsset, true);
vm.expectRevert();
setUpState(s);
}

function test_Constructor_RevertsWhen_Constructor_TwapWindowTooShort(FuzzableState memory s) public {
setBehavior(Behavior.Constructor_TwapWindowTooShort, true);
vm.expectRevert();
Expand Down
6 changes: 0 additions & 6 deletions test/adapter/pendle/PendleOracleHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ contract PendleOracleHelper is AdapterHelper {
s.base = s.pt;
}

if (behaviors[Behavior.Constructor_QuoteNotSyOrAsset]) {
vm.assume(s.quote != s.sy && s.quote != s.asset);
} else {
s.quote = uint160(s.quote) % 2 == 0 ? s.sy : s.asset;
}

if (behaviors[Behavior.Constructor_TwapWindowTooShort]) {
s.twapWindow = uint32(bound(s.twapWindow, 1, 5 minutes - 1));
} else if (behaviors[Behavior.Constructor_TwapWindowTooLong]) {
Expand Down