Skip to content

Commit 111d171

Browse files
committed
chore Add Code comments
1 parent 89091d9 commit 111d171

File tree

5 files changed

+137
-14
lines changed

5 files changed

+137
-14
lines changed

src/morpho-pyth/MorphoPythOracle.sol

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,23 @@ import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
1212

1313
/// @title MorphoPythOracle
1414
/// @author Pyth Data Association
15-
/// @notice Morpho Blue oracle using Pyth Price Feeds.
15+
/// @notice Morpho oracle implementation that combines Pyth price feeds with ERC-4626 vault pricing
16+
/// @dev This oracle calculates prices by combining multiple data sources:
17+
/// - Up to 2 Pyth price feeds each for base and quote assets
18+
/// - Optional ERC-4626 vault share-to-asset conversion for base and quote
19+
/// - Configurable staleness checks for price feed age validation
20+
///
21+
/// Price Calculation Formula:
22+
/// price = SCALE_FACTOR * (baseVaultAssets * baseFeed1 * baseFeed2) / (quoteVaultAssets * quoteFeed1 * quoteFeed2)
23+
///
24+
/// Security Considerations:
25+
/// - Single priceFeedMaxAge used for all feeds may not suit different asset volatilities
26+
/// - ERC-4626 vaults can be manipulated through donations, flash loans, or fee changes
27+
/// - Pyth confidence intervals are not validated, potentially accepting uncertain prices
28+
/// - Conversion samples must be large enough to avoid rounding to zero
29+
///
30+
/// @dev This contract follows Morpho's design philosophy prioritizing flexibility over safety.
31+
/// Users must validate all configuration parameters and monitor oracle behavior.
1632
contract MorphoPythOracle is IMorphoPythOracle {
1733
using Math for uint256;
1834

@@ -50,8 +66,40 @@ contract MorphoPythOracle is IMorphoPythOracle {
5066
uint256 public immutable SCALE_FACTOR;
5167

5268
/// @inheritdoc IMorphoPythOracle
69+
/// @dev WARNING: Single staleness threshold applied to all feeds regardless of asset characteristics.
70+
/// Fast-moving assets may need shorter max age (e.g., 15s) while stable assets could tolerate longer (e.g.,
71+
/// 60s).
72+
/// Using a universal value may reject valid stable prices or accept stale volatile prices.
73+
/// Consider asset-specific staleness checks for improved accuracy and reliability.
5374
uint256 public PRICE_FEED_MAX_AGE;
5475

76+
/// @notice Initializes a new MorphoPythOracle instance
77+
/// @dev Constructor performs parameter validation but cannot prevent all misconfigurations.
78+
/// Users must ensure parameters are appropriate for their use case.
79+
///
80+
/// @param pyth_ Address of the Pyth contract - must be the official Pyth contract for the chain
81+
/// @param baseVault ERC-4626 vault for base asset, or address(0) to skip vault conversion
82+
/// @param baseVaultConversionSample Sample shares amount for base vault conversion (must provide adequate
83+
/// precision)
84+
/// @param baseFeed1 First Pyth price feed ID for base asset, or bytes32(0) for price=1
85+
/// @param baseFeed2 Second Pyth price feed ID for base asset, or bytes32(0) for price=1
86+
/// @param baseTokenDecimals Decimal places for base token
87+
/// @param quoteVault ERC-4626 vault for quote asset, or address(0) to skip vault conversion
88+
/// @param quoteVaultConversionSample Sample shares amount for quote vault conversion (must provide adequate
89+
/// precision)
90+
/// @param quoteFeed1 First Pyth price feed ID for quote asset, or bytes32(0) for price=1
91+
/// @param quoteFeed2 Second Pyth price feed ID for quote asset, or bytes32(0) for price=1
92+
/// @param quoteTokenDecimals Decimal places for quote token
93+
/// @param priceFeedMaxAge Maximum acceptable age in seconds for price feeds (applies to all feeds)
94+
///
95+
/// @dev CRITICAL: Conversion samples must be large enough that convertToAssets() returns non-zero values.
96+
/// Small samples may round to zero, breaking price calculations. Test with actual vault implementations!
97+
///
98+
/// @dev VAULT SECURITY: If using vaults, ensure they are trusted implementations resistant to:
99+
/// - Share price manipulation via direct token transfers
100+
/// - Flash loan attacks that temporarily affect asset/share ratios
101+
/// - Dynamic fee changes that alter convertToAssets() results
102+
/// - First depositor attacks setting malicious initial exchange rates
55103
constructor(
56104
address pyth_,
57105
IERC4626 baseVault,
@@ -105,6 +153,21 @@ contract MorphoPythOracle is IMorphoPythOracle {
105153
/* PRICE */
106154

107155
/// @inheritdoc IOracle
156+
/// @notice Calculates the current price by combining vault asset values and Pyth feed prices
157+
/// @return The calculated price with 18 decimal precision
158+
/// @dev Price calculation: SCALE_FACTOR * (baseAssets * baseFeeds) / (quoteAssets * quoteFeeds)
159+
///
160+
/// SECURITY WARNINGS:
161+
/// - Vault prices can be manipulated if vaults are not manipulation-resistant
162+
/// - Single PRICE_FEED_MAX_AGE applied to all feeds regardless of asset volatility
163+
/// - Pyth confidence intervals are ignored - uncertain prices may be accepted
164+
/// - No per-block deviation caps - prices can change drastically within one block
165+
///
166+
/// @dev This function will revert if:
167+
/// - Any Pyth feed returns a negative price
168+
/// - Any feed is older than PRICE_FEED_MAX_AGE
169+
/// - Vault convertToAssets calls fail
170+
/// - Arithmetic overflow in multiplication/division
108171
function price() external view returns (uint256) {
109172
return SCALE_FACTOR.mulDiv(
110173
BASE_VAULT.getAssets(BASE_VAULT_CONVERSION_SAMPLE)

src/morpho-pyth/MorphoPythOracleFactory.sol

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,42 @@ import {MorphoPythOracle} from "./MorphoPythOracle.sol";
99

1010
/// @title MorphoPythOracleFactory
1111
/// @author Pyth Data Association
12-
/// @notice This contract allows to create MorphoPythOracle oracles, and to index them easily.
12+
/// @notice Factory contract for creating MorphoPythOracle instances with permissionless deployment
13+
/// @dev This factory provides a permissionless way to deploy MorphoPythOracle contracts. Users should carefully
14+
/// validate all parameters and resulting oracle configurations before use in production environments.
15+
///
16+
/// Security Considerations:
17+
/// - This factory accepts arbitrary Pyth contract addresses and feed IDs without validation
18+
/// - Market creators and users must verify oracle addresses and configurations independently
19+
/// - The factory only tracks that an oracle was deployed via this factory, not that it's safe to use
20+
/// - Malicious actors can deploy oracles with fake Pyth contracts or manipulated vault addresses
21+
///
22+
/// @dev Following Morpho's design philosophy, this factory prioritizes flexibility over built-in safety checks.
23+
/// See Morpho documentation on oracle risks and validation requirements.
1324
contract MorphoPythOracleFactory is IMorphoPythOracleFactory {
1425
/* STORAGE */
1526

1627
/// @inheritdoc IMorphoPythOracleFactory
28+
/// @dev This mapping only indicates that an oracle was deployed via this factory.
29+
/// It does NOT guarantee the oracle configuration is safe or uses trusted parameters.
30+
/// Users must independently verify oracle parameters including Pyth address and vault addresses.
1731
mapping(address => bool) public isMorphoPythOracle;
1832

1933
/* EXTERNAL */
2034

2135
/// @inheritdoc IMorphoPythOracleFactory
36+
/// @dev SECURITY WARNING: This function accepts arbitrary addresses and parameters without validation.
37+
/// Callers can provide malicious Pyth contracts, manipulable vaults, or invalid feed IDs.
38+
///
39+
/// Critical Validation Required by Users:
40+
/// - Verify `pyth` address matches the official Pyth contract for your chain
41+
/// - Validate all feed IDs exist and correspond to intended price feeds
42+
/// - Ensure vault addresses are trusted ERC-4626 implementations if used
43+
/// - Check that conversion samples provide adequate precision without overflow
44+
/// - Verify `priceFeedMaxAge` is appropriate for all asset types involved
45+
///
46+
/// @dev Following Morpho's trust model: "Market creators and users need to carefully validate
47+
/// the oracle address and its configuration." This includes all parameters passed to this function.
2248
function createMorphoPythOracle(
2349
address pyth,
2450
IERC4626 baseVault,

src/morpho-pyth/interfaces/IMorphoPythOracle.sol

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
77

88
/// @title IMorphoPythOracle
99
/// @author Pyth Data Association
10-
/// @notice Interface of MorphoPythOracle.
11-
/// @dev This interface is used to interact with the MorphoPythOracle contract.
12-
/// @dev Fetch price feed ids from https://www.pyth.network/developers/price-feed-ids
10+
/// @notice Interface for MorphoPythOracle - a Morpho Blue compatible oracle using Pyth price feeds
11+
/// @dev This interface extends IOracle to provide access to oracle configuration parameters.
12+
/// All configuration is immutable after deployment and should be carefully validated.
13+
///
14+
/// @dev Price feed IDs can be found at: https://www.pyth.network/developers/price-feed-ids
15+
/// Ensure feed IDs correspond to the intended assets before deployment.
16+
///
17+
/// @dev This oracle combines Pyth price feeds with optional ERC-4626 vault share pricing.
18+
/// Users must validate that all components (Pyth contract, feeds, vaults) are trustworthy.
1319
interface IMorphoPythOracle is IOracle {
1420
/// @notice Returns the address of the Pyth contract deployed on the chain.
1521
function pyth() external view returns (IPyth);

src/morpho-pyth/libraries/PythFeedLib.sol

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,43 @@ import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
66
import {PythErrorsLib} from "./PythErrorsLib.sol";
77
/// @title PythFeedLib
88
/// @author Pyth Data Association
9-
/// @notice Library exposing functions to interact with a Pyth feed.
9+
/// @notice Library exposing functions to interact with a Pyth feed
10+
/// @dev This library provides basic price fetching from Pyth feeds with staleness protection.
11+
///
12+
/// SECURITY LIMITATION: This implementation ignores Pyth's confidence intervals (conf field).
13+
/// Pyth aggregates prices from multiple providers and returns both the price and a confidence
14+
/// interval indicating price uncertainty. Large confidence intervals suggest unreliable prices
15+
/// that should potentially be rejected.
1016

1117
library PythFeedLib {
12-
/// @dev Returns the price of a `priceId`.
13-
/// @dev When `priceId` is the address zero, returns 1.
14-
/// @dev If the price is older than `maxAge`, throws `0x19abf40e` StalePrice Error.
18+
/// @notice Returns the price of a Pyth price feed
19+
/// @param pyth The Pyth contract instance
20+
/// @param priceId The Pyth price feed identifier, or bytes32(0) to return 1
21+
/// @param maxAge Maximum acceptable price age in seconds
22+
/// @return The price value (always positive)
23+
/// @dev When `priceId` is bytes32(0), returns 1 (useful for omitting feeds in calculations)
24+
/// @dev Reverts with `0x19abf40e` StalePrice error if price is older than `maxAge`
25+
/// @dev Reverts if price is negative (should not occur with valid Pyth feeds)
26+
///
27+
/// SECURITY WARNING: This function ignores the confidence interval (price.conf) returned by Pyth.
1528
function getPrice(IPyth pyth, bytes32 priceId, uint256 maxAge) internal view returns (uint256) {
1629
if (priceId == bytes32(0)) return 1;
1730

1831
PythStructs.Price memory price = pyth.getPriceNoOlderThan(priceId, maxAge);
1932
require(int256(price.price) >= 0, PythErrorsLib.NEGATIVE_ANSWER);
33+
34+
// NOTE: price.conf (confidence interval) is not validated here
35+
// Large conf values indicate uncertain prices that may need rejection
36+
2037
return uint256(int256(price.price));
2138
}
22-
/// @dev Returns the number of decimals of a `priceId`.
23-
/// @dev When `priceId` is the address zero, returns 0.
39+
/// @notice Returns the number of decimal places for a Pyth price feed
40+
/// @param pyth The Pyth contract instance
41+
/// @param priceId The Pyth price feed identifier, or bytes32(0) to return 0
42+
/// @return The number of decimal places for the price feed
43+
/// @dev When `priceId` is bytes32(0), returns 0 (useful for omitting feeds in calculations)
44+
/// @dev Uses getPriceUnsafe() which does not validate price age - only for decimal retrieval
45+
/// @dev Converts negative exponent to positive decimal count (e.g., expo=-8 returns 8)
2446

2547
function getDecimals(IPyth pyth, bytes32 priceId) internal view returns (uint256) {
2648
if (priceId == bytes32(0)) return 0;

src/morpho-pyth/libraries/VaultLib.sol

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@ import {IERC4626} from "../../interfaces/IERC4626.sol";
55

66
/// @title VaultLib
77
/// @author Pyth Data Association
8-
/// @notice Library exposing functions to price shares of an ERC4626 vault.
8+
/// @notice Library exposing functions to price shares of an ERC4626 vault
9+
/// @dev This library provides share-to-asset conversion for ERC-4626 vaults used in price calculations.
10+
/// Users should only use vaults with manipulation-resistant designs and trusted governance.
11+
912
library VaultLib {
10-
/// @dev Converts `shares` into the corresponding assets on the `vault`.
11-
/// @dev When `vault` is the address zero, returns 1.
13+
/// @notice Converts vault shares to underlying asset amount
14+
/// @param vault The ERC-4626 vault contract, or address(0) to return 1
15+
/// @param shares The amount of vault shares to convert
16+
/// @return The equivalent amount of underlying assets
17+
/// @dev When `vault` is address(0), returns 1 (useful for skipping vault conversion)
1218
function getAssets(IERC4626 vault, uint256 shares) internal view returns (uint256) {
1319
if (address(vault) == address(0)) return 1;
1420

0 commit comments

Comments
 (0)