Skip to content

Price Feed Resilience: Add support for Pyth oracles - #193

Merged
0xJem merged 19 commits into
price-feed-improvementsfrom
feature/pyth-price-feeds
Jan 9, 2026
Merged

0xJem merged 19 commits into
price-feed-improvementsfrom
feature/pyth-price-feeds

Conversation

@0xJem

@0xJem 0xJem commented Jan 7, 2026 •

Copy link
Copy Markdown
Member
  • Adds a PRICE submodule for Pyth oracles

Summary by CodeRabbit

  • New Features

    • Added Pyth Network price feed integration supporting single and dual-feed price operations, scaling across output decimals and handling feed exponents; enforces freshness and confidence limits and reports interface/version support.
  • Tests

    • Added extensive test suites covering success paths, parameter validation, error cases, exponent/rounding behaviors, stale/missing feeds, and multi-feed arithmetic.

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Introduces Pyth oracle support and plugs it into the PRICE module with strict validation.

  • New IPyth interface and PythPriceFeeds submodule (PRICE.PYTH) exposing getOneFeedPrice, getTwoFeedPriceDiv, and getTwoFeedPriceMul
  • Validates feed responses: positive price, freshness via updateThreshold, confidence bound (scaled across decimals), non-positive expo, and return-size checks; uses low-level staticcall
  • Adds SafeCast.encodeUInt64 used for confidence scaling
  • Test infra: MockPyth and comprehensive unit tests covering params, errors, exponent/rounding, division/multiplication, and supportsInterface
  • Fork test updates: installs PythPriceFeeds alongside Chainlink, configures WETH with real ETH/USD Pyth feed

Written by Cursor Bugbot for commit 89121b0. This will update automatically on new commits. Configure here.

@0xJem 0xJem self-assigned this Jan 7, 2026
@coderabbitai

coderabbitai Bot commented Jan 7, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds IPyth interface and SafeCast helper; implements a new PythPriceFeeds submodule that queries and validates Pyth feed prices (single and two-feed div/mul flows); provides MockPyth and extensive Forge tests; wires PythPriceFeeds into an existing fork test for WETH/ETH-USD.

Changes

Cohort / File(s) Summary
Interfaces & Utilities
src/interfaces/IPyth.sol, src/libraries/SafeCast.sol
New IPyth interface with Price struct and getPriceNoOlderThan(bytes32,uint256). Adds SafeCast.encodeUInt64(uint256) for safe downcast with overflow revert.
Pyth Price Feeds Submodule
src/modules/PRICE/submodules/feeds/PythPriceFeeds.sol
New PythPriceFeeds contract: parameter decoding (OneFeedParams/TwoFeedParams), IPyth staticcall integration, result validation (price/conf/publishTime/expo), scaling to output decimals, and public methods getOneFeedPrice, getTwoFeedPriceDiv, getTwoFeedPriceMul. Declares custom errors and supports IERC165/IVersioned.
Test Mocks
src/test/mocks/MockPyth.sol
MockPyth implements IPyth, with s_prices mapping, setPrice(...), and getPriceNoOlderThan(...) that reverts with PriceFeedNotFound or StalePrice when appropriate.
Test Fixtures & Helpers
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/PythPriceFeedsTest.sol
Test fixture deploying kernel, mock oracles, Pyth submodule; provides encodeOneFeedParams and encodeTwoFeedParams, seeds feed data and timewarp setup.
getOneFeedPrice Tests
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol
Extensive tests covering success, parameter validation, interface/return-data mismatches, stale/missing/invalid price cases, confidence/exponent boundary cases, and output-decimal behaviors.
getTwoFeedPriceDiv Tests
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol
Comprehensive division-path tests: success, denominator rounding-to-zero, parameter validation, feed validity/staleness, exponent/confidence edge-cases, and output-decimal conversions.
getTwoFeedPriceMul Tests
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceMul.t.sol
Comprehensive multiplication-path tests mirroring division suite: success and many edge/error scenarios, precision and scaling validations.
supportsInterface Tests
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/supportsInterface.t.sol
Tests supportsInterface for IERC165 (true), IVersioned (true), and a false case for an unsupported id.
Fork Integration
src/test/modules/PRICE/OlympusPricev1_2Fork.t.sol
Wires PythPriceFeeds into fork test: adds PYTH and WETH constants, deploys submodule, and configures WETH using Pyth ETH/USD feed.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PythPriceFeeds
    participant IPyth as PythContract
    participant Validator as _validatePriceFeedResult

    Client->>PythPriceFeeds: getOneFeedPrice(outputDecimals, params)
    PythPriceFeeds->>PythPriceFeeds: decode params & validate inputs
    PythPriceFeeds->>IPyth: staticcall getPriceNoOlderThan(priceId, age)
    IPyth-->>PythPriceFeeds: returns Price(price, conf, expo, publishTime) / revert
    PythPriceFeeds->>Validator: validate price, conf, publishTime
    Validator-->>PythPriceFeeds: ok / revert
    PythPriceFeeds->>PythPriceFeeds: enforce expo ≤ 0, scale/round to outputDecimals
    PythPriceFeeds-->>Client: return scaled uint256 price
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

