Skip to content

16 distributor upgrades handler #53

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 7 commits into
base: dev
Choose a base branch
from

Conversation

peersky
Copy link
Member

@peersky peersky commented Apr 26, 2025

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive documentation covering Distributions, Distributors, Indexer, Repositories, Upgradability, and Versioning concepts, including CLI usage guides and examples.
    • Added new mock contracts and interfaces to support advanced distribution, migration, and versioning workflows.
    • Implemented new abstract and concrete contracts for modular app distribution, version management, upgradeability, and repository handling.
    • Enhanced proxy contract functionality with immutable middleware hooks and secure admin access.
  • Improvements

    • Refined terminology throughout the system, replacing "instance" with "app" for clarity.
    • Expanded interfaces and contracts to support version migrations, distribution aliasing, and enhanced event tracking.
    • Updated spell checker configuration and upgraded several dependencies.
  • Bug Fixes

    • Improved error handling and event emission for distribution instantiation, migration failures, and permission checks.
  • Refactor

    • Removed deprecated abstract contracts and libraries, consolidating logic into updated modules.
    • Renamed and reorganized contracts and methods for consistency and maintainability.
  • Tests

    • Added and expanded test suites for distribution workflows, version management, upgradability, and repository operations, increasing coverage and reliability.
  • Chores

    • Added and updated configuration files for coverage analysis, deployment, and environment management.

peersky added 7 commits April 23, 2025 10:22
- Added `MockFailingDistribution`, `MockLowLevelDistribution`, `MockPanicDistribution`, `MockMigration`, `MockRepository`, and `MockLatestVersionDistribution` for testing purposes.
- Updated `LatestVersionDistribution` to allow instantiation with a virtual method.
- Enhanced `Distributor` and `OwnableDistributor` to support new mock distributions.
- Updated VSCode settings to include a new word for spell checking.
Copy link

changeset-bot bot commented Apr 26, 2025

⚠️ No Changeset found

Latest commit: acb00db

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@peersky
Copy link
Member Author

peersky commented Apr 26, 2025

@coderabbitai review

1 similar comment
@peersky
Copy link
Member Author

peersky commented Apr 26, 2025

@coderabbitai review

Copy link

coderabbitai bot commented Apr 26, 2025

✅ Actions performed

Review triggered.

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

Copy link

coderabbitai bot commented Apr 26, 2025

Walkthrough

This update introduces extensive changes across the codebase, focusing on modularizing and refactoring the distribution, installation, and versioning systems for smart contracts. Major abstract contracts and libraries related to installers and distributors were removed and replaced by new, more robust implementations. The new architecture supports advanced features such as version migrations, distribution disabling, and enhanced upgradability workflows. Several new mock contracts and interfaces were added to facilitate testing and extend functionality. The update also introduces new documentation files detailing system concepts and CLI usage. Test suites were expanded and refactored to align with the new architecture and terminology, ensuring comprehensive coverage of versioning, distribution, and upgradability workflows.

Changes

File(s) / Path(s) Change Summary
.cursorignore, .solcover.js, .vscode/settings.json, package.json, hardhat.config.ts Added/updated configuration files for environment, coverage, spell checking, and dependency versions.
docs/Distributions.md, docs/Distributors.md, docs/Indexer.md, docs/Repositories.md, docs/Upgradability.md, docs/Versioning.md Added new documentation files covering distributions, distributors, indexer, repositories, upgradability, and versioning concepts and CLI usage.
docs/contracts/index.md Updated IInstaller interface documentation to rename functions from "instance" to "app".
src/abstracts/CodeIndexer.sol, src/abstracts/Distributor.sol, src/abstracts/Installer.sol, src/abstracts/InstallerClonable.sol, src/libraries/LibInstaller.sol Deleted legacy abstract contracts and library for code indexing, distributors, installers, and installer storage.
src/distributions/CloneDistribution.sol Removed inheritance from CodeIndexer, now only inherits from IDistribution.
src/distributions/CodeHashDistribution.sol, src/distributions/LatestVersionDistribution.sol Updated imports, now use LibERC7744 for code hash lookups; simplified source address resolution.
src/distributions/UpgradableDistribution.sol Added new abstract contract for upgradable distributions, supporting proxy instantiation.
src/distributions/VersionDistribution.sol Added new contract for immutable, versioned distributions with metadata and user calldata control.
src/distributors/Distributor.sol Added new abstract distributor contract, supporting versioned/unversioned distributions, migrations, and comprehensive management.
src/distributors/OwnableDistributor.sol Updated to support migration management, readable names, and distribution disabling; added new migration-related functions.
src/distributors/TokenizedDistributor.sol Updated to use codeHash as mapping key, added readable name parameter, and migration management.
src/erc7744/ERC7744.sol Added isEIP7702 function; updated code validation logic to use code prefix instead of code hash.
src/erc7744/LibERC7744.sol Added new library for interacting with ERC7744 code index, including address lookup and registration.
src/interfaces/IAdminGetter.sol, src/interfaces/IMigration.sol Added new interfaces for proxy admin retrieval and migration execution.
src/interfaces/IDistributor.sol, src/interfaces/IInstaller.sol, src/interfaces/IRepository.sol Major refactor: added/renamed functions, events, errors; introduced migration and aliasing support; replaced "instance" with "app".
src/middleware/AuthorizationMiddleware.sol, src/middleware/ERC7746Hooked.sol, src/middleware/MiddlewareProxy.sol Renamed contracts and updated inheritance/imports for middleware components.
src/middleware/InstallerClonable.sol Added new abstract, upgradeable installer contract supporting app installation, upgrades, and distributor management.
src/middleware/LibMiddleware.sol Added changeLayers function for updating middleware layer arrays.
src/mocks/MockCloneDistribution.sol, src/mocks/MockERC20.sol, src/mocks/MockFailingDistribution.sol, src/mocks/MockInstaller.sol, src/mocks/MockLatestVersionDistribution.sol, src/mocks/MockLowLevelDistribution.sol, src/mocks/MockMigration.sol, src/mocks/MockPanicDistribution.sol, src/mocks/MockRepository.sol, src/mocks/MockTokenizedDistributor.sol Added/updated mock contracts for distributions, installers, repositories, and migrations to facilitate testing and demonstrate new features.
src/proxies/WrappedTransparentUpgradeableProxy.sol Added new proxy contract combining transparent upgradeability with middleware hooks and secure admin retrieval.
src/repositories/OwnableRepository.sol, src/repositories/Repository.sol Updated to support migration scripts per major version, new release signature, and migration script management.
test/eds/CloneDistribution.ts, test/eds/Distributor.ts, test/eds/Installer.ts, test/eds/LatestVersionDistribution.ts, test/eds/Repository.ts, test/eds/TokenizedDistributor.ts, test/eds/UpgradabilityWorkflows.ts, test/eds/VersionDistributor.ts, test/eds/VersionWorkflows.ts Refactored and expanded test suites to cover new architecture, updated terminology, migration/version workflows, and error handling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Installer
    participant Distributor
    participant Repository
    participant MigrationContract

    User->>Installer: install(distributor, distributionId, args)
    Installer->>Distributor: instantiate(distributionId, args)
    Distributor->>Repository: get(versionRequirement)
    Repository-->>Distributor: Source (address, version, metadata)
    Distributor-->>Installer: appComponents, name, version
    Installer-->>User: appId

    Note over User,Installer: App is installed and tracked

    User->>Installer: upgradeApp(appId, migrationId, userCalldata)
    Installer->>Distributor: upgradeUserInstance(appId, migrationId, userCalldata)
    Distributor->>MigrationContract: migrate(appComponents, oldVersion, newVersion, repository, distributorCalldata, userCalldata)
    MigrationContract-->>Distributor: (migration executed)
    Distributor-->>Installer: newVersion
    Installer-->>User: upgrade complete
Loading

Poem

(\(\
( -.-)
o_(")(")

A hop, a skip, a code refactor,
Distributions leap, migrations scatter!
Installers now are upgradable,
With apps and versions traceable.
Tests abound and docs anew—
This warren’s grown, with much to do!
The future’s bright for every bun,
With modular contracts—run, rabbit, run!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.

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: 32

🔭 Outside diff range comments (4)
src/middleware/AuthorizationMiddleware.sol (4)

21-21: 🛠️ Refactor suggestion

Storage position identifier still references old contract name.

Update the storage position constant to reflect the contract's new name.

- bytes32 private constant SACM_STORAGE_POSITION = keccak256("simple.access.manager.storage.position");
+ bytes32 private constant AUTH_MIDDLEWARE_STORAGE_POSITION = keccak256("authorization.middleware.storage.position");

Also update all references to this constant in the getStorage() function.


23-28: 🛠️ Refactor suggestion

Storage function references outdated constant name.

Update the getStorage() function to use the renamed storage position constant.

function getStorage() private pure returns (Storage storage s) {
-   bytes32 position = SACM_STORAGE_POSITION;
+   bytes32 position = AUTH_MIDDLEWARE_STORAGE_POSITION;
    assembly {
        s.slot := position
    }
}

30-34: 🛠️ Refactor suggestion

Struct name not updated to match renamed contract.

Update the struct name to maintain naming consistency with the renamed contract.

- struct SimpleAccessManagerInitializer {
+ struct AuthorizationMiddlewareInitializer {
    bytes4 selector;
    address[] disallowedAddresses;
    bool distributionComponentsOnly;
}

Don't forget to also update any references to this struct in the code, such as in the initialize function parameter.


40-44: 🛠️ Refactor suggestion

Update function parameter to use renamed struct.

The initialize function should use the renamed struct.

function initialize(
-   SimpleAccessManagerInitializer[] memory methodSettings,
+   AuthorizationMiddlewareInitializer[] memory methodSettings,
    address target,
    IDistributor distributor
) public initializer {
🧹 Nitpick comments (54)
.vscode/settings.json (1)

2-2: Validate dictionary entries: potential typos
The entry "continous" appears to be a misspelling of “continuous”. If this is intentional (e.g., a domain-specific term), it’s fine to keep; otherwise please correct it to avoid confusion. The other entries (extcodecopy, mstore, retval, strg) look like EVM opcodes or shorthand and are appropriate.

.cursorignore (1)

1-2: Also update version control ignore rules
To avoid accidentally committing sensitive files to Git, consider mirroring these entries in your .gitignore (if not already present).

package.json (1)

67-67: Dependency version upgrade for solidity-coverage.

The solidity-coverage package has been updated from ^0.8.5 to ^0.8.14. This upgrade provides bug fixes and performance improvements for code coverage analysis.

Consider documenting the specific benefits this version brings to your project in the PR description or commit message.

src/middleware/ERC7746Hooked.sol (1)

6-13: Documentation references outdated contract name.

The documentation comments still reference "ERC7746Middleware" instead of the new contract name "ERC7746Hooked".

- * @title ERC7746Middleware
+ * @title ERC7746Hooked
src/middleware/LibMiddleware.sol (1)

31-41: Good addition of dynamic layer management functionality

The new changeLayers function allows for in-place updates to middleware layers, which is a useful capability for dynamic reconfiguration. The implementation correctly handles both growing and shrinking the array.

One optimization suggestion: consider adding a check to avoid unnecessary operations when the new array length equals the existing array length.

 function changeLayers(LayerStruct[] memory newLayers) internal {
     LayerStruct[] storage ls = accessLayersStorage();
+    // Skip resizing if lengths are equal (minor gas optimization)
+    if (newLayers.length == ls.length) {
+        for (uint256 i; i < newLayers.length; ++i) {
+            ls[i] = newLayers[i];
+        }
+        return;
+    }
     for (uint256 i; i < newLayers.length; ++i) {
         ls[i] = newLayers[i];
     }
     if (newLayers.length < ls.length) {
         for (uint256 i; i < ls.length - newLayers.length; ++i) {
             ls.pop();
         }
     }
 }
hardhat.config.ts (1)

134-149: Redundant commented-out compiler overrides.

The commented-out compiler overrides for src/erc7744/ERC7744.sol use identical settings to the main compiler configuration. This doesn't add any value and should either be removed or modified to have different settings if specific customization is needed.

-    // overrides: {
-    //   "src/erc7744/ERC7744.sol": {
-    //     version: "0.8.28",
-    //     settings: {
-    //       evmVersion: "cancun",
-    //       viaIR: true,
-    //       optimizer: {
-    //         enabled: true,
-    //         runs: 2000
-    //       },
-    //       metadata: {
-    //         bytecodeHash: "none"
-    //       }
-    //     }
-    //   }
-    // }
docs/Versioning.md (2)

3-3: Grammar correction needed.

Missing article "a" before "Semantic versioning system".

-EDS implements Semantic versioning system within one bytes32 word.
+EDS implements a Semantic versioning system within one bytes32 word.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~3-~3: You might be missing the article “a” here.
Context: # Versioning EDS implements Semantic versioning system within one bytes32 wo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


5-5: Add comma for clarity.

A comma is needed after "On chain" for better readability.

-On chain semantic versioning is managed by [LibSemver](../src/versioning/LibSemver.sol).
+On-chain, semantic versioning is managed by [LibSemver](../src/versioning/LibSemver.sol).
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: Possible missing comma found.
Context: ...ing system within one bytes32 word. On chain semantic versioning is managed by [LibS...

(AI_HYDRA_LEO_MISSING_COMMA)

src/erc7744/ERC7744.sol (1)

14-21: Well-implemented EIP7702 detection with assembly.

The new isEIP7702 function effectively detects contracts following EIP7702 by checking the prefix bytes, which is a more direct and efficient approach than comparing code hashes.

Consider adding a brief comment explaining the meaning of the 0xef0100 magic bytes for future maintainers:

    function isEIP7702(address account) public view returns (bool) {
        bytes3 prefix;
        assembly {
            extcodecopy(account, 0, mload(0x40), 3) // Copy first 3 bytes to memory
            prefix := mload(0x40) // Load the 3 bytes from memory
        }
+       // The 0xef0100 prefix is defined in EIP7702 for identifying delegated EOAs
        return prefix == bytes3(0xef0100);
    }
test/eds/LatestVersionDistribution.ts (3)

18-18: Consider using a more specific type instead of 'any'.

Using any type, even with a comment explaining why, bypasses TypeScript's type checking benefits. Consider creating a more specific interface that includes the sources() method.

// Create an interface that extends the generated type but includes the sources method
interface LatestVersionDistributionWithSources extends LatestVersionDistribution {
  sources(): Promise<[string[], string, number]>;
}

// Then use this type instead of any
let latestVersionDist: LatestVersionDistributionWithSources;

93-98: Implement the skipped instantiate test.

The test for the instantiate method is currently skipped with a comment indicating it requires a fully mocked repository. Consider implementing this test to ensure complete coverage of the contract's functionality.

describe("instantiate", function () {
  it("should instantiate using the latest version from repository", async function () {
    // Mock the repository's getLatestVersion method if needed
    // Or ensure the repository fixture is properly set up for this test
    
    const instantiateResult = await latestVersionDist.instantiate("0x");
    
    // Verify the instantiation result matches expectations
    // e.g., check returned addresses, version, etc.
    expect(instantiateResult).to.not.be.undefined;
    // Additional assertions based on expected behavior
  });
  
  it("should revert when the repository has no versions", async function () {
    // Setup a repository with no versions
    const emptyRepo = await (await ethers.getContractFactory("MockRepository")).deploy();
    
    // Deploy distribution with empty repo
    const emptyRepoDist = await (await ethers.getContractFactory("MockLatestVersionDistribution")).deploy(
      emptyRepo.address,
      metadata
    );
    
    // Expect revert when trying to instantiate
    await expect(emptyRepoDist.instantiate("0x")).to.be.revertedWith(
      // Expected revert message
      "No versions available"
    );
  });
});

1-99: Consider adding tests for repository version changes.

The current tests verify the initial setup but don't test how the LatestVersionDistribution handles changes in the repository, such as when a new latest version is added.

describe("Repository version changes", function () {
  it("should use the updated latest version after repository update", async function () {
    // Initial state - already tested in previous tests
    
    // Add a new version to the repository
    const version2 = { major: 1, minor: 1, patch: 0 };
    const sourceId2 = ethers.utils.keccak256(
      ethers.utils.defaultAbiCoder.encode(["address"], [mockDistribution.address])
    );
    
    // Use different metadata for version 2
    const metadataBytes2 = ethers.utils.toUtf8Bytes("metadata-v2");
    
    // Add the new release
    await repository.newRelease(
      sourceId2,
      metadataBytes2,
      version2,
      ethers.utils.formatBytes32String("0x")
    );
    
    // Instantiate again and verify it uses the new version
    const instantiateResult = await latestVersionDist.instantiate("0x");
    
    // Verify the result shows the new version was used
    // This depends on what instantiate returns and how to verify the version
  });
});
docs/Distributors.md (2)

3-3: Minor grammatical correction needed

There's a grammatical error in this sentence.

-Distributors are contracts that are used to provide trusted sources to end-users from a entity owned domain.
+Distributors are contracts that are used to provide trusted sources to end-users from an entity owned domain.
🧰 Tools
🪛 LanguageTool

[misspelling] ~3-~3: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...ovide trusted sources to end-users from a entity owned domain. Distributors may ...

(EN_A_VS_AN)


15-15: Consider adding an article

The sentence would read more naturally with "the" before "Distributor".

-Distributions that use [ERC7746](./ERC7746.md) middleware approach, or that are installed via [Installer](./Installer.md) can be runtime verified by Distributor.
+Distributions that use [ERC7746](./ERC7746.md) middleware approach, or that are installed via [Installer](./Installer.md) can be runtime verified by the Distributor.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~15-~15: Possible missing article found.
Context: ...nstaller.md) can be runtime verified by Distributor. Distributor gets called with `beforeC...

(AI_HYDRA_LEO_MISSING_THE)

test/eds/Installer.ts (1)

49-51: Flatten the overloaded-function access for readability.
Breaking the property access over three lines makes the intent harder to scan and clutters diffs. A single-line access keeps the test concise without losing clarity.

-  .connect(owner)
-  [
-    "addDistribution(bytes32,address,string)"
-  ](cloneDistributionId, ethers.constants.AddressZero, "testDistribution");
+  .connect(owner)["addDistribution(bytes32,address,string)"](
+    cloneDistributionId,
+    ethers.constants.AddressZero,
+    "testDistribution"
+  );
test/eds/VersionDistributor.ts (1)

97-106: Style nit – keep overloaded signature access on one line.

-  distributor
-    .connect(owner)
-    [
-      "addDistribution(address,address,((uint64,uint64,uint128),uint8),string)"
-    ](repository.address, ethers.constants.AddressZero, { version: { major: 0, minor: 0, patch: 1 }, requirement: 1 }, "test")
+  await distributor.connect(owner)[
+    "addDistribution(address,address,((uint64,uint64,uint128),uint8),string)"
+  ](
+    repository.address,
+    ethers.constants.AddressZero,
+    { version: { major: 0, minor: 0, patch: 1 }, requirement: 1 },
+    "test"
+  );

Makes the intent clearer and reduces noise in git history.

test/eds/Repository.ts (2)

71-73: Same style nit as above – collapse overloaded selector access.
Keeps the test leaner and avoids accidental whitespace diffs.


80-88: Variable naming: dummyMigrationAddress holds a contract instance.
Exposing it as …Address is misleading. Renaming to dummyMigration (or splitting into dummyMigration & dummyMigrationAddress) prevents confusion later in the suite.

-let dummyMigrationAddress: MockMigration;
+let dummyMigration: MockMigration;-    dummyMigrationAddress = await MockMigration.deploy();
+    dummyMigration = await MockMigration.deploy();-    await codeIndex.register(dummyMigrationAddress.address);
-    dummyMigrationCodeHash = ethers.utils.keccak256(
-      await dummyMigrationAddress.provider.getCode(dummyMigrationAddress.address)
+    await codeIndex.register(dummyMigration.address);
+    dummyMigrationCodeHash = ethers.utils.keccak256(
+      await dummyMigration.provider.getCode(dummyMigration.address)
     );
contracts/mocks/MockPanicDistribution.sol (1)

15-23: Use revert() instead of assert(false) for intentional failure

assert(false) triggers Solidity’s Panic (0x01), which is reserved for internal invariants and consumes all remaining gas.
For a mock that is expected to revert, a conventional revert("MockPanic") (or a custom error) is safer and clearer to downstream tooling.

-        // This will cause a panic
-        assert(false);
+        // Intentionally revert to simulate a panic-style failure
+        revert("MockPanic");

Would switching to revert() break any test that explicitly expects a panic (0x01)?

docs/contracts/index.md (2)

269-276: Parameter name still refers to instanceId

The codebase migrated from “instance” to “app”, yet the snippet documents
getApp(uint256 instanceId). Please rename the parameter to appId for consistency.

-function getApp(uint256 instanceId) public view returns (address[] instaneContracts)
+function getApp(uint256 appId) public view returns (address[] appContracts)

817-827: Update external view version of getApp/getAppsNum

The same “instance” wording appears in the external-view declarations directly below.
Mirror the correction there as well to avoid friction for SDK/ABI generators.

src/mocks/MockFailingDistribution.sol (1)

9-11: Consider using a more explicit failure mechanism.

The current keccak256 comparison method works, but could benefit from a clearer approach. Consider using a more direct check or defining a constant for the failure marker.

-        if (args.length > 0 && keccak256(args) == keccak256(abi.encode(bytes32("FAIL")))) {
+        bytes32 constant FAIL_MARKER = bytes32("FAIL");
+        if (args.length > 0 && keccak256(args) == keccak256(abi.encode(FAIL_MARKER))) {
contracts/mocks/MockMigration.sol (1)

1-43: Implementation looks good, but consider adding more documentation

This mock contract correctly implements the IMigration interface and uses ERC165 for interface detection. The implementation is straightforward and appropriate for testing purposes, emitting events with migration details without performing actual migrations.

Consider adding NatSpec documentation for the contract and functions to improve code readability and maintainability:

+/**
+ * @title MockMigration
+ * @dev A mock implementation of the IMigration interface for testing purposes.
+ * This contract only emits events and doesn't perform actual migrations.
+ */
 contract MockMigration is IMigration, ERC165 {
     event MigrationExecuted(
         address[] instances,
         uint256 oldVersion,
         uint256 newVersion,
         bytes distributorCalldata,
         bytes userCalldata
     );

+    /**
+     * @dev Mocks a migration by emitting an event with the migration details.
+     * @param instances The addresses of instances to migrate
+     * @param oldVersion The version being migrated from
+     * @param newVersion The version being migrated to
+     * @param repository The repository reference
+     * @param distributorCalldata Additional calldata for the distributor
+     * @param userCalldata Additional calldata for user contracts
+     */
     function migrate(
         address[] memory instances,
         LibSemver.Version memory oldVersion,
         LibSemver.Version memory newVersion,
         IRepository repository,
         bytes calldata distributorCalldata,
         bytes calldata userCalldata
     ) external override {
docs/Indexer.md (2)

3-5: Missing articles in documentation text

The text has a few minor grammatical issues with missing articles.

-EDS indexer is [ERC7744](https://eips.ethereum.org/EIPS/eip-7744) standard compliant codehash registry that allows immutable code referencing in permission-less manner.
+The EDS indexer is an [ERC7744](https://eips.ethereum.org/EIPS/eip-7744) standard compliant codehash registry that allows immutable code referencing in a permission-less manner.

-ERC7744 indexer is the most upstream in dependency chain within the EDS ecosystem and is the only contract that functionality may be trusted by ALL parties.
+The ERC7744 indexer is the most upstream in the dependency chain within the EDS ecosystem and is the only contract whose functionality may be trusted by ALL parties.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~3-~3: You might be missing the article “a” here.
Context: ...at allows immutable code referencing in permission-less manner. ERC7744 indexer is the mo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~5-~5: Possible missing article found.
Context: ...ERC7744 indexer is the most upstream in dependency chain within the EDS ecosystem and is t...

(AI_HYDRA_LEO_MISSING_THE)


58-59: Incorrect Markdown syntax for note

The note is using GitHub-style alerts syntax (> [!NOTE]) which might not render correctly in all Markdown processors.

Consider using more widely supported Markdown syntax:

-> [!NOTE]
-> The CLI provides utilities to interact with the ERC7744 indexer.
+> **Note:** The CLI provides utilities to interact with the ERC7744 indexer.
🧰 Tools
🪛 LanguageTool

[style] ~58-~58: Using many exclamation marks might seem excessive (in this case: 4 exclamation marks for a text that’s 1097 characters long)
Context: ...er.get(codeHash); ``` ## Using CLI > [!NOTE] > The CLI provides utilities to in...

(EN_EXCESSIVE_EXCLAMATION)

src/erc7744/LibERC7744.sol (3)

9-16: Incorrect @inheritdoc annotations

The @inheritdoc tags are used incorrectly as they don't specify which interface function they're inheriting documentation from.

-    // @inheritdoc ICodeIndex
+    /**
+     * @dev Returns the ERC7744 indexer contract interface
+     * @return ICodeIndex interface of the deployed indexer
+     */
     function getContractsIndex() internal pure returns (ICodeIndex) {
         return INDEX_CONTRACT;
     }
-    // @inheritdoc ICodeIndex
+    /**
+     * @dev Registers a contract address with the ERC7744 indexer
+     * @param source The contract address to register
+     */
     function index(address source) internal {
         INDEX_CONTRACT.register(source);
     }

22-28: Missing documentation for error and function

The error and the function getContainerOrThrow lack documentation.

+    /**
+     * @dev Error thrown when attempting to retrieve a non-existent container
+     * @param id The code hash that wasn't found in the index
+     */
     error AddressNotFound(bytes32 id);

+    /**
+     * @dev Retrieves a container address for a given code hash, reverting if not found
+     * @param id The code hash to look up
+     * @return The container address
+     * @revert AddressNotFound if the container doesn't exist
+     */
     function getContainerOrThrow(bytes32 id) internal view returns (address) {
         address container = getContainer(id);
         if (container == address(0)) revert AddressNotFound(id);
         return container;
     }

13-20: Improve error handling for index function

The index function forwards the call to the indexer but doesn't handle potential errors or failed registrations.

Consider adding error handling or providing a version that returns success status:

     function index(address source) internal {
         INDEX_CONTRACT.register(source);
     }
+    
+    /**
+     * @dev Safely registers a contract address and returns success status
+     * @param source The contract address to register
+     * @return success True if registration succeeded, false otherwise
+     */
+    function indexSafe(address source) internal returns (bool success) {
+        // Use low-level call to handle potential errors
+        (success, ) = address(INDEX_CONTRACT).call(
+            abi.encodeWithSelector(INDEX_CONTRACT.register.selector, source)
+        );
+        return success;
+    }

     function getContainer(bytes32 id) internal view returns (address) {
         return getContractsIndex().get(id);
     }
src/mocks/MockLatestVersionDistribution.sol (3)

19-21: Consider adding test value for the sources override.

This method simply calls the parent implementation without changing behavior. Consider adding a comment explaining why this override is necessary for testing, or potentially returning mock values instead.


23-25: Unused parameter in instantiate method.

The instantiate method accepts a bytes parameter but doesn't use it, instead calling super._instantiate() without parameters. If this is intentional for mocking purposes, consider adding a comment explaining why.

- function instantiate(bytes memory) external override returns (address[] memory instances, bytes32 name, uint256 version) {
+ function instantiate(bytes memory /* params */) external override returns (address[] memory instances, bytes32 name, uint256 version) {
    return super._instantiate();
}

28-38: Consider optimizing the bytes32ToString function.

The function uses two separate loops with the same condition. You could optimize this by storing the length from the first loop:

function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
    uint8 i = 0;
    while(i < 32 && _bytes32[i] != 0) {
        i++;
    }
    bytes memory bytesArray = new bytes(i);
-   for (i = 0; i < 32 && _bytes32[i] != 0; i++) {
+   for (uint8 j = 0; j < i; j++) {
-       bytesArray[i] = _bytes32[i];
+       bytesArray[j] = _bytes32[j];
    }
    return string(bytesArray);
}
contracts/mocks/MockFailingDistribution.sol (1)

36-38: Consider making the contractURI consistent with the distribution name.

The hardcoded IPFS URI doesn't reflect the distribution name. Consider using the name in the URI for better traceability.

function contractURI() external pure returns (string memory) {
-    return "ipfs://mockFailingDistribution";
+    return string(abi.encodePacked("ipfs://", ShortStrings.toString(distributionName)));
}
docs/Upgradability.md (5)

1-9: Good introduction to the upgradeability concept.

The introduction clearly explains the motivation behind the EDS upgradeability pattern. Consider addressing some grammatical issues to improve readability.

# Upgrading contracts with EDS

## Introduction

- EDS implements next level abstraction pattern over traditional smart contract upgradeability. It allows to upgrade contracts in a safe and easy way, which is fully owned by the end users, without sacrificing any distributor trust to app nor installer.
+ EDS implements a next-level abstraction pattern over traditional smart contract upgradeability. It allows users to upgrade contracts in a safe and easy way, which is fully owned by the end users, without sacrificing any distributor trust to app nor installer.

## Trust assumptions
- Upgradability that is both trusted by end-user and distributor requires careful design. Hence this section describes the trust assumptions that are made by EDS.
+ Upgradability that is both trusted by end-user and distributor requires careful design. Hence, this section describes the trust assumptions that are made by EDS.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...th EDS ## Introduction EDS implements next level abstraction pattern over traditional sm...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~5-~5: Did you mean “upgrading”? Or maybe you should add a pronoun? In active voice, ‘allow’ + ‘to’ takes an object, usually a pronoun.
Context: ...mart contract upgradeability. It allows to upgrade contracts in a safe and easy way, which...

(ALLOW_TO)


[uncategorized] ~8-~8: A comma may be missing after the conjunctive/linking adverb ‘Hence’.
Context: ...nd distributor requires careful design. Hence this section describes the trust assump...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)


10-29: Improve clarity in the trust assumptions section.

The trust domains and relationships are well-defined, but there are several grammar and clarity issues. Consider correcting these for better readability.

- In EDS it is assumed that downstream contracts are trusted by upstream contracts by explicitly referring to them. Upstream dependencies are unaware of downstream contracts and do not trust them, however may expose public interfaces that are used by downstream contracts.
+ In EDS, it is assumed that downstream contracts are trusted by upstream contracts through explicit references. Upstream dependencies are unaware of downstream contracts and do not trust them; however, they may expose public interfaces that are used by downstream contracts.

**1. Ethereum (blockchain consensus, permission-less) domain:**
- [ERC7744 Indexer](./Indexer.md) is the most upstream in dependency chain within the EDS ecosystem and is the only contract that functionality may be trusted by ALL parties.
- In order for [Distributions](./Distributions.md) may be part of same trust domain they **MUST** use [ERC7744 Indexer](./Indexer.md) to refer code.
+ - [ERC7744 Indexer](./Indexer.md) is the most upstream in the dependency chain within the EDS ecosystem and is the only contract whose functionality may be trusted by ALL parties.
+ - In order for [Distributions](./Distributions.md) to be part of the same trust domain, they **MUST** use [ERC7744 Indexer](./Indexer.md) to refer to code.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~10-~10: Possible missing comma found.
Context: ...t assumptions that are made by EDS. In EDS it is assumed that downstream contracts...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~13-~13: Possible missing article found.
Context: ...](./Indexer.md) is the most upstream in dependency chain within the EDS ecosystem and is t...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~14-~14: Possible missing article found.
Context: ...ons](./Distributions.md) may be part of same trust domain they MUST use [ERC7744...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~14-~14: Possible missing comma found.
Context: ...ributions.md) may be part of same trust domain they MUST use [ERC7744 Indexer](./I...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~18-~18: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...744 Indexer](./Indexer.md) to refer code and they trust any code they deploy or list...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~22-~22: Possible missing comma found.
Context: ...versions](./Versions.md) he supports by users and may impose specific business or pro...

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~24-~24: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...Indexer](./Indexer.md) to refer code. - Distributors may allow cross-instance calls between ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[uncategorized] ~24-~24: Possible missing comma found.
Context: ...s between apps that they consider to be trusted and enforce these rules via runtime val...

