Skip to content
Merged

Dev #242

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ jobs:

steps:
- name: "Check out the repo"
uses: actions/checkout@v6
uses: actions/checkout@v7
Comment thread
matteoettam09 marked this conversation as resolved.
with:
submodules: recursive

- name: "Install Pnpm"
uses: pnpm/action-setup@v6
with:
version: "10"

- name: "Setup Node.js"
uses: actions/setup-node@v6
Expand Down Expand Up @@ -68,14 +66,12 @@ jobs:

steps:
- name: "Check out the repo"
uses: "actions/checkout@v6"
uses: "actions/checkout@v7"
with:
submodules: recursive

- name: "Install Pnpm"
uses: pnpm/action-setup@v6
with:
version: "10"

- name: "Setup Node.js"
uses: actions/setup-node@v6
Expand Down Expand Up @@ -109,14 +105,12 @@ jobs:

steps:
- name: "Check out the repo"
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
submodules: recursive

- name: "Install Pnpm"
uses: pnpm/action-setup@v6
with:
version: "10"

- name: "Setup Node.js"
uses: actions/setup-node@v6
Expand Down
20 changes: 11 additions & 9 deletions contracts/LiquidityOrchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* - Handling slippage and market execution differences from adapter price estimates via liquidity buffer.
* @custom:security-contact security@orionfinance.ai
*/
contract LiquidityOrchestrator is

Check warning on line 34 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Contract has 31 states declarations but allowed no more than 15
Initializable,
Ownable2StepUpgradeable,
ReentrancyGuardTransient,
Expand Down Expand Up @@ -112,7 +112,7 @@
uint256 public pendingProtocolFees;

/// @notice Tokens that failed during the current epoch's sell/buy execution (cleared at epoch end)
address[] private _failedEpochTokens;
address[] internal _failedEpochTokens;

/// @notice Cached assets hash from last full commitment build
bytes32 private _cachedAssetsHash;
Expand Down Expand Up @@ -159,6 +159,9 @@
/// @notice On-chain resume cursor for the active sell/buy minibatch window.
uint16 public completedInCurrentMinibatch;

/// @notice Buffer snapshot at BuyingLeg entry (after bufferIncrease apply) [assets]
uint256 public buyingLegEntryBuffer;

/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -474,25 +477,23 @@
} else if (currentPhase == LiquidityUpkeepPhase.SellingLeg) {
StatesStruct memory states = _verifyPerformData(_publicValues, proofBytes, statesBytes);

if (currentMinibatchIndex == 0) {
bufferAmount = states.bufferAmount;
_processMinibatchSell(states.sellLeg);
if (currentPhase == LiquidityUpkeepPhase.BuyingLeg) {
bufferAmount += states.bufferIncrease;
_pendingEpochProtocolFees = states.epochProtocolFees;
buyingLegEntryBuffer = bufferAmount;
}

_processMinibatchSell(states.sellLeg);
} else if (currentPhase == LiquidityUpkeepPhase.BuyingLeg) {
StatesStruct memory states = _verifyPerformData(_publicValues, proofBytes, statesBytes);
_processMinibatchBuy(states.buyLeg);
} else if (currentPhase == LiquidityUpkeepPhase.ProcessVaultOperations) {
StatesStruct memory states = _verifyPerformData(_publicValues, proofBytes, statesBytes);
_processMinibatchVaultsOperations(states.vaults);

if (currentMinibatchIndex == 0) {
// After the final minibatch, currentMinibatchIndex resets to 0, triggering epoch end
if (currentPhase == LiquidityUpkeepPhase.Idle) {
address[] memory failedTokens = _failedEpochTokens;
delete _failedEpochTokens;
config.completeAssetsRemoval(failedTokens);
// Emit epoch end and increment epoch counter.
emit EventsLib.EpochEnd(epochCounter, states.nettedRebalanceVolumeUnderlying);
++epochCounter;
}
Expand Down Expand Up @@ -524,6 +525,7 @@

// Freeze deterministic proof-input anchor at epoch start.
initialEpochBufferAmount = bufferAmount;
buyingLegEntryBuffer = 0;

// Reset incremental commitment state for the new epoch
_partialVaultsHash = bytes32(0);
Expand Down Expand Up @@ -562,7 +564,7 @@
}

/// @notice Folds the next batch of vault leaves into the running accumulator.
function _processCommitmentMinibatch() internal {

Check warning on line 567 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Function body contains 59 lines but allowed no more than 50 lines
uint16 vaultCount = uint16(_currentEpoch.vaultsEpoch.length);
uint256 maxFulfillBatchSize = config.maxFulfillBatchSize();

Expand Down Expand Up @@ -721,7 +723,7 @@
/// @param estimatedUnderlyingAmounts Leg underlying estimates
/// @param isSell True for sell leg, false for buy leg
// slither-disable-next-line reentrancy-no-eth
function _processMinibatchLeg(

Check warning on line 726 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Function body contains 54 lines but allowed no more than 50 lines
address[] memory tokens,
uint256[] memory amounts,
uint256[] memory estimatedUnderlyingAmounts,
Expand All @@ -736,7 +738,7 @@
}

uint16 start = i0 + completedInCurrentMinibatch;
if (start >= i1) {

Check warning on line 741 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

GC: Non strict inequality found. Try converting to a strict one
completedInCurrentMinibatch = 0;
++currentMinibatchIndex;
_finalizeMinibatchLeg(isSell, i1 == tokenCount);
Expand Down Expand Up @@ -779,7 +781,7 @@
}

/// @notice Records a failed minibatch leg and refreshes the epoch commitment without advancing the minibatch index
function _handleMinibatchLegFailure(address token) internal {

Check warning on line 784 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Mismatch in @param names for function '_handleMinibatchLegFailure'. Expected: [token], Found: []

Check warning on line 784 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Missing @param tag in function '_handleMinibatchLegFailure'
_failedEpochTokens.push(token);
_currentEpoch.epochStateCommitment = keccak256(
abi.encode(_buildProtocolStateHash(), _cachedAssetsHash, _cachedVaultsHash)
Expand All @@ -788,7 +790,7 @@
}

/// @notice Applies phase transitions after a minibatch window completes successfully
function _finalizeMinibatchLeg(bool isSell, bool legFinished) internal {

Check warning on line 793 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Mismatch in @param names for function '_finalizeMinibatchLeg'. Expected: [isSell, legFinished], Found: []

Check warning on line 793 in contracts/LiquidityOrchestrator.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Missing @param tag in function '_finalizeMinibatchLeg'
if (!legFinished) {
return;
}
Expand Down Expand Up @@ -1006,5 +1008,5 @@
}

/// @dev Storage gap to allow for future upgrades
uint256[46] private __gap;
uint256[45] private __gap;
}
5 changes: 0 additions & 5 deletions contracts/OrionConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* @author Orion Finance
* @custom:security-contact security@orionfinance.ai
*/
contract OrionConfig is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable, IOrionConfig {

Check warning on line 33 in contracts/OrionConfig.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Contract has 26 states declarations but allowed no more than 15
using EnumerableSet for EnumerableSet.AddressSet;

/// @notice Guardian address for emergency pausing
Expand Down Expand Up @@ -534,11 +534,6 @@
ILiquidityOrchestrator.LiquidityUpkeepPhase.Idle;
}

/// @inheritdoc IOrionConfig
function getTokenDecimals(address token) external view returns (uint8) {
return tokenDecimals[token];
}

/// @notice Sets the upgrade timelock address.
/// @dev If no timelock is set yet, only the owner may call this. Once a timelock is active,
/// only the timelock itself may replace it, preventing the owner from bypassing the delay.
Expand Down
2 changes: 1 addition & 1 deletion contracts/access_controllers/WhitelistAccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract WhitelistAccessControl is IOrionAccessControl, Ownable2Step {
constructor(address initialOwner_) Ownable(initialOwner_) {}

/// @inheritdoc IOrionAccessControl
function canRequestDeposit(address sender) external view override returns (bool) {
function canRequestDeposit(address sender, bytes calldata) external view override returns (bool) {
return whitelist[sender];
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/execution/ERC4626ExecutionAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ contract ERC4626ExecutionAdapter is IExecutionAdapter {
try IERC4626(asset).asset() returns (address vaultUnderlying) {
// 2. Verify registered vault decimals match config decimals
try IERC20Metadata(asset).decimals() returns (uint8 vaultDecimals) {
if (vaultDecimals != CONFIG.getTokenDecimals(asset)) {
if (vaultDecimals != CONFIG.tokenDecimals(asset)) {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
Expand All @@ -69,7 +69,7 @@ contract ERC4626ExecutionAdapter is IExecutionAdapter {
// 3. Verify underlying vault decimals match config decimals
// (vault underlying must be whitelisted in config)
try IERC20Metadata(vaultUnderlying).decimals() returns (uint8 vaultUnderlyingDecimals) {
if (vaultUnderlyingDecimals != CONFIG.getTokenDecimals(vaultUnderlying)) {
if (vaultUnderlyingDecimals != CONFIG.tokenDecimals(vaultUnderlying)) {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
Expand Down
6 changes: 5 additions & 1 deletion contracts/interfaces/ILiquidityOrchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface ILiquidityOrchestrator {
VaultState[] vaults;
SellLegOrders sellLeg;
BuyLegOrders buyLeg;
uint256 bufferAmount;
uint256 bufferIncrease;
uint256 epochProtocolFees;
uint256 nettedRebalanceVolumeUnderlying;
}
Expand Down Expand Up @@ -81,6 +81,10 @@ interface ILiquidityOrchestrator {
/// @return The initial epoch buffer amount
function initialEpochBufferAmount() external view returns (uint256);

/// @notice Returns the BuyingLeg-entry buffer snapshot (after sell→buy bufferIncrease apply)
/// @return The BuyingLeg entry buffer amount
function buyingLegEntryBuffer() external view returns (uint256);

/// @notice Returns the pending protocol fees
/// @return The pending protocol fees
function pendingProtocolFees() external view returns (uint256);
Expand Down
9 changes: 5 additions & 4 deletions contracts/interfaces/IOrionAccessControl.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ pragma solidity ^0.8.34;
*/
interface IOrionAccessControl {
/**
* @notice Check if an address is allowed to request deposits to the vault
* @param sender Address attempting to deposit
* @return True if sender is allowed to deposit, false otherwise
* @notice Check if a deposit request is allowed
* @param sender The address of the sender of the deposit request
* @param data The data of the deposit request
* @return True if the deposit request is allowed, false otherwise
*/
function canRequestDeposit(address sender) external view returns (bool);
function canRequestDeposit(address sender, bytes calldata data) external view returns (bool);
}
7 changes: 3 additions & 4 deletions contracts/interfaces/IOrionConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,10 @@ interface IOrionConfig {
/// @return True if the system is idle, false otherwise
function isSystemIdle() external view returns (bool);

/// @notice Returns the number of decimals for a given token
/// @dev This function returns the stored decimals for whitelisted tokens
/// @param token The address of the token
/// @notice Returns the stored decimals for a whitelisted token
/// @param token The token address
/// @return The number of decimals for the token
function getTokenDecimals(address token) external view returns (uint8);
function tokenDecimals(address token) external view returns (uint8);

/// @notice Returns the minimum deposit amount
/// @return The minimum deposit amount in underlying asset units
Expand Down
4 changes: 2 additions & 2 deletions contracts/price/ERC4626PriceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ contract ERC4626PriceAdapter is IPriceAdapter {
uint256 totalSupply = vault.totalSupply();

if (totalSupply == 0) {
return (0, PRICE_DECIMALS + CONFIG.getTokenDecimals(vaultUnderlying));
return (0, PRICE_DECIMALS + CONFIG.tokenDecimals(vaultUnderlying));
Comment thread
matteoettam09 marked this conversation as resolved.
}

uint8 effectiveShareDecimals = _effectiveShareDecimals(totalAssets, totalSupply, vaultAssetDecimals);
Expand All @@ -81,7 +81,7 @@ contract ERC4626PriceAdapter is IPriceAdapter {
10 ** CONFIG.priceAdapterDecimals()
);

return (vaultPrice, PRICE_DECIMALS + CONFIG.getTokenDecimals(vaultUnderlying));
return (vaultPrice, PRICE_DECIMALS + CONFIG.tokenDecimals(vaultUnderlying));
}

/// @notice Resolves share scale for pricing when reported vault decimals understate per-share value.
Expand Down
75 changes: 75 additions & 0 deletions contracts/test/LiquidityOrchestratorHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.34;

import { LiquidityOrchestrator } from "../LiquidityOrchestrator.sol";
import { EventsLib } from "../libraries/EventsLib.sol";

/**
* @title LiquidityOrchestratorHarness
Expand Down Expand Up @@ -39,4 +40,78 @@ contract LiquidityOrchestratorHarness is LiquidityOrchestrator {
shares
);
}

/// @notice Test-only: set upkeep phase
function h_setPhase(LiquidityUpkeepPhase phase) external {
currentPhase = phase;
}

/// @notice Test-only: set PVO/sell/buy minibatch index
function h_setCurrentMinibatchIndex(uint8 index) external {
currentMinibatchIndex = index;
}

/// @notice Test-only: set fulfill minibatch size (bypasses idle/owner checks)
function h_setMinibatchSize(uint8 size) external {
minibatchSize = size;
}

/// @notice Test-only: replace vaultsEpoch for the current epoch
function h_setVaultsEpoch(address[] calldata vaults) external {
delete _currentEpoch.vaultsEpoch;
for (uint256 i = 0; i < vaults.length; ++i) {
_currentEpoch.vaultsEpoch.push(vaults[i]);
}
}

/// @notice Test-only: run PVO minibatch + epoch-end gate (mirrors performUpkeep PVO branch, no ZK verify)
/// @param vaults Vault states aligned with vaultsEpoch indices
/// @param nettedRebalanceVolumeUnderlying Passed through to EpochEnd when completing
function h_processPvoMinibatchWithEpochEnd(
VaultState[] memory vaults,
uint256 nettedRebalanceVolumeUnderlying
) external {
_processMinibatchVaultsOperations(vaults);
_maybeEpochEndAfterPvo(nettedRebalanceVolumeUnderlying);
}

/**
* @notice Test-only: advance PVO minibatch index using the same completion predicate as
* `_processMinibatchVaultsOperations`, without vault I/O (avoids gas caps for wrap tests).
* @dev Mirrors: i0/i1 from currentMinibatchIndex*minibatchSize, ++index (uint8 wrap), Idle iff
* i1 >= vaultsEpochLength. Then applies the Idle epoch-end gate.
* @param vaultsEpochLength Simulated `_currentEpoch.vaultsEpoch.length`
* @param nettedRebalanceVolumeUnderlying Passed through to EpochEnd when completing
*/
function h_advancePvoIndexLikeProcessMinibatch(
uint256 vaultsEpochLength,
uint256 nettedRebalanceVolumeUnderlying
) external {
uint16 i0 = uint16(currentMinibatchIndex) * uint16(minibatchSize);
uint16 i1 = i0 + uint16(minibatchSize);
// Production uses checked ++ (panic at 255); wrap this in unchecked so the test can
// assert the Idle gate ignores a uint8 wrap mid-PVO (the old `index == 0` footgun).
unchecked {
++currentMinibatchIndex;
}
Comment thread
matteoettam09 marked this conversation as resolved.

if (i1 > vaultsEpochLength || i1 == vaultsEpochLength) {
currentPhase = LiquidityUpkeepPhase.Idle;
currentMinibatchIndex = 0;
completedInCurrentMinibatch = 0;
// Skip _nextUpdateTime (private); not required for epoch-end gate assertions
}

_maybeEpochEndAfterPvo(nettedRebalanceVolumeUnderlying);
}

function _maybeEpochEndAfterPvo(uint256 nettedRebalanceVolumeUnderlying) private {
if (currentPhase == LiquidityUpkeepPhase.Idle) {
address[] memory failedTokens = _failedEpochTokens;
delete _failedEpochTokens;
config.completeAssetsRemoval(failedTokens);
emit EventsLib.EpochEnd(epochCounter, nettedRebalanceVolumeUnderlying);
++epochCounter;
}
}
}
2 changes: 1 addition & 1 deletion contracts/test/MockERC4626PriceAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ contract MockERC4626PriceAdapter is IPriceAdapter {

// 3. Verify vault decimals are registered in config
try IERC20Metadata(asset).decimals() returns (uint8 decimals) {
if (decimals != config.getTokenDecimals(asset)) {
if (decimals != config.tokenDecimals(asset)) {
revert ErrorsLib.InvalidAdapter(asset);
}
} catch {
Expand Down
14 changes: 1 addition & 13 deletions contracts/test/MockOrionConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ contract MockOrionConfig {
address public liquidityOrchestrator;
address public priceAdapterRegistryAddress;
uint256 public slippageTolerance = 200; // 2% in basis points
mapping(address => uint8) private tokenDecimals;
mapping(address => uint8) public tokenDecimals;
mapping(address => bool) private whitelisted;
/// @dev When true, return 0 for unset tokens (simulates real OrionConfig where unwhitelisted tokens have no entry)
bool public returnZeroForUnsetTokens;
/// @dev Mirrors OrionConfig `isSystemIdle` for adapter tests; default true (idle).
bool public systemIdle = true;

Expand All @@ -42,12 +40,6 @@ contract MockOrionConfig {
return 14; // Protocol standard for price adapter decimals
}

function getTokenDecimals(address token) external view returns (uint8) {
uint8 decimals = tokenDecimals[token];
if (returnZeroForUnsetTokens && decimals == 0) return 0;
return decimals == 0 ? 18 : decimals; // Default to 18 if not set
}

// Mock helpers for testing
function setSlippageTolerance(uint256 _tolerance) external {
slippageTolerance = _tolerance;
Expand All @@ -73,10 +65,6 @@ contract MockOrionConfig {
whitelisted[asset] = _whitelisted;
}

function setReturnZeroForUnsetTokens(bool _returnZero) external {
returnZeroForUnsetTokens = _returnZero;
}

function setGuardian(address _guardian) external {
guardian = _guardian;
}
Expand Down
4 changes: 2 additions & 2 deletions contracts/vaults/OrionVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
if (!config.isSystemIdle()) return 0;
if (isDecommissioning || config.isDecommissionedVault(address(this))) return 0;
if (depositAccessControl != address(0)) {
if (!IOrionAccessControl(depositAccessControl).canRequestDeposit(receiver)) return 0;
if (!IOrionAccessControl(depositAccessControl).canRequestDeposit(receiver, "")) return 0;
}
return type(uint256).max;
}
Expand Down Expand Up @@ -316,7 +316,7 @@
/// @inheritdoc IOrionVault
function requestDeposit(uint256 assets) external nonReentrant {
if (depositAccessControl != address(0)) {
if (!IOrionAccessControl(depositAccessControl).canRequestDeposit(msg.sender))
if (!IOrionAccessControl(depositAccessControl).canRequestDeposit(msg.sender, msg.data))
revert ErrorsLib.DepositNotAllowed();
}

Expand Down Expand Up @@ -437,7 +437,7 @@
if (supported) {
IOrionStrategist(strategist_).setVault(address(this));
}
} catch {}

Check warning on line 440 in contracts/vaults/OrionVault.sol

View workflow job for this annotation

GitHub Actions / Build, Lint and Test

Code contains empty blocks
}

/// @inheritdoc IOrionVault
Expand Down
Loading