audit-convertible-deposits-trust

Poem

🐇
I nibble feeds and count by paw,
Pyth whispers prices, neat and raw,
I scale the decimals, hop with zest,
Two feeds tango, passing the test,
Mocks and suites — a tasty jest!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Price Feed Resilience: Add support for Pyth oracles' clearly and concisely summarizes the main change: adding Pyth oracle support to the price feed system.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@0xJem
0xJem marked this pull request as ready for review January 7, 2026 14:29
@cursor

cursor Bot commented Jan 7, 2026

Copy link
Copy Markdown

You have run out of free Bugbot PR reviews for this billing cycle. This will reset on February 9.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@0xJem

0xJem commented Jan 7, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 7, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
src/test/modules/PRICE/OlympusPricev1_2Fork.t.sol (1)

189-190: Address the TODO comment before merging.

The TODO indicates pending cleanup to remove the Chainlink mock price feed. Consider tracking this in an issue if it's intentionally deferred.

Would you like me to open an issue to track the removal of the Chainlink mock price feed?

src/test/mocks/MockPyth.sol (1)

37-40: Potential underflow in stale price check.

If block.timestamp < age, the subtraction block.timestamp - age will underflow in Solidity 0.8+, causing a revert. While unlikely in practice (age is typically small), this differs from Pyth's actual behavior which handles this edge case.

Consider adding a guard or noting this limitation:

♻️ Suggested fix
         // Check if price is stale
