Skip to content

Dev - #70

Merged
matteoettam09 merged 19 commits into
mainfrom
dev
Sep 16, 2025
Merged

Dev#70
matteoettam09 merged 19 commits into
mainfrom
dev

Conversation

@matteoettam09

@matteoettam09 matteoettam09 commented Sep 15, 2025

Copy link
Copy Markdown
Member

Summary by Sourcery

Implement full liquidity execution flow with slippage handling, buffer management, and enriched order estimation; refactor orchestrators, adapters, and vaults to support iterative minibatch processing and accurate state updates; update interfaces and tests accordingly.

New Features:

  • LiquidityOrchestrator now executes sell and buy minibatches with slippage bounds and tracks execution vs estimated amounts via a delta buffer
  • InternalStatesOrchestrator processes deposits, redemptions, and curator fees, updates vault states, and returns enriched buy/sell orders with estimated underlying amounts
  • ExecutionAdapter interface updated to accept share amounts and min/max underlying constraints, return actual executed amounts, and enforce slippage

Enhancements:

  • Vaults refactored to use InternalStatesOrchestrator for state updates, introduce convertToSharesWithPITTotalAssets and external accrueCuratorFees
  • Interfaces ILiquidityOrchestrator, IInternalStateOrchestrator, and IExecutionAdapter updated to reflect new function signatures and buffer management
  • Slippage tolerance logic incorporated into adapters and orchestrators to adjust buffer amounts dynamically

Tests:

  • Replaced mock adapters with real OrionAssetERC4626ExecutionAdapter and PriceAdapter in tests
  • Updated tests to use getOrders instead of separate getSellingOrders/getBuyingOrders and to validate full upkeep cycles

Chores:

  • Removed legacy mocks and TODO placeholders
  • Bumped package version to 0.4.4

Summary by CodeRabbit

  • New Features

    • Share-based ERC4626 buy/sell with slippage guards that return actual underlying spent/received.
    • Unified orders API and new public views for estimated underlying amounts and buffer delta tracking.
    • Vault helpers for asset→shares conversion and automatic high‑water‑mark updates.
  • Breaking Changes

    • Public signatures for buy/sell and fulfillDeposit/fulfillRedeem changed; explicit high‑water‑mark call removed.
    • Orchestrator responsibilities and access control moved to a different orchestrator component.
  • Tests

    • Updated to use new adapters, constructor signatures, unified order queries, and orchestrator flows.
  • Chores

    • Package version bumped to 0.4.4.

@sourcery-ai

sourcery-ai Bot commented Sep 15, 2025

Copy link
Copy Markdown

Reviewer's Guide

This PR implements the full execution pipeline by replacing stub logic and TODOs with concrete minibatch-based sell/buy flows in LiquidityOrchestrator, enhances InternalStatesOrchestrator to emit order arrays with estimated underlying amounts and process vault fees and requests, rewrites execution adapters and vault contracts to support slippage-aware buy/sell signatures, shifts state update responsibilities to the internal orchestrator, and updates all affected interfaces and tests to match the new APIs.

Sequence diagram for the new minibatch-based sell/buy execution in LiquidityOrchestrator

sequenceDiagram
  participant LO as LiquidityOrchestrator
  participant ISO as InternalStatesOrchestrator
  participant EA as ExecutionAdapter
  participant Vault as Vault

  LO->>ISO: getOrders()
  ISO-->>LO: sellingTokens, sellingAmounts, buyingTokens, buyingAmounts, sellingEstimatedUnderlyingAmounts, buyingEstimatedUnderlyingAmounts
  loop For each minibatch in sellingTokens
    LO->>EA: sell(token, sharesAmount, estimatedUnderlyingAmount)
    EA-->>LO: executionUnderlyingAmount
    LO->>LO: update deltaBufferAmount
  end
  loop For each minibatch in buyingTokens
    LO->>EA: buy(token, sharesAmount, estimatedUnderlyingAmount)
    EA-->>LO: executionUnderlyingAmount
    LO->>LO: update deltaBufferAmount
  end
  LO->>ISO: updateBufferAmount(deltaBufferAmount)
Loading

Class diagram for updated IExecutionAdapter and OrionAssetERC4626ExecutionAdapter

classDiagram
  class IExecutionAdapter {
    +buy(asset, sharesAmount, maxUnderlyingAmount) uint256
    +sell(asset, sharesAmount, minUnderlyingAmount) uint256
  }
  class OrionAssetERC4626ExecutionAdapter {
    +buy(asset, sharesAmount, maxUnderlyingAmount) uint256
    +sell(asset, sharesAmount, minUnderlyingAmount) uint256
    -underlyingAsset
    -liquidityOrchestrator
    -underlyingAssetToken
  }
  IExecutionAdapter <|.. OrionAssetERC4626ExecutionAdapter
Loading

Class diagram for updated InternalStatesOrchestrator and order API

classDiagram
  class InternalStatesOrchestrator {
    +getOrders() (sellingTokens, sellingAmounts, buyingTokens, buyingAmounts, sellingEstimatedUnderlyingAmounts, buyingEstimatedUnderlyingAmounts)
    +getPriceOf(token) uint256
    +updateBufferAmount(deltaAmount)
    -_countOrders(allTokens)
    -_populateOrders(...)
    -_currentEpoch
  }
Loading

Class diagram for updated OrionVault and vault state update logic

classDiagram
  class OrionVault {
    +convertToSharesWithPITTotalAssets(assets, pointInTimeTotalAssets, rounding) uint256
    +accrueCuratorFees(epoch, feeAmount)
    +fulfillDeposit(depositTotalAssets)
    +fulfillRedeem(redeemTotalAssets)
    -pendingCuratorFees
    -_depositRequests
    -_redeemRequests
  }
  class OrionTransparentVault {
    +updateVaultState(portfolio, newTotalAssets)
  }
  class OrionEncryptedVault {
    +updateVaultState(portfolio, newTotalAssets)
  }
  OrionVault <|-- OrionTransparentVault
  OrionVault <|-- OrionEncryptedVault
Loading

Class diagram for updated IOrionVault interface

classDiagram
  class IOrionVault {
    +convertToSharesWithPITTotalAssets(assets, pointInTimeTotalAssets, rounding) uint256
    +fulfillDeposit(depositTotalAssets)
    +fulfillRedeem(redeemTotalAssets)
    +accrueCuratorFees(epoch, feeAmount)
  }
Loading

Class diagram for updated IInternalStateOrchestrator interface

classDiagram
  class IInternalStateOrchestrator {
    +getOrders() (sellingTokens, sellingAmounts, buyingTokens, buyingAmounts, sellingEstimatedUnderlyingAmounts, buyingEstimatedUnderlyingAmounts)
    +getPriceOf(token) uint256
    +updateBufferAmount(deltaAmount)
  }
Loading

File-Level Changes

Change Details Files
LiquidityOrchestrator implements minibatch execution and buffer tracking
  • Add arrays for estimated underlying amounts and deltaBufferAmount state
  • Implement _processMinibatchSell and _processMinibatchBuy with phase transitions and skip of underlying asset
  • Update _executeSell/_executeBuy to accept slippage bounds, compute min/max amounts, call adapter, and adjust deltaBufferAmount
  • Revise epoch reset to call internalStatesOrchestrator.getOrders and conditional phase initialization
  • Simplify checkUpkeep/performUpkeep by removing placeholder state‐update branches
contracts/orchestrators/LiquidityOrchestrator.sol
InternalStatesOrchestrator provides full orders API and processes vault state changes
  • Replace getSellingOrders/getBuyingOrders with getOrders returning sell/buy tokens, amounts, and estimated underlying arrays
  • Introduce _countOrders and _populateOrders helpers to build nonzero order arrays
  • Invoke vault.accrueCuratorFees, fulfillRedeem, and fulfillDeposit for each vault during state processing
  • Add updateBufferAmount to adjust liquidity buffer based on execution vs estimate
  • Remove duplicated TODO stubs and unify phase transitions
contracts/orchestrators/InternalStatesOrchestrator.sol
Execution adapters updated for slippage‐aware buy/sell signatures
  • Change IExecutionAdapter buy/sell to accept sharesAmount, min/max underlying and return execution amount
  • OrionAssetERC4626ExecutionAdapter uses previewMint, safeTransferFrom, deposit/redeem, and throws on slippage
  • MockExecutionAdapter returns a fixed execution amount under new signature
contracts/interfaces/IExecutionAdapter.sol
contracts/execution/OrionAssetERC4626ExecutionAdapter.sol
contracts/mocks/MockExecutionAdapter.sol
Vault contracts refactored for internal state orchestrator control and PIT calculations
  • Change fulfillDeposit/fulfillRedeem to accept point-in-time totalAssets and use convertToSharesWithPITTotalAssets
  • Introduce convertToSharesWithPITTotalAssets helper
  • Move accrueCuratorFees to onlyInternalStatesOrchestrator and emit events
  • Update updateVaultState in encrypted/transparent vaults to onlyInternalStatesOrchestrator and recalc highWaterMark
contracts/vaults/OrionVault.sol
contracts/vaults/OrionEncryptedVault.sol
contracts/vaults/OrionTransparentVault.sol
Interfaces updated to match new orchestrator and adapter APIs
  • Extend IInternalStateOrchestrator.getOrders return values and add getPriceOf/updateBufferAmount
  • Update IOrionVault to include convertToSharesWithPITTotalAssets and new fulfill signatures
  • Remove obsolete ILiquidityOrchestrator StateUpdate phase
  • Align IExecutionAdapter signatures with slippage parameters
contracts/interfaces/IInternalStateOrchestrator.sol
contracts/interfaces/IOrionVault.sol
contracts/interfaces/ILiquidityOrchestrator.sol
contracts/interfaces/IExecutionAdapter.sol
Tests adapted to new order API, adapter instantiation, and phase workflow
  • Replace mock adapters with OrionAssetERC4626ExecutionAdapter deployments
  • Use getOrders instead of deprecated getters and validate estimated arrays
  • Drive LiquidityOrchestrator through multiple performUpkeep calls and assert phase transitions
  • Remove redundant decimals parameters in MockERC4626Asset constructors
test/Orchestrators.test.ts
test/EncryptedVault.test.ts
test/TransparentVault.test.ts
test/OrionConfigVault.test.ts
Minor bump and cleanup
  • Increment package.json version to 0.4.4
  • Remove numerous TODO comments and streamline imports
package.json

Possibly linked issues

  • chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates #123: The PR fixes the critical security vulnerability regarding deposit/redeem requests and implements the architectural changes for orchestrator responsibilities.
  • #0: The PR refactors orchestrator transaction processing and state updates, including slippage control, directly supporting gas optimization and cost measurement.

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 Sep 15, 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

Interfaces and implementations change buy/sell to share-based APIs with slippage and return values; orchestrators unify order retrieval, track estimated underlying and buffer deltas, and add a FulfillRedeem phase; vaults move control to InternalStatesOrchestrator, add PIT-based conversions and high-water mark updates; mocks, tests, and artifacts updated; package bumped.

Changes

Cohort / File(s) Summary
Execution adapters: share-based API with slippage
contracts/interfaces/IExecutionAdapter.sol, contracts/execution/OrionAssetERC4626ExecutionAdapter.sol, contracts/mocks/MockExecutionAdapter.sol, artifacts/contracts/execution/OrionAssetERC4626ExecutionAdapter.sol/*.json, artifacts/contracts/interfaces/IExecutionAdapter.sol/*.json, artifacts/contracts/mocks/MockExecutionAdapter.sol/*.json
buy/sell signatures now take sharesAmount and a max/min underlying param and return the executed underlying amount; adapter implements previewMint/deposit/redeem flows with SlippageExceeded checks; mocks and artifacts updated.
Orchestrators: unified orders, minibatches, buffer & phase
contracts/interfaces/IInternalStateOrchestrator.sol, contracts/interfaces/ILiquidityOrchestrator.sol, contracts/orchestrators/InternalStatesOrchestrator.sol, contracts/orchestrators/LiquidityOrchestrator.sol, artifacts/contracts/interfaces/IInternalStateOrchestrator.sol/*.json, artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/*.json
Replaced separate selling/buying getters with getOrders() returning six arrays; added getPriceOf, updateBufferAmount, getVaultTotalAssetsForFulfillRedeem; LiquidityOrchestrator tracks estimated underlying arrays and deltaBufferAmount, processes minibatches with slippage bounds, and adds a FulfillRedeem phase.
Vaults: PIT conversions, access-control, high-water mark & fee accrual relocation
contracts/interfaces/IOrionVault.sol, contracts/vaults/OrionVault.sol, contracts/vaults/OrionEncryptedVault.sol, contracts/vaults/OrionTransparentVault.sol, artifacts/contracts/interfaces/IOrionVault.sol/*.json, artifacts/contracts/interfaces/IOrionEncryptedVault.sol/*.json, artifacts/contracts/interfaces/IOrionTransparentVault.sol/*.json, artifacts/contracts/vaults/OrionVault.sol/*.json
Added convertToSharesWithPITTotalAssets; fulfillDeposit/fulfillRedeem now accept PIT totalAssets and are gated to onlyInternalStatesOrchestrator; moved accrueCuratorFees and vault-state update access to InternalStatesOrchestrator; vault update now maintains high-water mark; removed updateHighWaterMark.
Errors library
contracts/libraries/ErrorsLib.sol, artifacts/contracts/libraries/ErrorsLib.sol/*.json
Added SlippageExceeded() error; artifacts updated.
Mocks: ERC4626 decimals simplification
contracts/mocks/MockERC4626Asset.sol, artifacts/contracts/mocks/MockERC4626Asset.sol/*.json
Removed custom share decimals and decimals() override; constructor now 3 args; tests adjusted; artifacts updated.
Price adapters artifacts/formatting
contracts/price/OrionAssetERC4626PriceAdapter.sol, artifacts/contracts/price/OrionAssetERC4626PriceAdapter.sol/*.json, artifacts/contracts/price/PriceAdapterRegistry.sol/*.json
Minor formatting in source; artifact bytecode/deployedBytecode fields swapped/updated but ABI unchanged.
Config/Registry artifacts only
artifacts/contracts/OrionConfig.sol/*.json, artifacts/contracts/price/PriceAdapterRegistry.sol/*.json
Bytecode / deployedBytecode hex updates only; ABI unchanged.
Tests: align with new constructors/APIs and orchestrator
test/Orchestrators.test.ts, test/EncryptedVault.test.ts, test/TransparentVault.test.ts, test/OrionConfigVault.test.ts, test/OrionVaultExchangeRate.test.ts
Updated MockERC4626Asset deployments to use 3-arg constructor; replaced some mocks with Orion adapters; switched to getOrders(); tests use InternalStatesOrchestrator for fulfill flows; added slippage and multi-phase upkeep assertions.
Package metadata
package.json
Version bumped 0.4.3 → 0.4.4.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant ExecAdapter as OrionAssetERC4626ExecutionAdapter
  participant Vault as ERC4626 Vault
  participant Underlying as Underlying ERC20

  rect rgba(230,240,255,0.5)
  note right of ExecAdapter: Buy (shares-based, slippage guard)
  User->>ExecAdapter: buy(vault, sharesAmount, maxUnderlying)
  ExecAdapter->>Vault: previewMint(sharesAmount)
  Vault-->>ExecAdapter: estUnderlying
  ExecAdapter->>ExecAdapter: require(estUnderlying <= maxUnderlying) else SlippageExceeded
  ExecAdapter->>Underlying: transferFrom(User, ExecAdapter, estUnderlying)
  ExecAdapter->>Vault: deposit(estUnderlying, ExecAdapter)
  Vault-->>ExecAdapter: minted shares
  ExecAdapter->>User: transfer shares
  ExecAdapter-->>User: return spentUnderlying
  end

  rect rgba(255,240,230,0.5)
  note right of ExecAdapter: Sell (shares-based, slippage guard)
  User->>ExecAdapter: sell(vault, sharesAmount, minUnderlying)
  ExecAdapter->>Vault: previewMint(sharesAmount)
  Vault-->>ExecAdapter: estUnderlying
  ExecAdapter->>ExecAdapter: require(estUnderlying >= minUnderlying) else SlippageExceeded
  ExecAdapter->>Vault: redeem(sharesAmount, User, ExecAdapter)
  Vault-->>User: underlying
  ExecAdapter-->>User: return receivedUnderlying
  end
Loading
sequenceDiagram
  autonumber
  participant Keeper as Keeper
  participant LO as LiquidityOrchestrator
  participant ISO as InternalStatesOrchestrator
  participant Adapter as ExecutionAdapter
  participant Vault as OrionVault

  Keeper->>LO: performUpkeep()
  LO->>ISO: getOrders()
  ISO-->>LO: sellingTokens, sellingAmounts, buyingTokens, buyingAmounts, sellingEst, buyingEst
  alt SellingLeg
    loop minibatches
      LO->>Adapter: sell(token, sharesAmt, minUnderlying)
      Adapter-->>LO: executedUnderlying
      LO->>LO: deltaBuffer += (executed - est)
    end
    LO->>LO: switch to BuyingLeg
  else BuyingLeg
    loop minibatches
      LO->>Adapter: buy(token, sharesAmt, maxUnderlying)
      Adapter-->>LO: executedUnderlying
      LO->>LO: deltaBuffer += (est - executed)
    end
    LO->>ISO: updateBufferAmount(deltaBuffer)
  end
  alt FulfillRedeem
    LO->>ISO: getVaultTotalAssetsForFulfillRedeem(vault)
    LO->>Vault: fulfillRedeem(totalAssets)
  end
Loading

Estimated code review effort

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

Possibly related PRs

  • Develop #68 — Overlapping modifications to orchestrator and vault flows, likely closely related to these orchestrator/vault/interface changes.

Poem

A rabbit nibbles at the build,
Shares hop in where numbers filled.
Slippage guarded, buffers grow,
Vaults mark highs in steady flow.
Tests leap on — carrots in the code. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title Check ❓ Inconclusive The title "Dev" is a vague, non-descriptive label that does not communicate the primary changes in this PR (notably orchestrator and adapter interface updates, slippage handling, vault refactors, and test changes). Per the check rules, such a generic title is inconclusive because it fails to summarize the main change or intent for a reviewer scanning history. Ask the author to replace "Dev" with a concise, specific one-sentence title that highlights the main change, for example "Implement slippage-aware liquidity execution, buffer management, and updated adapters/interfaces" or "LiquidityOrchestrator: slippage execution flow and interface updates"; ensure the final title names the primary subsystem(s) affected and remains a single clear sentence.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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 - here's some feedback:

  • There’s a potential underflow in updateBufferAmount when deltaAmount is more negative than bufferAmount—consider bounding or safe‐checking before subtraction.
  • In OrionAssetERC4626ExecutionAdapter, you use IERC4626.previewMint for the sell operation but likely need previewRedeem to estimate underlying out of shares—please verify.
  • The loops for estimating underlying amounts and handling minibatches repeat a lot of logic; consider extracting common computations into private helpers for readability.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There’s a potential underflow in updateBufferAmount when deltaAmount is more negative than bufferAmount—consider bounding or safe‐checking before subtraction.
- In OrionAssetERC4626ExecutionAdapter, you use IERC4626.previewMint for the sell operation but likely need previewRedeem to estimate underlying out of shares—please verify.
- The loops for estimating underlying amounts and handling minibatches repeat a lot of logic; consider extracting common computations into private helpers for readability.

## Individual Comments

### Comment 1
<location> `contracts/orchestrators/LiquidityOrchestrator.sol:371-380` </location>
<code_context>
+        uint256 minUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 - slippageBound, 10000);
</code_context>

<issue_to_address>
**issue (bug_risk):** Slippage calculation uses fixed-point math; consider overflow/underflow risks.

Validation for slippageBound and estimatedUnderlyingAmount is recommended to prevent unexpected results from overflow or underflow.
</issue_to_address>

### Comment 2
<location> `contracts/orchestrators/LiquidityOrchestrator.sol:393-402` </location>
<code_context>
+        uint256 maxUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 + slippageBound, 10000);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Max underlying amount calculation could allow excessive slippage if slippageBound is high.

Enforcing an upper limit on slippageBound would help prevent unintended purchases at unfavorable prices.

Suggested implementation:

```
    function _executeBuy(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
        IExecutionAdapter adapter = executionAdapterOf[asset];
        if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();

        // Enforce upper limit on slippageBound (e.g., max 10% = 1000)
        uint256 MAX_SLIPPAGE_BOUND = 1000;
        if (slippageBound > MAX_SLIPPAGE_BOUND) revert ErrorsLib.ExcessiveSlippageBound();

        uint256 maxUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 + slippageBound, 10000);

        // Approve adapter to spend underlying assets with slippage tolerance
        // slither-disable-next-line unused-return
        IERC20(underlyingAsset).approve(address(adapter), 0);
        // slither-disable-next-line unused-return
        IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);

        // Execute buy through adapter, pull underlying assets from this contract and push shares to it.

```

1. You will need to define the `ErrorsLib.ExcessiveSlippageBound()` error in your `ErrorsLib` contract if it does not already exist.
2. If `slippageBound` is not a local or state variable, ensure it is passed as a parameter or available in scope.
3. Adjust `MAX_SLIPPAGE_BOUND` as appropriate for your protocol's risk tolerance.
</issue_to_address>

### Comment 3
<location> `contracts/vaults/OrionVault.sol:557-560` </location>
<code_context>

     /// @inheritdoc IOrionVault
-    function fulfillDeposit() external onlyLiquidityOrchestrator nonReentrant {
+    function fulfillDeposit(uint256 depositTotalAssets) external onlyInternalStatesOrchestrator nonReentrant {
         uint32 length = uint32(_depositRequests.length());
         // Collect all requests first to avoid index shifting issues when removing during iteration
</code_context>

<issue_to_address>
**issue (bug_risk):** Switching to convertToSharesWithPITTotalAssets may affect share calculation accuracy.

Verify that depositTotalAssets consistently represents the correct point-in-time value to prevent share misallocation.
</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/LiquidityOrchestrator.sol
Comment thread contracts/orchestrators/LiquidityOrchestrator.sol
Comment thread contracts/vaults/OrionVault.sol

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

Caution

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

⚠️ Outside diff range comments (5)
contracts/vaults/OrionVault.sol (2)

258-265: Fix ERC4626 conversion: asymmetric virtual offset breaks pricing

Using +1 in numerator and +10**_decimalsOffset() only in denominator distorts rates and can severely misprice shares. Use symmetric “virtual assets/shares” like OZ’s pattern.

-    return shares.mulDiv(pointInTimeTotalAssets + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
+    return shares.mulDiv(
+        pointInTimeTotalAssets + 10 ** _decimalsOffset(),
+        totalSupply() + 10 ** _decimalsOffset(),
+        rounding
+    );

279-296: Update tests & call sites: fulfillDeposit/fulfillRedeem signature/access control changed

fulfillDeposit/fulfillRedeem are now onlyInternalStatesOrchestrator and require a new parameter — update tests and any LiquidityOrchestrator call sites that call vault.fulfillDeposit() with no args.

Example failing call sites (tests): test/OrionVaultExchangeRate.test.ts:138, 162, 169, 193, 215, 248, 298, 340, 360, 395, 405, 415, 436, 461

contracts/orchestrators/InternalStatesOrchestrator.sol (3)

328-341: Critical: wrong keys cleared in epoch reset (stale totals leak across epochs).

Inside _handleStart you delete vaultsTotalAssets and encryptedVaultsTotalAssets using token keys, but those mappings are keyed by vault addresses. This leaves previous-epoch totals intact for vaults that aren’t re-populated (e.g., encrypted vaults with invalid intents), corrupting protocolTotalAssets, buffer math, and order deltas.

Apply this fix to clear by vault address before you overwrite the epoch vault arrays:

         for (uint16 i = 0; i < _currentEpoch.tokens.length; ++i) {
           address token = _currentEpoch.tokens[i];
           delete _currentEpoch.priceArray[token];
           delete _currentEpoch.initialBatchPortfolio[token];
-          delete _currentEpoch.vaultsTotalAssets[token];
           delete _currentEpoch.finalBatchPortfolio[token];
           delete _currentEpoch.sellingOrders[token];
           delete _currentEpoch.buyingOrders[token];
           delete _currentEpoch.tokenExists[token];
-          _currentEpoch.encryptedInitialBatchPortfolio[token] = _ezero;
-          _currentEpoch.encryptedVaultsTotalAssets[token] = _ezero;
-          _currentEpoch.encryptedFinalBatchPortfolio[token] = _ezero;
+          _currentEpoch.encryptedInitialBatchPortfolio[token] = _ezero;
+          _currentEpoch.encryptedFinalBatchPortfolio[token] = _ezero;
         }
+        // Clear last epoch totals by vault address (before reloading epoch vault arrays below).
+        for (uint16 i = 0; i < transparentVaultsEpoch.length; ++i) {
+            delete _currentEpoch.vaultsTotalAssets[transparentVaultsEpoch[i]];
+        }
+        for (uint16 i = 0; i < encryptedVaultsEpoch.length; ++i) {
+            delete _currentEpoch.vaultsTotalAssets[encryptedVaultsEpoch[i]];
+            _currentEpoch.encryptedVaultsTotalAssets[encryptedVaultsEpoch[i]] = _ezero;
+        }

702-735: Potential overflow/truncation: Position.value cast to uint32.

value can easily exceed 2^32−1 with ERC20 decimals and realistic TVLs. Truncation will corrupt portfolio and downstream order building.

Apply this change (and update the struct in IOrionTransparentVault accordingly):

- IOrionTransparentVault.Position[] memory portfolio = new IOrionTransparentVault.Position[](intentTokens.length);
+ IOrionTransparentVault.Position[] memory portfolio = new IOrionTransparentVault.Position[](intentTokens.length);

 ...
- portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
+ portfolio[j] = IOrionTransparentVault.Position({ token: token, value: value });

If the interface enforces uint32, switch to weights here and keep shares in-contract; otherwise upgrade Position.value to uint128/uint256.


401-429: Do not invert the math — registry.getPrice returns assets-per-share; update NatSpec (no code change).

Verified: PriceAdapterRegistry normalizes adapter.getPriceData and OrionAssetERC4626PriceAdapter.getPriceData returns underlyingAssetAmount for ONE share, so registry.getPrice(token) yields assets-per-share (scaled by priceAdapterPrecision); the existing arithmetic is correct.
Action: Change EpochState.priceArray NatSpec from "[shares/assets]" to "[assets/share]" in contracts/orchestrators/InternalStatesOrchestrator.sol and update any other misleading comments.

🧹 Nitpick comments (29)
contracts/libraries/ErrorsLib.sol (1)

65-66: New slippage error: good addition; consider richer context next.

SlippageExceeded() is fine now. For better UX/debuggability later, consider a variant that includes actual vs. bound amounts or the asset address.

artifacts/contracts/interfaces/IOrionEncryptedVault.sol/IOrionEncryptedVault.json (1)

558-586: Document rounding/units for convertToSharesWithPITTotalAssets.

Clarify:

  • What scale the price ratio uses (e.g., 1e18).
  • How Math.Rounding is applied vs convertToAssetsWithPITTotalAssets to guarantee inverse consistency.

Add regression tests for edge cases: 0 assets, 1 wei, and near-overflow totals.

contracts/interfaces/IExecutionAdapter.sol (2)

14-24: Good: buy now slippage-bounded and returns actual underlying spent.

Add explicit units in NatSpec (sharesAmount in asset share decimals; maxUnderlyingAmount in underlying decimals) and state MUST revert if execution > max.

 interface IExecutionAdapter {
+    /// @dev Revert convention: implementers SHOULD use ErrorsLib.SlippageExceeded()
     /// @notice Executes a buy operation by converting underlying assets to asset shares
-    /// @param sharesAmount The amount of shares to buy
-    /// @param maxUnderlyingAmount The maximum amount of underlying assets to spend
+    /// @param sharesAmount Amount of asset shares to buy (asset share decimals)
+    /// @param maxUnderlyingAmount Max underlying to spend (underlying decimals). MUST revert if exceeded.

25-35: Good: sell now slippage-bounded and returns actual underlying received.

Mirror docs as above; MUST revert if execution < minUnderlyingAmount.

-    /// @param minUnderlyingAmount The minimum amount of underlying assets to receive
+    /// @param minUnderlyingAmount Min underlying to receive (underlying decimals). MUST revert if not met.
contracts/interfaces/IInternalStateOrchestrator.sol (3)

71-88: getOrders returns six dynamic arrays — specify invariants and consider pagination.

  • Document that all arrays have equal length and aligned indices.
  • For large minibatches, consider chunked getters to avoid gas-heavy mem copies in on-chain calls.
     /// @notice Get selling and buying orders
+    /// @dev All returned arrays MUST have equal length; i-th entries across arrays refer to the same order.
+    ///      Consider paginated alternatives if minibatch size can grow.

90-94: Define price scale for getPriceOf.

Specify decimals/scale (e.g., 1e18) and quote convention (shares per asset or underlying per share) to prevent mismatched math.


95-99: updateBufferAmount access control is only documented — standardize revert.

Surface a standard error in the interface (e.g., UnauthorizedAccess) so implementers converge on a single revert reason; document units (underlying decimals).

 interface IInternalStateOrchestrator is AutomationCompatibleInterface {
+    /// @dev Standardized revert for unauthorized callers.
+    error UnauthorizedAccess();
 ...
-    /// @dev Can only be called by the Liquidity Orchestrator
+    /// @dev Can only be called by the Liquidity Orchestrator. Reverts UnauthorizedAccess() otherwise.
     function updateBufferAmount(int256 deltaAmount) external;
contracts/vaults/OrionTransparentVault.sol (1)

120-125: Guard high‑water‑mark update when totalSupply is zero.

Avoid setting an arbitrary HWM on empty supply; skip until shares exist.

-        // Update high watermark if current price is higher
-        uint256 currentSharePrice = convertToAssets(10 ** decimals());
-
-        if (currentSharePrice > feeModel.highWaterMark) {
-            feeModel.highWaterMark = currentSharePrice;
-        }
+        // Update high watermark only if shares exist, and if current price is higher
+        if (totalSupply() > 0) {
+            uint256 currentSharePrice = convertToAssets(10 ** decimals());
+            if (currentSharePrice > feeModel.highWaterMark) {
+                feeModel.highWaterMark = currentSharePrice;
+            }
+        }
artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (1)

519-530: Emit event on slippage bound changes.

Operationally useful and aids off‑chain monitoring/auditing.

-    function setSlippageBound(uint256 _slippageBound)
+    event SlippageBoundUpdated(uint256 oldValue, uint256 newValue);
+    function setSlippageBound(uint256 _slippageBound)
         external
     {
+        emit SlippageBoundUpdated(slippageBound, _slippageBound);
         slippageBound = _slippageBound;
     }
contracts/vaults/OrionEncryptedVault.sol (1)

186-192: Mirror HWM zero‑supply guard here as well.

Same rationale as TransparentVault.

-        // Update high watermark if current price is higher
-        uint256 currentSharePrice = convertToAssets(10 ** decimals());
-
-        if (currentSharePrice > feeModel.highWaterMark) {
-            feeModel.highWaterMark = currentSharePrice;
-        }
+        // Update high watermark only if shares exist, and if current price is higher
+        if (totalSupply() > 0) {
+            uint256 currentSharePrice = convertToAssets(10 ** decimals());
+            if (currentSharePrice > feeModel.highWaterMark) {
+                feeModel.highWaterMark = currentSharePrice;
+            }
+        }
artifacts/contracts/execution/OrionAssetERC4626ExecutionAdapter.sol/OrionAssetERC4626ExecutionAdapter.json (1)

73-89: Output name divergence vs interface — align for clarity.

Interface uses executionUnderlyingAmount; adapter uses spentUnderlyingAmount/receivedUnderlyingAmount. Types match so ABI is fine; consider harmonizing names to reduce confusion in typed clients.

Also applies to: 128-144

artifacts/contracts/interfaces/IOrionTransparentVault.sol/IOrionTransparentVault.json (1)

535-563: New convertToSharesWithPITTotalAssets — document rounding semantics for integrators.

Make sure docs specify expected rounding behaviors to avoid off‑by‑one issues in clients.

contracts/mocks/MockExecutionAdapter.sol (1)

13-19: LGTM; minor hygiene nits

Mock behavior is fine. To silence unused-param warnings in some toolchains, prefix params with underscore or use them in a no-op. Optional only.

-    function buy(
-        address asset,
-        uint256 sharesAmount,
-        uint256 maxUnderlyingAmount
-    ) external returns (uint256 executionUnderlyingAmount) {
+    function buy(
+        address /*asset*/,
+        uint256 /*sharesAmount*/,
+        uint256 /*maxUnderlyingAmount*/
+    ) external returns (uint256 executionUnderlyingAmount) {
         executionUnderlyingAmount = 1e12;
     }
