Skip to content

ResourceLockValidator, test suite cleanup and Soldeer integration #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

cryptonoyaiba
Copy link
Collaborator

@cryptonoyaiba cryptonoyaiba commented Feb 20, 2025

Description

  • Added ResourceLockValidator module and test suite.
  • Cleanup of test suite as a whole by making each contract its own test utils file and changing TestAdvancedUtils to ModularTestBase that moves test specific utils into their own contract util files and make utils in ModularTestBase extensible and generic.
  • Integration of Soldeer package manager.

Motivation and Context

  • New validator module to compliment CredibleAccountModule for validation of resource locks for enabling credible account sessions.
  • Upgrade of test suite to separate test specific utilities and more generic test utilities.
  • Move to a package manager rather than relying on git submodule for packages.

How Has This Been Tested?

  • Ran complete test suite and ensure that all are still passing.

Screenshots (if appropriate)

Screenshot 2025-02-20 at 17 16 19

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Summary by CodeRabbit

  • New Features
    • Introduced a resource lock validator module that enhances secure session key management and signature verification.
    • Added a new constants file defining standard values for Ethereum signature and module types.
    • Implemented a new interface for resource lock validators with event tracking.
  • Documentation
    • Revised installation instructions with a new dependencies section and improved clarity.
    • Added detailed documentation for the ResourceLockValidator module, including integration guidance.
  • Chores
    • Upgraded dependency management by replacing outdated submodules with more robust configurations using a native dependency manager.
    • Streamlined configuration settings, remappings, and build scripts for improved performance and clarity.
    • Updated import paths across modules to align with new dependency structure.
    • Simplified hook multiplexer contract by removing registry dependency and related checks.
  • Tests
    • Added comprehensive test suites for ResourceLockValidator, CredibleAccountModule, ERC20SessionKeyValidator, and modular wallet contracts.
    • Refactored and expanded test utilities and harnesses to improve modular testing capabilities.
    • Removed deprecated or redundant test files to streamline testing framework.
  • Release Scripts
    • Added deployment script for key modular wallet contracts including multiplexer, proof verifier, credible account module, and resource lock validator.

- Created `ResourceLockValidator` module which can handle standard ECDSA signed `UserOperation` and `UserOperation` with `ResourceLock` merkle proofs.
- Added test suite for `ResourceLockValidator`.
- Added `soldeer` dependency manager and reinstalled required dependencies (updated imports as required).
- Removed existing dependencies , `lib` folder and `remappings.txt`.
- Added dependency configs to `foundry.toml`.
- Added new `ModularTestBase` to replace `TestAdvancedUtils` and cleaned up functionality to make it more generic and extensible.
- Reorganised tests into folders and added specific test utils if needed which extend `ModularTestBase`.
- Created own `Constants` file which we can migrate to eventually to reduce dependency on ERC7579 implementation.
Copy link

coderabbitai bot commented Feb 20, 2025

"""

Walkthrough

This pull request updates dependency management and configuration files, along with significant restructuring of the project’s module and test architecture. Submodules and commit references have been removed in favor of new dependency directories and remappings. The documentation and configuration (e.g., README, foundry.toml, .env.example) are updated with new instructions and sections. New modules—including the Resource Lock Validator—and interfaces have been introduced, and various modules have been refactored with improved logging, error handling, and standardized import paths. Additionally, many test files have been added, modified, or removed to align with the updated modular design.

Changes

File(s) Change Summary
.gitignore, .gitmodules Removed node_modules/ reference and added dependencies/ in .gitignore; entire .gitmodules file removed, including all submodule entries
README.md, foundry.toml, remappings.txt, .env.example README updated with correct installation instructions and a new “Dependencies” section; foundry.toml restructured with new dependency and remapping sections; remappings.txt deleted; new environment variable added in .env.example
docs/RESOURCE_LOCK_VALIDATOR.md, src/common/Constants.sol, src/interfaces/IResourceLockValidator.sol, src/modules/validators/ResourceLockValidator.sol New documentation for ResourceLockValidator added; new constants defined; new interface and validator contract introduced for resource lock validation
src/common/Structs.sol Modified ResourceLock struct: removed nonce field, added bidHash field
src/modules/validators/CredibleAccountModule.sol, src/modules/validators/ERC20SessionKeyValidator.sol CredibleAccountModule improved with refined function signatures, error handling, and added logging; ERC20SessionKeyValidator updated with absolute import paths
src/modules/hooks/HookMultiPlexer.sol Simplified contract by removing registry adapter inheritance and registry attestation checks; reformatted functions and events for brevity
lib/... (account-abstraction, forge-std, openzeppelin-contracts, solady) Removed commit reference lines used for subproject tracking
test/harnesses/CredibleAccountModuleHarness.sol, test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol, test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol, test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol, test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol, test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol, test/modules/SessionKeyValidator/fuzz/SessionKeyValidator.t.sol, test/modules/SessionKeyValidator/utils/SessionKeyTestUtils.sol, test/modules/ERC1155FallbackHandler/concrete/ERC1155FallbackHandler.t.sol, test/modules/ERC20SessionKeyValidator/concrete/ERC20SessionKeyValidator.t.sol, test/wallet/ModularEtherspotWallet/concrete/ModularEtherspotWallet.t.sol, test/wallet/ModularEtherspotWallet/utils/ModularEtherspotWalletTestUtils.sol, test/wallet/ModularEtherspotWalletFactory/concrete/ModularEtherspotWalletFactory.t.sol, test/wallet/ModularEtherspotWalletFactory/fuzz/ModularEtherspotWalletFactory.t.sol, test/ModularTestBase.sol Added new test harnesses, concrete tests, fuzz tests, and utility contracts; restructured and updated tests for modular wallet, session key validators, credible account module, and resource lock validator; replaced or removed outdated test files
test/TestAdvancedUtils.t.sol, test/modules/ERC20SessionKeyValidator.t.sol, test/wallet/ModularEtherspotWallet.t.sol, test/wallet/ModularEtherspotWalletFactory.t.sol Deleted legacy or superseded test files
script/NonDeterministicDeployAllAndSetup.s.sol Import updated for Bootstrap; constant declarations and log statements reformatted for improved readability
script/CredibleAccountScript.s.sol New deployment script added to deploy HookMultiPlexer, ProofVerifier, CredibleAccountModule, and ResourceLockValidator contracts sequentially
src/test/dependencies/EntryPoint.sol Added new patched EntryPoint simulation contract and utility functions for Foundry testing environment

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant W as Modular Wallet
    participant V as ResourceLockValidator
    participant C as CryptoLib (ECDSA/MerkleProof)
    
    U->>W: Submit User Operation
    W->>V: Call validateUserOp(userOp, userOpHash)
    V->>C: Verify ECDSA signature & Merkle proof
    C-->>V: Return verification result
    V->>W: Return validation status
Loading

Poem

Oh, what a hop this change does bring,
New validators and cleaner strings!
Submodules out, dependencies in,
A modular garden where tests now spin.
I’m a bunny dancing in code so bright,
Leaping through updates with joy and delight!
🐇✨ Happy coding, day and night!
"""

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (66)
script/DeployAllAndSetup.s.sol (2)

59-75: Add comprehensive error handling for deployment failures.

While the script handles address mismatches, it could benefit from additional error handling:

  1. Check contract code size after deployment to verify successful deployment
  2. Add try-catch blocks for deployment operations
  3. Add revert messages for deployment failures

Example implementation:

 if (EXPECTED_IMPLEMENTATION.code.length == 0) {
+    try {
         implementation = new ModularEtherspotWallet{salt: SALT}();
+        if (address(implementation).code.length == 0) {
+            revert("Deployment failed: No code at implementation address");
+        }
         if (address(implementation) != EXPECTED_IMPLEMENTATION) {
             revert("Unexpected wallet implementation address!!!");
         } else {
             console2.log(
                 "Wallet implementation deployed at address",
                 address(implementation)
             );
         }
+    } catch Error(string memory reason) {
+        revert(string.concat("Deployment failed: ", reason));
+    } catch {
+        revert("Deployment failed: Unknown error");
+    }
 }

Also applies to: 80-99, 104-119, 124-145


44-44: Consider making stake amount and delay configurable.

The current implementation uses hardcoded values:

  • FACTORY_STAKE = 1e16 (0.01 ETH) might be too low for production
  • Unstake delay of 86400 seconds (1 day) might need adjustment per network

Consider making these values configurable:

-uint256 public constant FACTORY_STAKE = 1e16;
+uint256 public immutable FACTORY_STAKE;
+uint256 public immutable UNSTAKE_DELAY;
+
+constructor(uint256 factoryStake, uint256 unstakeDelay) {
+    FACTORY_STAKE = factoryStake;
+    UNSTAKE_DELAY = unstakeDelay;
+}

Then update the staking call:

-factory.addStake{value: FACTORY_STAKE}(address(entryPoint), 86400);
+factory.addStake{value: FACTORY_STAKE}(address(entryPoint), UNSTAKE_DELAY);

Also applies to: 150-156

src/common/Structs.sol (1)

49-58: Add NatSpec documentation for the ResourceLock struct.

While the struct's fields are well-defined, it lacks the comprehensive NatSpec documentation present in other structs. Consider adding documentation to maintain consistency and improve code readability.

Add the following documentation:

 // ResourceLockValidator
+/// @title ResourceLock
+/// @notice Struct containing data for resource locking
+/// @dev Used to manage and validate resource locks across chains
 struct ResourceLock {
     uint256 chainId;
     address smartWallet;
     address sessionKey;
     uint48 validAfter;
     uint48 validUntil;
     TokenData[] tokenData;
     uint256 nonce;
 }
src/utils/ERC7579HookBase.sol (1)

1-6: Consider pinning or updating the Solidity version.
Currently, the contract specifies pragma solidity ^0.8.23;. Ensure you are on a version that is stable and has no known security issues. If the environment supports a newer patch release (e.g., 0.8.20+), consider upgrading to benefit from available compiler fixes and enhancements.

src/interfaces/ICredibleAccountModule.sol (1)

1-3: Review Solidity version pinning.
As with other files, confirm that pragma solidity 0.8.23; is aligned with the project's overall version strategy and free of known issues.

src/modules/hooks/HookMultiPlexer.sol (7)

20-25: Leverage inherited components effectively.

By inheriting both ERC7484RegistryAdapter and TrustedForwarder, this contract benefits from registry checks and meta-transaction support. Verify that you really need both functionalities in this single contract to keep the module lean.


26-28: Consolidate library usage for clarity.

The widespread usage of HookMultiPlexerLib, LibSort, and other libraries is comprehensive. Ensure that developers are aware of each library’s responsibilities to mitigate confusion about which utilities belong to which library.


197-217: Optimize large-scale hook retrieval.

getHooks merges multiple arrays and sorts them, which can be costly if many hooks exist. Consider caching or more efficient lookups to reduce repeated sorting overhead for large sets.


266-288: Potential duplication in sig hooks.

addSigHook calls push on the array with no check for duplicates beyond the registry attestation. If the same hook is added repeatedly for the same signature, the array can grow unnecessarily.


342-391: Robust transaction gating via preCheck.

preCheck merges subHooks from various arrays, sorts them, and calls each subHook's preCheckSubHook. The sorting ensures uniqueness but can be expensive if many hooks exist. Consider a less expensive approach if the same subHooks get frequently re-run.


424-435: Private accessor naming convention.

$getConfig is used internally for convenience, but the leading $ can be unconventional. If you want to unify naming across your codebase, consider a more standard naming like _getConfig.


464-466: Semantic versioning.

version() returning 1.0.0 is straightforward, but consider implementing semantic versioning or a dynamic version if multi-release changes are expected, to help external integrators track updates.

src/libraries/HookMultiPlexerLib.sol (3)

22-25: Maintain descriptive error messages.

SubHookPreCheckError and SubHookPostCheckError indicate which hook failed, but not why. If debugging on chain is required, consider including a reason string for clarity.


115-149: Efficient array joining.

join(address[] memory a, address[] memory b) inlines an approach to extend arrays in memory. Ensure no out-of-bounds concerns for large arrays.


396-442: Batch execution hooking.

appendExecutionHook elegantly aggregates multiple sub-hooks from batch calls. Consider whether the appended hooks might get re-sorted excessively in the main contract, and if so, explore ways to unify the approach for performance.

test/ModularTestBase.sol (2)

120-186: Comprehensive environment setup in _testInit.

The _testInit function properly sets up mocks, tokens, and the primary wallet. The coverage is broad. Ensure that each segment is needed for core tests, or consider splitting the environment build into smaller, more specialized methods.


201-209: Token distribution in _createUser.

Assigning each user 100 ether and 100 tokens is suitable for general testing. Confirm that subsequent tests don’t require alternative initial balances.

test/modules/ERC1155FallbackHandler/concrete/ERC1155FallbackHandler.t.sol (3)

29-58: Installation test covers essential event emission
The test checks for event emission and uses _installModule with pre-encoded arguments. This is good for verifying success paths. Consider also testing intermediate states, such as partial or invalid encoding, if not already covered elsewhere.


303-326: test_receiveERC1155 – verifying direct token transfer
Good coverage for checking direct transfers from an external account to the modular wallet. Also, ensure you test any overlap with other fallback handlers or potential concurrency issues if batches are executed in parallel.


482-529: Selecting distinct selectors for separate tokens
Installing fallback handlers with different selectors for each token is a clean approach to modularizing calls. Be mindful that expansions (e.g., additional tokens or advanced usage) might require re-installation or more flexible fallback resolution.

src/modules/validators/CredibleAccountModule.sol (1)

241-310: Module type checks and onInstall/onUninstall
The approach to verifying correct installation ensures hooking via hookMultiPlexer. Consider logging or enumerating installed modules to facilitate better debuggability.

test/modules/SessionKeyValidator/fuzz/SessionKeyValidator.t.sol (3)

18-24: Room for additional negative tests
The event checks and setup logic are thorough. Consider adding negative test coverage for invalid session keys or missing session data, if not covered otherwise.


209-233: Fuzzed testFuzz_getSessionKeysByWallet
The incremental approach to adding multiple session keys appears robust. Carefully confirm that session key indexing is correct after each enablement, especially if keys are removed or rotated in other tests.


783-804: testFuzz_updateUses ensures decrement logic
Good approach verifying that permission usage is decremented. Also ensure coverage for edge outcomes (e.g., usage left is 1 or 0) to confirm the correct transition.

src/interfaces/IHookLens.sol (1)

3-5: Add NatSpec documentation for better code clarity.

Consider adding NatSpec documentation to describe the interface's purpose and function behavior:

+/// @title IHookLens
+/// @notice Interface for retrieving the currently active hook
 interface IHookLens {
+    /// @notice Returns the address of the currently active hook
+    /// @return hook The address of the active hook
     function getActiveHook() external view returns (address hook);
 }
src/utils/ProofVerifier.sol (1)

5-6: Fix @inheritdoc usage.

The @inheritdoc tag is used incorrectly. Either:

  1. Add NatSpec documentation in the interface, or
  2. Remove the @inheritdoc tag and add direct documentation
src/interfaces/IProofVerifier.sol (1)

4-9: Enhance documentation with proof format details.

The interface is well-documented, but could benefit from additional details about:

  • Expected proof format/structure
  • Verification failure conditions
  • Example usage
 /// @title IProofVerifier
 /// @author Etherspot
 /// @notice Interface for the contract with verification function to verify proof
 interface IProofVerifier {
+    /// @notice Verifies the provided proof
+    /// @param proof The proof data in bytes format
+    /// @return True if the proof is valid, false otherwise
+    /// @dev The proof should be formatted as [format details here]
     function verifyProof(bytes calldata proof) external view returns (bool);
 }
src/common/Enums.sol (2)

4-12: Add documentation for ComparisonRule enum values.

Consider adding NatSpec documentation to explain each comparison rule's purpose and usage:

-// SessionKeyValidator
+/// @notice Comparison rules used by SessionKeyValidator
 enum ComparisonRule {
+    /// @notice Less than comparison (<)
     LESS_THAN,
+    /// @notice Less than or equal comparison (<=)
     LESS_THAN_OR_EQUAL,
+    /// @notice Equality comparison (==)
     EQUAL,
+    /// @notice Greater than or equal comparison (>=)
     GREATER_THAN_OR_EQUAL,
+    /// @notice Greater than comparison (>)
     GREATER_THAN,
+    /// @notice Not equal comparison (!=)
     NOT_EQUAL
 }

14-21: Add documentation for HookType enum values.

Consider adding NatSpec documentation to explain each hook type's purpose and usage:

-// HookMultiplexer
+/// @notice Hook types supported by HookMultiplexer
 enum HookType {
+    /// @notice Global hooks that apply to all operations
     GLOBAL,
+    /// @notice Hooks specific to delegatecall operations
     DELEGATECALL,
+    /// @notice Hooks for operations involving value transfer
     VALUE,
+    /// @notice Hooks based on function signatures
     SIG,
+    /// @notice Hooks based on target address and function signature
     TARGET_SIG
 }
src/interfaces/IResourceLockValidator.sol (1)

6-13: Add NatSpec documentation for the interface and events.

Consider adding comprehensive NatSpec documentation to improve code readability and maintainability:

+/// @title IResourceLockValidator - Interface for resource lock validation
+/// @notice This interface extends IValidator to provide resource locking capabilities
 interface IResourceLockValidator is IValidator {
     /*//////////////////////////////////////////////////////////////
                                 EVENTS
     //////////////////////////////////////////////////////////////*/
 
+    /// @notice Emitted when a validator is enabled for a smart contract wallet
+    /// @param scw The address of the smart contract wallet
+    /// @param owner The address of the wallet owner
     event RLV_ValidatorEnabled(address indexed scw, address indexed owner);
+    /// @notice Emitted when a validator is disabled for a smart contract wallet
+    /// @param scw The address of the smart contract wallet
     event RLV_ValidatorDisabled(address indexed scw);
 }
src/utils/ERC7484RegistryAdapter.sol (1)

6-20: Add NatSpec documentation for the contract.

Consider adding comprehensive NatSpec documentation to improve code readability and maintainability:

