Skip to content

versioned interface implementation with strict ERC-165 validation - #199

Merged
0xJem merged 10 commits into
price-feed-improvementsfrom
versioned-interface-implementation
Jan 29, 2026
Merged

0xJem merged 10 commits into
price-feed-improvementsfrom
versioned-interface-implementation

Conversation

@0xJem

@0xJem 0xJem commented Jan 28, 2026 •

Copy link
Copy Markdown
Member

Summary

This PR implements strict ERC-165 interface validation for submodule installation. Building on the price feed improvements in the base branch, it ensures all submodules properly implement the ISubmodule interface before installation.

Changes

Core Architecture

  • src/Submodules.sol: Enhanced submodule base contract

    • Submodule now implements ISubmodule and IVersioned
    • Added supportsInterface() function with ERC-165 support
    • Added Module_SubmoduleInterfaceNotImplemented error
  • src/interfaces/ISubmodule.sol: New interface (MIT-licensed)

    • Extends IVersioned
    • Defines PARENT(), SUBKEYCODE(), INIT() functions
    • Provides type safety and interface compliance checking

Submodule Interface Validation

  • Strict ERC-165 requirement in _validateSubmodule(): Installation fails if:
    1. The contract doesn't implement supportsInterface (staticcall fails)
    2. The contract returns false for ISubmodule.interfaceId
    3. The contract returns empty data

Code Simplification

  • Removed unnecessary override from PriceSubmodule.supportsInterface()
  • Super delegation: All concrete PRICE submodules now use super.supportsInterface()

Test Coverage

  • New test file: SubmoduleInstallationTest.t.sol
    • Valid submodule installation succeeds
    • Submodule returning false for ISubmodule fails
    • Submodule without supportsInterface fails
  • New mocks: MockInvalidSubmodule, MockSubmoduleNoERC165

Summary by CodeRabbit

  • New Features

    • Added a formal submodule interface and ERC‑165 validation to ensure only compatible submodules can be installed or upgraded.
  • Bug Fixes

    • Installation/upgrade now rejects submodules that don't advertise required interfaces, preventing invalid deployments.
  • Tests

    • Added tests and mocks covering successful installs and failure cases for invalid or non‑ERC‑165 submodules; updated snapshot.
  • Chores

    • Simplified version-return code and import/path cleanups across price feed modules; adjusted compiler profile settings.

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

0xJem added 6 commits January 28, 2026 17:22
Add IVersioned interface implementation and ERC-165 supportsInterface()
function to PRICE v2 contracts for version discoverability and interface
compliance.

Changes:
- Submodule base class now inherits IVersioned and provides base
  supportsInterface() implementation
- PRICE.v2 now implements VERSION() returning (2, 0)
- All feed and strategy submodules now override supportsInterface()
  with IERC165 and IVersioned interface ID support
- Organized imports according to project style guidelines
Extract ISubmodule interface from Submodule abstract contract for better
type safety and interface compliance checking. Submodules must now
implement ISubmodule to be installable.

Changes:
- Add ISubmodule interface extending IVersioned with PARENT(), SUBKEYCODE(),
  and INIT() functions
- Submodule abstract contract now implements ISubmodule
- Add validation in _validateSubmodule to check candidate implements
  ISubmodule via ERC-165 supportsInterface check
- All PRICE submodule supportsInterface() functions now return true for
  ISubmodule interface ID
Simplify submodule supportsInterface() implementations by using super
delegation to the Submodule base class, and organize imports according
to project style guidelines.

Changes:
- Submodule.supportsInterface() changed from external to public for super calls
- PriceSubmodule now implements supportsInterface() that delegates to super
- All PRICE submodule supportsInterface() implementations now use super.supportsInterface()
- Removed unnecessary IERC165, IVersioned, ISubmodule imports from submodules
- Organized imports alphabetically within each section (Interfaces, Libraries, Bophades)
- Added tests for submodule installation validation

Tests: 3100 tests passing (2 new tests added)
…riceSubmodule

The override in PriceSubmodule was unnecessary since Submodule.supportsInterface()
cannot reach PRICEv2.supportsInterface() - they are in separate inheritance chains.
Concrete submodules using super.supportsInterface() now call directly to Submodule.

This simplifies the code and removes an unnecessary override layer.
Require submodules to properly implement ISubmodule via ERC-165
supportsInterface check. Installation now fails if:

1. The contract doesn't implement supportsInterface at all (staticcall fails)
2. The contract returns false for ISubmodule.interfaceId

Previously, contracts without supportsInterface would bypass validation
silently. This closes that loophole.

Tests:
- Added MockSubmoduleNoERC165 to test missing supportsInterface case
- Expanded SubmoduleInstallationTest with distinct test cases for:
  - Submodule with supportsInterface returning false for ISubmodule
  - Submodule without supportsInterface function at all
@0xJem 0xJem self-assigned this Jan 28, 2026
@coderabbitai

coderabbitai Bot commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds an ISubmodule interface and enforces ERC‑165 support for submodules during install/upgrade via staticcall; Submodule base updated to implement IVersioned and ERC‑165; PRICE submodules updated (VERSION refactor, some supportsInterface changes); new test mocks and a test suite validate installation failures; one snapshot numeric value updated.

Changes

Cohort / File(s) Summary
Submodule Core & Interface
src/Submodules.sol, src/interfaces/ISubmodule.sol
New ISubmodule interface; Submodule abstract now implements IVersioned/ISubmodule and advertises ERC‑165 via supportsInterface. Installation/upgrade now staticcalls supportsInterface(ISubmodule) and reverts with Module_SubmoduleInterfaceNotImplemented(address) if unsupported.
PRICE Module
src/modules/PRICE/PRICE.v2.sol
Adds VERSION() returning (2,0).
PRICE Feed & Strategy Submodules
src/modules/PRICE/submodules/feeds/..., src/modules/PRICE/submodules/strategies/SimplePriceFeedStrategy.sol
Import path reordering and consolidation; VERSION() implementations simplified to return tuples directly; some files had supportsInterface removed or relocated (notably PythPriceFeeds).
Test Mocks
src/test/mocks/MockInvalidSubmodule.sol, src/test/mocks/MockSubmoduleNoERC165.sol
Adds mocks that intentionally fail ERC‑165/ISubmodule validation (one returns false for ISubmodule, one omits ERC‑165).
Tests
src/test/modules/PRICE.v2/SubmoduleInstallation.t.sol
Adds tests covering successful installation and expected reverts for submodules that fail ERC‑165/ISubmodule validation.
Build Config & Scripts
foundry.toml, package.json
Adjusts optimizer runs profile from 5000→4000 for a specific compile target and updates related npm script.
Snapshot
snapshots/DepositRedemptionVaultFinishRedemptionTest.json
Updated numeric snapshot: "redeem" changed from "201080" to "203580".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Module
    participant Submodule
    participant ERC165 as IERC165

    User->>Module: installSubmodule(submoduleAddr)
    Module->>Module: _validateSubmodule(submoduleAddr)
    Module->>Submodule: staticcall supportsInterface(ISubmodule)
    Submodule->>ERC165: evaluate interfaceId
    alt supports ISubmodule
        ERC165-->>Submodule: true
        Submodule-->>Module: true
        Module->>Module: proceed with installation
        Module-->>User: installation succeeded
    else does not support
        ERC165-->>Submodule: false
        Submodule-->>Module: false
        Module-->>User: revert Module_SubmoduleInterfaceNotImplemented(submoduleAddr)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I hopped through code to check each badge,

I tapped interfaces, avoiding a fidget.
Submodules wear their badges true,
No sly surprises slip on through.
🥕 Hooray — installs now pass the widget!

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR objectives include implementing strict ERC-165 validation, versioning support, and submodule architecture changes. However, the linked issue #39 states 'It doesn't contain any contract updates,' which contradicts the substantial contract changes (Submodules.sol, ISubmodule.sol, multiple PRICE submodules, test mocks) present in this PR. Clarify whether the linked issue accurately describes this PR's scope. If #39 should not include contract updates, this PR should be linked to different issues (e.g., #27 or #37) that describe the versioning and ERC-165 validation features.
Out of Scope Changes check ❓ Inconclusive Most changes align with the stated objectives of ERC-165 validation and versioning implementation. However, the foundry.toml and package.json changes (reducing OlympusPrice.v1_2.sol optimizer runs from 5000 to 4000) appear unrelated to the versioning/ERC-165 validation work. Confirm whether the optimizer configuration changes for OlympusPrice.v1_2.sol are necessary for this PR or should be addressed separately in a build optimization PR.
✅ 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 'versioned interface implementation with strict ERC-165 validation' clearly and specifically describes the main architectural changes in the PR, which align with the primary objectives of implementing ERC-165 validation and versioning support.
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.

@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

🤖 Fix all issues with AI agents
In `@src/Submodules.sol`:
- Around line 190-200: The supportsInterface staticcall result handling on
address(newSubmodule_) must guard against short/malformed return data before
abi.decode; after the staticcall check (success and data.length != 0) also
require data.length >= 32 and treat anything shorter as a validation failure,
then only abi.decode(data, (bool)); on failure (success false, data.length < 32,
or decoded bool false) revert with
Module_SubmoduleInterfaceNotImplemented(address(newSubmodule_)) so callers
always get the intended custom error instead of an abi.decode panic.
🧹 Nitpick comments (2)
src/test/mocks/MockInvalidSubmodule.sol (1)

21-25: Keep ERC-165 semantics intact in the mock.

Right now the mock claims support for all interface IDs except ISubmodule, which is non‑ERC165‑compliant and could mask future interface checks. Consider returning false for ISubmodule but delegating to super for everything else.

♻️ Proposed refinement
-    function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
-        // Always return false for ISubmodule interface ID, making this contract fail validation
-        return interfaceId != type(ISubmodule).interfaceId;
-    }
+    function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
+        if (interfaceId == type(ISubmodule).interfaceId) return false;
+        return super.supportsInterface(interfaceId);
+    }
src/modules/PRICE/submodules/feeds/PythPriceFeeds.sol (1)

432-437: Consider removing the redundant supportsInterface override.

This override only delegates to super.supportsInterface(interfaceId) without adding any logic. Solidity's inheritance will automatically invoke the parent's implementation, making this override unnecessary. This pattern is repeated across all PRICE submodules (BalancerPoolTokenPrice, ChainlinkPriceFeeds, ERC4626Price, UniswapV2PoolTokenPrice, UniswapV3Price, SimplePriceFeedStrategy, and PythPriceFeeds), suggesting it could be removed from all of them for consistency.

If there's a specific reason for keeping this explicit override (e.g., documentation purposes, ABI clarity, or ensuring the function is included in the contract's interface), please disregard this suggestion.

Comment thread src/Submodules.sol Outdated
0xJem added 4 commits January 29, 2026 12:19
Require exactly 32 bytes of return data from supportsInterface
staticcall before decoding. This prevents abi.decode panic on
malformed return data and ensures callers always get the
intended Module_SubmoduleInterfaceNotImplemented custom error.
Add forge-lint disable directives to mock submodule contracts to suppress
warnings for uppercase function names (SUBKEYCODE, PARENT, VERSION, INIT)
required by the ISubmodule interface.
@0xJem
0xJem merged commit 2f6fadc into price-feed-improvements Jan 29, 2026
7 of 8 checks passed
@0xJem
0xJem deleted the versioned-interface-implementation branch January 29, 2026 09:05
@coderabbitai coderabbitai Bot mentioned this pull request Mar 5, 2026
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