...
-    function sell(
-        address asset,
-        uint256 sharesAmount,
-        uint256 minUnderlyingAmount
-    ) external returns (uint256 executionUnderlyingAmount) {
+    function sell(
+        address /*asset*/,
+        uint256 /*sharesAmount*/,
+        uint256 /*minUnderlyingAmount*/
+    ) external returns (uint256 executionUnderlyingAmount) {
         executionUnderlyingAmount = 1e12;
     }

Also applies to: 22-28

contracts/orchestrators/LiquidityOrchestrator.sol (7)

83-94: Public arrays expose mutable epoch state; consider events or getters

Publishing epoch arrays is convenient but increases surface area. Consider private storage + view getters or emitting events at start to reduce accidental external coupling.


256-259: performData length check is misleading

ABI-encoded (bytes4,uint8) is 64 bytes. The “< 5” guard is ineffective. Decode and rely on abi.decode revert, or check >= 64.

-        if (performData.length < 5) revert ErrorsLib.InvalidArguments();
+        // abi.decode will revert on malformed input; explicit length check not needed

287-299: Reset minibatch index at epoch start

currentMinibatchIndex should reset at _handleStart() to avoid stale indices from prior epochs.

         // Clear previous epoch data
         delete sellingTokens;
         delete sellingAmounts;
         delete buyingTokens;
         delete buyingAmounts;
         delete sellingEstimatedUnderlyingAmounts;
         delete buyingEstimatedUnderlyingAmounts;
         deltaBufferAmount = 0;
+        currentMinibatchIndex = 0;

Also applies to: 301-306


312-332: Minibatch range checks and index bump ordering

  • Use >= instead of (i1 > len || i1 == len).
  • Increment currentMinibatchIndex after successful processing to better reflect progress.
-        ++currentMinibatchIndex;
-        uint16 i0 = minibatchIndex * executionMinibatchSize;
+        uint16 i0 = minibatchIndex * executionMinibatchSize;
         uint16 i1 = i0 + executionMinibatchSize;
-        if (i1 > sellingTokens.length || i1 == sellingTokens.length) {
+        if (i1 >= sellingTokens.length) {
             i1 = uint16(sellingTokens.length);
             currentPhase = LiquidityUpkeepPhase.BuyingLeg;
             currentMinibatchIndex = 0;
-        }
+        } else {
+            ++currentMinibatchIndex;
+        }

337-361: Mirror the sell-path fixes for buy

Apply the same >= check and post-processing index bump, and keep updateBufferAmount call gated to final minibatch (already correct).

-        ++currentMinibatchIndex;
         uint16 i0 = minibatchIndex * executionMinibatchSize;
         uint16 i1 = i0 + executionMinibatchSize;
-        if (i1 > buyingTokens.length || i1 == buyingTokens.length) {
+        if (i1 >= buyingTokens.length) {
             i1 = uint16(buyingTokens.length);
             currentPhase = LiquidityUpkeepPhase.Idle;
             currentMinibatchIndex = 0;
-        }
+        } else {
+            ++currentMinibatchIndex;
+        }

371-383: Use SafeERC20 for non‑standard tokens

Approve/transfer patterns on some tokens (e.g., missing return booleans) can misbehave. Recommend SafeERC20 for approves/transfers here.

-        IERC20(asset).approve(address(adapter), 0);
-        IERC20(asset).approve(address(adapter), sharesAmount);
+        SafeERC20.safeApprove(IERC20(asset), address(adapter), 0);
+        SafeERC20.safeApprove(IERC20(asset), address(adapter), sharesAmount);
...
-        IERC20(underlyingAsset).approve(address(adapter), 0);
-        IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);
+        SafeERC20.safeApprove(IERC20(underlyingAsset), address(adapter), 0);
+        SafeERC20.safeApprove(IERC20(underlyingAsset), address(adapter), maxUnderlyingAmount);

Remember to import SafeERC20 and use SafeERC20 for transfers too.

Also applies to: 393-405


67-69: Dead/unused field: targetBufferRatio

targetBufferRatio is set but never used. Remove or wire into buffer logic.

-    uint256 public targetBufferRatio;
+    // uint256 public targetBufferRatio; // unused
contracts/vaults/OrionVault.sol (2)

457-461: Performance fee share price uses asymmetric +1

Use symmetric virtual offset to avoid under/overcharging near zero TVL or supply.

-        uint256 activeSharePrice = (10 ** decimals()).mulDiv(
-            feeTotalAssets + 1,
-            totalSupply() + 10 ** _decimalsOffset(),
-            Math.Rounding.Floor
-        );
+        uint256 activeSharePrice = (10 ** decimals()).mulDiv(
+            feeTotalAssets + 10 ** _decimalsOffset(),
+            totalSupply() + 10 ** _decimalsOffset(),
+            Math.Rounding.Floor
+        );

286-288: Consider SafeERC20 for transfers

For robustness with non‑standard ERC‑20s, prefer SafeERC20 for transfer/transferFrom.

Also applies to: 364-367

test/Orchestrators.test.ts (2)

476-482: getOrders: consider asserting new return tails too.

Contract now returns 6 arrays; you destructure 4. Optionally also assert on sellingEstimatedUnderlyingAmounts/buyingEstimatedUnderlyingAmounts lengths to guard the new API.

-const [sellingTokens, _sellingAmounts, buyingTokens, _buyingAmounts] = await internalStatesOrchestrator.getOrders();
+const [
+  sellingTokens,
+  _sellingAmounts,
+  buyingTokens,
+  _buyingAmounts,
+  _sellingUnderlyingEst,
+  _buyingUnderlyingEst,
+] = await internalStatesOrchestrator.getOrders();
+expect(_sellingUnderlyingEst.length).to.equal(sellingTokens.length);
+expect(_buyingUnderlyingEst.length).to.equal(buyingTokens.length);

537-565: Liquidity orchestrator multi-step flow: OK, but consider tightening assertions.

You already assert phase transitions; optionally add an epoch/targetBufferRatio validation after the last step for extra coverage.

contracts/orchestrators/InternalStatesOrchestrator.sol (4)

489-499: Encrypted math TODO: track upstream and guard for precision.

The OZ confidential contracts issue is still open; consider bounding intermediate values to avoid overflow when priceAdapterPrecision and decimals differ widely.


570-606: Encrypted vaults: same PIT + price semantics concerns as transparent path.

Mirror the verification and any fix applied to the transparent path to keep both branches consistent.


903-907: getPriceOf: consider fallback behavior.

Returning zero for unseen tokens may confuse callers; optionally revert on unset price or compute on-demand.


914-921: updateBufferAmount: guard negative deltas to avoid underflow panic.

Current subtraction relies on checked arithmetic and will revert with a generic panic if |delta| > bufferAmount. Emit a domain-specific error instead.

 function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
     if (deltaAmount > 0) {
         bufferAmount += uint256(deltaAmount);
     } else if (deltaAmount < 0) {
-        bufferAmount -= uint256(-deltaAmount);
+        uint256 abs = uint256(-deltaAmount);
+        if (abs > bufferAmount) revert ErrorsLib.InsufficientAmount();
+        bufferAmount -= abs;
     }
 }
contracts/execution/OrionAssetERC4626ExecutionAdapter.sol (1)

49-55: Failing TXs TODO: capture per-asset failures and propagate deltas.

Agree with the TODO. Consider returning a bitmap and per-asset deltas so the LO can reconcile partial executions against ISO totals without blocking epochs.

I can sketch an error-reporting interface and the LO reconciliation flow if helpful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5251cb5 and 1d5f5ba.

📒 Files selected for processing (33)
  • artifacts/contracts/OrionConfig.sol/OrionConfig.json (1 hunks)
  • artifacts/contracts/execution/OrionAssetERC4626ExecutionAdapter.sol/OrionAssetERC4626ExecutionAdapter.json (4 hunks)
  • artifacts/contracts/interfaces/IExecutionAdapter.sol/IExecutionAdapter.json (2 hunks)
  • artifacts/contracts/interfaces/IInternalStateOrchestrator.sol/IInternalStateOrchestrator.json (2 hunks)
  • artifacts/contracts/interfaces/IOrionEncryptedVault.sol/IOrionEncryptedVault.json (2 hunks)
  • artifacts/contracts/interfaces/IOrionTransparentVault.sol/IOrionTransparentVault.json (2 hunks)
  • artifacts/contracts/interfaces/IOrionVault.sol/IOrionVault.json (2 hunks)
  • artifacts/contracts/libraries/ErrorsLib.sol/ErrorsLib.json (2 hunks)
  • artifacts/contracts/mocks/MockERC4626Asset.sol/MockERC4626Asset.json (1 hunks)
  • artifacts/contracts/mocks/MockExecutionAdapter.sol/MockExecutionAdapter.json (2 hunks)
  • artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (6 hunks)
  • artifacts/contracts/price/OrionAssetERC4626PriceAdapter.sol/OrionAssetERC4626PriceAdapter.json (1 hunks)
  • artifacts/contracts/price/PriceAdapterRegistry.sol/PriceAdapterRegistry.json (1 hunks)
  • artifacts/contracts/vaults/OrionVault.sol/OrionVault.json (2 hunks)
  • contracts/execution/OrionAssetERC4626ExecutionAdapter.sol (1 hunks)
  • contracts/interfaces/IExecutionAdapter.sol (1 hunks)
  • contracts/interfaces/IInternalStateOrchestrator.sol (1 hunks)
  • contracts/interfaces/ILiquidityOrchestrator.sol (1 hunks)
  • contracts/interfaces/IOrionVault.sol (2 hunks)
  • contracts/libraries/ErrorsLib.sol (1 hunks)
  • contracts/mocks/MockERC4626Asset.sol (1 hunks)
  • contracts/mocks/MockExecutionAdapter.sol (1 hunks)
  • contracts/orchestrators/InternalStatesOrchestrator.sol (8 hunks)
  • contracts/orchestrators/LiquidityOrchestrator.sol (5 hunks)
  • contracts/price/OrionAssetERC4626PriceAdapter.sol (0 hunks)
  • contracts/vaults/OrionEncryptedVault.sol (2 hunks)
  • contracts/vaults/OrionTransparentVault.sol (2 hunks)
  • contracts/vaults/OrionVault.sol (5 hunks)
  • package.json (1 hunks)
  • test/EncryptedVault.test.ts (0 hunks)
  • test/Orchestrators.test.ts (7 hunks)
  • test/OrionConfigVault.test.ts (0 hunks)
  • test/TransparentVault.test.ts (0 hunks)
💤 Files with no reviewable changes (4)
  • contracts/price/OrionAssetERC4626PriceAdapter.sol
  • test/OrionConfigVault.test.ts
  • test/TransparentVault.test.ts
  • test/EncryptedVault.test.ts
🧰 Additional context used
🧬 Code graph analysis (8)
contracts/interfaces/IOrionVault.sol (1)
test/OrionVaultExchangeRate.test.ts (5)
  • it (428-474)
  • impersonateLiquidityOrchestrator (5-475)
  • it (124-181)
  • it (381-426)
  • it (183-235)
contracts/vaults/OrionTransparentVault.sol (2)
test/OrionVaultExchangeRate.test.ts (4)
  • impersonateLiquidityOrchestrator (5-475)
  • it (428-474)
  • it (381-426)
  • it (124-181)
test/EncryptedVault.test.ts (1)
  • newTotalAssets (434-447)
artifacts/contracts/mocks/MockERC4626Asset.sol/MockERC4626Asset.json (2)
test/EncryptedVault.test.ts (1)
  • MockERC4626AssetFactory (315-342)
test/TransparentVault.test.ts (1)
  • MockERC4626AssetFactory (261-283)
artifacts/contracts/interfaces/IOrionVault.sol/IOrionVault.json (1)
test/OrionVaultExchangeRate.test.ts (9)
  • it (428-474)
  • it (124-181)
  • it (183-235)
  • it (331-379)
  • impersonateLiquidityOrchestrator (5-475)
  • loadFixture (125-150)
  • it (381-426)
  • it (237-329)
  • loadFixture (332-349)
artifacts/contracts/interfaces/IOrionEncryptedVault.sol/IOrionEncryptedVault.json (3)
test/OrionVaultExchangeRate.test.ts (7)
  • it (428-474)
  • it (124-181)
  • it (183-235)
  • impersonateLiquidityOrchestrator (5-475)
  • it (331-379)
  • it (237-329)
  • it (381-426)
test/EncryptedVault.test.ts (1)
  • newTotalAssets (434-447)
test/OrionConfigVault.test.ts (1)
  • it (594-615)
contracts/mocks/MockERC4626Asset.sol (2)
test/EncryptedVault.test.ts (1)
  • MockERC4626AssetFactory (315-342)
test/TransparentVault.test.ts (1)
  • MockERC4626AssetFactory (261-283)
contracts/orchestrators/InternalStatesOrchestrator.sol (2)
test/OrionVaultExchangeRate.test.ts (6)
  • impersonateLiquidityOrchestrator (5-475)
  • it (428-474)
  • it (381-426)
  • deployVaultFixture (14-95)
  • it (124-181)
  • loadFixture (125-150)
test/OrionConfigVault.test.ts (1)
  • describe (429-679)
contracts/vaults/OrionEncryptedVault.sol (2)
test/EncryptedVault.test.ts (1)
  • newTotalAssets (434-447)
test/OrionVaultExchangeRate.test.ts (2)
  • impersonateLiquidityOrchestrator (5-475)
  • it (428-474)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Build, Lint and Test
🔇 Additional comments (35)
package.json (1)

4-4: Version bump looks good; remember to tag and publish.

Ensure a 0.4.4 release tag and changelog entry accompany this, so downstreams can pin artifacts reliably.

artifacts/contracts/price/OrionAssetERC4626PriceAdapter.sol/OrionAssetERC4626PriceAdapter.json (1)

91-92: Sanity‑check creation vs runtime bytecode.

The artifact flips changed both fields. Just double‑check they are not swapped to avoid deployment/runtime mismatches.

artifacts/contracts/interfaces/IOrionVault.sol/IOrionVault.json (2)

535-563: Add boundary/rounding tests for PIT conversion API

  • Test convertToSharesWithPITTotalAssets(assets, pointInTimeTotalAssets, rounding) and convertToAssetsWithPITTotalAssets(...): rounding edges (0, 1 wei, max values), discontinuities, and monotonicity between the two conversions (both Floor and Ceil).
  • Call-sites/interfaces: contracts/interfaces/IOrionVault.sol; contracts/vaults/OrionVault.sol — convertToShares at line ~560, convertToAssets at line ~590; contracts/orchestrators/InternalStatesOrchestrator.sol — calls at lines ~416 and ~592.
  • Repo search shows no tests referencing these functions — add unit tests (test/**/*.ts) covering rounding modes, boundary values, and conversion consistency.

647-658: fulfillRedeem signature changed — callers migrated; validate accounting

  • Definition/interface: contracts/vaults/OrionVault.sol:568, contracts/interfaces/IOrionVault.sol:189.
  • Call sites updated: contracts/orchestrators/InternalStatesOrchestrator.sol:421 and 597.
  • No zero‑arg invocations found.
  • Action: Manually verify accounting under slippage/fee paths and that deposit/redeem symmetry (fee paths) is correct.
contracts/mocks/MockERC4626Asset.sol (1)

8-13: Mock constructor simplification: OK — tests expect 18 share decimals.
Tests deploy MockERC4626Asset without a decimals arg and the vault decimals test asserts 18 (test/OrionConfigVault.test.ts:473–477); no tests assume shares mirror the underlying token's decimals.

artifacts/contracts/OrionConfig.sol/OrionConfig.json (1)

586-588: ABI added/changed in artifacts — confirm no ABI/interface drift.
artifacts/contracts/OrionConfig.sol/OrionConfig.json: origin/main had no ABI at .abi; this PR file contains a full ABI (new ABI added). Confirm this is intentional and that the contract interface hasn't changed; if not intended, revert or regenerate artifacts.

artifacts/contracts/mocks/MockERC4626Asset.sol/MockERC4626Asset.json (1)

821-823: Constructor change — update deployments to 3 args

MockERC4626Asset dropped the decimals constructor parameter; update all tests/scripts/factories that deploy it to pass 3 constructor args (not 4). Automated searches in this environment failed to locate usages — run a repo-wide search and update any remaining deploy calls.

artifacts/contracts/mocks/MockExecutionAdapter.sol/MockExecutionAdapter.json (3)

20-36: ABI aligned with shares-based buy; ensure return value is used.

buy(...) now returns executionUnderlyingAmount. Verify orchestrators/tests read and propagate this return for buffer/slippage accounting.


49-65: ABI aligned with shares-based sell; ensure return value is used.

sell(...) now returns executionUnderlyingAmount. Verify all call sites.


70-71: Bytecode changed without ABI change — confirm expected rebuild.
ABI unchanged: buy(address,uint256,uint256), sell(address,uint256,uint256).
Artifact (artifacts/contracts/mocks/MockExecutionAdapter.sol/MockExecutionAdapter.json) has no compiler/metadata; bytecode len=468 (sha256=6d3fce9f48c02eebbb7ed0faf1f36061a2063435d30cc26ea6906c9262d4d70d), deployedBytecode len=416 (sha256=51d97ad033d1ab79685b9c81c691f176acc5980a76bec6c87cb008ef90d9a737).
Confirm this recompile is intentional and limited to build settings (compiler/version/optimizer); if yes, include compiler+metadata or document the build config; if not, revert or rebuild with the original settings.

artifacts/contracts/price/PriceAdapterRegistry.sol/PriceAdapterRegistry.json (1)

226-227: Bytecode-only change — ABI unchanged; confirm compiler/optimizer/metadata

ABI from the artifact matches the existing external surface (adapterOf, configAddress, getPrice, owner, priceAdapterDecimals, renounceOwnership, setPriceAdapter, transferOwnership, unsetPriceAdapter).

  • Verify compiler version, optimizer settings and build metadata (hardhat/foundry/solc config and CI) to confirm the bytecode diff is non-functional and to restore reproducible builds if needed.
  • If logic did change, add/extend tests for owner/set/unset flows.
artifacts/contracts/interfaces/IOrionEncryptedVault.sol/IOrionEncryptedVault.json (1)

670-681: No action required — fulfillRedeem callers already updated.
contracts/orchestrators/InternalStatesOrchestrator.sol:421,597 call vault.fulfillRedeem(totalAssets); contracts/vaults/OrionVault.sol:568 and contracts/interfaces/IOrionVault.sol:189 declare fulfillRedeem(uint256 redeemTotalAssets).

artifacts/contracts/vaults/OrionVault.sol/OrionVault.json (2)

837-865: convertToSharesWithPITTotalAssets added — ensure symmetry with convertToAssetsWithPITTotalAssets

Confirm both functions use identical rounding policies (same Math.Rounding parameter handling and rounding direction) to preserve invertibility; inspect contracts/vaults/OrionVault.sol (functions near lines ~259 and ~268) and align if they differ.


977-988: fulfillDeposit/fulfillRedeem — confirm orchestrator wiring

InternalStatesOrchestrator forwards the local variable totalAssets to the vault calls (vault.fulfillRedeem(totalAssets) at contracts/orchestrators/InternalStatesOrchestrator.sol:421 & 597; vault.fulfillDeposit(totalAssets) at …:426 & 602). Order: fulfillRedeem is invoked before totalAssets -= pendingRedeem; fulfillDeposit is invoked after that subtraction but before totalAssets += pendingDeposit; the epoch value _currentEpoch.vaultsTotalAssets[address(vault)] is set after adding pendingDeposit. Confirm OrionVault.fulfillRedeem(uint256) and fulfillDeposit(uint256 depositTotalAssets) expect the exact pre/post-adjusted totals forwarded here — if their semantics differ, adjust the orchestrator call-site or vault logic to avoid epoch-state drift. See artifacts/contracts/vaults/OrionVault.sol/OrionVault.json (fulfillDeposit ABI around lines 977–988 and 990–1001).

artifacts/contracts/libraries/ErrorsLib.sol/ErrorsLib.json (1)

67-71: SlippageExceeded added — ABI updated; confirm uniform usage across adapters/orchestrators.

ErrorsLib defines SlippageExceeded (contracts/libraries/ErrorsLib.sol:66). Revert occurrences found at contracts/execution/OrionAssetERC4626ExecutionAdapter.sol:74 and :100; artifacts include the ABI entry. Confirm other adapters/orchestrators that revert on slippage reference ErrorsLib.SlippageExceeded.

contracts/vaults/OrionEncryptedVault.sol (1)

169-169: Confirmed — access restricted to InternalStatesOrchestrator and tests/orchestrator call sites present.

InternalStatesOrchestrator invokes updateVaultState (contracts/orchestrators/InternalStatesOrchestrator.sol:734). Tests call updateVaultState via impersonated liquidity orchestrator (test/OrionVaultExchangeRate.test.ts) and EncryptedVault.test asserts non-orchestrator calls revert (test/EncryptedVault.test.ts:442). Transparent/Encrypted vaults both use onlyInternalStatesOrchestrator (contracts/vaults/OrionTransparentVault.sol:109; contracts/vaults/OrionEncryptedVault.sol:169).

artifacts/contracts/interfaces/IExecutionAdapter.sol/IExecutionAdapter.json (1)

15-31: Breaking signature changes for buy/sell — callers updated and return value consumed.
Interface and adapters use the new 3-arg signature and LiquidityOrchestrator assigns the returned executionUnderlyingAmount.
Locations: contracts/interfaces/IExecutionAdapter.sol; contracts/orchestrators/LiquidityOrchestrator.sol:380,402; contracts/execution/OrionAssetERC4626ExecutionAdapter.sol:57,83; contracts/mocks/MockExecutionAdapter.sol:13,22

artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (1)

194-212: Index-aligned arrays — verified; no additional runtime checks required.
InternalStatesOrchestrator.getOrders() allocates selling/buying/estimated arrays with matching sizes and _populateOrders fills the estimated arrays using the same indices; LiquidityOrchestrator assigns those arrays atomically and iterates using token array lengths, so index reads are safe.
Locations: contracts/orchestrators/InternalStatesOrchestrator.sol (~819–824, 881–896); contracts/orchestrators/LiquidityOrchestrator.sol (~283–297, 318–355).

contracts/orchestrators/LiquidityOrchestrator.sol (1)

237-247: checkUpkeep action selection: OK

Branching covers start/sell/buy phases cleanly.

contracts/vaults/OrionVault.sol (2)

133-136: Access control shift: good

onlyInternalStatesOrchestrator modifier is correct for the new flow.


560-564: Incorrect — fulfillDeposit uses the vault's point-in-time totalAssets (PIT) as the denominator

InternalStatesOrchestrator computes the vault's point-in-time totalAssets and calls vault.fulfillDeposit(totalAssets); fulfillDeposit forwards that value into convertToSharesWithPITTotalAssets(..., depositTotalAssets, ...), so shares are minted against the PIT totalAssets, not the sum of deposits. See contracts/orchestrators/InternalStatesOrchestrator.sol (STEP 6 calls at ~lines 423–427 and 599–603) and contracts/vaults/OrionVault.sol (convertToSharesWithPITTotalAssets ~268–271; fulfillDeposit ~538–563).

Likely an incorrect or invalid review comment.

contracts/interfaces/IOrionVault.sol (2)

96-106: Rounding enum mapping verified — no action required

Interfaces declare Math.Rounding and implementations/call sites pass Math.Rounding.* (e.g., contracts/interfaces/IOrionVault.sol; contracts/vaults/OrionVault.sol; contracts/orchestrators/InternalStatesOrchestrator.sol); the compiled ABI encodes the enum as uint8 (expected). No additional mapping or change needed.