-        if (priceData.publishTime < block.timestamp - age) {
+        if (age > block.timestamp || priceData.publishTime < block.timestamp - age) {
             revert StalePrice();
         }
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol (1)

1149-1178: Consider extending fuzz range for outputDecimals.

The test bounds outputDecimals_ to [18, 36]. While this avoids precision loss with EXPO_3 = -18, consider adding a separate test with outputDecimals < 18 using feeds with smaller absolute exponents to ensure the scaling logic works in both directions.

src/modules/PRICE/submodules/feeds/PythPriceFeeds.sol (1)

396-411: Consider extracting shared parameter validation.

The parameter validation logic for TwoFeedParams is duplicated between getTwoFeedPriceDiv (lines 339-352) and getTwoFeedPriceMul (lines 398-411). Consider extracting to a private helper function.

♻️ Suggested refactor
+    /// @notice Validates TwoFeedParams
+    function _validateTwoFeedParams(TwoFeedParams memory params) internal pure {
+        if (params.firstPyth == address(0)) revert Pyth_ParamsPythInvalid(0, params.firstPyth);
+        if (params.firstPriceFeedId == bytes32(0))
+            revert Pyth_ParamsPriceFeedIdInvalid(1, params.firstPriceFeedId);
+        if (params.firstUpdateThreshold == 0)
+            revert Pyth_ParamsUpdateThresholdInvalid(2, params.firstUpdateThreshold);
+        if (params.firstMaxConfidence == 0)
+            revert Pyth_ParamsMaxConfidenceInvalid(3, params.firstMaxConfidence);
+        if (params.secondPyth == address(0)) revert Pyth_ParamsPythInvalid(4, params.secondPyth);
+        if (params.secondPriceFeedId == bytes32(0))
+            revert Pyth_ParamsPriceFeedIdInvalid(5, params.secondPriceFeedId);
+        if (params.secondUpdateThreshold == 0)
+            revert Pyth_ParamsUpdateThresholdInvalid(6, params.secondUpdateThreshold);
+        if (params.secondMaxConfidence == 0)
+            revert Pyth_ParamsMaxConfidenceInvalid(7, params.secondMaxConfidence);
+    }

Then replace the inline checks in both functions with _validateTwoFeedParams(params);

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (1)

493-515: Test name may be slightly misleading.

The test test_getOneFeedPrice_confidenceEqualsMaximum tests with CONF_1 = 1000000 which converts to 1e16, but MAX_CONFIDENCE = 2e16. This is actually testing confidence below maximum, not equal to maximum. Consider renaming or adjusting the test to truly test the boundary case.

💡 Suggested fix

To truly test confidence at the maximum boundary, set confidence to exactly 2e6 (which converts to 2e16 in output decimals):

 function test_getOneFeedPrice_confidenceEqualsMaximum() public {
-    // Confidence interval equals maximum threshold
-    // CONF_1 = 1000000 with expo=-8 converts to 1e16 in output decimals
-    // MAX_CONFIDENCE = 2e16, so CONF_1 should pass
-    // This test verifies that a confidence that is below the maximum passes
-    pyth.setPrice(PRICE_ID_1, PRICE_1, CONF_1, EXPO_1, block.timestamp);
+    // Set confidence exactly at maximum (2e6 in Pyth scale = 2e16 in output decimals)
+    uint64 exactMaxConf = 2e6;
+    pyth.setPrice(PRICE_ID_1, PRICE_1, exactMaxConf, EXPO_1, block.timestamp);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dde51b6 and 172a139.

📒 Files selected for processing (10)
  • src/interfaces/IPyth.sol
  • src/libraries/SafeCast.sol
  • src/modules/PRICE/submodules/feeds/PythPriceFeeds.sol
  • src/test/mocks/MockPyth.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/PythPriceFeedsTest.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceMul.t.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/supportsInterface.t.sol
  • src/test/modules/PRICE/OlympusPricev1_2Fork.t.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: src/modules/TRSRY/TRSRY.v1.sol:2-2
Timestamp: 2025-07-18T00:21:49.138Z
Learning: 0xJem prefers to avoid changing historical/already-deployed contracts to minimize risk and maintain stability, even when it would improve consistency across the codebase.
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: remappings.txt:35-40
Timestamp: 2025-07-18T00:22:32.511Z
Learning: 0xJem prefers to keep OpenZeppelin 4.8.0 as the standard/default dependency in remappings.txt for stability, with newer versions like 5.3.0 available only through explicit versioned dependency paths (e.g., openzeppelin-5.3.0/) to ensure intentional opt-in to newer versions.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
🔇 Additional comments (34)
src/libraries/SafeCast.sol (1)

28-33: LGTM!

The encodeUInt64 function follows the established pattern of other safe cast functions in the library. The overflow check and revert behavior are consistent with the existing implementations.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/supportsInterface.t.sol (1)

12-37: LGTM!

The test coverage for supportsInterface is comprehensive, covering both supported interfaces (IERC165, IVersioned) and verifying that unsupported interfaces return false. The test structure is clean and follows established patterns.

src/test/modules/PRICE/OlympusPricev1_2Fork.t.sol (2)

244-281: LGTM!

The WETH asset configuration with Pyth feed is well-structured. The 24-hour update threshold and $10 max confidence (10e18 in 18 decimals) are reasonable values for ETH/USD price feed tolerance. The comment at line 268 correctly notes that the call will revert if the Pyth feed fails.


57-62: PYTH mainnet address is correct. The address 0x4305FB66699C3B2702D4d05CF36551390A4c69C6 matches the official Pyth Network deployment on Ethereum mainnet per Pyth Developer Docs.

src/test/mocks/MockPyth.sol (1)

7-43: LGTM overall!

The MockPyth implementation correctly replicates Pyth's interface and error semantics. The setPrice function provides flexibility for test scenarios, and getPriceNoOlderThan appropriately validates price existence and staleness.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/PythPriceFeedsTest.sol (2)

60-92: LGTM!

The test setup is well-organized with clear documentation of the test price values and their decimal representations. The timestamp warp and price feed initialization correctly establish the boundary conditions for staleness testing.


97-127: LGTM!

The helper functions for encoding one-feed and two-feed parameters provide a clean abstraction for test cases. The encoding matches the expected parameter structure of PythPriceFeeds.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceMul.t.sol (6)

15-44: LGTM!

The success test case correctly validates the multiplication logic: (first_price * second_price) / 10^outputDecimals. The expected calculation is well-documented in comments.


46-228: LGTM!

Comprehensive parameter validation tests covering all edge cases for zero/invalid values across both feeds. The error expectations correctly match the parameter indices in the encoded params structure.


284-368: LGTM!

The price feed not found and stale price tests correctly validate error conditions. The fuzz bounds for publish time properly test the stale price boundary condition.


370-650: LGTM!

Thorough testing of first feed exponent handling scenarios. The tests correctly verify:

  • Positive exponents trigger Pyth_ExponentPositive error
  • Negative/zero exponents correctly scale prices to output decimals
  • Confidence thresholds are properly validated in Pyth's scale

682-967: LGTM!

Second feed exponent tests mirror the first feed tests, ensuring symmetric coverage. The calculations and expected values are correctly documented.


999-1027: LGTM!

The output decimals fuzz test provides good coverage for different decimal configurations. The bounds [18, 36] are appropriate to avoid underflow in exponent calculations, and the max confidence scaling correctly adapts to the output decimals.

src/interfaces/IPyth.sol (1)

13-18: The publishTime field in the Price struct is correctly declared as uint256, matching Pyth Network's official interface.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol (7)

1-17: LGTM - Test file setup and imports are correct.

The test contract properly extends PythPriceFeedsTest and imports necessary dependencies including FullMath for precise calculations and MockPyth for simulating price feed behavior.


22-41: LGTM - Success test correctly validates two-feed division.

The test properly sets up two feeds with different exponents and validates the division calculation using mulDiv for precision.


43-65: LGTM - Edge case for zero denominator handled correctly.

The test properly validates that when the denominator price rounds to zero due to exponent scaling, the function returns 0 without reverting, matching the implementation's early exit behavior.


67-249: LGTM - Comprehensive parameter validation tests.

All eight parameters of TwoFeedParams are tested for zero/invalid values with correct error selectors and parameter indices (0-3 for first feed, 4-7 for second feed).


251-341: LGTM - Feed validation tests cover invalid prices and missing feeds.

The fuzz tests properly bound prices to invalid ranges (≤0), and the price feed not found tests correctly expect MockPyth.PriceFeedNotFound for unregistered feed IDs.


343-389: LGTM - Stale price tests correctly validate freshness boundaries.

The fuzz tests properly bound publish times to stale values and expect MockPyth.StalePrice errors for both feeds.


391-536: LGTM - First feed exponent and confidence tests are thorough.

The tests correctly validate confidence interval calculations across different exponent scenarios including negative, zero, and positive exponents. The boundary cases for confidence at/below maximum are properly tested.

src/modules/PRICE/submodules/feeds/PythPriceFeeds.sol (8)

1-18: LGTM - License, pragma, and imports are appropriate.

The contract uses AGPL-3.0 license, requires Solidity ≥0.8.15 for overflow protection, and imports necessary interfaces and libraries.


25-141: LGTM - Well-structured parameter definitions and comprehensive error handling.

The struct definitions properly separate single and dual feed configurations. Error declarations include parameter indices for easier debugging and appropriate types for all fields.


173-202: LGTM - Validation logic is correct and complete.

The function properly validates positive price, publish time freshness, and confidence bounds. The pure visibility is appropriate since all data comes from parameters.


213-284: LGTM - Feed price retrieval is well-implemented with proper error handling.

The function correctly:

  • Uses staticcall for view-safe external calls
  • Validates return data length before decoding
  • Rejects positive exponents to prevent precision loss
  • Converts confidence and price between Pyth scale and output decimals scale
  • Uses FullMath.mulDiv for overflow-safe calculations

295-319: LGTM - Single feed price function is clean and correct.

Parameter validation covers all four fields with appropriate error indices. The unused first parameter follows the expected interface pattern.


332-378: LGTM - Two-feed division correctly handles zero denominator.

The function properly validates all parameters, handles the zero denominator edge case gracefully, and uses mulDiv for precision-safe division with proper scaling.


391-433: LGTM - Two-feed multiplication is correctly implemented.

The function properly validates parameters and uses mulDiv to compute the product while maintaining the correct decimal scale.


441-444: LGTM - Standard ERC165 interface support.

The implementation correctly reports support for IERC165 and IVersioned interfaces.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (5)

1-14: LGTM - Test file imports and structure are correct.

The test contract properly extends PythPriceFeedsTest and imports necessary dependencies.


18-100: LGTM - Success and parameter validation tests are comprehensive.

All four parameters are tested for zero/invalid values with correct error selectors and parameter indices.


102-231: LGTM - Feed validation tests cover all error paths.

Tests properly validate:

  • Invalid Pyth contract detection (no code at address)
  • Wrong return data length detection
  • Error bubbling from underlying Pyth calls
  • Invalid price values (≤0)
  • Missing price feeds
  • Stale price data

252-491: LGTM - Exponent handling tests are thorough.

Tests cover all exponent scenarios including negative, zero, positive (revert), equal to negative output decimals, and very negative values. Confidence interval calculations in expected errors are correctly computed.


591-614: LGTM - Output decimals fuzz test correctly handles variable precision.

The test appropriately bounds output decimals to avoid precision loss and correctly scales the expected values based on the exponent difference.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (1)

489-512: Consider adding explicit boundary test for confidence equals maximum.

The fuzz test test_confidenceBelowMaximum bounds confidence to [1, 2e6], which could occasionally hit the exact boundary. However, the two-feed tests have explicit confidenceEqualsMaximum tests while this file lacks one. Adding an explicit boundary test like test_confidenceEqualsMaximum (similar to Line 445-471 in getTwoFeedPriceDiv.t.sol) would strengthen boundary condition coverage.

🔧 Suggested test to add
// given the confidence interval is at the maximum (boundary case)
//  [X] it correctly converts the price
function test_confidenceEqualsMaximum() public {
    // expo = -8, outputDecimals = 18
    // confidenceExponent = 18 + (-8) = 10
    // maxConfidenceInPythScale = maxConfidence / 10^10 = 2e16 / 1e10 = 2e6
    // Test with confidence exactly at the maximum (2e6)
    uint64 priceConfidence_ = 2e6;
    pyth.setPrice(PRICE_ID_1, PRICE_1, priceConfidence_, EXPO_1, block.timestamp);

    bytes memory params = encodeOneFeedParams(
        address(pyth),
        PRICE_ID_1,
        UPDATE_THRESHOLD,
        MAX_CONFIDENCE
    );
    uint256 priceInt = pythSubmodule.getOneFeedPrice(address(0), PRICE_DECIMALS, params);

    assertEq(
        priceInt,
        EXPECTED_PRICE_1_18_DEC,
        "Price should match expected when confidence equals maximum"
    );
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 172a139 and 176fdb2.

📒 Files selected for processing (3)
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceMul.t.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: src/modules/TRSRY/TRSRY.v1.sol:2-2
Timestamp: 2025-07-18T00:21:49.138Z
Learning: 0xJem prefers to avoid changing historical/already-deployed contracts to minimize risk and maintain stability, even when it would improve consistency across the codebase.
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: remappings.txt:35-40
Timestamp: 2025-07-18T00:22:32.511Z
Learning: 0xJem prefers to keep OpenZeppelin 4.8.0 as the standard/default dependency in remappings.txt for stability, with newer versions like 5.3.0 available only through explicit versioned dependency paths (e.g., openzeppelin-5.3.0/) to ensure intentional opt-in to newer versions.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
🔇 Additional comments (13)
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceDiv.t.sol (4)

1-16: LGTM - Well-structured test file with comprehensive coverage.

The test contract properly extends PythPriceFeedsTest and uses FullMath for precise arithmetic operations. The test organization follows a clear pattern covering success cases, parameter validation, and edge cases.


251-276: Good fuzzing coverage for invalid price scenarios.

The fuzz test properly bounds the price to type(int64).min to 0 inclusive, ensuring all non-positive values trigger the Pyth_FeedPriceInvalid error. This catches both zero and negative prices.


1143-1171: Comprehensive output decimals fuzzing with proper scaling.

The test correctly scales MAX_CONFIDENCE proportionally when changing output decimals, and properly calculates expected values using the formula price * 10^(outputDecimals - expo). The bound of [18, 36] prevents overflow while testing meaningful ranges.


43-65: The test correctly validates intentional behavior. The getTwoFeedPriceDiv function explicitly returns 0 when the denominator price is zero, as documented in the implementation with comments stating "The PRICE module will handle the zero value." This is a deliberate architectural design where zero handling is delegated to the PRICE module, not a hidden edge case.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getTwoFeedPriceMul.t.sol (4)

1-44: LGTM - Multiplication test correctly calculates expected result.

The success test properly computes the expected result as (first * second) / 10^outputDecimals:

  • First feed: 1234567890000000000 (1.23456789 in 18 decimals)
  • Second feed: 500000000
  • Result: 1234567890000000000 * 500000000 / 10^18 = 617283945000

The mulDiv usage with denominator 10^PRICE_DECIMALS correctly implements this formula.


46-228: Parameter validation tests are thorough and correct.

All eight parameters (2 feeds × 4 params each) have validation tests with correct error selector indices matching their positions in the encoded params. This ensures comprehensive input validation coverage.


556-583: LGTM - Correctly tests precision loss when product is very small.

When first feed = 1e9 (expo=-18) and second feed = 500000000, the product divided by 10^18 rounds down to 0. The expected value calculation 1e9 * 500000000 / 10^18 = 0 is correct, and this edge case is important for understanding precision limitations.


1023-1115: Good coverage of precision loss scenarios.

The tests comprehensively cover scenarios where:

  1. Price < 1 in the scaled representation loses precision (Line 1026)
  2. Price rounds down to zero entirely (Line 1059)
  3. Price >= 1 but fractional part is lost (Line 1087)

This helps users understand the precision characteristics of the multiplication operation.

src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (5)

1-34: LGTM - Clean test structure with proper success case verification.

The success test correctly validates that price 123456789 with expo=-8 converts to 1234567890000000000 in 18 decimals (multiplied by 10^10).


124-159: Excellent test for return data validation.

This test verifies the implementation correctly rejects malformed return data by mocking a successful call that returns only 64 bytes instead of the expected 128 bytes (_PRICE_DATA_SIZE). This protects against oracle implementations returning truncated or malformed data.

The vm.clearMockedCalls() cleanup at Line 158 is good practice.


161-193: Good test for error propagation.

Using vm.mockCallRevert to verify that custom errors from the Pyth contract bubble up correctly is important for debugging in production. This ensures callers receive meaningful error information rather than generic failures.


563-585: Smart fuzzing bounds prevent false negatives from precision loss.

The bound [8, 36] for outputDecimals_ ensures the exponent difference is always non-negative (since expo=-8), so precision loss doesn't interfere with the fuzz test's purpose of verifying decimal conversion correctness. The precision loss scenarios are tested separately in dedicated tests below.


587-651: Thorough precision loss test coverage.

The three precision loss tests comprehensively cover:

  1. Price < 1: 23456789 (0.23456789) → 234567 in 6 decimals
  2. Price rounds to 0: 89 (0.00000089) → 0 in 6 decimals
  3. Price >= 1: 123456789 (1.23456789) → 1234567 in 6 decimals

These tests document the expected precision behavior for downstream consumers.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

);

return feedPrice;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing outputDecimals bounds validation causes unclear overflow panics

Low Severity

The PythPriceFeeds functions (getOneFeedPrice, getTwoFeedPriceDiv, getTwoFeedPriceMul) don't validate that outputDecimals_ is within bounds before computing 10 ** uint256(outputDecimals_). The comparable ChainlinkPriceFeeds implementation validates outputDecimals_ <= BASE_10_MAX_EXPONENT (50) and reverts with a clear error if exceeded. Without this check, extreme outputDecimals_ values (> ~77) cause the exponentiation to overflow uint256, resulting in a generic panic instead of a descriptive error message. This creates inconsistent error handling between the two price feed implementations.

Additional Locations (2)

Fix in Cursor Fix in Web

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (2)

124-193: Mock lifecycle: consider clearing mocks consistently after mockCallRevert too.
You clear in test_returnDataLengthInvalid_reverts but not after test_pythRevertsWithData_bubblesUpRevert; depending on forge-std behavior, this can reduce any chance of cross-test interference.

Proposed diff
 function test_pythRevertsWithData_bubblesUpRevert() public {
@@
     pythSubmodule.getOneFeedPrice(address(0), PRICE_DECIMALS, params);
+
+    vm.clearMockedCalls();
 }

252-285: Hardcoded “derived” confidence thresholds are a bit brittle.
Values like 2e6, 2e16, 2e18, 10 are correct per the comments, but will silently desync if MAX_CONFIDENCE, PRICE_DECIMALS, or expo conventions change; consider a small helper to compute “maxConfInPythScale” in one place.

Also applies to: 333-366, 386-469, 489-538

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 176fdb2 and 89121b0.

📒 Files selected for processing (1)
  • src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: src/modules/TRSRY/TRSRY.v1.sol:2-2
Timestamp: 2025-07-18T00:21:49.138Z
Learning: 0xJem prefers to avoid changing historical/already-deployed contracts to minimize risk and maintain stability, even when it would improve consistency across the codebase.
Learnt from: 0xJem
Repo: OlympusDAO/olympus-v3 PR: 29
File: remappings.txt:35-40
Timestamp: 2025-07-18T00:22:32.511Z
Learning: 0xJem prefers to keep OpenZeppelin 4.8.0 as the standard/default dependency in remappings.txt for stability, with newer versions like 5.3.0 available only through explicit versioned dependency paths (e.g., openzeppelin-5.3.0/) to ensure intentional opt-in to newer versions.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
  • GitHub Check: run-ci
🔇 Additional comments (3)
src/test/modules/PRICE.v2/submodules/feeds/PythPriceFeeds/getOneFeedPrice.t.sol (3)

1-34: Nice, tight “happy path” coverage; view is appropriate here.
This sets the baseline expected scaling behavior clearly.


36-123: Param validation + “non-IPyth” negative path looks solid.
Minor: using an EOA-like address (address(0x1234)) can be slightly less explicit than deploying a dummy contract that intentionally fails interface checks, but it’s still fine if the module treats “no code” as invalid.


588-676: Output-decimals coverage is strong (precision loss + round-to-zero cases are especially valuable).
The max-confidence rescaling in params matches the intent of testing non-18 decimal outputs.

@0xJem
0xJem merged commit 0f0c679 into price-feed-improvements Jan 9, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant