Skip to content

Upgradability - #119

Merged
matteoettam09 merged 27 commits into
mainfrom
upgradability
Dec 20, 2025
Merged

Upgradability#119
matteoettam09 merged 27 commits into
mainfrom
upgradability

Conversation

@matteoettam09

@matteoettam09 matteoettam09 commented Dec 20, 2025

Copy link
Copy Markdown
Member

Summary by Sourcery

Introduce a fully upgradeable Orion protocol architecture (config, orchestrators, vault factory and vaults) and centralize deployment logic, while tightening accounting, adapter validation and mainnet-fork safety checks.

New Features:

  • Add UUPS‑upgradeable OrionConfig, orchestrators, PriceAdapterRegistry and TransparentVaultFactory, with vaults deployed via an UpgradeableBeacon and BeaconProxy pattern.
  • Provide a reusable deployUpgradeableProtocol helper to stand up the full upgradeable protocol stack in tests.
  • Support user cancellation (full or partial) of pending redeem requests in Orion vaults.
  • Add on-chain USDC decimal discovery and enhanced immutability checks for third‑party ERC4626 vault integrations.
  • Introduce explicit upgrade test scaffolding contracts (OrionConfigV2, OrionTransparentVaultV2) to validate upgrade behavior.

Bug Fixes:

  • Fix batch limit accounting so pending deposits and redeems respect maxFulfillBatchSize and cannot be double-counted across epochs.
  • Strengthen price adapter and execution adapter checks so adapter decimals and whitelisting/decimals registration always match the underlying asset configuration.
  • Harden DOS‐related scenarios around minimum amounts, batch fulfillment and removeWhitelistedAsset flows by exercising them under the new accounting semantics.

Enhancements:

  • Refactor OrionVault and OrionTransparentVault to use OpenZeppelin upgradeable ERC20/ERC4626 and initializer patterns, with storage gaps for future upgrades.
  • Extend LiquidityOrchestrator and InternalStatesOrchestrator with UUPS upgradeability, storage gaps and safer signed math for delta buffer tracking.
  • Enrich protocol interfaces and libraries with security-contact annotations and new events (e.g. VaultBeaconUpdated) for better observability and security posture.
  • Tighten vault and adapter behavior around fee cooldowns, pause handling and curator/owner management under the upgradeable architecture.
  • Improve mainnet‑fork robustness tests to detect upgradeable vaults, verify property determinism across blocks and enforce stricter adapter price sanity ranges.

Build:

  • Bump protocol version to 1.0.0 and upgrade Hardhat toolbox and OpenZeppelin contracts to versions compatible with upgradeable patterns.
  • Introduce pnpm overrides and peer dependency rules to align toolchain and contract dependencies with the new upgradeable stack.

CI:

  • Update CI and Makefile to run dependency audits and typechain generation before linting, testing and static analysis (Slither).

Deployment:

  • Introduce and wire an UpgradeableBeacon for vault implementations, with factory‑level beacon management to support coordinated vault upgrades across the protocol.

Documentation:

  • Document security contact information via @Custom:security-contact annotations across core contracts and interfaces.

Tests:

  • Refactor most test suites to reuse a centralized upgradeable deployment helper instead of bespoke deployments.
  • Add dedicated batch limit consistency and redeem cancellation tests to guard against regressions in accounting and queue management.
  • Strengthen orchestrator, pause, access control, execution adapter and multi‑asset robustness tests to validate behavior under the upgradeable deployment model and stricter invariants.

Summary by CodeRabbit

Release Notes

  • New Features

    • Migrated protocol contracts to upgradeable proxy patterns (UUPS and Beacon Proxy), enabling future protocol upgrades without data loss.
    • Added vault beacon pattern for coordinated upgrades across multiple vaults.
    • Introduced comprehensive upgrade test suite covering UUPS, Beacon, and factory upgrade scenarios.
  • Documentation

    • Added security contact information to all contracts and interfaces.
  • Chores

    • Updated to version 1.0.0 with OpenZeppelin upgradeable contracts dependency.
    • Enhanced CI pipeline with dependency auditing.
    • Streamlined test infrastructure with centralized deployable protocol helper.

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

ojasarora77 and others added 25 commits December 4, 2025 13:53
…egistry

- Converted OrionConfig to OrionConfigUpgradeable using UUPS pattern
- Converted PriceAdapterRegistry to PriceAdapterRegistryUpgradeable
- Changed immutable ADMIN to regular storage variable
- Replaced constructors with initialize() functions
- Added _authorizeUpgrade() for owner-controlled upgrades
- Added 50-slot storage gaps for future upgrades
- Used standard EnumerableSet (no upgradeable version in OZ v5)

and Added @openzeppelin/contracts-upgradeable@^5.4.0 and
@openzeppelin/hardhat-upgrades for UUPS and Beacon proxy patterns
- Converted InternalStatesOrchestrator to upgradeable version
- Converted LiquidityOrchestrator to upgradeable version
- Changed all base contracts to upgradeable versions:
  - Ownable2Step → Ownable2StepUpgradeable
  - ReentrancyGuard → ReentrancyGuardUpgradeable
  - Pausable → PausableUpgradeable
- Added UUPS pattern with _authorizeUpgrade()
- Replaced constructors with initialize() functions
- Added 50-slot storage gaps
- Created OrionVaultUpgradeable abstract base contract
- Created OrionTransparentVaultUpgradeable concrete implementation
- Converted from ERC4626 to ERC4626Upgradeable
- Changed ReentrancyGuard to ReentrancyGuardUpgradeable
- Replaced constructors with initialize() functions
- Used __OrionVault_init() for internal initialization
- NO UUPS logic - upgrades handled by Beacon pattern
- Added 50-slot storage gaps

This enables all vault instances to share a single upgradeable
implementation via UpgradeableBeacon, allowing protocol-wide vault
upgrades with a single transaction.
- Converted TransparentVaultFactory to UUPS upgradeable pattern
- Added vaultBeacon state variable for UpgradeableBeacon reference
- Modified createVault() to deploy BeaconProxy instances instead of
  direct contracts
- Encodes initialization data and passes to BeaconProxy constructor
- Added setVaultBeacon() function for updating beacon address
- Storage gap: 49 slots (50 - 1 for vaultBeacon)

Factory now deploys all vaults as BeaconProxy instances pointing to
a shared UpgradeableBeacon, enabling single-transaction upgrades
for all vaults.
Demonstrates complete upgrade lifecycle:
- Deploy all UUPS contracts (Config, Registry, Orchestrators, Factory)
- Deploy Beacon + vault implementation
- Create vault via factory (BeaconProxy)
- Test V1 behavior
- Upgrade vault implementation via Beacon
- Verify state preservation after vault upgrade
- Upgrade OrionConfig via UUPS
- Verify state preservation after UUPS upgrade
Mock V2 implementations:
- OrionConfigUpgradeableV2: adds newV2Variable state and setV2Variable()
- OrionTransparentVaultUpgradeableV2: adds vaultDescription and setVaultDescription()
- Both include version() function returning "v2"

Test scripts:
1. testUpgradeability.ts - Full upgrade lifecycle test
   - Deploy all UUPS contracts (Config, Registry, Orchestrators, Factory)
   - Deploy Beacon + vault implementation
   - Create vault via factory (BeaconProxy)
   - Upgrade vault via beacon (V1 → V2)
   - Verify implementation address changes
   - Test V2 new features work
   - Upgrade OrionConfig via UUPS (V1 → V2)
   - Verify state preservation and V2 features

2. verifyUpgradeRequiresNewAddress.ts - Verification test
   - Proves implementation address MUST change for real upgrades
   - Tests both UUPS and Beacon patterns
   - Demonstrates same-address "upgrades" are no-ops
now uses a Deploy complete upgradeable protocol infrastructure
What the Tests Cover:
pendingDeposit Batch Limit (3 tests)
Returns exact amount when requests < maxFulfillBatchSize
Returns ONLY first maxFulfillBatchSize when requests exceed limit
Handles edge case when requests = maxFulfillBatchSize exactly
pendingRedeem Batch Limit (3 tests)
Returns exact shares when requests < maxFulfillBatchSize
Returns ONLY first maxFulfillBatchSize when requests exceed limit
Handles edge case when requests = maxFulfillBatchSize exactly
Verify Fix Prevents Double-Counting (2 tests)
Verifies pendingDeposit limiting prevents totalAssets overcounting
Verifies pendingRedeem limiting prevents double-subtraction from totalAssets
This commit addresses 4 critical code review action items:

1. **cancelRedeemRequest Edge-Case Tests** (OrionConfigVault.test.ts)
   - Add test for zero amount (should revert with AmountMustBeGreaterThanZero)
   - Add test for excessive amount (should revert with InsufficientAmount)
   - Add test for successful full cancellation
   - Add test for partial cancellation
   - Mirrors existing cancelDepositRequest test pattern
   - Uses LiquidityOrchestrator impersonation to mint shares for testing

2. **USDC Decimals On-Chain Fetch** (erc4626VaultCompatibility.test.ts)
   - Replace hardcoded USDC_DECIMALS = 6 with on-chain contract call
   - Prevents configuration drift detection issues
   - Fetches decimals via USDC contract interface in before() hook

3. **Real Immutability Test** (erc4626VaultCompatibility.test.ts)
   - Replace basic "call 3 times" test with comprehensive checks:
     * Bytecode immutability verification across blocks
     * EIP-1967 implementation slot immutability (proxy detection)
     * Cross-block property validation for asset() and decimals()
     * Deterministic read verification within same block
   - Detects upgradeable contracts and validates implementation slots

4. **Adapter Decimals Assertion** (erc4626VaultCompatibility.test.ts)
   - Add assertion that adapter decimals match underlying asset decimals
   - Critical for ensuring consistent price calculations
   - Prevents potential price calculation bugs

**Bonus Fix:**
- Execution adapter compatibility test now whitelists vaults before validation
- Ensures OrionConfig has vault decimals registered before adapter validates
- Prevents InvalidAdapter errors during validation
Test a) Different implementations after setVaultBeacon
Deploys first vault with V1 implementation via original beacon
Creates new beacon pointing to V2 implementation
Calls setVaultBeacon() to switch factory to new beacon
Deploys second vault with V2 implementation
Asserts: Vault 1 uses V1, Vault 2 uses V2
Test b) Same new implementation after vaultBeacon.upgradeTo
Deploys first vault with V1 implementation
Calls vaultBeacon.upgradeTo(newImpl) to upgrade existing beacon
Deploys second vault
Asserts: Both old and new vaults now use V2 implementation
Test c) Factory UUPS upgrade maintains beacon functionality
Deploys vault with original factory + V1 beacon
Upgrades factory itself via UUPS
Creates new V2 beacon and calls setVaultBeacon() on upgraded factory
Deploys vault with upgraded factory + V2 beacon
Asserts: First vault still V1, second vault uses V2, factory functions correctly after UUPS upgrade
@immunefi-magnus

Copy link
Copy Markdown

🛡️ Immunefi PR Reviews

We noticed that your project isn't set up for automatic code reviews. If you'd like this PR reviewed by the Immunefi team, you can request it manually using the link below:

