Skip to content

Conversation

@thedavidmeister
Copy link
Collaborator

@thedavidmeister thedavidmeister commented Dec 13, 2025

Motivation

Solution

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features

    • Added new clone- and beacon-based deployers to create vaults and receipts directly.
  • Refactor

    • Standardized vault configuration to a V2 shape and simplified initialization.
    • Migrated creation flows from factory/library helpers to explicit deployer contracts.
    • Removed legacy deployment script and obsolete creator helpers.
  • Tests

    • Added unit tests for deployers and updated tests/events to the V2 shapes.
  • Chores

    • Cleaned import paths and introduced deployer error signals.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Removed the legacy deployment script; introduced V2 config shapes and deployer contracts (beacon-set and clone deployers); refactored ReceiptVault to use ReceiptVaultConfigV2 and direct receipt addresses; migrated vaults, tests, and fixtures to new deployer-based flows and normalized import paths.

Changes

Cohort / File(s) Summary
Deployment script removal
\script/Deploy.sol``
Deleted the entire deployment script and its suite-routing/deployment flows.
Receipt vault core refactor
\src/abstract/ReceiptVault.sol``
Introduced ReceiptVaultConfigV2 (asset,name,symbol,receipt), made constructor parameterless, removed factory/implementation immutables and cloning logic; __ReceiptVault_init now accepts V2 and uses config.receipt directly.
ERC20 price-oracle vault V2
\src/concrete/vault/ERC20PriceOracleReceiptVault.sol``
Added ERC20PriceOracleReceiptVaultConfigV2 (priceOracle + nested ReceiptVaultConfigV2), updated initialize/event signatures to V2, removed legacy constructor and factory-based paths.
Offchain asset vault V2
\src/concrete/vault/OffchainAssetReceiptVault.sol``
Switched to OffchainAssetReceiptVaultConfigV2 with nested ReceiptVaultConfigV2; initialize now uses receiptVaultConfig, removed factory constructor and adjusted emitted init event payload.
Beacon-set deployer (new)
\src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol``
New contract deploying Receipt and OffchainAssetReceiptVault via UpgradeableBeacon + BeaconProxy; validates inputs, initializes implementations, emits Deployment event, and exposes beacon immutables.
Clone-based deployer (new)
\src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol``
New clone deployer using OpenZeppelin Clones: clones receipt + vault implementations, initializes them (writing receipt into config), emits deploy event, and returns the vault clone.
Deployer errors
\src/error/ErrDeployer.sol``
Added errors: ZeroReceiptImplementation, ZeroVaultImplementation, ZeroBeaconOwner, ZeroInitialAdmin, InitializeNonZeroReceipt(address), InitializeReceiptFailed, InitializeVaultFailed.
Removed factory test libs
\test/lib/LibERC20PriceOracleReceiptVaultCreator.sol`, `test/lib/LibOffchainAssetVaultCreator.sol``
Deleted library helpers that constructed/cloned via factory; tests migrated to deployer-based creation.
Test harness & helpers updated
\test/abstract/*`, `test/concrete/ConcreteReceiptVault.sol``
Tests refactored to use deployers (added I_DEPLOYER fields), removed factory() helper, updated createVault flows to call new* deployer methods, and migrated config types to V2.
New deployer unit tests
\test/src/concrete/deploy/.construct.t.sol`, `test/src/concrete/deploy/.new*.t.sol``
Added constructor and deployment-path tests for both deployers (zero-address reverts, successful deploys, event assertions).
Vault init & event tests migrated
\test/src/concrete/vault/*initialize.t.sol`, `test/src/concrete/vault/*initialize.t.sol``
Tests updated for V2 event payloads, decoding *ConfigV2, and changed sender expectations from factory to deployer.
createVault migration across tests
\test/src/**`` (many)
Replaced factory/lib instantiation with createVault helpers that use new deployers; updated event/config decoding and test harness inheritance where applicable.
Import path normalization
\test/src/concrete/**``
Numerous tests changed deep relative imports to root src/... module-style imports.
Gas snapshot and test renames
\.gas-snapshot``
Test suites and method names were reorganized/renamed to AssetTest variants and deployer-related suites; metrics re-baselined.
Minor test path/alias fixes
\test/src/concrete/receipt/*.t.sol``, other small tests
Adjusted import paths and small wiring changes (no functional logic changes).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Test as Test / Caller
    participant Deployer as Deployer (BeaconSet / CloneDeployer)
    participant Cloner as Clones / BeaconFactory
    participant ReceiptImpl as Receipt impl
    participant VaultImpl as Vault impl
    participant ReceiptProxy as Receipt Proxy
    participant VaultProxy as Vault Proxy

    Note over Test,Deployer: Beacon-set deploy flow (OffchainAsset)
    Test->>Deployer: newOffchainAssetReceiptVault(configV2)
    Deployer->>Cloner: deploy Receipt beacon (initialReceiptImpl)
    Deployer->>Cloner: deploy Vault beacon (initialVaultImpl)
    Deployer->>ReceiptProxy: deploy BeaconProxy(receiptBeacon)
    ReceiptProxy->>ReceiptImpl: delegatecall initialize(vaultAddr)
    Deployer->>VaultProxy: deploy BeaconProxy(vaultBeacon)
    VaultProxy->>VaultImpl: delegatecall initialize(configV2 with receipt)
    Deployer->>Test: emit Deployment(sender, vault, receipt) / return vault

    Note over Test,Deployer: Clone-deployer flow (ERC20PriceOracle)
    Test->>Deployer: newERC20PriceOracleReceiptVault(configV2)
    Deployer->>Cloner: clone Receipt implementation
    Deployer->>ReceiptImpl: call initialize(vaultPlaceholder)
    Deployer->>Cloner: clone Vault implementation
    Deployer->>VaultImpl: call initialize(configV2 with receipt)
    Deployer->>Test: emit CloneDeployerDeployment(sender, vault, receipt) / return vault
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • Areas needing focused review:
    • OffchainAssetReceiptVaultBeaconSetDeployer.sol — beacon creation, owner/initialOwner checks, BeaconProxy initialization order, and revert paths.
    • ERC20PriceOracleReceiptVaultCloneDeployer.sol — cloning correctness, ICLONEABLE_V2 init-success checks, and mutation of config.receipt before vault initialization.
    • src/abstract/ReceiptVault.sol — ensure manager/address validations remain correct after removing factory/clone logic and reading receipt directly.
    • Tests that decode init events — verify ABI tuple shapes for V2 events and updated sender expectations (factory → deployer).
    • ErrDeployer selectors — confirm tests assert the exact error selectors and messages.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'wip on proxy agnostic' is vague and does not clearly convey the main changes in the pull request, which involve a substantial refactoring from factory-based to deployer-based vault initialization patterns and removal of legacy deployment scripts. Replace with a more descriptive title that clearly identifies the primary change, such as 'Refactor vault deployment from factory pattern to deployer pattern' or 'Replace factory-based initialization with direct deployer implementation'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2025-12-13-proxy-agnostic

📜 Recent review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1bd791 and 2009cea.

📒 Files selected for processing (1)
  • src/abstract/ReceiptVault.sol (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
🔇 Additional comments (5)
src/abstract/ReceiptVault.sol (5)

16-18: LGTM on the re-export pattern.

The comment clearly documents why ICLONEABLE_V2_SUCCESS is imported and the lint directive is appropriate for intentional re-exports.


46-57: Clean struct definition for the proxy-agnostic design.

The ReceiptVaultConfigV2 struct appropriately captures the simplified initialization requirements where the receipt is pre-deployed rather than factory-cloned.


89-95: Documentation accurately reflects the new proxy-agnostic design.

Good clarity on deployer responsibilities and the trust model. This properly sets expectations for implementers.


124-126: Standard upgradeable contract pattern.

Parameterless constructor with _disableInitializers() is the correct pattern for proxy-based contracts.


139-151: Initialization logic is correct; validation responsibility properly delegated to deployers.

The manager check on lines 147-150 validates the receipt is properly bound to this vault. The direct assignment from config.receipt simplifies the initialization flow.

There is no explicit zero-address validation within this internal function—if config.receipt were address(0), the manager() call would revert with an unclear error. However, the deployer contracts (ERC20PriceOracleReceiptVaultCloneDeployer and OffchainAssetReceiptVaultBeaconSetDeployer) enforce that config.receiptVaultConfig.receipt is address(0) at entry, create the receipt, and then pass the non-zero address to this function. This design ensures __ReceiptVault_init always receives a valid receipt address.


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

Copy link
Contributor

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (1)

55-64: Fix incorrect field access pattern.

The code accesses config.receiptVaultConfig.vaultConfig.*, but ReceiptVaultConfigV2 does not have a nested vaultConfig field. The struct has flat fields: asset, name, symbol, and receipt.

Apply this diff to fix the field access:

-        assertEq(keccak256(bytes(vault.name())), keccak256(bytes(config.receiptVaultConfig.vaultConfig.name)));
-        assertEq(config.receiptVaultConfig.vaultConfig.name, shareName);
+        assertEq(keccak256(bytes(vault.name())), keccak256(bytes(config.receiptVaultConfig.name)));
+        assertEq(config.receiptVaultConfig.name, shareName);

-        assertEq(keccak256(bytes(vault.symbol())), keccak256(bytes(config.receiptVaultConfig.vaultConfig.symbol)));
-        assertEq(config.receiptVaultConfig.vaultConfig.symbol, shareSymbol);
+        assertEq(keccak256(bytes(vault.symbol())), keccak256(bytes(config.receiptVaultConfig.symbol)));
+        assertEq(config.receiptVaultConfig.symbol, shareSymbol);

-        assertEq(address(config.receiptVaultConfig.vaultConfig.asset), address(I_ASSET));
+        assertEq(address(config.receiptVaultConfig.asset), address(I_ASSET));
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (1)

44-118: Fix incorrect struct field access in event assertions

Lines 99, 102, 105 access a non-existent vaultConfig nested field. ReceiptVaultConfigV2 has asset, name, and symbol as direct fields:

  • Line 99: Change config.receiptVaultConfig.vaultConfig.name to config.receiptVaultConfig.name
  • Line 102: Change config.receiptVaultConfig.vaultConfig.symbol to config.receiptVaultConfig.symbol
  • Line 105: Change config.receiptVaultConfig.vaultConfig.asset to config.receiptVaultConfig.asset

Line 108 (config.receiptVaultConfig.receipt) is correct.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94d812e and f88a4a8.

📒 Files selected for processing (12)
  • script/Deploy.sol (1 hunks)
  • src/abstract/ReceiptVault.sol (2 hunks)
  • src/concrete/vault/ERC20PriceOracleReceiptVault.sol (4 hunks)
  • src/concrete/vault/OffchainAssetReceiptVault.sol (4 hunks)
  • test/abstract/ERC20PriceOracleReceiptVaultTest.sol (2 hunks)
  • test/concrete/ConcreteReceiptVault.sol (2 hunks)
  • test/lib/LibERC20PriceOracleReceiptVaultCreator.sol (2 hunks)
  • test/lib/LibOffchainAssetVaultCreator.sol (2 hunks)
  • test/src/abstract/ReceiptVault.decimals.t.sol (2 hunks)
  • test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (3 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (3 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (4 hunks)
🧰 Additional context used
🪛 GitHub Actions: Rainix CI
test/abstract/ERC20PriceOracleReceiptVaultTest.sol

[error] 59-59: Solidity compiler error: Member "ERC20PriceOracleReceiptVaultInitialized" not found or not visible after argument-dependent lookup. Command failed: forge build.

test/lib/LibERC20PriceOracleReceiptVaultCreator.sol

[error] 25-25: Solidity compiler error: Wrong argument count for struct constructor: 3 arguments given but expected 4. Command failed: forge build.

🔇 Additional comments (17)
src/abstract/ReceiptVault.sol (2)

55-66: LGTM! Struct definition is clear.

The refactoring from VaultConfig to ReceiptVaultConfigV2 with the addition of the receipt field is well-structured. The comments accurately describe each field.


158-170: LGTM! Initialization logic correctly updated.

The function signature and initialization logic properly use the new ReceiptVaultConfigV2 structure. The receipt validation at Line 166 ensures the receipt's manager matches the vault address.

test/concrete/ConcreteReceiptVault.sol (1)

8-8: LGTM! Test updated to use new config type.

The import and abi.decode statements correctly reference ReceiptVaultConfigV2, aligning with the struct rename.

Also applies to: 21-21

test/lib/LibOffchainAssetVaultCreator.sol (1)

7-8: LGTM! Helper correctly updated to V2 config types.

The imports and configuration construction properly use OffchainAssetReceiptVaultConfigV2 with the nested receiptVaultConfig field containing ReceiptVaultConfigV2.

Also applies to: 25-28

test/src/abstract/ReceiptVault.decimals.t.sol (1)

7-7: LGTM! Test correctly uses new config type.

The import and abi.encode statement properly reference ReceiptVaultConfigV2.

Also applies to: 26-26

script/Deploy.sol (1)

8-10: LGTM! Imports updated to V2 config types.

The imports correctly reference the renamed configuration structs: ERC20PriceOracleReceiptVaultConfigV2 and ReceiptVaultConfigV2.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (1)

7-7: LGTM! Config type correctly updated.

The import, variable declaration, and abi.decode statement properly use ERC20PriceOracleReceiptVaultConfigV2.

Also applies to: 33-33, 44-44

test/abstract/ERC20PriceOracleReceiptVaultTest.sol (1)

11-11: LGTM! Config type correctly updated.

The import and abi.decode statements properly use ERC20PriceOracleReceiptVaultConfigV2, and the field access correctly references config.receiptVaultConfig.receipt.

Also applies to: 61-63

test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (3)

19-23: Imports updated correctly.

The import changes align with the renamed config types.


411-418: Config construction is consistent within this file.

The OffchainAssetReceiptVaultConfigV2 construction matches the pattern used in testMintSimpleRealReceiptVault.


86-95: No action required. The ReceiptVaultConfigV2 struct is correctly defined with direct fields (asset, name, symbol, receipt) in src/abstract/ReceiptVault.sol, and the code at lines 88-91 correctly instantiates it with direct field initialization: ReceiptVaultConfigV2({asset: address(0), name: "Test Vault", symbol: "TVLT"}). This usage is consistent across the entire codebase. There is no nested vaultConfig field in ReceiptVaultConfigV2.

src/concrete/vault/OffchainAssetReceiptVault.sol (3)

5-12: Import changes align with the config type consolidation.

The import of ReceiptVaultConfigV2 replaces the previous VaultConfig and ReceiptVaultConfig types.


46-49: Struct definition is well-structured.

The OffchainAssetReceiptVaultConfigV2 struct cleanly nests the ReceiptVaultConfigV2 configuration.


304-331: Initialization logic correctly updated for new config structure.

The initialize function properly:

  • Decodes the config as OffchainAssetReceiptVaultConfigV2
  • Passes config.receiptVaultConfig to the base initialization
  • Validates asset address against config.receiptVaultConfig.asset
  • Emits the event with the correct config structure
src/concrete/vault/ERC20PriceOracleReceiptVault.sol (3)

5-11: Import changes are consistent with the refactor.

The import of ReceiptVaultConfigV2 aligns with the broader config type consolidation.


24-28: Struct definition follows the established pattern.

ERC20PriceOracleReceiptVaultConfigV2 correctly uses ReceiptVaultConfigV2 for the nested config field.


83-127: Event and initialization correctly migrated to V2.

The event signature, decoding, and initialization flow are properly updated to use the V2 config types. The pattern is consistent with OffchainAssetReceiptVault.sol.

Copy link
Contributor

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/abstract/ERC20PriceOracleReceiptVaultTest.sol (1)

17-32: Incomplete migration: I_DEPLOYER is referenced but not defined.

The createVault function at line 44 calls I_DEPLOYER.newERC20PriceOracleReceiptVault(...), but I_DEPLOYER is never declared or initialized in this contract. The constructor still uses the old factory-based pattern with I_FACTORY.

Additionally, ReceiptVaultConfigV2 is used at line 47 but not imported.

Apply a fix similar to OffchainAssetReceiptVaultTest.sol:

  1. Add import for the beacon deployer and ReceiptVaultConfigV2
  2. Declare I_DEPLOYER as an immutable
  3. Initialize it in the constructor with an ERC20PriceOracleReceiptVaultBeaconSetDeployer (or equivalent)

Alternatively, if the beacon deployer for ERC20PriceOracleReceiptVault doesn't exist yet, revert to the factory-based pattern until it's implemented:

     function createVault(IPriceOracleV2 priceOracle, string memory name, string memory symbol)
         internal
         returns (ERC20PriceOracleReceiptVault)
     {
-        return I_DEPLOYER.newERC20PriceOracleReceiptVault(
-            ERC20PriceOracleReceiptVaultConfigV2({
-                initialAdmin: address(this),
-                receiptVaultConfig: ReceiptVaultConfigV2({
-                    asset: address(I_ASSET),
-                    name: name,
-                    symbol: symbol,
-                    receipt: address(0)
-                }),
-                vaultPriceOracle: priceOracle
-            })
-        );
+        // TODO: Migrate to beacon deployer when available
+        return ERC20PriceOracleReceiptVault(
+            I_FACTORY.clone(
+                address(I_IMPLEMENTATION),
+                abi.encode(
+                    ERC20PriceOracleReceiptVaultConfigV2({
+                        priceOracle: priceOracle,
+                        receiptVaultConfig: ReceiptVaultConfigV2({
+                            asset: address(I_ASSET),
+                            name: name,
+                            symbol: symbol,
+                            receipt: address(I_RECEIPT_IMPLEMENTATION)
+                        })
+                    })
+                )
+            )
+        );
     }
test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (1)

66-66: Incorrect field name: config.priceOracle should be config.vaultPriceOracle.

Based on the V2 config structure shown in ERC20PriceOracleReceiptVaultTest.sol line 53, the field is named vaultPriceOracle, not priceOracle.

-        assertEq(address(config.priceOracle), address(vaultPriceOracle));
+        assertEq(address(config.vaultPriceOracle), address(vaultPriceOracle));
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (1)

47-115: Remove the view keyword from newOffchainAssetReceiptVault and add a return statement.

The function at src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol:28-48 is marked as view but performs state-modifying operations (creates new BeaconProxy contracts and calls initialize()). Additionally, the function declares a return type OffchainAssetReceiptVault but lacks a return statement. Change the function signature to remove view and add return offchainAssetReceiptVault; before the closing brace.

♻️ Duplicate comments (2)
test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (1)

36-48: Event signature mismatch: test looks for old event.

The test checks for the old event signature "ERC20PriceOracleReceiptVaultInitialized(address,(address,(address,(address,string,string))))" but the contract now emits ERC20PriceOracleReceiptVaultInitializedV2 with a different struct layout (ERC20PriceOracleReceiptVaultConfigV2).

Update the event signature to match the V2 event:

         for (uint256 i = 0; i < logs.length; i++) {
             if (
                 logs[i].topics[0]
-                    == keccak256(
-                        "ERC20PriceOracleReceiptVaultInitialized(address,(address,(address,(address,string,string))))"
-                    )
+                    == ERC20PriceOracleReceiptVault.ERC20PriceOracleReceiptVaultInitializedV2.selector
             ) {
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (1)

11-12: Duplicate import should be removed.

Line 12 contains a duplicate import of OffchainAssetReceiptVaultConfigV2, which is already imported on line 11.

Apply this diff to remove the duplicate:

 import {
     OffchainAssetReceiptVault,
     OffchainAssetReceiptVaultConfigV2,
-    OffchainAssetReceiptVaultConfigV2,
     ZeroInitialAdmin,
     NonZeroAsset
 } from "src/concrete/vault/OffchainAssetReceiptVault.sol";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f88a4a8 and a1bae92.

📒 Files selected for processing (14)
  • script/Deploy.sol (0 hunks)
  • src/abstract/ReceiptVault.sol (3 hunks)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • src/concrete/vault/ERC20PriceOracleReceiptVault.sol (4 hunks)
  • src/concrete/vault/OffchainAssetReceiptVault.sol (4 hunks)
  • test/abstract/ERC20PriceOracleReceiptVaultTest.sol (3 hunks)
  • test/abstract/OffchainAssetReceiptVaultTest.sol (1 hunks)
  • test/concrete/ConcreteReceiptVault.sol (1 hunks)
  • test/lib/LibERC20PriceOracleReceiptVaultCreator.sol (0 hunks)
  • test/lib/LibOffchainAssetVaultCreator.sol (0 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (4 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (4 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.mint.t.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (0 hunks)
💤 Files with no reviewable changes (4)
  • script/Deploy.sol
  • test/lib/LibOffchainAssetVaultCreator.sol
  • test/lib/LibERC20PriceOracleReceiptVaultCreator.sol
  • test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
🔇 Additional comments (13)
test/src/concrete/vault/OffchainAssetReceiptVault.mint.t.sol (1)

144-144: LGTM!

The vault creation now uses the simplified createVault helper method from the base test class, which aligns with the migration to the beacon-based deployment pattern.

test/abstract/OffchainAssetReceiptVaultTest.sol (2)

27-38: LGTM!

The constructor correctly initializes the beacon-based deployer with proper implementations and ownership configuration. This aligns with the migration from factory-based to beacon-based deployment.


40-55: LGTM!

The createVault helper correctly uses the new OffchainAssetReceiptVaultConfigV2 structure with:

  • asset: address(0) - correct for offchain assets
  • receipt: address(0) - signals the deployer to create a new receipt

This properly delegates vault creation to the beacon-based deployer.

test/concrete/ConcreteReceiptVault.sol (1)

15-20: LGTM!

The test contract correctly migrates to ReceiptVaultConfigV2 for initialization, properly decoding and passing the config to the base __ReceiptVault_init function.

test/abstract/ERC20PriceOracleReceiptVaultTest.sol (1)

66-70: Event selector and config type updated correctly.

The event selector has been updated to ERC20PriceOracleReceiptVaultInitializedV2 and the config decoding now uses ERC20PriceOracleReceiptVaultConfigV2, which aligns with the V2 migration.

However, this code will not compile until the I_DEPLOYER issue in createVault is resolved.

src/concrete/vault/OffchainAssetReceiptVault.sol (2)

46-49: LGTM!

The OffchainAssetReceiptVaultConfigV2 struct correctly nests ReceiptVaultConfigV2, aligning with the broader config consolidation in this PR.


302-329: LGTM!

The initialization logic correctly:

  1. Decodes the V2 config structure
  2. Passes config.receiptVaultConfig to the base initializer
  3. Validates that asset is zero (required for offchain vaults)
  4. Emits the V2 event with the proper config structure
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (1)

34-44: LGTM! Config structure correctly uses the V2 pattern.

The nested config structure properly uses ReceiptVaultConfigV2 within OffchainAssetReceiptVaultConfigV2, and the field name receiptVaultConfig is correct.

src/abstract/ReceiptVault.sol (3)

55-66: LGTM! Config struct consolidation simplifies the initialization interface.

The new ReceiptVaultConfigV2 struct cleanly consolidates configuration and includes the receipt field, replacing the previous two-struct approach. This aligns with the proxy-agnostic refactoring objectives.


132-134: Constructor correctly simplified for proxy-agnostic deployment.

Removing factory and receipt implementation parameters from the constructor is consistent with the new pattern where the receipt is pre-created and passed during initialization rather than cloned.


147-159: The deployment sequence already guarantees receipt manager is set before vault initialization.

The OffchainAssetReceiptVaultBeaconSetDeployer explicitly controls initialization order: it calls receipt.initialize(vault_address) (which sets the manager) before calling vault.initialize(). When __ReceiptVault_init executes, the receipt's manager is already correctly configured, so the WrongManager validation will pass by design. This is not a concern requiring external verification.

src/concrete/vault/ERC20PriceOracleReceiptVault.sol (2)

6-28: LGTM! Config struct updated consistently to V2 pattern.

The changes correctly adopt ReceiptVaultConfigV2 and nest it within ERC20PriceOracleReceiptVaultConfigV2. Field naming and structure align with the refactoring objectives.


104-125: LGTM! Initialization flow correctly updated for V2 config.

The decoding, parent initialization call, and event emission all correctly use the V2 config structure. Line 111 properly passes config.receiptVaultConfig to __ReceiptVault_init.

Copy link
Contributor

@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: 3

♻️ Duplicate comments (1)
test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (1)

37-47: Update the event signature to match the V2 event.

The hardcoded keccak256 hash uses the old event signature without the V2 suffix and missing the receipt field in the struct. This will cause the test to fail when searching for the event log.

Apply this diff to use the correct event selector:

         for (uint256 i = 0; i < logs.length; i++) {
             if (
                 logs[i].topics[0]
-                    == keccak256(
-                        "ERC20PriceOracleReceiptVaultInitialized(address,(address,(address,(address,string,string))))"
-                    )
+                    == ERC20PriceOracleReceiptVault.ERC20PriceOracleReceiptVaultInitializedV2.selector
             ) {

Alternatively, use the correct V2 event signature with the receipt field:

         for (uint256 i = 0; i < logs.length; i++) {
             if (
                 logs[i].topics[0]
                     == keccak256(
-                        "ERC20PriceOracleReceiptVaultInitialized(address,(address,(address,(address,string,string))))"
+                        "ERC20PriceOracleReceiptVaultInitializedV2(address,(address,(address,string,string,address)))"
                     )
             ) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a1bae92 and b082415.

📒 Files selected for processing (5)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
  • test/abstract/ERC20PriceOracleReceiptVaultTest.sol (3 hunks)
  • test/abstract/OwnerFreezableOwnerFreezeUntilTest.sol (1 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (3 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
🔇 Additional comments (7)
test/abstract/ERC20PriceOracleReceiptVaultTest.sol (4)

11-12: LGTM!

The import updates correctly introduce the V2 configuration types and the new clone deployer, aligning with the refactored vault creation approach.

Also applies to: 17-20


27-27: LGTM!

The deployer initialization is correctly structured, with appropriate implementation addresses provided to the deployer configuration.

Also applies to: 31-31, 34-39


52-63: LGTM!

The vault creation logic correctly uses the new deployer-based approach with ERC20PriceOracleReceiptVaultConfigV2. Setting receipt: address(0) is appropriate since the deployer creates and assigns the receipt contract.


74-74: LGTM!

The event handling correctly uses ERC20PriceOracleReceiptVaultInitializedV2.selector and decodes to ERC20PriceOracleReceiptVaultConfigV2, with proper nested field access for the receipt address. This resolves the issue previously flagged in past reviews.

Also applies to: 76-78

src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1)

21-24: LGTM!

The constructor correctly initializes the immutable implementation addresses from the provided configuration.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.construct.t.sol (2)

7-7: LGTM!

The import and configuration type correctly updated to use ERC20PriceOracleReceiptVaultConfigV2.

Also applies to: 33-33


44-44: LGTM!

The event decoding, field access, and assertions correctly reflect the V2 configuration structure. The msgSender assertion to address(0) is appropriate for the initialization context.

Also applies to: 52-52, 55-61

Copy link
Contributor

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/abstract/OffchainAssetReceiptVaultTest.sol (1)

55-71: Missing assertion when event is not found.

Unlike ERC20PriceOracleReceiptVaultTest.getReceipt() which asserts eventFound, this function silently returns address(0) when the event isn't found. This could mask test failures.

         }
         // Return an receipt contract
+        require(eventFound, "OffchainAssetReceiptVaultInitializedV2 event log not found");
         return ReceiptContract(receiptAddress);
     }
♻️ Duplicate comments (2)
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

30-30: Consider using a custom error for gas efficiency and consistency.

The codebase uses custom errors elsewhere (e.g., ZeroInitialAdmin, NonZeroAsset). For consistency:

+error NonZeroReceiptAddress();
+
 contract OffchainAssetReceiptVaultBeaconSetDeployer {
     // ...
-        require(config.receiptVaultConfig.receipt == address(0), "Receipt address must be zero");
+        if (config.receiptVaultConfig.receipt != address(0)) {
+            revert NonZeroReceiptAddress();
+        }
src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1)

27-49: LGTM!

The previously flagged issues (incorrect view modifier and missing return statement) have been addressed. The deployment flow is correct: clone receipt → clone vault → initialize receipt with vault reference → set receipt in config → initialize vault → return vault.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b082415 and 16c5aa6.

📒 Files selected for processing (13)
  • src/abstract/ReceiptVault.sol (4 hunks)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • src/concrete/vault/ERC20PriceOracleReceiptVault.sol (4 hunks)
  • src/concrete/vault/OffchainAssetReceiptVault.sol (4 hunks)
  • test/abstract/ERC20PriceOracleReceiptVaultTest.sol (3 hunks)
  • test/abstract/OffchainAssetReceiptVaultAuthorizerV1Test.sol (2 hunks)
  • test/abstract/OffchainAssetReceiptVaultTest.sol (1 hunks)
  • test/concrete/ConcreteReceiptVault.sol (1 hunks)
  • test/src/abstract/ReceiptVault.decimals.t.sol (2 hunks)
  • test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (2 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.ierc165.t.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
🔇 Additional comments (20)
test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (1)

21-26: Clean simplification of test setup.

The constructor now uses the createVault helper, which removes the boilerplate factory and implementation setup. This aligns well with the broader refactor to beacon/deployer-based vault creation.

test/abstract/OffchainAssetReceiptVaultAuthorizerV1Test.sol (1)

5-5: LGTM!

The inheritance change to OffchainAssetReceiptVaultTest provides access to the createVault helper and other test utilities, aligning with the broader test refactor pattern.

Also applies to: 15-15

test/src/concrete/vault/OffchainAssetReceiptVault.ierc165.t.sol (1)

12-27: LGTM!

Using the no-argument constructor to instantiate the implementation directly is appropriate for interface support testing. The supportsInterface method works on the implementation contract, and this approach correctly validates that all expected interfaces (IERC165, IReceiptManagerV2, IReceiptVaultV3, ICloneableV2, IAuthorizeV1) are supported.

test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (2)

61-61: LGTM!

The createVault helper simplifies the test setup while maintaining the same test functionality.


377-377: LGTM!

Consistent use of the createVault helper across tests.

test/concrete/ConcreteReceiptVault.sol (1)

5-13: LGTM - Clean migration to ReceiptVaultConfigV2.

The contract correctly migrates from VaultConfig to ReceiptVaultConfigV2 for initialization. The simplified structure aligns with the PR's goal of making vaults proxy-agnostic by including the receipt address directly in the config rather than cloning it. The struct contains all necessary fields (asset, name, symbol, receipt) and the initialization flow is correct.

test/src/abstract/ReceiptVault.decimals.t.sol (1)

20-46: LGTM! Clone-based deployment pattern is correctly implemented.

The test properly demonstrates the new clone-based initialization:

  1. Receipt is initialized with the vault address
  2. Vault is initialized with ReceiptVaultConfigV2 containing the receipt address
  3. The circular reference between receipt and vault is established correctly

The fuzz test with assetDecimals validates decimal pass-through from the underlying asset.

test/abstract/OffchainAssetReceiptVaultTest.sol (1)

25-36: LGTM! Beacon-based deployer integration is correct.

The constructor properly initializes the beacon set deployer with the receipt and vault implementations. The deployer will manage beacon proxy creation for both contracts.

src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

1-48: LGTM! Previous review issues have been addressed.

The deployer correctly:

  1. Uses IBeacon types for beacon references (addressing past review)
  2. Removes view modifier (addressing past review)
  3. Returns the created vault (addressing past review)
  4. Properly initializes receipt before vault, establishing the manager relationship
test/abstract/ERC20PriceOracleReceiptVaultTest.sol (2)

26-37: LGTM! Clone deployer integration is correct.

The constructor properly initializes the clone deployer with receipt and vault implementation addresses.


62-83: LGTM! Event handling is properly implemented.

The getReceipt function correctly:

  1. Uses the V2 event selector (addressing past review)
  2. Decodes the V2 config struct
  3. Asserts that the event was found before returning
src/abstract/ReceiptVault.sol (3)

46-57: LGTM! Consolidated config struct simplifies initialization.

ReceiptVaultConfigV2 combines all required fields including receipt, enabling the deployer pattern where receipt is created externally and passed in.


123-125: LGTM! Parameterless constructor enables proxy patterns.

Disabling initializers in the constructor is the correct pattern for contracts intended to be used as implementations behind proxies (clones, beacon proxies, etc.).


138-150: LGTM! Critical manager validation ensures correct initialization.

The check at lines 146-149 verifies that the receipt contract was initialized with this vault as its manager. This prevents misconfigured deployments where the receipt and vault are not properly linked.

src/concrete/vault/OffchainAssetReceiptVault.sol (3)

5-11: LGTM!

Import changes correctly bring in ReceiptVaultConfigV2 and ICLONEABLE_V2_SUCCESS for the V2 config migration.


45-48: LGTM!

The struct field type update to ReceiptVaultConfigV2 aligns with the V2 config migration across the codebase.


301-328: LGTM!

The initialization logic correctly migrates to V2 config:

  • Decodes OffchainAssetReceiptVaultConfigV2
  • Passes config.receiptVaultConfig to __ReceiptVault_init
  • Validates asset at the correct nested path
  • Event emission with the V2 config type
src/concrete/vault/ERC20PriceOracleReceiptVault.sol (3)

5-5: LGTM!

Import correctly updated to use ReceiptVaultConfigV2 for the V2 config migration.


18-22: LGTM!

The struct is correctly updated with V2 naming and uses ReceiptVaultConfigV2 for the nested config.


98-119: LGTM!

The initialization logic correctly migrates to V2 config:

  • Decodes ERC20PriceOracleReceiptVaultConfigV2
  • Passes config.receiptVaultConfig to __ReceiptVault_init
  • Emits the renamed V2 event with proper config type

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (1)

11-12: Duplicate import still present.

OffchainAssetReceiptVaultConfigV2 is imported twice on consecutive lines (lines 11 and 12). This was already flagged in a previous review.

Apply this diff to remove the duplicate:

 import {
     OffchainAssetReceiptVault,
     OffchainAssetReceiptVaultConfigV2,
-    OffchainAssetReceiptVaultConfigV2,
     ZeroInitialAdmin,
     NonZeroAsset
 } from "src/concrete/vault/OffchainAssetReceiptVault.sol";
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

26-41: Use custom error for non‑zero receipt and document ACL on deployer

  • Line 30: For gas and consistency with the rest of the codebase, prefer a custom error over a string require, e.g.:
error NonZeroReceiptAddress();

function newOffchainAssetReceiptVault(OffchainAssetReceiptVaultConfigV2 memory config)
    external
    returns (OffchainAssetReceiptVault)
{
    if (config.receiptVaultConfig.receipt != address(0)) {
        revert NonZeroReceiptAddress();
    }
    // ...
}
  • The function is permissionless (external with no modifier). If that’s not intentional, add the appropriate access control (e.g., onlyOwner) or at least document in NatSpec that anyone can deploy new off‑chain asset vaults via this contract.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c38275f and f5d0b6b.

📒 Files selected for processing (2)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
🔇 Additional comments (5)
test/src/concrete/vault/OffchainAssetReceiptVault.construct.t.sol (4)

22-43: Well-structured zero admin validation test.

The manual beacon proxy setup and nested config structure correctly test that initialAdmin cannot be address(0). The test appropriately creates the receipt first and references it in the vault config.


67-135: Comprehensive initialization event validation.

The test correctly validates the initialization event and all configuration fields. The msgSender being address(0) (lines 112, 129) is expected behavior when initialization occurs through proxy construction. The nested receiptVaultConfig field access pattern aligns with the V2 config structure.


138-169: LGTM!

The test correctly validates creating multiple vaults with different configurations and admins. All assertions properly verify the vault properties including receipt manager relationships and ownership.


53-64: The test is correctly structured and doesn't have a validation order fragility issue. The receipt: address(0) is a required parameter for the deployer, which internally creates and manages the Receipt instance. No changes needed.

Likely an incorrect or invalid review comment.

src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

10-24: Config struct and beacon setup look solid

The deployer config and use of immutable IBeacon references in the constructor are clean and type‑safe. The pattern of wiring both beacons to the same initialOwner is reasonable as long as your upgrade policy expects a single admin for both receipt and vault implementations.

thedavidmeister and others added 2 commits December 15, 2025 16:40
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
test/src/concrete/vault/OffchainAssetReceiptVault.initialize.t.sol (1)

11-12: Duplicate import of OffchainAssetReceiptVaultConfigV2 persists.

This was flagged in a previous review and marked as addressed, but the duplicate import still appears on consecutive lines 11-12.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f5d0b6b and 6de6b78.

📒 Files selected for processing (5)
  • .gas-snapshot (4 hunks)
  • test/abstract/OffchainAssetReceiptVaultTest.sol (1 hunks)
  • test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (2 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.initialize.t.sol (2 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.initialize.t.sol (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
🔇 Additional comments (12)
.gas-snapshot (1)

1-299: Auto-generated gas snapshot looks structurally consistent

This appears to be a standard Foundry .gas-snapshot update reflecting the broader vault/test refactor (renamed test suites, updated μ/~ metrics). The format is intact across all changed lines (proper runs/gas, μ, ~ fields, and balanced parentheses), and nothing suggests manual corruption or malformed entries. As long as this file matches the latest forge snapshot output, it’s fine to keep as-is.

test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (2)

61-61: LGTM!

The vault creation is simplified to use the createVault helper from the base test class, aligning with the new deployer-based pattern introduced in this PR.


377-377: LGTM!

Consistent use of the createVault helper for vault instantiation.

test/abstract/OffchainAssetReceiptVaultTest.sol (4)

12-20: LGTM!

Imports are correctly updated to support the new beacon-based deployment pattern with Clones for authorizer cloning and the new OffchainAssetReceiptVaultBeaconSetDeployer.


28-39: LGTM!

The constructor correctly initializes the OffchainAssetReceiptVaultBeaconSetDeployer with the receipt and vault implementations.


41-63: LGTM!

The createVault function correctly implements the new deployment pattern:

  1. Deploys vault via the beacon deployer
  2. Clones and initializes the authorizer
  3. Sets the authorizer on the vault under the admin context

65-81: LGTM!

The getReceipt function correctly parses the V2 event structure. The eventFound flag is tracked but the function returns ReceiptContract(address(0)) if not found, which is acceptable for test utility code since the calling test would fail on subsequent operations anyway.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.initialize.t.sol (2)

15-55: LGTM!

The test function is correctly updated to validate the V2 event emission with proper event topic signature and config decoding.


58-74: LGTM!

Assertions correctly access config properties through the new receiptVaultConfig path, consistent with the V2 struct layout.

test/src/concrete/vault/OffchainAssetReceiptVault.initialize.t.sol (3)

20-43: LGTM!

The test correctly creates beacon proxies directly to test the ZeroInitialAdmin revert condition during initialization, bypassing the deployer's validation.


45-65: LGTM!

The test correctly validates the NonZeroAsset revert condition using the deployer with the V2 config structure.


67-140: LGTM!

The test comprehensively validates the V2 initialization event emission and the AuthorizerSet event. All assertions correctly use the receiptVaultConfig path for config property access.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6de6b78 and c2dc4b1.

📒 Files selected for processing (1)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
🔇 Additional comments (3)
src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (3)

1-10: LGTM!

Imports are appropriate for the clone deployer pattern. Using OpenZeppelin's Clones library is the correct approach for minimal proxy deployments.


12-16: LGTM!

The config struct is clean and minimal, capturing only the necessary implementation addresses.


22-27: LGTM!

The constructor properly validates that implementation addresses are non-zero before storing them.

Copy link
Contributor

@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: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2dc4b1 and 70b89cc.

📒 Files selected for processing (5)
  • .gas-snapshot (4 hunks)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • src/error/ErrDeployer.sol (1 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.initialize.t.sol (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
🔇 Additional comments (5)
.gas-snapshot (1)

1-299: Gas snapshot updates reflect test refactoring.

The re-baselined metrics and renamed test groups align with the V2 config migration and deployer-based test flows described in the PR summary. The changes appear consistent with the structural refactoring.

Minor: The file is missing a newline at the end (Line 299), which may cause git warnings.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.initialize.t.sol (2)

13-22: Test correctly updated for V2 initialization event.

The event topic hash and config type changes properly align with the V2 configuration structure. The test captures the new event signature with nested config fields.


39-72: Assertions comprehensively validate V2 initialization.

The test correctly verifies:

  • Event sender is the deployer
  • Vault name/symbol match config
  • Asset is correctly set
  • Receipt is non-zero and linked to vault
  • Price oracle is configured
  • Receipt's manager is the vault
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (2)

40-63: Deployment flow correctly mirrors the clone deployer pattern.

The beacon proxy deployment follows the same correct initialization order:

  1. Create both proxies (uninitialized)
  2. Initialize receipt with vault as manager
  3. Update config with receipt address
  4. Initialize vault with complete config
  5. Return the vault

Custom errors are used consistently for all failure cases.


27-38: No validation needed for initialOwner. In OpenZeppelin v5, UpgradeableBeacon automatically reverts if initialOwner is address(0) through its inherited Ownable constructor, so the misconfiguration scenario described is already prevented.

Comment on lines 1 to 19
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;

/// Thrown when constructing a deployer with a zero receipt implementation
/// address.
error ZeroReceiptImplementation();

/// Thrown when constructing a deployer with a zero vault implementation address.
error ZeroVaultImplementation();

/// Thrown when attempting to initialize with a non-zero receipt address.
/// @param receipt The receipt address that is not zero in the initialization
/// config.
error InitializeNonZeroReceipt(address receipt);

error InitializeReceiptFailed();

error InitializeVaultFailed();
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 15, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Error definitions are well-structured.

The custom errors provide clear, gas-efficient revert reasons for deployer validation failures. The InitializeNonZeroReceipt error helpfully includes the problematic address for debugging.

Minor observations:

  1. InitializeReceiptFailed and InitializeVaultFailed (lines 17-19) lack NatSpec comments unlike the other errors.
  2. The pragma uses ^0.8.25 while other files in this PR use =0.8.25. Consider aligning for consistency.
🤖 Prompt for AI Agents
In src/error/ErrDeployer.sol lines 1 to 19, update the file to add NatSpec
comment lines for the InitializeReceiptFailed and InitializeVaultFailed errors
(mirroring the style used for the other custom errors) and change the pragma
from ^0.8.25 to =0.8.25 to match the project's Solidity version pinning; keep
the comments concise, describing when each error is thrown (receipt
initialization failure and vault initialization failure respectively).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

check

Copy link
Contributor

Choose a reason for hiding this comment

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

@thedavidmeister, noted! Let me know if you'd like any clarification on the suggestions.

Copy link
Contributor

@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: 1

♻️ Duplicate comments (1)
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

41-63: Consider emitting a deployment event for easier off‑chain tracking

The deployment flow and error handling look correct: zero‑receipt guard, typed beacon proxies, and explicit ICLONEABLE_V2_SUCCESS checks with custom errors.

For observability and indexing, consider emitting an event after both initializations succeed, e.g.:

event OffchainAssetReceiptVaultDeployed(
    address indexed vault,
    address indexed receipt,
    address indexed deployer
);

// after successful initializes:
emit OffchainAssetReceiptVaultDeployed(
    address(offchainAssetReceiptVault),
    address(receipt),
    msg.sender
);

This makes it much easier to discover and track vault/receipt pairs off‑chain, especially now that creation is beacon‑driven rather than factory‑based.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 474b280 and 45f15bf.

📒 Files selected for processing (1)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
🔇 Additional comments (1)
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

18-27: Config struct and typed beacon references look solid

Using concrete Receipt/OffchainAssetReceiptVault types in the config and IBeacon-typed immutable references keeps the deployer API clear and type-safe while still exposing the underlying beacons.

Copy link
Contributor

@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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc77811 and 6784a38.

📒 Files selected for processing (1)
  • test/src/concrete/vault/OffchainAssetReceiptVault.initialize.t.sol (4 hunks)
🔇 Additional comments (2)
test/src/concrete/vault/OffchainAssetReceiptVault.initialize.t.sol (2)

96-106: Verify OffchainAssetReceiptVaultInitializedV2 event definition vs decoding assumptions.

This loop hashes "OffchainAssetReceiptVaultInitializedV2(address,(address,(address,string,string,address)))" and then decodes logs[i].data as (address, OffchainAssetReceiptVaultConfigV2). This is correct only if:

  • The event has exactly two parameters with those types, and
  • The first address (msgSender) is not indexed (otherwise its value lives in topics[1], not in data).

If msgSender is indexed in the event, you should instead:

  • Read msgSender from logs[i].topics[1], and
  • Decode only the config struct from logs[i].data.

Please double‑check the event definition in OffchainAssetReceiptVault.sol and adjust either the signature string or the decoding accordingly if it diverges.


66-139: Good coverage of new initialization and authorizer event semantics.

The new testOffchainAssetReceiptVaultInitializedV2Event thoroughly validates:

  • The full nested receiptVaultConfig inside OffchainAssetReceiptVaultConfigV2.
  • That the deployer is recorded as msgSender in the init event.
  • That the AuthorizerSet event wires alice to vault.authorizer() and that the vault is set as the receipt manager.

This is a solid regression test around the new deployer/beacon-based initialization path.

Copy link
Contributor

@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: 1

♻️ Duplicate comments (3)
test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (1)

377-377: LGTM!

Consistent usage of the createVault helper. This test uses a real vault instance without mocking, which provides good integration test coverage.

src/error/ErrDeployer.sol (1)

3-24: Custom errors are solid; minor consistency nits on pragma and ZeroBeaconOwner

The error set cleanly captures deployer failure modes, and the added NatSpec on InitializeReceiptFailed / InitializeVaultFailed is helpful.

Two small follow‑ups:

  1. Line [3]: pragma uses ^0.8.25 while the rest of this PR appears to pin =0.8.25. Consider aligning for consistency and to avoid accidental compiler drift.
  2. Lines [12-14]: ZeroBeaconOwner is documented as “when the beacon owner is set to the zero address”. In OffchainAssetReceiptVaultBeaconSetDeployer, it’s currently used for config.initialAdmin == address(0) rather than the beacon owner itself. Either:
    • Add a dedicated check for config.initialOwner == address(0) in that constructor so this error actually covers the beacon-owner case, and use a separate error (e.g. ZeroInitialAdmin) for the admin; or
    • Broaden/rename this error’s docs if you intend it to cover both beacon owner and admin being zero.
src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1)

31-42: Still no explicit guard on zero beacon owner; ZeroBeaconOwner semantics are inconsistent

The constructor validates implementation addresses but still allows config.initialOwner == address(0) to flow into UpgradeableBeacon. In that case OZ’s Ownable will revert with its own error instead of your deployer‑specific ZeroBeaconOwner, which contradicts the error’s NatSpec (“when the beacon owner is set to the zero address”). At the same time, ZeroBeaconOwner is currently used in newOffchainAssetReceiptVault for config.initialAdmin == address(0), which isn’t about the beacon owner.

I’d strongly recommend:

  • Guarding initialOwner explicitly in the constructor so the intended custom error is actually used:
     constructor(OffchainAssetReceiptVaultBeaconSetDeployerConfig memory config) {
         if (address(config.initialReceiptImplementation) == address(0)) {
             revert ZeroReceiptImplementation();
         }
         if (address(config.initialOffchainAssetReceiptVaultImplementation) == address(0)) {
             revert ZeroVaultImplementation();
         }
+
+        if (config.initialOwner == address(0)) {
+            revert ZeroBeaconOwner();
+        }
 
         I_RECEIPT_BEACON = new UpgradeableBeacon(address(config.initialReceiptImplementation), config.initialOwner);
         I_OFFCHAIN_ASSET_RECEIPT_VAULT_BEACON =
             new UpgradeableBeacon(address(config.initialOffchainAssetReceiptVaultImplementation), config.initialOwner);
     }
  • Clarifying how you want to handle config.initialAdmin == address(0) in Line [52]: either keep that check but introduce/use a more precise error for an invalid admin, or broaden the meaning/doc of ZeroBeaconOwner if you really intend to reuse it for both beacon owner and admin.

Also applies to: 52-52

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6784a38 and 9978d7a.

📒 Files selected for processing (12)
  • .gas-snapshot (4 hunks)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • src/error/ErrDeployer.sol (1 hunks)
  • test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (3 hunks)
  • test/src/concrete/receipt/Receipt.metadata.t.sol (1 hunks)
  • test/src/concrete/receipt/Receipt.t.sol (1 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.mint.t.sol (1 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.redeem.t.sol (1 hunks)
  • test/src/concrete/vault/ERC20PriceOracleReceiptVault.withdraw.t.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.handler.t.sol (1 hunks)
  • test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
🔇 Additional comments (13)
test/src/concrete/receipt/Receipt.metadata.t.sol (1)

7-7: LGTM! Import path normalized.

The import path has been updated from a deep relative path to a root-relative path, improving maintainability and consistency with the broader project refactoring.

test/src/concrete/receipt/Receipt.t.sol (1)

5-5: LGTM! Import paths normalized.

Both import paths have been updated from deep relative paths to root-relative paths, improving code maintainability and consistency with the broader project refactoring.

Also applies to: 9-9

.gas-snapshot (1)

1-299: Auto-generated gas snapshot reflects test refactoring.

This file is generated by forge snapshot and captures gas metrics for all tests. The changes align with the broader PR refactoring (V2 config structures, deployer patterns, namespace reorganization). Gas values appear within expected ranges for vault operations.

Consider committing this file only after the PR stabilizes to avoid churn from intermediate test changes.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.mint.t.sol (1)

5-15: LGTM! Import path refactoring is clean and consistent.

The import paths have been successfully updated from relative paths to module-style aliases (e.g., "src/..."), which improves maintainability and aligns with the broader proxy-agnostic refactor. No behavioral changes to the test logic.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.withdraw.t.sol (1)

5-14: LGTM! Import path refactoring is clean and consistent.

The import paths have been successfully updated from relative paths to module-style aliases, matching the same pattern applied across the test suite. No behavioral changes to the test logic.

test/src/concrete/vault/ERC20PriceOracleReceiptVault.redeem.t.sol (1)

5-14: LGTM! Import path refactoring is clean and consistent.

The import paths have been successfully updated from relative paths to module-style aliases, maintaining consistency across all three test files. No behavioral changes to the test logic.

test/src/concrete/vault/OffchainAssetReceiptVault.ownerFreezeUntil.t.sol (2)

9-17: Updated imports correctly align with new vault and receipt shapes

The switch to importing OffchainAssetReceiptVault, IReceiptV3, and the role constants from OffchainAssetReceiptVaultAuthorizerV1 matches how they’re used in the tests and keeps this file consistent with the V2/proxy‑agnostic setup. No issues from a correctness or style perspective here.


25-26: Constructor’s use of createVault helper looks sound

Using createVault(sAlice, "vault", "VLT") and storing the result as IOwnerFreezableV1 cleanly abstracts away the deploy mechanics while preserving the owner‑freezable interface this test relies on. The later casts to OffchainAssetReceiptVault via address(sOwnerFreezable) are consistent with a proxy‑agnostic design, assuming createVault always returns the expected vault implementation.

If you haven’t already, it’s worth double‑checking that all other tests using createVault agree on the share name/symbol and that createVault always yields an OffchainAssetReceiptVault (or compatible proxy) so this downcast remains valid.

test/src/concrete/vault/OffchainAssetReceiptVault.confiscateReceipt.t.sol (2)

11-11: Unable to complete verification due to repository access limitations.


10-10: Import paths updated to project-relative aliases.

The import paths have been refactored from relative paths (../../../../src/...) to root-relative paths (src/..., test/...). All imported source files exist at their expected locations and resolve correctly. No additional configuration changes are required.

test/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.deposit.t.sol (2)

17-17: LGTM!

The import is necessary to support the type declaration for createVault return values used in the test methods.


61-61: createVault helper is properly defined and correctly used.

The createVault method is defined in the parent class OffchainAssetReceiptVaultTest with the signature function createVault(address admin, string memory shareName, string memory shareSymbol) internal returns (OffchainAssetReceiptVault). The usage at line 61 matches the expected signature and is properly inherited through the test class hierarchy.

test/src/concrete/vault/OffchainAssetReceiptVault.handler.t.sol (1)

5-16: Import path refactor looks correct

Switching to root-relative src/... imports for the vault, receipt, and authorizer aligns with the project’s convention and doesn’t affect test behavior. No further issues spotted in this file.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e90fa1f and d7763b4.

📒 Files selected for processing (3)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
  • src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol (1 hunks)
  • test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newERC20PriceOracleReceiptVault.t.sol (1 hunks)
🔇 Additional comments (1)
src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1)

38-62: This comment is based on an incorrect assumption.

ERC20PriceOracleReceiptVaultConfigV2 does not contain an initialAdmin field. The struct only includes IPriceOracleV2 priceOracle and ReceiptVaultConfigV2 receiptVaultConfig. The comparison with OffchainAssetReceiptVaultBeaconSetDeployer is invalid because they use different configuration structures designed for different deployment patterns—the beacon deployer's OffchainAssetReceiptVaultConfigV2 has initialAdmin, while the clone deployer's config does not. No validation change is needed.

Likely an incorrect or invalid review comment.

Comment on lines 53 to 75
function testNewOffchainAssetReceiptVault(OffchainAssetReceiptVaultConfigV2 memory config) external {
vm.assume(config.receiptVaultConfig.receipt == address(0));
vm.assume(config.initialAdmin != address(0));
vm.assume(config.receiptVaultConfig.asset == address(0));

ReceiptContract receiptImplementation = new ReceiptContract();
OffchainAssetReceiptVault offchainAssetReceiptVaultImplementation = new OffchainAssetReceiptVault();
OffchainAssetReceiptVaultBeaconSetDeployer deployer = new OffchainAssetReceiptVaultBeaconSetDeployer(
OffchainAssetReceiptVaultBeaconSetDeployerConfig({
initialOwner: address(this),
initialReceiptImplementation: address(receiptImplementation),
initialOffchainAssetReceiptVaultImplementation: address(offchainAssetReceiptVaultImplementation)
})
);

// expectEmit
OffchainAssetReceiptVault offchainAssetReceiptVault = deployer.newOffchainAssetReceiptVault(config);

assertEq(
address(OffchainAssetReceiptVault(payable(offchainAssetReceiptVault)).receipt().manager()),
address(offchainAssetReceiptVault)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider expanding test assertions for more comprehensive coverage.

The test currently only verifies that the receipt's manager equals the vault address. Consider adding assertions to verify:

  • The vault's admin is correctly set to config.initialAdmin
  • The receipt address is non-zero and correctly set
  • The vault's asset configuration matches the provided config
  • The returned vault address is non-zero

Example additions:

assertEq(offchainAssetReceiptVault.hasRole(offchainAssetReceiptVault.DEFAULT_ADMIN_ROLE(), config.initialAdmin), true);
assertTrue(address(offchainAssetReceiptVault.receipt()) != address(0));
🤖 Prompt for AI Agents
In
test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newERC20PriceOracleReceiptVault.t.sol
around lines 53-75, the test only asserts the receipt's manager; expand
assertions to comprehensively validate deployment: assert the returned vault
address is non-zero, assert the vault's DEFAULT_ADMIN_ROLE is granted to
config.initialAdmin (use hasRole with assertEq/assertTrue), assert the receipt()
address is non-zero, and assert the vault's asset/config fields match
config.receiptVaultConfig (e.g., asset and any other exposed config getters) to
ensure the vault was initialized with the provided config.

Copy link
Contributor

@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: 3

♻️ Duplicate comments (1)
test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (1)

68-75: Address the missing event verification and expand test assertions.

The past review correctly identified two issues that remain unaddressed:

  1. Missing event verification (lines 68-69): The comment // expectEmit indicates intent but no actual verification is present. Use vm.recordLogs() to capture and verify the Deployment event is emitted with correct parameters.

  2. Insufficient assertions (lines 71-74): Only the receipt manager is validated. Add assertions for:

    • Vault address is non-zero: assertTrue(address(offchainAssetReceiptVault) != address(0));
    • Receipt address is non-zero: assertTrue(address(offchainAssetReceiptVault.receipt()) != address(0));
    • Initial admin role is granted: assertTrue(offchainAssetReceiptVault.hasRole(offchainAssetReceiptVault.DEFAULT_ADMIN_ROLE(), config.initialAdmin));

Based on learnings, past review feedback should be addressed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7763b4 and 8ef0464.

📒 Files selected for processing (3)
  • .gas-snapshot (4 hunks)
  • test/src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.newERC20PriceOracleReceiptVault.t.sol (1 hunks)
  • test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
🔇 Additional comments (2)
.gas-snapshot (1)

1-311: Auto-generated gas snapshot – changes are expected.

This file is automatically generated by Forge and contains baseline gas consumption metrics for test cases. The complete regeneration (all lines changed) is normal after refactoring. The minor variations in gas consumption across tests reflect the architectural changes from factory-based deployment to beacon proxies and clone patterns mentioned in the PR summary.

Note: Ensure that:

  • Test suite passes with the new gas baselines
  • No unexpected major gas increases have been introduced
  • The changes align with the proxy-agnostic refactoring objectives
test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (1)

56-56: The assumption is correct by design — OffchainAssetReceiptVault requires zero asset address.

The vm.assume(config.receiptVaultConfig.asset == address(0)) on line 56 is intentional and correct. The OffchainAssetReceiptVault contract explicitly requires the asset to be address(0), enforced by the check on line 307-308 of OffchainAssetReceiptVault.sol which reverts with NonZeroAsset() if a non-zero asset is provided. The contract is designed to track offchain assets only, so the zero address represents the absence of an on-chain ERC20 token.

Likely an incorrect or invalid review comment.

Comment on lines +21 to +35
function testNewERC20PriceOracleReceiptVaultNonZeroReceipt(ERC20PriceOracleReceiptVaultConfigV2 memory config)
external
{
vm.assume(config.receiptVaultConfig.receipt != address(0));
ReceiptContract receiptImplementation = new ReceiptContract();
ERC20PriceOracleReceiptVault erc20PriceOracleReceiptVaultImplementation = new ERC20PriceOracleReceiptVault();
ERC20PriceOracleReceiptVaultCloneDeployer deployer = new ERC20PriceOracleReceiptVaultCloneDeployer(
ERC20PriceOracleReceiptVaultCloneDeployerConfig({
receiptImplementation: address(receiptImplementation),
erc20PriceOracleReceiptVaultImplementation: address(erc20PriceOracleReceiptVaultImplementation)
})
);
vm.expectRevert(abi.encodeWithSelector(InitializeNonZeroReceipt.selector, config.receiptVaultConfig.receipt));
deployer.newERC20PriceOracleReceiptVault(config);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Move implementation deployments to setUp() for fuzz test efficiency.

Creating ReceiptContract and ERC20PriceOracleReceiptVault implementations inside the fuzz test causes them to be deployed on every fuzz iteration, which is inefficient. Move these deployments to a setUp() function and store them as contract-level state variables.

Apply this pattern:

+contract ERC20PriceOracleReceiptVaultCloneDeployerNewERC20PriceOracleReceiptVaultTest is Test {
+    ReceiptContract receiptImplementation;
+    ERC20PriceOracleReceiptVault erc20PriceOracleReceiptVaultImplementation;
+    ERC20PriceOracleReceiptVaultCloneDeployer deployer;
+
+    function setUp() public {
+        receiptImplementation = new ReceiptContract();
+        erc20PriceOracleReceiptVaultImplementation = new ERC20PriceOracleReceiptVault();
+        deployer = new ERC20PriceOracleReceiptVaultCloneDeployer(
+            ERC20PriceOracleReceiptVaultCloneDeployerConfig({
+                receiptImplementation: address(receiptImplementation),
+                erc20PriceOracleReceiptVaultImplementation: address(erc20PriceOracleReceiptVaultImplementation)
+            })
+        );
+    }
+
     function testNewERC20PriceOracleReceiptVaultNonZeroReceipt(ERC20PriceOracleReceiptVaultConfigV2 memory config)
         external
     {
         vm.assume(config.receiptVaultConfig.receipt != address(0));
-        ReceiptContract receiptImplementation = new ReceiptContract();
-        ERC20PriceOracleReceiptVault erc20PriceOracleReceiptVaultImplementation = new ERC20PriceOracleReceiptVault();
-        ERC20PriceOracleReceiptVaultCloneDeployer deployer = new ERC20PriceOracleReceiptVaultCloneDeployer(
-            ERC20PriceOracleReceiptVaultCloneDeployerConfig({
-                receiptImplementation: address(receiptImplementation),
-                erc20PriceOracleReceiptVaultImplementation: address(erc20PriceOracleReceiptVaultImplementation)
-            })
-        );
         vm.expectRevert(abi.encodeWithSelector(InitializeNonZeroReceipt.selector, config.receiptVaultConfig.receipt));
         deployer.newERC20PriceOracleReceiptVault(config);
     }

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

🤖 Prompt for AI Agents
In
test/src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.newERC20PriceOracleReceiptVault.t.sol
around lines 21-35, the test deploys ReceiptContract and
ERC20PriceOracleReceiptVault inside the fuzz test causing repeated deployments;
move those deployments into a setUp() function and store the deployed addresses
as contract-level state variables (e.g., receiptImplementation and
erc20PriceOracleReceiptVaultImplementation) initialized once in setUp(), then
update the test to use those stored variables when constructing the
ERC20PriceOracleReceiptVaultCloneDeployer so the fuzz iterations reuse the same
implementations.

Comment on lines 37 to 50
function testNewERC20PriceOracleReceiptVaultSuccess(ERC20PriceOracleReceiptVaultConfigV2 memory config) external {
vm.assume(config.receiptVaultConfig.receipt == address(0));
ReceiptContract receiptImplementation = new ReceiptContract();
ERC20PriceOracleReceiptVault erc20PriceOracleReceiptVaultImplementation = new ERC20PriceOracleReceiptVault();
ERC20PriceOracleReceiptVaultCloneDeployer deployer = new ERC20PriceOracleReceiptVaultCloneDeployer(
ERC20PriceOracleReceiptVaultCloneDeployerConfig({
receiptImplementation: address(receiptImplementation),
erc20PriceOracleReceiptVaultImplementation: address(erc20PriceOracleReceiptVaultImplementation)
})
);
ERC20PriceOracleReceiptVault vault = deployer.newERC20PriceOracleReceiptVault(config);
assert(address(vault) != address(0));
assert(vault.asset() == config.receiptVaultConfig.asset);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Expand test assertions for comprehensive deployment validation.

The success test only verifies the vault address is non-zero and the asset matches. Consider adding assertions to validate:

  • The receipt address is non-zero: assert(address(vault.receipt()) != address(0));
  • The receipt's manager is correctly set to the vault: assertEq(address(vault.receipt().manager()), address(vault));
  • Other critical config fields are properly initialized (e.g., priceOracle if exposed via a getter)

This ensures the deployer correctly wires all components together.

🤖 Prompt for AI Agents
In
test/src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.newERC20PriceOracleReceiptVault.t.sol
around lines 37 to 50, the test only checks that the vault address is non-zero
and asset matches; add assertions to more fully validate deployment: assert the
vault.receipt() address is non-zero, assert the receipt's manager equals the
deployed vault address, and assert any other exposed critical fields (e.g.,
priceOracle getter) match the config values; insert these additional assertions
after the existing asserts so the test fails if components are not wired
correctly.

Comment on lines +17 to +32
function testNewOffchainAssetReceiptVaultNonZeroReceipt(OffchainAssetReceiptVaultConfigV2 memory config) external {
vm.assume(config.receiptVaultConfig.receipt != address(0));
vm.assume(config.initialAdmin != address(0));

ReceiptContract receiptImplementation = new ReceiptContract();
OffchainAssetReceiptVault offchainAssetReceiptVaultImplementation = new OffchainAssetReceiptVault();
OffchainAssetReceiptVaultBeaconSetDeployer deployer = new OffchainAssetReceiptVaultBeaconSetDeployer(
OffchainAssetReceiptVaultBeaconSetDeployerConfig({
initialOwner: address(this),
initialReceiptImplementation: address(receiptImplementation),
initialOffchainAssetReceiptVaultImplementation: address(offchainAssetReceiptVaultImplementation)
})
);
vm.expectRevert(abi.encodeWithSelector(InitializeNonZeroReceipt.selector, config.receiptVaultConfig.receipt));
deployer.newOffchainAssetReceiptVault(config);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Move implementation and deployer setup to setUp() for test efficiency.

All three test functions create ReceiptContract, OffchainAssetReceiptVault, and OffchainAssetReceiptVaultBeaconSetDeployer instances. For fuzz tests, this results in redundant deployments on every iteration. Extract the common setup into a setUp() function.

Example:

+contract OffchainAssetReceiptVaultBeaconSetDeployerNewOffchainAssetReceiptVaultTest is Test {
+    ReceiptContract receiptImplementation;
+    OffchainAssetReceiptVault offchainAssetReceiptVaultImplementation;
+    OffchainAssetReceiptVaultBeaconSetDeployer deployer;
+
+    function setUp() public {
+        receiptImplementation = new ReceiptContract();
+        offchainAssetReceiptVaultImplementation = new OffchainAssetReceiptVault();
+        deployer = new OffchainAssetReceiptVaultBeaconSetDeployer(
+            OffchainAssetReceiptVaultBeaconSetDeployerConfig({
+                initialOwner: address(this),
+                initialReceiptImplementation: address(receiptImplementation),
+                initialOffchainAssetReceiptVaultImplementation: address(offchainAssetReceiptVaultImplementation)
+            })
+        );
+    }

Also applies to: 34-51, 53-75

🤖 Prompt for AI Agents
In
test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol
around lines 17-32, the test repeatedly constructs ReceiptContract,
OffchainAssetReceiptVault, and OffchainAssetReceiptVaultBeaconSetDeployer for
each fuzz iteration; move these common instantiations into a setUp() function by
adding state-level variables for receiptImplementation,
offchainAssetReceiptVaultImplementation, and deployer, initialize them once in
setUp(), and then remove the duplicate construction code from this test (and the
other test blocks at lines 34-51 and 53-75) so the tests reuse the
pre-initialized instances to improve fuzz performance.

Copy link
Contributor

@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: 1

♻️ Duplicate comments (4)
test/src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.newERC20PriceOracleReceiptVault.t.sol (2)

21-35: Move implementation deployments to setUp() for fuzz test efficiency.

Creating ReceiptContract and ERC20PriceOracleReceiptVault implementations inside the fuzz test causes them to be deployed on every fuzz iteration, which is inefficient. Move these deployments to a setUp() function and store them as contract-level state variables.


37-76: Move implementation deployments to setUp() and expand test assertions.

Two issues:

  1. Same as the previous test: implementations are deployed on every fuzz iteration. Move to setUp().
  2. The test only verifies the vault address is non-zero and the asset matches. Consider adding assertions to validate:
    • The receipt address is non-zero: assert(address(vault.receipt()) != address(0));
    • The receipt's manager is correctly set to the vault: assertEq(address(vault.receipt().manager()), address(vault));
    • Other critical config fields if exposed via getters

This ensures the deployer correctly wires all components together.

test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (2)

17-98: Extract common setup to setUp() function for efficiency and maintainability.

All three test functions create identical implementations (ReceiptContract, OffchainAssetReceiptVault) and deployer instances. For fuzz tests, this results in redundant deployments on every iteration, impacting performance and violating DRY principles. This was previously flagged in past reviews.

Apply this diff to move the common setup:

 contract OffchainAssetReceiptVaultBeaconSetDeployerNewOffchainAssetReceiptVaultTest is Test {
+    ReceiptContract receiptImplementation;
+    OffchainAssetReceiptVault offchainAssetReceiptVaultImplementation;
+    OffchainAssetReceiptVaultBeaconSetDeployer deployer;
+
+    function setUp() public {
+        receiptImplementation = new ReceiptContract();
+        offchainAssetReceiptVaultImplementation = new OffchainAssetReceiptVault();
+        deployer = new OffchainAssetReceiptVaultBeaconSetDeployer(
+            OffchainAssetReceiptVaultBeaconSetDeployerConfig({
+                initialOwner: address(this),
+                initialReceiptImplementation: address(receiptImplementation),
+                initialOffchainAssetReceiptVaultImplementation: address(offchainAssetReceiptVaultImplementation)
+            })
+        );
+    }
+
     function testNewOffchainAssetReceiptVaultNonZeroReceipt(OffchainAssetReceiptVaultConfigV2 memory config) external {
         vm.assume(config.receiptVaultConfig.receipt != address(0));
         vm.assume(config.initialAdmin != address(0));
 
-        ReceiptContract receiptImplementation = new ReceiptContract();
-        OffchainAssetReceiptVault offchainAssetReceiptVaultImplementation = new OffchainAssetReceiptVault();
-        OffchainAssetReceiptVaultBeaconSetDeployer deployer = new OffchainAssetReceiptVaultBeaconSetDeployer(
-            OffchainAssetReceiptVaultBeaconSetDeployerConfig({
-                initialOwner: address(this),
-                initialReceiptImplementation: address(receiptImplementation),
-                initialOffchainAssetReceiptVaultImplementation: address(offchainAssetReceiptVaultImplementation)
-            })
-        );
         vm.expectRevert(abi.encodeWithSelector(InitializeNonZeroReceipt.selector, config.receiptVaultConfig.receipt));
         deployer.newOffchainAssetReceiptVault(config);
     }

Remove the duplicate setup code from the other two test functions similarly.


53-98: Expand test assertions to comprehensively validate deployment.

The test currently only verifies that the receipt's manager equals the vault address. As flagged in previous reviews, consider adding assertions to verify:

  • The returned vault address is non-zero
  • The receipt address is non-zero
  • The vault's DEFAULT_ADMIN_ROLE is correctly granted to config.initialAdmin
  • The receipt reference is correctly stored in the vault

Add these assertions after line 97:

         assertEq(
             address(OffchainAssetReceiptVault(payable(offchainAssetReceiptVault)).receipt().manager()),
             address(offchainAssetReceiptVault)
         );
+        
+        assertTrue(address(offchainAssetReceiptVault) != address(0), "Vault address should be non-zero");
+        assertTrue(address(offchainAssetReceiptVault.receipt()) != address(0), "Receipt address should be non-zero");
+        assertTrue(
+            offchainAssetReceiptVault.hasRole(
+                offchainAssetReceiptVault.DEFAULT_ADMIN_ROLE(),
+                config.initialAdmin
+            ),
+            "Admin role should be granted to initialAdmin"
+        );
     }
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ef0464 and da21dde.

📒 Files selected for processing (3)
  • src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (1 hunks)
  • test/src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.newERC20PriceOracleReceiptVault.t.sol (1 hunks)
  • test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-test)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-legal)
  • GitHub Check: rainix (ubuntu-latest, rainix-sol-static)
🔇 Additional comments (3)
src/concrete/deploy/ERC20PriceOracleReceiptVaultCloneDeployer.sol (2)

33-38: LGTM! Constructor validation is correct.

The zero-address checks with custom errors provide clear error handling, and the public immutable storage pattern is appropriate for implementation addresses.


40-66: LGTM! Deployment flow is correctly ordered and error handling is comprehensive.

The initialization sequence properly establishes the bidirectional relationship:

  1. Receipt is initialized with the vault as its manager
  2. Config is updated with the receipt address
  3. Vault is initialized with the complete config

Each step includes appropriate error handling with custom errors, and the event emission provides good observability for off-chain indexing.

test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol (1)

59-59: No action needed—zero asset address is required by the OffchainAssetReceiptVault.

The assumption that config.receiptVaultConfig.asset == address(0) is correct. The vault explicitly enforces this requirement by reverting with NonZeroAsset() if a non-zero asset is provided. This is intentional design for offchain asset vaults, not a validation bypass.

vm.stopPrank();

bool eventFound = false;
bytes32 eventTopic = keccak256("OffchainAssetReceiptVaultBeaconSetDeployerDeployment(address,address,address)");
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Replace manual event topic hash with type-safe construction.

Manually constructing the event topic hash with a string literal is error-prone. A typo in the event signature would cause the test to silently fail to find the event without detecting the mismatch.

Apply this diff to use the actual event selector from the deployer:

-        bytes32 eventTopic = keccak256("OffchainAssetReceiptVaultBeaconSetDeployerDeployment(address,address,address)");
+        bytes32 eventTopic = OffchainAssetReceiptVaultBeaconSetDeployer.Deployment.selector;

Alternatively, if the event is not exposed as a selector, reference it via the interface or use the event declaration directly to ensure type safety.

🤖 Prompt for AI Agents
test/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.newOffchainAssetReceiptVault.t.sol
around line 78: currently the event topic is built from a hard-coded string
literal; replace this with a type-safe construction by referencing the deployer
contract's event instead of a raw string. Import or reference the
OffchainAssetReceiptVaultBeaconSetDeployer contract/interface and compute the
topic from the actual event declaration (e.g., derive the topic from the
contract's event selector/ABI) so the test uses the contract-defined event
signature rather than a manual keccak256 string.

@thedavidmeister thedavidmeister merged commit 15cbe7e into main Dec 17, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants