Skip to content

Upgradability - #118

Closed
ojasarora77 wants to merge 15 commits into
mainfrom
upgradability
Closed

Upgradability#118
ojasarora77 wants to merge 15 commits into
mainfrom
upgradability

Conversation

@ojasarora77

@ojasarora77 ojasarora77 commented Dec 16, 2025

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Introduce upgradeable (UUPS and beacon-based) versions of core Orion protocol contracts and refactor tests to use the new upgradeable deployment flow, while adding regression tests around batch limits and redeem cancellation.

New Features:

  • Add upgradeable OrionConfig, InternalStatesOrchestrator, LiquidityOrchestrator, TransparentVaultFactory, PriceAdapterRegistry, and Orion transparent vault contracts using UUPS and beacon proxy patterns.
  • Provide deployment helpers and scripts to deploy and verify the upgradeable protocol end-to-end, including mock V2 implementations for upgrade testing.
  • Introduce a cancelRedeemRequest flow on the upgradeable transparent vault with validation for zero and excessive amounts.

Enhancements:

  • Refactor protocol and orchestrator tests to construct the system via a shared deployUpgradeableProtocol helper and use upgradeable contract types throughout.
  • Strengthen mainnet-fork ERC4626 compatibility tests with on-chain USDC decimals, stricter immutability checks, and validation that adapter decimals and config-registered decimals match underlying assets.
  • Improve multi-asset and access-control test coverage to work with upgradeable vaults and registries.

Tests:

  • Add BatchLimitConsistency tests to ensure pendingDeposit/pendingRedeem respect maxFulfillBatchSize and prevent double-counting in accounting.
  • Extend BatchLimitAccounting and MinimumAmountDOS tests to use the upgradeable protocol and verify correct behavior under DoS-related edge cases.
  • Add upgradeability-focused scripts and tests (including V2 mock contracts) that exercise UUPS and beacon upgrades and verify state preservation and new functionality after upgrades.

Summary by CodeRabbit

  • New Features

    • Added upgradeable vault infrastructure supporting asynchronous deposits and redemptions with batch processing.
    • Introduced flexible fee models with performance, management, and curator fee calculations.
    • Added protocol configuration management with whitelist controls for assets and vault owners.
    • Implemented multi-phase liquidity orchestration for coordinated portfolio rebalancing.
    • Added emergency pause and guardian controls for protocol safety.
  • Chores

    • Updated OpenZeppelin dependencies to support upgradeable contracts.

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

…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
@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 16, 2025

Copy link
Copy Markdown

Reviewer's Guide

Introduces a fully upgradeable version of the Orion protocol (config, orchestrators, vaults, price adapter registry, and factory) using UUPS + Beacon patterns, centralizes deployment/ wiring in a shared helper, and extends the test suite to cover upgradeability behaviors and new redeem-request cancellation semantics while updating all existing tests to target the new upgradeable contracts.

Sequence diagram for upgrade flow of config and transparent vaults

sequenceDiagram
    actor Owner
    participant Admin
    participant OrionConfigProxy
    participant ConfigImplV1 as OrionConfigImplV1
    participant ConfigImplV2 as OrionConfigImplV2
    participant PriceRegistryProxy
    participant ISOProxy as InternalStatesOrchestratorProxy
    participant LOProxy as LiquidityOrchestratorProxy
    participant FactoryProxy as TransparentVaultFactoryProxy
    participant VaultBeacon
    participant VaultImplV1 as OrionTransparentVaultImplV1
    participant VaultImplV2 as OrionTransparentVaultImplV2
    participant VaultProxy as OrionTransparentVaultBeaconProxy

    Note over Owner,Admin: Initial deployment of upgradeable core contracts
    Owner->>OrionConfigProxy: deployProxy(OrionConfigUpgradeable, initialize(owner, admin, underlying))
    OrionConfigProxy-->>ConfigImplV1: delegatecall initialize

    Owner->>PriceRegistryProxy: deployProxy(PriceAdapterRegistryUpgradeable, initialize(owner, config))
    OrionConfigProxy->>PriceRegistryProxy: setPriceAdapterRegistry(address)

    Owner->>ISOProxy: deployProxy(InternalStatesOrchestratorUpgradeable, initialize(owner, config, automationRegistry))
    Owner->>LOProxy: deployProxy(LiquidityOrchestratorUpgradeable, initialize(owner, config, automationRegistry))

    OrionConfigProxy->>ISOProxy: setInternalStatesOrchestrator(address)
    OrionConfigProxy->>LOProxy: setLiquidityOrchestrator(address)

    Note over Owner,VaultBeacon: Deploy vault implementation and beacon
    Owner->>VaultImplV1: deploy OrionTransparentVaultUpgradeable
    Owner->>VaultBeacon: deploy UpgradeableBeacon(VaultImplV1, owner)

    Owner->>FactoryProxy: deployProxy(TransparentVaultFactoryUpgradeable, initialize(owner, config, VaultBeacon))
    OrionConfigProxy->>FactoryProxy: setVaultFactory(address)

    Note over Owner,VaultProxy: Create a transparent vault via factory
    Owner->>FactoryProxy: createVault(curator, name, symbol, fees, accessControl)
    FactoryProxy->>VaultBeacon: new BeaconProxy(beacon, initData)
    VaultBeacon-->>VaultProxy: deploy proxy pointing to VaultImplV1
    VaultProxy-->>VaultImplV1: delegatecall initialize(vaultOwner, curator, config,...)
    OrionConfigProxy->>OrionConfigProxy: addOrionVault(vault, Transparent)

    Note over Owner,VaultBeacon: Upgrade vault implementation via beacon
    Owner->>VaultImplV2: deploy OrionTransparentVaultUpgradeableV2
    Owner->>VaultBeacon: upgradeTo(VaultImplV2)
    VaultBeacon-->>VaultImplV2: set new implementation
    VaultProxy-->>VaultImplV2: future calls delegate to V2

    Note over Owner,OrionConfigProxy: Upgrade OrionConfig via UUPS
    Owner->>ConfigImplV2: deploy OrionConfigUpgradeableV2
    Owner->>OrionConfigProxy: upgradeTo(address ConfigImplV2)
    OrionConfigProxy-->>ConfigImplV2: future calls delegate to V2
    OrionConfigProxy-->>ConfigImplV2: preserve storage (ADMIN, underlying, vault sets)

    Note over Owner,VaultProxy: After upgrades, vaults and orchestrators
    Note over Owner,VaultProxy: continue operating with same proxy addresses but new logic
Loading

Sequence diagram for epoch processing across internal and liquidity orchestrators

sequenceDiagram
    actor Automation as ChainlinkAutomation
    participant ISO as InternalStatesOrchestratorProxy
    participant LO as LiquidityOrchestratorProxy
    participant Config as OrionConfigProxy
    participant Vault as OrionTransparentVaultBeaconProxy

    Note over Automation,ISO: Epoch start when system is idle and time passed
    Automation->>ISO: checkUpkeep()
    ISO-->>Automation: upkeepNeeded = true (Idle && time > nextUpdateTime)
    Automation->>ISO: performUpkeep(processLP, excludedAssets)
    ISO->>ISO: _handleStart()
    ISO->>Config: getAllOrionVaults(Transparent)
    ISO->>ISO: _buildTransparentVaultsEpoch()
    ISO->>ISO: currentPhase = PreprocessingTransparentVaults

    loop Preprocess transparent minibatches
        Automation->>ISO: performUpkeep(...)
        ISO->>Vault: getPortfolio()
        ISO->>Vault: curatorFee(totalAssets)
        ISO->>Vault: accrueCuratorFees(curatorFee)
        ISO->>Vault: pendingRedeem(maxFulfillBatchSize)
        ISO->>Vault: pendingDeposit(maxFulfillBatchSize)
        ISO->>ISO: update vaultsTotalAssets mappings
        ISO->>ISO: if last minibatch set currentPhase = Buffering
    end

    Note over ISO: Buffering step adjusts protocol buffer
    Automation->>ISO: performUpkeep(...)
    ISO->>ISO: _buffer()
    ISO->>LO: targetBufferRatio()
    ISO->>ISO: bufferAmount updated
    ISO->>ISO: currentPhase = PostprocessingTransparentVaults

    loop Postprocess transparent minibatches
        Automation->>ISO: performUpkeep(excludedAssets)
        ISO->>Vault: getIntent()
        ISO->>ISO: build finalBatchPortfolio per vault
        ISO->>ISO: if last minibatch set currentPhase = BuildingOrders
    end

    Note over ISO: Build aggregated buy/sell orders
    Automation->>ISO: performUpkeep(...)
    ISO->>ISO: _buildOrders()
    ISO->>ISO: currentPhase = Idle
    ISO->>LO: advanceIdlePhase()
    LO->>LO: currentPhase = SellingLeg

    Note over Automation,LO: LiquidityOrchestrator executes orders and vault operations
    loop Sell leg
        Automation->>LO: performUpkeep()
        LO->>ISO: getOrders(true)
        LO->>LO: _processSellLeg()
        LO->>ExecutionAdapter: sell(token, amount, estimatedUnderlying)
        LO->>LO: update deltaBufferAmount
        LO->>LO: currentPhase = BuyingLeg
    end

    loop Buy leg
        Automation->>LO: performUpkeep()
        LO->>ISO: getOrders(false)
        LO->>ExecutionAdapter: buy(token, amount, estimatedUnderlying)
        LO->>LO: update deltaBufferAmount
        LO->>ISO: updateBufferAmount(deltaBufferAmount)
        LO->>LO: deltaBufferAmount = 0
        LO->>LO: currentPhase = ProcessVaultOperations
    end

    loop Process vault operations minibatches
        Automation->>LO: performUpkeep()
        LO->>Config: getAllOrionVaults(Transparent)
        LO->>ISO: getVaultTotalAssetsAll(vault)
        alt processLP == true
            LO->>Vault: pendingRedeem(maxFulfillBatchSize)
            LO->>Vault: fulfillRedeem(totalAssetsForRedeem)
            LO->>Vault: pendingDeposit(maxFulfillBatchSize)
            LO->>Vault: fulfillDeposit(totalAssetsForDeposit)
        end
        LO->>ISO: getVaultPortfolio(vault)
        LO->>Vault: updateVaultState(tokens, shares, finalTotalAssets)
        LO->>Config: completeVaultDecommissioning(vault) (if fully in underlying)
        LO->>LO: if last minibatch currentPhase = Idle, increment epochCounter
    end

    LO->>ISO: updateNextUpdateTime()
    ISO->>ISO: _nextUpdateTime = block.timestamp + epochDuration
Loading

Class diagram for upgradeable core Orion contracts

classDiagram
    direction LR

    %% OpenZeppelin upgradeable bases
    class Initializable
    class UUPSUpgradeable
    class Ownable2StepUpgradeable
    class OwnableUpgradeable
    class ReentrancyGuardUpgradeable
    class PausableUpgradeable
    class ERC20Upgradeable
    class ERC4626Upgradeable

    %% Interfaces
    class IOrionConfig {
        +address underlyingAsset()
        +address internalStatesOrchestrator()
        +address liquidityOrchestrator()
        +address transparentVaultFactory()
        +address priceAdapterRegistry()
        +uint8 curatorIntentDecimals
        +uint8 priceAdapterDecimals()
        +uint16 riskFreeRate()
        +uint256 minDepositAmount()
        +uint256 minRedeemAmount()
        +uint256 feeChangeCooldownDuration()
        +uint256 maxFulfillBatchSize()
        +bool isWhitelisted(address)
        +bool isWhitelistedVaultOwner(address)
        +bool isOrionVault(address)
        +bool isDecommissioningVault(address)
        +bool isDecommissionedVault(address)
        +bool isSystemIdle()
        +uint8 getTokenDecimals(address)
        +address admin()
        +addOrionVault(address, VaultType)
        +completeVaultDecommissioning(address)
    }

    class IInternalStateOrchestrator {
        <<interface>>
        +enum InternalUpkeepPhase
        +uint32 epochDuration()
        +InternalUpkeepPhase currentPhase()
        +uint256 bufferAmount()
        +bool processLP()
        +updateNextUpdateTime()
        +getOrders(bool)
        +getEpochTokens()
        +getPriceOf(address)
        +subtractPendingProtocolFees(uint256)
        +updateBufferAmount(int256)
        +getVaultTotalAssetsAll(address)
        +getTransparentVaultsEpoch()
        +getVaultPortfolio(address)
        +pause()
        +unpause()
    }

    class ILiquidityOrchestrator {
        <<interface>>
        +enum LiquidityUpkeepPhase
        +LiquidityUpkeepPhase currentPhase()
        +uint8 minibatchSize()
        +uint256 targetBufferRatio()
        +updateMinibatchSize(uint8)
        +updateAutomationRegistry(address)
        +setInternalStatesOrchestrator(address)
        +setTargetBufferRatio(uint256)
        +depositLiquidity(uint256)
        +withdrawLiquidity(uint256)
        +claimProtocolFees(uint256)
        +setExecutionAdapter(address, IExecutionAdapter)
        +advanceIdlePhase()
        +returnDepositFunds(address, uint256)
        +transferCuratorFees(uint256)
        +transferRedemptionFunds(address, uint256)
        +withdraw(uint256, address)
        +pause()
        +unpause()
    }

    class IOrionVault {
        <<interface>>
        +address vaultOwner()
        +address curator()
        +overrideIntentForDecommissioning()
        +requestDeposit(uint256)
        +cancelDepositRequest(uint256)
        +requestRedeem(uint256)
        +cancelRedeemRequest(uint256)
        +vaultWhitelist() address[]
        +curatorFee(uint256) uint256
        +claimCuratorFees(uint256)
        +setDepositAccessControl(address)
        +pendingDeposit(uint256) uint256
        +pendingRedeem(uint256) uint256
        +accrueCuratorFees(uint256)
        +fulfillDeposit(uint256)
        +fulfillRedeem(uint256)
    }

    class IOrionTransparentVault {
        <<interface>>
        +struct IntentPosition
        +submitIntent(IntentPosition[])
        +getPortfolio() address[] uint256[]
        +getIntent() address[] uint32[]
        +updateVaultState(address[], uint256[], uint256)
        +removeFromVaultWhitelist(address)
    }

    class IPriceAdapterRegistry {
        <<interface>>
        +setPriceAdapter(address, IPriceAdapter)
        +getPrice(address) uint256
    }

    class IPriceAdapter {
        <<interface>>
        +validatePriceAdapter(address)
        +getPriceData(address) uint256 uint8
    }

    class IExecutionAdapter {
        <<interface>>
        +validateExecutionAdapter(address)
        +sell(address, uint256, uint256) uint256
        +buy(address, uint256, uint256) uint256
    }

    %% Concrete upgradeable config
    class OrionConfigUpgradeable {
        +address ADMIN
        +address guardian
        +IERC20 underlyingAsset
        +address internalStatesOrchestrator
        +address liquidityOrchestrator
        +address transparentVaultFactory
        +address priceAdapterRegistry
        +uint8 curatorIntentDecimals
        +uint8 priceAdapterDecimals
        +uint16 riskFreeRate
        +uint256 minDepositAmount
        +uint256 minRedeemAmount
        +uint256 feeChangeCooldownDuration
        +uint256 maxFulfillBatchSize
        +addWhitelistedAsset(address, address, address)
        +removeWhitelistedAsset(address)
        +addWhitelistedVaultOwner(address)
        +removeWhitelistedVaultOwner(address)
        +setInternalStatesOrchestrator(address)
        +setLiquidityOrchestrator(address)
        +setVaultFactory(address)
        +setPriceAdapterRegistry(address)
        +setProtocolRiskFreeRate(uint16)
        +setMinDepositAmount(uint256)
        +setMinRedeemAmount(uint256)
        +setFeeChangeCooldownDuration(uint256)
        +setMaxFulfillBatchSize(uint256)
        +setGuardian(address)
        +pauseAll()
        +unpauseAll()
    }

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

    %% Price adapter registry
    class PriceAdapterRegistryUpgradeable {
        +address configAddress
        +uint8 priceAdapterDecimals
        +mapping adapterOf
        +setPriceAdapter(address, IPriceAdapter)
        +getPrice(address) uint256
    }

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

    %% Internal state orchestrator
    class InternalStatesOrchestratorUpgradeable {
        +address automationRegistry
        +IOrionConfig config
        +ILiquidityOrchestrator liquidityOrchestrator
        +IPriceAdapterRegistry registry
        +uint256 priceAdapterPrecision
        +uint256 intentFactor
        +address underlyingAsset
        +uint8 underlyingDecimals
        +uint16 vFeeCoefficient
        +uint16 rsFeeCoefficient
        +uint16 oldVFeeCoefficient
        +uint16 oldRsFeeCoefficient
        +uint256 newProtocolFeeRatesTimestamp
        +uint256 pendingProtocolFees
        +uint32 epochDuration
        +uint8 transparentMinibatchSize
        +InternalUpkeepPhase currentPhase
        +uint8 currentMinibatchIndex
        +address[] transparentVaultsEpoch
        +uint256 bufferAmount
        +bool processLP
        +updateAutomationRegistry(address)
        +updateEpochDuration(uint32)
        +updateMinibatchSize(uint8)
        +updateProtocolFees(uint16, uint16)
        +activeProtocolFees() uint16 uint16
        +resetPhase(InternalUpkeepPhase)
        +checkUpkeep(bytes) bool bytes
        +performUpkeep(bytes)
        +updateNextUpdateTime()
        +getOrders(bool)
        +getEpochTokens() address[]
        +getPriceOf(address) uint256
        +subtractPendingProtocolFees(uint256)
        +updateBufferAmount(int256)
        +getVaultTotalAssetsAll(address)
        +getTransparentVaultsEpoch() address[]
        +getVaultPortfolio(address) address[] uint256[]
        +pause()
        +unpause()
    }

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

    %% Liquidity orchestrator
    class LiquidityOrchestratorUpgradeable {
        +address automationRegistry
        +IOrionConfig config
        +IInternalStateOrchestrator internalStatesOrchestrator
        +address underlyingAsset
        +address admin
        +mapping executionAdapterOf
        +uint16 epochCounter
        +uint8 minibatchSize
        +LiquidityUpkeepPhase currentPhase
        +uint8 currentMinibatchIndex
        +uint256 targetBufferRatio
        +uint256 slippageTolerance
        +int256 deltaBufferAmount
        +updateMinibatchSize(uint8)
        +updateAutomationRegistry(address)
        +setInternalStatesOrchestrator(address)
        +setTargetBufferRatio(uint256)
        +depositLiquidity(uint256)
        +withdrawLiquidity(uint256)
        +claimProtocolFees(uint256)
        +setExecutionAdapter(address, IExecutionAdapter)
        +advanceIdlePhase()
        +returnDepositFunds(address, uint256)
        +transferCuratorFees(uint256)
        +transferRedemptionFunds(address, uint256)
        +withdraw(uint256, address)
        +checkUpkeep(bytes) bool bytes
        +performUpkeep(bytes)
        +pause()
        +unpause()
    }

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

    %% Base vault
    class OrionVaultUpgradeable {
        <<abstract>>
        +address vaultOwner
        +address curator
        +IOrionConfig config
        +IInternalStateOrchestrator internalStatesOrchestrator
        +ILiquidityOrchestrator liquidityOrchestrator
        +address depositAccessControl
        +uint256 _totalAssets
        +uint256 pendingCuratorFees
        +bool isDecommissioning
        +struct FeeModel
        +FeeModel feeModel
        +FeeModel oldFeeModel
        +uint256 newFeeRatesTimestamp
        +overrideIntentForDecommissioning()
        +requestDeposit(uint256)
        +cancelDepositRequest(uint256)
        +requestRedeem(uint256)
        +cancelRedeemRequest(uint256)
        +vaultWhitelist() address[]
        +updateFeeModel(uint8, uint16, uint16)
        +curatorFee(uint256) uint256
        +claimCuratorFees(uint256)
        +setDepositAccessControl(address)
        +pendingDeposit(uint256) uint256
        +pendingRedeem(uint256) uint256
        +accrueCuratorFees(uint256)
        +fulfillDeposit(uint256)
        +fulfillRedeem(uint256)
    }

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

    %% Transparent vault
    class OrionTransparentVaultUpgradeable {
        +mapping _portfolio
        +mapping _portfolioIntent
        +initialize(address, address, IOrionConfig, string, string, uint8, uint16, uint16, address)
        +submitIntent(IntentPosition[])
        +getPortfolio() address[] uint256[]
        +getIntent() address[] uint32[]
        +updateVaultState(address[], uint256[], uint256)
        +updateCurator(address)
        +updateVaultWhitelist(address[])
        +removeFromVaultWhitelist(address)
    }

    OrionTransparentVaultUpgradeable ..|> IOrionTransparentVault
    OrionTransparentVaultUpgradeable --|> OrionVaultUpgradeable

    %% Transparent vault factory
    class TransparentVaultFactoryUpgradeable {
        +IOrionConfig config
        +UpgradeableBeacon vaultBeacon
        +initialize(address, address, address)
        +createVault(address, string, string, uint8, uint16, uint16, address) address
        +setVaultBeacon(address)
    }

    TransparentVaultFactoryUpgradeable --|> Initializable
    TransparentVaultFactoryUpgradeable --|> OwnableUpgradeable
    TransparentVaultFactoryUpgradeable --|> UUPSUpgradeable

    %% Relationships between core components
    OrionConfigUpgradeable o-- PriceAdapterRegistryUpgradeable : configAddress
    OrionConfigUpgradeable o-- InternalStatesOrchestratorUpgradeable : internalStatesOrchestrator
    OrionConfigUpgradeable o-- LiquidityOrchestratorUpgradeable : liquidityOrchestrator
    OrionConfigUpgradeable o-- TransparentVaultFactoryUpgradeable : transparentVaultFactory

    PriceAdapterRegistryUpgradeable o-- IPriceAdapter : adapterOf

    InternalStatesOrchestratorUpgradeable o-- IOrionConfig : config
    InternalStatesOrchestratorUpgradeable o-- ILiquidityOrchestrator : liquidityOrchestrator
    InternalStatesOrchestratorUpgradeable o-- IPriceAdapterRegistry : registry

    LiquidityOrchestratorUpgradeable o-- IOrionConfig : config
    LiquidityOrchestratorUpgradeable o-- IInternalStateOrchestrator : internalStatesOrchestrator
    LiquidityOrchestratorUpgradeable o-- IExecutionAdapter : executionAdapterOf

    OrionVaultUpgradeable o-- IOrionConfig : config
    OrionVaultUpgradeable o-- IInternalStateOrchestrator : internalStatesOrchestrator
    OrionVaultUpgradeable o-- ILiquidityOrchestrator : liquidityOrchestrator

    TransparentVaultFactoryUpgradeable o-- IOrionConfig : config
    TransparentVaultFactoryUpgradeable o-- OrionTransparentVaultUpgradeable : creates

    OrionTransparentVaultUpgradeable <.. OrionTransparentVaultUpgradeableV2 : upgraded_version
    OrionConfigUpgradeable <.. OrionConfigUpgradeableV2 : upgraded_version
Loading

File-Level Changes

Change Details Files
Introduce upgradeable protocol contracts (config, orchestrators, vaults, price registry, factory) using UUPS and Beacon proxy patterns.
  • Add OrionConfigUpgradeable implementing IOrionConfig with UUPS upgradeability, guardian pause controls, fee cooldown, vault owner decommissioning, and whitelists stored in upgrade-safe layout.
  • Add InternalStatesOrchestratorUpgradeable with epoch-based state estimation, fee accrual (including cooldown-aware protocol fees), buffer management, and batched order building, all as a UUPS implementation.
  • Add LiquidityOrchestratorUpgradeable as a UUPS contract orchestrating sell/buy legs, buffer updates, liquidity deposit/withdrawal, protocol fee claiming, and vault LP operations with minibatch processing.
  • Add OrionVaultUpgradeable base ERC4626-compatible upgradeable vault implementing async deposit/redeem queues, curator fee logic, decommissioning override, and batch-aware conversion helpers.
  • Add OrionTransparentVaultUpgradeable extending OrionVaultUpgradeable with curator intents, portfolio state, per-vault whitelists, and config-driven decommissioning behavior.
  • Add PriceAdapterRegistryUpgradeable (UUPS) for registering and querying price adapters with consistent decimals.
  • Add TransparentVaultFactoryUpgradeable (UUPS) using an UpgradeableBeacon for OrionTransparentVaultUpgradeable instances and wiring new vaults into OrionConfigUpgradeable.
contracts/OrionConfigUpgradeable.sol
contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol
contracts/vaults/OrionVaultUpgradeable.sol
contracts/vaults/OrionTransparentVaultUpgradeable.sol
contracts/price/PriceAdapterRegistryUpgradeable.sol
contracts/factories/TransparentVaultFactoryUpgradeable.sol
Add upgradeability test/migration scaffolding and helper deployment utilities, plus mock V2 implementations to validate upgrade flows.
  • Introduce deployUpgradeableProtocol test helper that deploys the full upgradeable stack (config, registry, orchestrators, beacon, factory, underlying asset) and wires dependencies in the correct order.
  • Add OrionConfigUpgradeableV2 and OrionTransparentVaultUpgradeableV2 mocks with extra state/functions to prove storage-gap-safe upgrades.
  • Add scripts to exercise end-to-end upgrade flows (UUPS + Beacon) and to verify that meaningful upgrades require new implementation addresses.
  • Expose helper to attach to existing vault BeaconProxy instances.
test/helpers/deployUpgradeable.ts
contracts/mocks/OrionConfigUpgradeableV2.sol
contracts/mocks/OrionTransparentVaultUpgradeableV2.sol
scripts/testUpgradeability.ts
scripts/verifyUpgradeRequiresNewAddress.ts
Refactor existing tests to use the new upgradeable protocol deployment helper and upgradeable contract types instead of legacy non-upgradeable contracts.
  • Update core vault, orchestrator, access control, protocol pause, passive curator strategy, exchange rate, removal, batch-limit accounting, and orchestrator-zero-state tests to construct their environments via deployUpgradeableProtocol and reference OrionConfigUpgradeable, *Upgradeable orchestrators, TransparentVaultFactoryUpgradeable, and OrionTransparentVaultUpgradeable.
  • Remove ad-hoc deployment wiring of OrionConfig, LiquidityOrchestrator, InternalStatesOrchestrator, TransparentVaultFactory, and PriceAdapterRegistry from tests in favor of shared helper wiring.
  • Adjust type imports and contract lookups (ethers.getContractAt/Factory) throughout tests to target Upgradeable artifacts and the beacon-based vault implementation name.
  • Add tsconfig include of scripts/**/.ts so new upgrade scripts are type-checked.
test/OrionConfigVault.test.ts
test/TransparentVault.test.ts
test/orchestrator/OrchestratorConfiguration.test.ts
test/orchestrator/OrchestratorSecurity.test.ts
test/AccessControl.test.ts
test/ProtocolPause.test.ts
test/PassiveCuratorStrategy.test.ts
test/OrionVaultExchangeRate.test.ts
test/VaultOwnerRemoval.test.ts
test/Removal.test.ts
test/BatchLimitAccounting.test.ts
test/orchestrator/OrchestratorsZeroState.test.ts
test/FeeCooldown.test.ts
test/Adapters.test.ts
test/MinimumAmountDOS.test.ts
test/ExecutionAdapterValidation.test.ts
test/mainnet-fork/multiAssetRobustness.test.ts
test/mainnet-fork/removeWhitelistedAsset.test.ts
tsconfig.json
Strengthen ERC4626 mainnet-fork compatibility tests to detect upgradeability, enforce underlying decimal consistency, and validate adapter behavior against whitelisting rules.
  • Fetch USDC decimals on-chain instead of hardcoding and assert adapter decimals match underlying asset decimals from getPriceData.
  • Extend immutability tests to compare contract bytecode across blocks, inspect EIP-1967 implementation storage slot to detect proxies, and verify asset()/decimals() stability over time and within blocks.
  • Require vaults to be whitelisted in OrionConfig before execution adapter validation so config token decimals match the vault, and clean up whitelist after each check.
test/mainnet-fork/erc4626VaultCompatibility.test.ts
Add redeem-request cancellation tests and batch-limit consistency tests to cover new queue semantics and prevent accounting drift with maxFulfillBatchSize.
  • Introduce Redeem Request Cancellation tests verifying zero-amount requests revert, over-cancelling reverts with InsufficientAmount, full cancellation restores user share balance, and partial cancellation adjusts pendingRedeem and balances correctly using impersonated LiquidityOrchestrator to fulfill deposits.
  • Add BatchLimitConsistency tests that verify pendingDeposit/pendingRedeem respect maxFulfillBatchSize, use snapshot-based share/asset conversions in fulfillDeposit/fulfillRedeem, and prevent double counting of requests across epochs.
  • Document the critical accounting bug fixed around pending queue over-counting and how new tests guard against regressions.
test/OrionConfigVault.test.ts
test/BatchLimitConsistency.test.ts

Possibly linked issues

  • #unknown: PR adds upgradeable Config, Vault, Factory, Orchestrators, registry, plus tests and scripts, directly addressing protocol upgradability requirement.
  • #test: PR implements all requested tests: cancelRedeem edge cases, dynamic USDC decimals, real immutability, and adapter decimals assertion.

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 16, 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 PR migrates core Orion protocol contracts from non-upgradeable implementations to OpenZeppelin UUPS upgradeable proxies. It introduces upgradeable variants of OrionConfig, vault implementations, factories, orchestrators, and price adapters with Initializable/Ownable2Step/UUPS patterns, includes a new deployable protocol helper for tests, and updates 20+ test files to use the new upgradeable architecture.

Changes

Cohort / File(s) Summary
Core Configuration
contracts/OrionConfigUpgradeable.sol
New upgradeable config contract implementing IOrionConfig with admin/guardian roles, protocol parameters (fees, batch sizes, cooldowns), whitelist management (assets, vault owners), vault registry, and UUPS upgrade mechanism; includes all original non-upgradeable functionality with Initializable/Ownable2Step patterns.
Vault Base & Transparent
contracts/vaults/OrionVaultUpgradeable.sol
contracts/vaults/OrionTransparentVaultUpgradeable.sol
New abstract OrionVaultUpgradeable base with asynchronous deposits/redemptions, fee model with cooldowns, batch fulfillment logic, and access control. OrionTransparentVaultUpgradeable adds curator intent submission, portfolio tracking, decommissioning support, and vault state updates via orchestrators. Both use UUPS pattern with storage gaps.
Mock V2 Upgradeable Vaults
contracts/mocks/OrionConfigUpgradeableV2.sol
contracts/mocks/OrionTransparentVaultUpgradeableV2.sol
Test-only mock V2 implementations extending upgradeable base contracts; add new state variables, events, and version identifiers to support upgrade path testing.
Factory
contracts/factories/TransparentVaultFactoryUpgradeable.sol
New upgradeable factory using BeaconProxy pattern to deploy transparent vaults; validates whitelist and idle-state checks, encodes init data, registers deployed vaults in config, manages beacon address updates.
Orchestrators
contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol
New upgradeable orchestrators implementing multi-phase upkeep (InternalStatesOrchestrator: Idle/PreprocessingTransparentVaults/Buffering/PostprocessingTransparentVaults/BuildingOrders; LiquidityOrchestrator: SellingLeg/BuyingLeg/ProcessVaultOperations). Extensive portfolio calculations, order generation, execution adapter integration, vault state updates, and fee/buffer accounting.
Price Registry
contracts/price/PriceAdapterRegistryUpgradeable.sol
New upgradeable price adapter registry with adapter-per-asset mapping, config-gated updates, price retrieval with decimal conversion via UtilitiesLib, and UUPS upgrade authorization.
Libraries
contracts/libraries/EventsLib.sol
Added VaultBeaconUpdated event for vault beacon state transitions.
Dependencies
package.json
Added "@openzeppelin/contracts-upgradeable": "^5.4.0" dependency.
Test Infrastructure
test/helpers/deployUpgradeable.ts
New helper module exporting deployUpgradeableProtocol function and UpgradeableProtocolContracts interface; orchestrates full upgradeable protocol deployment with proper initialization, dependency wiring, and beacon setup for vault proxies.
Test Suite Updates
test/*.test.ts
test/orchestrator/*.test.ts
test/mainnet-fork/*.test.ts
20+ test files migrated to use upgradeable contract variants (OrionConfigUpgradeable, TransparentVaultFactoryUpgradeable, InternalStatesOrchestratorUpgradeable, LiquidityOrchestratorUpgradeable, OrionTransparentVaultUpgradeable, PriceAdapterRegistryUpgradeable) and centralized deployUpgradeableProtocol helper instead of manual multi-step deployments. All test interactions and type references updated accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Areas requiring extra attention:

  • InternalStatesOrchestratorUpgradeable (contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol): Dense orchestration logic spanning ~800+ lines with multi-phase upkeep state machine, portfolio calculations, order assembly, and fee/buffer accounting
  • LiquidityOrchestratorUpgradeable (contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol): Complex interaction between execution adapters, vault operations, and orchestrator state transitions
  • OrionVaultUpgradeable (contracts/vaults/OrionVaultUpgradeable.sol): Extensive fee model implementation with multiple FeeType variants, batch processing logic, and PIT (point-in-time) asset conversions
  • Storage layout & UUPS patterns: Verify correct use of initializers, storage gaps (50-slot), and upgrade authorization across all new contracts
  • Dependency wiring in test/helpers/deployUpgradeable.ts: Critical path for all test deployments; confirm initialization order and all cross-contract address assignments
  • Test coverage consistency: Verify all 20+ migrated tests properly wire upgraded components and haven't lost assertions during refactoring

Possibly related PRs

  • Issue 93 #105: Implements guardian-based emergency pause controls (setGuardian, pauseAll, unpauseAll) and Pausable/whenNotPaused guards that are similarly structured in orchestrators and vaults
  • Dev #88: Touches same core contracts (OrionConfig, orchestrators, vaults, adapters) with overlapping modifications to admin roles, batch processing, and intent handling
  • Dev #70: Introduces orchestrator function signatures and slippage-aware execution logic that align with the InternalStatesOrchestrator/LiquidityOrchestrator implementations here

Poem

🐰 Hops of proxy glory shine so bright,
UUPS patterns wrapped up tight,
Upgradeable dreams take their flight,
Storage gaps preserve the night,
Orion vaults dance in the light!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Upgradability' is concise and accurately summarizes the primary change: introducing upgradeable contract patterns across the Orion protocol.
✨ 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 there - I've reviewed your changes and found some issues that need to be addressed.

  • In TransparentVaultFactoryUpgradeable.setVaultBeacon you allow the owner to change the beacon at any time without checking config.isSystemIdle(), which is stricter elsewhere (e.g. OrionConfigUpgradeable.setVaultFactory/update*); consider adding the same idle-phase guard so upgrades can't happen mid-epoch.
  • Both LiquidityOrchestratorUpgradeable and InternalStatesOrchestratorUpgradeable expose updateAutomationRegistry but the automationRegistry address is not validated against any known registry interface or sanity-checked beyond non-zero; if this is security-sensitive, consider constraining or emitting additional diagnostic information to help detect misconfiguration.
  • The upgradeability test scripts under scripts/ (e.g. testUpgradeability.ts, verifyUpgradeRequiresNewAddress.ts) embed fairly heavy, test-like logic; if they are meant purely for local experiments it may be worth clearly separating or guarding them so they are not confused with production tooling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TransparentVaultFactoryUpgradeable.setVaultBeacon` you allow the owner to change the beacon at any time without checking `config.isSystemIdle()`, which is stricter elsewhere (e.g. `OrionConfigUpgradeable.setVaultFactory/update*`); consider adding the same idle-phase guard so upgrades can't happen mid-epoch.
- Both `LiquidityOrchestratorUpgradeable` and `InternalStatesOrchestratorUpgradeable` expose `updateAutomationRegistry` but the `automationRegistry` address is not validated against any known registry interface or sanity-checked beyond non-zero; if this is security-sensitive, consider constraining or emitting additional diagnostic information to help detect misconfiguration.
- The upgradeability test scripts under `scripts/` (e.g. `testUpgradeability.ts`, `verifyUpgradeRequiresNewAddress.ts`) embed fairly heavy, test-like logic; if they are meant purely for local experiments it may be worth clearly separating or guarding them so they are not confused with production tooling.

## Individual Comments

### Comment 1
<location> `contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol:540-541` </location>
<code_context>
+        for (uint16 i = 0; i < nTransparentVaults; ++i) {
+            address vault = transparentVaultsEpoch[i];
+            uint256 vaultAssets = _currentEpoch.vaultsTotalAssets[address(vault)];
+            uint256 vaultBufferCost = deltaBufferAmount.mulDiv(vaultAssets, protocolTotalAssets);
+            _currentEpoch.vaultsTotalAssets[address(vault)] -= vaultBufferCost;
+            actualBufferAllocated += vaultBufferCost;
+        }
</code_context>

<issue_to_address>
**issue (bug_risk):** Handle protocolTotalAssets == 0 in _buffer to avoid division-by-zero.

In `_buffer`, when all vaults have zero assets, `protocolTotalAssets` remains 0 and `deltaBufferAmount.mulDiv(vaultAssets, protocolTotalAssets)` will revert due to division by zero. Add a guard (e.g., `if (protocolTotalAssets == 0) return;` before computing per-vault costs) to avoid reverting upkeep when TVL is zero.
</issue_to_address>

### Comment 2
<location> `contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol:739-743` </location>
<code_context>
+    }
+
+    /// @inheritdoc IInternalStateOrchestrator
+    function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
+        if (deltaAmount > 0) {
+            bufferAmount += uint256(deltaAmount);
+        } else if (deltaAmount < 0) {
+            bufferAmount -= uint256(-deltaAmount);
+        }
+    }
</code_context>

<issue_to_address>
**issue:** Add safety checks to prevent bufferAmount underflow when applying negative deltas.

When `deltaAmount < 0`, this subtracts `uint256(-deltaAmount)` from `bufferAmount` without ensuring `bufferAmount` is sufficient. Unlike the admin withdrawal path, the P&L `deltaBufferAmount` path has no guard. A large negative delta (e.g., from slippage) could underflow, revert, and wedge the protocol in a non-idle phase. Please either cap the negative delta at `bufferAmount` or explicitly revert with a domain-specific error when `uint256(-deltaAmount) > bufferAmount` so the failure mode is controlled and clear.
</issue_to_address>

### Comment 3
<location> `contracts/factories/TransparentVaultFactoryUpgradeable.sol:106-108` </location>
<code_context>
+
+    /// @notice Updates the vault beacon address
+    /// @param newVaultBeacon The new UpgradeableBeacon address
+    function setVaultBeacon(address newVaultBeacon) external onlyOwner {
+        if (newVaultBeacon == address(0)) revert ErrorsLib.ZeroAddress();
+        vaultBeacon = UpgradeableBeacon(newVaultBeacon);
+    }
+
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider restricting beacon changes to idle system state to avoid mid-epoch implementation flips.

Elsewhere (e.g. `OrionConfigUpgradeable`) config setters require `isSystemIdle()`, but `setVaultBeacon` does not. Changing the beacon mid-epoch could cause some vault calls in the same cycle to hit different implementations. Please add a `config.isSystemIdle()` check here to match the rest of the config surface and avoid inconsistent behaviour during upgrades.

Suggested implementation:

```
    /// @notice Updates the vault beacon address
    /// @param newVaultBeacon The new UpgradeableBeacon address
    function setVaultBeacon(address newVaultBeacon) external onlyOwner {
        if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
        if (newVaultBeacon == address(0)) revert ErrorsLib.ZeroAddress();
        vaultBeacon = UpgradeableBeacon(newVaultBeacon);
    }

```

I assumed the existence of a `config` state variable of type `IOrionConfig` and a custom error `ErrorsLib.SystemNotIdle()`, based on the rest of the codebase pattern you referenced. If the error name differs, adjust `ErrorsLib.SystemNotIdle()` to the correct one used in other config setters that gate on `isSystemIdle()`. Also ensure `config` is already initialized in the contract’s initializer/constructor as it is for other config-dependent functions.
</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/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/factories/TransparentVaultFactoryUpgradeable.sol
Comment thread scripts/verifyUpgradeRequiresNewAddress.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: 19

Caution

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

⚠️ Outside diff range comments (4)
test/FeeCooldown.test.ts (1)

55-85: Fix return type to match upgradeable contract.

The function returns OrionTransparentVaultUpgradeable but declares return type as OrionTransparentVault. This type mismatch should be corrected.

   async function createVault(
     fixture: Awaited<ReturnType<typeof deployFixture>>,
     feeType: number,
     performanceFee: number,
     managementFee: number,
-  ): Promise<OrionTransparentVault> {
+  ): Promise<OrionTransparentVaultUpgradeable> {
test/mainnet-fork/erc4626VaultCompatibility.test.ts (1)

34-55: Missing type imports for orchestrator variables.

InternalStatesOrchestrator and LiquidityOrchestrator types are used at lines 50-51 but are not imported from typechain-types. This will cause TypeScript compilation errors.

 import {
   OrionConfigUpgradeable,
   TransparentVaultFactoryUpgradeable,
   PriceAdapterRegistryUpgradeable,
   OrionAssetERC4626PriceAdapter,
   OrionAssetERC4626ExecutionAdapter,
+  InternalStatesOrchestrator,
+  LiquidityOrchestrator,
 } from "../../typechain-types";
test/mainnet-fork/removeWhitelistedAsset.test.ts (2)

106-118: Type mismatch: deploying non-upgradeable contracts but casting to upgradeable types.

The test deploys OrionConfig and TransparentVaultFactory (non-upgradeable versions) but casts them to OrionConfigUpgradeable and TransparentVaultFactoryUpgradeable. These are fundamentally different contracts with different ABIs and storage layouts. The as unknown as cast masks compile-time type errors but will cause runtime issues if upgradeable-specific methods are called.

Either:

  1. Use the deployUpgradeableProtocol helper like other test files, or
  2. Keep non-upgradeable types if testing with non-upgradeable contracts
-    orionConfig = orionConfigDeployed as unknown as OrionConfigUpgradeable;
+    // Keep using non-upgradeable types since we're deploying non-upgradeable contracts
+    // Or switch to deployUpgradeableProtocol helper for consistency

143-149: Same type mismatch issue for PriceAdapterRegistry.

PriceAdapterRegistry is deployed but cast to PriceAdapterRegistryUpgradeable. This is inconsistent with the upgradeable architecture.

🧹 Nitpick comments (23)
tsconfig.json (1)

11-11: Track the TODO for cleanup after migration testing.

The inline comment indicates this inclusion is temporary. Consider creating a tracking issue to ensure this is revisited once upgradeable contracts are validated in production.

contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (1)

384-422: Consider overflow safety for int256 casts.

Lines 397 and 421 cast uint256 values to int256 for delta calculations. While unlikely in practice, extremely large amounts could overflow. Consider using SafeCast from OpenZeppelin for explicit overflow protection.

+import "@openzeppelin/contracts/utils/math/SafeCast.sol";
 ...
+    using SafeCast for uint256;
 ...
-    deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
+    deltaBufferAmount += executionUnderlyingAmount.toInt256() - estimatedUnderlyingAmount.toInt256();
test/OrionVaultExchangeRate.test.ts (1)

42-55: Add explicit check for missing event.

If event is undefined (e.g., event not emitted or transaction failed), parseLog(event!) will throw a confusing error. Consider adding an explicit check.

     const event = receipt?.logs.find((log) => {
       try {
         const parsed = factory.interface.parseLog(log);
         return parsed?.name === "OrionVaultCreated";
       } catch {
         return false;
       }
     });
 
+    if (!event) {
+      throw new Error("OrionVaultCreated event not found in transaction receipt");
+    }
+
     const parsedEvent = factory.interface.parseLog(event!);
     const vaultAddress = parsedEvent?.args[0];
scripts/verifyUpgradeRequiresNewAddress.ts (1)

20-20: Unused variable admin.

The admin signer is destructured but never used in this script. Consider removing it to avoid confusion.

-  const [owner, admin] = await ethers.getSigners();
+  const [owner] = await ethers.getSigners();
test/BatchLimitConsistency.test.ts (1)

113-124: Add null check before parsing event.

If no matching log is found, event will be undefined and the non-null assertion on line 123 could cause a runtime error. Consider adding explicit validation.

     const event = receipt?.logs.find((log) => {
       try {
         const parsed = transparentVaultFactory.interface.parseLog(log);
         return parsed?.name === "OrionVaultCreated";
       } catch {
         return false;
       }
     });
 
+    if (!event) {
+      throw new Error("OrionVaultCreated event not found");
+    }
     const parsedEvent = transparentVaultFactory.interface.parseLog(event!);
     const vaultAddress = parsedEvent?.args[0];
scripts/testUpgradeability.ts (1)

239-248: Consider using interface.parseLog for event extraction.

The current event parsing approach with manual type coercion is fragile. Using the factory's interface for parsing (as done in test files) is more reliable.

   // Get vault address from event
-  const vaultCreatedEvent = receipt.logs.find((log: unknown) => {
-    const parsed = log as { fragment?: { name?: string }; args?: string[] };
-    return parsed.fragment && parsed.fragment.name === "OrionVaultCreated";
-  }) as { args: string[] } | undefined;
-
-  if (!vaultCreatedEvent || !vaultCreatedEvent.args[0]) {
+  const vaultCreatedEvent = receipt.logs.find((log) => {
+    try {
+      const parsed = factoryProxy.interface.parseLog(log);
+      return parsed?.name === "OrionVaultCreated";
+    } catch {
+      return false;
+    }
+  });
+
+  if (!vaultCreatedEvent) {
     throw new Error("OrionVaultCreated event not found or invalid");
   }
 
-  const vaultAddress: string = vaultCreatedEvent.args[0];
+  const parsedEvent = factoryProxy.interface.parseLog(vaultCreatedEvent);
+  const vaultAddress: string = parsedEvent?.args[0];
contracts/price/PriceAdapterRegistryUpgradeable.sol (1)

85-89: Storage gap calculation is incorrect.

The comment states "Total storage slots reserved: 50", but the contract has 3 state variables (configAddress, priceAdapterDecimals, adapterOf) consuming slots before the gap. With a 50-slot gap, the total is 53 slots. If the intention is 50 total slots for future upgrades, the gap should be 47. Alternatively, update the comment to clarify the gap reserves 50 additional slots.

     /**
      * @dev Storage gap to allow for future upgrades
-     * Total storage slots reserved: 50
+     * Storage gap for future state variables (50 additional slots)
      */
     uint256[50] private __gap;