(AI_HYDRA_LEO_MISSING_COMMA)


42-49: Improve clarity in the upgradable distributions section.

The requirements and trust model for upgradable distributions are well-explained, but some grammatical issues impact readability.

## Upgradable distributions
- Since double trust assumptions, regular proxies owned by single party are not allowed. Developers **MUST** deploy upgradeable distributions for their app, example of such is [WrappedTransparentUpgradeableProxy](../src/proxies/WrappedTransparentUpgradeableProxy.sol).
+ Due to dual trust assumptions, regular proxies owned by a single party are not allowed. Developers **MUST** deploy upgradeable distributions for their app; an example of such is [WrappedTransparentUpgradeableProxy](../src/proxies/WrappedTransparentUpgradeableProxy.sol).

- Such contracts create upgradable instances of app, which are **owned** by distributor but **access permissions** are managed by Installer. Attempts to call upgradability selectors **MUST** result in ERC7746 hook call to Installer.
+ Such contracts create upgradable instances of the app, which are **owned** by the distributor but **access permissions** are managed by the Installer. Attempts to call upgradability selectors **MUST** result in an ERC7746 hook call to the Installer.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~43-~43: You might be missing the article “a” here.
Context: ...t assumptions, regular proxies owned by single party are not allowed. Developers **MUS...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~43-~43: Do not mix variants of the same word (‘upgradeable’ and ‘upgradable’) within a single text.
Context: ...not allowed. Developers MUST deploy upgradeable distributions for their app, example of...

(EN_WORD_COHERENCY)


[uncategorized] ~45-~45: Possible missing article found.
Context: ...ontracts create upgradable instances of app, which are owned by distributor but...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~45-~45: You might be missing the article “the” here.
Context: ...nstances of app, which are owned by distributor but access permissions are managed ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~45-~45: You might be missing the article “the” here.
Context: ...t access permissions are managed by Installer. Attempts to call upgradability selecto...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~47-~47: Did you mean “upgrading”? Or maybe you should add a pronoun? In active voice, ‘allow’ + ‘to’ takes an object, usually a pronoun.
Context: ...all to Installer. This mechanic allows to upgrade app without requiring any changes to di...

(ALLOW_TO)


[uncategorized] ~47-~47: Possible missing article found.
Context: ...aller. This mechanic allows to upgrade app without requiring any changes to distri...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~47-~47: Possible missing article found.
Context: ...de app without requiring any changes to distributor, while still allowing to enforce busine...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ...o enforce business logic on upgrade. If user wants to renounce distributor, he can d...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ... do so, but will not be able to upgrade app anymore, unless distributor transfers o...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ... be able to upgrade app anymore, unless distributor transfers ownership to user or new dist...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ...less distributor transfers ownership to user or new distributor. ## Upgrading Pro...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “a” here.
Context: ...tributor transfers ownership to user or new distributor. ## Upgrading Process of...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


51-64: Well-structured upgrade process explanation.

The step-by-step upgrade process is clearly outlined and easy to follow. Consider adding line breaks or using numbered lists to improve readability.

## Upgrading

Process of normal upgrade routine is as follows:

- 1. Distributor lists new upgrade version for a Repository with a specific version.
- 2. Users calls Installer initialize upgrade via `upgradeApp` function.
- 3. Installer sets flag that app is in upgrade mode and will call Distributor with `upgradeUserInstance` hook.
- 4. Distributor validates that upgrade is allowed and that version is listed.
- 5. Distributor loads migration plan from his storage and executes migration logic.
- 6. Migration logic one way or another must call upgrade on the app instance, as example in [WrappedTransparentUpgradeableProxy](../src/proxies/WrappedTransparentUpgradeableProxy.sol) it is done by `upgradeToAndCall` function by wrapping standard OpenZeppelin upgrade function.
- 7. App instance contact **MUST** call back to Installer via `beforeCall` hook.
- 8. Installer validates that call was allowed by Distributor.
- 9.  Installer sets flag that app is no longer in upgrade mode.
+ 1. The Distributor lists a new upgrade version for a Repository with a specific version.
+ 2. The User calls the Installer to initialize upgrade via the `upgradeApp` function.
+ 3. The Installer sets a flag that the app is in upgrade mode and will call the Distributor with the `upgradeUserInstance` hook.
+ 4. The Distributor validates that the upgrade is allowed and that the version is listed.
+ 5. The Distributor loads the migration plan from its storage and executes migration logic.
+ 6. Migration logic must call upgrade on the app instance; for example, in [WrappedTransparentUpgradeableProxy](../src/proxies/WrappedTransparentUpgradeableProxy.sol) it is done by the `upgradeToAndCall` function by wrapping the standard OpenZeppelin upgrade function.
+ 7. The App instance contract **MUST** call back to the Installer via the `beforeCall` hook.
+ 8. The Installer validates that the call was allowed by the Distributor.
+ 9. The Installer sets a flag that the app is no longer in upgrade mode.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~52-~52: A determiner appears to be missing. Consider inserting it.
Context: ...ser or new distributor. ## Upgrading Process of normal upgrade routine is as follows...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~55-~55: Possible missing article found.
Context: ...rmal upgrade routine is as follows: 1. Distributor lists new upgrade version for a Reposit...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~55-~55: Possible missing article found.
Context: ...ne is as follows: 1. Distributor lists new upgrade version for a Repository with a...

(AI_HYDRA_LEO_MISSING_A)


[uncategorized] ~58-~58: A determiner appears to be missing. Consider inserting it.
Context: ...tor with upgradeUserInstance hook. 4. Distributor validates that upgrade is allowed and t...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~59-~59: Possible missing article found.
Context: ...version is listed. 5. Distributor loads migration plan from his storage and executes migr...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~60-~60: You might be missing the article “the” here.
Context: ...upgradeToAndCall function by wrapping standard OpenZeppelin upgrade function. 7. App i...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ... to Installer via beforeCall hook. 8. Installer validates that call was allowed by Dist...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ...Call` hook. 8. Installer validates that call was allowed by Distributor. 9. Install...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ...ller validates that call was allowed by Distributor. 9. Installer sets flag that app is no...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...at call was allowed by Distributor. 9. Installer sets flag that app is no longer in upgr...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: You might be missing the article “a” here.
Context: ...owed by Distributor. 9. Installer sets flag that app is no longer in upgrade mode. ...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...stributor. 9. Installer sets flag that app is no longer in upgrade mode. ## Migra...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


65-103: Good explanation of migration plans and strategies.

The migration plan structure and strategies are well-documented, helping developers understand the different migration approaches available.

Add language specification to the code block for proper syntax highlighting:

- ```
+ ```solidity
struct MigrationPlan {
    LibSemver.VersionRequirement from;
    LibSemver.VersionRequirement to;
    IMigration migrationContract;
    MigrationStrategy strategy;
}
  • Eg. if migrating from v0.0.1 to v3.0.0 Distributor will execute migrations for v1.0.0, v2.0.0 and v3.0.0.
  • E.g., if migrating from v0.0.1 to v3.0.0, the Distributor will execute migrations for v1.0.0, v2.0.0, and v3.0.0.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[uncategorized] ~75-~75: A determiner appears to be missing. Consider inserting it.
Context: ...;     MigrationStrategy strategy; } ``` Migration plan is stored in Distributor storage a...

(AI_EN_LECTOR_MISSING_DETERMINER)

---

