-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathPendleMarketOracle.sol
More file actions
154 lines (129 loc) · 5.88 KB
/
Copy pathPendleMarketOracle.sol
File metadata and controls
154 lines (129 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.19;
import '@openzeppelin/contracts/access/Ownable2Step.sol';
import '@openzeppelin/contracts/utils/math/Math.sol';
import '@pendle/core-v2/contracts/oracles/PendlePtLpOracle.sol';
import '@pendle/core-v2/contracts/core/Market/v3/PendleMarketV3.sol';
import '@marginly/contracts/contracts/interfaces/IPriceOracle.sol';
/// @dev Oracle to get price from Pendle market Pt to Ib token
contract PendleMarketOracle is IPriceOracle, Ownable2Step {
struct OracleParams {
address pendleMarket;
address ibToken;
uint16 secondsAgo;
uint16 secondsAgoLiquidation;
uint8 ptSyDecimalsDelta;
}
uint256 private constant X96ONE = 2 ** 96;
uint8 private constant PRICE_DECIMALS = 18;
IPPtLpOracle public immutable pendle;
mapping(address => mapping(address => OracleParams)) public getParams;
error ZeroPrice();
error ZeroAddress();
error WrongValue();
error WrongIbSyDecimals();
error WrongPtAddress();
error WrongIbTokenAddress();
error PairAlreadyExist();
error UnknownPair();
error PendlePtLpOracleIsNotInitialized(uint16);
constructor(address _pendle) {
if (_pendle == address(0)) revert ZeroAddress();
pendle = IPPtLpOracle(_pendle);
}
/// @notice Create token pair oracle price params. Can be called only once per pair.
/// @param quoteToken address of IbToken or PtToken
/// @param baseToken address of PtToken or IbToken
/// @param pendleMarket Address of PendleMarket contract with PtToken and IbToken
/// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
/// @param secondsAgoLiquidation Same as `secondsAgo`, but for liquidation case
function setPair(
address quoteToken,
address baseToken,
address pendleMarket,
uint16 secondsAgo,
uint16 secondsAgoLiquidation
) external onlyOwner {
if (secondsAgo == 0 || secondsAgoLiquidation == 0) revert WrongValue();
if (secondsAgo < secondsAgoLiquidation) revert WrongValue();
if (quoteToken == address(0) || baseToken == address(0) || pendleMarket == address(0)) {
revert ZeroAddress();
}
if (getParams[quoteToken][baseToken].pendleMarket != address(0)) revert PairAlreadyExist();
_assertOracleIsInitialized(pendleMarket, secondsAgo);
(IStandardizedYield sy, IPPrincipalToken pt, ) = PendleMarketV3(pendleMarket).readTokens();
address ibToken;
if (baseToken == address(pt)) {
ibToken = quoteToken;
} else if (quoteToken == address(pt)) {
ibToken = baseToken;
} else {
revert WrongPtAddress();
}
if (!sy.isValidTokenIn(ibToken) || !sy.isValidTokenOut(ibToken)) revert WrongIbTokenAddress();
uint8 ptDecimals = IERC20Metadata(baseToken).decimals();
uint8 syDecimals = IERC20Metadata(address(sy)).decimals();
uint8 ibDecimals = IERC20Metadata(ibToken).decimals();
//We assume that sy ib ratio is 1:1 and decimals for both tokens are equals
if (syDecimals != ibDecimals) revert WrongIbSyDecimals();
OracleParams memory oracleParams = OracleParams({
pendleMarket: pendleMarket,
ibToken: ibToken,
secondsAgo: secondsAgo,
secondsAgoLiquidation: secondsAgoLiquidation,
ptSyDecimalsDelta: PRICE_DECIMALS + ptDecimals - syDecimals
});
getParams[quoteToken][baseToken] = oracleParams;
getParams[baseToken][quoteToken] = oracleParams;
}
/// @notice Update `secondsAgo` and `secondsAgoLiquidation` for token pair
/// @param quoteToken Quote token address, IbToken e.g. ezETH
/// @param baseToken PT token e.g. PT-ezETH-27JUN2024
/// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means
/// @param secondsAgoLiquidation Same as `secondsAgo`, but for liquidation case
function updateTwapDuration(
address quoteToken,
address baseToken,
uint16 secondsAgo,
uint16 secondsAgoLiquidation
) external onlyOwner {
if (secondsAgoLiquidation == 0) revert WrongValue();
if (secondsAgo < secondsAgoLiquidation) revert WrongValue();
OracleParams memory oracleParams = getParams[quoteToken][baseToken];
if (oracleParams.pendleMarket == address(0)) revert UnknownPair();
oracleParams.secondsAgo = secondsAgo;
oracleParams.secondsAgoLiquidation = secondsAgoLiquidation;
getParams[quoteToken][baseToken] = oracleParams;
getParams[baseToken][quoteToken] = oracleParams;
}
/// @notice Check Pendle oracle is initialized - https://docs.pendle.finance/Developers/Integration/HowToIntegratePtAndLpOracle#third-initialize-the-oracle
function _assertOracleIsInitialized(address pendleMarket, uint16 secondsAgo) private view {
(bool increaseCardinalityRequired, , bool oldestObservationSatisfied) = pendle.getOracleState(
pendleMarket,
secondsAgo
);
if (increaseCardinalityRequired) revert PendlePtLpOracleIsNotInitialized(secondsAgo);
if (!oldestObservationSatisfied) revert PendlePtLpOracleIsNotInitialized(secondsAgo);
}
function getBalancePrice(address quoteToken, address baseToken) external view returns (uint256) {
return _getPriceX96(quoteToken, baseToken, false);
}
function getMargincallPrice(address quoteToken, address baseToken) external view returns (uint256) {
return _getPriceX96(quoteToken, baseToken, true);
}
function _getPriceX96(
address quoteToken,
address baseToken,
bool isMargincallPrice
) private view returns (uint256 priceX96) {
OracleParams storage poolParams = getParams[quoteToken][baseToken];
if (poolParams.pendleMarket == address(0)) revert UnknownPair();
uint256 pendlePrice = pendle.getPtToSyRate(
poolParams.pendleMarket,
isMargincallPrice ? poolParams.secondsAgoLiquidation : poolParams.secondsAgo
);
priceX96 = poolParams.ibToken == quoteToken
? Math.mulDiv(pendlePrice, X96ONE, 10 ** poolParams.ptSyDecimalsDelta)
: Math.mulDiv(X96ONE, 10 ** poolParams.ptSyDecimalsDelta, pendlePrice);
}
}