🔗 Send this PR in for review

Once submitted, we'll take care of assigning a reviewer and follow up here.

@sourcery-ai

sourcery-ai Bot commented Dec 20, 2025

Copy link
Copy Markdown

Reviewer's Guide

Introduce full upgradability to the Orion protocol core (config, orchestrators, vault factory, vaults, and price-adapter registry) via UUPS and beacon proxies, centralize upgradeable deployment logic in a test helper, tighten accounting around batch limits, and extend tests/CI to validate immutability, adapter correctness, and upgrade flows.

Sequence diagram for creating an upgradeable transparent vault via BeaconProxy

sequenceDiagram
    actor VaultOwner
    participant TransparentVaultFactory
    participant OrionConfig
    participant UpgradeableBeacon
    participant BeaconProxy
    participant OrionTransparentVault_impl as OrionTransparentVaultImplementation
    participant OrionVault

    VaultOwner->>TransparentVaultFactory: createOrionTransparentVault(vaultOwner,curator,name,symbol,feeType,performanceFee,managementFee,depositAccessControl)

    TransparentVaultFactory->>OrionConfig: isWhitelistedVaultOwner(vaultOwner)
    OrionConfig-->>TransparentVaultFactory: bool

    TransparentVaultFactory->>OrionConfig: isSystemIdle()
    OrionConfig-->>TransparentVaultFactory: bool

    TransparentVaultFactory->>TransparentVaultFactory: encode initialize(vaultOwner,curator,config,name,symbol,feeType,performanceFee,managementFee,depositAccessControl)

    TransparentVaultFactory->>UpgradeableBeacon: read implementation()
    UpgradeableBeacon-->>TransparentVaultFactory: OrionTransparentVault_impl

    TransparentVaultFactory->>BeaconProxy: new BeaconProxy(vaultBeacon, initData)

    BeaconProxy->>OrionTransparentVault_impl: delegatecall initialize(...)
    OrionTransparentVault_impl->>OrionVault: __OrionVault_init(...)
    OrionVault-->>OrionTransparentVault_impl: initialized
    OrionTransparentVault_impl-->>BeaconProxy: initialized

    TransparentVaultFactory-->>VaultOwner: vault address = BeaconProxy

    TransparentVaultFactory->>OrionConfig: addOrionVault(vaultAddress,VaultType.Transparent)
    OrionConfig-->>TransparentVaultFactory: success
Loading

Class diagram for upgraded vault and factory contracts

classDiagram
    class Initializable
    class UUPSUpgradeable
    class Ownable2StepUpgradeable
    class ReentrancyGuardUpgradeable
    class PausableUpgradeable
    class ERC20Upgradeable
    class ERC4626Upgradeable
    class UpgradeableBeacon
    class BeaconProxy
    class IOrionConfig
    class IOrionVault
    class IOrionTransparentVault

    class OrionVault {
        <<abstract>>
        +address vaultOwner
        +address curator
        +IOrionConfig config
        +uint256 totalUserShares
        +uint256 totalAssets()
        +uint8 decimals()
        +redeem(uint256 assets,address receiver,address owner) uint256
        +deposit(uint256 assets,address receiver) uint256
        +mint(uint256 shares,address receiver) uint256
        +withdraw(uint256 assets,address receiver,address owner) uint256
        +__OrionVault_init(address vaultOwner,address curator,IOrionConfig config,string name,string symbol,uint8 feeType,uint16 performanceFee,uint16 managementFee,address depositAccessControl)
        -uint256[50] __gap
    }

    class OrionTransparentVault {
        +EnumerableMap.AddressToUintMap _portfolioIntent
        +constructor()
        +initialize(address vaultOwner,address curator,IOrionConfig config,string name,string symbol,uint8 feeType,uint16 performanceFee,uint16 managementFee,address depositAccessControl)
        -uint256[50] __gap
    }

    class TransparentVaultFactory {
        +IOrionConfig config
        +UpgradeableBeacon vaultBeacon
        +constructor()
        +initialize(address initialOwner,address configAddress,address vaultBeaconAddress)
        +createOrionTransparentVault(address vaultOwner,address curator,string name,string symbol,uint8 feeType,uint16 performanceFee,uint16 managementFee,address depositAccessControl) address
        +setVaultBeacon(address newVaultBeacon)
        +_authorizeUpgrade(address newImplementation)
        -uint256[50] __gap
    }

    OrionVault --|> Initializable
    OrionVault --|> ERC4626Upgradeable
    OrionVault --|> ReentrancyGuardUpgradeable
    OrionVault ..|> IOrionVault

    OrionTransparentVault --|> OrionVault
    OrionTransparentVault ..|> IOrionTransparentVault

    TransparentVaultFactory --|> Initializable
    TransparentVaultFactory --|> Ownable2StepUpgradeable
    TransparentVaultFactory --|> UUPSUpgradeable

    TransparentVaultFactory --> IOrionConfig : uses
    TransparentVaultFactory --> UpgradeableBeacon : manages
    TransparentVaultFactory --> BeaconProxy : deploys
Loading

Class diagram for upgraded orchestrators, config, and price registry

classDiagram
    class Initializable
    class UUPSUpgradeable
    class Ownable2StepUpgradeable
    class ReentrancyGuardUpgradeable
    class PausableUpgradeable

    class IOrionConfig
    class ILiquidityOrchestrator
    class IInternalStateOrchestrator
    class IPriceAdapterRegistry

    class OrionConfig {
        +address ADMIN
        +address guardian
        +IERC20 underlyingAsset
        +constructor()
        +initialize(address initialOwner,address admin,address underlyingAsset)
        +admin() address
        +_authorizeUpgrade(address newImplementation)
        -uint256[50] __gap
    }

    class LiquidityOrchestrator {
        +IOrionConfig config
        +address underlyingAsset
        +address admin
        +int256 deltaBufferAmount
        +constructor()
        +initialize(address initialOwner,address config,address automationRegistry)
        +executeSellOrder(...)
        +executeBuyOrder(...)
        +pause()
        +unpause()
        +_authorizeUpgrade(address newImplementation)
        -uint256[50] __gap
    }

    class InternalStatesOrchestrator {
        +IOrionConfig config
        +IPriceAdapterRegistry registry
        +uint256 intentFactor
        +constructor()
        +initialize(address initialOwner,address config,address automationRegistry)
        +updateVaultStates(...)
        +triggerLiquidityOrchestrator(...)
        +pause()
        +unpause()
        +_authorizeUpgrade(address newImplementation)
        -uint256[50] __gap
    }

    class PriceAdapterRegistry {
        +address configAddress
        +uint8 priceAdapterDecimals
        +constructor()
        +initialize(address initialOwner,address configAddress)
        +setPriceAdapter(address asset,address adapter)
        +getPrice(address asset) uint256
        +_authorizeUpgrade(address newImplementation)
        -uint256[50] __gap
    }

    OrionConfig --|> Initializable
    OrionConfig --|> Ownable2StepUpgradeable
    OrionConfig --|> UUPSUpgradeable
    OrionConfig ..|> IOrionConfig

    LiquidityOrchestrator --|> Initializable
    LiquidityOrchestrator --|> Ownable2StepUpgradeable
    LiquidityOrchestrator --|> ReentrancyGuardUpgradeable
    LiquidityOrchestrator --|> PausableUpgradeable
    LiquidityOrchestrator --|> UUPSUpgradeable
    LiquidityOrchestrator ..|> ILiquidityOrchestrator

    InternalStatesOrchestrator --|> Initializable
    InternalStatesOrchestrator --|> Ownable2StepUpgradeable
    InternalStatesOrchestrator --|> ReentrancyGuardUpgradeable
    InternalStatesOrchestrator --|> PausableUpgradeable
    InternalStatesOrchestrator --|> UUPSUpgradeable
    InternalStatesOrchestrator ..|> IInternalStateOrchestrator

    PriceAdapterRegistry --|> Initializable
    PriceAdapterRegistry --|> Ownable2StepUpgradeable
    PriceAdapterRegistry --|> UUPSUpgradeable
    PriceAdapterRegistry ..|> IPriceAdapterRegistry

    LiquidityOrchestrator --> IOrionConfig : uses
    InternalStatesOrchestrator --> IOrionConfig : uses
    InternalStatesOrchestrator --> IPriceAdapterRegistry : uses
    PriceAdapterRegistry --> IOrionConfig : reads settings
Loading

Flow diagram for UUPS upgrade process of a core contract

flowchart TD
    A["Owner calls upgradeTo on UUPS contract"] --> B["UUPS proxy receives call"]
    B --> C["Proxy delegates call to current implementation"]
    C --> D["Implementation _authorizeUpgrade(newImplementation) with onlyOwner"]
    D -->|reverts| E["Upgrade reverted"]
    D -->|success| F["Implementation performs upgrade to newImplementation"]
    F --> G["Proxy now delegates to newImplementation"]
    G --> H["State preserved via storage layout and __gap"]
Loading

File-Level Changes

Change Details Files
Migrate core protocol contracts to upgradeable patterns (UUPS and beacon) and adapt vaults to be upgradeable ERC4626 implementations.
  • Convert OrionConfig, LiquidityOrchestrator, InternalStatesOrchestrator, and PriceAdapterRegistry to UUPS upgradeable contracts using Initializable, Ownable2StepUpgradeable, and UUPSUpgradeable with disabled constructors and explicit initialize functions.
  • Refactor OrionVault to use ERC20Upgradeable/ERC4626Upgradeable/ReentrancyGuardUpgradeable, replacing the constructor with an internal __OrionVault_init initializer that is invoked from OrionTransparentVault.initialize.
  • Refactor OrionTransparentVault into an upgradeable implementation with a no-arg constructor that disables initializers, a public initialize method that calls __OrionVault_init, and a storage gap for future upgrades.
  • Change TransparentVaultFactory into a UUPS upgradeable contract that deploys vaults as BeaconProxy instances pointing to an UpgradeableBeacon for OrionTransparentVault, adds a configurable vaultBeacon with setVaultBeacon, and uses encoded initialize() data when deploying proxies.
  • Add storage gaps and _authorizeUpgrade hooks (owner-gated) to all newly upgradeable contracts to maintain upgrade safety and future-proof storage layout.
contracts/OrionConfig.sol
contracts/orchestrators/LiquidityOrchestrator.sol
contracts/orchestrators/InternalStatesOrchestrator.sol
contracts/price/PriceAdapterRegistry.sol
contracts/factories/TransparentVaultFactory.sol
contracts/vaults/OrionVault.sol
contracts/vaults/OrionTransparentVault.sol
Introduce a shared upgradeable deployment helper and refactor tests to use the upgradeable protocol stack instead of direct constructor deployments.
  • Add deployUpgradeableProtocol helper that deploys OrionConfig, PriceAdapterRegistry, LiquidityOrchestrator, InternalStatesOrchestrator, TransparentVaultFactory (as UUPS proxies) plus an UpgradeableBeacon and OrionTransparentVault implementation, wiring all cross-contract references and orchestrator links.
  • Provide attachToVault helper for attaching to beacon-proxied OrionTransparentVault instances in tests.
  • Refactor most unit/integration tests (vault, orchestrator, access control, protocol pause, DOS/fee tests, removal/strategy tests, adapters, batch-limit tests, etc.) to call deployUpgradeableProtocol and, where needed, use the deployed transparentVaultFactory to create vaults instead of deploying contracts directly.
  • Adjust tests that rely on internal orchestrator wiring (e.g., zero-state, orchestrator configuration/security) to assume the new wiring performed by deployUpgradeableProtocol instead of manual set* calls.
test/helpers/deployUpgradeable.ts
test/OrionConfigVault.test.ts
test/OrionVaultExchangeRate.test.ts
test/TransparentVault.test.ts
test/AccessControl.test.ts
test/ProtocolPause.test.ts
test/BatchLimitAccounting.test.ts
test/MinimumAmountDOS.test.ts
test/FeeCooldown.test.ts
test/VaultOwnerRemoval.test.ts
test/Removal.test.ts
test/PassiveCuratorStrategy.test.ts
test/orchestrator/Orchestrators.test.ts
test/orchestrator/OrchestratorsZeroState.test.ts
test/orchestrator/OrchestratorSecurity.test.ts
test/orchestrator/OrchestratorConfiguration.test.ts
test/Adapters.test.ts
test/ExecutionAdapterValidation.test.ts
test/RedeemBeforeDepositOrder.test.ts
test/mainnet-fork/multiAssetRobustness.test.ts
Tighten batch-limit accounting for deposits/redeems and add regression tests to prevent double-counting in totalAssets.
  • Clarify pendingDeposit and pendingRedeem semantics to cap returned amounts by maxFulfillBatchSize so that internal accounting only considers the number of requests that can actually be fulfilled in one epoch.
  • Introduce BatchLimitConsistency tests that create multiple deposit/redeem requests, impersonate the LiquidityOrchestrator to call fulfillDeposit/fulfillRedeem, and assert that pendingDeposit/pendingRedeem respect batch-size limits (and that overflows are excluded), preventing overcounting and double-subtraction in totalAssets.
  • Update related batch accounting tests to use the upgradeable protocol helper and to attach to vault implementations correctly when interacting with beacon proxies.
test/BatchLimitAccounting.test.ts
test/BatchLimitConsistency.test.ts
contracts/vaults/OrionVault.sol
Add new functional tests around redeem-request cancellation and improve existing mainnet-fork tests to validate immutability and adapter correctness under upgradeable-aware assumptions.
  • Extend OrionConfigVault tests with a "Redeem Request Cancellation" suite that mints shares via deposit+fulfill, then validates cancelRedeemRequest behavior for zero, over-sized, full, and partial cancellations, including share balance restoration and pendingRedeem updates.
  • In mainnet-fork ERC4626 compatibility tests, fetch USDC decimals on-chain instead of hardcoding and enhance immutability checks to validate bytecode stability, EIP-1967 implementation slots for proxies, and cross-block determinism of asset() and decimals().
  • Tighten price adapter tests to assert adapter decimals match the underlying asset decimals, and ensure execution adapter tests whitelist vaults before validation, then clean them up from OrionConfig after validation.
  • Update multi-asset robustness tests to deploy the upgradeable protocol stack and respect the new deployment pattern.
test/OrionConfigVault.test.ts
test/mainnet-fork/erc4626VaultCompatibility.test.ts
test/mainnet-fork/multiAssetRobustness.test.ts
Enhance upgradability/observability with test-only V2 implementations and security-contact metadata, and adjust CI/deps for the new upgradeable stack.
  • Add OrionConfigV2 and OrionTransparentVaultV2 test contracts that extend the base implementations with new state and functions (e.g., version(), setV2Variable, setVaultDescription) to validate UUPS and beacon upgrades without breaking storage layout.
  • Annotate core contracts, libraries, interfaces, access controllers, strategies, and adapters with @Custom:security-contact security@orionfinance.ai for better ecosystem tooling and disclosure metadata.
  • Update package.json to bump protocol version to 1.0.0, bring @nomicfoundation/hardhat-toolbox to v4, add @openzeppelin/contracts-upgradeable, and configure pnpm overrides/peer rules for OpenZeppelin upgradeable packages.
  • Revise the Makefile and GitHub CI workflow to run pnpm audit --prod --audit-level high and ensure typechain runs before lint/tests/slither in local CI and as a dedicated step in GitHub Actions.
  • Ensure LiquidityOrchestrator uses SafeCast when updating deltaBufferAmount to avoid manual int256 casts with potential overflow issues.