[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...ller and will be managed by Repository. Distributor will not execute any migration logic, b...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

---

[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...y migration logic, but will only signal Installer to execute migration.  Distributor cont...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

---

[uncategorized] ~94-~94: Possible missing comma found.
Context: ...itory specified migration script one by one executing migrations for any major vers...

(AI_HYDRA_LEO_MISSING_COMMA)

---

[uncategorized] ~94-~94: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...lease between `from` and `to` versions. Eg. if migrating from v0.0.1 to v3.0.0 Dist...

(E_G)

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

90-90: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

</details>

</details>

</blockquote></details>
<details>
<summary>src/distributions/UpgradableDistribution.sol (2)</summary><blockquote>

`23-26`: **Minor gas-saving loop micro-optimisation**

The loop can be made cheaper by:

1. Caching `srcsLength` (already done ✅).
2. Using an `unchecked` block for the increment.

```diff
-        for (uint256 i; i < srcsLength; ++i) {
+        for (uint256 i; i < srcsLength; ) {
             address proxy = address(new WrappedTransparentUpgradeableProxy(_sources[i], installer, data, ""));
             instances[i] = proxy;
-            ++i;
+            unchecked { ++i; }
         }

This shaves ~30-40 gas for longer source arrays while preserving correctness.


8-12: Unused artefacts — dead code / imports

Clones and LibERC7744 are imported, and CodeNotFoundInIndex is declared, but none are referenced in this contract.
If they are kept only for anticipated future use, add a short comment; otherwise remove them to keep byte-code size down.

test/eds/Distributor.ts (2)

24-39: Duplicate helper definition – move to shared test utils

createVersionRequirement is duplicated in several test suites. Extract it once into test/utils/version.ts (or similar) and import where needed to follow DRY.


168-172: Left-over console output

console.log(instanceAddress); sneaked into committed tests. This clutters CI output and slightly slows runs – please remove.

-        console.log(instanceAddress);
src/repositories/Repository.sol (3)

64-72: Logic is correct but error strings could mislead

"Migration is not allowed for minor/patch releases" is thrown even when migrationHash == bytes32(0) but contract does not support IMigration.
Consider differentiating the two error causes (Non-zero migrationHash vs Unsupported interface) for faster debugging.


198-203: require(migrationHash.getContainerOrThrow() != address(0)) is unreachable

getContainerOrThrow() reverts on failure, so the require condition is evaluated only when the call already succeeded and returns a non-zero address.
Remove the redundant require or refactor as:

-        require(migrationHash.getContainerOrThrow() != address(0), "Invalid migration");
+        // Will revert internally if hash is unknown
+        migrationHash.getContainerOrThrow();

91-149: resolveVersion is growing complex – consider refactoring

The 150-line if/else ladder is hard to reason about and prone to future off-by-one issues.
Extract per-requirement helpers or use a table-driven approach to keep cyclomatic complexity low.

test/eds/VersionWorkflows.ts (1)

36-60: Duplicate helper utilities across tests

createVersion and createVersionRequirement exist in multiple test suites.
Export them from a shared test/helpers/version.ts to avoid drift.

docs/Distributions.md (2)

5-9: Grammar & clarity improvements

Current sentence:
“Distributions are enshrined to be developed in state-less manner, this allows easier auditing and secure portability of code cross-chain.”

Suggested:

-Distributions are enshrined to be developed in state-less manner, this allows easier auditing and secure portability of code cross-chain.
+Distributions should be implemented as stateless contracts.  
+Statelessness simplifies auditing and guarantees that the same bytecode can be deployed safely across chains.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: You might be missing the article “a” here.
Context: ...utions are enshrined to be developed in state-less manner, this allows easier auditin...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~8-~8: Possible missing comma found.
Context: ... > [!WARNING] > If you deploy stateless distributions they will may likely be problematic to ...

(AI_HYDRA_LEO_MISSING_COMMA)


[grammar] ~8-~8: Two modal verbs in a row. Did you mean: “will, may”, “will” or “may”?
Context: ...you deploy stateless distributions they will may likely be problematic to adapt by devel...

(TWO_CONNECTED_MODAL_VERBS)


52-57: Fix determiners & plural forms

-In EDS [Upgradability](./Upgradability.md) is complex multi-party trust process. It enables pattern where distributor & user must agree on upgrade before it can be executed.
+In EDS, [Upgradability](./Upgradability.md) is **a** complex, multi-party trust process.  
+It enables **a** pattern in which the distributor and the user must both approve an upgrade before it can be executed.

Also change “This hooks” → “These hooks”.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~52-~52: Possible missing comma found.
Context: ...ble repository managed distribution In EDS Upgradability is ...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~52-~52: You might be missing the article “a” here.
Context: ... Upgradability is complex multi-party trust process. It enables p...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~52-~52: You might be missing the article “a” here.
Context: ...x multi-party trust process. It enables pattern where distributor & user must agree on ...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[style] ~53-~53: Consider a shorter alternative to avoid wordiness.
Context: ... on upgrade before it can be executed. In order to enable this process, standard upgradabl...

(IN_ORDER_TO_PREMIUM)


[uncategorized] ~54-~54: You might be missing the article “an” here.
Context: ...6 Hooks](./Hooks.md) implemented within immutable part of the contract. This hooks must b...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[grammar] ~55-~55: Use the singular noun “hook” with the word ‘This’.
Context: ...in immutable part of the contract. This hooks must be implemented by developer of the...

(DT_JJ_NNS_MD)


[uncategorized] ~55-~55: You might be missing the article “the” here.
Context: ...ract. This hooks must be implemented by developer of the distribution in such way, that o...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~55-~55: You might be missing the article “a” here.
Context: ...y developer of the distribution in such way, that only [Distributor](./Distributors...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~56-~56: A determiner appears to be missing. Consider inserting it.
Context: ...staller consent is checked in runtime. Example of such upgradable distribution is [Wra...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~57-~57: You might be missing the article “an” here.
Context: ...is checked in runtime. Example of such upgradable distribution is [WrappedTransparentUpgr...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)

src/distributions/VersionDistribution.sol (1)

8-11: Fix docstring reference

The class documentation refers to "CodeHashDistribution" instead of "VersionDistribution", suggesting this might be copy-pasted documentation that wasn't properly updated.

-/**
- * @title CodeHashDistribution
- * @notice This contract creates immutable. It allows to add metadata to the distribution as well as specify name and version for EIP712 compatibility reasons.
- * @dev This contract is a base for creating distributions that are identified by a deployed functionality (byte code hash).
- */
+/**
+ * @title VersionDistribution
+ * @notice This contract creates immutable distribution. It allows to add metadata to the distribution as well as specify name and version for EIP712 compatibility reasons.
+ * @dev This contract is a base for creating distributions that are identified by a deployed functionality (byte code hash).
+ */
src/mocks/MockTokenizedDistributor.sol (2)

97-106: Fix inconsistent indentation

The indentation in the addVersionMigration function is inconsistent with the rest of the file. The opening brace is aligned with the function name instead of being indented.

-  function addVersionMigration(
+    function addVersionMigration(
         bytes32 distributionId,
         LibSemver.VersionRequirement memory from,
         LibSemver.VersionRequirement memory to,
         bytes32 migrationHash,
         MigrationStrategy strategy,
         bytes memory distributorCalldata
     ) external onlyRole(DEFAULT_ADMIN_ROLE) {
         super._addVersionMigration(distributionId, from, to, migrationHash, strategy, distributorCalldata);
     }

24-26: Update function naming for clarity

You have two addDistribution methods with different signatures. Consider renaming them for better clarity, such as addDistributionByInitializer and addDistributionByRepository.

Also applies to: 85-93

src/distributors/OwnableDistributor.sol (1)

6-8: Duplicate IMigration import – safe to remove.

IMigration.sol is imported twice (lines 6 & 8).
While harmless for compilation, it bloats byte-code metadata and hurts readability.

-import "../interfaces/IMigration.sol";
src/interfaces/IInstaller.sol (1)

37-38: Error name style – minor consistency issue.

The error alreadyAllowed starts with a lowercase letter, unlike all other error identifiers in the file.
For consistency with Solidity & OZ conventions (PascalCase), consider renaming to AlreadyAllowed.

src/middleware/InstallerClonable.sol (1)

42-43: Redundant second using EnumerableSet directive.

using EnumerableSet … is declared twice (lines 16 and 39).
Remove the duplicate to reduce byte-code size.

src/distributors/Distributor.sol (1)

38-41: Confusing mapping key names (appComponentappId)

mapping(uint256 appComponent => bytes32 distributorsId) public distributionOf;

The key is an appId, not an appComponent address. Misleading names increase maintenance cost and onboarding time for new contributors. Consider renaming:

-mapping(uint256 appComponent => bytes32 distributorsId) public distributionOf;
+mapping(uint256 appId => bytes32 distributorsId) public distributionOf;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b55d95e and acb00db.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (65)
  • .cursorignore (1 hunks)
  • .solcover.js (1 hunks)
  • .vscode/settings.json (1 hunks)
  • contracts/mocks/MockFailingDistribution.sol (1 hunks)
  • contracts/mocks/MockLowLevelDistribution.sol (1 hunks)
  • contracts/mocks/MockMigration.sol (1 hunks)
  • contracts/mocks/MockPanicDistribution.sol (1 hunks)
  • contracts/mocks/MockRepository.sol (1 hunks)
  • deploy/ERC7744.js (1 hunks)
  • docs/Distributions.md (1 hunks)
  • docs/Distributors.md (1 hunks)
  • docs/Indexer.md (1 hunks)
  • docs/Repositories.md (1 hunks)
  • docs/Upgradability.md (1 hunks)
  • docs/Versioning.md (1 hunks)
  • docs/contracts/index.md (2 hunks)
  • hardhat.config.ts (2 hunks)
  • package.json (1 hunks)
  • src/abstracts/CodeIndexer.sol (0 hunks)
  • src/abstracts/Distributor.sol (0 hunks)
  • src/abstracts/Installer.sol (0 hunks)
  • src/abstracts/InstallerClonable.sol (0 hunks)
  • src/distributions/CloneDistribution.sol (1 hunks)
  • src/distributions/CodeHashDistribution.sol (3 hunks)
  • src/distributions/LatestVersionDistribution.sol (3 hunks)
  • src/distributions/UpgradableDistribution.sol (1 hunks)
  • src/distributions/VersionDistribution.sol (1 hunks)
  • src/distributors/Distributor.sol (1 hunks)
  • src/distributors/OwnableDistributor.sol (2 hunks)
  • src/distributors/TokenizedDistributor.sol (2 hunks)
  • src/erc7744/ERC7744.sol (1 hunks)
  • src/erc7744/LibERC7744.sol (1 hunks)
  • src/interfaces/IAdminGetter.sol (1 hunks)
  • src/interfaces/IDistributor.sol (9 hunks)
  • src/interfaces/IInstaller.sol (3 hunks)
  • src/interfaces/IMigration.sol (1 hunks)
  • src/interfaces/IRepository.sol (4 hunks)
  • src/libraries/LibInstaller.sol (0 hunks)
  • src/middleware/AuthorizationMiddleware.sol (1 hunks)
  • src/middleware/ERC7746Hooked.sol (2 hunks)
  • src/middleware/InstallerClonable.sol (1 hunks)
  • src/middleware/LibMiddleware.sol (1 hunks)
  • src/middleware/MiddlewareProxy.sol (1 hunks)
  • src/mocks/MockCloneDistribution.sol (2 hunks)
  • src/mocks/MockERC20.sol (1 hunks)
  • src/mocks/MockFailingDistribution.sol (1 hunks)
  • src/mocks/MockInstaller.sol (2 hunks)
  • src/mocks/MockLatestVersionDistribution.sol (1 hunks)
  • src/mocks/MockLowLevelDistribution.sol (1 hunks)
  • src/mocks/MockMigration.sol (1 hunks)
  • src/mocks/MockPanicDistribution.sol (1 hunks)
  • src/mocks/MockRepository.sol (1 hunks)
  • src/mocks/MockTokenizedDistributor.sol (4 hunks)
  • src/proxies/WrappedTransparentUpgradeableProxy.sol (1 hunks)
  • src/repositories/OwnableRepository.sol (1 hunks)
  • src/repositories/Repository.sol (5 hunks)
  • test/eds/CloneDistribution.ts (2 hunks)
  • test/eds/Distributor.ts (7 hunks)
  • test/eds/Installer.ts (4 hunks)
  • test/eds/LatestVersionDistribution.ts (1 hunks)
  • test/eds/Repository.ts (4 hunks)
  • test/eds/TokenizedDistributor.ts (5 hunks)
  • test/eds/UpgradabilityWorkflows.ts (1 hunks)
  • test/eds/VersionDistributor.ts (7 hunks)
  • test/eds/VersionWorkflows.ts (1 hunks)
💤 Files with no reviewable changes (5)
  • src/abstracts/CodeIndexer.sol
  • src/libraries/LibInstaller.sol
  • src/abstracts/Distributor.sol
  • src/abstracts/Installer.sol
  • src/abstracts/InstallerClonable.sol
🧰 Additional context used
🪛 LanguageTool
docs/Versioning.md

[uncategorized] ~3-~3: You might be missing the article “a” here.
Context: # Versioning EDS implements Semantic versioning system within one bytes32 wo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~5-~5: Possible missing comma found.
Context: ...ing system within one bytes32 word. On chain semantic versioning is managed by [LibS...

(AI_HYDRA_LEO_MISSING_COMMA)

docs/Distributors.md

[misspelling] ~3-~3: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...ovide trusted sources to end-users from a entity owned domain. Distributors may ...

(EN_A_VS_AN)


[uncategorized] ~15-~15: Possible missing article found.
Context: ...nstaller.md) can be runtime verified by Distributor. Distributor gets called with `beforeC...

(AI_HYDRA_LEO_MISSING_THE)

docs/Upgradability.md

[uncategorized] ~5-~5: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...th EDS ## Introduction EDS implements next level abstraction pattern over traditional sm...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[grammar] ~5-~5: Did you mean “upgrading”? Or maybe you should add a pronoun? In active voice, ‘allow’ + ‘to’ takes an object, usually a pronoun.
Context: ...mart contract upgradeability. It allows to upgrade contracts in a safe and easy way, which...

(ALLOW_TO)


[uncategorized] ~8-~8: A comma may be missing after the conjunctive/linking adverb ‘Hence’.
Context: ...nd distributor requires careful design. Hence this section describes the trust assump...

(SENT_START_CONJUNCTIVE_LINKING_ADVERB_COMMA)


[uncategorized] ~10-~10: Possible missing comma found.
Context: ...t assumptions that are made by EDS. In EDS it is assumed that downstream contracts...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~13-~13: Possible missing article found.
Context: ...](./Indexer.md) is the most upstream in dependency chain within the EDS ecosystem and is t...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~14-~14: Possible missing article found.
Context: ...ons](./Distributions.md) may be part of same trust domain they MUST use [ERC7744...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~14-~14: Possible missing comma found.
Context: ...ributions.md) may be part of same trust domain they MUST use [ERC7744 Indexer](./I...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~18-~18: Use a comma before ‘and’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...744 Indexer](./Indexer.md) to refer code and they trust any code they deploy or list...

(COMMA_COMPOUND_SENTENCE)


[uncategorized] ~22-~22: Possible missing comma found.
Context: ...versions](./Versions.md) he supports by users and may impose specific business or pro...

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~24-~24: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...Indexer](./Indexer.md) to refer code. - Distributors may allow cross-instance calls between ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[uncategorized] ~24-~24: Possible missing comma found.
Context: ...s between apps that they consider to be trusted and enforce these rules via runtime val...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~32-~32: You might be missing the article “an” here.
Context: ...ion with ERC7746 Whenever an app calls end-user contract, or anyone attempts to ca...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[uncategorized] ~32-~32: You might be missing the article “a” here.
Context: ...er contract, or anyone attempts to call user-owned application, ERC7746 interface is...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~32-~32: You might be missing the article “the” here.
Context: ...ttempts to call user-owned application, ERC7746 interface is used to implement [runtime...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~43-~43: You might be missing the article “a” here.
Context: ...t assumptions, regular proxies owned by single party are not allowed. Developers **MUS...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~43-~43: Do not mix variants of the same word (‘upgradeable’ and ‘upgradable’) within a single text.
Context: ...not allowed. Developers MUST deploy upgradeable distributions for their app, example of...

(EN_WORD_COHERENCY)


[uncategorized] ~45-~45: Possible missing article found.
Context: ...ontracts create upgradable instances of app, which are owned by distributor but...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~45-~45: You might be missing the article “the” here.
Context: ...nstances of app, which are owned by distributor but access permissions are managed ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~45-~45: You might be missing the article “the” here.
Context: ...t access permissions are managed by Installer. Attempts to call upgradability selecto...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[grammar] ~47-~47: Did you mean “upgrading”? Or maybe you should add a pronoun? In active voice, ‘allow’ + ‘to’ takes an object, usually a pronoun.
Context: ...all to Installer. This mechanic allows to upgrade app without requiring any changes to di...

(ALLOW_TO)


[uncategorized] ~47-~47: Possible missing article found.
Context: ...aller. This mechanic allows to upgrade app without requiring any changes to distri...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~47-~47: Possible missing article found.
Context: ...de app without requiring any changes to distributor, while still allowing to enforce busine...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ...o enforce business logic on upgrade. If user wants to renounce distributor, he can d...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ... do so, but will not be able to upgrade app anymore, unless distributor transfers o...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ... be able to upgrade app anymore, unless distributor transfers ownership to user or new dist...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “the” here.
Context: ...less distributor transfers ownership to user or new distributor. ## Upgrading Pro...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~48-~48: You might be missing the article “a” here.
Context: ...tributor transfers ownership to user or new distributor. ## Upgrading Process of...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~52-~52: A determiner appears to be missing. Consider inserting it.
Context: ...ser or new distributor. ## Upgrading Process of normal upgrade routine is as follows...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~55-~55: Possible missing article found.
Context: ...rmal upgrade routine is as follows: 1. Distributor lists new upgrade version for a Reposit...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~55-~55: Possible missing article found.
Context: ...ne is as follows: 1. Distributor lists new upgrade version for a Repository with a...

(AI_HYDRA_LEO_MISSING_A)


[uncategorized] ~58-~58: A determiner appears to be missing. Consider inserting it.
Context: ...tor with upgradeUserInstance hook. 4. Distributor validates that upgrade is allowed and t...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~59-~59: Possible missing article found.
Context: ...version is listed. 5. Distributor loads migration plan from his storage and executes migr...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~60-~60: You might be missing the article “the” here.
Context: ...upgradeToAndCall function by wrapping standard OpenZeppelin upgrade function. 7. App i...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ... to Installer via beforeCall hook. 8. Installer validates that call was allowed by Dist...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ...Call` hook. 8. Installer validates that call was allowed by Distributor. 9. Install...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: You might be missing the article “the” here.
Context: ...ller validates that call was allowed by Distributor. 9. Installer sets flag that app is no...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...at call was allowed by Distributor. 9. Installer sets flag that app is no longer in upgr...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~63-~63: You might be missing the article “a” here.
Context: ...owed by Distributor. 9. Installer sets flag that app is no longer in upgrade mode. ...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~63-~63: You might be missing the article “the” here.
Context: ...stributor. 9. Installer sets flag that app is no longer in upgrade mode. ## Migra...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~75-~75: A determiner appears to be missing. Consider inserting it.
Context: ...; MigrationStrategy strategy; } ``` Migration plan is stored in Distributor storage a...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...ller and will be managed by Repository. Distributor will not execute any migration logic, b...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~92-~92: You might be missing the article “the” here.
Context: ...y migration logic, but will only signal Installer to execute migration. Distributor cont...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~94-~94: Possible missing comma found.
Context: ...itory specified migration script one by one executing migrations for any major vers...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~94-~94: The abbreviation “e.g.” (= for example) requires two periods.
Context: ...lease between from and to versions. Eg. if migrating from v0.0.1 to v3.0.0 Dist...

(E_G)

docs/Distributions.md

[grammar] ~3-~3: In the grammatical structure ‘pronoun + be/get + used + to + verb’, the verb ‘used’ is used with the gerund.
Context: ... are the main entities in EDS. They are used to distribute and upgrade contracts. Distributions a...

(ADMIT_ENJOY_VB)


[uncategorized] ~5-~5: You might be missing the article “a” here.
Context: ...utions are enshrined to be developed in state-less manner, this allows easier auditin...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~8-~8: Possible missing comma found.
Context: ... > [!WARNING] > If you deploy stateless distributions they will may likely be problematic to ...

(AI_HYDRA_LEO_MISSING_COMMA)


[grammar] ~8-~8: Two modal verbs in a row. Did you mean: “will, may”, “will” or “may”?
Context: ...you deploy stateless distributions they will may likely be problematic to adapt by devel...

(TWO_CONNECTED_MODAL_VERBS)


[grammar] ~12-~12: In the grammatical structure ‘pronoun + be/get + used + to + verb’, the verb ‘used’ is used with the gerund.
Context: ...utions that are not stateless. They are used to distribute and upgrade contracts that are not stat...

(ADMIT_ENJOY_VB)


[uncategorized] ~14-~14: You might be missing the article “the” here.
Context: ...stributors.md) instead that will manage state of your distribution and is designed wi...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[style] ~17-~17: Consider a shorter alternative to avoid wordiness.
Context: ...t in mind. ## Creating a distribution In order to create a distribution, first deploy you...