+/// @title ERC7484RegistryAdapter - Abstract contract for ERC7484 registry integration
+/// @notice This contract provides base functionality for interacting with an ERC7484 registry
+/// @dev Inherit from this contract to implement registry-specific functionality
 abstract contract ERC7484RegistryAdapter {
     // registry address
     IERC7484 public immutable REGISTRY;
src/test/mocks/MockModule.sol (1)

6-20: Enhance mock implementation for better testing coverage.

Consider these improvements to make the mock more useful for testing:

  1. Add NatSpec documentation
  2. Add revert cases
  3. Add state tracking for function calls
+/// @title MockModule - Mock implementation of IModule for testing
+/// @dev This contract provides a basic mock with configurable behaviors
 contract MockModule is IModule {
+    // Track function calls for testing
+    bool public installCalled;
+    bool public uninstallCalled;
+    mapping(address => bool) public initializationState;
+
+    // Configurable return values
+    bool public moduleTypeResponse = true;
+
     function isModuleType(uint256) external view returns (bool) {
-        return true;
+        return moduleTypeResponse;
     }
 
-    function onInstall(bytes calldata) external {}
+    function onInstall(bytes calldata) external {
+        installCalled = true;
+    }
 
-    function onUninstall(bytes calldata) external {}
+    function onUninstall(bytes calldata) external {
+        uninstallCalled = true;
+    }
 
     function isInitialized(address smartAccount) external view returns (bool) {
-        return false;
+        return initializationState[smartAccount];
     }
 
+    // Configuration functions for testing
+    function setModuleTypeResponse(bool response) external {
+        moduleTypeResponse = response;
+    }
+
+    function setInitializationState(address smartAccount, bool state) external {
+        initializationState[smartAccount] = state;
+    }
+
     receive() external payable {}
 }
src/interfaces/IHookMultiplexer.sol (1)

6-10: Enhance interface documentation.

The NatSpec documentation could be improved by adding @param and @return descriptions for each function.

test/wallet/ModularEtherspotWallet/utils/ModularEtherspotWalletTestUtils.sol (1)

28-32: Add input validation to _addGuardians function.

The function should validate that the input wallet address is not zero and that guardian public keys are valid.

     function _addGuardians(ModularEtherspotWallet _scw) internal {
+        require(address(_scw) != address(0), "Invalid wallet address");
+        require(guardian1.pub != address(0), "Invalid guardian1 address");
+        require(guardian2.pub != address(0), "Invalid guardian2 address");
+        require(guardian3.pub != address(0), "Invalid guardian3 address");
         _scw.addGuardian(guardian1.pub);
         _scw.addGuardian(guardian2.pub);
         _scw.addGuardian(guardian3.pub);
     }
src/test/mocks/MockHook.sol (2)

2-2: Update Solidity version for consistency.

The file uses ^0.8.21 while other files use ^0.8.23. Consider updating for consistency across the codebase.

-pragma solidity ^0.8.21;
+pragma solidity ^0.8.23;

21-32: Extract magic strings into constants.

The strings "revert", "revertPost", and "success" should be defined as constants to improve maintainability and reduce the risk of typos.

+    string constant REVERT_MESSAGE = "revert";
+    string constant REVERT_POST_MESSAGE = "revertPost";
+    string constant SUCCESS_MESSAGE = "success";

     if (
-        mode == ModeSelector.wrap(bytes4(keccak256(abi.encode("revert"))))
+        mode == ModeSelector.wrap(bytes4(keccak256(abi.encode(REVERT_MESSAGE))))
     ) {
-        revert("revert");
+        revert(REVERT_MESSAGE);
     } else if (
         mode ==
-        ModeSelector.wrap(bytes4(keccak256(abi.encode("revertPost"))))
+        ModeSelector.wrap(bytes4(keccak256(abi.encode(REVERT_POST_MESSAGE))))
     ) {
-        hookData = abi.encode("revertPost");
+        hookData = abi.encode(REVERT_POST_MESSAGE);
     } else {
-        hookData = abi.encode("success");
+        hookData = abi.encode(SUCCESS_MESSAGE);
     }
src/interfaces/IERC7484.sol (2)

2-2: Specify exact Solidity version.

Consider using a specific version instead of ^0.8.0 to ensure consistent behavior across compilations.

-pragma solidity ^0.8.0;
+pragma solidity ^0.8.23;

10-22: Add NatSpec documentation for check functions.

The check functions lack proper documentation explaining their purpose, parameters, and return values.

src/test/mocks/MockRegistry.sol (2)

11-13: Consider using a custom error with a descriptive message.

Replace the basic revert() with a custom error to provide more context about why the check failed.

+    error InvalidModule(address module);
     function check(address module) external view {
         if (module == address(0x420)) {
-            revert();
+            revert InvalidModule(module);
         }
     }

1-70: Reduce code duplication in check functions.

The same validation logic is repeated across multiple check functions. Consider extracting the common logic into a private function.

+    function _checkModule(address module) private pure {
+        if (module == address(0x420)) {
+            revert InvalidModule(module);
+        }
+    }
+
     function check(address module) external view {
-        if (module == address(0x420)) {
-            revert();
-        }
+        _checkModule(module);
     }
     // Apply similar changes to other check functions
src/utils/TrustedForwarder.sol (1)

51-67: Enhance assembly block safety.

The assembly block could benefit from additional safety checks and clearer variable naming.

     function _getAccount() internal view returns (address account) {
         account = msg.sender;
         address _account;
         address forwarder;
-        if (msg.data.length >= 40) {
+        uint256 minDataLength = 40;
+        if (msg.data.length >= minDataLength) {
             assembly {
+                let dataEnd := sub(calldatasize(), 1)
+                if lt(dataEnd, minDataLength) { revert(0, 0) }
                 _account := shr(96, calldataload(sub(calldatasize(), 20)))
                 forwarder := shr(96, calldataload(sub(calldatasize(), 40)))
             }
test/harnesses/CredibleAccountModuleHarness.sol (1)

9-12: Add constructor parameter validation and documentation.

The constructor should validate input parameters and include documentation about test-specific behavior.

+    /// @notice Test harness for CredibleAccountModule
+    /// @dev Exposes internal functions for testing purposes
+    /// @param _proofVerifier Address of the proof verifier contract
+    /// @param _hookMultiPlexer Address of the hook multiplexer contract
     constructor(
         address _proofVerifier,
         address _hookMultiPlexer
-    ) CredibleAccountModule(_proofVerifier, _hookMultiPlexer) {}
+    ) CredibleAccountModule(_proofVerifier, _hookMultiPlexer) {
+        require(_proofVerifier != address(0), "Invalid proof verifier");
+        require(_hookMultiPlexer != address(0), "Invalid hook multiplexer");
+    }
src/ERC7579/core/HookManager.sol (1)

30-34: Document the msg.value addition in preCheck.

The addition of msg.value to preCheck parameters should be documented to explain the change.

+            // Pass msg.value to allow hooks to validate transaction value
             bytes memory hookData = IHook(hook).preCheck(
                 msg.sender,
                 msg.value,
                 msg.data
             );
test/wallet/ModularEtherspotWalletFactory/fuzz/ModularEtherspotWalletFactory.t.sol (1)

27-67: Enhance test coverage with additional assertions and edge cases.

While the test verifies basic account creation, consider adding:

  1. Assertions to verify the initialized state of validators, executors, and hooks
  2. Negative test cases with invalid configurations
  3. Tests for boundary conditions in the initialization parameters
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (2)

72-86: Make test data generation more flexible.

The _generateResourceLock function uses hardcoded values which could make tests brittle. Consider:

  1. Parameterizing values like chainId, timestamps, and nonce
  2. Adding helper functions to generate dynamic test data
  3. Using block.timestamp for time-based values
-function _generateResourceLock(address _scw, address _sk) internal view returns (ResourceLock memory) {
+function _generateResourceLock(
+    address _scw,
+    address _sk,
+    uint256 _chainId,
+    uint256 _validAfter,
+    uint256 _validUntil,
+    uint256 _nonce
+) internal view returns (ResourceLock memory) {
     TokenData[] memory td = new TokenData[](2);
     td[0] = TokenData({token: address(usdt), amount: 100});
     td[1] = TokenData({token: address(dai), amount: 200});
     ResourceLock memory rl = ResourceLock({
-        chainId: 42161,
+        chainId: _chainId,
         smartWallet: _scw,
         sessionKey: _sk,
-        validAfter: 1732176210,
-        validUntil: 1732435407,
+        validAfter: _validAfter,
+        validUntil: _validUntil,
         tokenData: td,
-        nonce: 14
+        nonce: _nonce
     });
     return rl;
 }

18-26: Consider adding validation in withRequiredModules modifier.

The modifier installs multiple modules but doesn't verify if the installation was successful. Consider adding assertions to validate the installation state.

test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol (3)

31-36: Consider adding validation for token amounts.

The test variables for token amounts use hardcoded decimals which might not match all ERC20 tokens.

Consider making the amounts configurable based on token decimals:

-    uint256[3] internal amounts = [100e6, 200e18, 300e18];
+    uint256[3] internal amounts;
+
+    function _setAmounts() internal {
+        amounts[0] = 100 * (10 ** usdc.decimals());
+        amounts[1] = 200 * (10 ** dai.decimals());
+        amounts[2] = 300 * (10 ** usdt.decimals());
+    }

73-88: Consider adding chain ID validation.

The _createResourceLock function uses a hardcoded chain ID.

Consider making the chain ID configurable:

-            chainId: 42161, // Arbitrum
+            chainId: block.chainid,

125-145: Consider adding gas optimization for batch operations.

The _claimTokensBySolver function could be optimized for gas usage.

Consider using a more gas-efficient approach for batch operations:

-        Execution[] memory batch = new Execution[](3);
-        batch[0] = Execution({target: address(usdc), value: 0, callData: usdcData});
-        batch[1] = Execution({target: address(dai), value: 0, callData: daiData});
-        batch[2] = Execution({target: address(usdt), value: 0, callData: usdtData});
+        uint256 batchLength = 0;
+        for (uint256 i; i < 3; i++) {
+            if (amounts[i] > 0) batchLength++;
+        }
+        Execution[] memory batch = new Execution[](batchLength);
+        uint256 j;
+        if (amounts[0] > 0) batch[j++] = Execution({target: address(usdc), value: 0, callData: usdcData});
+        if (amounts[1] > 0) batch[j++] = Execution({target: address(dai), value: 0, callData: daiData});
+        if (amounts[2] > 0) batch[j++] = Execution({target: address(usdt), value: 0, callData: usdtData});
test/wallet/ModularEtherspotWalletFactory/concrete/ModularEtherspotWalletFactory.t.sol (2)

33-72: Consider adding revert test cases.

The test for account creation could include cases where creation should fail.

Consider adding tests for failure scenarios:

+    function test_createAccount_RevertsOnInvalidInitCode() public {
+        bytes memory invalidInitCode = hex"deadbeef";
+        vm.startPrank(eoa.pub);
+        vm.expectRevert("Invalid init code");
+        factory.createAccount({salt: TEST_SALT, initCode: invalidInitCode});
+        vm.stopPrank();
+    }

127-177: Consider adding gas usage assertions.

The event emission test could include gas usage checks to prevent regressions.

Consider adding gas usage assertions:

+        uint256 gasBefore = gasleft();
         scw = ModularEtherspotWallet(
             payable(
                 factory.createAccount({salt: TEST_SALT, initCode: initCode})
             )
         );
+        uint256 gasUsed = gasBefore - gasleft();
+        assertLt(gasUsed, 1000000, "Gas usage too high");
test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (3)

34-78: Consider adding invariant checks for session key data.

The session key enablement test could include additional invariant checks.

Consider adding these invariant checks:

+        // Check invariants
+        assertGt(retrievedData.validUntil, retrievedData.validAfter, "Invalid time range");
+        for (uint256 i; i < lockedTokens.length; ++i) {
+            assertGt(lockedTokens[i].lockedAmount, 0, "Invalid locked amount");
+            assertLt(lockedTokens[i].claimedAmount, lockedTokens[i].lockedAmount, "Invalid claimed amount");
+        }

160-161: Remove debug logging in tests.

Debug logging should not be present in production test code.

-        console2.log("sessionKeyData.validUntil", sessionKeyData.validUntil);

169-187: Strengthen session key validation test.

The session key validation test could be more comprehensive.

Consider adding more specific test cases:

+        // Test with expired session
+        vm.warp(validUntil + 1);
+        assertFalse(cam.validateSessionKeyParams(sessionKey.pub, op), "Should fail for expired session");
+
+        // Test with future session
+        vm.warp(validAfter - 1);
+        assertFalse(cam.validateSessionKeyParams(sessionKey.pub, op), "Should fail for future session");
src/access/AccessController.sol (2)

91-94: Consider adding a more descriptive error message.

The error handling for removing an owner could be more informative by including the reason in the error name, similar to how other errors are named (e.g., WalletNeedsOwner).

-        if (ownerCount <= 1) {
-            revert WalletNeedsOwner();
-        }
+        if (ownerCount <= 1) {
+            revert RemovingLastOwner();
+        }

12-14: Consider adding documentation for state variables.

The state variables lack documentation explaining their purpose and constraints.

     /// State Variables
+    /// @dev Multiplier used for percentage calculations
     uint128 constant MULTIPLY_FACTOR = 1000;
+    /// @dev Threshold for guardian approval (60%)
     uint16 constant SIXTY_PERCENT = 600;
+    /// @dev Default timelock duration for proposals
     uint24 constant INITIAL_PROPOSAL_TIMELOCK = 24 hours;
test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1)

1-553: Consider adding test cases for concurrent resource locks.

The test suite is comprehensive but could benefit from additional test cases:

  1. Multiple concurrent resource locks for the same session key
  2. Resource lock expiration scenarios
  3. Race conditions in resource lock validation
test/modules/ERC20SessionKeyValidator/concrete/ERC20SessionKeyValidator.t.sol (2)

177-191: Fix inconsistent test function naming.

The test function name test_fail_enableSessionKey_invalidValidUntil doesn't follow the established naming convention. Other similar test functions use test_enableSessionKey_RevertIf_*.

-    function test_fail_enableSessionKey_invalidValidUntil() public {
+    function test_enableSessionKey_RevertIf_InvalidValidUntil() public {

557-558: Fix typo in test function name.

The test function name has a typo: ReveertIf should be RevertIf.

-    function test_discardCurrentProposal_ReveertIf_NotOwnerOrGuardianOrSelf() public {
+    function test_discardCurrentProposal_RevertIf_NotOwnerOrGuardianOrSelf() public {
test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (2)

535-535: Hardcoded session key address in test data.

The test uses a hardcoded session key address which might make the test brittle.

Consider using a dynamic address generation or existing test variables for session keys:

-sessionKey: 0xB071527c3721215A46958d172A42E7E3BDd1DF46,
+sessionKey: otherSessionKey.pub,

746-777: Test case could be more comprehensive.

The test for Uniswap V2 swap only checks for failure when there's insufficient balance.

Consider adding positive test cases to verify successful swaps when sufficient balance is available and proper approvals are set.

README.md (1)

18-27: Consider adding version information for Soldeer.

The new Dependencies section introduces Soldeer but doesn't specify the required version.

Consider adding the minimum required version of Soldeer and installation instructions if needed:

## Dependencies

Uses Solidity native dependency manager [Soldeer](https://soldeer.xyz/) as package manager.

+Requires Soldeer v1.0.0 or higher. To install Soldeer:
+
+```bash
+cargo install soldeer
+```
+
To install dependencies:

```bash
forge soldeer install

</blockquote></details>
<details>
<summary>docs/RESOURCE_LOCK_VALIDATOR.md (2)</summary><blockquote>

`35-43`: **Add example signature formats for validation methods.**

The validation methods section would benefit from concrete examples of valid signature formats.

Consider adding code examples showing both standard signatures and merkle proof formats:

```solidity
// Standard 65-byte signature
bytes signature = hex"1234..."; // r, s, v components

// Merkle proof packed signature
bytes signature = abi.encodePacked(
    bytes32 resourceLockHash,
    bytes32[] proof,
    bytes standardSignature
);

92-98: Enhance security considerations section.

The security section could be more detailed about potential risks and mitigations.

Consider adding:

  • Recommendations for merkle tree depth limits
  • Guidelines for timestamp validations
  • Best practices for nonce management
  • Potential front-running considerations
foundry.toml (2)

13-20: Updated Remappings Configuration:
The remappings now explicitly define paths for various dependencies, including:

  • @openzeppelin/contracts pointing to dependencies/@openzeppelin-contracts-5.2.0/
  • ERC4337 pointing to dependencies/eth-infinitism-account-abstraction-0.7/contracts/
  • ERC7579 pointing to src/ERC7579/
  • forge-std pointing to dependencies/forge-std-1.9.6/src/
  • solady and solarray similarly defined.

Please verify that each remapping correctly reflects the folder structure established by your dependency manager (Soldeer) and that version-specific directory naming (e.g., appending -5.2.0 or -0.7) is handled as expected. Additionally, confirm that the mapping for ERC7579 (located in src/ERC7579/) is the intended design for in-project modules versus external dependencies.


34-39: Explicit Dependencies Declaration:
The [dependencies] section now lists the external dependencies along with their versions and, in the case of solarray, the git source and revision. This explicit declaration improves clarity over dependency management.

A couple of points to check:

  • Ensure that the directory names inferred for each dependency (e.g., eth-infinitism-account-abstraction-0.7) match what is expected by the remappings defined above.
  • Verify that any naming conventions between the dependencies and their remapping targets are correctly aligned.

If there’s any ambiguity, consider adding a post-dependency-install verification step to confirm that the generated directory names match your remapping configuration.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ad2e337 and 5572901.

⛔ Files ignored due to path filters (1)
  • soldeer.lock is excluded by !**/*.lock
📒 Files selected for processing (67)
  • .gitignore (1 hunks)
  • .gitmodules (0 hunks)
  • README.md (1 hunks)
  • docs/RESOURCE_LOCK_VALIDATOR.md (1 hunks)
  • foundry.toml (1 hunks)
  • lib/account-abstraction (0 hunks)
  • lib/forge-std (0 hunks)
  • lib/openzeppelin-contracts (0 hunks)
  • lib/solady (0 hunks)
  • remappings.txt (0 hunks)
  • script/DeployAllAndSetup.s.sol (1 hunks)
  • src/ERC7579/core/HookManager.sol (1 hunks)
  • src/ERC7579/interfaces/IERC7579Module.sol (1 hunks)
  • src/ERC7579/test/Bootstrap.t.sol (1 hunks)
  • src/access/AccessController.sol (10 hunks)
  • src/common/Constants.sol (1 hunks)
  • src/common/Enums.sol (2 hunks)
  • src/common/Structs.sol (2 hunks)
  • src/interfaces/ICredibleAccountModule.sol (1 hunks)
  • src/interfaces/IERC20SessionKeyValidator.sol (1 hunks)
  • src/interfaces/IERC7484.sol (1 hunks)
  • src/interfaces/IHookLens.sol (1 hunks)
  • src/interfaces/IHookMultiplexer.sol (1 hunks)
  • src/interfaces/IModularEtherspotWallet.sol (1 hunks)
  • src/interfaces/IProofVerifier.sol (1 hunks)
  • src/interfaces/IResourceLockValidator.sol (1 hunks)
  • src/interfaces/ISessionKeyValidator.sol (1 hunks)
  • src/libraries/ErrorsLib.sol (0 hunks)
  • src/libraries/HookMultiPlexerLib.sol (1 hunks)
  • src/modules/fallbacks/ERC1155FallbackHandler.sol (1 hunks)
  • src/modules/hooks/HookMultiPlexer.sol (1 hunks)
  • src/modules/hooks/ModuleIsolationHook.sol (3 hunks)
  • src/modules/validators/CredibleAccountModule.sol (1 hunks)
  • src/modules/validators/ERC20SessionKeyValidator.sol (1 hunks)
  • src/modules/validators/MultipleOwnerECDSAValidator.sol (1 hunks)
  • src/modules/validators/ResourceLockValidator.sol (1 hunks)
  • src/modules/validators/SessionKeyValidator.sol (1 hunks)
  • src/test/TestExecutor.sol (1 hunks)
  • src/test/mocks/MockHook.sol (1 hunks)
  • src/test/mocks/MockModule.sol (1 hunks)
  • src/test/mocks/MockRegistry.sol (1 hunks)
  • src/test/mocks/MockTarget.sol (1 hunks)
  • src/utils/ERC4337Utils.sol (1 hunks)
  • src/utils/ERC7484RegistryAdapter.sol (1 hunks)
  • src/utils/ERC7579HookBase.sol (1 hunks)
  • src/utils/ProofVerifier.sol (1 hunks)
  • src/utils/TrustedForwarder.sol (1 hunks)
  • src/wallet/ModularEtherspotWallet.sol (1 hunks)
  • test/ModularTestBase.sol (1 hunks)
  • test/TestAdvancedUtils.t.sol (0 hunks)
  • test/harnesses/CredibleAccountModuleHarness.sol (1 hunks)
  • test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (1 hunks)
  • test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (1 hunks)
  • test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol (1 hunks)
  • test/modules/ERC1155FallbackHandler/concrete/ERC1155FallbackHandler.t.sol (2 hunks)
  • test/modules/ERC20SessionKeyValidator.t.sol (0 hunks)
  • test/modules/ERC20SessionKeyValidator/concrete/ERC20SessionKeyValidator.t.sol (1 hunks)
  • test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1 hunks)
  • test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1 hunks)
  • test/modules/SessionKeyValidator/fuzz/SessionKeyValidator.t.sol (30 hunks)
  • test/modules/SessionKeyValidator/utils/SessionKeyTestUtils.sol (4 hunks)
  • test/wallet/ModularEtherspotWallet.t.sol (0 hunks)
  • test/wallet/ModularEtherspotWallet/concrete/ModularEtherspotWallet.t.sol (1 hunks)
  • test/wallet/ModularEtherspotWallet/utils/ModularEtherspotWalletTestUtils.sol (1 hunks)
  • test/wallet/ModularEtherspotWalletFactory.t.sol (0 hunks)
  • test/wallet/ModularEtherspotWalletFactory/concrete/ModularEtherspotWalletFactory.t.sol (1 hunks)
  • test/wallet/ModularEtherspotWalletFactory/fuzz/ModularEtherspotWalletFactory.t.sol (1 hunks)
💤 Files with no reviewable changes (11)
  • lib/solady
  • lib/openzeppelin-contracts
  • .gitmodules
  • src/libraries/ErrorsLib.sol
  • remappings.txt
  • lib/forge-std
  • test/wallet/ModularEtherspotWallet.t.sol
  • test/TestAdvancedUtils.t.sol
  • test/wallet/ModularEtherspotWalletFactory.t.sol
  • test/modules/ERC20SessionKeyValidator.t.sol
  • lib/account-abstraction
✅ Files skipped from review due to trivial changes (11)
  • src/utils/ERC4337Utils.sol
  • src/interfaces/IERC20SessionKeyValidator.sol
  • src/modules/fallbacks/ERC1155FallbackHandler.sol
  • src/interfaces/IModularEtherspotWallet.sol
  • src/interfaces/ISessionKeyValidator.sol
  • src/modules/validators/MultipleOwnerECDSAValidator.sol
  • src/ERC7579/test/Bootstrap.t.sol
  • src/common/Constants.sol
  • src/modules/validators/ERC20SessionKeyValidator.sol
  • src/modules/validators/SessionKeyValidator.sol
  • src/wallet/ModularEtherspotWallet.sol
🧰 Additional context used
🪛 LanguageTool
docs/RESOURCE_LOCK_VALIDATOR.md

[uncategorized] ~87-~87: Loose punctuation mark.
Context: ...ror Conditions - RLV_AlreadyInstalled: Validator already installed for account...

(UNLIKELY_OPENING_PUNCTUATION)

🔇 Additional comments (82)
script/DeployAllAndSetup.s.sol (2)

8-8: LGTM! Import path updated for Soldeer integration.

The import path change from relative to absolute aligns with the PR's objective of transitioning to the Soldeer package manager.


34-43:

❓ Verification inconclusive

Verify the hardcoded expected addresses.

These addresses are crucial for deterministic deployment. Please ensure they are correctly calculated for the target network.

Run the following script to verify the addresses:


🏁 Script executed:

#!/bin/bash
# Description: Verify that the expected addresses match the CREATE2 computation.

# Search for any existing deployments or address computations
rg -A 2 'Expected.*address.*0x[a-fA-F0-9]{40}'

Length of output: 48


Address Verification for Deterministic Deployment

The search command did not return any evidence that the expected addresses are computed using CREATE2. Please manually verify that the addresses defined in script/DeployAllAndSetup.s.sol on lines 34–43:

  • DEPLOYER: 0x09FD4F6088f2025427AB1e89257A44747081Ed59
  • EXPECTED_IMPLEMENTATION: 0x339eAB59e54fE25125AceC3225254a0cBD305A7b
  • EXPECTED_FACTORY: 0x2A40091f044e48DEB5C0FCbc442E443F3341B451
  • EXPECTED_BOOTSTRAP: 0x0D5154d7751b6e2fDaa06F0cC9B400549394C8AA
  • EXPECTED_MULTIPLE_OWNER_ECDSA_VALIDATOR: 0x0740Ed7c11b9da33d9C80Bd76b826e4E90CC1906

...match the derivations (e.g., via CREATE2) expected for your target network. If these values are updated elsewhere or need recomputation, please address that accordingly.

test/modules/SessionKeyValidator/utils/SessionKeyTestUtils.sol (11)

5-9: Imports appear consistent with new architecture.
The updated imports from Solady, ERC4337, and ERC7579 libraries reflect the shift toward a more modular approach for user operations and session handling. No immediate issues found.


14-15: New test contract imports are aligned with expanded testing requirements.
The introductions of TestERC721 and TestUniswapV3 suggest broader test coverage. Ensure these mocked contracts accurately represent the real-world behavior being tested.


16-16: Adoption of ModularTestBase aligns well with the PR’s overarching changes.
Switching to ModularTestBase likely centralizes common test functionality, fostering a more organized test suite.


18-18: Inheritance from ModularTestBase adheres to the new modular testing design.
This reflects the larger transition seen in the PR, removing the previous TestAdvancedUtils in favor of a more general and extensible base.


29-30: Additional internal references for ERC721 and Uniswap test instances.
Declaring these new contract instances helps broaden the test scenarios. Verify they are properly utilized in subsequent tests to maintain full coverage.


38-53: New validatorInstalled modifier for streamlined module installation flow.
Encapsulating the module installation and environment setup code into a modifier is a good approach. Just be cautious about re-entrancy or side effects if it’s used multiple times in a single test file.

Would you like to confirm usage across the suite to ensure this modifier doesn’t inadvertently alter test assumptions?


60-60: Refined _testInit() call and new test contract creations.
The switch to _testInit() and creation of cryptoPunk / uniswapV3 appear consistent with your modular architecture. Ensure these additions are reflected in newly added or updated tests.

Also applies to: 64-65


68-69: Refactored _getSessionKeyAndPermissions to handle a User struct.
Migrating to a User struct with _sessionKey.pub is a clearer pattern. Confirm that alice.pub is declared and properly initialized elsewhere to avoid referencing an undefined test user.

Also applies to: 82-82


99-108: Helper _getExecutionValidation() is straightforward.
Provides a clean interface for setting validAfter and validUntil fields without side effects. Looks correct.


111-111: _setupSingleUserOp method updated for new user operation structure.
Encoding logic for single operation flows is neatly contained. Ensure thorough test coverage for signature creation and validity checks.

Also applies to: 113-113, 116-133


136-152: _setupBatchUserOp method mirrors single-op logic for batch calls.
Adopting a parallel approach to single user operations enhances consistency and clarity. Implementation looks aligned with the new user op model.

src/common/Structs.sol (2)

4-4: LGTM!

The import statement is appropriate for a common file that uses multiple enums.


41-47: LGTM!

The TokenData struct is well-documented and appropriately structured for storing token information.

src/utils/ERC7579HookBase.sol (3)

7-24: Well-structured hook interface integration for preCheck.
The external preCheck function clearly delegates to the internal _preCheck method, promoting extensibility. Documentation comments are concise and align with the parameters.


26-35: postCheck mirrors preCheck effectively.
The external postCheck function provides symmetrical post-execution handling, ensuring that all pre-check data flows consistently into _postCheck.


36-63: Good use of internal virtual methods _preCheck and _postCheck.
Declaring _preCheck and _postCheck as internal virtual enables child contracts to override them and implement custom logic. This maintains clean separation of concerns within the hook.

src/interfaces/ICredibleAccountModule.sol (3)

9-72: Comprehensive event definitions for session key life cycle.
Defining separate events for installation, uninstallation, enabling, disabling, pausing, and unpausing facilitates robust tracking of session key states for off-chain indexers. The clarity in param naming is beneficial.


77-159: Clear function signatures for session key management.
Functions such as enableSessionKey, disableSessionKey, and validateSessionKeyParams are well-documented. Ensure that implementing contracts properly handle all return conditions and edge cases (e.g., repeated enabling, disabling, or validating).


163-195: preCheck and postCheck method definitions maintain compatibility with IHook.
The added methods align with the extended IHook interface. Ensure that the implementing contract consistently encodes and decodes hookData for correct usage in post-execution checks.

src/modules/validators/ResourceLockValidator.sol (3)

1-39: Initialization mapping and struct usage look sound.
The validatorStorage mapping properly tracks an owner's address and whether a validator is enabled. The use of a dedicated ValidatorStorage struct helps keep stateful data organized.


53-67: Installation logic is straightforward but verify repeated installation edge cases.
The onInstall function checks if the validator is already enabled and reverts if so, preventing accidental double-installs. When the validator is removed, onUninstall cleans up storage. Confirm your integration tests capture reinstall attempts or partial state updates.


139-217: Internal helper functions enhance clarity but watch for revert messages.
The _getResourceLock function reverts with RLV_OnlyCallTypeSingle if the calltype is not single, which is appropriate. Verify that revert messages are descriptive enough for debugging. Also, the _buildResourceLockHash approach methodically captures all relevant fields, providing strong replay protection.

src/modules/hooks/HookMultiPlexer.sol (9)

72-79: Validate hook type bounding logic.

onlySupportedHookType assumes that hook types up to HookType.TARGET_SIG are valid. If new hook types arise, adjusting this check might be overlooked. Consider more descriptive checks or enumerations.


96-147: Potential double initialization hazard.

onInstall reverts if the module is already initialized, which is good. However, it also invokes onInstall on each global subHook. Confirm that all subHooks can safely handle repeated calls. If not, consider a protective mechanism in subHooks or a stricter check here.


149-174: Ensure robust cleanup on uninstall.

onUninstall clears all hooks, but calls _removeHook only for GLOBAL hooks. Confirm that you intend to silently delete VALUE or DELEGATECALL hooks without explicitly calling their onUninstall function. This can leave the subHook state out of sync if that logic is needed.


184-188: Straightforward initialization check.

isInitialized is well-defined for reading the module’s state. No concerns here.


235-256: Validate hook correctness before adding.

addHook ensures the hook is attested in the registry, then calls onInstall. This is a good pattern. Consider also verifying that the hook is not already present, to avoid calling onInstall on duplicates.


300-309: Reentrancy consideration on external call.

_removeHook calls onUninstall on the hook prior to removing it. If the hook externally calls back into this contract, it could produce unexpected states. Verify that it’s safe for hooks to reenter or that execution halts if reentry occurs.


392-422: Post-execution indexing approach is sound.

postCheck decodes stored contexts and calls postCheckSubHook. This logic is straightforward. If preCheck or postCheck expansions are planned, ensure the data structure remains backward-compatible.


444-448: Module type check is concise.

isModuleType directly returns true if typeID == MODULE_TYPE_HOOK. This is simple and effective.


455-457: Name function clarity.

name() returns "HookMultiPlexer". This is standard practice for module introspection.

src/libraries/HookMultiPlexerLib.sol (7)

1-3: Precise compiler version and licensing.

Declaring pragma solidity 0.8.23; and AGPL-3.0-only licensing is consistent with the rest of the project. No issues here.


39-58: Sensible subhook pre-check aggregation.

Collecting contexts in preCheckSubHooks is a robust approach. Keep memory usage in mind if subHooks can expand significantly.


70-91: Careful about nested external calls.

preCheckSubHook encodes the original msg.sender with msgData and calls into the subHook. Confirm that subHooks are trusted or that re-entrancy cannot exploit intermediate states.


93-113: Post-check calls mirror pre-check.

postCheckSubHook usage is consistent with preCheckSubHook. Confirm subHook code paths can handle partial failures or potential revert scenarios.


168-177: Sorted uniqueness enforced systematically.

requireSortedAndUnique ensures that hooks are pre-validated. This is important for stable merges. Using an error type HooksNotSorted is helpful for debugging.


289-338: Inline assembly for savings.

decodeOnInstall uses assembly to decode arrays in a single pass, which is advanced and fosters efficiency. Thoroughly test edge cases (e.g., zero hooks) to confirm no memory misalignment.


340-380: Signature hooks store and cleanup.

storeSelectorHooks and deleteHooks systematically manage the sig-based hooks. This approach is good, but watch for potential collisions in sorting or edge cases where no sig hooks exist.

test/ModularTestBase.sol (7)

1-3: Consistent licensing strategy.

Using MIT licensing for test utilities is consistent with open-source norms. No issues here.


48-73: Centralized references to system contracts.

This contract references numerous modules (MultipleOwnerECDSAValidator, ERC20SessionKeyValidator, etc.). The approach is cohesive, but double-check that you only include what’s actually tested to avoid unnecessary dependencies.


95-106: User structure for improved clarity.

Defining a User struct for storing addresses and private keys is a good pattern. This is straightforward for testing and debugging.


270-294: Initialize HookMultiPlexer in _createSCW.

The contract uses a pre-set array of hooks (globalHooks, valueHooks, etc.) before installation. The approach is thorough. Validate that newly added hook types in the future won’t break the current initialization procedure.


296-318: Module installation logic.

_installModule uses mockExec.executeViaAccount to install modules. This is a good approach for testing. Keep in mind to thoroughly test revert scenarios or module collisions.


352-362: Hook management wrapper methods.

_installHookViaMultiplexer and _uninstallHookViaMultiplexer provide good clarity in tests about how hooks are manipulated.


383-412: Merkle-based testing utilities.

getTestProof and _hashPair help you simulate valid/invalid proofs. If other tests or modules also need Merkle proofs, centralizing this logic was a good move.

test/modules/ERC1155FallbackHandler/concrete/ERC1155FallbackHandler.t.sol (5)

5-10: Use of new imports for modular fallback handling
These import statements allow for hooking into the module management and fallback logic. Make sure all imported types and constants (e.g., CALLTYPE_SINGLE) are indeed used throughout the test suite and remain in sync with their definitions in the referenced libraries.


13-24: Switch to ModularTestBase
Inheriting from ModularTestBase centralizes test utilities for modular contract testing. Verify that any replaced or removed helper logic is ported correctly, and confirm that shared setup routines are still valid in this new inheritance structure.


60-79: Multiple allowed callers
Allowing multiple token addresses in the array broadens the fallback handler’s scope. Ensure that other tests confirm no interference occurs between tokens, particularly in edge cases of partial batch transfers or unusual token logic.


100-126: Ensuring revert on repeated selector usage
The test effectively confirms that only one fallback handler can be installed per selector. This prevents collisions but also might block certain multi-scenario fallbacks. Consider whether advanced fallback chaining is needed.


218-244: Mismatch between allowed selector and actual minted calls
Reverting on the wrong selector ensures no accidental call acceptance. The test thoroughly checks the revert scenario, but keep an eye on potential conflicts with other fallback modules.

src/modules/validators/CredibleAccountModule.sol (4)

1-19: New contract introducing session key and token locking logic
This contract centralizes session key management and locked token tracking. Ensure all library versions are pinned to prevent unexpected changes, given the reliance on external imports.


20-45: Extensive custom errors for module installation & session checks
Defining descriptive errors aids debugging. Verify error coverage with thorough tests, especially for partial or incomplete data (e.g., zero session keys, zero locked tokens).


46-75: Constructor validations
The constructor reverts if _proofVerifier or _hookMultiPlexer is zero, which is good for preventing misconfiguration. Double-check cross-contract references to ensure hooking logic is fully set upon deployment.


207-231: validateUserOp – primary gate to user operation
This function recovers the session key from the signature and checks the proof. Good layering with validateSessionKeyParams. However, ensure that any signature replays are invalidated by additional checks if needed.

test/modules/SessionKeyValidator/fuzz/SessionKeyValidator.t.sol (3)

6-16: Integration of PackedUserOperation with SessionKey logic
Switching to TestUtils centralizes repeated test patterns. Ensure that all new references to PackedUserOperation map consistently to the original test approach (e.g., encoded call data structure).


55-73: setUp method and validatorInstalled
The new validatorInstalled modifier ensures a base precondition. Confirm that older test cases without installation steps are updated or otherwise excluded so they won’t fail unexpectedly.


451-470: Removing session key permission
This test uses fuzzed indices for removing permissions—a solid strategy to discover off-by-one or memory shifting errors. No concerns noted, but consider verifying partial removal (where multiple permissions exist).

src/interfaces/IResourceLockValidator.sol (1)

1-5: LGTM!

The pragma version and import statement are correctly specified.

src/utils/ERC7484RegistryAdapter.sol (1)

1-5: LGTM!

The pragma version and import statement are correctly specified.

src/test/mocks/MockTarget.sol (2)

1-8: LGTM!

The state variables are correctly declared.


9-18: LGTM!

The setter functions are implemented correctly.

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

66-69: Add implementation for trustAttesters function.

The empty implementation might not be suitable for all test scenarios. Consider adding basic validation or tracking of attesters.

     function trustAttesters(
         uint8 threshold,
         address[] calldata attesters
-    ) external {}
+    ) external {
+        require(threshold <= attesters.length, "Invalid threshold");
+        // Add logic to track trusted attesters if needed for tests
+    }
src/test/TestExecutor.sol (1)

4-7: LGTM! Import paths have been standardized.

The switch to absolute imports from the "ERC7579" directory improves maintainability and reduces the risk of path-related issues.

src/ERC7579/interfaces/IERC7579Module.sol (2)

82-84: LGTM! Added msg.value parameter to preCheck.

The addition of msgValue parameter enables hooks to validate and track value transfers, which is essential for comprehensive transaction validation.


87-87:

❓ Verification inconclusive

Verify the impact of removing postCheck's return value.

The removal of returns (bool success) from postCheck changes how errors are handled. Ensure all implementations are updated to use revert for error handling instead of returning false.


🏁 Script executed:

#!/bin/bash
# Search for implementations of postCheck that might still return bool
ast-grep --pattern 'function postCheck(bytes calldata $_) $_ returns (bool $_) {
  $$$
}'

Length of output: 91


Verify that postCheck no longer returns a bool
Our automated searches (both ast-grep and an additional rg-based check) did not reveal any implementations of postCheck that still specify a returns (bool). Please double-check that all implementations are updated to use revert for error handling instead of relying on a boolean return value.


🏁 Script executed:

#!/bin/bash
# Re-run the search using a regex with rg to look for lingering bool return types in implementations of postCheck
rg "function\s+postCheck\([^)]*\)\s+returns\s*\(bool" -t sol

Length of output: 90


Action Required: Verify Use of Revert for Error Handling
Our previous searches did not conclusively detect implementations of postCheck that still return a bool. Please ensure that all implementations of postCheck no longer specify a return type (i.e., returns (bool)) and instead use revert-based error handling. Manual verification may be needed to confirm that no implementations have been missed.


🏁 Script executed:

#!/bin/bash
# Re-run the search for any lingering 'returns (bool ...)' in Solidity files
rg -g '*.sol' "function\s+postCheck\([^)]*\)\s+returns\s*\(bool"

Length of output: 66


Action Required: Verify the Updated Error Handling for postCheck
Our automated searches did not detect any implementations of postCheck that still specify a returns (bool) signature. Please ensure that all implementations have been updated to remove the boolean return and now use reverts for signaling errors. Double-check any custom implementations or overrides to confirm consistency with the updated interface.

src/modules/hooks/ModuleIsolationHook.sol (3)

9-11: LGTM! Import paths updated.

The import paths have been updated to use absolute paths from the root, which improves maintainability.


47-51: LGTM! Function signature updated to match interface.

The preCheck function signature has been updated to include the msgValue parameter, maintaining consistency with the interface.


118-118: LGTM! Simplified postCheck function.

The postCheck function's return type has been removed since it wasn't being used, making the code more concise.

src/access/AccessController.sol (1)

47-66: LGTM! Custom error declarations improve gas efficiency and error handling clarity.

The custom error declarations are well-organized and provide clear error messages for different scenarios.

test/wallet/ModularEtherspotWallet/concrete/ModularEtherspotWallet.t.sol (1)

1-611: LGTM! Comprehensive test coverage.

The test suite thoroughly covers:

  • Module installation/uninstallation
  • Transaction execution (single, batch, delegate calls)
  • Owner and guardian management
  • Error cases and edge conditions
test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (1)

1-883: Well-structured and comprehensive test suite.

The test file demonstrates good practices:

  • Clear test organization with descriptive names
  • Thorough coverage of success and failure cases
  • Proper event verification
  • Comprehensive validation of state changes
.gitignore (1)

22-23: LGTM! Appropriate gitignore entry for Soldeer.

The addition of the dependencies directory to .gitignore is correct for projects using Soldeer as the dependency manager.

README.md (1)

1-90: Well-structured documentation with clear instructions.

The README provides clear installation steps and properly documents the new dependency management system.

🧰 Tools
🪛 LanguageTool

[style] ~35-~35: To form a complete sentence, be sure to include a subject.
Context: ...nd in .env.example. ### Deployments Can be found in /script folder. There are...

(MISSING_IT_THERE)


[grammar] ~38-~38: It seems that hyphens are missing.
Context: ...ng the wallet factory. There is also an all in one script to deploy all required contracts...

(ALL_IN_ONE_HYPHEN)

docs/RESOURCE_LOCK_VALIDATOR.md (1)

1-111: Comprehensive and well-structured documentation.

The documentation effectively covers all major aspects of the ResourceLockValidator:

  • Clear explanation of functionality
  • Detailed method descriptions
  • Important security considerations
  • Integration guidelines
🧰 Tools
🪛 LanguageTool

[uncategorized] ~87-~87: Loose punctuation mark.
Context: ...ror Conditions - RLV_AlreadyInstalled: Validator already installed for account...

(UNLIKELY_OPENING_PUNCTUATION)

foundry.toml (6)

4-4: Dependency Directory Update:
The libs setting is now updated to ["dependencies"] instead of the old value (likely ["lib"]). This change aligns with the new approach for managing external dependencies via the Soldeer package manager. Ensure that all related documentation and scripts reflect this new dependency folder.


9-9: Treat Warnings as Errors:
Enabling deny_warnings = true enforces stricter quality control by treating compilation warnings as errors. Verify that all current warnings are resolved to prevent unexpected build failures.


11-11: Optimization Runs Decrease:
Reducing the optimizer runs from a higher value (previously 1000) to 256 can impact the gas efficiency of deployments. Please ensure that this setting has been validated against your performance and cost requirements.


12-12: Universal Gas Reporting:
Changing gas_reports to ["*"] now enables gas reporting for all contracts. This is useful for comprehensive monitoring but may result in more verbose output. Confirm that the increased report volume is acceptable and meets your needs.


21-21: Manual Remapping Detection Disabled:
Disabling auto_detect_remappings by setting it to false ensures that only explicitly defined remappings (as specified above) are used. This strict control can prevent unexpected resolution errors but requires manual updates if dependency paths change.


26-32: New Soldeer Configuration Section:
The [soldeer] section introduces several new parameters (such as remappings_generate, remappings_regenerate, remappings_version, etc.) for managing dependency remappings. These settings are critical for ensuring proper handling of dependencies via Soldeer rather than git submodules. It’s advisable to validate these options with your intended workflow and update internal documentation accordingly to reflect this transition.

Comment on lines 60 to 80
// HookMultiplexer
struct SigHookInit {
bytes4 sig;
address[] subHooks;
}

struct HookAndContext {
address hook;
bytes context;
}

struct SignatureHooks {
bytes4[] allSigs;
mapping(bytes4 => address[]) sigHooks;
}

struct Config {
bool initialized;
mapping(HookType hookType => address[]) hooks;
mapping(HookType hookType => SignatureHooks) sigHooks;
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Add NatSpec documentation for hook-related structs.

The hook-related structs lack NatSpec documentation. Consider adding comprehensive documentation to maintain consistency with other structs in the file.

Add documentation for each struct:

 // HookMultiplexer
+/// @title SigHookInit
+/// @notice Struct for initializing signature hooks
+/// @dev Used to configure function-specific hooks
 struct SigHookInit {
     bytes4 sig;
     address[] subHooks;
 }

+/// @title HookAndContext
+/// @notice Struct containing a hook address and its context
+/// @dev Used to manage hook execution with associated context data
 struct HookAndContext {
     address hook;
     bytes context;
 }

+/// @title SignatureHooks
+/// @notice Struct managing function-specific hooks
+/// @dev Maps function signatures to their associated hooks
 struct SignatureHooks {
     bytes4[] allSigs;
     mapping(bytes4 => address[]) sigHooks;
 }

+/// @title Config
+/// @notice Configuration struct for hook management
+/// @dev Manages initialization state and hook mappings by type
 struct Config {
     bool initialized;
     mapping(HookType hookType => address[]) hooks;
     mapping(HookType hookType => SignatureHooks) sigHooks;
 }

Verify Solidity version compatibility for named mapping parameters.

The Config struct uses the new mapping syntax with named parameters. Let's verify compatibility with Solidity 0.8.23.


🌐 Web query:

When was named mapping parameters feature introduced in Solidity?

💡 Result:

The named parameters in mapping types feature was introduced in Solidity 0.8.18, released on February 1, 2023[3][6]. This update allowed developers to specify parameter names for keys in mappings, improving code readability and ABI output clarity.

For example:

mapping(uint256 key => string value) public dataMap;

Here, key and value become named parameters in the automatically generated getter function's ABI[1][6]. This change was particularly useful for nested mappings where multiple parameters might otherwise be unnamed[8].

The update was part of Solidity's ongoing improvements to developer experience and contract interoperability[3][6]. Subsequent versions have maintained this syntax while adding other compiler features and optimizations[3].

Citations:


Documentation and Solidity Version Confirmation

  • The hook-related structs in src/common/Structs.sol (lines 60-80) currently lack NatSpec documentation. Please add comprehensive NatSpec comments to each struct as illustrated below.
  • The named mapping parameters used in the Config struct have been verified to be supported since Solidity 0.8.18. Ensure your project is compiled with Solidity 0.8.18 or later.

Suggested diff snippet for documentation:

 // HookMultiplexer
+/// @title SigHookInit
+/// @notice Struct for initializing signature hooks.
+/// @dev Used to configure function-specific hooks.
 struct SigHookInit {
     bytes4 sig;
     address[] subHooks;
 }

+/// @title HookAndContext
+/// @notice Struct containing a hook address alongside its context.
+/// @dev Used to manage hook execution with associated context data.
 struct HookAndContext {
     address hook;
     bytes context;
 }

+/// @title SignatureHooks
+/// @notice Struct for managing function-specific hooks.
+/// @dev Maps function signatures to their associated hooks.
 struct SignatureHooks {
     bytes4[] allSigs;
     mapping(bytes4 => address[]) sigHooks;
 }

+/// @title Config
+/// @notice Configuration struct for hook management.
+/// @dev Manages initialization state and hook mappings by type.
 struct Config {
     bool initialized;
     mapping(HookType hookType => address[]) hooks;
     mapping(HookType hookType => SignatureHooks) sigHooks;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// HookMultiplexer
struct SigHookInit {
bytes4 sig;
address[] subHooks;
}
struct HookAndContext {
address hook;
bytes context;
}
struct SignatureHooks {
bytes4[] allSigs;
mapping(bytes4 => address[]) sigHooks;
}
struct Config {
bool initialized;
mapping(HookType hookType => address[]) hooks;
mapping(HookType hookType => SignatureHooks) sigHooks;
}
// HookMultiplexer
/// @title SigHookInit
/// @notice Struct for initializing signature hooks.
/// @dev Used to configure function-specific hooks.
struct SigHookInit {
bytes4 sig;
address[] subHooks;
}
/// @title HookAndContext
/// @notice Struct containing a hook address alongside its context.
/// @dev Used to manage hook execution with associated context data.
struct HookAndContext {
address hook;
bytes context;
}
/// @title SignatureHooks
/// @notice Struct for managing function-specific hooks.
/// @dev Maps function signatures to their associated hooks.
struct SignatureHooks {
bytes4[] allSigs;
mapping(bytes4 => address[]) sigHooks;
}
/// @title Config
/// @notice Configuration struct for hook management.
/// @dev Manages initialization state and hook mappings by type.
struct Config {
bool initialized;
mapping(HookType hookType => address[]) hooks;
mapping(HookType hookType => SignatureHooks) sigHooks;
}

Comment on lines 107 to 137
function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature)
external
view
override
returns (bytes4)
{
address walletOwner = validatorStorage[msg.sender].owner;
if (signature.length == 65) {
if (walletOwner == ECDSA.recover(hash, signature)) {
return ERC1271_MAGIC_VALUE;
}
bytes32 sigHash = ECDSA.toEthSignedMessageHash(hash);
address recoveredSigner = ECDSA.recover(sigHash, signature);
if (walletOwner != recoveredSigner) return ERC1271_INVALID;
return ERC1271_MAGIC_VALUE;
}
bytes memory ecdsaSig = signature[0:65];
bytes32 root = bytes32(signature[65:97]);
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[]));
if (!MerkleProofLib.verify(proof, root, hash)) {
revert RLV_ResourceLockHashNotInProof();
}
// simple ecdsa verification
if (walletOwner == ECDSA.recover(root, ecdsaSig)) {
return ERC1271_MAGIC_VALUE;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSig);
if (walletOwner != recoveredMSigner) return ERC1271_INVALID;
return ERC1271_MAGIC_VALUE;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

isValidSignatureWithSender follows the same dual approach but ensure consistent signature checks.
Similar to validateUserOp, ensure the minimum signature length is enforced to prevent slicing errors. Otherwise, the approach of verifying both direct and eth-signed message hashes is beneficial for improved signature compatibility.

Comment on lines 69 to 105
// TODO: Need to figure out how to unpack resource lock data and hash
// from UserOperation
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
external
override
returns (uint256)
{
bytes calldata signature = userOp.signature;
address walletOwner = validatorStorage[msg.sender].owner;
// Standard signature length - no proof packing
if (signature.length == 65) {
// standard ECDSA recover
if (walletOwner == ECDSA.recover(userOpHash, signature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigHash = ECDSA.toEthSignedMessageHash(userOpHash);
address recoveredSigner = ECDSA.recover(sigHash, signature);
if (walletOwner != recoveredSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Signature handling in validateUserOp should check minimum signature length.
When parsing standard ECDSA signatures (65 bytes) vs. extended signatures (≥ 65 bytes), consider an explicit check to avoid out-of-bounds slicing if the signature is shorter than expected. If a malformed signature is passed in, the call could revert unexpectedly.

Below is an example diff to add a length check:

 function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
     external
     override
     returns (uint256)
 {
     bytes calldata signature = userOp.signature;

+    require(signature.length >= 65, "Invalid signature length");
     address walletOwner = validatorStorage[msg.sender].owner;
     ...
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: Need to figure out how to unpack resource lock data and hash
// from UserOperation
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
external
override
returns (uint256)
{
bytes calldata signature = userOp.signature;
address walletOwner = validatorStorage[msg.sender].owner;
// Standard signature length - no proof packing
if (signature.length == 65) {
// standard ECDSA recover
if (walletOwner == ECDSA.recover(userOpHash, signature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigHash = ECDSA.toEthSignedMessageHash(userOpHash);
address recoveredSigner = ECDSA.recover(sigHash, signature);
if (walletOwner != recoveredSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// TODO: Need to figure out how to unpack resource lock data and hash
// from UserOperation
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
external
override
returns (uint256)
{
bytes calldata signature = userOp.signature;
require(signature.length >= 65, "Invalid signature length");
address walletOwner = validatorStorage[msg.sender].owner;
// Standard signature length - no proof packing
if (signature.length == 65) {
// standard ECDSA recover
if (walletOwner == ECDSA.recover(userOpHash, signature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigHash = ECDSA.toEthSignedMessageHash(userOpHash);
address recoveredSigner = ECDSA.recover(sigHash, signature);
if (walletOwner != recoveredSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}

Comment on lines 77 to 150
/*//////////////////////////////////////////////////////////////
VALIDATOR PUBLIC/EXTERNAL
//////////////////////////////////////////////////////////////*/

// @inheritdoc ICredibleAccountModule
function enableSessionKey(bytes calldata _resourceLock) external {
ResourceLock memory rl = abi.decode(_resourceLock, (ResourceLock));
if (rl.sessionKey == address(0))
revert CredibleAccountModule_InvalidSessionKey();
if (rl.validAfter == 0)
revert CredibleAccountModule_InvalidValidAfter();
if (rl.validUntil == 0 || rl.validUntil <= rl.validAfter)
revert CredibleAccountModule_InvalidValidUntil(rl.validUntil);
sessionData[rl.sessionKey][msg.sender] = SessionData({
sessionKey: rl.sessionKey,
validAfter: rl.validAfter,
validUntil: rl.validUntil,
live: true // unused
});
for (uint256 i; i < rl.tokenData.length; ++i) {
lockedTokens[rl.sessionKey].push(
LockedToken({
token: rl.tokenData[i].token,
lockedAmount: rl.tokenData[i].amount,
claimedAmount: 0
})
);
}
walletSessionKeys[msg.sender].push(rl.sessionKey);
emit CredibleAccountModule_SessionKeyEnabled(rl.sessionKey, msg.sender);
}

// @inheritdoc ICredibleAccountModule
function disableSessionKey(address _sessionKey) external {
if (sessionData[_sessionKey][msg.sender].validUntil == 0)
revert CredibleAccountModule_SessionKeyDoesNotExist(_sessionKey);
if (
sessionData[_sessionKey][msg.sender].validUntil >=
block.timestamp &&
!isSessionClaimed(_sessionKey)
) revert CredibleAccountModule_LockedTokensNotClaimed(_sessionKey);
delete sessionData[_sessionKey][msg.sender];
delete lockedTokens[_sessionKey];
address[] storage keys = walletSessionKeys[msg.sender];
for (uint256 i; i < keys.length; ++i) {
if (keys[i] == _sessionKey) {
keys[i] = keys[keys.length - 1];
keys.pop();
break;
}
}
emit CredibleAccountModule_SessionKeyDisabled(_sessionKey, msg.sender);
}

// @inheritdoc ICredibleAccountModule
function validateSessionKeyParams(
address _sessionKey,
PackedUserOperation calldata userOp
) public returns (bool) {
if (isSessionClaimed(_sessionKey)) return false;
bytes calldata callData = userOp.callData;
if (bytes4(callData[:4]) == IERC7579Account.execute.selector) {
ModeCode mode = ModeCode.wrap(bytes32(callData[4:36]));
(CallType calltype, , , ) = ModeLib.decode(mode);
if (calltype == CALLTYPE_SINGLE) {
return
_validateSingleCall(callData, _sessionKey, userOp.sender);
} else if (calltype == CALLTYPE_BATCH) {
return _validateBatchCall(callData, _sessionKey, userOp.sender);
}
}
return false;
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Session key creation and disabling
The logic in enableSessionKey and disableSessionKey ensures time-bound validity and mandatory locked token claims. Consider a scenario with partial claims on multiple tokens—double-check that disableSessionKey accounts for partial redemption.

Potential edge case: If some tokens are only partially claimed, the session can’t be disabled. Confirm that this is intentional or provide a partial-claim scenario.

Comment on lines 7 to 9
function verifyProof(bytes calldata proof) external view returns (bool) {
return true;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

❓ Verification inconclusive

Implement actual proof verification logic.

The current implementation always returns true without verifying the proof. This is a critical security risk if used in production.

Please confirm:

  1. Is this intended to be a placeholder implementation?
  2. Are there plans to implement the actual verification logic?
  3. Are there any test cases that rely on this implementation?

Run the following script to check for usages of this contract:


🏁 Script executed:

#!/bin/bash
# Search for imports and contract instantiations
rg -A 5 "ProofVerifier|IProofVerifier"

Length of output: 5726


Security Risk: Placeholder Implementation in ProofVerifier

The verifyProof function in src/utils/ProofVerifier.sol currently always returns true, effectively acting as a placeholder. This implementation is used both in testing (as seen in test/ModularTestBase.sol) and by other modules (e.g., in CredibleAccountModule.sol), which means if it's deployed as-is, it presents a serious security risk.

  • Please confirm if this placeholder implementation is intentional.
  • If it is a temporary stub, are there plans to implement the actual proof verification logic?
  • Are there any tests or module behaviors that depend on the current implementation?

Comment on lines 22 to 27
function hasHook(
address walletAddress,
address hookAddress,
HookType hookType
) external returns (bool);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add view modifier to hasHook function.

The hasHook function appears to be a query function that doesn't modify state and should be marked as view.

     function hasHook(
         address walletAddress,
         address hookAddress,
         HookType hookType
-    ) external returns (bool);
+    ) external view returns (bool);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function hasHook(
address walletAddress,
address hookAddress,
HookType hookType
) external returns (bool);
}
function hasHook(
address walletAddress,
address hookAddress,
HookType hookType
) external view returns (bool);
}

pragma solidity ^0.8.0;

interface IERC7484 {
event NewTrustedAttesters();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add parameters to NewTrustedAttesters event.

The event should include relevant parameters such as the attesters and threshold for better tracking and filtering.

-    event NewTrustedAttesters();
+    event NewTrustedAttesters(
+        address indexed smartAccount,
+        address[] attesters,
+        uint8 threshold
+    );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
event NewTrustedAttesters();
event NewTrustedAttesters(
address indexed smartAccount,
address[] attesters,
uint8 threshold
);

Comment on lines 20 to 22
function setTrustedForwarder(address forwarder) external {
trustedForwarder[msg.sender] = forwarder;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add event emission and zero address validation.

Track forwarder changes with events and validate the forwarder address.

+    event TrustedForwarderSet(address indexed account, address indexed forwarder);
+    event TrustedForwarderCleared(address indexed account);

     function setTrustedForwarder(address forwarder) external {
+        require(forwarder != address(0), "Invalid forwarder address");
         trustedForwarder[msg.sender] = forwarder;
+        emit TrustedForwarderSet(msg.sender, forwarder);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function setTrustedForwarder(address forwarder) external {
trustedForwarder[msg.sender] = forwarder;
}
event TrustedForwarderSet(address indexed account, address indexed forwarder);
event TrustedForwarderCleared(address indexed account);
function setTrustedForwarder(address forwarder) external {
require(forwarder != address(0), "Invalid forwarder address");
trustedForwarder[msg.sender] = forwarder;
emit TrustedForwarderSet(msg.sender, forwarder);
}

Comment on lines 30 to 37
bytes memory hookData = IHook(hook).preCheck(
msg.sender,
msg.value,
msg.data
);
_;
if (!IHook(hook).postCheck(hookData)) revert HookPostCheckFailed();
IHook(hook).postCheck(hookData);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

❓ Verification inconclusive

Critical: Verify the removal of postCheck error handling.

The error handling for postCheck has been removed, which could lead to silent failures. This change needs careful consideration.


🏁 Script executed:

#!/bin/bash
# Search for postCheck implementations to understand potential impact
ast-grep --pattern 'function postCheck($$$) {
  $$$
  return false;
  $$$
}'

Length of output: 78


Attention: Manual Verification Needed for postCheck Error Handling

The current implementation calls IHook(hook).postCheck(hookData) without any try-catch or explicit error handling. Our initial script did not detect any implementation using a return false pattern for error handling, which makes it unclear whether the removal of error handling was intentional or might lead to silent failures.

  • File: src/ERC7579/core/HookManager.sol (Lines 30-37)
  • Concern: The absence of error handling around the postCheck call could lead to unreported failures if postCheck reverts or behaves unexpectedly.
  • Action: Please manually verify if the removal of error handling in postCheck is intentional and that any failure in the hook’s execution is appropriately managed elsewhere or is acceptable for the overall design.

🏁 Script executed:

#!/bin/bash
# Print the context around the postCheck call from HookManager.sol to check for error handling mechanisms.
rg -A 5 -B 5 "IHook(hook).postCheck" src/ERC7579/core/HookManager.sol

Length of output: 71


Critical: The current implementation in src/ERC7579/core/HookManager.sol now calls IHook(hook).postCheck(hookData) without any visible error handling (e.g., try/catch or explicit failure checks). Since our automated searches (for error-handling patterns in postCheck and related contexts) produced no output, please manually verify that this design change is intentional and that any failures in postCheck are either acceptable or appropriately handled upstream.

Comment on lines 503 to 563
function test_enableSessionKey_ViaUserOp() public withRequiredModules {
// Enable session key
_enableSessionKey(address(scw));
// Enable another session key
uint256[2] memory newAmounts = [uint256(10e6), uint256(40e18)];
usdc.mint(address(scw), newAmounts[0]);
dai.mint(address(scw), newAmounts[1]);
vm.deal(address(scw), uint256(113e18));
TokenData[] memory newTokenData = new TokenData[](tokens.length + 1);
for (uint256 i; i < 2; ++i) {
newTokenData[i] = TokenData(tokens[i], newAmounts[i]);
}
bytes memory rl = abi.encode(
ResourceLock({
chainId: 42161,
smartWallet: address(scw),
sessionKey: otherSessionKey.pub,
validAfter: validAfter,
validUntil: validUntil,
tokenData: newTokenData,
nonce: 2
})
);
bytes memory enableSessionKeyData = abi.encodeWithSelector(CAM.enableSessionKey.selector, rl);
TokenData[] memory newTokenData2 = new TokenData[](tokens.length + 1);
newTokenData2[0] = TokenData(0xa0Cb889707d426A7A386870A03bc70d1b0697598, uint256(10000000)); // USDC
newTokenData2[1] = TokenData(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, uint256(40000000000000000000)); // DAI
// Encode the session data
bytes memory newRl = abi.encode(
ResourceLock({
chainId: 42161,
smartWallet: address(scw),
sessionKey: 0xB071527c3721215A46958d172A42E7E3BDd1DF46,
validAfter: uint48(1729743735),
validUntil: uint48(1729744025),
tokenData: newTokenData2,
nonce: 3
})
);
// Encode the function call data with the function selector and the encoded session data
bytes memory enableSessionKeyData2 = abi.encodeWithSelector(CAM.enableSessionKey.selector, newRl);
bytes memory opCalldata = abi.encodeCall(
IERC7579Account.execute,
(ModeLib.encodeSimpleSingle(), ExecutionLib.encodeSingle(address(cam), 0, enableSessionKeyData))
);
(PackedUserOperation memory op,) = _createUserOpWithSignature(eoa, address(scw), address(moecdsav), opCalldata);
// Execute the user operation
_executeUserOp(op);
// Get cumulative locked funds for wallet
TokenData[] memory data = cam.cumulativeLockedForWallet();
// Verify retrieved data matches expected
assertEq(data[0].token, address(usdc), "First token address does not match expected (expected USDC)");
assertEq(
data[0].amount, amounts[0] + newAmounts[0], "Cumulative USDC locked balance does not match expected amount"
);
assertEq(data[1].token, address(dai), "Second token address does not match expected (expected DAI)");
assertEq(
data[1].amount, amounts[1] + newAmounts[1], "Cumulative DAI locked balance does not match expected amount"
);
vm.stopPrank();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Hardcoded addresses in test data could lead to test failures.

The test uses hardcoded addresses for tokens which might not exist on all test networks:

newTokenData2[0] = TokenData(0xa0Cb889707d426A7A386870A03bc70d1b0697598, uint256(10000000)); // USDC
newTokenData2[1] = TokenData(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, uint256(40000000000000000000)); // DAI

Consider using the existing token variables (usdc, dai) instead of hardcoded addresses to make the tests more maintainable and network-agnostic:

-newTokenData2[0] = TokenData(0xa0Cb889707d426A7A386870A03bc70d1b0697598, uint256(10000000)); // USDC
-newTokenData2[1] = TokenData(0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, uint256(40000000000000000000)); // DAI
+newTokenData2[0] = TokenData(address(usdc), uint256(10000000));
+newTokenData2[1] = TokenData(address(dai), uint256(40000000000000000000));

- Added `FOUNDRY_DISABLE_NIGHTLY_WARNING` param to .example.env.
- Removed `deny_warnings` config from foundry.toml.
- Fixed import in deploy script.
Copy link

@coderabbitai coderabbitai bot left a comment

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 (4)
src/modules/validators/CredibleAccountModule.sol (2)

387-403: Remove commented-out code in _digestSignature.

The function contains commented-out code that appears to be an alternative implementation. This should be removed to maintain clean, production-ready code.

 function _digestSignature(bytes calldata _signature) internal pure returns (bytes memory, bytes memory) {
     bytes32 r = bytes32(_signature[0:32]);
     bytes32 s = bytes32(_signature[32:64]);
     uint8 v = uint8(_signature[64]);
     bytes memory proof = bytes(_signature[65:]);
     bytes memory signature = abi.encodePacked(r, s, v);
-    // r (32 bytes) + s (32 bytes) + v (1 byte) = 65 bytes.
-    // uint256 proofLength = _signature.length - 65;
-    // proof = new bytes(proofLength);
-    // if (proofLength > 0) {
-    //     uint256 proofStart = 65; // 32 byte r + 32 byte s + 1 byte v
-    //     for (uint256 i; i < proofLength; ++i) {
-    //         proof[i] = _signature[proofStart + i];
-    //     }
-    // }
     return (signature, proof);
 }

347-347: Missing NatSpec documentation.

There's an empty line where NatSpec documentation should be for the _validateTokenData function. Consider adding proper documentation to maintain consistency.

 /**
  * @notice check if the tokenAddress in calldata of userOp is part of the session data and wallet has sufficient token balance
  * @dev locked tokenBalance check is done in the CredibleAccountModule
  * @dev for `transfer` as function-selector, then check for the wallet balance
  * @dev for `transferFrom` as function-selector, then check for the wallet balance and allowance
+ * @param _sessionKey The address of the session key
+ * @param _wallet The address of the wallet
+ * @param _amount The amount of tokens to transfer
+ * @param _token The address of the token
+ * @return bool indicating whether the token data is valid
  */
foundry.toml (2)

13-20: Explicit Remappings Configuration

The new remappings array lists explicit paths for various dependencies and contracts. This approach enhances clarity and control over dependency resolution. Please double-check that each remapping (e.g., for ERC7579, solady, solarray) correctly corresponds to your project’s directory structure and that related changes (such as removal of submodules) are reflected elsewhere in your documentation.


34-40: New Dependencies Section Added

The [dependencies] section now explicitly lists dependencies such as solady, @openzeppelin-contracts, eth-infinitism-account-abstraction, forge-std, and solarray. Consider verifying that:

  • The versions specified are correct and compatible.
  • The SSH URL for solarray is accessible in your deployment environment (switching to HTTPS might avoid SSH key issues if needed).

This explicit declaration aids in transparency and simplifies dependency management.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5572901 and 8608a80.

📒 Files selected for processing (5)
  • .env.example (1 hunks)
  • foundry.toml (1 hunks)
  • script/NonDeterministicDeployAllAndSetup.s.sol (4 hunks)
  • src/modules/validators/CredibleAccountModule.sol (22 hunks)
  • test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • script/NonDeterministicDeployAllAndSetup.s.sol
🔇 Additional comments (20)
test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (8)

6-7: Good addition of standard libraries.

Adding ECDSA, IERC20, ExecutionLib, and ModeLib imports provides better type safety and functionality over raw operations.

Also applies to: 11-12


15-16: Note the test utility restructuring.

The test inherits from TestUtils (aliased from CredibleAccountModuleTestUtils), indicating a broader test utilities restructuring that aligns with the PR objective of cleaning up the test suite.


35-42: Improved session key handling with User objects.

Converting from raw address to structured User objects with _createUser(_sessionKey) improves code organization and encapsulates the session key functionality better.


52-57: Updated Resource Lock structure with wallet reference.

The resource lock now correctly references address(scw) as the smart wallet and uses the public key from the User object (sk.pub) as the session key.


87-89: Variable naming standardization to scw.

Changed from mew to scw (likely SmartContractWallet) for consistency with the rest of the codebase.


108-118: Improved execution creation pattern for token transfers.

The code now uses a more structured approach to create token transfer executions and batch them together, which better represents how these operations would occur in production.


40-40: Test functions now use withRequiredModules modifier.

All test functions have been updated to include the withRequiredModules modifier, which ensures proper module prerequisites are set up before running tests. This is a good practice for ensuring test isolation and clarity.

Also applies to: 81-82, 138-139


26-28:

❓ Verification inconclusive

Simplified setup method.

The setUp method no longer calls super.setUp() and directly invokes _testSetup(). Ensure that all necessary setup from the parent class is still being performed.


🏁 Script executed:

#!/bin/bash
# Look for _testSetup implementation to check what initialization is happening
rg -A 10 "_testSetup\(\)" test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol

Length of output: 590


Simplified setup verification – Ensure Complete Initialization is Maintained

The verification script shows that _testSetup() in test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol performs the full initialization (calling _testInit() and setting up contracts, wallets, and test variables). This indicates that the parent class's setup logic is either encapsulated within _testInit() or has been intentionally replaced by _testSetup(). Please confirm that this change is deliberate and that no essential initialization from the former super.setUp() call has been omitted.

  • Location: test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (lines 26-28)
  • Initialization Logic: Refer to _testSetup() implementation in test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol
src/modules/validators/CredibleAccountModule.sol (7)

5-5: Updated import path follows package manager convention.

Changed from "openzeppelin/contracts/token/ERC20/IERC20.sol" to "@openzeppelin/contracts/token/ERC20/IERC20.sol" to align with standard import path conventions when using package managers like Soldeer.


23-23: Added console logging for debugging.

The addition of the console2 import enables better debugging capabilities during development and testing. Ensure these are used appropriately and don't impact production gas costs.

Is the console2 import appropriately guarded to prevent inclusion in production builds? Consider checking if there's a convention in the project for development-only imports.


73-78: Improved conditional formatting with explicit braces.

Adding explicit braces to conditional statements improves code readability and prevents potential bugs from implicit scoping. This is a good practice for maintaining code clarity.

Also applies to: 90-98, 119-121, 267-271


153-156: Added diagnostic logging.

The logging statements provide useful information during execution, which will aid in debugging and understanding the flow of operations during tests and development.

Also applies to: 246-246


57-57: Mapping declaration formatting.

The mapping declaration for sessionData has been reformatted to a single line for consistency with other mappings in the contract.


119-121: Session key disabling with locked tokens validation.

The check for not allowing session key disabling when tokens are not claimed and the session is still valid is important. Consider a scenario with partial claims on multiple tokens—ensure that disableSessionKey correctly handles partial redemption scenarios.


315-320: Improved calldata extraction in validation methods.

The validation methods now use better patterns for extracting and processing calldata, which improves readability and maintainability of the code.

Also applies to: 332-339

.env.example (1)

3-3: New Environment Variable for Foundry Warning Suppression

The addition of FOUNDRY_DISABLE_NIGHTLY_WARNING=1 provides a clear way to disable Foundry’s nightly warnings. Please ensure that this setting is documented in your project guidelines or README to help new developers understand its purpose.

foundry.toml (4)

4-4: Updated Libraries Directory

The libs field now points to ["dependencies"] instead of what may have been a previous value (e.g., "lib"). This change aligns with the new dependency management approach. Please verify that all documentation and any related tooling references have been updated accordingly.


10-11: Adjusted Optimization and Gas Reporting Settings

The optimizer runs value has been reduced to 256, and the gas_reports field is now set to report for all contracts (["*"]). Ensure that 256 optimization runs are sufficient for your deployment targets and that comprehensive gas reporting does not lead to unnecessary clutter or performance issues during builds.


21-21: Disable Auto-Detection of Remappings

Setting auto_detect_remappings to false enforces manual control over dependency paths. This can prevent unexpected behavior due to automatic remapping. Just ensure that future dependency updates or re-arrangements are manually updated in this configuration.


26-33: New Soldeer Package Manager Configuration

The [soldeer] block introduces configurations for the Soldeer package manager, disabling automatic remapping generation and version checks while enabling recursive dependency handling. Confirm that these settings integrate well with your overall dependency workflow and that the Soldeer integration has been validated through testing.

- No longer hashing the `TokenData[]` in `ResourceLock` and then rehashing with all of the other data. Only encoding `TokenData[]` and hashing with other data.
- Fixed helper util and retested.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/modules/validators/ResourceLockValidator.sol (2)

71-105: Signature handling in validateUserOp should check minimum signature length.

When parsing standard ECDSA signatures (65 bytes) vs. extended signatures (≥ 65 bytes), consider an explicit check to avoid out-of-bounds slicing if the signature is shorter than expected.

function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
    external
    override
    returns (uint256)
{
    bytes calldata signature = userOp.signature;
+   if (signature.length < 65) return SIG_VALIDATION_FAILED;
    address walletOwner = validatorStorage[msg.sender].owner;
    // Rest of function...

107-137: isValidSignatureWithSender follows the same dual approach but needs signature length validation.

Similar to validateUserOp, ensure the minimum signature length is enforced to prevent slicing errors. Otherwise, the approach of verifying both direct and eth-signed message hashes is beneficial for improved signature compatibility.

function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature)
    external
    view
    override
    returns (bytes4)
{
+   if (signature.length < 65) return ERC1271_INVALID;
    address walletOwner = validatorStorage[msg.sender].owner;
    // Rest of function...
🧹 Nitpick comments (3)
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (2)

32-52: Consider adding validation for dynamic test scenarios.

The function creates user operations with resource locks, but consider adding parameters to test various edge cases (e.g., expired timestamps, zero token amounts) for more comprehensive testing.

-function _createUserOpWithResourceLock(address _scw, User memory _user, bool _validProof)
+function _createUserOpWithResourceLock(
+    address _scw, 
+    User memory _user, 
+    bool _validProof,
+    uint48 validAfter,
+    uint48 validUntil
+)
    internal
    returns (PackedUserOperation memory op, ResourceLock memory rl, bytes32[] memory proof, bytes32 root)
{
    // Create base UserOp
    op = _createUserOp(_scw, address(rlv));
    // Create ResourceLock and generate proof
-   rl = _generateResourceLock(_scw, _user.pub);
+   rl = _generateResourceLock(_scw, _user.pub, validAfter, validUntil);
    (proof, root,) = getTestProof(_buildResourceLockHash(rl), _validProof);
    // Rest of function...

68-82: Hardcoded values may limit test flexibility.

The function uses hardcoded values for chain ID, timestamps, and nonce, which might make tests brittle. Consider parameterizing these values for more flexible test scenarios.

-function _generateResourceLock(address _scw, address _sk) internal view returns (ResourceLock memory) {
+function _generateResourceLock(
+    address _scw, 
+    address _sk, 
+    uint48 _validAfter, 
+    uint48 _validUntil
+) internal view returns (ResourceLock memory) {
    TokenData[] memory td = new TokenData[](2);
    td[0] = TokenData({token: address(usdt), amount: 100});
    td[1] = TokenData({token: address(dai), amount: 200});
    ResourceLock memory rl = ResourceLock({
        chainId: 42161,
        smartWallet: _scw,
        sessionKey: _sk,
-       validAfter: 1732176210,
-       validUntil: 1732435407,
+       validAfter: _validAfter == 0 ? 1732176210 : _validAfter,
+       validUntil: _validUntil == 0 ? 1732435407 : _validUntil,
        tokenData: td,
        nonce: 14
    });
    return rl;
}
src/modules/validators/ResourceLockValidator.sol (1)

69-70: Remove TODO comment before finalizing.

The TODO comment indicates incomplete work regarding resource lock data extraction from UserOperation. Either implement the solution or document why it's not needed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8608a80 and 6a31993.

📒 Files selected for processing (2)
  • src/modules/validators/ResourceLockValidator.sol (1 hunks)
  • test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1 hunks)
🔇 Additional comments (3)
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (2)

18-26: Good design with the withRequiredModules modifier.

The modifier efficiently sets up the test environment by installing all necessary modules and hooks in the correct order. This creates a clean pattern for test setup that should be easy to reuse across test cases.


54-66: LGTM - Consistent hashing implementation.

The ResourceLock hash implementation is correct and matches the implementation in the core contract.

src/modules/validators/ResourceLockValidator.sol (1)

195-207: LGTM - Resource lock hash generation is consistent.

The implementation of _buildResourceLockHash correctly encodes all relevant fields of the ResourceLock struct, maintaining consistency with the test utilities implementation.

Comment on lines 155 to 158
function _getArrayInfo(bytes calldata _data) internal pure returns (uint256 offset, uint256 length) {
offset = uint256(bytes32(_data[260:292]));
length = uint256(bytes32(_data[100 + offset:132 + offset]));
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Hardcoded offsets in _getArrayInfo are brittle.

The function uses hardcoded offsets (260, 292, 100) to extract array information from calldata, which makes the code fragile if the calldata structure changes. Consider using a more robust approach.


🏁 Script executed:

#!/bin/bash
# Verify if the expected calldata format matches what's being used in tests

# Look for tests that could indicate the expected calldata format
echo "Searching for tests that construct calldata for ResourceLockValidator..."
rg -A 5 "ResourceLock" test/ | grep -A 3 "callData ="

Length of output: 1499


Action Needed: Refactor Hardcoded CallData Offsets in _getArrayInfo

While the tests (e.g., those in ResourceLockValidatorTestUtils.sol and ResourceLockValidator.t.sol) currently construct calldata using abi.encodeCall and thus adhere to an expected format, the reliance on hardcoded offsets (260, 292, and the base offset of 100) in _getArrayInfo remains inherently brittle. If the calldata structure ever changes, these fixed offsets will no longer align with the actual data layout, potentially breaking the validator logic.

  • File: src/modules/validators/ResourceLockValidator.sol (lines 155–158)
  • Issue: Extraction of array information relies on fixed offsets, which may not be future-proof.
  • Suggestion: Investigate using a dynamic or more descriptive decoding approach (e.g., leveraging abi.decode) to automatically adjust to any modifications in the calldata format.

Please review and refactor accordingly to enhance long-term maintainability.

Comment on lines 167 to 189
function _getResourceLock(bytes calldata _callData) internal view returns (ResourceLock memory) {
if (bytes4(_callData[:4]) == IERC7579Account.execute.selector) {
(CallType calltype,,,) = ModeLib.decode(ModeCode.wrap(bytes32(_callData[4:36])));
if (calltype == CALLTYPE_SINGLE) {
(,, bytes calldata execData) = ExecutionLib.decodeSingle(_callData[100:]);
(uint256 arrayOffset, uint256 arrayLength) = _getArrayInfo(execData);
TokenData[] memory td = new TokenData[](arrayLength);
for (uint256 i; i < arrayLength; ++i) {
td[i] = _getSingleTokenData(execData, 132 + arrayOffset + (i * 64));
}
return ResourceLock({
chainId: uint256(bytes32(execData[100:132])),
smartWallet: address(uint160(uint256(bytes32(execData[132:164])))),
sessionKey: address(uint160(uint256(bytes32(execData[164:196])))),
validAfter: uint48(uint256(bytes32(execData[196:228]))),
validUntil: uint48(uint256(bytes32(execData[228:260]))),
tokenData: td,
nonce: uint256(bytes32(execData[292:324]))
});
}
revert RLV_OnlyCallTypeSingle();
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate extracted ResourceLock data in _getResourceLock.

The function extracts a ResourceLock from calldata without validating that the timestamps make sense (validAfter ≤ validUntil) or that extracted addresses are non-zero. This could lead to acceptance of invalid resource locks.

function _getResourceLock(bytes calldata _callData) internal view returns (ResourceLock memory) {
    // ... existing implementation ...
    
+   // Add validation at the end before returning
+   ResourceLock memory lock = ResourceLock({
        chainId: uint256(bytes32(execData[100:132])),
        smartWallet: address(uint160(uint256(bytes32(execData[132:164])))),
        sessionKey: address(uint160(uint256(bytes32(execData[164:196])))),
        validAfter: uint48(uint256(bytes32(execData[196:228]))),
        validUntil: uint48(uint256(bytes32(execData[228:260]))),
        tokenData: td,
        nonce: uint256(bytes32(execData[292:324]))
    });
+   
+   // Basic validation
+   require(lock.smartWallet != address(0), "RLV: Invalid smart wallet");
+   require(lock.sessionKey != address(0), "RLV: Invalid session key");
+   require(lock.validAfter <= lock.validUntil, "RLV: Invalid time range");
+   
+   return lock;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _getResourceLock(bytes calldata _callData) internal view returns (ResourceLock memory) {
if (bytes4(_callData[:4]) == IERC7579Account.execute.selector) {
(CallType calltype,,,) = ModeLib.decode(ModeCode.wrap(bytes32(_callData[4:36])));
if (calltype == CALLTYPE_SINGLE) {
(,, bytes calldata execData) = ExecutionLib.decodeSingle(_callData[100:]);
(uint256 arrayOffset, uint256 arrayLength) = _getArrayInfo(execData);
TokenData[] memory td = new TokenData[](arrayLength);
for (uint256 i; i < arrayLength; ++i) {
td[i] = _getSingleTokenData(execData, 132 + arrayOffset + (i * 64));
}
return ResourceLock({
chainId: uint256(bytes32(execData[100:132])),
smartWallet: address(uint160(uint256(bytes32(execData[132:164])))),
sessionKey: address(uint160(uint256(bytes32(execData[164:196])))),
validAfter: uint48(uint256(bytes32(execData[196:228]))),
validUntil: uint48(uint256(bytes32(execData[228:260]))),
tokenData: td,
nonce: uint256(bytes32(execData[292:324]))
});
}
revert RLV_OnlyCallTypeSingle();
}
}
function _getResourceLock(bytes calldata _callData) internal view returns (ResourceLock memory) {
if (bytes4(_callData[:4]) == IERC7579Account.execute.selector) {
(CallType calltype,,,) = ModeLib.decode(ModeCode.wrap(bytes32(_callData[4:36])));
if (calltype == CALLTYPE_SINGLE) {
(,, bytes calldata execData) = ExecutionLib.decodeSingle(_callData[100:]);
(uint256 arrayOffset, uint256 arrayLength) = _getArrayInfo(execData);
TokenData[] memory td = new TokenData[](arrayLength);
for (uint256 i; i < arrayLength; ++i) {
td[i] = _getSingleTokenData(execData, 132 + arrayOffset + (i * 64));
}
// Add validation at the end before returning
ResourceLock memory lock = ResourceLock({
chainId: uint256(bytes32(execData[100:132])),
smartWallet: address(uint160(uint256(bytes32(execData[132:164])))),
sessionKey: address(uint160(uint256(bytes32(execData[164:196])))),
validAfter: uint48(uint256(bytes32(execData[196:228]))),
validUntil: uint48(uint256(bytes32(execData[228:260]))),
tokenData: td,
nonce: uint256(bytes32(execData[292:324]))
});
// Basic validation
require(lock.smartWallet != address(0), "RLV: Invalid smart wallet");
require(lock.sessionKey != address(0), "RLV: Invalid session key");
require(lock.validAfter <= lock.validUntil, "RLV: Invalid time range");
return lock;
}
revert RLV_OnlyCallTypeSingle();
}
}

- Tracking nonce per SCW for signatures with Merkle proofs appended (ResourceLock signatures).
- Adjusted test utils to take into account new changes.
- Tested successful increment of nonce on valid signature.
- Added EntryPoint test dependency as was previously overridden by .gitignore as Soldeer uses dependencies folder and both were being selected.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

♻️ Duplicate comments (2)
src/modules/validators/ResourceLockValidator.sol (2)

70-78: Add a global minimum-length guard for signatures

Both execution branches assume signature.length >= 65.
When a malformed (e.g. 60-byte) signature is supplied the slices below silently truncate and will revert with an out-of-bounds panic.

bytes calldata signature = userOp.signature;
-require(signature.length == 65 || signature.length >= 97, "RLV: bad sig length");
+require(signature.length == 65 || signature.length >= 97, "RLV: bad sig length");

(The 97 constant is 65 /*sig*/ + 32 /*root*/ — the minimum before any proof bytes.)


110-140: Replicate the length guard in isValidSignatureWithSender

Identical unsafe slicing occurs here; apply the same ≥ 97 byte guard as above to avoid out-of-bounds panics.
(The earlier comment already covered this pattern.)

🧹 Nitpick comments (7)
test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (2)

344-352: Suppress “unused-variable” warnings from discarded return values

Several tests capture rl, proof, merkleRoot but never read them, triggering compiler warnings and obscuring real issues during CI.

-        (PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =
+        (PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

can be rewritten as

-        (PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =
+        (PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

(PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

becomes

(PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

Instead, drop the unused identifiers:

(PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

(PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =

(or replace with _ placeholders).
This keeps the bytecode identical but silences warnings.

Also applies to: 370-378, 398-405


413-441: Un-checked loop increment saves gas in test runs

The for (uint256 i; i < 10; ++i) loop is executed on-chain during tests; using an unchecked { ++i; } block avoids the redundant overflow check:

for (uint256 i; i < 10; ++i) {
-    tokens[i] = TokenData({token: vm.randomAddress(), amount: vm.randomUint()});
+    unchecked { ++i; }
+    tokens[i] = TokenData({token: vm.randomAddress(), amount: vm.randomUint()});
}

While minor, this is a free optimisation and keeps gas profiles consistent with production contracts that may borrow this pattern.

Also applies to: 492-520

test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1)

68-82: Use runtime block.chainid instead of hard-coding Arbitrum (42161)

Hard-coding the chain id makes the helper brittle; tests will fail when run on a local fork or an L1 simulation.

-            chainId: 42161,
+            chainId: block.chainid,
src/test/dependencies/EntryPoint.sol (2)

8-27: Consider making _entrypointAddr immutable

If the contract is always initialised via init(), storing _entrypointAddr once as immutable (constructor) removes an SLOAD every time it’s read:

address immutable _entrypointAddr;
constructor(address ep) {
    _entrypointAddr = ep;
    initSenderCreator();
}

This requires minor refactoring of etchEntrypoint() but yields cheaper reads in every simulation.


39-74: Helper free-functions risk name collisions

Global-scope functions (sign, label, prank, …) are convenient but collide if another utility file defines the same signature. Wrapping them in a library Cheats avoids shadowing and keeps the public namespace clean.

library Cheats {
    function getAddr(uint256 pk) internal pure returns (address) { … }
    …
}

Call sites then import with using Cheats for uint256; or Cheats.getAddr(pk).

src/modules/validators/ResourceLockValidator.sol (2)

174-197: Ensure call-type is enforced at entry

CALLTYPE_SINGLE is validated, but the earlier selector check is silent.
Move the CALLTYPE_SINGLE guard outside and revert early to save gas.


218-223: Use unchecked increment for minor gas saving

nonce cannot overflow under realistic conditions. Wrapping the addition in unchecked {} saves ~5 gas.

function _incrementNonce(address smartAccount) internal returns (uint256) {
-    uint256 newNonce = validatorStorage[smartAccount].nonce + 1;
+    uint256 newNonce;
+    unchecked { newNonce = validatorStorage[smartAccount].nonce + 1; }
    validatorStorage[smartAccount].nonce = newNonce;
    emit RLV_NonceUpdated(smartAccount, newNonce);
    return newNonce;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a31993 and 1f8c265.

📒 Files selected for processing (6)
  • .gitignore (1 hunks)
  • src/interfaces/IResourceLockValidator.sol (1 hunks)
  • src/modules/validators/ResourceLockValidator.sol (1 hunks)
  • src/test/dependencies/EntryPoint.sol (1 hunks)
  • test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1 hunks)
  • test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .gitignore
  • src/interfaces/IResourceLockValidator.sol
🔇 Additional comments (2)
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1)

18-26: withRequiredModules leaves the test in scw context

vm.startPrank(address(scw)) is opened inside the modifier but the caller might need to perform assertions from the default test signer later.
Accidentally leaking the prank across internal calls can mask authZ bugs.

Consider moving the prank inside each helper or balancing it with vm.stopPrank() in a try…finally style after hook:

modifier withRequiredModules() {
    …
-    vm.startPrank(address(scw));
-    _;
-    vm.stopPrank();
+    {
+        vm.startPrank(address(scw));
+        _;
+        vm.stopPrank();
+    }
}
src/modules/validators/ResourceLockValidator.sol (1)

199-215: Hash construction may be non-canonical

abi.encode(_lock.tokenData) encodes a dynamic array, including its length and offset, which changes the hash if token ordering or padding varies.
If ordering is not semantically significant, sort tokenData or hash each element individually using abi.encodePacked.

Comment on lines 54 to 66
function _buildResourceLockHash(ResourceLock memory _lock) internal pure returns (bytes32) {
return keccak256(
abi.encode(
_lock.chainId,
_lock.smartWallet,
_lock.sessionKey,
_lock.validAfter,
_lock.validUntil,
abi.encode(_lock.tokenData),
_lock.nonce
)
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

_buildResourceLockHash double-encodes the dynamic array

abi.encode(_lock.tokenData) embeds a length prefix plus the entire in-memory layout; two semantically equal arrays with different non-canonical in-memory representation hash to different values.

Safer pattern:

-                abi.encode(_lock.tokenData),
+                keccak256(abi.encode(_lock.tokenData)),

so the outer hash depends only on the logical content of tokenData, not its memory encoding.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _buildResourceLockHash(ResourceLock memory _lock) internal pure returns (bytes32) {
return keccak256(
abi.encode(
_lock.chainId,
_lock.smartWallet,
_lock.sessionKey,
_lock.validAfter,
_lock.validUntil,
abi.encode(_lock.tokenData),
_lock.nonce
)
);
}
function _buildResourceLockHash(ResourceLock memory _lock) internal pure returns (bytes32) {
return keccak256(
abi.encode(
_lock.chainId,
_lock.smartWallet,
_lock.sessionKey,
_lock.validAfter,
_lock.validUntil,
- abi.encode(_lock.tokenData),
+ keccak256(abi.encode(_lock.tokenData)),
_lock.nonce
)
);
}

Comment on lines +31 to +35
function etchEntrypoint() returns (IEntryPoint) {
address payable entryPoint = payable(address(new EntryPointSimulationsPatch()));
etch(ENTRYPOINT_ADDR, entryPoint.code);
EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(entryPoint);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrong address passed to init – breaks deterministic SenderCreator

etchEntrypoint() calls init(entryPoint) where entryPoint is the temporary address of the freshly-deployed patch, not the final ENTRYPOINT_ADDR.
Consequently _entrypointAddr inside the contract differs from its on-chain address and the RLP-based calculation in initSenderCreator() produces an invalid SenderCreator.

-    EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(entryPoint);
+    EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(ENTRYPOINT_ADDR);

Without this fix any CREATE-based simulations relying on the sender creator may revert or emit unexpected addresses.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function etchEntrypoint() returns (IEntryPoint) {
address payable entryPoint = payable(address(new EntryPointSimulationsPatch()));
etch(ENTRYPOINT_ADDR, entryPoint.code);
EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(entryPoint);
function etchEntrypoint() returns (IEntryPoint) {
address payable entryPoint = payable(address(new EntryPointSimulationsPatch()));
etch(ENTRYPOINT_ADDR, entryPoint.code);
- EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(entryPoint);
+ EntryPointSimulationsPatch(payable(ENTRYPOINT_ADDR)).init(ENTRYPOINT_ADDR);
}

Comment on lines 162 to 166
function _getArrayInfo(bytes calldata _data) internal pure returns (uint256 offset, uint256 length) {
offset = uint256(bytes32(_data[260:292]));
length = uint256(bytes32(_data[100 + offset:132 + offset]));
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Hard-coded calldata offsets are brittle

_getArrayInfo relies on magic numbers (260, 292, 100). Any abi-encoding change will silently break decoding.

Consider packing the array length immediately after the array pointer or, preferably, switch to abi.decode on a typed struct:

struct ExecHeader {
    uint256 chainId;
    address smartWallet;
    ...
    TokenData[] tokenData;
}
ExecHeader memory h = abi.decode(execData, (ExecHeader));

Comment on lines +54 to +62
function onInstall(bytes calldata _data) external override {
address owner = address(bytes20(_data[_data.length - 20:]));
if (validatorStorage[msg.sender].enabled) {
revert RLV_AlreadyInstalled(msg.sender, validatorStorage[msg.sender].owner);
}
validatorStorage[msg.sender].owner = owner;
validatorStorage[msg.sender].enabled = true;
emit RLV_ValidatorEnabled(msg.sender, owner);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate _data length and zero-address owner on install

onInstall blindly slices the last 20 bytes of _data and stores them as the owner.
If _data.length < 20, or if those 20 bytes decode to address(0), the validator either reverts with a low-level slice panic or leaves the contract in an unusable state.

function onInstall(bytes calldata _data) external override {
-    address owner = address(bytes20(_data[_data.length - 20:]));
+    require(_data.length >= 20, "RLV: install data too short");
+    address owner = address(bytes20(_data[_data.length - 20:]));
+    require(owner != address(0), "RLV: zero owner");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function onInstall(bytes calldata _data) external override {
address owner = address(bytes20(_data[_data.length - 20:]));
if (validatorStorage[msg.sender].enabled) {
revert RLV_AlreadyInstalled(msg.sender, validatorStorage[msg.sender].owner);
}
validatorStorage[msg.sender].owner = owner;
validatorStorage[msg.sender].enabled = true;
emit RLV_ValidatorEnabled(msg.sender, owner);
}
function onInstall(bytes calldata _data) external override {
require(_data.length >= 20, "RLV: install data too short");
address owner = address(bytes20(_data[_data.length - 20:]));
require(owner != address(0), "RLV: zero owner");
if (validatorStorage[msg.sender].enabled) {
revert RLV_AlreadyInstalled(msg.sender, validatorStorage[msg.sender].owner);
}
validatorStorage[msg.sender].owner = owner;
validatorStorage[msg.sender].enabled = true;
emit RLV_ValidatorEnabled(msg.sender, owner);
}

Comment on lines 90 to 108
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
// Nonce validation
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Nonce and chain-context are unchecked; lock validity window ignored

  1. rl.nonce is taken from validatorStorage[scw].nonce; an attacker could craft calldata with an arbitrary scw that is not msg.sender, thus bypassing the correct nonce.
  2. validAfter/validUntil are never compared to block.timestamp; an expired lock can still pass.
  3. rl.chainId is not compared to block.chainid.
ResourceLock memory rl = _getResourceLock(userOp.callData);

+require(rl.smartWallet == msg.sender, "RLV: scw mismatch");
+require(rl.chainId == block.chainid, "RLV: wrong chain");
+require(
+    rl.validAfter <= block.timestamp && block.timestamp <= rl.validUntil,
+    "RLV: lock expired/too early"
+);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
// Nonce validation
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// or if signature.length >= 65 (standard signature length + proof packing)
ResourceLock memory rl = _getResourceLock(userOp.callData);
// Ensure the lock was actually issued for this wallet
require(rl.smartWallet == msg.sender, "RLV: scw mismatch");
// Prevent replay across chains
require(rl.chainId == block.chainid, "RLV: wrong chain");
// Enforce the lock’s valid time window
require(
rl.validAfter <= block.timestamp && block.timestamp <= rl.validUntil,
"RLV: lock expired/too early"
);
// Nonce validation
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes in signature
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;

Comment on lines 100 to 108
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Require extended signature ≥ 97 bytes before slicing

signature[65:97] and signature[97:] will revert if signature.length is 66–96.

-bytes32 root = bytes32(signature[65:97]); // 32 bytes
-bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes
+require(signature.length >= 97, "RLV: short extended sig");
+bytes32 root = bytes32(signature[65:97]);
+bytes32[] memory proof = abi.decode(signature[97:], (bytes32[]));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;
}
// Ensure extended signature has at least 97 bytes before slicing
require(signature.length >= 97, "RLV: short extended sig");
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof = abi.decode(signature[97:], (bytes32[])); // Rest of bytes
if (walletOwner == ECDSA.recover(root, ecdsaSignature)) {
_incrementNonce(msg.sender);
return SIG_VALIDATION_SUCCESS;
}
bytes32 sigRoot = ECDSA.toEthSignedMessageHash(root);
address recoveredMSigner = ECDSA.recover(sigRoot, ecdsaSignature);
if (walletOwner != recoveredMSigner) return SIG_VALIDATION_FAILED;
return SIG_VALIDATION_SUCCESS;

- Proof will now unpack correctly for non-existent proof, single proof and multiple proofs.
- Non-existent proof will now fail gracefully rather than just via `EVMError`.
- Removed `nonce` from ResourceLock and added `bidHash`.
- Fixed test helper functions to correctly apply proof packing.
- Fixed test suite and added new tests for checking mismatched nonces and correct signature unpacking.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🔭 Outside diff range comments (2)
script/CredibleAccountScript.s.sol (1)

1-96: 🛠️ Refactor suggestion

Add error handling to the deployment script

The script lacks error handling for deployment failures. Consider adding try/catch blocks to handle potential deployment issues gracefully.

 function run() external {
     HookMultiPlexer hookMultiPlexer;
     CredibleAccountModule credibleAccountModule;
     ResourceLockValidator resourceLockValidator;
     ProofVerifier proofVerifier;
     uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
+    
+    // Track deployment success
+    bool success = true;
+    string memory failureReason = "";
+    
     vm.startBroadcast(deployerPrivateKey);

     /*//////////////////////////////////////////////////////////////
                         Starting Deployment
     //////////////////////////////////////////////////////////////*/

     console2.log("Starting deployment sequence...");

     /*//////////////////////////////////////////////////////////////
                         Deploy HookMultiPlexer
     //////////////////////////////////////////////////////////////*/

     console2.log("Deploying HookMultiPlexer...");
+    try new HookMultiPlexer() returns (HookMultiPlexer _hookMultiPlexer) {
+        hookMultiPlexer = _hookMultiPlexer;
+        console2.log("HookMultiPlexer deployed at address", address(hookMultiPlexer));
+    } catch Error(string memory reason) {
+        console2.log("Failed to deploy HookMultiPlexer:", reason);
+        success = false;
+        failureReason = reason;
+    } catch {
+        console2.log("Failed to deploy HookMultiPlexer: Unknown error");
+        success = false;
+        failureReason = "Unknown error during HookMultiPlexer deployment";
+    }
+    
+    // Only continue if previous deployment succeeded
+    if (!success) {
+        console2.log("Deployment sequence failed:", failureReason);
+        vm.stopBroadcast();
+        return;
+    }
-    hookMultiPlexer = new HookMultiPlexer();
-    console2.log("HookMultiPlexer deployed at address", address(hookMultiPlexer));

     // Apply similar try/catch pattern to other contract deployments
src/modules/hooks/HookMultiPlexer.sol (1)

124-133: ⚠️ Potential issue

Logic bug – hooks are deleted before they are iterated.

delete $config.hooks[HookType.GLOBAL]; zeroes the array, so the subsequent loop iterates over an empty list and never calls onUninstall on the sub-hooks.

Move the deletion after the loop:

- delete $config.hooks[HookType.GLOBAL];
  uint256 length = $config.hooks[HookType.GLOBAL].length;
  for (uint256 i = 0; i < length; i++) {
      address hookAddress = $config.hooks[HookType.GLOBAL][i];
      _removeHook(hookAddress, HookType.GLOBAL);
  }
+ delete $config.hooks[HookType.GLOBAL];
♻️ Duplicate comments (7)
src/common/Structs.sol (1)

60-80: Add NatSpec documentation for hook-related structs

The hook-related structs still lack NatSpec documentation. Please add comprehensive documentation to maintain consistency with other structs in the file.

 // HookMultiplexer
+/// @title SigHookInit
+/// @notice Struct for initializing signature hooks.
+/// @dev Used to configure function-specific hooks.
 struct SigHookInit {
     bytes4 sig;
     address[] subHooks;
 }

+/// @title HookAndContext
+/// @notice Struct containing a hook address alongside its context.
+/// @dev Used to manage hook execution with associated context data.
 struct HookAndContext {
     address hook;
     bytes context;
 }

+/// @title SignatureHooks
+/// @notice Struct for managing function-specific hooks.
+/// @dev Maps function signatures to their associated hooks.
 struct SignatureHooks {
     bytes4[] allSigs;
     mapping(bytes4 => address[]) sigHooks;
 }

+/// @title Config
+/// @notice Configuration struct for hook management.
+/// @dev Manages initialization state and hook mappings by type.
 struct Config {
     bool initialized;
     mapping(HookType hookType => address[]) hooks;
     mapping(HookType hookType => SignatureHooks) sigHooks;
 }
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1)

54-66: ⚠️ Potential issue

Fix double-encoding of dynamic array in _buildResourceLockHash

abi.encode(_lock.tokenData) embeds a length prefix plus the entire in-memory layout. Two semantically equal arrays with different non-canonical in-memory representation will hash to different values.

Apply this safer pattern:

 function _buildResourceLockHash(ResourceLock memory _lock) internal pure returns (bytes32) {
     return keccak256(
         abi.encode(
             _lock.chainId,
             _lock.smartWallet,
             _lock.sessionKey,
             _lock.validAfter,
             _lock.validUntil,
             _lock.bidHash,
-            abi.encode(_lock.tokenData)
+            keccak256(abi.encode(_lock.tokenData))
         )
     );
 }

This ensures the outer hash depends only on the logical content of tokenData, not its memory encoding.

src/modules/validators/ResourceLockValidator.sol (4)

53-61: No length / zero-address checks in onInstall.

An empty _data slice reverts with a slicing panic, or worse, installs a validator with owner == address(0).

- address owner = address(bytes20(_data[_data.length - 20:]));
+ require(_data.length >= 20, "RLV: install data too short");
+ address owner = address(bytes20(_data[_data.length - 20:]));
+ require(owner != address(0), "RLV: zero owner");

76-88: validateUserOp misses a minimal signature length guard.

If signature.length < 65, the slice operations revert.
Insert a defensive check:

+ require(signature.length >= 65, "RLV: short signature");

184-187: Hard-coded calldata offsets are brittle and undocumented.

_getArrayInfo assumes fixed positions (292, 324, 100). Any change to the encoded struct breaks decoding.

Refactor to an abi.decode approach or at least centralise the constants with comments describing the layout.


196-219: ⚠️ Potential issue

Function can fall through without returning a value → compilation error.

If bytes4(_callData) is not execute.selector, the function reaches the end without return/revert, violating its declared return type.

Add an explicit revert:

- }
+ }
+ revert("RLV: unsupported calldata");
test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (1)

528-530: Hard-coded main-net token addresses – same concern as earlier review

Previous feedback about replacing magic addresses with the existing in-memory usdc/dai variables still applies.

🧹 Nitpick comments (13)
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (2)

18-26: Add explanatory comments to the withRequiredModules modifier

The modifier installs multiple modules and hooks without explaining their purpose or relationships.

 modifier withRequiredModules() {
+    // Install ECDSA validator for basic signature verification
     _installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(moecdsav), hex"");
+    // Install Credible Account Module as a global hook for session key management
     _installHookViaMultiplexer(scw, address(cam), HookType.GLOBAL);
+    // Install Credible Account Module as a validator
     _installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
+    // Install Resource Lock Validator with EOA as the trusted validator
     _installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(rlv), abi.encode(eoa.pub));
     vm.startPrank(address(scw));
     _;
     vm.stopPrank();
 }

76-79: Use constants instead of magic numbers for time values

The hardcoded values 1732176210 and 1732435407 for validAfter and validUntil should be declared as constants with explanatory names.

+// Define time constants at contract level
+uint48 private constant VALID_AFTER_TIME = 1732176210; // May 21, 2024
+uint48 private constant VALID_UNTIL_TIME = 1732435407; // May 24, 2024

 function _generateResourceLock(address _scw, address _sk) internal view returns (ResourceLock memory) {
     TokenData[] memory td = new TokenData[](2);
     td[0] = TokenData({token: address(usdt), amount: 100});
     td[1] = TokenData({token: address(dai), amount: 200});
     ResourceLock memory rl = ResourceLock({
         chainId: 42161,
         smartWallet: _scw,
         sessionKey: _sk,
-        validAfter: 1732176210,
-        validUntil: 1732435407,
+        validAfter: VALID_AFTER_TIME,
+        validUntil: VALID_UNTIL_TIME,
         bidHash: DUMMY_BID_HASH,
         tokenData: td
     });
     return rl;
 }
test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (3)

555-567: Document the purpose and usage of the skipped test

The test_signature_unpacking test is skipped by default and lacks clear documentation on how to use it for debugging.

-    // NOTE: This test is for testing specific signatures to check correct unpacking
-    // Replace the op.signature with your own, add logs and run test
-    // Test will fail with RLV_ResourceLockHashNotInProof()
+    /// @notice Test for debugging signature unpacking mechanism
+    /// @dev This test is skipped by default and intended for manual debugging
+    /// How to use:
+    /// 1. Replace op.signature with your own signature to debug
+    /// 2. Add console.log statements to inspect unpacked components
+    /// 3. Run with `forge test --match-test test_signature_unpacking -v`
+    /// 4. Expected failure: RLV_ResourceLockHashNotInProof()
     function test_signature_unpacking() public withRequiredModules {
         vm.skip(true);
         // Create UserOp with ResourceLock
         (PackedUserOperation memory op,, bytes32[] memory proof, bytes32 merkleRoot) =
             _createUserOpWithResourceLock(address(scw), sessionKey, true);
         // Use predefined sig
         op.signature =
             hex"137ad66810b0325f2820c1f9160c2076a1607e5fd7010c4b02368b3905bccef1222086c638e9d828464dcc6330517430cd93516969b23612e3e41199f65950621b4a2c9276c86b3c670b424ab981c89c53f858e870f31a2999cf52353837897362bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a";
         _executeUserOp(op);
     }

414-445: Extract token setup to a helper function

Multiple test functions (test_validateUserOp_DirectMerkleSignature_LargeTokenData and test_validateUserOp_EthSignedMerkleSignature_LargeTokenData) use nearly identical code for setting up token data and verification.

Extract the common logic to a helper function to reduce duplication:

+    /// @notice Helper function to create and validate a user operation with large token data
+    /// @param signType Type of signature to use (0 = direct, 1 = ethSigned)
+    /// @return op The executed user operation
+    function _createAndValidateUserOpWithLargeTokenData(uint8 signType) 
+        internal 
+        returns (PackedUserOperation memory op) 
+    {
+        // Create UserOp
+        op = _createUserOp(address(scw), address(rlv));
+        // Create TokenData array with 10 entries
+        TokenData[] memory tokens = new TokenData[](10);
+        for (uint256 i; i < 10; ++i) {
+            tokens[i] = TokenData({token: vm.randomAddress(), amount: vm.randomUint()});
+        }
+        // Create ResourceLock with large TokenData array
+        ResourceLock memory rl = _generateResourceLock(address(scw), sessionKey.pub);
+        rl.tokenData = tokens;
+        // Generate proof and merkle root
+        (bytes32[] memory proof, bytes32 merkleRoot,) = getTestProof(_buildResourceLockHash(rl), true);
+        // Create UserOp calldata
+        op.callData = abi.encodeCall(
+            IERC7579Account.execute,
+            (
+                ModeLib.encodeSimpleSingle(),
+                ExecutionLib.encodeSingle(
+                    address(cam), 0, abi.encodeWithSelector(cam.enableSessionKey.selector, abi.encode(rl))
+                )
+            )
+        );
+        // Sign merkle root
+        bytes memory sig;
+        if (signType == 0) {
+            sig = _sign(merkleRoot, eoa);
+        } else {
+            sig = _ethSign(merkleRoot, eoa);
+        }
+        op.signature = bytes.concat(sig, abi.encodePacked(merkleRoot), _packProofForSignature(proof));
+        _executeUserOp(op);
+        
+        // Check locked tokens
+        ICredibleAccountModule.LockedToken[] memory locked = cam.getLockedTokensForSessionKey(sessionKey.pub);
+        assertEq(locked.length, 10);
+        for (uint256 i; i < 10; ++i) {
+            assertEq(locked[i].token, tokens[i].token);
+            assertEq(locked[i].lockedAmount, tokens[i].amount);
+        }
+        
+        return op;
+    }

     function test_validateUserOp_DirectMerkleSignature_LargeTokenData() public withRequiredModules {
-        // Create UserOp
-        PackedUserOperation memory op = _createUserOp(address(scw), address(rlv));
-        // Create TokenData array with 10 entries
-        TokenData[] memory tokens = new TokenData[](10);
-        for (uint256 i; i < 10; ++i) {
-            tokens[i] = TokenData({token: vm.randomAddress(), amount: vm.randomUint()});
-        }
-        // Create ResourceLock with large TokenData array
-        ResourceLock memory rl = _generateResourceLock(address(scw), sessionKey.pub);
-        rl.tokenData = tokens;
-        // Generate proof and merkle root
-        (bytes32[] memory proof, bytes32 merkleRoot,) = getTestProof(_buildResourceLockHash(rl), true);
-        // Create UserOp calldata
-        op.callData = abi.encodeCall(
-            IERC7579Account.execute,
-            (
-                ModeLib.encodeSimpleSingle(),
-                ExecutionLib.encodeSingle(
-                    address(cam), 0, abi.encodeWithSelector(cam.enableSessionKey.selector, abi.encode(rl))
-                )
-            )
-        );
-        // Sign merkle root with eth prefix
-        bytes memory sig = _sign(merkleRoot, eoa);
-        op.signature = bytes.concat(sig, abi.encodePacked(merkleRoot), _packProofForSignature(proof));
-        _executeUserOp(op);
-        // Check locked tokens
-        ICredibleAccountModule.LockedToken[] memory locked = cam.getLockedTokensForSessionKey(sessionKey.pub);
-        assertEq(locked.length, 10);
-        for (uint256 i; i < 10; ++i) {
-            assertEq(locked[i].token, tokens[i].token);
-            assertEq(locked[i].lockedAmount, tokens[i].amount);
-        }
+        _createAndValidateUserOpWithLargeTokenData(0);
     }

And similarly update the corresponding eth-sign test function.


262-262: Use specific error selectors in revert expectations

The test uses bytes4(0) as the expected error selector in several places. This seems to indicate a general low-level Solidity error rather than a specific error from the contract.

Consider checking for specific error selectors that better represent what should happen, or at least add a comment explaining why bytes4(0) is expected.

Also applies to: 274-274, 286-286

script/CredibleAccountScript.s.sol (2)

12-16: Unused constants and placeholders

The script contains several constants that are either unused or have placeholder values (0x0000...). These should either be properly set or removed to prevent confusion.

-    bytes32 public immutable SALT = bytes32(abi.encodePacked("ModularEtherspotWallet:Create2:salt"));
-    address public constant DEPLOYER = 0x09FD4F6088f2025427AB1e89257A44747081Ed59;
-    address public constant EXPECTED_MULTIPLEXER_ADDRESS = 0x0000000000000000000000000000000000000000;
-    address public constant EXPECTED_CA_MODULE_ADDRESS = 0x0000000000000000000000000000000000000000;
-    address public constant EXPECTED_RESOURCE_LOCK_VALIDATOR_ADDRESS = 0x0000000000000000000000000000000000000000;
+    // TODO: Set up actual expected addresses for deterministic deployment verification
+    // or remove if not needed for the deployment flow

37-46: Clean up commented deployment verification code

The script contains three sections of commented code that are intended for verifying deployment addresses. Since this functionality isn't being used, consider either implementing it fully or removing the comments to maintain a cleaner codebase.

Instead of keeping the commented verification code in three places, implement a helper function for verification:

+    /**
+     * @notice Helper to verify deployed address matches expected address
+     * @param deployed The actual deployed contract address
+     * @param expected The expected contract address
+     * @param contractName The name of the contract for logging
+     */
+    function _verifyDeployment(address deployed, address expected, string memory contractName) internal {
+        if (expected != address(0) && deployed != expected) {
+            revert(string.concat("Unexpected ", contractName, " address!!!"));
+        }
+        console2.log(string.concat(contractName, " deployed at address"), deployed);
+    }

     function run() external {
         // ...existing code...
         
         console2.log("Deploying HookMultiPlexer...");
-        // if (EXPECTED_MULTIPLEXER_ADDRESS.code.length == 0) {
         hookMultiPlexer = new HookMultiPlexer();
-        // if (address(hookMultiPlexer) != EXPECTED_MULTIPLEXER_ADDRESS) {
-        //     revert("Unexpected HookMultiPlexer address!!!");
-        // } else {
-        console2.log("HookMultiPlexer deployed at address", address(hookMultiPlexer));
-        //     }
-        // } else {
-        //     console2.log("HookMultiPlexer already deployed at address", EXPECTED_MULTIPLEXER_ADDRESS);
-        // }
+        _verifyDeployment(address(hookMultiPlexer), EXPECTED_MULTIPLEXER_ADDRESS, "HookMultiPlexer");

         // ... apply similar changes to other deployment sections ...

Then uncomment this function once you have actual expected addresses to verify against.

Also applies to: 61-70, 77-86

src/modules/hooks/HookMultiPlexer.sol (2)

166-184: getHooks omits SIG/TARGET_SIG uniqueness across nested mappings.

SigHook entries are added via storeSelectorHooks, but getHooks only flattens allSigs values, not the actual addresses per selector.
Consumers calling this view may get incomplete data.

Consider iterating over sigHooks.allSigs and joining each array of hooks per selector, or expose a dedicated getter for sig hooks.


198-208: addHook allows silent duplication.

Hooks are blindly push-ed; multiple identical hooks of the same type will accumulate and waste gas in preCheck().

Add an existence check before pushing, or use pushUnique as done elsewhere:

- $getConfig({account: msg.sender}).hooks[hookType].push(hook);
+ $getConfig({account: msg.sender}).hooks[hookType].pushUnique(hook);
src/modules/validators/ResourceLockValidator.sol (1)

225-236: Docstring/outcome mismatch – nonce omitted from hash.

The Natspec says the nonce is included, but _buildResourceLockHash leaves it out (bidHash is included instead).
Either update the docs or add the nonce field to prevent ambiguous lock hashes.

test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (1)

128-129: Remove debug console2.log in production-grade tests

console2.log slows fuzzing and pollutes the output; keep it only while developing.

-        console2.log("sessionKeyData.validUntil", sessionKeyData.validUntil);
test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol (1)

125-145: Unused _user parameter in _claimTokensBySolver – dead code & API noise

_user is never referenced, making the signature misleading and hiding potential mistakes at call sites.

-function _claimTokensBySolver(
-    User memory _user,
+function _claimTokensBySolver(

(also drop the argument from all invocations)

test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (1)

544-547: Dead variables increase cognitive load

enableSessionKeyData2 / newRl are prepared but never used, so the test does the same work twice and risks diverging expectations.

-        bytes memory enableSessionKeyData2 = abi.encodeWithSelector(CAM.enableSessionKey.selector, newRl);

(If the second payload was meant to be executed, add it to the batch; otherwise delete the unused code.)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f8c265 and 44d16e0.

📒 Files selected for processing (12)
  • broadcast/CredibleAccountScript.s.sol/11155111/run-1746104233.json (1 hunks)
  • broadcast/CredibleAccountScript.s.sol/11155111/run-latest.json (1 hunks)
  • script/CredibleAccountScript.s.sol (1 hunks)
  • src/common/Structs.sol (1 hunks)
  • src/modules/hooks/HookMultiPlexer.sol (14 hunks)
  • src/modules/validators/ResourceLockValidator.sol (1 hunks)
  • test/ModularTestBase.sol (1 hunks)
  • test/modules/CredibleAccountModule/concrete/CredibleAccountModule.t.sol (5 hunks)
  • test/modules/CredibleAccountModule/fuzz/CredibleAccountModule.t.sol (4 hunks)
  • test/modules/CredibleAccountModule/utils/CredibleAccountModuleTestUtils.sol (1 hunks)
  • test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1 hunks)
  • test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • broadcast/CredibleAccountScript.s.sol/11155111/run-latest.json
  • broadcast/CredibleAccountScript.s.sol/11155111/run-1746104233.json
🔇 Additional comments (2)
src/common/Structs.sol (1)

56-56: Field type changed from uint256 nonce to bytes32 bidHash

The ResourceLock struct has been updated to use bytes32 bidHash instead of the previous uint256 nonce field. This change aligns with the new ResourceLockValidator module implementation that uses the bidHash for signature verification and Merkle proof validation.

test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1)

292-304:

❌ Incorrect review comment

Check for specific revert reason in empty proof test

While the test correctly expects a revert with RLV_ResourceLockHashNotInProof, it might be more accurate to check for a specific error related to empty proof arrays.


🏁 Script executed:

#!/bin/bash
# Check if ResourceLockValidator has a specific error for empty proofs

# Look for specific error constant in the ResourceLockValidator contract
rg -A 1 "error RLV_" --include "*.sol" src/modules/validators/ResourceLockValidator.sol

Length of output: 531


#!/bin/bash
# Correctly search for ResourceLockValidator error constants in .sol files
rg -A1 "error RLV_" -g '*.sol' src/modules/validators

#!/bin/bash
# List all RLV_ error definitions in validators
rg -n "error RLV_" -g "*.sol" src/modules/validators

# List all error declarations in ResourceLockValidator.sol
rg -n "error " src/modules/validators/ResourceLockValidator.sol

#!/bin/bash
# Search for ResourceLockValidator error definitions related to empty proofs
rg -n "error RLV_" -g "*.sol" src/modules/validators

No specific empty-proof error exists—test is correct as written
The ResourceLockValidator contract only defines RLV_ResourceLockHashNotInProof for proof-related failures; there isn’t a distinct error for empty proof arrays. Your test already asserts the correct revert reason, so no change is needed.

Likely an incorrect or invalid review comment.

Comment on lines +304 to +317
vm.startPrank(_owner);
// Execute the module installation
mockExec.executeViaAccount(
IERC7579Account(_scw),
address(_scw),
0,
abi.encodeWithSelector(_scw.installModule.selector, _moduleType, _module, _initData)
);
if (_moduleType == MODULE_TYPE_FALLBACK) {
bytes4 selector = bytes4(bytes32(_initData));
return scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
}
vm.stopPrank();
// Verify that the module is installed
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

vm.stopPrank() is skipped on early return → prank leaks into subsequent code.

When installing a fallback module the function returns inside the if, leaving the prank active.

if (_moduleType == MODULE_TYPE_FALLBACK) {
    bytes4 selector = bytes4(bytes32(_initData));
-   return scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
+   bool installed = scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
+   vm.stopPrank();
+   return installed;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
vm.startPrank(_owner);
// Execute the module installation
mockExec.executeViaAccount(
IERC7579Account(_scw),
address(_scw),
0,
abi.encodeWithSelector(_scw.installModule.selector, _moduleType, _module, _initData)
);
if (_moduleType == MODULE_TYPE_FALLBACK) {
bytes4 selector = bytes4(bytes32(_initData));
return scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
}
vm.stopPrank();
// Verify that the module is installed
vm.startPrank(_owner);
// Execute the module installation
mockExec.executeViaAccount(
IERC7579Account(_scw),
address(_scw),
0,
abi.encodeWithSelector(_scw.installModule.selector, _moduleType, _module, _initData)
);
if (_moduleType == MODULE_TYPE_FALLBACK) {
bytes4 selector = bytes4(bytes32(_initData));
bool installed = scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
vm.stopPrank();
return installed;
}
vm.stopPrank();
// Verify that the module is installed

Comment on lines +328 to +351
vm.startPrank(_owner);
if (_moduleType == MODULE_TYPE_FALLBACK) {
mockExec.executeViaAccount(
IERC7579Account(_scw),
address(_scw),
0,
abi.encodeWithSelector(_scw.uninstallModule.selector, _moduleType, _module, _deInitData)
);
return scw.isModuleInstalled(_moduleType, _module, _deInitData);
}
address prevValidator = _getPrevValidator(_scw, _module);
// Execute the module installation
mockExec.executeViaAccount(
IERC7579Account(_scw),
address(_scw),
0,
abi.encodeWithSelector(
_scw.uninstallModule.selector, _moduleType, _module, abi.encode(prevValidator, _deInitData)
)
);
// Verify that the module is installed
return _scw.isModuleInstalled(_moduleType, _module, "");
vm.stopPrank();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Unreachable vm.stopPrank() and missing stop on early return.

The return statements above line 349 prevent the final vm.stopPrank() from executing, again keeping the prank open.

Move vm.stopPrank() before each return, or use a try…finally pattern:

- return _scw.isModuleInstalled(_moduleType, _module, "");
- vm.stopPrank();
+ bool uninstalled = _scw.isModuleInstalled(_moduleType, _module, "");
+ vm.stopPrank();
+ return uninstalled;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +280 to +286
BootstrapConfig[] memory validators = new BootstrapConfig[](1);
validators[0] = _makeBootstrapConfig(address(mockVal), "");
BootstrapConfig[] memory executors = makeBootstrapConfig(address(mockExec), "");
BootstrapConfig memory hook = _makeBootstrapConfig(address(hmp), hmpData);
BootstrapConfig[] memory fallbacks = makeBootstrapConfig(address(0), "");
bytes memory _initCode = abi.encode(
_owner,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

makeBootstrapConfig call is undefined / wrong overload.

makeBootstrapConfig is not declared in this contract and the imported BootstrapUtil exposes _makeBootstrapConfig (note the leading underscore) that returns a single BootstrapConfig.
Compiling will fail here.

- BootstrapConfig[] memory executors = makeBootstrapConfig(address(mockExec), "");
+ BootstrapConfig[] memory executors = new BootstrapConfig[](1);
+ executors[0] = _makeBootstrapConfig(address(mockExec), "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BootstrapConfig[] memory validators = new BootstrapConfig[](1);
validators[0] = _makeBootstrapConfig(address(mockVal), "");
BootstrapConfig[] memory executors = makeBootstrapConfig(address(mockExec), "");
BootstrapConfig memory hook = _makeBootstrapConfig(address(hmp), hmpData);
BootstrapConfig[] memory fallbacks = makeBootstrapConfig(address(0), "");
bytes memory _initCode = abi.encode(
_owner,
BootstrapConfig[] memory validators = new BootstrapConfig[](1);
validators[0] = _makeBootstrapConfig(address(mockVal), "");
BootstrapConfig[] memory executors = new BootstrapConfig[](1);
executors[0] = _makeBootstrapConfig(address(mockExec), "");
BootstrapConfig memory hook = _makeBootstrapConfig(address(hmp), hmpData);
BootstrapConfig[] memory fallbacks = makeBootstrapConfig(address(0), "");
bytes memory _initCode = abi.encode(
_owner,

Comment on lines +140 to +142
impl = new ModularEtherspotWallet();
factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
moecdsav = new MultipleOwnerECDSAValidator();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Factory uses an un-initialised owner (likely address(0)).

eoa is populated later in _testInit, so at this point eoa.pub is the default zero address.
This means every wallet produced by the factory in the test run will be owned by address(0), which can mask bugs or cause silent failures in owner-checks.

- factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
+ // Initialise `eoa` first or pass the intended owner explicitly
+ eoa = _createUser("EOA");
+ factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl = new ModularEtherspotWallet();
factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
moecdsav = new MultipleOwnerECDSAValidator();
impl = new ModularEtherspotWallet();
// Initialise `eoa` first or pass the intended owner explicitly
eoa = _createUser("EOA");
factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
moecdsav = new MultipleOwnerECDSAValidator();

Comment on lines +91 to +112
// Nonce validation
bytes memory ecdsaSignature = signature[0:65];
bytes32 root = bytes32(signature[65:97]); // 32 bytes
bytes32[] memory proof;
if (signature.length > 97) {
// Calculate how many proof elements we have
uint256 proofCount = (signature.length - 97) / 32;
// Create an array of the right size
proof = new bytes32[](proofCount);
// Extract each proof element
for (uint256 i; i < proofCount; ++i) {
uint256 startPos = 97 + (i * 32);
proof[i] = bytes32(signature[startPos:startPos + 32]);
}
} else {
// Empty proof
proof = new bytes32[](0);
}
if (!MerkleProofLib.verify(proof, root, _buildResourceLockHash(rl))) {
revert RLV_ResourceLockHashNotInProof();
}
// check proof is signed
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Resource-lock specific verification ignores chain, wallet, and time window.

rl is parsed but never validated:

require(rl.smartWallet == msg.sender, "RLV: SCW mismatch");
require(rl.chainId == block.chainid, "RLV: wrong chain");
require(rl.validAfter <= block.timestamp && block.timestamp <= rl.validUntil,
        "RLV: lock expired / not yet valid");

Without these, an attacker can replay a lock on another chain, another wallet, or outside the allowed window.

Comment on lines +147 to 150
if (_sessionKey == sessionKey.pub) {
// Additional checks based on _callData content could be added here
assertTrue(isValid || !isValid);
} else {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ineffective assertion renders the fuzz test meaningless

assertTrue(isValid || !isValid); is a tautology that can never fail, so the branch where _sessionKey == sessionKey.pub provides no verification value.
Either assert the expected relation between _callData and isValid (preferred) or drop the branch entirely.

-            // Additional checks based on _callData content could be added here
-            assertTrue(isValid || !isValid);
+            // Expect the validator result to be `true` for the wallet’s own session key
+            assertTrue(isValid, "validateSessionKeyParams should return true for the active session key");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (_sessionKey == sessionKey.pub) {
// Additional checks based on _callData content could be added here
assertTrue(isValid || !isValid);
} else {
if (_sessionKey == sessionKey.pub) {
// Expect the validator result to be `true` for the active session key
assertTrue(isValid, "validateSessionKeyParams should return true for the active session key");
} else {

Comment on lines +41 to 47
modifier withRequiredModules() {
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(moecdsav), hex"");
_installHookViaMultiplexer(scw, address(cam), HookType.GLOBAL);
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
vm.startPrank(address(scw));
_;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

withRequiredModules never stops its prank – risk of leaking msg.sender across tests

The modifier starts a prank but relies on every test to remember vm.stopPrank(). This is error-prone and already duplicated in every call-site.

-        _installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
-        vm.startPrank(address(scw));
-        _;
+        _installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
+        vm.startPrank(address(scw));
+        _;
+        vm.stopPrank(); // guarantees cleanup even when a test reverts early
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier withRequiredModules() {
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(moecdsav), hex"");
_installHookViaMultiplexer(scw, address(cam), HookType.GLOBAL);
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
vm.startPrank(address(scw));
_;
}
modifier withRequiredModules() {
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(moecdsav), hex"");
_installHookViaMultiplexer(scw, address(cam), HookType.GLOBAL);
_installModule(eoa.pub, scw, MODULE_TYPE_VALIDATOR, address(cam), abi.encode(MODULE_TYPE_VALIDATOR));
vm.startPrank(address(scw));
_;
vm.stopPrank(); // guarantees cleanup even when a test reverts early
}

- Added batch `UserOperation` support.
- Added tests for batch with direct Merkle signature and Eth signed Merkle signature.
- Added utility function for batch `UserOperation` in `ResourceLockValidatorTestUtils`.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (5)
test/ModularTestBase.sol (4)

280-285: makeBootstrapConfig is still undefined – compilation will fail

Previous review already highlighted that only _makeBootstrapConfig exists in BootstrapUtil.
Replace the calls or add a wrapper to avoid a hard compiler error.

- BootstrapConfig[] memory executors = makeBootstrapConfig(address(mockExec), "");
+ BootstrapConfig[] memory executors = new BootstrapConfig[](1);
+ executors[0] = _makeBootstrapConfig(address(mockExec), "");

- BootstrapConfig[] memory fallbacks = makeBootstrapConfig(address(0), "");
+ BootstrapConfig[] memory fallbacks = new BootstrapConfig[](1);
+ fallbacks[0] = _makeBootstrapConfig(address(0), "");

304-316: Early-return path leaks vm.prank context

In the fallback-module branch the function returns before calling vm.stopPrank(), so the prank
remains active for the rest of the test – exactly the issue flagged in the previous review.

if (_moduleType == MODULE_TYPE_FALLBACK) {
     bytes4 selector = bytes4(bytes32(_initData));
-    return scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
+    bool installed = scw.isModuleInstalled(_moduleType, _module, abi.encode(selector));
+    vm.stopPrank();
+    return installed;
}

328-351: Same prank-leak & unreachable code in _uninstallModule

vm.stopPrank() is never hit (there is a return above it) and the fallback branch again omits the stop call entirely.
Apply the same fix as for _installModule to every early-return.


140-143: ⚠️ Potential issue

Factory is deployed with owner = address(0) – this breaks all owner-checks downstream

eoa is instantiated after these lines, so its default value is the zero address when passed to ModularEtherspotWalletFactory.
Every wallet produced by the factory will therefore be owned by address(0), masking bugs and allowing anyone to control the wallets.

- factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
+ // Initialise the EOA first, then pass the correct owner
+ eoa = _createUser("EOA");
+ factory = new ModularEtherspotWalletFactory(address(impl), eoa.pub);
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1)

70-80: tokenData is double-encoded – identical logical arrays can hash differently

The outer abi.encode embeds the full in-memory layout of the dynamic array, making the hash
depend on memory representation instead of pure content. Use an inner keccak256 as previously suggested.

-                abi.encode(_lock.tokenData)
+                keccak256(abi.encode(_lock.tokenData))
🧹 Nitpick comments (3)
test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1)

55-66: Shadowed / unused named return variable hurts readability

You declare userOp as a named return variable but build a local op and return that instead.
Either reuse userOp or drop the name to avoid confusion.

- PackedUserOperation memory op = _createUserOp(_scw, address(rlv));
+ userOp = _createUserOp(_scw, address(rlv));
   rl = _generateResourceLock(_scw, _user.pub);
   ...
- return (op, rl, proof, root);
+ return (userOp, rl, proof, root);
test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (2)

226-233: Docstring says “eth-signed” but code uses raw _sign – keep comments and code in sync

The test is still valid (it is meant to fail on wrong proof), yet the mismatch may confuse future readers.

- // Sign merkle root with eth prefix
- bytes memory sig = _sign(merkleRoot, eoa);
+ // Direct signature (not eth-signed), the proof itself is intentionally invalid
+ bytes memory sig = _sign(merkleRoot, eoa);

582-590: Dead-code helper left behind – consider deleting or gating with forge test --match

vm.skip(true); already disables the body, but the placeholder test bloats the suite.
If it is only for ad-hoc debugging, move it to a separate scratch file or guard it behind
if (false) so it never ships to CI.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44d16e0 and 27e75e5.

📒 Files selected for processing (4)
  • src/modules/validators/ResourceLockValidator.sol (1 hunks)
  • test/ModularTestBase.sol (1 hunks)
  • test/modules/ResourceLockValidator/concrete/ResourceLockValidator.t.sol (1 hunks)
  • test/modules/ResourceLockValidator/utils/ResourceLockValidatorTestUtils.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/modules/validators/ResourceLockValidator.sol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants