Skip to content
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

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

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
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
27 changes: 25 additions & 2 deletions contracts/oracles/PendleOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,28 @@ import { ensureNonzeroAddress, ensureNonzeroValue } from "@venusprotocol/solidit
* @notice This oracle fetches the price of a pendle token
*/
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 underlying
/// asset directly. Note that 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
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/underlying 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 @@ -31,6 +49,7 @@ contract PendleOracle is CorrelatedTokenOracle {
constructor(
address market,
address ptOracle,
RateKind rateKind,
address ptToken,
address underlyingToken,
address resilientOracle,
Expand All @@ -42,6 +61,7 @@ contract PendleOracle is CorrelatedTokenOracle {

MARKET = market;
PT_ORACLE = IPendlePtOracle(ptOracle);
RATE_KIND = rateKind;
TWAP_DURATION = twapDuration;

(bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = PT_ORACLE.getOracleState(
Expand All @@ -54,10 +74,13 @@ 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 or SY token for 1 pendle token
* @return amount The amount of underlying or SY token for pendle token
*/
function _getUnderlyingAmount() internal view override returns (uint256) {
if (RATE_KIND == RateKind.PT_TO_SY) {
return PT_ORACLE.getPtToSyRate(MARKET, TWAP_DURATION);
}
return PT_ORACLE.getPtToAssetRate(MARKET, TWAP_DURATION);
}
}
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 */
Copy link
Contributor

@GitGuru7 GitGuru7 Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we have commented the args? Can't we remove them?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solidity compiler complains when names are present but unused. I commented them out to silence the warnings while preserving the readability. We can remove them for sure if you think it's better, I'm ok with either.

)
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 { ADDRESSES } from "../helpers/deploymentConfig";

enum PendleRateKind {

Check failure on line 7 in deploy/7-deploy-pendle-oracle.ts

View workflow job for this annotation

GitHub Actions / Compile / Lint / Build

'PendleRateKind' is already declared in the upper scope on line 7 column 6
PT_TO_ASSET = 0,
PT_TO_SY = 1,
}

const func: DeployFunction = async ({
getNamedAccounts,
deployments,
Expand Down Expand Up @@ -32,6 +37,7 @@
args: [
PTweETH_26DEC2024_Market || "0x0000000000000000000000000000000000000001",
ptOracleAddress,
PendleRateKind.PT_TO_ASSET,
PTweETH_26DEC2024,
WETH,
oracle.address,
Expand Down
58 changes: 42 additions & 16 deletions test/PendleOracle.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { smock } from "@defi-wonderland/smock";
import { FakeContract, smock } from "@defi-wonderland/smock";
import chai from "chai";
import { parseUnits } from "ethers/lib/utils";
import { ethers } from "hardhat";

import { ADDRESSES } from "../helpers/deploymentConfig";
import { BEP20Harness, IPendlePtOracle, ResilientOracleInterface } from "../typechain-types";
import {
BEP20Harness,
IPendlePtOracle,
PendleOracle__factory,
ResilientOracleInterface,
} from "../typechain-types";
import { addr0000 } from "./utils/data";

const { expect } = chai;
chai.use(smock.matchers);

const { PTweETH_26DEC2024, PTweETH_26DEC2024_Market, PTOracle, eETH } = ADDRESSES.ethereum;
const eETH_PRICE = parseUnits("3400", 18);
const PRICE_DENOMINATOR = parseUnits("1", 18);
const EETH_AMOUNT_FOR_ONE_WEETH = parseUnits("0.923601422168630818", 18);
const PT_TO_ASSET_RATE = parseUnits("0.923601422168630818", 18);
const PT_TO_SY_RATE = parseUnits("0.93", 18);
const DURATION = 3600; // 1 hour

enum PendleRateKind {

Check failure on line 24 in test/PendleOracle.ts

View workflow job for this annotation

GitHub Actions / Compile / Lint / Build

'PendleRateKind' is already declared in the upper scope on line 24 column 6
PT_TO_ASSET,
PT_TO_SY,
}

describe("PendleOracle unit tests", () => {
let ptWeETHMock;
let resilientOracleMock;
let pendleOracle;
let pendleOracleFactory;
let ptOracleMock;
let ptWeETHMock: FakeContract<BEP20Harness>;
let resilientOracleMock: FakeContract<ResilientOracleInterface>;
let pendleOracleFactory: PendleOracle__factory;
let ptOracleMock: FakeContract<IPendlePtOracle>;

before(async () => {
// To initialize the provider we need to hit the node with any request
await ethers.getSigners();
Expand All @@ -32,7 +42,8 @@
ptWeETHMock.decimals.returns(18);

ptOracleMock = await smock.fake<IPendlePtOracle>("IPendlePtOracle", { address: PTOracle });
ptOracleMock.getPtToAssetRate.returns(EETH_AMOUNT_FOR_ONE_WEETH);
ptOracleMock.getPtToAssetRate.returns(PT_TO_ASSET_RATE);
ptOracleMock.getPtToSyRate.returns(PT_TO_SY_RATE);
ptOracleMock.getOracleState.returns([false, 0, true]);

pendleOracleFactory = await ethers.getContractFactory("PendleOracle");
Expand All @@ -44,6 +55,7 @@
pendleOracleFactory.deploy(
addr0000,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
eETH,
resilientOracleMock.address,
Expand All @@ -56,6 +68,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
addr0000,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
eETH,
resilientOracleMock.address,
Expand All @@ -68,6 +81,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
addr0000,
eETH,
resilientOracleMock.address,
Expand All @@ -80,6 +94,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
addr0000,
resilientOracleMock.address,
Expand All @@ -92,6 +107,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
eETH,
addr0000,
Expand All @@ -104,6 +120,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
eETH,
resilientOracleMock.address,
Expand All @@ -119,6 +136,7 @@
pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
PendleRateKind.PT_TO_ASSET,
ptWeETHMock.address,
eETH,
resilientOracleMock.address,
Expand All @@ -128,26 +146,34 @@

ptOracleMock.getOracleState.returns([false, 0, true]);
});
});

it("should deploy contract", async () => {
pendleOracle = await pendleOracleFactory.deploy(
describe("getPrice", () => {
const deploy = (kind: PendleRateKind) => {

Check failure on line 152 in test/PendleOracle.ts

View workflow job for this annotation

GitHub Actions / Compile / Lint / Build

Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`
return pendleOracleFactory.deploy(
PTweETH_26DEC2024_Market,
ptOracleMock.address,
kind,
ptWeETHMock.address,
eETH,
resilientOracleMock.address,
DURATION,
);
});
});
};

describe("getPrice", () => {
it("revert if wstETH address is wrong", async () => {
const pendleOracle = await deploy(PendleRateKind.PT_TO_ASSET);
await expect(pendleOracle.getPrice(addr0000)).to.be.revertedWithCustomError(pendleOracle, "InvalidTokenAddress");
});

it("should get correct price", async () => {
it("should get correct price for PT_TO_ASSET rate kind", async () => {
const pendleOracle = await deploy(PendleRateKind.PT_TO_ASSET);
expect(await pendleOracle.getPrice(ptWeETHMock.address)).to.equal(parseUnits("3140.2448353733447812", 18));
});

it("should get correct price for PT_TO_SY rate kind", async () => {
const pendleOracle = await deploy(PendleRateKind.PT_TO_SY);
expect(await pendleOracle.getPrice(ptWeETHMock.address)).to.equal(parseUnits("3162.0", 18));
});
});
});
Loading