(IN_ORDER_TO_PREMIUM)


[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ... address you want to use and then index contract code using Indexer Onc...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~20-~20: You might be missing the article “the” here.
Context: ... can create a distribution using one of available [distribution contracts](../src/distrib...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~22-~22: You might be missing the article “the” here.
Context: ...at will deploy and link every source to proxy. Here is simple example of stateless d...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~24-~24: You might be missing the article “a” here.
Context: ...nd link every source to proxy. Here is simple example of stateless distribution using...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~52-~52: Possible missing comma found.
Context: ...ble repository managed distribution In EDS Upgradability is ...

(AI_HYDRA_LEO_MISSING_COMMA)


[uncategorized] ~52-~52: You might be missing the article “a” here.
Context: ... Upgradability is complex multi-party trust process. It enables p...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~52-~52: You might be missing the article “a” here.
Context: ...x multi-party trust process. It enables pattern where distributor & user must agree on ...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[style] ~53-~53: Consider a shorter alternative to avoid wordiness.
Context: ... on upgrade before it can be executed. In order to enable this process, standard upgradabl...

(IN_ORDER_TO_PREMIUM)


[uncategorized] ~54-~54: You might be missing the article “an” here.
Context: ...6 Hooks](./Hooks.md) implemented within immutable part of the contract. This hooks must b...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[grammar] ~55-~55: Use the singular noun “hook” with the word ‘This’.
Context: ...in immutable part of the contract. This hooks must be implemented by developer of the...

(DT_JJ_NNS_MD)


[uncategorized] ~55-~55: You might be missing the article “the” here.
Context: ...ract. This hooks must be implemented by developer of the distribution in such way, that o...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~55-~55: You might be missing the article “a” here.
Context: ...y developer of the distribution in such way, that only [Distributor](./Distributors...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~56-~56: A determiner appears to be missing. Consider inserting it.
Context: ...staller consent is checked in runtime. Example of such upgradable distribution is [Wra...

(AI_EN_LECTOR_MISSING_DETERMINER)


[uncategorized] ~57-~57: You might be missing the article “an” here.
Context: ...is checked in runtime. Example of such upgradable distribution is [WrappedTransparentUpgr...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[uncategorized] ~59-~59: You might be missing the article “the” here.
Context: ...agement of the upgrades & migrations on developer side is done via [Repository](./Reposit...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[uncategorized] ~62-~62: A comma might be missing here.
Context: ...per packages as they need to. For more details refer to [Upgradability](./Upgradabilit...

(AI_EN_LECTOR_MISSING_PUNCTUATION_COMMA)

docs/Indexer.md

[uncategorized] ~3-~3: You might be missing the article “a” here.
Context: ...at allows immutable code referencing in permission-less manner. ERC7744 indexer is the mo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~5-~5: Possible missing article found.
Context: ...ERC7744 indexer is the most upstream in dependency chain within the EDS ecosystem and is t...

(AI_HYDRA_LEO_MISSING_THE)


[uncategorized] ~18-~18: You might be missing the article “the” here.
Context: ...s a library that helps to interact with ERC7744 indexer. Typical usage is as follows: ...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)


[style] ~58-~58: Using many exclamation marks might seem excessive (in this case: 4 exclamation marks for a text that’s 1097 characters long)
Context: ...er.get(codeHash); ``` ## Using CLI > [!NOTE] > The CLI provides utilities to in...

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.17.2)
docs/Upgradability.md

35-35: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)


90-90: Emphasis used instead of a heading
null

(MD036, no-emphasis-as-heading)

🔇 Additional comments (81)
.cursorignore (1)

1-2: Sensitive files are now ignored
This .cursorignore file correctly prevents the .env file and the entire .secrets/ directory from being processed by Cursor, reducing the risk of leaking environment variables or credentials.

.solcover.js (1)

1-3: Appropriate configuration for code coverage.

The configuration correctly excludes mock contracts and ERC7744 implementations from coverage analysis, which is a good practice. This ensures coverage metrics focus on your actual project code rather than test mocks or external standards implementation.

src/interfaces/IAdminGetter.sol (1)

1-6: Clean interface declaration for proxy admin access.

This interface properly defines a single view function for retrieving the wrapped proxy admin address, which supports the proxy pattern enhancement described in the summary.

src/mocks/MockERC20.sol (1)

10-18: Mock implementation properly supports distribution testing.

The added instantiate function correctly implements a simplified version that returns this contract's address as the only instance, which is suitable for testing distribution workflows.

src/middleware/ERC7746Hooked.sol (1)

4-4: Import path updated for LibMiddleware.

The import path for LibMiddleware has been changed from a parent directory to the current directory, suggesting a file structure reorganization.

src/distributions/CloneDistribution.sol (2)

8-8: CodeNotFoundInIndex error retained without CodeIndexer.

The contract still defines the CodeNotFoundInIndex error, but since it no longer inherits from CodeIndexer, ensure that this error is properly used in concrete implementations or if it should be moved elsewhere.

Is this error still relevant in the context where CodeIndexer is no longer used? Please verify if concrete implementations are throwing this error, or if it should be updated or moved to a different contract.


7-7:

✅ Verification successful

Removed inheritance from CodeIndexer.

The contract no longer inherits from CodeIndexer and now only implements the IDistribution interface. This aligns with the broader refactoring to use LibERC7744 for code index interactions.


🏁 Script executed:

#!/bin/bash
# Check how CloneDistribution implementations handle code indexing without CodeIndexer

# Look for implementations of CloneDistribution
echo "Checking for implementations of CloneDistribution..."
rg "contract.*is.*CloneDistribution" --type sol

# Check for CodeNotFoundInIndex error usage
echo "Checking for CodeNotFoundInIndex error usage..."
rg "CodeNotFoundInIndex" --type sol

# Check if LibERC7744 is being used in implementations
echo "Checking for LibERC7744 usage in implementations..."
rg "LibERC7744" --type sol

Length of output: 529


Let's retry the searches using explicit .sol globs:


🏁 Script executed:

#!/bin/bash
# Search for any contracts inheriting CloneDistribution
echo "Checking for implementations of CloneDistribution..."
rg -g '*.sol' -P "contract\s+\w+\s+is\s+CloneDistribution" -A2

# Search for CodeNotFoundInIndex references
echo "Checking for CodeNotFoundInIndex usage..."
rg -g '*.sol' "CodeNotFoundInIndex"

# Search for LibERC7744 usage
echo "Checking for LibERC7744 usage..."
rg -g '*.sol' "LibERC7744"

Length of output: 2631


Removal of CodeIndexer inheritance in CloneDistribution confirmed

CloneDistribution.sol now declares:

abstract contract CloneDistribution is IDistribution {
    error CodeNotFoundInIndex(bytes32 codeId);
    // …
}

It no longer inherits from CodeIndexer. All downstream implementations handle code‐index interactions via LibERC7744:

  • src/mocks/MockCloneDistribution.sol
  • src/distributions/CodeHashDistribution.sol
  • src/distributions/LatestVersionDistribution.sol

No further changes required.

test/eds/CloneDistribution.ts (2)

9-9: Updated constructor call to include distribution name.

The MockCloneDistribution deployment now includes "MockCloneDistribution" as a constructor parameter, reflecting changes in the contract implementation to use named distributions.


24-26:

✅ Verification successful

Updated string parsing for ShortString compatibility.

The test now uses a specific approach to parse the bytes32 name by slicing off the last two characters and appending "00" before converting to a string. This change accommodates the new ShortString format used for distribution names.


🏁 Script executed:

#!/bin/bash
# Verify the ShortString implementation and usage pattern

# Check the MockCloneDistribution implementation
echo "Checking MockCloneDistribution implementation..."
fd -e sol "MockCloneDistribution.sol" | xargs cat

# Look for ShortString usage in the codebase
echo "Checking for ShortString imports and usage patterns..."
rg "ShortString" --type sol

# Check for similar string parsing patterns in other test files
echo "Checking for similar string parsing patterns in tests..."
rg "slice\(0, -2\) \+ \"00\"" --type ts

Length of output: 1630


Confirmed ShortString parsing is correct in CloneDistribution test

The test in test/eds/CloneDistribution.ts intentionally slices off the last two characters and appends "00" before calling ethers.utils.parseBytes32String. This matches how ShortStrings.toShortString(name) pads a bytes32 value with trailing null bytes, and ensures that unwrapping the distribution name yields "MockCloneDistribution" as expected.

• Verified MockCloneDistribution.sol imports and uses ShortStrings.toShortString and ShortString.unwrap.
• No other tests employ this parsing pattern.

src/distributions/CodeHashDistribution.sol (3)

4-5: Import path improvements and library integration

These import changes reflect a refactoring of the codebase structure, with the CloneDistribution contract now being imported from the local directory and the new LibERC7744 library being introduced. This aligns with a more modular approach.


17-17: Library integration for cleaner code

Adding the LibERC7744 library for bytes32 types allows for improved encapsulation of ERC7744 indexer interactions.


30-30: Simplified code indexing lookup with improved error handling

Replacing direct index lookup with codeHash.getContainerOrThrow() centralizes the indexer interaction logic and error handling in the library, making the code more maintainable and consistent.

src/middleware/MiddlewareProxy.sol (2)

4-5: Contract dependencies refactoring

The import paths have been updated to reflect the middleware system refactoring, importing from local paths and renaming components.


12-12:

✅ Verification successful

Base contract inheritance change

The contract now inherits from ERC7746Hooked instead of what was likely ERC7746Middleware. Ensure that the new base contract provides the same functionality and doesn't break existing behavior.


🏁 Script executed:

#!/bin/bash
# Examine the new ERC7746Hooked contract to verify compatibility with previous ERC7746Middleware
cat src/middleware/ERC7746Hooked.sol 2>/dev/null || echo "File not found"

# Check for any places in tests where MiddlewareProxy is used to ensure they're still valid
rg -A 3 "MiddlewareProxy" --type ts

Length of output: 1614


🏁 Script executed:

#!/bin/bash
# Search for any references to the old base contract name
rg -n "ERC7746Middleware"

# Search for occurrences of the new base contract in imports/usages
rg -n "ERC7746Hooked"

# Search for any usage of MiddlewareProxy across all files (tests, scripts, configs)
rg -n "MiddlewareProxy"

Length of output: 7051


Inheritance change verified

The new ERC7746Hooked abstract contract defines the same ERC7746C and ERC7746 modifiers as the original ERC7746Middleware, so MiddlewareProxy’s behavior remains identical after switching its base contract. No further changes are needed.

deploy/ERC7744.js (2)

17-17: Suppressed logging for local development

Logs are now only printed when not running on the hardhat network, which improves clarity during testing. This is a good practice for reducing noise in test output.

Also applies to: 20-20


8-9:

✅ Verification successful

Updated deterministic deployment salt

The salt for deterministic deployment has been changed to a new fixed value. This will result in a different deployment address for the contract, which could impact references to the contract throughout the codebase.


🏁 Script executed:

#!/bin/bash
# Check if the contract address is referenced elsewhere in the codebase
rg -A 1 "ERC7744" --type sol

# Also check for direct address references that might need updating
# (Would need to know the previous deployed address to search for it)

Length of output: 56


🏁 Script executed:

#!/bin/bash
# 1. Make ripgrep recognize Solidity files
rg --type-add 'sol:*.sol' --type sol "ERC7744" -A 1

# 2. Search for any ERC7744 references in JavaScript/TypeScript
rg "ERC7744" -g '*.js' -A 1
rg "ERC7744" -g '*.ts' -A 1

# 3. Confirm no other occurrences of the new salt value
rg "9425035d50edcd7504fe5eeb5df841cc74fe5eeb5df841cc74fe6cccd82dca6ee75bcdf774bd88d9" -A 1

Length of output: 12485


Confirm isolated salt update; no address references found

The new deterministic salt in deploy/ERC7744.js is only used by the Hardhat deploy script. We found no hard-coded contract addresses or previous salt values elsewhere in the codebase. All tests and contracts use Hardhat’s deployments fixture to fetch the ERC7744 address at runtime, so they’ll automatically pick up the new address.

• File updated: deploy/ERC7744.js – salt changed
• No direct address or salt references in .sol, .js, or .ts files
• Tests use deployments.get("ERC7744") and don’t assert a fixed address

hardhat.config.ts (3)

13-13: Good addition for test coverage tracking.

Adding solidity-coverage will help ensure better test coverage monitoring, which is essential for quality assurance of smart contracts.


122-124:

❓ Verification inconclusive

Compiler settings optimization with trade-offs to consider.

The changes to the compiler settings have important implications:

  • Setting evmVersion to "cancun" ensures access to the latest EVM features, but may limit compatibility with chains that haven't upgraded
  • Enabling viaIR can improve optimizations at the cost of longer compile times

Consider whether all target deployment chains support the Cancun EVM version:


🏁 Script executed:

#!/bin/bash
# Check compatibility of Cancun EVM with common EVM networks

echo "Checking network statuses for Cancun EVM compatibility..."
curl -s https://chainid.network/chains.json | jq -r '.[] | select(.name | contains("Ethereum") or contains("Arbitrum") or contains("Polygon") or contains("Optimism")) | "\(.name): \(.status)"'

Length of output: 984


Validate Cancun EVM support across all target networks

The current compiler config in hardhat.config.ts (lines 122–124):

evmVersion: "cancun",
viaIR: true,
optimizer: { … }

unlocks the latest opcodes and IR-based optimizations but introduces two risks:

  • Some chains may not yet have activated the Cancun hard fork, causing transactions to fail.
  • viaIR can significantly increase compile times.

Action items:

  • For each deployment target, confirm the Cancun fork activation block via its official docs or an RPC call (e.g. check eth_getBlockByNumber around the known fork height).
  • If any network hasn’t upgraded, consider falling back to a more widely supported evmVersion (e.g., london or paris).
  • Evaluate compilation performance with and without viaIR to find the right trade-off.

126-126:

❓ Verification inconclusive

Consider the impact of reduced optimizer runs.

Reducing optimizer runs from what was likely a much higher value to 2,000 significantly changes the optimization strategy. This optimizes for deployment cost rather than runtime efficiency, which could increase gas costs for frequently called functions.

Consider whether this is intentional or if a higher value might be more appropriate for frequently called functions:


🌐 Web query:

What is the optimal value for Solidity optimizer runs for different contract usage patterns?

💡 Result:

Optimal Solidity Optimizer Runs Value: Guidance by Contract Usage

The "optimizer runs" setting in the Solidity compiler is a trade-off parameter that influences the balance between contract deployment cost (gas spent when the contract is first published) and the cost of executing contract functions later. There is no single universally optimal value for all cases; the best setting depends on the contract's intended usage pattern and projected transaction frequency[6][8].

How the "runs" Parameter Works

  • Lower "runs" (e.g., 1): Reduces deployment cost (smaller code size), but leads to higher function execution costs after deployment. This is ideal for contracts where most of the gas will be spent on deployment and relatively fewer function calls are expected[1][3][6].
  • Higher "runs" (e.g., 1000, 10000, or even up to 1,000,000): Increases deployment cost (larger code size), but decreases the cost of each subsequent transaction, making it suitable for contracts that will be called frequently after deployment[3][6][7][8].

Recommended Values by Usage Pattern

Contract Pattern Recommended "runs" Value Explanation
One-off/rarely used contract 1–200 Cheaper deployment is prioritized, function execution cost less significant[1][3][6].
Frequently used (“utility”) 1000–10,000+ Focus on minimizing long-term function execution costs[3][7][8].
ERC-20/ERC-721 tokens, DEXs 5,000–1,000,000 High volume of transactions; function call optimization critical[2][3][7].
Custom, complex logic Test various values (e.g., 200–10,000) Analyze deployment/function costs; pick value that fits anticipated usage[2][8].
  • Default: If you do not specify, Solidity uses 200 as the default “runs” value[3][8].
  • Extreme uses: For contracts expecting extremely high call volume, values up to 1,000,000 are possible, but note that some verification services (like Etherscan) may have upper limits for accepted values[2].

How to Choose Optimally

There is no mathematical "best" value—you must estimate the contract’s lifetime use:

  • If the contract is mostly “set-and-forget” (e.g., a DAO with rare function calls): Use low runs.
  • If the contract will process thousands/millions of transactions (e.g., a token, marketplace, or DeFi contract): Use high runs.

Tip: If unsure, benchmark the contract with different --optimize-runs values and compare gas costs for both deployment and the functions most likely to be used frequently[2][8]. For some contracts, the cost savings from a higher runs value levels off beyond a certain point[8].

Summary Table

Usage Type Example Suggested Runs
Cheap deploy, few calls Governance, setup, migration 1–200
Frequent transaction ERC-20, DEX, utility contracts 1000–10,000
Extreme frequency Major DApps, bridges, rollups 10,000–1,000,000 (ensure Etherscan compatibility)

Key Takeaways

  • The “runs” value is not a number of optimization iterations—it is a cost trade-off knob[2][6][8].
  • Low value: Cheaper deployment, costlier execution.
  • High value: Costlier deployment, cheaper execution.
  • There is no universal best value: analyze your contract’s expected transaction pattern and select accordingly.
  • For most standard token contracts, 10,000 is often adopted as a practical high-frequency value[7].
  • Benchmarking on real function usage is the only way to find true optimality for your scenario.

For details on how the optimizer works internally, refer to Solidity documentation[1][6].

Citations:


Reevaluate your Solidity optimizer runs setting

The current setting in hardhat.config.ts (line 126) is:

runs: 2000

While 2,000 falls within the broad “frequently used” range (1,000–10,000), the optimal value depends on your contract’s transaction patterns:

  • If this is a high-volume contract (ERC-20/DEX/utility), consider raising runs to 5,000–10,000+ (up to 1,000,000) to minimize per-call gas.
  • If it’s rarely invoked (governance, one-off setup), you could lower it toward 200 or even below 2000 to save on deployment gas.
  • Benchmark your most critical functions at several runs values (e.g., 2,000 vs. 10,000) to measure deployment vs. execution gas trade-offs.

Please confirm your contract’s expected usage profile and adjust the optimizer runs value to match your deployment vs. runtime gas-cost priorities.

src/distributions/LatestVersionDistribution.sol (3)

7-7: Good integration of LibERC7744 for code index interactions.

Using LibERC7744 as a library for bytes32 types improves code organization and simplifies interactions with the ERC7744 indexer.

Also applies to: 18-18


37-37: More concise source address retrieval with improved error handling.

Changed from using separate calls to get the container address to using the extension method getContainerOrThrow() directly on the source ID. This makes the code more concise and likely improves error handling by explicitly throwing when a container isn't found.


30-30:

✅ Verification successful

Consider inheritance implications of adding virtual modifier.

Adding the virtual modifier to the instantiate function enables overriding in derived contracts, enhancing extensibility. However, ensure all derived contracts that may override this method are reviewed for compatibility with this change.


🏁 Script executed:

#!/bin/bash
# Check for any contracts that extend LatestVersionDistribution
echo "Searching for contracts that inherit from LatestVersionDistribution..."
fd -e sol | xargs grep -l "is LatestVersionDistribution" || echo "No direct inheritors found"

# Check for any instantiate function in derived contracts
echo "Checking for instantiate functions in inheriting contracts..."
fd -e sol | xargs grep -l "is LatestVersionDistribution" | xargs grep -l "function instantiate" || echo "No overriding instantiate functions found"

Length of output: 528


🏁 Script executed:

#!/bin/bash
echo "Displaying src/mocks/MockLatestVersionDistribution.sol:"
sed -n '1,200p' src/mocks/MockLatestVersionDistribution.sol

Length of output: 1527


No further action needed for instantiate overrides

The only contract inheriting from LatestVersionDistribution is MockLatestVersionDistribution.sol, and it already overrides instantiate, so marking the base method as virtual does not introduce any unresolved inheritance issues.

docs/Versioning.md (1)

1-56: Comprehensive versioning documentation with useful examples.

This is a well-structured documentation file that clearly explains the semantic versioning system, provides both Solidity and CLI examples, and includes important usage notes. This will be valuable for developers working with the EDS framework.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~3-~3: You might be missing the article “a” here.
Context: # Versioning EDS implements Semantic versioning system within one bytes32 wo...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~5-~5: Possible missing comma found.
Context: ...ing system within one bytes32 word. On chain semantic versioning is managed by [LibS...

(AI_HYDRA_LEO_MISSING_COMMA)

src/erc7744/ERC7744.sol (2)

25-26: Improved container validation with multiple checks.

The updated isValidContainer function now performs more comprehensive validation by checking the code hash and using the new isEIP7702 function. This prevents indexing of invalid or delegated contracts.


33-33: Documentation updated to match implementation.

The updated comment accurately describes the behavior of the register function when provided with an EIP7702 delegated EOA, maintaining consistency between documentation and implementation.

src/interfaces/IMigration.sol (1)

1-15: Well-designed migration interface for handling version upgrades.

The IMigration interface provides a clean and comprehensive contract for handling migrations between different versions. It follows good interface design practices with clear naming and a focused purpose.

docs/Repositories.md (1)

1-52: Comprehensive documentation for repository management.

The documentation clearly explains the concept of Repositories and provides a complete reference of CLI commands for managing them. The structure is logical and the commands follow a consistent pattern.

A suggestion to enhance this further would be to include example command outputs or sample workflows that demonstrate common repository management scenarios, if not already covered elsewhere in the documentation.

src/mocks/MockCloneDistribution.sol (5)

3-3: Good use of OpenZeppelin's ShortStrings for gas efficiency.

Using the ShortStrings library is an excellent choice for storing distribution names efficiently, as it optimizes gas usage for strings up to 31 bytes.


5-5: Updated import path reflects code reorganization.

The change in import path from abstracts to distributions indicates a logical reorganization of the codebase structure.


8-9: Efficient state variable declarations.

Using immutable for the distribution name and constant for the version are good practices for optimizing gas usage while clearly indicating the intended mutability of these values.


11-13: Improved flexibility with parametrized constructor.

Adding a constructor that accepts a name parameter makes the mock distribution more configurable and reusable across different test scenarios.


26-26: Sources function now uses state variables instead of hardcoded values.

Returning the unwrapped ShortString and version constant from state variables rather than hardcoded values improves maintainability and consistency.

test/eds/LatestVersionDistribution.ts (5)

1-13: Well-structured imports and type definitions.

The test file correctly imports all necessary testing utilities and type definitions, ensuring type safety and clear code organization.


14-22: Good test setup with clear variable declarations.

The test setup clearly defines all the necessary variables and constants for the tests, making the test context easy to understand.


23-66: Well-implemented test fixture using the loadFixture pattern.

The test fixture follows good practices by:

  1. Setting up signers
  2. Deploying all necessary contracts
  3. Initializing the repository with test data
  4. Returning all necessary objects for tests

This approach makes tests more maintainable and efficient.


72-82: Constructor tests verify essential functionality.

The tests correctly verify that the constructor sets the repository address and metadata as expected.


84-90: Clear test for contractURI functionality.

The test verifies that the contractURI method returns the expected metadata string.

test/eds/TokenizedDistributor.ts (4)

20-20: Variable renamed to better reflect its purpose

The variable name change from distributionId to distributedCodeHash is more semantically accurate, as it represents the keccak256 hash of the contract bytecode rather than a generic ID.


36-36: Constructor argument added for mock contract

Adding the string constructor argument "MockCloneDistribution" aligns with the updated mock contract implementation pattern used throughout the codebase.


39-39: Code hash calculation with appropriate variable name

The renaming of the variable used to store the hash makes the code easier to understand and maintains consistency with the updated terminology.


70-74: Updated function signature to include distribution name

The addDistribution calls now properly include a third parameter for the distribution name ("testDistribution"), conforming to the updated interface. This change is consistently applied across all instances in the file.

Also applies to: 86-90, 97-101

src/repositories/OwnableRepository.sol (4)

4-4: Updated import path

The import path for Repository.sol has been modified to reflect the new file structure.


7-8: Added new imports for migration support

The addition of IMigration.sol and ShortStrings.sol imports supports the new migration functionality implemented in this contract.


15-17: Enhanced newRelease function with migration support

The newRelease function now accepts a migrationHash parameter, enabling version migration capabilities. The function properly delegates to the superclass implementation.


19-21: Added migration script management functionality

The new changeMigrationScript function provides a way to update migration scripts for specific major versions, enhancing the repository's version management capabilities.

docs/Distributors.md (1)

1-66: Comprehensive documentation for Distributors

The documentation provides clear explanations of distributors, their purpose, and usage patterns. The CLI command reference is particularly thorough and will be valuable for users.

🧰 Tools
🪛 LanguageTool

[misspelling] ~3-~3: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...ovide trusted sources to end-users from a entity owned domain. Distributors may ...

(EN_A_VS_AN)


[uncategorized] ~15-~15: Possible missing article found.
Context: ...nstaller.md) can be runtime verified by Distributor. Distributor gets called with `beforeC...

(AI_HYDRA_LEO_MISSING_THE)

src/mocks/MockPanicDistribution.sol (2)

1-29: Well-implemented mock for testing panic scenarios

This mock contract effectively simulates runtime panic scenarios by intentionally performing a division by zero. It provides a good test case for error handling in the distributor system and properly implements all required interface methods.


8-18: Deliberate panic implementation for testing

The division by zero technique is an effective way to simulate runtime panics for testing, and the code is well-commented to indicate that this is intentional.

test/eds/Installer.ts (3)

42-42: Constructor arg looks good – nothing else to flag.
Passing the constructor-name string is in line with the new MockCloneDistribution constructor signature.


125-129: Verify that the expected error comes from the correct contract.
The call originates from installer.beforeCall, but you expect a revert attached to distributor’s ABI. That is valid only if the installer bubbles the exact custom error from the distributor. If the installer wraps or re-throws, this assertion will fail.

Consider asserting against the installer instance instead, or explicitly propagate the error in the contract.


101-107:

❌ Incorrect review comment

Possible off-by-one: confirm the index returned by getAppsNum.
getAppsNum() usually returns the current count, in which case the valid indices are 0 … count - 1. Querying getApp(instanceNum) risks an “index out of bounds” revert if the contract is zero-indexed.

Please double-check the contract’s implementation or add -1 when appropriate.


🏁 Script executed:

#!/bin/bash
# Quick check: search for getAppsNum implementation and see how it's used.
rg --context 3 --line-number $'function getAppsNum' | head -n 20

Length of output: 1063


🏁 Script executed:

#!/bin/bash
# Show getApp implementation with context
rg -n --context 5 'function getApp' src/middleware/InstallerClonable.sol || true

# Show getAppsNum implementation with context
rg -n --context 5 'function getAppsNum' src/middleware/InstallerClonable.sol || true

# Show where appNum is modified (installation logic)
rg -n --context 10 'appNum' src/middleware/InstallerClonable.sol || true

Length of output: 4564


Off-by-one concern resolved: getAppsNum is 1-based
No change needed—strg.appNum is incremented before storing each app, so the first app lives at ID 1 and getAppsNum() returns the latest valid index. Calling getApp(instanceNum) is correct and won’t underflow.

Likely an incorrect or invalid review comment.

test/eds/VersionDistributor.ts (3)

37-41: Good addition of MockInstaller.
Creating a dedicated installer instance makes the tests closer to the production flow.


46-55: Confirm that the hash matches the code registered in ERC7744.
codeIndex.register(dummyMigration.address) stores the address, not the hash, whereas you later pass the keccak‐256 hash of the runtime byte-code to newRelease. Ensure the repository compares identical identifiers (address vs hash), otherwise the migration script may never be found at runtime.


138-141: Sanity-check instantiateData packing.
The encoder packs (address, bytes) but the decode comment above (now removed) hinted at a tuple InstantiateData. Verify the solidity side decodes instantiateData the same way, including the order and types.

src/mocks/MockFailingDistribution.sol (2)

7-18: The instantiate function implements controllable failure behavior.

This mock contract function provides a good mechanism for testing error handling in the distributor system by intentionally failing when specific arguments are provided.


24-28: The get function returns consistent metadata.

The get function correctly returns the same metadata as a successful instantiation, which ensures consistent behavior when querying the distribution.

src/mocks/MockLowLevelDistribution.sol (3)

7-18: The instantiate function correctly implements low-level error simulation.

Using inline assembly with the invalid() opcode is an effective way to simulate a low-level error for testing how the distribution system handles such failures.


13-17: Unreachable code clearly marked.

The comment correctly indicates that the code following the assembly block is unreachable. This is good documentation for future maintainers.


24-28: The get function provides expected metadata.

The get function returns consistent data about the distribution, which is necessary for testing the system's query capabilities.

contracts/mocks/MockLowLevelDistribution.sol (2)

15-26: Low-level error simulation uses a different approach.

This implementation simulates a low-level error by making a call to a non-existent function at address(0), while the other version uses the invalid() opcode. Both are valid approaches but produce different error types which could affect error handling tests.


28-33: ShortStrings usage is consistent with naming conventions.

The use of OpenZeppelin's ShortStrings for the distribution name is consistent with best practices for gas-efficient string storage.

src/distributors/TokenizedDistributor.sol (4)

13-13: Improved mapping key naming for clarity.

Changing from id to codeHash makes the purpose of the mapping clearer and more aligned with its actual usage.


34-41: Added support for human-readable distribution names.

The addition of the readableName parameter enhances the usability of the system by providing a way to identify distributions with descriptive names.


46-48: Updated function signature adds versioning support.

The addition of LibSemver.VersionRequirement shows this implementation now supports semantic versioning, which is an important feature for contract management.


56-67: The payment logic remains unchanged.

The payment transfer functionality is correctly maintained during the refactoring, ensuring that the tokenized distribution mechanism continues to work as expected.

src/mocks/MockLatestVersionDistribution.sol (2)

7-12: Good implementation of the mock contract constructor and state variable.

The contract correctly stores the metadata parameter as an immutable variable and properly initializes the parent contract. This is a good pattern for mock contracts.


14-17: Good implementation of the contractURI method.

The contractURI() implementation correctly converts the bytes32 metadata to a string using the helper function. This provides a clean way to access metadata in tests.

contracts/mocks/MockFailingDistribution.sol (3)

7-13: Good implementation of the mock distribution contract constructor.

The contract correctly initializes the distribution name using OpenZeppelin's ShortStrings library, which is an efficient way to store short strings in contracts.


29-34: The get method correctly implements the IDistribution interface.

This implementation properly returns the contract's address, name, and version matching the behavior of the instantiate method (minus side effects), which ensures consistent behavior.


15-27:

✅ Verification successful

Verify the expected failure trigger matches consumer expectations.

The instantiate function reverts when args match the encoded bytes32 "FAIL". Make sure consumers of this mock know they need to pass abi.encode(bytes32("FAIL")) to trigger the failure, not just the string "FAIL".


🏁 Script executed:

#!/bin/bash
# Check how the "FAIL" argument should be encoded in tests

# Look for test files that use this mock
rg -A 3 "MockFailingDistribution" --type ts

Length of output: 629


🏁 Script executed:

#!/bin/bash
# Search for instantiate usage in TypeScript tests
rg -A3 "\.instantiate" --type ts

# Search for distribution calls that may pass args through Distributor
rg -A3 "\.distribute" --type ts

# Search for revert assertions matching the mock's error message
rg -F "Intentional instantiation failure" --type ts

Length of output: 7878


No changes needed—tests already use the correct “FAIL” encoding

The existing TypeScript tests trigger the mock’s revert by passing a 32-byte hex string (via ethers.utils.formatBytes32String("FAIL")), which matches abi.encode(bytes32("FAIL")) under the hood.

• In test/eds/Distributor.ts you can see:

await expect(
  distributor
    .connect(owner)
    .instantiate(failingDistId, ethers.utils.formatBytes32String("FAIL"))
).to.be.reverted;
src/interfaces/IRepository.sol (6)

4-6: The import paths have been updated correctly.

The imports now reference the versioning library and add the IMigration interface, which is needed for the migration functionality.


51-56: Good addition of the MigrationScriptAdded event.

The event is well-documented with NatSpec comments and includes indexed parameters to make filtering for these events more efficient.


92-98: Good addition of changeMigrationScript function.

The function is well-documented with proper NatSpec comments explaining its purpose and events that must be emitted.


100-106: Good addition of getMigrationScript function.

The function includes helpful explanatory comments about how the migration script should behave, clarifying that it must handle migration from the previous major version to the new one.


120-125: Good addition of resolveVersion function.

This new function provides an important capability to resolve version requirements to specific versions, which is essential for dependency management in versioned systems.


90-90:

✅ Verification successful

Breaking change to the newRelease function - verify implementations are updated.

The newRelease function signature has been updated to include a migration hash parameter. This is a breaking change that requires updates to all implementations of this interface.


🏁 Script executed:

#!/bin/bash
# Find all implementation of the newRelease function
# to verify they've been updated to include the migrationHash parameter

# Find repository implementations
rg "function newRelease\(" --include="*.sol" -A 2

# Check for implementations using Repository
rg -A 3 "is IRepository" --include="*.sol"

Length of output: 980


I’ll re-run searches with the correct ripgrep flags to locate all newRelease implementations and any contracts implementing IRepository.


🏁 Script executed:

#!/bin/bash
# Find all occurrences of newRelease in Solidity files
rg --glob '*.sol' -n "function newRelease\("

# Find all contracts that implement the IRepository interface
rg --glob '*.sol' -n "is IRepository"

Length of output: 1020


Implementations updated to include migrationHash in newRelease

All contracts implementing IRepository already match the updated signature:

  • contracts/mocks/MockRepository.sol
  • src/mocks/MockRepository.sol
  • src/repositories/OwnableRepository.sol

No missing overrides remain.

docs/Upgradability.md (1)

30-40: Good explanation of runtime validation flow.

The explanation of the runtime validation with ERC7746 clearly shows the call flow and middleware hooks. The ASCII diagram is particularly helpful for understanding the execution flow.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~32-~32: You might be missing the article “an” here.
Context: ...ion with ERC7746 Whenever an app calls end-user contract, or anyone attempts to ca...

(AI_EN_LECTOR_MISSING_DETERMINER_AN)


[uncategorized] ~32-~32: You might be missing the article “a” here.
Context: ...er contract, or anyone attempts to call user-owned application, ERC7746 interface is...

(AI_EN_LECTOR_MISSING_DETERMINER_A)


[uncategorized] ~32-~32: You might be missing the article “the” here.
Context: ...ttempts to call user-owned application, ERC7746 interface is used to implement [runtime...

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

🪛 markdownlint-cli2 (0.17.2)

35-35: Fenced code blocks should have a language specified
null

(MD040, fenced-code-language)

test/eds/UpgradabilityWorkflows.ts (1)

212-220: Potential runtime error when reading first component

appComponents.contracts[0] is accessed without verifying that contracts.length > 0.
After the change above, add an explicit expect:

expect(appComponents.contracts.length).to.be.gt(0);

to fail fast if installer returns an empty array.

contracts/mocks/MockRepository.sol (1)

1-86: Clean implementation of the IRepository interface

The MockRepository contract provides a thorough implementation of the IRepository interface with proper version management, metadata handling, and migration script support. The code is well-structured and follows good practices.

src/mocks/MockRepository.sol (1)

1-86: Well-implemented mock for IRepository interface

This mock implementation provides all necessary functionality for the IRepository interface, with clear function implementations and appropriate state management. The contract properly emits events when state changes and handles version resolution correctly.

src/interfaces/IInstaller.sol (1)

130-138: Event/documentation mismatch – missing permissions description.

event Installed introduces a third indexed parameter permissions, yet the NatSpec block does not explain its semantics and the param list omits it.
Please document the meaning and expected values for permissions (or remove the param if unused) to keep the public ABI self-describing.

@@ -6,7 +6,7 @@ import {IERC7746} from "../interfaces/IERC7746.sol";
import {IDistributor} from "../interfaces/IDistributor.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
contract SimpleAccessManager is Initializable, IERC7746, ERC165 {
contract AuthorizationMiddleware is Initializable, IERC7746, ERC165 {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Contract renamed but internal references not updated.

While the contract has been renamed from SimpleAccessManager to AuthorizationMiddleware, the internal constant SACM_STORAGE_POSITION (line 21) and struct SimpleAccessManagerInitializer (line 30) still reference the old name.

- contract AuthorizationMiddleware is Initializable, IERC7746, ERC165 {
+ contract AuthorizationMiddleware is Initializable, IERC7746, ERC165 {
    // ... later in the code ...
-   bytes32 private constant SACM_STORAGE_POSITION = keccak256("simple.access.manager.storage.position");
+   bytes32 private constant AUTH_MIDDLEWARE_STORAGE_POSITION = keccak256("authorization.middleware.storage.position");
    // ... and ...
-   struct SimpleAccessManagerInitializer {
+   struct AuthorizationMiddlewareInitializer {

Please update all internal references to match the new contract name for consistency.

📝 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
contract AuthorizationMiddleware is Initializable, IERC7746, ERC165 {
contract AuthorizationMiddleware is Initializable, IERC7746, ERC165 {
// ... later in the code ...
bytes32 private constant AUTH_MIDDLEWARE_STORAGE_POSITION =
keccak256("authorization.middleware.storage.position");
// ... and ...
struct AuthorizationMiddlewareInitializer {
// (fields go here)
}
// ... rest of contract ...
}

Comment on lines +73 to +74
"@openzeppelin/contracts": "^5.3.0",
"@openzeppelin/contracts-upgradeable": "^5.3.0",
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

OpenZeppelin contracts upgraded to latest minor version.

Both @openzeppelin/contracts and @openzeppelin/contracts-upgradeable have been updated from ^5.0.2 to ^5.3.0. This is a minor version upgrade that should maintain backward compatibility while providing new features and security improvements.


🏁 Script executed:

#!/bin/bash
# Check for breaking changes or security advisories in the OpenZeppelin contracts upgrade

# Get the changelog to see what changed between 5.0.2 and 5.3.0
curl -s https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/CHANGELOG.md | grep -A 20 "## 5.3.0" | grep -B 20 "## 5.0.2" || echo "Changelog sections not found"

# Check GitHub for any open security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "openzeppelin-contracts") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 570


🏁 Script executed:

#!/bin/bash
# Fetch and inspect CHANGELOG.md for OpenZeppelin Contracts
gh api repos/OpenZeppelin/openzeppelin-contracts/contents/CHANGELOG.md --jq '.content' | base64 -d > changelog.md

echo "Headings in CHANGELOG.md:"
grep -n "^##" changelog.md

echo -e "\nSection for 5.3.0:"
sed -n '/## 5\.3\.0/,/## [0-9]\+\.[0-9]\+\.[0-9]\+/p' changelog.md

echo -e "\nSection for 5.0.2:"
sed -n '/## 5\.0\.2/,/## [0-9]\+\.[0-9]\+\.[0-9]\+/p' changelog.md

echo -e "\nGlobal 'breaking' mentions:"
grep -ni "breaking" changelog.md || echo "None found"

Length of output: 670


🏁 Script executed:

#!/bin/bash
set -e

# Download raw CHANGELOG.md
curl -sSL https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/CHANGELOG.md -o changelog.md

echo "## Headings in CHANGELOG.md"
grep -n '^## ' changelog.md || echo "No '##' headings found"

echo -e "\n## Context around '5.3.0'"
grep -n '5\.3\.0' changelog.md -A 5 -B 2 || echo "No direct matches for 5.3.0 in CHANGELOG.md"

echo -e "\n## Context around '5.0.2'"
grep -n '5\.0\.2' changelog.md -A 5 -B 2 || echo "No direct matches for 5.0.2 in CHANGELOG.md"

echo -e "\n## GitHub release notes for v5.3.0"
gh release view v5.3.0 --repo OpenZeppelin/openzeppelin-contracts --json name,body || echo "Release v5.3.0 not found"

echo -e "\n## GitHub release notes for v5.0.2"
gh release view v5.0.2 --repo OpenZeppelin/openzeppelin-contracts --json name,body || echo "Release v5.0.2 not found"

Length of output: 9282


Review OpenZeppelin Contracts Upgrade – Breaking Changes Introduced in v5.3.0

The upgrade from 5.0.2→5.3.0 isn’t purely additive: v5.3.0 includes breaking changes you must address before merging.

GovernorCountingOverridable.VoteReceipt

  • Renamed struct fields hasOverridenhasOverridden and overridenWeightoverriddenWeight.
    Custom errors
  • GovernorAlreadyOverridenVoteGovernorAlreadyOverriddenVote
  • GovernorOnlyProposerGovernorUnableToCancel

Please update all references (imports, type usages, error checks) to the new identifiers and run a full test suite to catch any regressions.

@@ -11,7 +11,7 @@ import "../libraries/LibMiddleware.sol";
* that can be extended and customized by derived contracts.
* @author Peeramid Labs, 2024
*/
abstract contract ERC7746Middleware {
abstract contract ERC7746Hooked {
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

Contract renamed from ERC7746Middleware to ERC7746Hooked.

The contract has been renamed while maintaining the same functionality. This renaming better reflects its purpose as a hook for ERC7746 middleware operations.


🏁 Script executed:

#!/bin/bash
# Check for references to the old contract name that might need updating

# Look for imports or references to the old contract name
echo "Checking for any remaining references to ERC7746Middleware..."
rg "ERC7746Middleware" --type-add "sol:*.sol" --type-add "ts:*.ts" --type sol,ts

# Look for contracts that might be inheriting from this contract
echo "Checking for contracts that inherit from ERC7746Hooked..."
rg "contract.*is.*ERC7746Hooked" --type sol

Length of output: 443


🏁 Script executed:

#!/bin/bash
set -e

echo "Searching for any remaining references to 'ERC7746Middleware'..."
rg "ERC7746Middleware" --glob '*.sol' --glob '*.ts' -n || true

echo "Searching for contracts inheriting from 'ERC7746Hooked'..."
rg -E "contract\s+\w+\s+is\s+ERC7746Hooked" --glob '*.sol' -n || true

Length of output: 872


🏁 Script executed:

#!/bin/bash
set -e

echo "Verifying inheritance of ERC7746Hooked in all Solidity files..."
rg -n "is\s+ERC7746Hooked" --glob '*.sol' || true

Length of output: 271


Update NatSpec @title to reflect renamed contract

The code rename from ERC7746Middleware to ERC7746Hooked is correct, but the NatSpec title wasn’t updated:

• In src/middleware/ERC7746Hooked.sol

  • Line 7: change
    - * @title ERC7746Middleware
    + * @title ERC7746Hooked

No other references to the old name were found, and MiddlewareProxy correctly inherits from ERC7746Hooked.

📝 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
abstract contract ERC7746Hooked {
/**
* @title ERC7746Hooked
* @author ...
* @notice ...
*/
abstract contract ERC7746Hooked {
// ...
}

Comment on lines +33 to +40
for (uint256 i; i < newLayers.length; ++i) {
ls[i] = newLayers[i];
}
if (newLayers.length < ls.length) {
for (uint256 i; i < ls.length - newLayers.length; ++i) {
ls.pop();
}
}
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

Array length boundary concerns

Make sure to verify that the function handles edge cases correctly:

  1. If newLayers is longer than the current storage array, this could lead to out-of-bounds access or unexpected behavior when assigning elements to non-existent indices.
  2. The pop loop logic could be optimized by directly calculating how many items to pop.

🏁 Script executed:

#!/bin/bash
# Check if there are any existing function calls to changeLayers to understand how it's used
rg -A 3 "changeLayers\(" --type sol

Length of output: 65


To locate the changeLayers implementation and its callers without filtering by file type, let’s retry a broader search:


🏁 Script executed:

#!/bin/bash
# Search for the function definition in LibMiddleware.sol
rg -n "function changeLayers" -A5 src/middleware/LibMiddleware.sol

# Search for any calls to changeLayers across the repo
rg -n "changeLayers\(" -A3 src

Length of output: 745


Ensure safe resizing in changeLayers and simplify popping logic

The current implementation can revert or leave the array in an inconsistent state when newLayers.length differs from the stored array length:

  • If newLayers.length > ls.length, writing to ls[i] out-of-bounds will revert. Either:
    • Require the new length not exceed the current length:
      require(newLayers.length <= ls.length, "Too many layers");
    • Or extend the storage array before assignment:
      for (uint256 i = ls.length; i < newLayers.length; i++) {
          ls.push(newLayers[i]);
      }
  • The pop loop’s condition recomputes ls.length each iteration, so it only removes about half of the extra elements. Instead, compute the difference once or use a while loop:
    // Option A: fixed-loop using diff
    uint256 diff = ls.length - newLayers.length;
    for (uint256 i = 0; i < diff; i++) {
        ls.pop();
    }
    
    // Option B: simple while-loop
    while (ls.length > newLayers.length) {
        ls.pop();
    }

Locations to update:

  • src/middleware/LibMiddleware.sol lines 33–40

contract MockRepository is IRepository {
using LibERC7744 for bytes32;
using LibSemver for LibSemver.Version;
using LibSemver for uint256
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing semicolon in code example.

The semicolon is missing after the using statement, which would cause a compilation error.

-    using LibSemver for uint256
+    using LibSemver for uint256;
📝 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
using LibSemver for uint256
using LibSemver for uint256;

Comment on lines +187 to +202
event MigrationContractAddedFromVersions(
bytes32 indexed distributionId,
uint256 indexed baseVersion,
LibSemver.requirements indexed semanticRequirement,
MigrationStrategy strategy,
bytes32 migrationHash,
bytes32 migrationId
);
event MigrationContractAddedToVersions(
bytes32 indexed distributionId,
bytes32 indexed migrationHash,
LibSemver.requirements indexed semanticRequirement,
MigrationStrategy strategy,
uint256 baseVersion,
bytes32 migrationId
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Invalid type name LibSemver.requirements in events

LibSemver.requirements does not exist; the correct struct is LibSemver.VersionRequirement. This produces a compilation error.

-        LibSemver.requirements indexed semanticRequirement,
+        LibSemver.VersionRequirement indexed semanticRequirement,

(Apply the same change to both MigrationContractAddedFromVersions and MigrationContractAddedToVersions.)

📝 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 MigrationContractAddedFromVersions(
bytes32 indexed distributionId,
uint256 indexed baseVersion,
LibSemver.requirements indexed semanticRequirement,
MigrationStrategy strategy,
bytes32 migrationHash,
bytes32 migrationId
);
event MigrationContractAddedToVersions(
bytes32 indexed distributionId,
bytes32 indexed migrationHash,
LibSemver.requirements indexed semanticRequirement,
MigrationStrategy strategy,
uint256 baseVersion,
bytes32 migrationId
);
event MigrationContractAddedFromVersions(
bytes32 indexed distributionId,
uint256 indexed baseVersion,
LibSemver.VersionRequirement indexed semanticRequirement,
MigrationStrategy strategy,
bytes32 migrationHash,
bytes32 migrationId
);
event MigrationContractAddedToVersions(
bytes32 indexed distributionId,
bytes32 indexed migrationHash,
LibSemver.VersionRequirement indexed semanticRequirement,
MigrationStrategy strategy,
uint256 baseVersion,
bytes32 migrationId
);

Comment on lines 3 to +8
import {IERC7746} from "../interfaces/IERC7746.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../interfaces/IRepository.sol";
import "../libraries/LibSemver.sol";
import "../versioning/LibSemver.sol";
import {IERC7746} from "../interfaces/IERC7746.sol";
import "../interfaces/IMigration.sol";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate import of IERC7746

IERC7746 is imported twice (lines 3 and 7). The second import is redundant and will break Solidity compilation (Identifier already declared).

-import {IERC7746} from "../interfaces/IERC7746.sol";
📝 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
import {IERC7746} from "../interfaces/IERC7746.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../interfaces/IRepository.sol";
import "../libraries/LibSemver.sol";
import "../versioning/LibSemver.sol";
import {IERC7746} from "../interfaces/IERC7746.sol";
import "../interfaces/IMigration.sol";
import {IERC7746} from "../interfaces/IERC7746.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "../interfaces/IRepository.sol";
import "../versioning/LibSemver.sol";
import "../interfaces/IMigration.sol";

Comment on lines +9 to +13
enum MigrationStrategy {
CALL,
DELEGATECALL,
REPOSITORY_MANGED
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Typo in enum constant: REPOSITORY_MANGEDREPOSITORY_MANAGED

The constant is missing the “A” in “MANAGED”. Besides hurting readability, this typo propagates to every contract that consumes the enum and forces callers to use the misspelled word forever.

-    REPOSITORY_MANGED
+    REPOSITORY_MANAGED

Remember to update every reference (MigrationStrategy.REPOSITORY_MANGED) in the code-base accordingly.

📝 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
enum MigrationStrategy {
CALL,
DELEGATECALL,
REPOSITORY_MANGED
}
enum MigrationStrategy {
CALL,
DELEGATECALL,
- REPOSITORY_MANGED
+ REPOSITORY_MANAGED
}

Comment on lines +299 to +336
function _addVersionMigration(
bytes32 distributionId,
LibSemver.VersionRequirement memory from,
LibSemver.VersionRequirement memory to,
bytes32 migrationHash,
MigrationStrategy strategy,
bytes memory distributorCalldata
) internal {
if (LibSemver.toUint256(versionRequirements[distributionId].version) == 0) {
revert UnversionedDistribution(distributionId);
}
if(from.version.major == to.version.major) {
require(strategy != MigrationStrategy.REPOSITORY_MANGED, "Repository managed migration is not allowed for minor version migrations");
}
bytes32 migrationId;
migrationId = keccak256(abi.encode(distributionId, migrationHash, strategy));
require(migrations[migrationId].distributionId == bytes32(0), MigrationAlreadyExists(migrationId));
require(distributionComponents[distributionId].distributionLocation != address(0), "Distribution not found");
require(from.version.major < to.version.major, "Major version mismatch");
migrations[migrationId] = MigrationPlan(from, to, migrationHash, strategy, distributorCalldata, distributionId);

emit MigrationContractAddedFromVersions(
distributionId,
from.version.toUint256(),
from.requirement,
strategy,
migrationHash,
migrationId
);
emit MigrationContractAddedToVersions(
distributionId,
migrationHash,
to.requirement,
strategy,
to.version.toUint256(),
migrationId
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential underflow & wrong loop bound in repository-managed migration

for (uint256 i = 0; i < migrationPlan.from.version.major - migrationPlan.to.version.major; i++)

from.major is strictly less than to.major (checked earlier), therefore the subtraction underflows and the loop reverts.
The intended range is probably (from.major+1 … to.major).

-for (uint256 i = 0; i < migrationPlan.from.version.major - migrationPlan.to.version.major; i++) {
-    bytes32 migrationHash = repository.getMigrationScript(uint64(migrationPlan.from.version.major + 1 + i));
+uint256 numSteps = migrationPlan.to.version.major - migrationPlan.from.version.major;
+for (uint256 i; i < numSteps; ++i) {
+    bytes32 migrationHash = repository.getMigrationScript(
+        uint64(migrationPlan.from.version.major + 1 + i)
+    );

Without this fix every repository-managed upgrade will revert.

Comment on lines +154 to +205
// External initializer is provided, delegatecall to it
// Contrary, if no initializer is provided, the distribution is expected to be self-initializing
bool externallyInitialized = distributionComponent.initializer == address(0);
bytes4 selector = IInitializer.initialize.selector;
address distributionLocation;
numAppInstances++;
uint256 appId = numAppInstances;

if (LibSemver.toUint256(versionRequirement.version) == 0) {
// Unversioned distribution, expect IDistribution
distributionLocation = distributionComponent.distributionLocation;
// Unversioned distributions are considered to be at version 0
appVersions[numAppInstances] = LibSemver.parse(0);
} else {
// Versioned distribution, expect IRepository
IRepository repository = IRepository(distributionComponent.distributionLocation);
IRepository.Source memory repoSource = repository.get(versionRequirement);
distributionLocation = repoSource.sourceId.getContainerOrThrow();
appVersions[numAppInstances] = repoSource.version;
}
try IDistribution(distributionLocation).instantiate(args) returns (
address[] memory _newAppComponents,
bytes32 _distributionName,
uint256 _distributionVersion
) {
newAppComponents = _newAppComponents;
distributionName = _distributionName;
distributionVersion = _distributionVersion;
} catch Error(string memory reason) {
revert(reason);
} catch Panic(uint errorCode) {
revert DistributionInstantiationPanic(errorCode);
} catch (bytes memory lowLevelData) {
revert DistributionInstantiationFailed(lowLevelData);
}

if (externallyInitialized) {
(bool success, bytes memory result) = address(distributionComponent.initializer).delegatecall(
abi.encodeWithSelector(selector, newAppComponents, args)
);
if (!success) {
if (result.length > 0) {
assembly {
let returndata_size := mload(result)
revert(add(32, result), returndata_size)
}
} else {
revert("initializer delegatecall failed without revert reason");
}
}
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Delegate-call executed when initializer is ZERO address

externallyInitialized is set to true when initializer == address(0) (lines 156-157).
Later, the delegate-call is executed only when externallyInitialized is true (lines 190-205).
Result: the contract tries to delegatecall to address 0 and reverts every time an initializer is not provided, while it never calls the real initializer when it is provided.

-// External initializer is provided, delegatecall to it
-// Contrary, if no initializer is provided, the distribution is expected to be self-initializing
-bool externallyInitialized = distributionComponent.initializer == address(0);
+// `hasExternalInitializer` is true only when an initializer contract is supplied
+// If no initializer is provided, the distribution must be self-initialising
+bool hasExternalInitializer = distributionComponent.initializer != address(0);
 ...
-if (externallyInitialized) {
+if (hasExternalInitializer) {

This single-line bug blocks every instantiation path without an initializer and prevents valid initializers from ever running.

📝 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
// External initializer is provided, delegatecall to it
// Contrary, if no initializer is provided, the distribution is expected to be self-initializing
bool externallyInitialized = distributionComponent.initializer == address(0);
bytes4 selector = IInitializer.initialize.selector;
address distributionLocation;
numAppInstances++;
uint256 appId = numAppInstances;
if (LibSemver.toUint256(versionRequirement.version) == 0) {
// Unversioned distribution, expect IDistribution
distributionLocation = distributionComponent.distributionLocation;
// Unversioned distributions are considered to be at version 0
appVersions[numAppInstances] = LibSemver.parse(0);
} else {
// Versioned distribution, expect IRepository
IRepository repository = IRepository(distributionComponent.distributionLocation);
IRepository.Source memory repoSource = repository.get(versionRequirement);
distributionLocation = repoSource.sourceId.getContainerOrThrow();
appVersions[numAppInstances] = repoSource.version;
}
try IDistribution(distributionLocation).instantiate(args) returns (
address[] memory _newAppComponents,
bytes32 _distributionName,
uint256 _distributionVersion
) {
newAppComponents = _newAppComponents;
distributionName = _distributionName;
distributionVersion = _distributionVersion;
} catch Error(string memory reason) {
revert(reason);
} catch Panic(uint errorCode) {
revert DistributionInstantiationPanic(errorCode);
} catch (bytes memory lowLevelData) {
revert DistributionInstantiationFailed(lowLevelData);
}
if (externallyInitialized) {
(bool success, bytes memory result) = address(distributionComponent.initializer).delegatecall(
abi.encodeWithSelector(selector, newAppComponents, args)
);
if (!success) {
if (result.length > 0) {
assembly {
let returndata_size := mload(result)
revert(add(32, result), returndata_size)
}
} else {
revert("initializer delegatecall failed without revert reason");
}
}
}
// `hasExternalInitializer` is true only when an initializer contract is supplied
// If no initializer is provided, the distribution must be self-initialising
bool hasExternalInitializer = distributionComponent.initializer != address(0);
bytes4 selector = IInitializer.initialize.selector;
address distributionLocation;
numAppInstances++;
uint256 appId = numAppInstances;
if (LibSemver.toUint256(versionRequirement.version) == 0) {
// Unversioned distribution, expect IDistribution
distributionLocation = distributionComponent.distributionLocation;
// Unversioned distributions are considered to be at version 0
appVersions[numAppInstances] = LibSemver.parse(0);
} else {
// Versioned distribution, expect IRepository
IRepository repository = IRepository(distributionComponent.distributionLocation);
IRepository.Source memory repoSource = repository.get(versionRequirement);
distributionLocation = repoSource.sourceId.getContainerOrThrow();
appVersions[numAppInstances] = repoSource.version;
}
try IDistribution(distributionLocation).instantiate(args) returns (
address[] memory _newAppComponents,
bytes32 _distributionName,
uint256 _distributionVersion
) {
newAppComponents = _newAppComponents;
distributionName = _distributionName;
distributionVersion = _distributionVersion;
} catch Error(string memory reason) {
revert(reason);
} catch Panic(uint errorCode) {
revert DistributionInstantiationPanic(errorCode);
} catch (bytes memory lowLevelData) {
revert DistributionInstantiationFailed(lowLevelData);
}
if (hasExternalInitializer) {
(bool success, bytes memory result) = address(distributionComponent.initializer).delegatecall(
abi.encodeWithSelector(selector, newAppComponents, args)
);
if (!success) {
if (result.length > 0) {
assembly {
let returndata_size := mload(result)
revert(add(32, result), returndata_size)
}
} else {
revert("initializer delegatecall failed without revert reason");
}
}
}

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

Successfully merging this pull request may close these issues.

1 participant