test/mainnet-fork/multiAssetRobustness.test.ts (1)

324-363: Test deploys non-upgradeable contracts but uses upgradeable types.

This test deploys the non-upgradeable versions (OrionConfig, PriceAdapterRegistry, TransparentVaultFactory) but casts them to upgradeable types. This is inconsistent with the PR's upgradeable architecture and doesn't actually test the upgradeable contracts.

Consider using deployUpgradeableProtocol helper (as done in OrchestratorsZeroState.test.ts) or deploying the upgradeable versions directly for consistency across the test suite.

contracts/factories/TransparentVaultFactoryUpgradeable.sol (2)

104-109: Consider emitting an event when the vault beacon is updated.

Changing the vault beacon affects all future vault deployments. Emitting an event would help with off-chain monitoring and auditing of beacon changes.

+    // Add to EventsLib:
+    // event VaultBeaconUpdated(address indexed oldBeacon, address indexed newBeacon);

     function setVaultBeacon(address newVaultBeacon) external onlyOwner {
         if (newVaultBeacon == address(0)) revert ErrorsLib.ZeroAddress();
+        address oldBeacon = address(vaultBeacon);
         vaultBeacon = UpgradeableBeacon(newVaultBeacon);
+        // emit EventsLib.VaultBeaconUpdated(oldBeacon, newVaultBeacon);
     }

117-121: Storage gap calculation in comment is incorrect.

The comment states "50 - 1 for vaultBeacon" but there are 2 state variables (config at line 22 and vaultBeacon at line 25). The gap should be 48 (50 - 2), or the comment should be updated to reflect the actual calculation.

     /**
      * @dev Storage gap to allow for future upgrades
-     * Total storage slots reserved: 49 (50 - 1 for vaultBeacon)
+     * Storage gap: 48 slots (50 - 2 for config and vaultBeacon)
      */
-    uint256[49] private __gap;
+    uint256[48] private __gap;
test/helpers/deployUpgradeable.ts (2)

23-23: Consider using proper type for vaultBeacon instead of unknown.

The unknown type loses type safety. Consider importing and using the proper type from the typechain-generated types or at minimum use a more descriptive type.

-  vaultBeacon: unknown; // UpgradeableBeacon instance
+  vaultBeacon: Awaited<ReturnType<typeof ethers.deployContract>>; // UpgradeableBeacon instance

Alternatively, if you have generated types for OpenZeppelin contracts:

import { UpgradeableBeacon } from "../../typechain-types/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon";

91-109: Duplicate step numbering: two "step 5" comments.

Steps 5 (lines 91-98) and 5 (lines 100-109) have the same number. This creates confusion in the deployment sequence documentation.

-  // 5. Deploy InternalStatesOrchestratorUpgradeable (UUPS) - reads liquidityOrchestrator from config
+  // 5. Deploy InternalStatesOrchestratorUpgradeable (UUPS) - reads liquidityOrchestrator from config
   const InternalStatesOrchestratorFactory = await ethers.getContractFactory("InternalStatesOrchestratorUpgradeable");
   ...
   await internalStatesOrchestrator.waitForDeployment();

-  // 5. Deploy UpgradeableBeacon for vaults
+  // 6. Deploy UpgradeableBeacon for vaults
   const VaultImplFactory = await ethers.getContractFactory("OrionTransparentVaultUpgradeable");
   ...
   
-  // 6. Deploy TransparentVaultFactoryUpgradeable (UUPS)
+  // 7. Deploy TransparentVaultFactoryUpgradeable (UUPS)
   ...
   
-  // 7. Configure OrionConfig with remaining deployed contracts
+  // 8. Configure OrionConfig with remaining deployed contracts
   ...
   
-  // 8. Link orchestrators (LiquidityOrchestrator needs InternalStatesOrchestrator reference)
+  // 9. Link orchestrators (LiquidityOrchestrator needs InternalStatesOrchestrator reference)
test/TransparentVault.test.ts (1)

29-31: Unused variables declared with underscore prefix.

The variables _priceAdapterRegistry, _internalStatesOrchestrator, and _liquidityOrchestrator are assigned from the deployment result but appear unused in this test file. If they're intentionally unused, consider not destructuring them at all to avoid confusion.

-let _priceAdapterRegistry: PriceAdapterRegistryUpgradeable;
-let _internalStatesOrchestrator: InternalStatesOrchestratorUpgradeable;
-let _liquidityOrchestrator: LiquidityOrchestratorUpgradeable;

And at lines 65-67:

-  _priceAdapterRegistry = deployed.priceAdapterRegistry;
-  _internalStatesOrchestrator = deployed.internalStatesOrchestrator;
-  _liquidityOrchestrator = deployed.liquidityOrchestrator;
+  // priceAdapterRegistry, internalStatesOrchestrator, liquidityOrchestrator available from deployed if needed
contracts/vaults/OrionTransparentVaultUpgradeable.sol (1)

111-120: Consider using uint256 for loop indices to avoid potential truncation.

Multiple functions cast .length() to uint16, which could truncate for large maps:

  • Line 112: uint16(_portfolio.length())
  • Line 131: uint16(_portfolioIntent.length())
  • Line 150: uint16(tokens.length)

While unlikely to exceed 65,535 in practice, using uint256 is safer and consistent with Solidity conventions.

-    uint16 length = uint16(_portfolio.length());
+    uint256 length = _portfolio.length();
     tokens = new address[](length);
     sharesPerAsset = new uint256[](length);
-    for (uint16 i = 0; i < length; ++i) {
+    for (uint256 i = 0; i < length; ++i) {

Also applies to: 131-139, 150-154

test/BatchLimitAccounting.test.ts (2)

27-42: Unused automationRegistry signer while deployment uses default.

The fixture declares automationRegistry at line 31 but doesn't pass it to deployUpgradeableProtocol. The deployment helper will default to using admin (which is owner here) as the automation registry. This inconsistency could cause confusion if tests expect automationRegistry to have special privileges.

If automationRegistry should be used:

-    const deployed = await deployUpgradeableProtocol(owner, owner);
+    const deployed = await deployUpgradeableProtocol(owner, owner, undefined, automationRegistry);

If automationRegistry is intentionally unused, remove it:

     const owner = allSigners[0];
     const curator = allSigners[1];
-    const automationRegistry = allSigners[2];
-    const users = allSigners.slice(3); // Remaining signers for testing
+    const users = allSigners.slice(2); // Remaining signers for testing

72-83: Fixture return object includes unused automationRegistry.

Given the earlier observation that automationRegistry isn't passed to deployment and may not have expected privileges, including it in the returned object could mislead test authors. Either pass it to deployment or remove from returns.

test/OrionConfigVault.test.ts (1)

414-421: Impersonated account not stopped after use.

The impersonateAccount is called for loAddress but stopImpersonatingAccount is not called after the fulfillDeposit operation. While this may work in isolated tests, it's good practice to clean up impersonation to avoid state leakage across tests in the same beforeEach block.

       await vault.connect(loSigner).fulfillDeposit(depositAmount);
+
+      // Stop impersonation
+      await ethers.provider.send("hardhat_stopImpersonatingAccount", [loAddress]);
     });
test/Adapters.test.ts (1)

44-48: Incorrect type cast: MockUnderlyingAsset cast to MockERC4626Asset.

The code deploys MockUnderlyingAsset (a simple ERC20 token) but casts it to MockERC4626Asset (an ERC4626 vault). These are fundamentally different contracts:

  • MockUnderlyingAsset is a basic ERC20 with mint function
  • MockERC4626Asset is an ERC4626 vault with deposit, withdraw, asset(), etc.

The test at line 59 calls orionConfig.addWhitelistedAsset with this miscast asset, which should correctly revert with InvalidAdapter since a plain ERC20 is not ERC4626-compliant. However, the variable name and type are misleading.

Consider renaming for clarity:

-    // Deploy additional mock asset for testing (different from underlying)
+    // Deploy a regular ERC20 (not ERC4626) for adapter validation tests
     const MockERC20AssetFactory = await ethers.getContractFactory("MockUnderlyingAsset");
     const mockAsset1Deployed = await MockERC20AssetFactory.deploy(10);
     await mockAsset1Deployed.waitForDeployment();
-    mockAsset1 = mockAsset1Deployed as unknown as MockERC4626Asset;
+    // Note: This is intentionally a plain ERC20 to test adapter rejection
+    mockAsset1 = mockAsset1Deployed as unknown as MockERC4626Asset; // Used to test InvalidAdapter errors
contracts/OrionConfigUpgradeable.sol (2)

37-84: Storage gap should be placed before state variables or storage layout documented.

In upgradeable contracts, the storage gap at line 463 comes after all state variables. However, the EnumerableSet variables (lines 73-83) are complex types that internally manage their own storage slots. When adding new state variables in future upgrades, developers must be careful to add them before the gap, not after the existing variables.

Consider documenting the storage layout explicitly or moving the gap declaration closer to a comment that clarifies where new variables should be inserted in V2.


251-267: External call to this.isWhitelisted is unnecessary gas overhead.

Using this.isWhitelisted(asset) on line 254 makes an external call to the same contract. Since isWhitelisted just reads from whitelistedAssets, you can call the internal storage directly.

 function addWhitelistedAsset(address asset, address priceAdapter, address executionAdapter) external onlyOwner {
     if (!isSystemIdle()) revert ErrorsLib.SystemNotIdle();

-    if (!this.isWhitelisted(asset)) {
+    if (!whitelistedAssets.contains(asset)) {
         // slither-disable-next-line unused-return
         whitelistedAssets.add(asset);
     }
contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol (2)

568-577: TODO comment indicates incomplete implementation for excluded assets.

Lines 568-577 contain a TODO describing complex logic for handling excluded assets during rebalancing. This appears to be unimplemented functionality that could affect accounting correctness if excludedAssets is non-empty.

The TODO describes reinterpreting intents when assets are excluded. Would you like help implementing this logic, or should a follow-up issue be created to track this?


93-118: Clarify the EpochState mapping invariant with explicit documentation.

The EpochState struct with nested mappings relies on a subtle invariant: all mappings (priceArray, initialBatchPortfolio, finalBatchPortfolio, sellingOrders, buyingOrders) are only updated for tokens tracked in the tokens array, ensuring epoch reset in _handleStart clears all stale data. Solidity cannot delete mappings because it does not know the keys, so this array-based tracking is essential.

The code currently maintains this invariant correctly: priceArray updates are gated by tokenExists checks, and all other mapping assignments are followed by or derive from _addTokenIfNotExists calls. However, this pattern is implicit and fragile. Add a comment above the EpochState struct definition clearly stating this invariant, or consider adding runtime checks (e.g., assertions in _handleStart) to detect violations during testing.

contracts/vaults/OrionVaultUpgradeable.sol (1)

224-230: _initializeVaultWhitelist reverts on duplicate which should be impossible.

The check if (!inserted) revert ErrorsLib.AlreadyRegistered() on line 228 would only trigger if the protocol whitelist contains duplicates, which should be prevented by OrionConfig.addWhitelistedAsset. This is defensive but the revert could be removed or changed to a continue.

 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();
+        // Protocol whitelist should not contain duplicates
+        _vaultWhitelistedAssets.add(protocolAssets[i]);
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (34)
  • contracts/OrionConfigUpgradeable.sol (1 hunks)
  • contracts/factories/TransparentVaultFactoryUpgradeable.sol (1 hunks)
  • contracts/mocks/OrionConfigUpgradeableV2.sol (1 hunks)
  • contracts/mocks/OrionTransparentVaultUpgradeableV2.sol (1 hunks)
  • contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol (1 hunks)
  • contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (1 hunks)
  • contracts/price/PriceAdapterRegistryUpgradeable.sol (1 hunks)
  • contracts/vaults/OrionTransparentVaultUpgradeable.sol (1 hunks)
  • contracts/vaults/OrionVaultUpgradeable.sol (1 hunks)
  • package.json (1 hunks)
  • scripts/testUpgradeability.ts (1 hunks)
  • scripts/verifyUpgradeRequiresNewAddress.ts (1 hunks)
  • test/AccessControl.test.ts (7 hunks)
  • test/Adapters.test.ts (2 hunks)
  • test/BatchLimitAccounting.test.ts (3 hunks)
  • test/BatchLimitConsistency.test.ts (1 hunks)
  • test/ExecutionAdapterValidation.test.ts (1 hunks)
  • test/FeeCooldown.test.ts (3 hunks)
  • test/MinimumAmountDOS.test.ts (3 hunks)
  • test/OrionConfigVault.test.ts (5 hunks)
  • test/OrionVaultExchangeRate.test.ts (2 hunks)
  • test/PassiveCuratorStrategy.test.ts (6 hunks)
  • test/ProtocolPause.test.ts (6 hunks)
  • test/Removal.test.ts (5 hunks)
  • test/TransparentVault.test.ts (8 hunks)
  • test/VaultOwnerRemoval.test.ts (4 hunks)
  • test/helpers/deployUpgradeable.ts (1 hunks)
  • test/mainnet-fork/erc4626VaultCompatibility.test.ts (11 hunks)
  • test/mainnet-fork/multiAssetRobustness.test.ts (6 hunks)
  • test/mainnet-fork/removeWhitelistedAsset.test.ts (6 hunks)
  • test/orchestrator/OrchestratorConfiguration.test.ts (12 hunks)
  • test/orchestrator/OrchestratorSecurity.test.ts (12 hunks)
  • test/orchestrator/OrchestratorsZeroState.test.ts (3 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (14)
test/BatchLimitConsistency.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/orchestrator/OrchestratorsZeroState.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/Adapters.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/BatchLimitAccounting.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/VaultOwnerRemoval.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/MinimumAmountDOS.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/PassiveCuratorStrategy.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/OrionConfigVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/ExecutionAdapterValidation.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (2)
test/orchestrator/Orchestrators.test.ts (2)
  • expect (677-2173)
  • it (676-2608)
test/RedeemBeforeDepositOrder.test.ts (1)
  • processLiquidityOrchestrator (73-79)
test/ProtocolPause.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/TransparentVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/Removal.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
test/AccessControl.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (44-138)
🪛 GitHub Actions: CI
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol

[error] 465-465: Hardhat compilation failed: TypeError: Member "processLP" not found or not visible after argument-dependent lookup in contract IInternalStateOrchestrator.

⏰ 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 (78)
package.json (1)

80-80: Verify package version validity and security advisories.

The addition of @openzeppelin/contracts-upgradeable is appropriate for the PR's upgradeable architecture objectives. Ensure that version 5.4.0 exists, is secure, and is compatible with the base contracts version.

Run the following script to verify the package version and check for security advisories:

#!/bin/bash
# Description: Verify @openzeppelin/contracts-upgradeable version 5.4.0 exists and check for vulnerabilities

# Check NPM registry for version existence
echo "Checking NPM registry for version 5.4.0..."
curl -s https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/5.4.0 | jq -r '.version // "Version not found"'

# Check latest available version
echo -e "\nLatest version available:"
curl -s https://registry.npmjs.org/@openzeppelin/contracts-upgradeable/latest | jq -r '.version'

# Check for security advisories
echo -e "\nChecking for security advisories..."
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: NPM, package: "@openzeppelin/contracts-upgradeable") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (6)

510-514: Storage gap placement looks correct.

The 50-slot storage gap at the end of the contract follows OpenZeppelin's upgradeable contract pattern and provides reasonable room for future state additions.


504-508: LGTM: Standard UUPS authorization.

The _authorizeUpgrade implementation correctly restricts upgrades to the owner.


306-334: Upkeep functions have proper guards.

The performUpkeep correctly uses onlyAuthorizedTrigger, nonReentrant, and whenNotPaused modifiers. Phase transitions are deterministic.


424-451: Batch processing logic is sound.

The minibatch approach correctly handles vault iteration with proper boundary checks and state resets. This prevents gas limit issues for large vault counts.


178-183: One-time setter for internalStatesOrchestrator.

The function intentionally prevents re-registration. Ensure the correct address is set during deployment as there's no recovery path without redeployment.


465-477: Remove this comment — processLP() is correctly defined in IInternalStateOrchestrator interface.

The method processLP() is properly declared in the interface at line 63 as function processLP() external view returns (bool);. The code at line 465 is valid and will compile without errors.

Likely an incorrect or invalid review comment.

test/OrionVaultExchangeRate.test.ts (2)

63-80: Fixture structure looks consistent with test patterns.

The return structure correctly provides both contract instances and addresses. The naming convention (liquidityOrchestrator for signer, liquidityOrchestratorContract for contract) aligns with impersonation patterns used in tests.


1-6: Clean migration to upgradeable protocol deployment.

The test file correctly adopts the deployUpgradeableProtocol helper and upgrades all contract interactions to use upgradeable variants. Test semantics remain unchanged.

contracts/mocks/OrionConfigUpgradeableV2.sol (1)

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

The mock correctly extends OrionConfigUpgradeable, adds a new state variable after inherited storage, and provides upgrade verification via version(). The onlyOwner modifier is correctly inherited.

contracts/mocks/OrionTransparentVaultUpgradeableV2.sol (1)

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

The mock correctly extends the V1 contract, adds new state after inherited storage, and provides a version() function for upgrade verification. The onlyVaultOwner modifier is properly inherited from OrionVaultUpgradeable and ensures access control consistency.

scripts/verifyUpgradeRequiresNewAddress.ts (3)

1-3: LGTM on imports.

The imports are correctly structured for a Hardhat upgrades verification script.


84-147: LGTM on Beacon pattern verification.

The Beacon upgrade test correctly demonstrates the no-op behavior when upgrading to the same implementation and validates successful upgrade to V2.


190-195: LGTM on script execution pattern.

Standard Hardhat script execution pattern with proper error handling and exit codes.

test/BatchLimitConsistency.test.ts (4)

1-13: LGTM on imports and helper function.

The imports are correctly structured for the upgradeable protocol testing, and the impersonateLiquidityOrchestrator helper correctly funds the impersonated account with ETH for gas.


135-193: LGTM on pendingDeposit batch limit tests.

The tests correctly handle Hardhat's signer limitations and validate the batch limiting logic with appropriate assertions.


195-281: LGTM on pendingRedeem batch limit tests.

The nested beforeEach correctly sets up users with shares, and the tests properly validate redeem batch limiting behavior.


283-356: LGTM on double-counting prevention tests.

The tests effectively validate the critical accounting fix, ensuring pendingDeposit and pendingRedeem respect maxFulfillBatchSize to prevent overcounting in totalAssets().

scripts/testUpgradeability.ts (3)

1-3: LGTM on imports.

Correctly imports ethers, upgrades, and chai expect for the upgrade demonstration script.


267-339: LGTM on Beacon upgrade testing.

The script properly demonstrates Beacon upgrade lifecycle: V2 deployment, beacon upgrade, state preservation verification, and V2 feature testing.


341-420: LGTM on UUPS upgrade testing and summary.

The UUPS upgrade demonstration is thorough, with proper state preservation verification and V2 feature testing. The summary provides excellent documentation of upgrade semantics.

test/MinimumAmountDOS.test.ts (3)

1-6: LGTM on imports update.

Imports correctly updated to include the OpenZeppelin upgrades plugin and the deployUpgradeableProtocol helper.


20-54: LGTM on fixture migration to upgradeable protocol.

The fixture correctly uses deployUpgradeableProtocol and attaches the vault using the upgradeable factory pattern.


75-422: LGTM on DOS prevention test coverage.

The tests comprehensively cover minimum amount enforcement, attack prevention scenarios, edge cases, and integration with existing validations. The migration to upgradeable contracts is seamless.

test/FeeCooldown.test.ts (3)

1-6: LGTM on imports update.

Imports correctly updated to include the OpenZeppelin upgrades plugin and the deployUpgradeableProtocol helper.


31-53: LGTM on fixture migration.

The fixture correctly uses deployUpgradeableProtocol and properly types the InternalStatesOrchestratorUpgradeable.


87-619: LGTM on fee cooldown test coverage.

The tests comprehensively cover cooldown duration management, timing transitions, authorization, edge cases, and attack prevention scenarios. The migration to upgradeable contracts is properly integrated.

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

30-40: LGTM - correctly uses upgradeable deployment helper.

The test properly uses deployUpgradeableProtocol and correctly assigns all returned components. The pattern aligns with the upgradeable architecture introduced in this PR.

Note: user is passed as the admin parameter (second argument), which appears intentional for this test's access control setup.


60-63: LGTM - vault resolution uses correct upgradeable type.

The vault is correctly retrieved using OrionTransparentVaultUpgradeable contract name, consistent with the beacon proxy pattern used in the upgradeable factory.

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

316-381: Good enhancement: Comprehensive immutability checks for proxy contracts.

The updated immutability tests now properly:

  • Check bytecode stability across blocks
  • Detect proxy contracts via EIP-1967 implementation slot
  • Verify implementation slot immutability for proxies
  • Validate asset() and decimals() consistency across block boundaries

This is a solid approach for validating that external ERC4626 vaults meet Orion's immutability requirements.

test/helpers/deployUpgradeable.ts (1)

44-138: LGTM on the deployment flow logic.

The deployment sequence correctly handles the dependency ordering:

  1. OrionConfig first (central registry)
  2. PriceAdapterRegistry (depends on config)
  3. LiquidityOrchestrator before InternalStatesOrchestrator (due to config dependency)
  4. Setting LiquidityOrchestrator in config before InternalStatesOrchestrator deployment
  5. Beacon + Factory for vaults
  6. Final wiring of cross-references

The UUPS proxy pattern is correctly applied with kind: "uups" and proper initializer functions.

test/TransparentVault.test.ts (2)

36-68: LGTM on the migration to upgradeable protocol deployment.

The test setup correctly:

  1. Deploys mock underlying asset with explicit decimals
  2. Uses the new deployUpgradeableProtocol helper with the pre-deployed underlying asset
  3. Extracts required components from the deployment result
  4. Properly types contract instances with upgradeable variants

124-127: Consistent usage of upgradeable contract artifacts.

All vault retrievals correctly use "OrionTransparentVaultUpgradeable" artifact name and cast to OrionTransparentVaultUpgradeable type. This is consistent with the migration pattern.

Also applies to: 160-163, 201-204, 235-238, 416-419

contracts/vaults/OrionTransparentVaultUpgradeable.sol (4)

36-38: LGTM on the constructor pattern for upgradeable contracts.

The constructor correctly calls _disableInitializers() to prevent the implementation contract from being initialized directly, following OpenZeppelin's UUPS best practices.


223-228: LGTM on storage gap for upgrade safety.

The uint256[50] private __gap provides 50 storage slots for future upgrades, following OpenZeppelin's upgrade-safe storage pattern.


199-221: LGTM on removeFromVaultWhitelist weight redistribution.

The function correctly:

  1. Removes the asset from the whitelist
  2. Retrieves the blacklisted asset's weight from intent
  3. Redistributes the weight to the underlying asset
  4. Handles both cases where underlying already exists in intent or not

This ensures intent always sums to 100% after blacklist removal.


143-166: No reentrancy guard needed for updateVaultState – the called function is view-only.

The convertToAssets function is a public view function, meaning it cannot make external calls or modify state. The function only performs arithmetic operations and reads internal state. While the function modifies critical state variables (_portfolio, _totalAssets, feeModel.highWaterMark), the access control modifier onlyLiquidityOrchestrator provides sufficient protection. Note that similar privileged functions like fulfillDeposit and fulfillRedeem do include the nonReentrant modifier for consistency—consider adding it here if you prefer uniform defensive coverage across all orchestrator-called functions.

test/ProtocolPause.test.ts (2)

86-97: LGTM on migration to upgradeable protocol deployment.

The test correctly uses deployUpgradeableProtocol with proper parameters:

  • admin as both owner and admin
  • undefined for underlying asset (creates mock internally)
  • automationRegistry for automation registry

Components are correctly extracted from the deployment result.


155-158: Consistent usage of upgradeable contract artifacts for vault retrieval.

The vault is correctly retrieved using "OrionTransparentVaultUpgradeable" artifact name.

test/BatchLimitAccounting.test.ts (1)

63-64: LGTM on vault retrieval with upgradeable contract.

The vault is correctly obtained using getContractAt("OrionTransparentVaultUpgradeable", vaultAddress) and properly typed.

test/OrionConfigVault.test.ts (3)

40-48: Good migration to upgradeable protocol deployment.

The test correctly uses deployUpgradeableProtocol helper and properly destructures the returned components. Type assignments are consistent with the upgradeable contract variants.


112-115: Vault instantiation correctly uses upgradeable type.

The vault is correctly retrieved using OrionTransparentVaultUpgradeable contract name, consistent with the upgradeable factory deployment.


397-506: Comprehensive redeem request cancellation tests added.

The new test section properly sets up the user with shares via deposit fulfillment, then tests various cancellation scenarios including zero amount, over-cancellation, full cancellation, and partial cancellation. The test logic correctly validates share balances before and after operations.

test/ExecutionAdapterValidation.test.ts (3)

35-42: Correct upgradeable protocol deployment.

The test properly uses deployUpgradeableProtocol with appropriate signers and correctly destructures the returned components.


24-26: Underscore prefix indicates intentionally unused variables.

The variables _priceAdapterRegistry and _internalStatesOrchestrator are prefixed with underscore following the convention for variables that need to be declared for destructuring but aren't used in tests. This is consistent with other test files.


183-201: Impersonation pattern is correct with proper cleanup.

The test correctly impersonates LiquidityOrchestrator, sets balance for gas, executes operations, and stops impersonation in the finally block. This pattern is consistent throughout the file.

test/Adapters.test.ts (2)

35-42: Correct upgradeable protocol deployment.

The test properly uses deployUpgradeableProtocol with the appropriate signers including automationRegistry.


176-218: ERC4626 Execution Adapter tests are well-structured.

The nested beforeEach properly sets up an actual ERC4626 vault with the correct underlying asset, seeds it with initial deposits, and whitelists it. The slippage tolerance configuration ensures realistic testing conditions.

test/VaultOwnerRemoval.test.ts (4)

31-38: Correct upgradeable protocol deployment in fixture.

The fixture properly uses deployUpgradeableProtocol and correctly types all returned components with their upgradeable variants.


59-89: Well-typed createVault helper function.

The helper function correctly:

  • Accepts TransparentVaultFactoryUpgradeable and OrionConfigUpgradeable parameters
  • Returns Promise<OrionTransparentVaultUpgradeable>
  • Uses getContractAt("OrionTransparentVaultUpgradeable", ...) to retrieve the vault

This is consistent with the upgradeable architecture.


91-128: Comprehensive vault owner removal tests.

Tests properly validate:

  • Decommissioning of all vaults owned by removed vault owner
  • Event emission for VaultOwnerRemoved
  • State consistency (isOrionVault, isDecommissioningVault, isWhitelistedVaultOwner)

243-273: Good integration test for vault lifecycle.

The test properly demonstrates the complete decommissioning flow:

  1. Creates vault
  2. Removes vault owner (triggers decommissioning)
  3. Impersonates LiquidityOrchestrator to complete decommissioning
  4. Validates final state (isDecommissionedVault)

The impersonation pattern with balance setting is correctly implemented.

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

71-85: LGTM!

The imports are correctly updated to use the upgradeable contract type variants, and the import path for deployUpgradeableProtocol is properly set.


186-193: LGTM!

The upgradeable protocol deployment is correctly wired. The helper is called with the appropriate parameters (owner, user, underlyingAsset, automationRegistry), and the returned components are properly destructured and assigned to the test variables.

test/PassiveCuratorStrategy.test.ts (3)

1-6: LGTM!

The imports are correctly placed and updated to use upgradeable contract variants.


110-117: LGTM!

The upgradeable protocol deployment is properly integrated. The mock underlying asset is deployed first and then passed to the helper, which is the correct pattern for tests requiring a specific underlying asset configuration.


190-193: LGTM!

Vault lookup correctly uses the upgradeable contract name OrionTransparentVaultUpgradeable.

test/AccessControl.test.ts (3)

1-13: LGTM!

Imports are correctly updated for the upgradeable migration. The @openzeppelin/hardhat-upgrades import is properly placed.


23-27: LGTM!

The underscore prefix convention for unused variables (_orionConfig, _priceAdapterRegistry, _internalStatesOrchestrator, _liquidityOrchestrator) is appropriate and signals that these are intentionally unused in this test file.


35-43: LGTM!

The deployment correctly uses the upgradeable helper and destructures all needed components. Passing owner as both the owner and admin parameters is appropriate for these access control tests.

test/Removal.test.ts (3)

1-6: LGTM!

Imports are correctly updated for the upgradeable migration, and the import placement is proper.


77-84: LGTM!

The upgradeable protocol deployment is correctly integrated with the mock underlying asset deployed beforehand. The destructuring properly assigns all required components.


130-133: LGTM!

Vault lookup correctly uses the upgradeable contract name OrionTransparentVaultUpgradeable.

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

94-108: LGTM!

The imports after the JSDoc block are correctly set up. Once the misplaced import on line 3 is fixed, this file will have proper import structure.


209-216: LGTM!

The upgradeable protocol deployment is correctly wired with proper parameter passing and destructuring.

contracts/OrionConfigUpgradeable.sol (2)

455-456: LGTM on upgrade authorization.

The _authorizeUpgrade function correctly restricts upgrades to only the owner, following UUPS best practices.


434-440: I need the review comment to verify and rewrite. Please provide:

  1. The original review comment that needs to be rewritten
  2. The relevant code context or file contents
  3. Any repository or project information needed for verification

[cannot_proceed_missing_input]

contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol (3)

410-497: LGTM on preprocessing minibatch logic.

The preprocessing logic correctly:

  • Handles minibatch pagination with proper bounds checking
  • Caches prices to avoid redundant oracle calls
  • Calculates fees in the correct order (protocol volume fee, then curator fees)
  • Uses proper decimal conversions via UtilitiesLib.convertDecimals

516-546: Buffer calculation could leave dust due to rounding.

In _buffer(), the deltaBufferAmount is distributed proportionally across vaults using mulDiv. Due to integer division rounding, the sum of vaultBufferCost across all vaults may be slightly less than deltaBufferAmount. The code correctly tracks actualBufferAllocated to handle this, which is good.


785-789: Storage gap is correctly placed and sized.

The 50-slot storage gap follows OpenZeppelin's recommended pattern for upgradeable contracts, allowing future versions to add state variables without breaking storage layout.

contracts/vaults/OrionVaultUpgradeable.sol (8)

158-162: Constructor correctly disables initializers for implementation contract.

This follows the OpenZeppelin pattern for UUPS proxies, preventing the implementation contract from being initialized directly.


351-375: Deposit request flow correctly validates and transfers to liquidity orchestrator.

The requestDeposit function:

  • Checks access control, system idle state, and decommissioning status
  • Validates minimum deposit amount
  • Safely transfers tokens using safeTransferFrom
  • Correctly accumulates requests for the same user

517-555: Fee calculation order matters - management fee reduces base before performance fee.

The curatorFee function calculates management fee first, then uses the reduced intermediateTotalAssets for performance fee calculation. This is intentional and correctly prevents double-counting. The _managementFeeAmount and _performanceFeeAmount functions properly prorate fees based on epoch duration.


561-581: Potential division by zero in _getBenchmark when divisor is 0.

In _getBenchmark, the divisor variable can be 0 if currentSharePrice is 0 (for ABSOLUTE type) or if feeModel.highWaterMark is 0 (for HIGH_WATER_MARK type). The caller _performanceFeeAmount checks if (activeSharePrice < benchmark || divisor == 0) return 0 which guards against division by zero, but only after calling _getBenchmark.

The current code is safe because the check happens before division, but documenting this invariant would help maintainability.


742-746: Storage gap correctly reserves 50 slots for future upgrades.

The storage gap follows best practices and is placed at the end of the contract's state variables.


42-46: Abstract contract lacks UUPSUpgradeable - verify derived contracts include it.

OrionVaultUpgradeable is abstract and inherits from Initializable, ERC4626Upgradeable, and ReentrancyGuardUpgradeable, but not UUPSUpgradeable. For the UUPS pattern to work, derived contracts (e.g., OrionTransparentVaultUpgradeable) must inherit UUPSUpgradeable and implement _authorizeUpgrade.

Verify derived vault contracts include UUPS:

#!/bin/bash
# Check if derived vault contracts inherit UUPSUpgradeable
rg -n "contract.*Vault.*Upgradeable.*is" --type sol -A 5 | rg -A 5 "OrionVaultUpgradeable"

698-740: fulfillRedeem does not update _totalAssets after burning shares.

Similar to fulfillDeposit, the fulfillRedeem function burns shares and transfers assets without updating _totalAssets. The function accepts a redeemTotalAssets parameter but never uses it to modify the state variable. This leaves the vault's total assets tracking stale after redemptions are processed. Verify whether the orchestration layer properly updates _totalAssets through a separate mechanism or if this represents an accounting gap.


659-696: fulfillDeposit does not update _totalAssets despite minting shares.

When deposits are fulfilled, shares are minted to users but _totalAssets is not increased. While share pricing correctly uses the depositTotalAssets parameter passed from InternalStatesOrchestrator (not the vault's internal _totalAssets), this design creates an asymmetry where the vault's share count increases while its reported totalAssets() remains unchanged. Verify that this async architecture—where assets remain in LiquidityOrchestrator rather than transferred to the vault—is intentional and clarify whether totalAssets() should reflect assets under management or assets physically held.

Comment thread contracts/factories/TransparentVaultFactoryUpgradeable.sol
Comment thread contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol
Comment thread test/mainnet-fork/removeWhitelistedAsset.test.ts
Comment thread test/mainnet-fork/removeWhitelistedAsset.test.ts
Comment thread test/orchestrator/OrchestratorConfiguration.test.ts Outdated
Comment thread test/orchestrator/OrchestratorSecurity.test.ts Outdated
Comment thread test/ProtocolPause.test.ts
@ojasarora77

ojasarora77 commented Dec 16, 2025

Copy link
Copy Markdown
Contributor Author

@matteoettam09, we need to delete the original contracts as well as they are all replaced by upgradable versions of the same

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

Caution

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

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

181-225: Inconsistent deployment: Deploys non-upgradeable contracts but uses upgradeable type annotations.

Lines 188-191 deploy OrionConfig (non-upgradeable) but cast to OrionConfigUpgradeable. Similarly for other contracts. This creates a mismatch between runtime behavior and compile-time types.

🔎 Suggested fix: Use consistent types

Either use the original non-upgradeable imports and types:

-import {
-  OrionConfigUpgradeable,
-  TransparentVaultFactoryUpgradeable,
-  PriceAdapterRegistryUpgradeable,
+import {
+  OrionConfig,
+  TransparentVaultFactory,
+  PriceAdapterRegistry,
   ...
-let orionConfig: OrionConfigUpgradeable;
+let orionConfig: OrionConfig;

Or deploy actual upgradeable contracts using the helper:

import { deployUpgradeableProtocol } from "../helpers/deployUpgradeable";
// In before():
const deployed = await deployUpgradeableProtocol(owner, admin);
orionConfig = deployed.orionConfig;
♻️ Duplicate comments (11)
contracts/price/PriceAdapterRegistryUpgradeable.sol (1)

49-59: LGTM! Zero address validation and initialization are correct.

The past review concern about missing initialOwner_ validation has been addressed. The initialization properly chains the upgradeable base contracts and reads priceAdapterDecimals from config.

contracts/factories/TransparentVaultFactoryUpgradeable.sol (2)

38-47: LGTM! Zero address validation now included for all initialization parameters.

The past review concern has been addressed - initialOwner is now validated before initialization proceeds.


105-111: Missing isSystemIdle() check in setVaultBeacon.

As previously noted, changing the vault beacon while the system is active could cause vaults to behave inconsistently within the same epoch (some calls hitting old implementation, others hitting new). Other config setters in the protocol require isSystemIdle().

🔎 Recommended fix
 function setVaultBeacon(address newVaultBeacon) external onlyOwner {
+    if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
     if (newVaultBeacon == address(0)) revert ErrorsLib.ZeroAddress();
     vaultBeacon = UpgradeableBeacon(newVaultBeacon);
     emit EventsLib.VaultBeaconUpdated(newVaultBeacon);
 }
contracts/vaults/OrionTransparentVaultUpgradeable.sol (1)

89-98: uint16 cast issue persists from previous review.

The cast uint16(intent.length) at line 90 can silently truncate if the array exceeds 65,535 elements. While practically unlikely, this pattern is repeated in getPortfolio (line 112), getIntent (line 131), and updateVaultState (line 150). Using uint256 for loop variables and length is safer and has negligible gas impact.

🔎 Proposed fix for all affected functions
     uint256 totalWeight = 0;
-    uint16 intentLength = uint16(intent.length);
-    for (uint16 i = 0; i < intentLength; ++i) {
+    uint256 intentLength = intent.length;
+    for (uint256 i = 0; i < intentLength; ++i) {

Apply similar changes to getPortfolio, getIntent, and updateVaultState.

contracts/OrionConfigUpgradeable.sol (2)

290-292: Potential truncation casting length() to uint16.

If the number of whitelisted assets exceeds 65,535, the cast will silently truncate. While unlikely, consider adding a bounds check or using uint256.


230-237: Potential revert if orchestrators not yet set.

If pauseAll is called before internalStatesOrchestrator or liquidityOrchestrator are set, the calls will revert with an unhelpful error. Consider adding explicit zero-address checks.

contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol (4)

362-367: Loop uses uint16 for index but array length is uint256.

If allTransparent.length exceeds 65,535, the loop index would overflow. Consider using uint256 for the loop index.


192-201: Dependencies from config are not validated before use.

Lines 193 and 201 read priceAdapterRegistry() and liquidityOrchestrator() from config. If these haven't been set on the config contract yet, registry or liquidityOrchestrator could be address(0), causing issues later. Consider adding validation.

🔎 Proposed fix
 config = IOrionConfig(config_);
-registry = IPriceAdapterRegistry(config.priceAdapterRegistry());
+address registryAddr = config.priceAdapterRegistry();
+if (registryAddr == address(0)) revert ErrorsLib.ZeroAddress();
+registry = IPriceAdapterRegistry(registryAddr);
 // ...
-liquidityOrchestrator = ILiquidityOrchestrator(config.liquidityOrchestrator());
+address loAddr = config.liquidityOrchestrator();
+if (loAddr == address(0)) revert ErrorsLib.ZeroAddress();
+liquidityOrchestrator = ILiquidityOrchestrator(loAddr);

516-546: Division by zero when protocolTotalAssets is zero.

If all vaults have zero assets, protocolTotalAssets remains 0, and deltaBufferAmount.mulDiv(vaultAssets, protocolTotalAssets) at line 540 will revert. Add a guard to return early when TVL is zero.

🔎 Proposed fix
 function _buffer() internal {
     currentPhase = InternalUpkeepPhase.PostprocessingTransparentVaults;

     uint16 nTransparentVaults = uint16(transparentVaultsEpoch.length);

     uint256 protocolTotalAssets = 0;
     for (uint16 i = 0; i < nTransparentVaults; ++i) {
         address vault = transparentVaultsEpoch[i];
         protocolTotalAssets += _currentEpoch.vaultsTotalAssets[address(vault)];
     }

+    // Guard against division by zero when TVL is zero
+    if (protocolTotalAssets == 0) return;
+
     uint256 targetBufferAmount = protocolTotalAssets.mulDiv(

739-745: Potential underflow in updateBufferAmount when delta exceeds buffer.

If deltaAmount is negative and its absolute value exceeds bufferAmount, line 743 will underflow. While caller logic in LiquidityOrchestrator may protect against this, adding a defensive check provides safety.

🔎 Proposed fix
 function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
     if (deltaAmount > 0) {
         bufferAmount += uint256(deltaAmount);
     } else if (deltaAmount < 0) {
+        uint256 absAmount = uint256(-deltaAmount);
+        if (absAmount > bufferAmount) revert ErrorsLib.InsufficientAmount();
-        bufferAmount -= uint256(-deltaAmount);
+        bufferAmount -= absAmount;
     }
 }
contracts/vaults/OrionVaultUpgradeable.sol (1)

261-264: Potential underflow when updating _totalAssets in redeem.

Line 263 subtracts assets from _totalAssets without verifying assets <= _totalAssets. While previewRedeem should return a consistent value, adding a defensive check prevents potential accounting issues.

🔎 Proposed fix
 uint256 assets = previewRedeem(shares);
 // Update total assets accounting
+if (assets > _totalAssets) revert ErrorsLib.InsufficientAmount();
 _totalAssets -= assets;
🧹 Nitpick comments (9)
test/TransparentVault.test.ts (1)

30-88: beforeEach positioned outside describe block affects test isolation.

The beforeEach hook (lines 30-88) is defined at module scope, outside any describe block. This means it runs before every test in the file, including those in nested describe blocks.

While this works, it's unconventional and could cause confusion. Consider wrapping all tests in a top-level describe block to make the scope explicit:

+describe("TransparentVault Tests", function () {
   beforeEach(async function () {
     // ... setup code
   });
   
   describe("TransparentVault - Curator Pipeline", function () {
     // ... tests
   });
+});

This is a minor structural concern and doesn't affect test correctness.

test/Adapters.test.ts (1)

24-26: Underscore-prefixed variables appear unused.

The variables _priceAdapterRegistry and _internalStatesOrchestrator are declared and assigned but never referenced in the test file. If they're intentionally kept for debugging or future use, this is fine. Otherwise, consider removing them to reduce noise.

-  let _priceAdapterRegistry: PriceAdapterRegistryUpgradeable;
   let liquidityOrchestrator: LiquidityOrchestratorUpgradeable;
-  let _internalStatesOrchestrator: InternalStatesOrchestratorUpgradeable;

And update the assignment:

-    _priceAdapterRegistry = deployed.priceAdapterRegistry;
-    _internalStatesOrchestrator = deployed.internalStatesOrchestrator;
contracts/price/PriceAdapterRegistryUpgradeable.sol (1)

57-58: Consider: priceAdapterDecimals is cached at initialization and could become stale.

If priceAdapterDecimals is ever updated in OrionConfig after this registry is initialized, this contract's cached value won't reflect the change. This may be intentional for consistency, but worth documenting or adding a setter if runtime updates are expected.

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

66-84: Missing @openzeppelin/hardhat-upgrades import.

Unlike other test files (e.g., BatchLimitAccounting.test.ts, OrionConfigVault.test.ts), this file does not have the import "@openzeppelin/hardhat-upgrades"; side-effect import. While the tests may still run if the plugin is globally registered via hardhat.config.ts, adding the explicit import maintains consistency across test files and ensures the upgrades plugin is properly loaded.

🔎 Proposed fix
 import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
 import { expect } from "chai";
 import { ethers } from "hardhat";
+import "@openzeppelin/hardhat-upgrades";
 import { time } from "@nomicfoundation/hardhat-network-helpers";
 import { deployUpgradeableProtocol } from "../helpers/deployUpgradeable";
test/orchestrator/OrchestratorSecurity.test.ts (1)

89-107: Missing @openzeppelin/hardhat-upgrades import for consistency.

Same as OrchestratorConfiguration.test.ts, this file lacks the explicit import "@openzeppelin/hardhat-upgrades"; side-effect import that other test files include.

🔎 Proposed fix
 import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
 import { expect } from "chai";
 import { ethers } from "hardhat";
+import "@openzeppelin/hardhat-upgrades";
 import { time } from "@nomicfoundation/hardhat-network-helpers";
 import { deployUpgradeableProtocol } from "../helpers/deployUpgradeable";
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (3)

514-515: Storage gap placement is acceptable but verify slot count.

The 50-slot gap is reasonable. However, verify that the total state variable slots plus the gap will remain consistent across upgrades. Consider documenting the current slot usage to prevent storage collisions in future upgrades.


327-338: PortfolioRebalanced event emitted prematurely on each phase.

The event is emitted after every performUpkeep call (selling, buying, or vault operations), but the portfolio is only fully rebalanced after the final phase. Consider emitting this event only in _processVaultOperations when the epoch completes (near line 441-442).

🔎 Proposed fix
     function performUpkeep(bytes calldata) external override onlyAuthorizedTrigger nonReentrant whenNotPaused {
         if (currentPhase == LiquidityUpkeepPhase.SellingLeg) {
             _processSellLeg();
         } else if (currentPhase == LiquidityUpkeepPhase.BuyingLeg) {
             _processBuyLeg();
         } else if (currentPhase == LiquidityUpkeepPhase.ProcessVaultOperations) {
             _processVaultOperations();
             internalStatesOrchestrator.updateNextUpdateTime();
         }
-
-        emit EventsLib.PortfolioRebalanced();
     }

Then in _processVaultOperations, after the epoch completes:

         if (i1 > transparentVaults.length || i1 == transparentVaults.length) {
             i1 = uint16(transparentVaults.length);
             currentPhase = LiquidityUpkeepPhase.Idle;
             currentMinibatchIndex = 0;
             emit EventsLib.EpochProcessed(epochCounter);
+            emit EventsLib.PortfolioRebalanced();
             ++epochCounter;
         }

354-359: Loop index uses uint16 but array length is uint256.

While unlikely in practice, if sellingTokens.length exceeds 65,535, the loop index i would overflow. Consider using uint256 for consistency and safety.

-for (uint16 i = 0; i < sellingTokens.length; ++i) {
+for (uint256 i = 0; i < sellingTokens.length; ++i) {
contracts/vaults/OrionVaultUpgradeable.sol (1)

621-625: Loop index uses uint16 but batchSize is uint256.

If batchSize exceeds 65,535 (theoretically possible if maxFulfillBatchSize is set very high), the loop index would overflow. Consider using uint256 for consistency.

-for (uint16 i = 0; i < batchSize; ++i) {
+for (uint256 i = 0; i < batchSize; ++i) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6493955 and ff545ea.

📒 Files selected for processing (22)
  • contracts/OrionConfigUpgradeable.sol (1 hunks)
  • contracts/factories/TransparentVaultFactoryUpgradeable.sol (1 hunks)
  • contracts/libraries/EventsLib.sol (1 hunks)
  • contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol (1 hunks)
  • contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (1 hunks)
  • contracts/price/PriceAdapterRegistryUpgradeable.sol (1 hunks)
  • contracts/vaults/OrionTransparentVaultUpgradeable.sol (1 hunks)
  • contracts/vaults/OrionVaultUpgradeable.sol (1 hunks)
  • test/Adapters.test.ts (2 hunks)
  • test/BatchLimitAccounting.test.ts (3 hunks)
  • test/BatchLimitConsistency.test.ts (1 hunks)
  • test/FeeCooldown.test.ts (4 hunks)
  • test/OrionConfigVault.test.ts (5 hunks)
  • test/OrionVaultExchangeRate.test.ts (2 hunks)
  • test/ProtocolPause.test.ts (5 hunks)
  • test/TransparentVault.test.ts (8 hunks)
  • test/helpers/deployUpgradeable.ts (1 hunks)
  • test/mainnet-fork/erc4626VaultCompatibility.test.ts (10 hunks)
  • test/mainnet-fork/multiAssetRobustness.test.ts (1 hunks)
  • test/mainnet-fork/removeWhitelistedAsset.test.ts (5 hunks)
  • test/orchestrator/OrchestratorConfiguration.test.ts (11 hunks)
  • test/orchestrator/OrchestratorSecurity.test.ts (11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/mainnet-fork/multiAssetRobustness.test.ts
  • test/mainnet-fork/removeWhitelistedAsset.test.ts
🧰 Additional context used
🧬 Code graph analysis (8)
test/OrionVaultExchangeRate.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/TransparentVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/BatchLimitConsistency.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/BatchLimitAccounting.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/orchestrator/OrchestratorConfiguration.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/FeeCooldown.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/OrionConfigVault.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
test/ProtocolPause.test.ts (1)
test/helpers/deployUpgradeable.ts (1)
  • deployUpgradeableProtocol (45-139)
🪛 GitHub Actions: CI
contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol

[error] 469-469: TypeError: Member "processLP" not found or not visible after argument-dependent lookup in contract IInternalStateOrchestrator. HH600: Compilation failed.

⏰ 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 (35)
test/OrionVaultExchangeRate.test.ts (2)

19-26: Verify attacker as admin parameter is intentional.

The deployUpgradeableProtocol helper receives attacker as the second argument (admin), which grants admin privileges to this signer. While this may be intentional for test isolation, it differs from other test files that typically use a dedicated admin or other signer.

If the intent is to test scenarios where the attacker has no special privileges, consider using a separate signer for the admin role:

-    const deployed = await deployUpgradeableProtocol(owner, attacker);
+    const [owner, curator, lp1, lp2, lp3, internalStatesOrchestratorSigner, liquidityOrchestratorSigner, attacker, admin] =
+      await ethers.getSigners();
+    const deployed = await deployUpgradeableProtocol(owner, admin);

31-59: LGTM!

The vault creation via factory with event parsing is correctly implemented. The pattern is consistent with other test files in this PR, and the error handling for missing events is appropriate.

test/BatchLimitConsistency.test.ts (4)

15-73: Well-documented test file with clear explanation of the critical bug fix.

The documentation block thoroughly explains the accounting bug, its impact, the fix, and the test approach. The limitation section honestly addresses Hardhat signer constraints.


156-182: LGTM!

The effectiveBatchSize calculation correctly handles the limited signer constraint while still validating the batch limiting logic. The test properly verifies that pendingDeposit returns only the first effectiveBatchSize requests and not all requests.


200-221: Consider potential test isolation issue with shared beforeEach.

The describe("2. pendingRedeem Batch Limit") block has its own beforeEach that runs in addition to the outer beforeEach. This deposits and fulfills for up to 30 users, but individual tests may expect different user counts.

For test "Should return exact shares when requests < maxFulfillBatchSize" (line 223), only 10 users are processed, but 30 users may have received shares from the inner beforeEach. This could cause unexpected behavior if tests rely on specific user counts.

Consider moving the share provisioning into individual tests or ensuring test expectations account for the full user set.


287-360: LGTM!

The double-counting prevention tests correctly verify that pendingDeposit and pendingRedeem respect batch limits, preventing the critical accounting bug where totalAssets would be overcounted or double-subtracted across epochs.

contracts/libraries/EventsLib.sol (1)

139-143: LGTM!

The new VaultBeaconUpdated event is correctly defined with appropriate NatSpec documentation and an indexed parameter for efficient filtering. This enables observability for beacon updates in the upgradeable vault factory.

test/TransparentVault.test.ts (1)

115-118: LGTM!

The contract retrieval is correctly updated to use the upgradeable vault type. The pattern is consistent across all test cases in this file.

test/Adapters.test.ts (2)

35-42: LGTM!

The migration to deployUpgradeableProtocol is correct. The helper receives the automation registry as the 4th parameter, and since underlyingAsset is undefined, the helper will deploy a mock underlying asset internally.


44-56: LGTM!

The comment on lines 48-49 clearly explains why a plain ERC20 is cast to MockERC4626Asset — it's intentional for testing adapter rejection when non-ERC4626 tokens are used. This is good documentation for non-obvious test setup.

test/FeeCooldown.test.ts (2)

31-53: LGTM! Clean migration to upgradeable protocol deployment.

The fixture correctly uses deployUpgradeableProtocol helper and properly destructures the deployed components. The type annotations are consistent with the upgradeable contract types.


55-85: LGTM! Vault creation helper properly updated for upgradeable contracts.

The createVault function correctly returns OrionTransparentVaultUpgradeable and uses getContractAt with the upgradeable contract name.

contracts/price/PriceAdapterRegistryUpgradeable.sol (1)

80-87: LGTM! Upgrade authorization and storage gap are correctly implemented.

The _authorizeUpgrade function with onlyOwner modifier follows the UUPS pattern correctly. The 50-slot storage gap is standard practice for upgradeable contracts.

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

317-382: LGTM! Comprehensive immutability verification for proxy-aware testing.

The enhanced immutability checks properly verify:

  1. Bytecode consistency across blocks
  2. EIP-1967 implementation slot stability for proxies
  3. Cross-block determinism for asset() and decimals()

This is a thorough approach for validating external ERC4626 vaults.


566-573: LGTM! Critical validation that adapter decimals match underlying asset.

This assertion ensures price calculations remain consistent across the protocol by verifying the adapter returns decimals matching the underlying USDC decimals.


607-631: LGTM! Proper whitelisting workflow for execution adapter validation.

The test now correctly whitelists vaults before validation (required for OrionConfig.getTokenDecimals lookup) and cleans up afterward, preventing test pollution.

contracts/factories/TransparentVaultFactoryUpgradeable.sol (2)

58-103: LGTM! Vault creation properly enforces authorization and system state checks.

The createVault function correctly:

  1. Validates caller is whitelisted vault owner
  2. Ensures system is idle before deployment
  3. Encodes initialization data and deploys via BeaconProxy
  4. Registers vault in config and emits event

113-121: LGTM! Standard UUPS upgrade authorization and storage gap.

The _authorizeUpgrade with onlyOwner and the 50-slot storage gap follow best practices for upgradeable contracts.

test/ProtocolPause.test.ts (3)

45-58: LGTM! Clean imports for upgradeable protocol testing.

The imports are properly organized with the deployment helper and upgradeable types. The past issue with imports inside the JSDoc block has been resolved.


85-96: LGTM! Proper migration to upgradeable protocol deployment.

The test correctly uses deployUpgradeableProtocol helper and destructures the deployed components. This aligns with the pattern used across other test files in this PR.


136-168: LGTM! Vault creation and retrieval correctly use upgradeable types.

The vault factory is obtained from the deployed protocol, and the vault is correctly attached using OrionTransparentVaultUpgradeable contract name.

test/helpers/deployUpgradeable.ts (2)

45-139: Well-structured upgradeable deployment helper.

The deployment sequence is correctly ordered (LiquidityOrchestrator before InternalStatesOrchestrator, config wiring before dependent deployments). The helper centralizes protocol deployment for tests, reducing duplication across test files.

Minor observation: the as unknown as Type casts are necessary due to ethers v6 and typechain typing quirks, but consider adding a brief inline comment explaining this pattern for future maintainers.


147-150: LGTM!

Simple attach helper that correctly uses the contract factory pattern to connect to an existing vault proxy.

test/BatchLimitAccounting.test.ts (1)

27-81: Clean migration to upgradeable protocol deployment.

The fixture correctly uses deployUpgradeableProtocol and extracts components. The vault creation and retrieval pattern using ethers.getContractAt("OrionTransparentVaultUpgradeable", ...) is appropriate.

One note: the impersonation pattern for loSigner (lines 141-145) using hardhat_setBalance and getImpersonatedSigner is correct for testing LiquidityOrchestrator calls.

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

185-192: Correct upgradeable protocol deployment integration.

The test correctly deploys the mock underlying asset first, then passes it to deployUpgradeableProtocol along with the automationRegistry signer. The destructured components are properly assigned to module-level variables.

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

208-215: Correct integration with upgradeable deployment helper.

The test properly deploys the upgradeable protocol and extracts the required components for security testing.

test/OrionConfigVault.test.ts (2)

37-120: Clean migration to upgradeable protocol with proper test setup.

The beforeEach correctly uses deployUpgradeableProtocol and sets up additional mock assets and adapters needed for testing. The vault creation and retrieval pattern is consistent with other migrated tests.


397-509: Comprehensive redeem request cancellation test coverage.

Excellent addition of the cancelRedeemRequest test suite covering:

  • Zero amount rejection
  • Over-cancellation rejection
  • Full cancellation with share return verification
  • Partial cancellation with correct remaining balance

The test setup in beforeEach (lines 398-424) properly creates shares via deposit fulfillment using LO impersonation. The assertions verify both the user's balance changes and pending redeem state.

contracts/vaults/OrionTransparentVaultUpgradeable.sol (3)

36-38: Correct UUPS implementation pattern.

The constructor properly disables initializers on the implementation contract, preventing direct initialization of the implementation and ensuring only proxies can be initialized.


199-221: Weight rebalancing on asset removal is well-designed.

When a blacklisted asset is removed from the intent, its weight is correctly transferred to the underlying asset, maintaining the 100% total weight invariant. The logic handles both cases: when the underlying asset already exists in the intent (add to existing weight) and when it doesn't (set the blacklisted weight).


223-224: Storage gap correctly sized.

The __gap[50] provides adequate space for future storage variables while following OpenZeppelin's recommended upgrade-safe pattern.

contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol (1)

140-159: LGTM - Initialization is properly implemented.

All three parameters (initialOwner, config_, automationRegistry_) are now validated for zero addresses, and parent initializers are correctly chained. This addresses the previous review concern.

contracts/OrionConfigUpgradeable.sol (1)

113-137: LGTM - Initialization properly validates all parameters.

The initialOwner zero-address check has been added (line 114), addressing the previous review concern. The initialization correctly sets up defaults and whitelists the underlying asset.

contracts/vaults/OrionVaultUpgradeable.sol (2)

659-696: LGTM - Batch processing correctly uses snapshot pattern.

The fulfillDeposit function properly captures snapshotTotalSupply before processing and collects all users/amounts first to avoid swap-and-pop reordering issues during EnumerableMap removal. This ensures consistent pricing for all users in the batch.


527-555: LGTM - Fee calculations properly handle edge cases.

The fee calculation logic correctly handles zero fees (lines 529, 541), guards against division by zero (line 551), and applies epoch-based proration. The benchmark/divisor pattern properly supports all fee types.

Comment thread contracts/orchestrators/InternalStatesOrchestratorUpgradeable.sol
Comment thread contracts/orchestrators/LiquidityOrchestratorUpgradeable.sol
Comment thread contracts/OrionConfigUpgradeable.sol
Comment thread contracts/vaults/OrionVaultUpgradeable.sol
Comment thread test/mainnet-fork/erc4626VaultCompatibility.test.ts
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