183-189: ```shell
#!/bin/bash
set -euo pipefail

echo "Listing tracked .sol files (git ls-files):"
git ls-files '*.sol' || true
echo

echo "Searching for fulfillDeposit occurrences:"
rg -nP "\bfulfillDeposit\s*(" -C3 --hidden --no-ignore-vcs || true
echo

echo "Searching for fulfillRedeem occurrences:"
rg -nP "\bfulfillRedeem\s*(" -C3 --hidden --no-ignore-vcs || true
echo

echo "Searching for PIT / totalAssets / InternalStatesOrchestrator / param names:"
rg -nP "InternalStatesOrchestrator|pointInTimeTotalAssets|depositTotalAssets|redeemTotalAssets|pointInTime|PIT|totalAssets" -C3 --hidden --no-ignore-vcs || true
echo

echo "Locate IOrionVault interface file(s) and print lines ~160-200:"
FILES=$(rg --files-with-matches -n "interface\s+IOrionVault" || true)
if [ -n "$FILES" ]; then
for f in $FILES; do
echo "---- $f ----"
sed -n '1,260p' "$f" | sed -n '160,200p' || true
done
else
echo "No IOrionVault interface file found by rg."
fi


</blockquote></details>
<details>
<summary>artifacts/contracts/interfaces/IInternalStateOrchestrator.sol/IInternalStateOrchestrator.json (2)</summary><blockquote>

`94-125`: **Incorrect — getOrders already enforces matching array lengths.**
getOrders computes selling/buying counts with _countOrders, allocates arrays of those exact sizes, and populates them with _populateOrders using the same criteria; mismatched lengths cannot occur. See contracts/orchestrators/InternalStatesOrchestrator.sol (getOrders/_countOrders/_populateOrders). 

> Likely an incorrect or invalid review comment.

---

`201-213`: **Signed delta OK — checked arithmetic and access control verified**

updateBufferAmount(int256) (contracts/orchestrators/InternalStatesOrchestrator.sol:915–920) converts the signed delta to uint and applies +=/− (Solidity checked arithmetic will revert on overflow/underflow); onlyLiquidityOrchestrator (contracts/orchestrators/InternalStatesOrchestrator.sol:158–161) restricts callers to the LiquidityOrchestrator. No changes required.

</blockquote></details>
<details>
<summary>test/Orchestrators.test.ts (6)</summary><blockquote>

`31-34`: **Adapter vars wiring looks correct.**

Declaring distinct Orion price/execution adapters per asset aligns with the new API surface.

---

`108-111`: **Third price adapter deployment LGTM.**

Consistent with prior two instances and the config address wiring.

---

`162-179`: **Execution adapters deployment LGTM.**

Factory + config injection are correct and consistent across 3 instances.

---

`185-199`: **Whitelist entries wired to Orion adapters — good.**

Each asset whitelisted with its corresponding Orion price and execution adapter.

---

`345-347`: **Idle-guard coverage for addWhitelistedAsset is good.**

Revert expectation matches SystemNotIdle gating.

---

`525-535`: **Amount sanity checks are fine.**

Loops correctly no-op when arrays are empty; > 0 guards are appropriate.

</blockquote></details>
<details>
<summary>contracts/orchestrators/InternalStatesOrchestrator.sol (4)</summary><blockquote>

`413-428`: **Order of deposit/redeem accounting: double-check PIT usage.**

You pass totalAssets (post-fee, pre-withdraw/deposit) into fulfillRedeem/fulfillDeposit and then adjust totalAssets locally. If vault math assumes PIT is pre-withdraw/pre-deposit, this is OK; otherwise, exchange-rate drift can occur.

If vault expects different PIT points, swap the arithmetic:
- subtract pendingRedeem before calling fulfillRedeem if PIT should reflect post-withdrawals.
- add pendingDeposit before calling fulfillDeposit if PIT should reflect post-deposits.

---

`803-836`: **getOrders: good zero-elision and estimated-underlying outputs.**

API and helpers look clean and efficient.

---

`839-853`: **_countOrders: OK.**

Linear scan bounded by small token set; fine.

---

`856-901`: **_populateOrders: price usage consistent with getOrders semantics.**

Estimates derived via mulDiv(price, precision) match “assets per share” interpretation; see earlier note on price units.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread artifacts/contracts/interfaces/IOrionVault.sol/IOrionVault.json
Comment thread contracts/execution/OrionAssetERC4626ExecutionAdapter.sol
Comment thread contracts/execution/OrionAssetERC4626ExecutionAdapter.sol
Comment thread contracts/interfaces/ILiquidityOrchestrator.sol
Comment thread contracts/vaults/OrionTransparentVault.sol
Comment thread contracts/vaults/OrionVault.sol
Comment thread contracts/vaults/OrionVault.sol
@codecov

codecov Bot commented Sep 16, 2025

Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (4)
contracts/orchestrators/InternalStatesOrchestrator.sol (4)

330-336: Bug: clearing vaultsTotalAssetsForFulfillRedeem by token key leaves stale per‑vault state

You delete vaultsTotalAssetsForFulfillRedeem using token addresses, but the mapping is keyed by vault addresses. This leaves previous-epoch values uncleared and can leak stale exchange-rate inputs across epochs.

Apply this diff to delete by prior epoch vault addresses and remove the wrong delete:

 for (uint16 i = 0; i < _currentEpoch.tokens.length; ++i) {
   address token = _currentEpoch.tokens[i];
   delete _currentEpoch.priceArray[token];
   delete _currentEpoch.initialBatchPortfolio[token];
   delete _currentEpoch.vaultsTotalAssets[token];
-  delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[token];
   delete _currentEpoch.finalBatchPortfolio[token];
   delete _currentEpoch.sellingOrders[token];
   delete _currentEpoch.buyingOrders[token];
   delete _currentEpoch.tokenExists[token];
   _currentEpoch.encryptedInitialBatchPortfolio[token] = _ezero;
   _currentEpoch.encryptedVaultsTotalAssets[token] = _ezero;
   _currentEpoch.encryptedFinalBatchPortfolio[token] = _ezero;
 }
+// Clear fulfillRedeem totals for previous epoch vaults
+for (uint16 i = 0; i < transparentVaultsEpoch.length; ++i) {
+  delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[transparentVaultsEpoch[i]];
+}
+for (uint16 i = 0; i < encryptedVaultsEpoch.length; ++i) {
+  delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[encryptedVaultsEpoch[i]];
+}

571-605: Encrypted path never fulfills redeem — withdrawals not applied

You compute pendingRedeem and set PIT totals but never call fulfillRedeem. This leaves redemption shares unburned and exchange-rate state inconsistent with the transparent path.

 uint256 pendingRedeem = vault.convertToAssetsWithPITTotalAssets(
   vault.pendingRedeem(),
   totalAssets,
   Math.Rounding.Floor
 );
 _currentEpoch.vaultsTotalAssetsForFulfillRedeem[address(vault)] = totalAssets;
+vault.fulfillRedeem(totalAssets);
 
 // STEP 6: DEPOSIT PROCESSING (add deposits, subtract withdrawals)
 totalAssets -= pendingRedeem;

653-675: Division-by-zero risk in _buffer when TVL=0 and delta=0

When protocolTotalAssets==0 and deltaBufferAmount==0, mulDiv(0, x, 0) will revert. Short‑circuit before proportional allocation.

 uint256 targetBufferAmount = protocolTotalAssets.mulDiv(
   liquidityOrchestrator.targetBufferRatio(),
   BASIS_POINTS_FACTOR
 );
 // Only increase buffer if current buffer is below target (conservative approach)
 if (bufferAmount > targetBufferAmount) return;
 
 uint256 deltaBufferAmount = targetBufferAmount - bufferAmount;
+if (protocolTotalAssets == 0 || deltaBufferAmount == 0) {
+  return;
+}

701-729: uint32 truncation in Position.value

Casting value to uint32 can silently truncate for larger vaults/tokens. Add a bound check (or widen the Position type if possible).

-portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
+if (value > type(uint32).max) revert ErrorsLib.InvalidArguments();
+portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
♻️ Duplicate comments (1)
contracts/orchestrators/LiquidityOrchestrator.sol (1)

156-163: Slippage cap added — confirm 20% is acceptable.

Cap of 2000bps is implemented; prior feedback suggested enforcing an upper limit. If your risk policy prefers 10% max, lower this to 1000bps.

🧹 Nitpick comments (22)
contracts/interfaces/IInternalStateOrchestrator.sol (4)

71-88: Fix units in docs for buyingAmounts (shares vs underlying).

Code elsewhere treats buyingAmounts as shares (and returns separate estimated-underlying arrays). Update the NatSpec to “amounts to buy in shares” to avoid confusion.

Apply this doc-only diff:

-    /// @return buyingAmounts The amounts to buy in underlying assets
+    /// @return buyingAmounts The amounts to buy in shares

90-94: Clarify price units/scale returned by getPriceOf.

Specify whether this is assets-per-share or shares-per-asset and the fixed‑point scale (e.g., 1e18). Ambiguity here propagates to adapters/orchestrators.

Proposed doc tweak:

-    /// @return price The corresponding price [shares/assets]
+    /// @return price Assets per 1 share, scaled by 1e18 (assets/share, 18 decimals)

95-99: Enforce caller restriction for updateBufferAmount in implementation.

NatSpec says “only Liquidity Orchestrator” but the interface can’t enforce it. Ensure the implementation has an explicit access control check (e.g., onlyLiquidityOrchestrator using the address from config).


100-103: Name/semantics of getVaultTotalAssetsForFulfillRedeem.

Make clear in docs this returns underlying-asset units (and scale) captured for the current epoch’s fulfillRedeem, not live totals. Helps avoid misuse.

artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (1)

654-656: Note on artifacts in VCS.

If build artifacts are not required for consumption, consider excluding them to reduce churn. If they are required, no action needed.

test/Orchestrators.test.ts (5)

177-191: Whitelist wiring LGTM.

Single price/execution adapters for all assets is fine for tests; consider per‑asset adapters in future integration tests.


468-474: Use all getOrders() returns in assertions.

You ignore the two estimated-underlying arrays. Consider asserting length consistency to catch mismatches.

Example:

const [sellingTokens, sellingAmts, buyingTokens, buyingAmts, sEst, bEst] = await internalStatesOrchestrator.getOrders();
expect(sellingAmts.length).to.eq(sellingTokens.length);
expect(buyingAmts.length).to.eq(buyingTokens.length);
expect(sEst.length).to.eq(sellingTokens.length);
expect(bEst.length).to.eq(buyingTokens.length);

472-473: Assumptions on tokens count.

Asserting sellingTokens.length === 0 may become brittle with future flows (e.g., redemptions). Consider asserting non-negativity or relative expectations instead.

Also applies to: 529-531


533-564: Phase progression assertions could be stronger.

Also assert minibatch index changes and that PortfolioRebalanced is emitted on each step; optionally check updateBufferAmount is invoked (via spy/mock or event).


622-634: Edge slippage cases LGTM; add targetBufferRatio check.

Given it’s derived from slippageBound, add expect(await liquidityOrchestrator.targetBufferRatio()).to.equal(110); when bound is 100.

contracts/orchestrators/LiquidityOrchestrator.sol (4)

261-275: Harden performUpkeep action dispatch.

Add an else revert InvalidArguments() for unknown actions; consider emitting PortfolioRebalanced only on epoch completion to reduce noise.

Apply:

         } else if (action == ACTION_PROCESS_FULFILL_REDEEM) {
             _processMinibatchFulfillRedeem();
-        }
+        } else {
+            revert ErrorsLib.InvalidArguments();
+        }
-        emit EventsLib.PortfolioRebalanced();
+        emit EventsLib.PortfolioRebalanced();

319-339: Indexing and bounds nits in _processMinibatchSell.

  • Use >= instead of redundant > || ==.
  • Prefer uint256 for indices to avoid 16‑bit overflow on larger lists.
  • Increment index deterministically: currentMinibatchIndex = minibatchIndex + 1;.

Apply:

-        ++currentMinibatchIndex;
-        uint16 i0 = minibatchIndex * executionMinibatchSize;
-        uint16 i1 = i0 + executionMinibatchSize;
-        if (i1 > sellingTokens.length || i1 == sellingTokens.length) {
-            i1 = uint16(sellingTokens.length);
+        currentMinibatchIndex = minibatchIndex + 1;
+        uint256 i0 = uint256(minibatchIndex) * executionMinibatchSize;
+        uint256 i1 = i0 + executionMinibatchSize;
+        if (i1 >= sellingTokens.length) {
+            i1 = sellingTokens.length;
             currentPhase = LiquidityUpkeepPhase.BuyingLeg;
             currentMinibatchIndex = 0;
         }
-        for (uint16 i = i0; i < i1; ++i) {
+        for (uint256 i = i0; i < i1; ++i) {
             address token = sellingTokens[i];
             if (token == address(underlyingAsset)) continue;
             uint256 amount = sellingAmounts[i];
             _executeSell(token, amount, sellingEstimatedUnderlyingAmounts[i]);
         }

344-368: Same indexing fixes for _processMinibatchBuy; update buffer only once.

Mirror the sell fixes and keep updateBufferAmount when i1 >= buyingTokens.length.

Apply:

-        ++currentMinibatchIndex;
-        uint16 i0 = minibatchIndex * executionMinibatchSize;
-        uint16 i1 = i0 + executionMinibatchSize;
-        if (i1 > buyingTokens.length || i1 == buyingTokens.length) {
-            i1 = uint16(buyingTokens.length);
+        currentMinibatchIndex = minibatchIndex + 1;
+        uint256 i0 = uint256(minibatchIndex) * executionMinibatchSize;
+        uint256 i1 = i0 + executionMinibatchSize;
+        if (i1 >= buyingTokens.length) {
+            i1 = buyingTokens.length;
             currentPhase = LiquidityUpkeepPhase.FulfillRedeem;
             currentMinibatchIndex = 0;
         }
-        for (uint16 i = i0; i < i1; ++i) {
+        for (uint256 i = i0; i < i1; ++i) {
             address token = buyingTokens[i];
             if (token == address(underlyingAsset)) continue;
             uint256 amount = buyingAmounts[i];
             _executeBuy(token, amount, buyingEstimatedUnderlyingAmounts[i]);
         }
-        if (i1 == buyingTokens.length) {
+        if (i1 >= buyingTokens.length) {
             internalStatesOrchestrator.updateBufferAmount(deltaBufferAmount);
         }

378-381: Defensive note: signed deltas from uints.

Casting uint256 to int256 will revert on very large inputs. If upstream estimates could ever exceed int256.max, clamp or pre‑validate earlier.

Also applies to: 389-391, 411-412

contracts/orchestrators/InternalStatesOrchestrator.sol (2)

418-433: Transparent vaults: persist fulfillRedeem PIT totalAssets

You compute pendingRedeem against totalAssets but don’t store the per‑vault PIT totals for the transparent path. For feature parity with encrypted and to support external consumers via getVaultTotalAssetsForFulfillRedeem, persist it.

 uint256 pendingRedeem = vault.convertToAssetsWithPITTotalAssets(
     vault.pendingRedeem(),
     totalAssets,
     Math.Rounding.Floor
 );
+_currentEpoch.vaultsTotalAssetsForFulfillRedeem[address(vault)] = totalAssets;
 vault.fulfillRedeem(totalAssets);

899-903: getPriceOf returns zero for unseen tokens

If a token wasn’t touched this epoch, price is zero. Consider computing/caching on-demand or reverting for unset tokens to avoid misleading zeros.

test/OrionVaultExchangeRate.test.ts (6)

34-41: Constructor param naming: automation registry vs signer

You pass internalStatesOrchestratorSigner.address as automationRegistry_. That’s fine for tests, but rename the local variable to automationRegistry to avoid confusion when reading.


80-97: Fixture return key misleads: liquidityOrchestrator is a Signer, not the contract

Rename liquidityOrchestrator → liquidityOrchestratorSigner to avoid accidental misuse.

-      liquidityOrchestrator: liquidityOrchestratorSigner,
+      liquidityOrchestratorSigner,

128-152: Fragile assertion: shares > deposit

Virtual-offset implementations vary; asserting shares > deposit can be flaky. Prefer a symmetric closeness check to 1:1 and drop the strict inequality.

- expect(shares).to.be.gt(depositAmount);
+ expect(shares).to.be.closeTo(depositAmount, ethers.parseUnits("1", 6));

289-333: Ratio check uses Number() on bigints — precision risk

Cast to JS Number can lose precision. Use bigint cross‑multiplication for proportionality.

- const expectedRatio = Number(shares1Before) / Number(shares2Before);
- const actualRatio = Number(increase1) / Number(increase2);
- expect(actualRatio).to.be.closeTo(expectedRatio, 0.01);
+ // Cross-multiply to avoid floating precision: increase1/shares1 ≈ increase2/shares2
+ expect(increase1 * shares2Before).to.be.closeTo(increase2 * shares1Before, (increase2 * shares1Before) / 100n);

155-174: Reduce duplication with a small helper for “seed deposit then update”

These blocks repeat the same 3 calls. Extract a helper to make tests shorter and less error‑prone.

Example helper:

async function seed(vault, orchestratorAddr, totalBefore, totalAfter) {
  const imp = await impersonateOrchestrator(orchestratorAddr);
  await vault.connect(imp).fulfillDeposit(totalBefore);
  await vault.connect(imp).updateVaultState([], totalAfter);
}

Also applies to: 188-198, 210-220, 338-347, 357-367, 388-402, 407-412, 419-422, 435-443, 466-468


242-266: Derive virtualOffset from decimals — don’t hard‑code it

Replace the hard-coded line in test/OrionVaultExchangeRate.test.ts (around line 276) with a runtime computation, e.g.:

const virtualOffset = 10n ** BigInt((await vault.decimals()) - (await underlyingAsset.decimals()));

Keeps the test resilient to changes in share/underlying decimals.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d5f5ba and 295ab5d.

📒 Files selected for processing (9)
  • artifacts/contracts/OrionConfig.sol/OrionConfig.json (1 hunks)
  • artifacts/contracts/interfaces/IInternalStateOrchestrator.sol/IInternalStateOrchestrator.json (2 hunks)
  • artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (6 hunks)
  • contracts/interfaces/IInternalStateOrchestrator.sol (1 hunks)
  • contracts/interfaces/ILiquidityOrchestrator.sol (1 hunks)
  • contracts/orchestrators/InternalStatesOrchestrator.sol (12 hunks)
  • contracts/orchestrators/LiquidityOrchestrator.sol (7 hunks)
  • test/Orchestrators.test.ts (9 hunks)
  • test/OrionVaultExchangeRate.test.ts (18 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • contracts/interfaces/ILiquidityOrchestrator.sol
  • artifacts/contracts/OrionConfig.sol/OrionConfig.json
⏰ 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 (15)
artifacts/contracts/orchestrators/LiquidityOrchestrator.sol/LiquidityOrchestrator.json (3)

27-31: ABI addition LGTM (AdapterNotSet).


53-57: ABI addition LGTM (InvalidState).


194-212: Public getters for estimation arrays and delta buffer are aligned with on-chain inspection needs.

Good visibility for ops/monitoring.

Also applies to: 309-320, 449-467

artifacts/contracts/interfaces/IInternalStateOrchestrator.sol/IInternalStateOrchestrator.json (4)

94-129: ABI change to getOrders(...) matches interface updates.


131-148: ABI add getPriceOf(address) LGTM.


150-167: ABI add getVaultTotalAssetsForFulfillRedeem(address) LGTM.


220-232: ABI add updateBufferAmount(int256) LGTM — verify access control in implementation.

test/Orchestrators.test.ts (4)

9-10: Switch to Orion adapters looks correct.

Deployments and typed bindings align with the new interfaces.

Also applies to: 29-31, 94-98, 165-172


148-163: Slippage bound validation coverage LGTM.

Good negative and positive cases.


337-339: Idle-only guard test LGTM.

Asserts revert paths when system not idle.


517-526: Good sanity on positive amounts.

Covers minimal order integrity.

contracts/orchestrators/LiquidityOrchestrator.sol (2)

84-95: State additions for estimations + delta buffer LGTM.

Public arrays and deltaBufferAmount are appropriate for introspection.


246-252: FulfillRedeem action wiring in checkUpkeep LGTM.

Adds a clean terminal leg.

contracts/orchestrators/InternalStatesOrchestrator.sol (1)

354-356: Reentrancy suppression comment — ensure guard coverage

These blocks disable slither’s reentrancy warning. Confirm all external calls from performUpkeep paths are covered by nonReentrant and cannot reenter via callbacks (e.g., vault hooks). If any path can be called externally outside performUpkeep, add function-level nonReentrant or restructure to CEI.

Also applies to: 756-757

test/OrionVaultExchangeRate.test.ts (1)

6-10: Helper LGTM

Impersonation + funding pattern is clean and reusable.

Comment on lines 852 to +898
for (uint16 i = 0; i < allTokensLength; ++i) {
address token = allTokens[i];
tokens[i] = token;
amounts[i] = _currentEpoch.buyingOrders[token];
uint256 sellingAmount = _currentEpoch.sellingOrders[token];
uint256 buyingAmount = _currentEpoch.buyingOrders[token];

if (sellingAmount > 0) {
sellingTokens[sellingIndex] = token;
sellingAmounts[sellingIndex] = sellingAmount;
sellingEstimatedUnderlyingAmounts[sellingIndex] = sellingAmount.mulDiv(
_currentEpoch.priceArray[token],
priceAdapterPrecision
);
++sellingIndex;
}
if (buyingAmount > 0) {
buyingTokens[buyingIndex] = token;
buyingAmounts[buyingIndex] = buyingAmount;
buyingEstimatedUnderlyingAmounts[buyingIndex] = buyingAmount.mulDiv(
_currentEpoch.priceArray[token],
priceAdapterPrecision
);
++buyingIndex;
}
}
}

/// @inheritdoc IInternalStateOrchestrator
function getPriceOf(address token) external view returns (uint256 price) {
return _currentEpoch.priceArray[token];
}

/// @inheritdoc IInternalStateOrchestrator
function subtractPendingProtocolFees(uint256 amount) external onlyLiquidityOrchestrator {
if (amount > pendingProtocolFees) revert ErrorsLib.InsufficientAmount();
pendingProtocolFees -= amount;
}

/// @inheritdoc IInternalStateOrchestrator
function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
if (deltaAmount > 0) {
bufferAmount += uint256(deltaAmount);
} else if (deltaAmount < 0) {
bufferAmount -= uint256(-deltaAmount);
}
}

/// @notice Get total assets for fulfill redeem for a specific vault
/// @param vault The vault address
/// @return totalAssets The total assets for fulfill redeem
function getVaultTotalAssetsForFulfillRedeem(address vault) external view returns (uint256 totalAssets) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Estimated underlying amounts ignore token vs underlying decimals

sellingEstimatedUnderlyingAmounts/buyingEstimatedUnderlyingAmounts multiply shares by price but don’t convert token decimals → underlying decimals, producing mis‑scaled estimates for non‑underlying tokens.

- sellingEstimatedUnderlyingAmounts[sellingIndex] = sellingAmount.mulDiv(
-   _currentEpoch.priceArray[token],
-   priceAdapterPrecision
- );
+ {
+   uint256 est = sellingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
+   sellingEstimatedUnderlyingAmounts[sellingIndex] = UtilitiesLib.convertDecimals(
+     est,
+     config.getTokenDecimals(token),
+     underlyingDecimals
+   );
+ }
 ...
- buyingEstimatedUnderlyingAmounts[buyingIndex] = buyingAmount.mulDiv(
-   _currentEpoch.priceArray[token],
-   priceAdapterPrecision
- );
+ {
+   uint256 est = buyingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
+   buyingEstimatedUnderlyingAmounts[buyingIndex] = UtilitiesLib.convertDecimals(
+     est,
+     config.getTokenDecimals(token),
+     underlyingDecimals
+   );
+ }
📝 Committable suggestion

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

Suggested change
/// @notice Populates the order arrays with non-zero values
/// @param allTokens Array of all tokens
/// @param sellingTokens Array to populate with selling tokens
/// @param sellingAmounts Array to populate with selling amounts
/// @param sellingEstimatedUnderlyingAmounts Array to populate with selling estimated amounts
/// @param buyingTokens Array to populate with buying tokens
/// @param buyingAmounts Array to populate with buying amounts
/// @param buyingEstimatedUnderlyingAmounts Array to populate with buying estimated amounts
function _populateOrders(
address[] memory allTokens,
address[] memory sellingTokens,
uint256[] memory sellingAmounts,
uint256[] memory sellingEstimatedUnderlyingAmounts,
address[] memory buyingTokens,
uint256[] memory buyingAmounts,
uint256[] memory buyingEstimatedUnderlyingAmounts
) private view {
uint16 allTokensLength = uint16(allTokens.length);
tokens = new address[](allTokensLength);
amounts = new uint256[](allTokensLength);
uint16 sellingIndex = 0;
uint16 buyingIndex = 0;
for (uint16 i = 0; i < allTokensLength; ++i) {
address token = allTokens[i];
tokens[i] = token;
amounts[i] = _currentEpoch.buyingOrders[token];
uint256 sellingAmount = _currentEpoch.sellingOrders[token];
uint256 buyingAmount = _currentEpoch.buyingOrders[token];
if (sellingAmount > 0) {
sellingTokens[sellingIndex] = token;
sellingAmounts[sellingIndex] = sellingAmount;
sellingEstimatedUnderlyingAmounts[sellingIndex] = sellingAmount.mulDiv(
_currentEpoch.priceArray[token],
priceAdapterPrecision
);
++sellingIndex;
}
if (buyingAmount > 0) {
buyingTokens[buyingIndex] = token;
buyingAmounts[buyingIndex] = buyingAmount;
buyingEstimatedUnderlyingAmounts[buyingIndex] = buyingAmount.mulDiv(
_currentEpoch.priceArray[token],
priceAdapterPrecision
);
++buyingIndex;
}
}
}
/// @notice Populates the order arrays with non-zero values
/// @param allTokens Array of all tokens
/// @param sellingTokens Array to populate with selling tokens
/// @param sellingAmounts Array to populate with selling amounts
/// @param sellingEstimatedUnderlyingAmounts Array to populate with selling estimated amounts
/// @param buyingTokens Array to populate with buying tokens
/// @param buyingAmounts Array to populate with buying amounts
/// @param buyingEstimatedUnderlyingAmounts Array to populate with buying estimated amounts
function _populateOrders(
address[] memory allTokens,
address[] memory sellingTokens,
uint256[] memory sellingAmounts,
uint256[] memory sellingEstimatedUnderlyingAmounts,
address[] memory buyingTokens,
uint256[] memory buyingAmounts,
uint256[] memory buyingEstimatedUnderlyingAmounts
) private view {
uint16 allTokensLength = uint16(allTokens.length);
uint16 sellingIndex = 0;
uint16 buyingIndex = 0;
for (uint16 i = 0; i < allTokensLength; ++i) {
address token = allTokens[i];
uint256 sellingAmount = _currentEpoch.sellingOrders[token];
uint256 buyingAmount = _currentEpoch.buyingOrders[token];
if (sellingAmount > 0) {
sellingTokens[sellingIndex] = token;
sellingAmounts[sellingIndex] = sellingAmount;
{
uint256 est = sellingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
sellingEstimatedUnderlyingAmounts[sellingIndex] = UtilitiesLib.convertDecimals(
est,
config.getTokenDecimals(token),
underlyingDecimals
);
}
+sellingIndex;
}
if (buyingAmount > 0) {
buyingTokens[buyingIndex] = token;
buyingAmounts[buyingIndex] = buyingAmount;
{
uint256 est = buyingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
buyingEstimatedUnderlyingAmounts[buyingIndex] = UtilitiesLib.convertDecimals(
est,
config.getTokenDecimals(token),
underlyingDecimals
);
}
+buyingIndex;
}
}
}

Comment on lines 911 to 917

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

updateBufferAmount can underflow on negative delta

A negative delta larger than bufferAmount reverts via underflow. Make it explicit and emit a clear error.

 function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
   if (deltaAmount > 0) {
     bufferAmount += uint256(deltaAmount);
   } else if (deltaAmount < 0) {
-    bufferAmount -= uint256(-deltaAmount);
+    uint256 dec = uint256(-deltaAmount);
+    if (dec > bufferAmount) revert ErrorsLib.InsufficientAmount();
+    bufferAmount -= dec;
   }
 }
📝 Committable suggestion

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

Suggested change
function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
if (deltaAmount > 0) {
bufferAmount += uint256(deltaAmount);
} else if (deltaAmount < 0) {
bufferAmount -= uint256(-deltaAmount);
}
}
function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
if (deltaAmount > 0) {
bufferAmount += uint256(deltaAmount);
} else if (deltaAmount < 0) {
uint256 dec = uint256(-deltaAmount);
if (dec > bufferAmount) revert ErrorsLib.InsufficientAmount();
bufferAmount -= dec;
}
}
🤖 Prompt for AI Agents
In contracts/orchestrators/InternalStatesOrchestrator.sol around lines 911 to
917, the subtraction when deltaAmount < 0 can underflow if uint256(-deltaAmount)
> bufferAmount; add an explicit guard that checks that the absolute negative
delta is <= bufferAmount and revert with a clear error message (or a custom
error) when it is not, then perform the subtraction with the safe cast; ensure
positive branch still casts and updates bufferAmount as before.

Comment thread contracts/orchestrators/LiquidityOrchestrator.sol
Comment on lines 372 to 390
// slither-disable-next-line unused-return
IERC20(asset).approve(address(adapter), amount);

// TODO: pass slippageBound, oracle price and number of shares to adapters.

// TODO: not performing trade if slippage is too high, record we still have
// the open position.
IERC20(asset).approve(address(adapter), sharesAmount);

// Execute sell through adapter, pull shares from this contract and push underlying assets to it.
adapter.sell(asset, amount);
uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, minUnderlyingAmount);

deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
}

/// @notice Executes a buy order
/// @param asset The asset to buy
/// @param amount The amount of shares to buy
function _executeBuy(address asset, uint256 amount) internal {
/// @param sharesAmount The amount of shares to buy
/// @param estimatedUnderlyingAmount The estimated underlying amount to spend
function _executeBuy(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Revoke allowances after adapter.sell to avoid leftover spend.

Reset share allowance back to 0 post‑call to minimize exposure window.

Apply:

         IERC20(asset).approve(address(adapter), sharesAmount);
         // Execute sell through adapter, pull shares from this contract and push underlying assets to it.
         uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, minUnderlyingAmount);
+        // Revoke leftover allowance
+        IERC20(asset).approve(address(adapter), 0);
 
         deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
📝 Committable suggestion

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

Suggested change
/// @param sharesAmount The amount of shares to sell
/// @param estimatedUnderlyingAmount The estimated underlying amount to receive
function _executeSell(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();
uint256 minUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 - slippageBound, 10000);
// Approve adapter to spend shares
// slither-disable-next-line unused-return
IERC20(asset).approve(address(adapter), 0);
// slither-disable-next-line unused-return
IERC20(asset).approve(address(adapter), amount);
// TODO: pass slippageBound, oracle price and number of shares to adapters.
// TODO: not performing trade if slippage is too high, record we still have
// the open position.
IERC20(asset).approve(address(adapter), sharesAmount);
// Execute sell through adapter, pull shares from this contract and push underlying assets to it.
adapter.sell(asset, amount);
uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, minUnderlyingAmount);
deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
}
/// @param sharesAmount The amount of shares to sell
/// @param estimatedUnderlyingAmount The estimated underlying amount to receive
function _executeSell(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();
uint256 minUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 - slippageBound, 10000);
// Approve adapter to spend shares
// slither-disable-next-line unused-return
IERC20(asset).approve(address(adapter), 0);
// slither-disable-next-line unused-return
IERC20(asset).approve(address(adapter), sharesAmount);
// Execute sell through adapter, pull shares from this contract and push underlying assets to it.
uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, minUnderlyingAmount);
// Revoke leftover allowance
IERC20(asset).approve(address(adapter), 0);
deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
}
🤖 Prompt for AI Agents
In contracts/orchestrators/LiquidityOrchestrator.sol around lines 372 to 390,
the function _executeSell leaves the adapter allowance set to sharesAmount after
calling adapter.sell; after the sell completes, reset the ERC20 allowance for
the adapter back to 0 to minimize the exposure window. Modify the function to
call IERC20(asset).approve(address(adapter), 0) immediately after adapter.sell
(and before updating deltaBufferAmount) ensuring the approval reset executes
regardless of sell outcome (e.g., keep it in the same flow since adapter.sell is
external) so leftover spend is removed.

Comment on lines +396 to +413
// Approve adapter to spend underlying assets with slippage tolerance
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), 0);
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), amount);
IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);

// Execute buy through adapter, pull underlying assets from this contract and push shares to it.
adapter.buy(asset, amount);
uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, maxUnderlyingAmount);

deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
}

/// @notice Handles the fulfill redeem action
function _processMinibatchFulfillRedeem() internal {
if (currentPhase != LiquidityUpkeepPhase.FulfillRedeem) {
revert ErrorsLib.InvalidState();
}

currentPhase = LiquidityUpkeepPhase.Idle;

// Process transparent vaults
address[] memory transparentVaults = config.getAllOrionVaults(EventsLib.VaultType.Transparent);
for (uint16 i = 0; i < transparentVaults.length; ++i) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Revoke allowances after adapter.buy; guard int256 casts.

  • Reset underlying allowance to 0 post‑call.
  • Add a safety check before casting large uints to int256.

Apply:

         IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);
         // Execute buy through adapter, pull underlying assets from this contract and push shares to it.
         uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, maxUnderlyingAmount);
-        deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
+        // Revoke leftover allowance
+        IERC20(underlyingAsset).approve(address(adapter), 0);
+        // Safe cast guards (defensive; estimated/executed should be well below int256 max)
+        if (estimatedUnderlyingAmount > uint256(type(int256).max) || executionUnderlyingAmount > uint256(type(int256).max)) {
+            revert ErrorsLib.InvalidArguments();
+        }
+        deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
📝 Committable suggestion

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

Suggested change
function _executeBuy(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();
// TODO: analogous API updates as sell.
// TODO: underlying asset/numeraire needs to be part of the whitelisted investment universe,
// as if an order does not pass the underlying equivalent
// is set into the portfolio state for all vaults.
uint256 maxUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 + slippageBound, 10000);
// Approve adapter to spend underlying assets
// Approve adapter to spend underlying assets with slippage tolerance
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), 0);
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), amount);
IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);
// Execute buy through adapter, pull underlying assets from this contract and push shares to it.
adapter.buy(asset, amount);
uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, maxUnderlyingAmount);
deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
}
function _executeBuy(address asset, uint256 sharesAmount, uint256 estimatedUnderlyingAmount) internal {
IExecutionAdapter adapter = executionAdapterOf[asset];
if (address(adapter) == address(0)) revert ErrorsLib.AdapterNotSet();
uint256 maxUnderlyingAmount = estimatedUnderlyingAmount.mulDiv(10000 + slippageBound, 10000);
// Approve adapter to spend underlying assets with slippage tolerance
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), 0);
// slither-disable-next-line unused-return
IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);
// Execute buy through adapter, pull underlying assets from this contract and push shares to it.
uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, maxUnderlyingAmount);
// Revoke leftover allowance
IERC20(underlyingAsset).approve(address(adapter), 0);
// Safe cast guards (defensive; estimated/executed should be well below int256 max)
if (estimatedUnderlyingAmount > uint256(type(int256).max) || executionUnderlyingAmount > uint256(type(int256).max)) {
revert ErrorsLib.InvalidArguments();
}
deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
}
🤖 Prompt for AI Agents
In contracts/orchestrators/LiquidityOrchestrator.sol around lines 396-413, the
function _executeBuy needs two fixes: after calling adapter.buy, reset the
underlyingAsset allowance back to 0 to avoid leaving approvals open; and before
casting estimatedUnderlyingAmount and executionUnderlyingAmount to int256,
validate they fit within int256 range (i.e. <= uint256(type(int256).max) ) and
revert or handle the overflow case, then perform the safe casts and adjust
deltaBufferAmount. Ensure the post-call approve(address(adapter), 0) uses
IERC20(underlyingAsset).approve and that any failure paths also reset approvals
as necessary.

Comment on lines 414 to 445

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

FulfillRedeem: set Idle after processing; consider minibatching vaults.

  • Don’t expose Idle mid‑processing; move currentPhase = Idle to the end.
  • Looping over all vaults can exceed gas; add minibatch support similar to buy/sell.

Apply minimal safety tweak:

-        currentPhase = LiquidityUpkeepPhase.Idle;
+        // Move to Idle after fulfill loops complete

And set at end:

         for (uint16 i = 0; i < encryptedVaults.length; ++i) {
             ...
         }
+        currentPhase = LiquidityUpkeepPhase.Idle;

Follow‑up (non‑blocking): add minibatch indices for vault fulfillments.

📝 Committable suggestion

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

Suggested change
/// @notice Handles the fulfill redeem action
function _processMinibatchFulfillRedeem() internal {
if (currentPhase != LiquidityUpkeepPhase.FulfillRedeem) {
revert ErrorsLib.InvalidState();
}
currentPhase = LiquidityUpkeepPhase.Idle;
// Process transparent vaults
address[] memory transparentVaults = config.getAllOrionVaults(EventsLib.VaultType.Transparent);
for (uint16 i = 0; i < transparentVaults.length; ++i) {
address vault = transparentVaults[i];
uint256 totalAssetsForRedeem = internalStatesOrchestrator.getVaultTotalAssetsForFulfillRedeem(vault);
if (totalAssetsForRedeem > 0) {
// Call fulfillRedeem on the vault with the stored totalAssets
IOrionVault(vault).fulfillRedeem(totalAssetsForRedeem);
}
}
// Process encrypted vaults
address[] memory encryptedVaults = config.getAllOrionVaults(EventsLib.VaultType.Encrypted);
for (uint16 i = 0; i < encryptedVaults.length; ++i) {
address vault = encryptedVaults[i];
uint256 totalAssetsForRedeem = internalStatesOrchestrator.getVaultTotalAssetsForFulfillRedeem(vault);
if (totalAssetsForRedeem > 0) {
// Call fulfillRedeem on the vault with the stored totalAssets
IOrionVault(vault).fulfillRedeem(totalAssetsForRedeem);
}
}
}
/// @notice Handles the fulfill redeem action
function _processMinibatchFulfillRedeem() internal {
if (currentPhase != LiquidityUpkeepPhase.FulfillRedeem) {
revert ErrorsLib.InvalidState();
}
// Move to Idle after fulfill loops complete
// Process transparent vaults
address[] memory transparentVaults = config.getAllOrionVaults(EventsLib.VaultType.Transparent);
for (uint16 i = 0; i < transparentVaults.length; ++i) {
address vault = transparentVaults[i];
uint256 totalAssetsForRedeem = internalStatesOrchestrator.getVaultTotalAssetsForFulfillRedeem(vault);
if (totalAssetsForRedeem > 0) {
// Call fulfillRedeem on the vault with the stored totalAssets
IOrionVault(vault).fulfillRedeem(totalAssetsForRedeem);
}
}
// Process encrypted vaults
address[] memory encryptedVaults = config.getAllOrionVaults(EventsLib.VaultType.Encrypted);
for (uint16 i = 0; i < encryptedVaults.length; ++i) {
address vault = encryptedVaults[i];
uint256 totalAssetsForRedeem = internalStatesOrchestrator.getVaultTotalAssetsForFulfillRedeem(vault);
if (totalAssetsForRedeem > 0) {
// Call fulfillRedeem on the vault with the stored totalAssets
IOrionVault(vault).fulfillRedeem(totalAssetsForRedeem);
}
}
currentPhase = LiquidityUpkeepPhase.Idle;
}

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

Caution

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

⚠️ Outside diff range comments (2)
contracts/orchestrators/InternalStatesOrchestrator.sol (2)

330-343: Fix incorrect clearing of vault mappings keyed by token addresses.

You’re deleting vaultsTotalAssets, encryptedVaultsTotalAssets, and vaultsTotalAssetsForFulfillRedeem using token keys. These mappings are keyed by vault addresses, so entries from the previous epoch can persist and corrupt fulfillment. Clear by iterating prior epoch vault arrays before reassigning them.

         for (uint16 i = 0; i < _currentEpoch.tokens.length; ++i) {
             address token = _currentEpoch.tokens[i];
             delete _currentEpoch.priceArray[token];
             delete _currentEpoch.initialBatchPortfolio[token];
-            delete _currentEpoch.vaultsTotalAssets[token];
-            delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[token];
             delete _currentEpoch.finalBatchPortfolio[token];
             delete _currentEpoch.sellingOrders[token];
             delete _currentEpoch.buyingOrders[token];
             delete _currentEpoch.tokenExists[token];
             _currentEpoch.encryptedInitialBatchPortfolio[token] = _ezero;
-            _currentEpoch.encryptedVaultsTotalAssets[token] = _ezero;
             _currentEpoch.encryptedFinalBatchPortfolio[token] = _ezero;
         }
         delete _currentEpoch.tokens;
         delete _decryptedValues;
 
-        transparentVaultsEpoch = config.getAllOrionVaults(EventsLib.VaultType.Transparent);
-        encryptedVaultsEpoch = config.getAllOrionVaults(EventsLib.VaultType.Encrypted);
+        // Clear vault-keyed mappings using previous-epoch vault lists
+        for (uint16 i = 0; i < transparentVaultsEpoch.length; ++i) {
+            address v = transparentVaultsEpoch[i];
+            delete _currentEpoch.vaultsTotalAssets[v];
+            delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[v];
+            _currentEpoch.encryptedVaultsTotalAssets[v] = _ezero;
+        }
+        for (uint16 i = 0; i < encryptedVaultsEpoch.length; ++i) {
+            address v = encryptedVaultsEpoch[i];
+            delete _currentEpoch.vaultsTotalAssets[v];
+            delete _currentEpoch.vaultsTotalAssetsForFulfillRedeem[v];
+            _currentEpoch.encryptedVaultsTotalAssets[v] = _ezero;
+        }
+
+        transparentVaultsEpoch = config.getAllOrionVaults(EventsLib.VaultType.Transparent);
+        encryptedVaultsEpoch = config.getAllOrionVaults(EventsLib.VaultType.Encrypted);

364-366: Fix uint8 multiplication overflow in all minibatch windows.

Same overflow pattern as in LiquidityOrchestrator: multiply two uint8s after widening.

-        uint16 i0 = minibatchIndex * transparentMinibatchSize;
-        uint16 i1 = i0 + transparentMinibatchSize;
+        uint16 i0 = uint16(minibatchIndex) * uint16(transparentMinibatchSize);
+        uint16 i1 = i0 + uint16(transparentMinibatchSize);
-        uint16 i0 = minibatchIndex * encryptedMinibatchSize;
-        uint16 i1 = i0 + encryptedMinibatchSize;
+        uint16 i0 = uint16(minibatchIndex) * uint16(encryptedMinibatchSize);
+        uint16 i1 = i0 + uint16(encryptedMinibatchSize);

Also applies to: 448-450, 685-687, 742-744

♻️ Duplicate comments (6)
contracts/orchestrators/LiquidityOrchestrator.sol (4)

290-315: Reset minibatch index at epoch start.

currentMinibatchIndex isn’t reset in _handleStart(), risking stale indices and skipped work on new epochs. Reset to 0 when initializing the epoch. This mirrors prior feedback.

         // Clear previous epoch data
         delete sellingTokens;
         delete sellingAmounts;
         delete buyingTokens;
         delete buyingAmounts;
         delete sellingEstimatedUnderlyingAmounts;
         delete buyingEstimatedUnderlyingAmounts;
         deltaBufferAmount = 0;
+        currentMinibatchIndex = 0;

158-164: Acknowledgement: upper slippage bound added.

Max 20% slippage bound enforces sane limits and prevents underflow in (10000 - slippageBound). Good safeguard; aligns with earlier suggestion.


381-391: Revoke allowances after adapter calls; add safe int256 cast guards.

  • Leave-no-allowance principle: reset approvals to 0 after sell/buy.
  • Guard int256 casts of large uint256 amounts to avoid UB on extreme values. This mirrors prior feedback on the buy path; apply to both.
         IERC20(asset).approve(address(adapter), sharesAmount);
 
         // Execute sell through adapter, pull shares from this contract and push underlying assets to it.
         uint256 executionUnderlyingAmount = adapter.sell(asset, sharesAmount, minUnderlyingAmount);
-
-        deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
+        // Revoke leftover allowance
+        IERC20(asset).approve(address(adapter), 0);
+        // Safe cast guards
+        if (
+            estimatedUnderlyingAmount > uint256(type(int256).max) ||
+            executionUnderlyingAmount > uint256(type(int256).max)
+        ) revert ErrorsLib.InvalidArguments();
+        deltaBufferAmount += int256(executionUnderlyingAmount) - int256(estimatedUnderlyingAmount);
         IERC20(underlyingAsset).approve(address(adapter), maxUnderlyingAmount);
 
         // Execute buy through adapter, pull underlying assets from this contract and push shares to it.
         uint256 executionUnderlyingAmount = adapter.buy(asset, sharesAmount, maxUnderlyingAmount);
-
-        deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);
+        // Revoke leftover allowance
+        IERC20(underlyingAsset).approve(address(adapter), 0);
+        // Safe cast guards
+        if (
+            estimatedUnderlyingAmount > uint256(type(int256).max) ||
+            executionUnderlyingAmount > uint256(type(int256).max)
+        ) revert ErrorsLib.InvalidArguments();
+        deltaBufferAmount += int256(estimatedUnderlyingAmount) - int256(executionUnderlyingAmount);

Also applies to: 403-413


421-446: Move Idle transition to the end of FulfillRedeem.

Setting currentPhase = Idle before the loops can expose Idle mid‑processing and race the next upkeep. Move it after both loops finish. Mirrors prior feedback.

-        currentPhase = LiquidityUpkeepPhase.Idle;
+        // set to Idle after fulfill loops complete
...
-        }
-    }
+        }
+        currentPhase = LiquidityUpkeepPhase.Idle;
+    }
contracts/orchestrators/InternalStatesOrchestrator.sol (2)

878-896: Estimated underlying amounts ignore token↔underlying decimals.

price * shares / precision needs decimal conversion into underlying units. This was previously flagged.

-                sellingEstimatedUnderlyingAmounts[sellingIndex] = sellingAmount.mulDiv(
-                    _currentEpoch.priceArray[token],
-                    priceAdapterPrecision
-                );
+                {
+                    uint256 unscaled = sellingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
+                    sellingEstimatedUnderlyingAmounts[sellingIndex] = UtilitiesLib.convertDecimals(
+                        unscaled,
+                        config.getTokenDecimals(token),
+                        underlyingDecimals
+                    );
+                }
...
-                buyingEstimatedUnderlyingAmounts[buyingIndex] = buyingAmount.mulDiv(
-                    _currentEpoch.priceArray[token],
-                    priceAdapterPrecision
-                );
+                {
+                    uint256 unscaled = buyingAmount.mulDiv(_currentEpoch.priceArray[token], priceAdapterPrecision);
+                    buyingEstimatedUnderlyingAmounts[buyingIndex] = UtilitiesLib.convertDecimals(
+                        unscaled,
+                        config.getTokenDecimals(token),
+                        underlyingDecimals
+                    );
+                }

911-917: Guard buffer subtraction on negative deltas.

bufferAmount -= uint256(-deltaAmount) underflows when the negative delta exceeds the buffer. Make it explicit, as previously suggested.

     function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
       if (deltaAmount > 0) {
         bufferAmount += uint256(deltaAmount);
       } else if (deltaAmount < 0) {
-        bufferAmount -= uint256(-deltaAmount);
+        uint256 dec = uint256(-deltaAmount);
+        if (dec > bufferAmount) revert ErrorsLib.InsufficientAmount();
+        bufferAmount -= dec;
       }
     }
🧹 Nitpick comments (6)
contracts/orchestrators/LiquidityOrchestrator.sol (2)

328-333: Simplify boundary check to use >=.

Use >= length instead of i1 > length || i1 == length. Clearer and avoids redundant branch checks.

-        if (i1 > sellingTokens.length || i1 == sellingTokens.length) {
+        if (i1 >= sellingTokens.length) {
...
-        if (i1 > buyingTokens.length || i1 == buyingTokens.length) {
+        if (i1 >= buyingTokens.length) {

Also applies to: 353-357


172-174: Prefer SafeERC20 for transfers/approvals.

Many ERC20s are non‑standard on return values. Use SafeERC20’s safeTransfer/forceApprove to avoid silent failures and weird tokens.

Would you like a follow-up PR converting these call sites to SafeERC20?

Also applies to: 205-207, 219-220, 229-231, 381-386, 403-408

contracts/orchestrators/InternalStatesOrchestrator.sol (4)

354-356: Scope of slither reentrancy suppression.

Reentrancy is disabled for a broad region. Consider narrowing the suppression to only the exact external-call sites to keep static analysis useful.

Also applies to: 756-757


364-371: Also simplify boundary check to use >=.

Minor readability tweak mirroring LO.

-        if (i1 > transparentVaultsEpoch.length || i1 == transparentVaultsEpoch.length) {
+        if (i1 >= transparentVaultsEpoch.length) {

818-821: Nit: allocate arrays once; early return if both counts are zero.

Skip _populateOrders when both counts are zero.


879-896: Add decimal conversion tests.

Once patched, please add tests covering tokens with non‑18 decimals to avoid regressions.

I can draft unit tests hitting 6/8/18‑dec tokens for both buy/sell paths.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 295ab5d and c1c5cac.

📒 Files selected for processing (2)
  • contracts/orchestrators/InternalStatesOrchestrator.sol (12 hunks)
  • contracts/orchestrators/LiquidityOrchestrator.sol (7 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Sourcery review
  • GitHub Check: Build, Lint and Test
🔇 Additional comments (6)
contracts/orchestrators/InternalStatesOrchestrator.sol (6)

798-833: getOrders: good API shape; verify zero-order elision.

The split arrays and non-zero filtering look right. Please verify callers assume dense arrays without gaps.


653-675: Buffering math LGTM; dependency on targetBufferRatio basis-points.

targetBufferAmount = protocolTotalAssets * targetBufferRatio / 1e4 assumes bps. Matches LiquidityOrchestrator’s computation; keep that invariant in docs/tests.


450-453: Consistent phase transitions; ensure currentMinibatchIndex resets.

You reset currentMinibatchIndex = 0 on phase changes—good. Keep this consistent if further phases are added.

Also applies to: 744-753


571-605: Encrypted path: good separation of fulfillRedeem values vs deposits.

Setting vaultsTotalAssetsForFulfillRedeem for encrypted vaults before deposits aligns with deferred fulfillment by LO.

Also applies to: 591-605


819-833: Counting and populating orders separated: good cohesion.

Private helpers are clean and testable. Keep it.


364-366: Repo‑wide verification: search for minibatchIndex * <X>MinibatchSize (uint multiply pattern)

rg search earlier failed due to a file‑type filter; cannot confirm absence of other matches. Run these from the repo root and paste results:

  • rg (preferred):
    rg -nP -C2 '\bminibatchIndex\s**\s*(?:[A-Za-z_]\wMinibatchSize|\d+)\b' -g '.sol' --hidden -S || true

  • fallback (find + grep):
    find . -type f -name '.sol' -print0 | xargs -0 -r grep -n -E -C2 "minibatchIndex[[:space:]]*[[:space:]]*([A-Za-z_][A-Za-z0-9_]*MinibatchSize|[0-9]+)" || true

  • quick scan for size symbols:
    rg -n '\bMinibatchSize\b' -g '*.sol' --hidden -S || true

Reference: contracts/orchestrators/InternalStatesOrchestrator.sol:364-366

Comment on lines 701 to 704
@@ -742,6 +753,8 @@ contract InternalStatesOrchestrator is SepoliaConfig, Ownable, ReentrancyGuard,
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Potential truncation: casting portfolio value to uint32.

value can exceed 32 bits post conversions; truncation corrupts state. Either bound-check before cast or widen Position.value if possible.

-            IOrionTransparentVault.Position[] memory portfolio = new IOrionTransparentVault.Position[](
+            IOrionTransparentVault.Position[] memory portfolio = new IOrionTransparentVault.Position[](
                 intentTokens.length
             );
...
-            portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
+            if (value > type(uint32).max) revert ErrorsLib.InvalidArguments();
+            portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });

If the interface allows, prefer changing value to uint256 in Position.

Also applies to: 724-729


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "pwd: $(pwd)"
git rev-parse --abbrev-ref HEAD 2>/dev/null || true

echo
echo "Searching for InternalStatesOrchestrator.sol..."
rg -n --hidden "InternalStatesOrchestrator.sol" || true

FILE="contracts/orchestrators/InternalStatesOrchestrator.sol"
if [ -f "$FILE" ]; then
  echo
  echo "== Showing $FILE (lines 1-240) =="
  sed -n '1,240p' "$FILE"
  echo
  echo "== Showing $FILE (lines 660-760) =="
  sed -n '660,760p' "$FILE"
else
  echo
  echo "$FILE not found at that path; searching for symbol 'InternalStatesOrchestrator'..."
  rg -n --hidden "InternalStatesOrchestrator" || true
fi

echo
echo "== Searching for IOrionTransparentVault occurrences =="
rg -n --hidden "IOrionTransparentVault" || true

echo
echo "== Searching for 'interface IOrionTransparentVault' files =="
rg -n --hidden "interface IOrionTransparentVault" || true

if files=$(rg -n --hidden "interface IOrionTransparentVault" -l || true); then
  for f in $files; do
    echo
    echo "== Found interface file: $f (showing up to 300 lines) =="
    sed -n '1,300p' "$f" || true
  done
fi

echo
echo "== Searching for 'struct Position' definitions =="
rg -n --hidden "struct Position" || true

echo
echo "== Searching for Position[] usages, assignments and uint32 casts =="
rg -n --hidden "IOrionTransparentVault.Position\[" -S || true
rg -n --hidden "Position\[\]" -S || true
rg -n --hidden "Position\s*\{" -S || true
rg -n --hidden "uint32(value)" -S || true
rg -n --hidden "value\s*:\s*uint32" -S || true

if [ -f "$FILE" ]; then
  echo
  echo "== Showing $FILE (lines 712-736) =="
  sed -n '712,736p' "$FILE" || true
fi

echo
echo "Done."

Length of output: 24459


Fix uint32 cast that can truncate portfolio value (contracts/orchestrators/InternalStatesOrchestrator.sol ≈701–736)

IOrionTransparentVault.Position.value is uint32 (interface) but value is computed as a uint256 and cast with no bounds check — risk of truncation/corrupted state.

  • Remediation: either bounds-check before casting or change Position.value to uint256 in the interface and update implementations (breaking change).
  • Suggested quick patch:
-            portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
+            if (value > type(uint32).max) revert ErrorsLib.InvalidArguments();
+            portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });

Also applies to the portfolio assignment in the same function (around line ~724).

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

Comment thread contracts/orchestrators/LiquidityOrchestrator.sol
@matteoettam09
matteoettam09 merged commit 0dc670a into main Sep 16, 2025
3 of 5 checks passed
@matteoettam09
matteoettam09 deleted the dev branch September 16, 2025 20:15

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
contracts/orchestrators/InternalStatesOrchestrator.sol (1)

652-674: Buffer rounding creates assets from dust.

You add deltaBufferAmount to bufferAmount, but per‑vault proportional deductions floor, so the sum of deductions ≤ deltaBufferAmount. The difference mints buffer out of thin air.

Minimal fix: add what you actually deducted.

-        uint256 deltaBufferAmount = targetBufferAmount - bufferAmount;
+        uint256 deltaBufferAmount = targetBufferAmount - bufferAmount;
+        uint256 totalDeducted = 0;
         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;
+          totalDeducted += vaultBufferCost;
         }
         for (uint16 i = 0; i < nEncryptedVaults; ++i) {
           address vault = encryptedVaultsEpoch[i];
           uint256 vaultAssets = _currentEpoch.vaultsTotalAssets[address(vault)];
           uint256 vaultBufferCost = deltaBufferAmount.mulDiv(vaultAssets, protocolTotalAssets);
           _currentEpoch.vaultsTotalAssets[address(vault)] -= vaultBufferCost;
+          totalDeducted += vaultBufferCost;
         }
-        bufferAmount += deltaBufferAmount;
+        bufferAmount += totalDeducted;

Optional refinement: push the remainder to the last vault to hit the exact target.

♻️ Duplicate comments (3)
contracts/orchestrators/InternalStatesOrchestrator.sol (3)

717-724: Potential truncation: casting portfolio value to uint32 (re‑raise).

value is uint256. Silent down‑cast to uint32 can corrupt state.

-                portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });
+                if (value > type(uint32).max) revert ErrorsLib.InvalidArguments();
+                portfolio[j] = IOrionTransparentVault.Position({ token: token, value: uint32(value) });

If the interface allows, prefer widening Position.value to uint256 across the stack in a follow‑up PR.


877-885: Estimated underlying amounts ignore token vs underlying decimals (re‑raise).

Selling/Buying estimated underlying amounts are computed without converting token decimals → underlying decimals, leading to mis‑scaled values for non‑underlying tokens. This can break slippage bounds and buffer accounting.

Apply this diff inside _populateOrders:

-                sellingEstimatedUnderlyingAmounts[sellingIndex] = sellingAmount.mulDiv(
-                    _currentEpoch.priceArray[token],
-                    priceAdapterPrecision
-                );
+                {
+                    uint256 est = sellingAmount.mulDiv(
+                        _currentEpoch.priceArray[token],
+                        priceAdapterPrecision
+                    );
+                    sellingEstimatedUnderlyingAmounts[sellingIndex] = UtilitiesLib.convertDecimals(
+                        est,
+                        config.getTokenDecimals(token),
+                        underlyingDecimals
+                    );
+                }
...
-                buyingEstimatedUnderlyingAmounts[buyingIndex] = buyingAmount.mulDiv(
-                    _currentEpoch.priceArray[token],
-                    priceAdapterPrecision
-                );
+                {
+                    uint256 est = buyingAmount.mulDiv(
+                        _currentEpoch.priceArray[token],
+                        priceAdapterPrecision
+                    );
+                    buyingEstimatedUnderlyingAmounts[buyingIndex] = UtilitiesLib.convertDecimals(
+                        est,
+                        config.getTokenDecimals(token),
+                        underlyingDecimals
+                    );
+                }

Also applies to: 886-894


910-916: updateBufferAmount can underflow on negative delta (re‑raise).

Subtracting uint256(-deltaAmount) without a bound check can revert. Guard explicitly.

 function updateBufferAmount(int256 deltaAmount) external onlyLiquidityOrchestrator {
   if (deltaAmount > 0) {
     bufferAmount += uint256(deltaAmount);
   } else if (deltaAmount < 0) {
-    bufferAmount -= uint256(-deltaAmount);
+    uint256 dec = uint256(-deltaAmount);
+    if (dec > bufferAmount) revert ErrorsLib.InsufficientAmount();
+    bufferAmount -= dec;
   }
 }
🧹 Nitpick comments (6)
contracts/orchestrators/InternalStatesOrchestrator.sol (6)

393-401: Add zero‑price guard before using price.

Division by price elsewhere and economic correctness both require price > 0.

                 uint256 price = _currentEpoch.priceArray[token];
+                if (price == 0) revert ErrorsLib.InvalidArguments();

715-721: Add zero‑price guard in post‑processing too.

Protects allocation math and avoids division by zero.

                 uint256 price = _currentEpoch.priceArray[token];
+                if (price == 0) revert ErrorsLib.InvalidArguments();

872-895: Conservative rounding for min/max constraints.

For estimated underlying amounts: sells should bias down (floor), buys bias up (ceil) to be protective under slippage. UtilitiesLib.convertDecimals likely floors; consider ceiling for buys or adding +1 when decimals shrink.

Would you like me to propose a small helper to apply ceiling on decimal conversion for buy estimates?


366-371: Minor: simplify boundary checks.

if (i1 > len || i1 == len) can be if (i1 >= len) in four places.

Also applies to: 449-451, 687-692, 743-752


119-125: Counters and loop index widths may be too tight.

  • epochCounter is uint16; long‑running deployments can overflow.
  • Several loops use uint16. If tokens/vaults ever exceed 65,535, indices break.

Consider uint32 for counters/indices.

Also applies to: 788-791


898-902: Optional: price fallback.

getPriceOf returns 0 for unseen tokens in the current epoch. Consider falling back to the registry for convenience, or document the 0 behavior explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1c5cac and 2401e31.

📒 Files selected for processing (1)
  • contracts/orchestrators/InternalStatesOrchestrator.sol (12 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build, Lint and Test
  • GitHub Check: Sourcery review
🔇 Additional comments (1)
contracts/orchestrators/InternalStatesOrchestrator.sol (1)

507-534: Confirm FHE handling for invalid intents.

You request decryption for all nVaults, but earlier you skip invalid intents. For those entries, encryptedVaultsTotalAssets[vault] may be default‑zero; ensure FHE.allowThis/FHE.toBytes32 on such values is supported by the FHE lib and won’t revert or taint proofs.

If unsupported, build cipherTexts only for valid intents and track indices, or initialize mapping entries to _ezero when skipping.

@coderabbitai coderabbitai Bot mentioned this pull request Oct 2, 2025
Merged
This was referenced Oct 16, 2025
Merged
Merged
@coderabbitai coderabbitai Bot mentioned this pull request Nov 18, 2025
Merged
This was referenced Dec 15, 2025
Merged
@coderabbitai coderabbitai Bot mentioned this pull request Jan 21, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant