versioned interface implementation with strict ERC-165 validation - #199
Conversation
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
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
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
falsefor ISubmodule but delegating tosuperfor 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 redundantsupportsInterfaceoverride.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.
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.
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
ISubmoduleinterface before installation.Changes
Core Architecture
src/Submodules.sol: Enhanced submodule base contractSubmodulenow implementsISubmoduleandIVersionedsupportsInterface()function with ERC-165 supportModule_SubmoduleInterfaceNotImplementederrorsrc/interfaces/ISubmodule.sol: New interface (MIT-licensed)IVersionedPARENT(),SUBKEYCODE(),INIT()functionsSubmodule Interface Validation
_validateSubmodule(): Installation fails if:supportsInterface(staticcall fails)falseforISubmodule.interfaceIdCode Simplification
PriceSubmodule.supportsInterface()super.supportsInterface()Test Coverage
SubmoduleInstallationTest.t.solfalseforISubmodulefailssupportsInterfacefailsMockInvalidSubmodule,MockSubmoduleNoERC165Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.