Skip to content

[VEN-2943]: add support for Pendle's getPtToSyRate #240

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"camelcase": 1,
"no-await-in-loop": 1,
"no-restricted-syntax": 1,
"no-shadow": 0,
"@typescript-eslint/no-shadow": 2,
"import/no-extraneous-dependencies": [
"error",
{
Expand Down
Binary file added audits/119_pendleOracleUpdate_certik_20241226.pdf
Binary file not shown.
3 changes: 3 additions & 0 deletions contracts/interfaces/IPendlePtOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ pragma solidity 0.8.25;

interface IPendlePtOracle {
function getPtToAssetRate(address market, uint32 duration) external view returns (uint256);

function getPtToSyRate(address market, uint32 duration) external view returns (uint256);

function getOracleState(
address market,
uint32 duration
Expand Down
58 changes: 53 additions & 5 deletions contracts/oracles/PendleOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,43 @@ pragma solidity 0.8.25;
import { IPendlePtOracle } from "../interfaces/IPendlePtOracle.sol";
import { CorrelatedTokenOracle } from "./common/CorrelatedTokenOracle.sol";
import { ensureNonzeroAddress, ensureNonzeroValue } from "@venusprotocol/solidity-utilities/contracts/validators.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

/**
* @title PendleOracle
* @author Venus
* @notice This oracle fetches the price of a pendle token
* @dev As a base price the oracle uses either the price of the Pendle
* market's asset (in this case PT_TO_ASSET rate should be used) or
* the price of the Pendle market's interest bearing token (e.g. wstETH
* for stETH; in this case PT_TO_SY rate should be used). Technically,
* interest bearing token is different from standardized yield (SY) token,
* but since SY is a wrapper around an interest bearing token, we can safely
* assume the prices of the two are equal. This is not always true for asset
* price though: using PT_TO_ASSET rate assumes that the yield token can
* be seamlessly redeemed for the underlying asset. In reality, this might
* not always be the case. For more details, see
* https://docs.pendle.finance/Developers/Contracts/StandardizedYield
*/
contract PendleOracle is CorrelatedTokenOracle {
/// @notice Which asset to use as a base for the returned PT
/// price. Can be either a standardized yield token (SY), in
/// this case PT/SY price is returned, or the Pendle
/// market's asset directly.
enum RateKind {
PT_TO_ASSET,
PT_TO_SY
}

/// @notice Address of the PT oracle
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IPendlePtOracle public immutable PT_ORACLE;

/// @notice Whether to use PT/SY (standardized yield token) rate
/// or PT/market asset rate
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
RateKind public immutable RATE_KIND;

/// @notice Address of the market
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable MARKET;
Expand All @@ -23,14 +49,28 @@ contract PendleOracle is CorrelatedTokenOracle {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint32 public immutable TWAP_DURATION;

/// @notice Decimals of the underlying token
/// @dev We make an assumption that the underlying decimals will
/// not change throughout the lifetime of the Pendle market
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
uint8 public immutable UNDERLYING_DECIMALS;

/// @notice Thrown if the duration is invalid
error InvalidDuration();

/// @notice Constructor for the implementation contract.
/// @custom:oz-upgrades-unsafe-allow constructor
/// @param market Pendle market
/// @param ptOracle Pendle oracle
/// @param rateKind Either PT_TO_ASSET or PT_TO_SY
/// @param ptToken Pendle PT token
/// @param underlyingToken Underlying token, can be either the market's asset or the interest bearing token
/// @param resilientOracle Venus Oracle to get the underlying token price from
/// @param twapDuration TWAP duration to call Pendle oracle with
constructor(
address market,
address ptOracle,
RateKind rateKind,
address ptToken,
address underlyingToken,
address resilientOracle,
Expand All @@ -42,7 +82,9 @@ contract PendleOracle is CorrelatedTokenOracle {

MARKET = market;
PT_ORACLE = IPendlePtOracle(ptOracle);
RATE_KIND = rateKind;
TWAP_DURATION = twapDuration;
UNDERLYING_DECIMALS = IERC20Metadata(UNDERLYING_TOKEN).decimals();

(bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = PT_ORACLE.getOracleState(
MARKET,
Expand All @@ -53,11 +95,17 @@ contract PendleOracle is CorrelatedTokenOracle {
}
}

/**
* @notice Fetches the amount of underlying token for 1 pendle token
* @return amount The amount of underlying token for pendle token
*/
/// @notice Fetches the amount of underlying token for 1 PT
/// @return amount The amount of underlying token (either the market's asset
/// or the yield token) for 1 PT, adjusted for decimals such that the result
/// has the same precision as the underlying token
function _getUnderlyingAmount() internal view override returns (uint256) {
return PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);
uint256 rate;
if (RATE_KIND == RateKind.PT_TO_SY) {
rate = PT_ORACLE.getPtToSyRate(MARKET, TWAP_DURATION);
} else {
rate = PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);
}
return ((10 ** UNDERLYING_DECIMALS) * rate) / 1e18;
}
}
15 changes: 12 additions & 3 deletions contracts/oracles/mocks/MockPendlePtOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,32 @@ import "@openzeppelin/contracts/access/Ownable.sol";

contract MockPendlePtOracle is IPendlePtOracle, Ownable {
mapping(address => mapping(uint32 => uint256)) public ptToAssetRate;
mapping(address => mapping(uint32 => uint256)) public ptToSyRate;

constructor() Ownable() {}

function setPtToAssetRate(address market, uint32 duration, uint256 rate) external onlyOwner {
ptToAssetRate[market][duration] = rate;
}

function setPtToSyRate(address market, uint32 duration, uint256 rate) external onlyOwner {
ptToSyRate[market][duration] = rate;
}

function getPtToAssetRate(address market, uint32 duration) external view returns (uint256) {
return ptToAssetRate[market][duration];
}

function getPtToSyRate(address market, uint32 duration) external view returns (uint256) {
return ptToSyRate[market][duration];
}

function getOracleState(
address market,
uint32 duration
address /* market */,
uint32 /* duration */
)
external
view
pure
returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied)
{
return (false, 0, true);
Expand Down
6 changes: 6 additions & 0 deletions deploy/7-deploy-pendle-oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { HardhatRuntimeEnvironment } from "hardhat/types";

import { ADDRESSES } from "../helpers/deploymentConfig";

enum PendleRateKind {
PT_TO_ASSET = 0,
PT_TO_SY = 1,
}

const func: DeployFunction = async ({
getNamedAccounts,
deployments,
Expand Down Expand Up @@ -32,6 +37,7 @@ const func: DeployFunction = async ({
args: [
PTweETH_26DEC2024_Market || "0x0000000000000000000000000000000000000001",
ptOracleAddress,
PendleRateKind.PT_TO_ASSET,
PTweETH_26DEC2024,
WETH,
oracle.address,
Expand Down
Loading
Loading