contracts/test/OrionConfigV2.sol
contracts/test/OrionTransparentVaultV2.sol
contracts/OrionConfig.sol
contracts/factories/TransparentVaultFactory.sol
contracts/vaults/OrionTransparentVault.sol
contracts/vaults/OrionVault.sol
contracts/orchestrators/LiquidityOrchestrator.sol
contracts/orchestrators/InternalStatesOrchestrator.sol
contracts/price/PriceAdapterRegistry.sol
contracts/price/OrionAssetERC4626PriceAdapter.sol
contracts/execution/OrionAssetERC4626ExecutionAdapter.sol
contracts/access_controllers/WhitelistAccessControl.sol
contracts/strategies/KBestTvlWeightedAverage.sol
contracts/libraries/EventsLib.sol
contracts/libraries/ErrorsLib.sol
contracts/libraries/UtilitiesLib.sol
contracts/interfaces/*.sol
package.json
Makefile
.github/workflows/ci.yml

Possibly linked issues

  • #Protocol Upgradability: PR implements UUPS and beacon upgradeability for config, orchestrators, vault factory, and vaults as required by the issue.
  • #test: PR implements all requested tests: cancelRedeem edge cases, on-chain USDC decimals, real immutability checks, and adapter-decimal assertions.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Dec 20, 2025

Copy link
Copy Markdown

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This pull request migrates the Orion Finance protocol from non-upgradeable contracts to UUPS and Beacon proxy patterns. Core contracts (OrionConfig, orchestrators, vaults, factories) are converted to use initializers instead of constructors, new upgradeability hooks are added, and test infrastructure is consolidated via a new deployment helper. Dependencies include OpenZeppelin upgradeable contracts, CI audit steps are added, and the package version is bumped to 1.0.0.

Changes

Cohort / File(s) Summary
CI & Build Configuration
.github/workflows/ci.yml, Makefile, .gitignore, package.json
Added pnpm audit step for production dependencies, restructured CI target with new typechain step, updated dependencies to @openzeppelin/contracts-upgradeable ^5.4.0, bumped hardhat-toolbox to ^4.0.0, added pnpm overrides and peer dependency rules, version bump to 1.0.0, reorganized ignore entries.
Core Contract Upgradeability
contracts/OrionConfig.sol, contracts/vaults/OrionVault.sol, contracts/vaults/OrionTransparentVault.sol, contracts/orchestrators/InternalStatesOrchestrator.sol, contracts/orchestrators/LiquidityOrchestrator.sol, contracts/factories/TransparentVaultFactory.sol, contracts/price/PriceAdapterRegistry.sol
Migrated contracts from non-upgradeable base classes (Ownable2Step, ReentrancyGuard, Pausable, ERC4626) to upgradeable equivalents (Ownable2StepUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable, ERC4626Upgradeable), replaced constructors with public initializer functions marked with initializer, added UUPS upgrade authorization via _authorizeUpgrade, added storage gaps (uint256[50] private __gap). Factory converts to Beacon Proxy pattern with vaultBeacon management.
Security Contact Metadata
contracts/access_controllers/WhitelistAccessControl.sol, contracts/execution/OrionAssetERC4626ExecutionAdapter.sol, contracts/interfaces/I*, contracts/libraries/ErrorsLib.sol, contracts/price/OrionAssetERC4626PriceAdapter.sol, contracts/strategies/KBestTvlWeightedAverage.sol
Added @custom:security-contact security@orionfinance.ai NatSpec tags to contract and interface headers for documentation purposes.
Library Updates
contracts/libraries/EventsLib.sol, contracts/libraries/UtilitiesLib.sol
Added VaultBeaconUpdated event to EventsLib, added security contact tags to library headers.
Test Helpers
test/helpers/deployUpgradeable.ts
New module exporting deployUpgradeableProtocol function and UpgradeableProtocolContracts interface; orchestrates UUPS proxy deployments for OrionConfig, PriceAdapterRegistry, orchestrators, and Beacon proxy setup for vaults; handles initialization order and contract wiring.
Test Mock Contracts
contracts/test/OrionConfigV2.sol, contracts/test/OrionTransparentVaultV2.sol
Added V2 mock upgrade contracts for testing upgradeability; include newV2Variable/vaultDescription state, setters, events, and version() function to verify upgrade behavior.
Test Suite Refactoring
test/*.test.ts, test/orchestrator/*.test.ts, test/mainnet-fork/*.test.ts
Consolidated test setup across 20+ test files by replacing manual multi-contract deployments with single deployUpgradeableProtocol helper calls; removed explicit deployments of OrionConfig, orchestrators, factories, and price registries; updated imports to remove now-encapsulated contract types.
New Upgrade Test Suite
test/Upgrade.test.ts
New comprehensive test covering UUPS upgrades (OrionConfig), Beacon Proxy upgrades (OrionTransparentVault), and Factory upgradeability; validates state preservation, access control, event emissions, and cross-vault consistency across upgrade scenarios.
Batch Limit Tests
test/BatchLimitConsistency.test.ts
New test suite validating batch processing constraints for deposit/redeem requests in vaults with maxFulfillBatchSize limits; tests edge cases and prevents double-counting in totalAssets.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Areas requiring extra attention:

  • Initialization order and dependencies: Multiple contracts have interdependent initializers (OrionConfig → orchestrators → factory); ensure initialize functions are called in correct sequence and all __*_init variants are properly invoked.
  • Storage layout changes: All upgradeable contracts introduce storage gaps; verify no existing storage variables conflict and that layout remains compatible across versions.
  • Test deployment helper logic (test/helpers/deployUpgradeable.ts): Central to all test refactoring; inspect deployment sequence, initialization parameter passing, and contract wiring thoroughly.
  • Beacon Proxy implementation: TransparentVaultFactory's conversion to use UpgradeableBeacon; verify beacon initialization, proxy creation, and setVaultBeacon authorization checks.
  • Constructor → Initializer conversion: Inspect each contract's initialize function for completeness; ensure all prior constructor logic is migrated and __disableInitializers() is correctly placed in no-arg constructors.
  • Test coverage scope: 20+ test files modified; spot-check a few key test files to ensure deployUpgradeableProtocol integration is complete and test assertions still validate intended behavior.
  • SafeCast usage in LiquidityOrchestrator: New toInt256 conversions for deltaBufferAmount; confirm type safety and no overflow/underflow risks.

Possibly related PRs

  • fix: enable adapters overwriting #83 — Modifies PriceAdapterRegistry contract (being upgraded to UUPS proxy pattern in this PR).
  • Dev #108 — Refactors InternalStatesOrchestrator and LiquidityOrchestrator order/upkeep APIs (same contracts being migrated to upgradeable patterns here).
  • Dev #73 — Changes TransparentVaultFactory vault-creation validation and OrionConfig whitelist logic (same factory being converted to Beacon Proxy pattern here).

Poem

🐰 Contracts now upgradeable, oh what a sight!
Proxies and beacons dancing in the night,
Storage gaps preserved for versions to come,
Initialize functions replacing old sums.
From v0.7 to 1.0, the protocol's complete—
An elegant hop toward upgrades so sweet! 🎩✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Upgradability' is concise and directly reflects the primary objective of the changeset, which is to introduce a fully upgradeable Orion protocol architecture with UUPS-upgradeable contracts and related infrastructure changes.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch upgradability

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

❤️ Share

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • In OrionVault.__OrionVault_init the previous constructor checks for non-zero curator_ and config_ were removed; consider reintroducing these validations to avoid accidentally creating unusable vaults via a misconfigured factory or upgrade.
  • The new deployUpgradeableProtocol helper silently defaults automationRegistry to admin when not provided; if this is only meant for tests, consider making that explicit or enforcing a non-zero, distinct registry to avoid masking misconfigurations when the helper is reused elsewhere.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `OrionVault.__OrionVault_init` the previous constructor checks for non-zero `curator_` and `config_` were removed; consider reintroducing these validations to avoid accidentally creating unusable vaults via a misconfigured factory or upgrade.
- The new `deployUpgradeableProtocol` helper silently defaults `automationRegistry` to `admin` when not provided; if this is only meant for tests, consider making that explicit or enforcing a non-zero, distinct registry to avoid masking misconfigurations when the helper is reused elsewhere.

## Individual Comments

### Comment 1
<location> `contracts/vaults/OrionVault.sol:175-178` </location>
<code_context>
     /// @param depositAccessControl_ The address of the deposit access control contract (address(0) = permissionless)
-    constructor(
+    // solhint-disable-next-line func-name-mixedcase, use-natspec
+    function __OrionVault_init(
         address vaultOwner_,
         address curator_,
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The initializer no longer validates `curator_` and `config_` addresses, weakening constructor-time safety guarantees.

The original constructor reverted when `curator_ == address(0)` or `address(config_) == address(0)`, ensuring vaults were always deployed with valid dependencies. The new `__OrionVault_init` omits these checks, so a misconfigured proxy init could leave the vault unusable. Please add equivalent validations (and any other constructor invariants you rely on) to the initializer to maintain the same safety guarantees.

Suggested implementation:

```
    // solhint-disable-next-line func-name-mixedcase, use-natspec
    function __OrionVault_init(
        address vaultOwner_,
        address curator_,
        IOrionConfig config_,
        uint16 performanceFee_,
        uint16 managementFee_,
        address depositAccessControl_
    ) internal onlyInitializing {
        // Maintain constructor-time safety guarantees
        require(curator_ != address(0), "OrionVault: curator is zero address");
        require(address(config_) != address(0), "OrionVault: config is zero address");

        // Initialize parent contracts
        __ERC20_init(name_, symbol_);

```

1. If the original constructor used custom errors or different revert messages (e.g. `error InvalidCurator()` / `InvalidConfig()`), replace the two `require(...)` statements with the corresponding error checks to keep revert semantics consistent:
   - `if (curator_ == address(0)) revert InvalidCurator();`
   - `if (address(config_) == address(0)) revert InvalidConfig();`
2. If the original constructor enforced additional invariants (e.g. bounds on `performanceFee_` / `managementFee_`, non-zero `vaultOwner_`, or `depositAccessControl_` constraints), replicate those checks at the top of `__OrionVault_init` as well, mirroring the constructor logic exactly.
</issue_to_address>

### Comment 2
<location> `contracts/vaults/OrionVault.sol:214-223` </location>
<code_context>
     function _initializeVaultWhitelist() internal {
         address[] memory protocolAssets = config.getAllWhitelistedAssets();
         for (uint256 i = 0; i < protocolAssets.length; ++i) {
-            bool inserted = _vaultWhitelistedAssets.add(protocolAssets[i]);
-            if (!inserted) revert ErrorsLib.AlreadyRegistered();
+            // slither-disable-next-line unused-return
+            _vaultWhitelistedAssets.add(protocolAssets[i]);
         }
     }
</code_context>

<issue_to_address>
**question (bug_risk):** Behavior change: duplicate assets in `config.getAllWhitelistedAssets()` no longer cause a revert when initializing the vault whitelist.

This change removes the defensive `AlreadyRegistered` invariant and now silently ignores duplicate assets. If `protocolAssets` is always unique this is fine, but if that assumption is ever violated, the vault will still initialize and the inconsistency will be harder to detect. To preserve the invariant while handling the Slither warning, consider keeping the revert and suppressing the warning locally, or enforce uniqueness directly in `OrionConfig` instead.
</issue_to_address>

### Comment 3
<location> `test/OrionConfigVault.test.ts:387-396` </location>
<code_context>
+  describe("Redeem Request Cancellation", function () {
</code_context>

<issue_to_address>
**suggestion (testing):** Add negative tests for cancelling non-existent or other users' redeem requests

To make the suite more robust, please also cover:

1) Calling `cancelRedeemRequest` when the caller has no pending redeem, asserting the expected revert.
2) A different account attempting to cancel someone else’s pending redeem and verifying it reverts.

This will confirm the cancel logic is strictly limited to the original requester.

Suggested implementation:

```typescript
  describe("Redeem Request Cancellation", function () {
    beforeEach(async function () {
      // Setup: Give user shares by depositing and fulfilling
      const depositAmount = ethers.parseUnits("1000", 6);

      // Mint and approve underlying asset for user
      await underlyingAsset.mint(user.address, depositAmount);
      await underlyingAsset.connect(user).approve(await vault.getAddress(), depositAmount);

      // Request deposit
      await vault.connect(user).requestDeposit(depositAmount);
    });

    it("reverts when caller has no pending redeem", async function () {
      // user has gone through the standard deposit flow in beforeEach
      // but has NOT opened a redeem request yet
      await expect(
        vault.connect(user).cancelRedeemRequest(),
      ).to.be.reverted;
    });

    it("reverts when a different account attempts to cancel someone else's redeem", async function () {
      // Arrange: set up a pending redeem request for `user`
      const redeemAmount = ethers.parseUnits("100", 6);

      // Give user enough shares / liquidity to request a redeem
      // (the beforeEach has already provisioned the initial state)
      await vault.connect(user).requestRedeem(redeemAmount);

      // Sanity check: original requester can cancel successfully
      // (optional, but keeps behaviour explicit)
      await expect(
        vault.connect(user).cancelRedeemRequest(),
      ).to.not.be.reverted;

      // Re-open a redeem request for the same user to test the negative path
      await vault.connect(user).requestRedeem(redeemAmount);

      // Act & Assert: another account (e.g., `otherUser`) cannot cancel `user`'s redeem
      await expect(
        vault.connect(otherUser).cancelRedeemRequest(),
      ).to.be.reverted;
    });

```

The above edits assume:

1. `requestRedeem(uint256)` and `cancelRedeemRequest()` exist on `vault`, and `requestRedeem` takes the same decimal precision as `underlyingAsset`.
2. `otherUser` is already defined in the test fixture (e.g., from `const [deployer, user, otherUser, ...] = await ethers.getSigners();`).
3. A generic `.to.be.reverted` matcher is acceptable. If your contract uses specific custom errors or revert reasons (e.g. `NoPendingRedeem` or `UnauthorizedRedeemCancel`), you should tighten the expectations, for example:
   - `await expect(...).to.be.revertedWithCustomError(vault, "NoPendingRedeem");`
   - `await expect(...).to.be.revertedWithCustomError(vault, "UnauthorizedRedeemCancel");`

If the actual method names or arguments differ (e.g. `requestRedeemShares`, `cancelPendingRedeem`, or a struct-based API), update the two new tests to match the real interface and any existing helper functions used elsewhere in the suite to open redeem requests.
</issue_to_address>

### Comment 4
<location> `test/OrionConfigVault.test.ts:495-497` </location>
<code_context>
+      const sharesAfterPartialCancel = await vault.balanceOf(user.address);
+      expect(sharesAfterPartialCancel).to.equal(userSharesBefore - remainingRedeem);
+
+      // Verify pending redeems reflects the remaining amount
+      const pendingRedeems = await vault.pendingRedeem(await orionConfig.maxFulfillBatchSize());
+      expect(pendingRedeems).to.be.gte(remainingRedeem);
+    });
</code_context>

<issue_to_address>
**suggestion (testing):** Tighten assertion on pendingRedeem after partial cancellation

Given this test only creates a single redeem request for this user, `pendingRedeems` should match `remainingRedeem` exactly. Using `expect(pendingRedeems).to.equal(remainingRedeem);` would make the test stricter and better at catching cases where extra shares are incorrectly kept pending. If `pendingRedeems` is expected to aggregate other users’ requests, it’d be clearer to set up that scenario explicitly and assert on the exact expected sum rather than using `>=`.

```suggestion
      // Verify pending redeems matches the remaining amount for this single request
      const pendingRedeems = await vault.pendingRedeem(await orionConfig.maxFulfillBatchSize());
      expect(pendingRedeems).to.equal(remainingRedeem);
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread contracts/vaults/OrionVault.sol
Comment thread contracts/vaults/OrionVault.sol
Comment thread test/OrionConfigVault.test.ts
Comment thread test/OrionConfigVault.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (3)
test/mainnet-fork/multiAssetRobustness.test.ts (1)

323-397: Critical: Function name doesn't match implementation.

The function is named deployUpgradeableProtocol but deploys non-upgradeable contracts using regular deploy() instead of upgrades.deployProxy(). This contradicts the PR objective of migrating to upgradeable architecture.

Compare with the proper upgradeable deployment in test/helpers/deployUpgradeable.ts (lines 44-140), which uses:

  • upgrades.deployProxy() with kind: "uups" for UUPS proxies
  • UpgradeableBeacon for vault beacon pattern

Either:

  1. Import and use the actual deployUpgradeableProtocol from test/helpers/deployUpgradeable.ts, or
  2. Revert the function name to deployProtocol if this test intentionally uses non-upgradeable contracts
test/ExecutionAdapterValidation.test.ts (1)

31-50: Potential decimal mismatch in initial deposit.

The deployUpgradeableProtocol helper creates a MockUnderlyingAsset with 6 decimals by default (USDC-like), but line 47 uses ethers.parseUnits("10000", 12) with 12 decimals. This mismatch means the deposit amount is 10^6 times larger than intended.

🔎 Proposed fix
-    const initialDeposit = ethers.parseUnits("10000", 12);
+    const decimals = await underlyingAsset.decimals();
+    const initialDeposit = ethers.parseUnits("10000", decimals);

Alternatively, query the decimals from the deployed asset to ensure consistency.

package.json (1)

1-111: Fix Prettier formatting to resolve pipeline failure.

The CI pipeline reports a Prettier formatting check failure for this file. Run pnpm prettier:write to fix the formatting.

#!/bin/bash
# Check the actual prettier differences
npx prettier --check "package.json" 2>&1 || true
🧹 Nitpick comments (10)
test/OrionVaultExchangeRate.test.ts (1)

41-58: Event parsing logic is robust; minor redundancy noted.

The try/catch pattern for parsing logs is defensive. However, line 55 uses non-null assertion (event!) after the null check on line 51, which is safe but redundant since the code would have thrown before reaching line 55 if event were null.

Minor cleanup suggestion
-    const parsedEvent = factory.interface.parseLog(event!);
+    const parsedEvent = factory.interface.parseLog(event);

Since the error is thrown on line 52 if event is falsy, the non-null assertion is unnecessary.

test/Upgrade.test.ts (2)

152-166: Event parsing pattern differs from other test files.

This file uses log.fragment?.name === "OrionVaultCreated" while other test files (e.g., OrionVaultExchangeRate.test.ts) use factory.interface.parseLog(log)?.name. The fragment property may not be present on all log types, making this approach less robust.

Consider using consistent event parsing pattern
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      const vault1Address = receipt1?.logs.find((log: any) => log.fragment?.name === "OrionVaultCreated")?.args?.[0];
+      const vault1Address = receipt1?.logs.find((log) => {
+        try {
+          return vaultFactory.interface.parseLog(log)?.name === "OrionVaultCreated";
+        } catch {
+          return false;
+        }
+      });
+      const parsedLog1 = vault1Address ? vaultFactory.interface.parseLog(vault1Address) : null;
+      const vault1Addr = parsedLog1?.args[0];

This pattern with try/catch is more robust and consistent with other test files in this PR. Consider extracting a helper function to avoid repetition.


95-101: Access control test should verify the specific error.

The test verifies that non-owner upgrade attempts are reverted, but the assertion uses .to.be.reverted without checking the specific error. For better test precision, consider matching the expected error.

More precise error assertion
-      await expect(upgrades.upgradeProxy(proxyAddress, OrionConfigV2Factory.connect(user))).to.be.reverted;
+      await expect(upgrades.upgradeProxy(proxyAddress, OrionConfigV2Factory.connect(user)))
+        .to.be.revertedWithCustomError(orionConfig, "OwnableUnauthorizedAccount");
test/VaultOwnerRemoval.test.ts (1)

98-116: Test assertions use void expect(...) pattern.

The void expect(...) pattern is used throughout the test assertions. While this works, it's unconventional. The void operator discards the return value but doesn't affect the assertion behavior. This appears to be a style choice, possibly to satisfy a linter rule about floating promises, though Chai assertions are synchronous.

test/RedeemBeforeDepositOrder.test.ts (1)

237-237: Console.log statements should be removed for production tests.

Multiple console.log statements are present in the test file (lines 237, 245, 272, 273, 284, 300, 301, 306, 325, 361, 390-392). While useful for debugging, these should typically be removed or converted to debug-level output for cleaner test runs.

Also applies to: 245-245, 272-273

test/mainnet-fork/erc4626VaultCompatibility.test.ts (1)

607-631: Whitelist cleanup should handle test failures gracefully.

The whitelist addition and removal are done inline within the try block. If validateExecutionAdapter fails, the vault won't be removed from the whitelist, potentially affecting subsequent tests.

Consider moving the cleanup to a finally block:

🔎 Suggested improvement
         try {
           // CRITICAL: Whitelist the vault first so OrionConfig has its decimals
           await orionConfig
             .connect(owner)
             .addWhitelistedAsset(
               vaultInfo.address,
               await priceAdapter.getAddress(),
               await executionAdapter.getAddress(),
             );

           console.log(`   ✓ Vault whitelisted in OrionConfig`);

           // Validate that execution adapter can validate the vault
           await executionAdapter.validateExecutionAdapter(vaultInfo.address);
           console.log(`   ✓ Execution adapter validation: PASS`);
-
-          // Clean up: Remove from whitelist for next test
-          await orionConfig.connect(admin).removeWhitelistedAsset(vaultInfo.address);
-          console.log(`   ✓ Vault removed from whitelist`);
         } catch (error: unknown) {
           const errorMessage = error instanceof Error ? error.message : String(error);
           throw new Error(`${vaultInfo.name}: execution adapter validation failed - ${errorMessage}`);
+        } finally {
+          // Clean up: Remove from whitelist for next test (even on failure)
+          try {
+            await orionConfig.connect(admin).removeWhitelistedAsset(vaultInfo.address);
+            console.log(`   ✓ Vault removed from whitelist`);
+          } catch {
+            // Ignore cleanup errors
+          }
         }
test/MinimumAmountDOS.test.ts (1)

174-210: Comprehensive spam prevention test, but may be slow.

This test creates 160 attacker accounts to validate DOS prevention at scale. While thorough, this may significantly slow down test execution. Consider:

  1. Adding a this.timeout() if running in Mocha to prevent timeout failures
  2. Or adding a .skip condition for CI with a smaller subset test

The security validation is valuable, so keeping it as-is for comprehensive coverage is acceptable if test duration is not a concern.

test/BatchLimitConsistency.test.ts (1)

152-178: Consider adding a skip condition when effectiveBatchSize cannot meaningfully test the batch limit.

When numUsers <= 2, the effectiveBatchSize becomes max(1, numUsers - 2) which is 1 or less, making the test less meaningful. Consider adding a skip condition or minimum user check.

🔎 Optional: Add skip condition for insufficient signers
     it("Should return ONLY first maxFulfillBatchSize requests when requests exceed limit", async function () {
       // NOTE: Hardhat provides limited signers (~18-20), so we can't truly test 150+ requests
       // This test verifies the batch limiting logic works with available signers
       const excessUsers = 5;
       const numUsers = Math.min(maxFulfillBatchSize + excessUsers, users.length);

       // For this test to be meaningful, we need more requests than batch size
       // If we don't have enough signers, we use a smaller batch size for testing
       const effectiveBatchSize = Math.min(maxFulfillBatchSize, Math.max(1, numUsers - 2));

+      // Skip if we can't create a meaningful excess scenario
+      if (effectiveBatchSize >= numUsers) {
+        this.skip();
+      }
+
       // Create deposit requests exceeding effective batch limit
contracts/orchestrators/InternalStatesOrchestrator.sol (1)

33-40: Consider contract decomposition in future iterations.

Static analysis flags 23 state declarations (vs 15 recommended limit). Given the orchestrator's complex responsibilities, this is acceptable for now, but consider splitting epoch management or fee processing into separate contracts in future refactors.

contracts/factories/TransparentVaultFactory.sol (1)

107-113: Consider adding system idle check for beacon updates.

Updating the vault beacon changes the implementation for all existing vaults on their next call. While this is standard beacon behavior, consider adding if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle(); to prevent mid-epoch upgrades that could cause unexpected behavior.

🔎 Proposed fix
 function setVaultBeacon(address newVaultBeacon) external onlyOwner {
     if (newVaultBeacon == address(0)) revert ErrorsLib.ZeroAddress();
+    if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
     vaultBeacon = UpgradeableBeacon(newVaultBeacon);
     emit EventsLib.VaultBeaconUpdated(newVaultBeacon);
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ff5a8d and 2b13d25.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (54)
  • .github/workflows/ci.yml (1 hunks)
  • .gitignore (2 hunks)
  • Makefile (1 hunks)
  • contracts/OrionConfig.sol (4 hunks)
  • contracts/access_controllers/WhitelistAccessControl.sol (1 hunks)
  • contracts/execution/OrionAssetERC4626ExecutionAdapter.sol (1 hunks)
  • contracts/factories/TransparentVaultFactory.sol (3 hunks)
  • contracts/interfaces/IExecutionAdapter.sol (1 hunks)
  • contracts/interfaces/IInternalStateOrchestrator.sol (1 hunks)
  • contracts/interfaces/ILiquidityOrchestrator.sol (1 hunks)
  • contracts/interfaces/IOrionAccessControl.sol (1 hunks)
  • contracts/interfaces/IOrionConfig.sol (1 hunks)
  • contracts/interfaces/IOrionStrategy.sol (1 hunks)
  • contracts/interfaces/IOrionTransparentVault.sol (1 hunks)
  • contracts/interfaces/IOrionVault.sol (1 hunks)
  • contracts/interfaces/IPriceAdapter.sol (1 hunks)
  • contracts/interfaces/IPriceAdapterRegistry.sol (1 hunks)
  • contracts/libraries/ErrorsLib.sol (1 hunks)
  • contracts/libraries/EventsLib.sol (2 hunks)
  • contracts/libraries/UtilitiesLib.sol (1 hunks)
  • contracts/orchestrators/InternalStatesOrchestrator.sol (4 hunks)
  • contracts/orchestrators/LiquidityOrchestrator.sol (7 hunks)
  • contracts/price/OrionAssetERC4626PriceAdapter.sol (1 hunks)
  • contracts/price/PriceAdapterRegistry.sol (3 hunks)
  • contracts/strategies/KBestTvlWeightedAverage.sol (1 hunks)
  • contracts/test/OrionConfigV2.sol (1 hunks)
  • contracts/test/OrionTransparentVaultV2.sol (1 hunks)
  • contracts/vaults/OrionTransparentVault.sol (3 hunks)
  • contracts/vaults/OrionVault.sol (9 hunks)
  • package.json (3 hunks)
  • test/AccessControl.test.ts (2 hunks)
  • test/Adapters.test.ts (2 hunks)
  • test/BatchLimitAccounting.test.ts (2 hunks)
  • test/BatchLimitConsistency.test.ts (1 hunks)
  • test/ExecutionAdapterValidation.test.ts (1 hunks)
  • test/FeeCooldown.test.ts (2 hunks)
  • test/MinimumAmountDOS.test.ts (3 hunks)
  • test/OrionConfigVault.test.ts (5 hunks)
  • test/OrionVaultExchangeRate.test.ts (2 hunks)
  • test/PassiveCuratorStrategy.test.ts (4 hunks)
  • test/ProtocolPause.test.ts (3 hunks)
  • test/RedeemBeforeDepositOrder.test.ts (2 hunks)
  • test/Removal.test.ts (4 hunks)
  • test/TransparentVault.test.ts (2 hunks)
  • test/Upgrade.test.ts (1 hunks)
  • test/VaultOwnerRemoval.test.ts (2 hunks)
  • test/helpers/deployUpgradeable.ts (1 hunks)
  • test/mainnet-fork/erc4626VaultCompatibility.test.ts (8 hunks)
  • test/mainnet-fork/multiAssetRobustness.test.ts (2 hunks)
  • test/mainnet-fork/removeWhitelistedAsset.test.ts (1 hunks)
  • test/orchestrator/OrchestratorConfiguration.test.ts (4 hunks)
  • test/orchestrator/OrchestratorSecurity.test.ts (4 hunks)
  • test/orchestrator/Orchestrators.test.ts (3 hunks)
  • test/orchestrator/OrchestratorsZeroState.test.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
test/mainnet-fork/multiAssetRobustness.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/RedeemBeforeDepositOrder.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/TransparentVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/FeeCooldown.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/BatchLimitAccounting.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/orchestrator/OrchestratorConfiguration.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/orchestrator/OrchestratorsZeroState.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/BatchLimitConsistency.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/ExecutionAdapterValidation.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/Adapters.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/AccessControl.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/MinimumAmountDOS.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/Removal.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/OrionVaultExchangeRate.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/ProtocolPause.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/OrionConfigVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
test/VaultOwnerRemoval.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-141)
🪛 GitHub Actions: CI
contracts/factories/TransparentVaultFactory.sol

[warning] 1-1: SOLHINT warnings detected across Solidity sources (e.g., string length, max-states-count). Review and fix to reduce warnings.

package.json

[error] 1-1: Prettier formatting check failed. Run 'prettier --write' to fix code style issues in this file.

🪛 GitHub Check: Build, Lint and Test
contracts/factories/TransparentVaultFactory.sol

[warning] 75-75:
GC: String exceeds 32 bytes

contracts/orchestrators/InternalStatesOrchestrator.sol

[warning] 33-33:
Contract has 23 states declarations but allowed no more than 15

⏰ 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). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (99)
contracts/interfaces/IOrionAccessControl.sol (1)

9-9: Documentation addition approved.

Adding the security contact email to the interface NatSpec is a good practice that provides a clear channel for vulnerability reporting. The tag format and email address are correctly formed and align with the documentation pattern across the PR.

contracts/libraries/UtilitiesLib.sol (1)

9-9: Security contact metadata properly added.

The NatSpec @custom:security-contact annotation is a good addition that follows industry best practices and aligns with metadata augmentations across the PR.

.gitignore (1)

26-28: LGTM!

The new ignore entries align well with the upgradeable contract migration. Ignoring artifacts/, gasReporterOutput.json, and project-specific directories like res/ and running_node/ is standard practice for blockchain projects and prevents generated build/deployment artifacts from polluting version control.

Also applies to: 39-39, 41-41

contracts/interfaces/IOrionStrategy.sol (1)

11-11: LGTM! Security contact metadata added.

Adding the security contact tag is a best practice for production smart contracts, especially for upgradeable protocols. This metadata-only change has no functional impact on the interface.

contracts/interfaces/ILiquidityOrchestrator.sol (1)

10-10: Excellent addition of security contact information.

Adding a security contact to the interface documentation is a best practice for DeFi protocols, providing a clear channel for security researchers to report vulnerabilities responsibly.

contracts/strategies/KBestTvlWeightedAverage.sol (2)

16-16: LGTM! Good security practice.

Adding the security contact annotation is a best practice that helps security researchers and auditors report vulnerabilities responsibly.


18-34: No changes needed—strategy contracts are correctly designed as non-upgradeable.

Strategies like KBestTvlWeightedAverage are utility contracts called by upgradeable vaults to compute intents; they don't require upgradeable patterns themselves. The stateless IOrionStrategy interface keeps strategies lightweight and replaceable, making the non-upgradeable design the correct architectural choice. The upgradeable vault (OrionTransparentVault) and non-upgradeable strategy have no incompatibility.

contracts/price/OrionAssetERC4626PriceAdapter.sol (1)

1-59: No changes required. The adapter's constructor pattern is correct for its architectural role.

The PriceAdapterRegistry uses an upgradeable UUPS pattern and manages which price adapters are registered. Individual adapters like OrionAssetERC4626PriceAdapter are stateless utility contracts deployed directly (not via proxy) and only initialized once through their constructors. The state variables (config address, underlying asset, decimals) are read-only and never modified after deployment. This is the correct architecture: the upgradeable registry can point to different adapter implementations when needed, eliminating the need for individual adapters to be proxy-deployed or use initializer functions.

Likely an incorrect or invalid review comment.

contracts/libraries/ErrorsLib.sol (1)

7-7: LGTM!

Adding the @custom:security-contact NatSpec tag is a good practice for security disclosures and aligns with the broader effort across the codebase.

contracts/interfaces/IPriceAdapterRegistry.sol (1)

9-9: LGTM!

Security contact metadata addition is consistent with the protocol-wide documentation update.

contracts/interfaces/IPriceAdapter.sol (1)

7-7: LGTM!

Security contact metadata addition is consistent with the protocol-wide documentation update.

contracts/access_controllers/WhitelistAccessControl.sol (1)

11-11: LGTM!

Security contact metadata addition is consistent with the protocol-wide documentation update.

test/Removal.test.ts (2)

3-6: LGTM!

Clean migration to use the centralized deployUpgradeableProtocol helper, reducing boilerplate and ensuring consistent upgradeable deployment across tests.


75-80: Verify user is the intended admin for this test.

The deployUpgradeableProtocol call passes user as the second parameter (admin role). This appears intentional since removeWhitelistedAsset is later called by user (lines 207, 335), but confirm this aligns with the expected access control for asset removal.

contracts/orchestrators/LiquidityOrchestrator.sol (4)

4-8: LGTM!

Correct upgradeable imports from OpenZeppelin contracts-upgradeable package, along with SafeCast for safe integer conversions.

Also applies to: 21-22


130-160: LGTM!

The upgradeable initialization pattern is correctly implemented:

  • Constructor disables initializers on the implementation contract
  • initialize function has the initializer modifier
  • All parent initializers are called in the correct order
  • Zero-address validation is present for all parameters

400-401: LGTM!

Good use of SafeCast.toInt256() for safe conversion. This prevents silent overflow (though practically impossible for token amounts) and makes the conversion intent explicit.

Also applies to: 424-425


504-512: _authorizeUpgrade implementation is correct.

The empty function with onlyOwner modifier properly restricts upgrades to the owner per UUPS pattern. The 50-slot storage gap follows OpenZeppelin conventions and supports future extensibility.

test/mainnet-fork/removeWhitelistedAsset.test.ts (1)

275-283: LGTM!

The addition of ethers.ZeroAddress as the depositAccessControl parameter correctly aligns with the updated TransparentVaultFactory.createVault signature, which uses address(0) to indicate permissionless mode.

contracts/interfaces/IOrionConfig.sol (1)

10-10: LGTM! Security contact metadata added.

The addition of the security contact NatSpec tag is a good practice for protocol security documentation.

contracts/interfaces/IInternalStateOrchestrator.sol (1)

10-10: LGTM! Security contact metadata added.

contracts/interfaces/IExecutionAdapter.sol (1)

12-12: LGTM! Security contact metadata added.

contracts/interfaces/IOrionVault.sol (1)

11-11: LGTM! Security contact metadata added.

contracts/execution/OrionAssetERC4626ExecutionAdapter.sol (1)

20-20: LGTM! Security contact metadata added.

.github/workflows/ci.yml (1)

34-36: LGTM! Dependency audit added to CI pipeline.

Adding pnpm audit --prod --audit-level high enhances security by catching high-severity vulnerabilities in production dependencies before they reach deployment.

test/mainnet-fork/multiAssetRobustness.test.ts (1)

236-236: Verify: Test may not be using upgradeable contracts.

The call to deployUpgradeableProtocol() references a local function (line 323) that doesn't actually deploy upgradeable contracts, despite its name. This test may not be validating the upgradeable architecture as intended.

contracts/libraries/EventsLib.sol (2)

7-7: LGTM! Security contact metadata added.


141-143: LGTM! New event supports upgradeable vault architecture.

The VaultBeaconUpdated event properly supports the Beacon Proxy pattern introduced in this PR, allowing observers to track when the vault implementation beacon is updated.

contracts/interfaces/IOrionTransparentVault.sol (1)

9-9: LGTM! Security contact metadata is a good addition.

Adding @custom:security-contact aligns with security best practices for upgradeable contracts, enabling responsible disclosure. This matches similar additions across other interfaces in this PR.

Makefile (1)

8-13: LGTM! The CI command sequence is well-structured.

The reordering is logical:

  1. audit first provides fail-fast on dependency vulnerabilities
  2. typechain before lint ensures TypeScript type definitions exist
  3. slither after lint ensures code is well-formed before static analysis
  4. test last as the most time-consuming step
test/FeeCooldown.test.ts (1)

30-51: LGTM! Clean migration to upgradeable protocol helper.

The fixture correctly uses deployUpgradeableProtocol(owner, owner) with owner as both owner and admin. The extracted components are properly typed and the fixture structure is clean.

Note: automationRegistry signer is obtained at line 31 but not passed to the helper. The helper defaults to using admin (i.e., owner) as automation registry, which is acceptable for these fee cooldown tests since they don't exercise automation-specific access control.

test/orchestrator/Orchestrators.test.ts (4)

118-142: Manual UUPS deployment is appropriate for this comprehensive test.

Unlike simpler test files that use deployUpgradeableProtocol helper, this test maintains manual deployment for granular control over:

  • Custom mock assets with specific decimals
  • Complex deposit/gain/loss simulation scenarios
  • Fine-grained phase transition testing

The deployment pattern correctly follows UUPS initialization with kind: "uups" and proper initializer functions.


150-167: LGTM! Beacon and factory deployment follows correct upgrade pattern.

The deployment correctly:

  1. Deploys vault implementation contract
  2. Creates UpgradeableBeacon pointing to implementation with owner as upgrade authority
  3. Deploys TransparentVaultFactory as UUPS proxy with beacon address

Using the full contract path @openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol:UpgradeableBeacon avoids potential contract name ambiguity.


682-698: Comprehensive upkeep cycle testing with proper phase transitions.

The test correctly validates the full upkeep lifecycle:

  • Phase transition verification (Idle → PreprocessingTransparentVaults → Buffering → etc.)
  • Fee cooldown duration consideration before triggering upkeep
  • Proper use of automation registry for performUpkeep calls

The pattern of processing phases in while loops until transition ensures complete phase processing regardless of minibatch sizes.


2702-2730: LGTM! DOS attack protection tests are well-designed.

The test suite correctly validates:

  • Owner-only access control for configuring minimum amounts
  • Rejection of deposits/redemptions below minimum thresholds
  • Event emission on configuration changes

The economic infeasibility calculation at lines 2802-2819 provides good documentation of the attack cost with minimum deposit requirements.

contracts/test/OrionConfigV2.sol (1)

12-31: LGTM! Well-structured mock V2 for upgrade testing.

The mock V2 implementation correctly:

  • Extends OrionConfig to inherit UUPS upgrade functionality
  • Adds new state variable and event for verifying upgrade behavior
  • Applies onlyOwner access control on the setter
  • Provides a version() function for upgrade verification in tests

OrionConfig has adequate storage gap (50 uint256 slots reserved), so newV2Variable will not cause storage collisions during upgrade.

contracts/test/OrionTransparentVaultV2.sol (1)

12-31: Storage gap verification confirmed—safe for upgrade.

OrionTransparentVault has a uint256[50] __gap storage slot reservation, providing adequate space for the new vaultDescription string variable in V2. The implementation correctly follows the beacon proxy upgrade pattern, mirroring the OrionConfigV2 approach with onlyVaultOwner access control and version detection for upgrade verification.

test/orchestrator/OrchestratorsZeroState.test.ts (1)

33-42: No changes needed—admin parameter usage is intentional.

The test deliberately passes user as the admin to OrionConfig initialization. This is a valid test scenario where the admin differs from the owner; other tests in the suite similarly use different admin values (owner, other, attacker, etc.) to test various authorization scenarios.

test/OrionVaultExchangeRate.test.ts (2)

1-5: Clean migration to upgradeable protocol helper.

The imports and helper integration are correctly set up. The @openzeppelin/hardhat-upgrades import enables the upgrades plugin, and the centralized deployment helper simplifies the test setup.


19-25: Using attacker as admin parameter is intentional but worth documenting.

The deployUpgradeableProtocol(owner, attacker) call passes attacker as the admin parameter. Per the helper signature, admin is used as the default automation registry if not provided. For these tests focused on inflation attacks, this appears intentional since attacker doesn't need admin privileges for the test scenarios.

test/Upgrade.test.ts (3)

1-23: Comprehensive upgrade test suite with good structure.

The test file properly covers all three upgrade patterns (UUPS, Beacon Proxy, Factory) with appropriate test cases for state preservation, access control, storage gaps, and event emissions.


335-394: Good test for dynamic beacon replacement scenario.

This test correctly verifies that vaults created before and after setVaultBeacon use different implementations. The assertion that vault1 (V1) doesn't have version() is validated indirectly by checking vaultOwner() instead, which is a reasonable approach.


443-504: Integration test covering factory UUPS upgrade with beacon changes.

This test validates the complex scenario of upgrading the factory itself via UUPS while also swapping the vault beacon. Good coverage of the combined upgrade path.

test/Adapters.test.ts (2)

28-50: Intentional type cast for testing adapter validation.

The cast of MockUnderlyingAsset to MockERC4626Asset (line 42) is documented and intentional. This allows testing that adapters correctly reject plain ERC20 tokens that don't implement the ERC4626 interface. The comment at line 41 makes this clear.


31-35: Correct usage of deployUpgradeableProtocol with automation registry.

The helper is correctly invoked with automationRegistry as the fourth parameter, ensuring the orchestrators are properly configured for the adapter tests.

test/VaultOwnerRemoval.test.ts (2)

31-37: Using owner as both owner and admin is appropriate for these tests.

The deployUpgradeableProtocol(owner, owner) call uses the same signer for both owner and admin roles. This is acceptable for vault owner removal tests where the admin role distinction isn't being tested.


57-87: Well-structured vault creation helper with proper event parsing.

The createVault helper function encapsulates the factory call and event parsing cleanly. The try/catch pattern for log parsing is consistent with best practices seen in other test files.

test/RedeemBeforeDepositOrder.test.ts (2)

95-127: Correct integration with custom underlying asset.

The test correctly deploys its own underlyingAsset (line 100) and passes it to deployUpgradeableProtocol (line 116) to maintain precise control over decimal precision for share/asset calculations. The automationRegistry is also correctly passed to enable orchestrator interactions.


145-156: Event parsing consistent with other test files.

The vault creation and event parsing pattern matches the robust try/catch approach used in other test files. The non-null assertion on event! at line 154 is safe given the parsing context.

test/AccessControl.test.ts (1)

26-37: LGTM - Clean refactoring to use centralized deployment helper.

The test setup is correctly simplified by using deployUpgradeableProtocol. Passing owner for both owner and admin parameters is appropriate for this access control test context.

test/mainnet-fork/erc4626VaultCompatibility.test.ts (2)

188-225: Mainnet fork test uses constructor-based deployment instead of upgradeable pattern.

This test deploys contracts directly with constructors (e.g., OrionConfigFactory.deploy(...)) rather than using the upgradeable proxy pattern (upgrades.deployProxy) that the rest of the codebase has migrated to.

This inconsistency may be intentional for mainnet fork testing scenarios, but it means this test won't validate the upgradeable deployment path. Consider whether this test should also use deployUpgradeableProtocol or a similar upgradeable deployment approach for consistency.


317-377: Well-designed immutability verification tests.

The enhanced immutability checks are thorough:

  • Bytecode consistency across blocks
  • EIP-1967 implementation slot detection for proxies
  • Cross-block consistency for asset() and decimals()
  • Determinism validation within the same block

This is a solid approach for validating that ERC4626 vaults maintain immutable properties critical for Orion protocol integration.

test/orchestrator/OrchestratorConfiguration.test.ts (1)

119-188: LGTM - Proper customization of upgradeable deployment for orchestrator tests.

The test correctly:

  1. Deploys a custom MockUnderlyingAsset with 12 decimals before calling the helper
  2. Passes the custom underlying asset and automationRegistry to deployUpgradeableProtocol
  3. Extracts needed components from the deployed bundle

This maintains test-specific requirements while leveraging the centralized deployment helper.

contracts/price/PriceAdapterRegistry.sol (3)

36-55: LGTM - Correct UUPS initialization pattern.

The implementation correctly:

  • Disables initializers in the constructor to prevent implementation contract takeover
  • Uses initializer modifier on the initialize function
  • Validates zero addresses before setting state
  • Calls all required parent initializers (__Ownable_init, __Ownable2Step_init, __UUPSUpgradeable_init)

76-83: LGTM - Proper UUPS upgrade authorization and storage gap.

The _authorizeUpgrade function correctly restricts upgrades to the owner only. The 50-slot storage gap follows OpenZeppelin conventions and provides adequate space for future state additions.

Consider adding a newImplementation parameter validation if you want to enforce specific upgrade constraints in the future, though the current empty implementation is acceptable for standard UUPS usage.


21-30: Storage layout correctly implements UUPS upgrade safety.

The contract properly declares state variables (configAddress, priceAdapterDecimals, adapterOf) before the 50-slot storage gap at the end. The constructor correctly disables initializers, the initialize function calls all parent contract initializers, and _authorizeUpgrade is properly protected with onlyOwner. No changes needed.

test/MinimumAmountDOS.test.ts (1)

20-72: LGTM - Clean refactoring of fixture to use upgradeable deployment.

The fixture properly:

  1. Uses deployUpgradeableProtocol for centralized deployment
  2. Extracts necessary components from the deployed bundle
  3. Uses VaultFactory.attach(vaultAddress) which works correctly with beacon proxies since the ABI is the same
test/BatchLimitConsistency.test.ts (4)

1-23: LGTM: Well-structured test helper and imports.

The impersonation helper is correctly implemented using @nomicfoundation/hardhat-network-helpers. The imports are appropriate for the test requirements.


88-133: LGTM: Clean test setup using the centralized deployment helper.

The beforeEach correctly utilizes deployUpgradeableProtocol and properly extracts the vault address from the OrionVaultCreated event. The vault creation parameters are appropriate for testing.


196-217: LGTM: Proper setup for redeem tests.

The nested beforeEach correctly establishes the precondition of users having shares before testing redeem functionality. The flow of deposit → fulfill → shares is properly implemented.


283-356: LGTM: Comprehensive tests for the critical accounting fix.

These tests effectively validate that pendingDeposit and pendingRedeem return only the processable amount (up to maxFulfillBatchSize), preventing the double-counting bug described in the file header.

test/ProtocolPause.test.ts (3)

48-48: LGTM: Import updated for upgradeable deployment helper.

The import correctly references the centralized deployment helper.


89-95: LGTM: Clean migration to upgradeable protocol deployment.

The test correctly uses deployUpgradeableProtocol with the automationRegistry parameter and properly extracts the required components (underlyingAsset, orionConfig, internalStatesOrchestrator, liquidityOrchestrator).


135-136: LGTM: Vault factory sourced from deployed protocol.

Correctly obtains transparentVaultFactory from the deployed protocol helper instead of deploying separately.

test/BatchLimitAccounting.test.ts (2)

3-6: LGTM: Updated imports for upgradeable protocol testing.

The imports correctly include the OpenZeppelin upgrades plugin and the centralized deployment helper.


23-76: LGTM: Fixture properly migrated to use deployUpgradeableProtocol.

The fixture correctly:

  1. Uses deployUpgradeableProtocol for protocol deployment
  2. Extracts all required components from the deployed object
  3. Maintains the vault creation and user funding logic
  4. Returns a comprehensive object for test consumption
contracts/OrionConfig.sol (4)

4-6: LGTM: Correct imports for UUPS upgradeable pattern.

The imports include all necessary OpenZeppelin upgradeable contracts: Initializable, Ownable2StepUpgradeable, and UUPSUpgradeable.


100-104: LGTM: Correct UUPS constructor pattern.

The constructor properly disables initializers for the implementation contract, preventing direct initialization of the implementation. The @custom:oz-upgrades-unsafe-allow constructor annotation is correctly placed.


113-137: LGTM: Well-structured initializer with proper validation and setup.

The initialize function:

  1. Validates all critical addresses are non-zero
  2. Correctly initializes __Ownable_init, __Ownable2Step_init, and __UUPSUpgradeable_init
  3. Sets reasonable defaults for protocol parameters
  4. Properly whitelists the underlying asset and initial owner

453-460: LGTM: Proper upgrade authorization and storage gap.

The _authorizeUpgrade function correctly restricts upgrades to the owner, and the 50-slot storage gap (__gap) follows OpenZeppelin's recommended practice for upgradeable contracts, allowing future state variables to be added without storage collisions.

test/OrionConfigVault.test.ts (4)

3-17: LGTM: Updated imports for upgradeable protocol testing.

The imports correctly include OpenZeppelin upgrades plugin and the necessary helpers (deployUpgradeableProtocol, impersonateAccount, setBalance).


36-41: LGTM: Correctly uses deployUpgradeableProtocol with owner and admin signers.

The deployment uses owner as the protocol owner and other as the admin, which aligns with the test's access control scenarios (e.g., other calling admin-only functions like removeWhitelistedAsset).


387-414: LGTM: Proper setup for redeem cancellation tests using impersonation.

The beforeEach correctly:

  1. Deposits and approves underlying assets
  2. Funds the LiquidityOrchestrator
  3. Impersonates the LO to call fulfillDeposit
  4. Properly stops impersonation after use

This gives the user shares needed for testing redeem functionality.


444-498: LGTM: Comprehensive redeem cancellation tests.

The tests cover the critical scenarios:

  1. Successful full cancellation with proper share restoration
  2. Partial cancellation with correct remaining balance verification
  3. Assertions verify both user share balances and pending redeem amounts

The math operations use BigInt correctly (e.g., userShares / 2n, (redeemAmount * 3n) / 10n).

test/ExecutionAdapterValidation.test.ts (1)

1-67: LGTM - Clean refactor to upgradeable deployment helper.

The migration to deployUpgradeableProtocol simplifies the test setup significantly. The test correctly extracts underlyingAsset, orionConfig, and liquidityOrchestrator from the deployed bundle and proceeds with ERC4626 vault setup.

package.json (1)

82-110: Good security practice with pnpm overrides.

The pnpm overrides correctly patch vulnerable OpenZeppelin versions (4.3.0-4.8.3) in transitive dependencies to ^4.9.6. This addresses known security vulnerabilities in older OZ versions without affecting your direct v5.4.0 dependencies.

test/orchestrator/OrchestratorSecurity.test.ts (2)

142-212: Clean migration to upgradeable deployment pattern.

The beforeEach setup correctly:

  1. Deploys the underlying asset with 12 decimals first
  2. Sets up mock ERC4626 assets with initial deposits
  3. Passes the pre-deployed underlyingAsset and automationRegistry to the helper
  4. Extracts the required contracts from the deployed bundle

The security test logic for malicious payload protection remains intact.


206-212: No action needed. The user parameter passed as the admin role is an intentional pattern used consistently across multiple test files (Removal.test.ts, PassiveCuratorStrategy.test.ts, OrchestratorConfiguration.test.ts). This design is appropriate for security tests that validate orchestrator resilience against malicious payloads, rather than testing admin-specific permissions.

contracts/vaults/OrionTransparentVault.sol (2)

34-77: Correct UUPS upgradeable pattern implementation.

The implementation follows OpenZeppelin's recommended upgrade pattern:

  • Constructor with _disableInitializers() prevents implementation contract initialization
  • initialize() function with initializer modifier ensures one-time setup
  • Delegates to __OrionVault_init for parent initialization before setting contract-specific state

223-226: Storage gap correctly implemented for upgrade safety.

The uint256[50] private __gap provides 50 storage slots for future state variables, following OpenZeppelin's upgrade-safe storage pattern. This allows adding new state variables in future versions without storage collision.

test/PassiveCuratorStrategy.test.ts (2)

42-113: Clean refactor to upgradeable deployment pattern.

The test setup correctly:

  1. Deploys underlying asset with 12 decimals before calling the helper
  2. Sets up 4 mock ERC4626 assets with different TVLs for strategy testing
  3. Passes pre-deployed underlyingAsset and automationRegistry to the helper
  4. Extracts required contracts from the deployment bundle

Note: Same observation as OrchestratorSecurity.test.ts - user is passed as the admin argument to deployUpgradeableProtocol. If this is intentional, consider adding a clarifying comment.


1-211: LGTM - Well-structured test migration.

The refactoring successfully consolidates protocol deployment while maintaining comprehensive test coverage for:

  • Strategy configuration (k parameter)
  • Intent computation and weight distribution
  • Vault integration with strategy
  • Parameter updates and whitelist validation
  • Error handling edge cases
test/TransparentVault.test.ts (2)

55-58: LGTM! Clean migration to centralized deployment helper.

The test correctly uses deployUpgradeableProtocol and extracts the required contracts. The underlyingAsset is properly passed to ensure consistent asset usage across the test.


1-5: Imports correctly set up for upgradeable testing.

The side-effect import of @openzeppelin/hardhat-upgrades registers the Hardhat upgrades plugin, and deployUpgradeableProtocol is properly imported from the helper module.

contracts/orchestrators/InternalStatesOrchestrator.sol (3)

168-173: Correct implementation of constructor for upgradeable contract.

The constructor properly calls _disableInitializers() to prevent initialization of the implementation contract, following OpenZeppelin's UUPS pattern.


179-205: Well-structured initialization function.

The initializer correctly:

  1. Validates all input addresses against zero address
  2. Calls all parent contract initializers in proper order
  3. Sets up contract state from the config contract

731-739: LGTM! Standard UUPS upgrade authorization and storage gap.

The _authorizeUpgrade function correctly restricts upgrades to the owner. The 50-slot storage gap follows OpenZeppelin's recommendation for upgradeable contracts.

test/helpers/deployUpgradeable.ts (4)

45-61: Well-designed helper with sensible defaults.

The function properly handles optional parameters with defaults (automationRegistry defaults to admin, underlying asset created if not provided). The 6-decimal mock asset correctly simulates USDC-like tokens.


80-100: Deployment sequence correctly handles cross-contract dependencies.

The comment on line 80 correctly documents the critical ordering: LiquidityOrchestrator must be deployed and registered in config before InternalStatesOrchestrator, since the latter reads liquidityOrchestrator from config during initialization.


102-114: Correct Beacon Proxy pattern implementation.

The vault implementation is deployed directly (not as a proxy), then the UpgradeableBeacon is created pointing to this implementation. This allows future vault upgrades by updating the beacon.


149-152: Useful helper for vault interaction in tests.

The attachToVault function provides a clean way to get a typed contract instance for vaults created via the factory's BeaconProxy pattern.

contracts/factories/TransparentVaultFactory.sol (3)

28-49: Correct upgradeable initialization pattern.

The constructor properly disables initializers, and initialize validates all inputs and calls parent initializers in the correct order.


74-90: Correct BeaconProxy deployment pattern.

The vault creation correctly encodes the initialization call and deploys a BeaconProxy pointing to the shared vault beacon. The static analysis warning about string length (function signature) is a false positive — function signatures commonly exceed 32 bytes and this is expected.


115-122: LGTM! Standard UUPS authorization and storage gap.

The upgrade authorization and storage gap follow OpenZeppelin's recommended patterns.

contracts/vaults/OrionVault.sol (5)

158-162: Correct pattern for abstract upgradeable contract.

The constructor properly disables initializers on the implementation, preventing direct initialization of the base contract.


174-216: Well-implemented internal initializer for abstract base.

The __OrionVault_init function correctly uses internal onlyInitializing modifier pattern for abstract upgradeable contracts. All parent initializers (__ERC20_init, __ERC4626_init, __ReentrancyGuard_init) are called, and comprehensive input validation is performed.


229-286: ERC4626 overrides correctly reference upgradeable base.

The function overrides properly specify both ERC4626Upgradeable and the interface (IERC4626 or IERC20Metadata) to satisfy Solidity's override requirements for the upgradeable inheritance chain.


221-227: Correct whitelist initialization pattern.

The slither-disable comment for unused-return on line 224 is appropriate since the return value of add() is safely ignorable during initial population of an empty set.


738-740: Storage gap correctly placed.

The 50-slot storage gap at the end of the abstract contract reserves space for future state variables. Storage gaps in storage layout are recommended in order to safely add storage variables when using inheritance. The derived contract OrionTransparentVault also includes its own storage gap, following best practices for upgradeable contract design.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
package.json (1)

102-109: Consider addressing deprecated package dependencies.

The allowedDeprecatedVersions section permits several deprecated packages (glob, inflight, rimraf, lodash.isequal, zksync-web3). While this suppresses warnings, deprecated packages may have:

  • Unpatched security vulnerabilities
  • Compatibility issues with future Node.js versions
  • Missing features or bug fixes

Consider investigating which dependencies introduce these deprecated packages and updating them if newer versions are available.

#!/bin/bash
# Description: Identify which dependencies introduce deprecated packages

echo "=== Checking dependency tree for deprecated packages ==="
for pkg in "glob" "inflight" "rimraf" "lodash.isequal" "zksync-web3"; do
  echo -e "\n--- Checking: $pkg ---"
  pnpm why "$pkg" 2>/dev/null || echo "Not found in current dependencies"
done
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b13d25 and 0d70e84.

📒 Files selected for processing (1)
  • package.json (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). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Build, Lint and Test
🔇 Additional comments (3)
package.json (3)

4-4: LGTM: Version bump aligns with major architectural changes.

The major version bump to 1.0.0 appropriately reflects the introduction of upgradeability patterns (UUPS, Beacon) and significant protocol architecture changes described in the PR objectives.


40-40: Verify package version specification in package.json (line 40).

The version ^4.0.0 referenced does not exist for @nomicfoundation/hardhat-toolbox. The current stable version is 6.1.0. Verify whether this entry targets the correct package or requires updating to an actual released version.


82-86: The pnpm overrides won't result in multiple OZ versions; overrides globally enforce a single version across the dependency graph.

The override pattern @openzeppelin/contracts@>=4.3.0 <4.8.3 targets transitive dependencies in that version range and replaces them all with ^4.9.6. If you have a direct dependency on a different major version (e.g., ^5.4.0), both versions may exist—but this is not caused by the override mechanism; it's the natural result of having direct dependencies requiring different versions.

Verify whether dependencies actually require v4.x OpenZeppelin contracts and consider upgrading them if possible. If mixing v4.x and v5.x is intentional, ensure storage layouts are compatible for your use case.

Comment thread package.json
@codecov

codecov Bot commented Dec 20, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/OrionConfigVault.test.ts (2)

388-414: Approve setup with optional helper extraction suggestion.

The beforeEach setup correctly uses impersonation to enable deposit fulfillment and give the user shares for testing redeem cancellation. The pattern (impersonate → fund gas → call → stop impersonation) is sound.

💡 Optional: Extract impersonation pattern to helper function

Consider extracting the impersonation pattern into a reusable helper function to improve maintainability and reduce duplication if this pattern is used elsewhere:

async function fulfillDepositAsOrchestrator(
  vault: OrionTransparentVault,
  liquidityOrchestrator: LiquidityOrchestrator,
  amount: bigint
) {
  const loAddress = await liquidityOrchestrator.getAddress();
  await impersonateAccount(loAddress);
  await setBalance(loAddress, ethers.parseEther("1"));
  const loSigner = await ethers.getSigner(loAddress);
  
  await vault.connect(loSigner).fulfillDeposit(amount);
  
  await ethers.provider.send("hardhat_stopImpersonatingAccount", [loAddress]);
}

Then use it as:

await fulfillDepositAsOrchestrator(vault, liquidityOrchestrator, depositAmount);

444-471: Consider tightening assertion at line 459 for consistency.

The test correctly verifies full redeem request cancellation. However, line 459 uses gte (greater than or equal) when checking pendingRedeems, while the partial cancellation test at line 497 uses strict equal after being corrected per past review feedback.

Since this beforeEach creates a clean state with only one user making a single redeem request, pendingRedeems should match redeemAmount exactly.

🔎 Suggested consistency fix
      // Verify redeem request was created
      const pendingRedeems = await vault.pendingRedeem(await orionConfig.maxFulfillBatchSize());
-     expect(pendingRedeems).to.be.gte(redeemAmount);
+     expect(pendingRedeems).to.equal(redeemAmount);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d70e84 and b9d06bc.

📒 Files selected for processing (3)
  • test/OrionConfigVault.test.ts (5 hunks)
  • test/OrionVaultExchangeRate.test.ts (2 hunks)
  • test/Upgrade.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/OrionVaultExchangeRate.test.ts
  • test/Upgrade.test.ts
⏰ 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). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Build, Lint and Test
🔇 Additional comments (3)
test/OrionConfigVault.test.ts (3)

3-3: LGTM! Appropriate imports for upgradeable protocol testing.

The addition of @openzeppelin/hardhat-upgrades, the centralized deployUpgradeableProtocol helper, and Hardhat network helpers for impersonation correctly support the new upgradeable architecture and test flow.

Also applies to: 16-17


36-41: LGTM! Centralized deployment improves test maintainability.

The adoption of deployUpgradeableProtocol consolidates deployment logic and eliminates manual contract wiring, making tests more maintainable and consistent across the suite.


473-534: LGTM! Past review feedback properly addressed.

The partial cancellation test now uses strict equality at line 497 (addressing previous review feedback), and both negative test cases from past reviews are properly implemented:

  1. Lines 500-510: Verifies that attempting to cancel when no pending redeem exists reverts with InsufficientAmount.
  2. Lines 512-534: Verifies that a different account cannot cancel someone else's pending redeem request.

All tests correctly validate the expected behavior and error conditions for redeem request cancellation.

@matteoettam09
matteoettam09 merged commit 8e2c0cb into main Dec 20, 2025
5 checks passed
@matteoettam09
matteoettam09 deleted the upgradability branch December 20, 2025 20:30
This was referenced Feb 3, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants