Skip to content
Merged

Dev #231

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
158 changes: 97 additions & 61 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 30 states declarations but allowed no more than 15
Initializable,
Ownable2StepUpgradeable,
ReentrancyGuardTransient,
Expand Down Expand Up @@ -156,6 +156,9 @@
/// @notice Number of vault leaves already folded this epoch
uint16 private _commitmentBatchIndex;

/// @notice On-chain resume cursor for the active sell/buy minibatch window.
uint16 public completedInCurrentMinibatch;

/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -238,14 +241,6 @@
_nextUpdateTime = block.timestamp + epochDuration;
}

/// @dev Must be called via upgradeToAndCall so migration is atomic with the implementation swap.
// solhint-disable-next-line use-natspec
function initializeV2() external reinitializer(2) onlyOwner {
if (commitmentMinibatchSize == 0) {
commitmentMinibatchSize = 1;
}
}

/* -------------------------------------------------------------------------- */
/* OWNER FUNCTIONS */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -276,7 +271,7 @@
/// @inheritdoc ILiquidityOrchestrator
function updateCommitmentMinibatchSize(uint8 _commitmentMinibatchSize) external onlyOwnerOrGuardian {
if (_commitmentMinibatchSize == 0) revert ErrorsLib.InvalidArguments();
if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
if (commitmentMinibatchSize != 0 && !config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
commitmentMinibatchSize = _commitmentMinibatchSize;
}

Expand All @@ -291,15 +286,13 @@
/// @inheritdoc ILiquidityOrchestrator
function updateVerifier(address newVerifier) external onlyOwner {
if (newVerifier == address(0)) revert ErrorsLib.ZeroAddress();
if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
verifier = ISP1Verifier(newVerifier);
emit EventsLib.SP1VerifierUpdated(newVerifier);
}

/// @inheritdoc ILiquidityOrchestrator
function updateVKey(bytes32 newvKey) external onlyOwner {
if (newvKey == bytes32(0)) revert ErrorsLib.InvalidArguments();
if (!config.isSystemIdle()) revert ErrorsLib.SystemNotIdle();
vKey = newvKey;
emit EventsLib.VKeyUpdated(newvKey);
}
Expand Down Expand Up @@ -535,6 +528,7 @@
// Reset incremental commitment state for the new epoch
_partialVaultsHash = bytes32(0);
_commitmentBatchIndex = 0;
completedInCurrentMinibatch = 0;

currentPhase = LiquidityUpkeepPhase.StateCommitment;

Expand Down Expand Up @@ -568,7 +562,7 @@
}

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

Check warning on line 565 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 @@ -624,6 +618,7 @@
abi.encode(protocolStateHash, assetsHash, _partialVaultsHash)
);

completedInCurrentMinibatch = 0;
currentPhase = LiquidityUpkeepPhase.SellingLeg;
emit EventsLib.EpochStateCommitted(epochCounter, _currentEpoch.epochStateCommitment);
}
Expand Down Expand Up @@ -705,71 +700,108 @@

/// @notice Handles the sell action
/// @param sellLeg The sell leg orders
// slither-disable-next-line reentrancy-no-eth
function _processMinibatchSell(SellLegOrders memory sellLeg) internal {
uint16 i0 = currentMinibatchIndex * executionMinibatchSize;
uint16 i1 = i0 + executionMinibatchSize;
_processMinibatchLeg(
sellLeg.sellingTokens,
sellLeg.sellingAmounts,
sellLeg.sellingEstimatedUnderlyingAmounts,
true
);
}

if (i1 > sellLeg.sellingTokens.length || i1 == sellLeg.sellingTokens.length) {
i1 = uint16(sellLeg.sellingTokens.length);
/// @notice Handles the buy action
/// @param buyLeg The buy leg orders
function _processMinibatchBuy(BuyLegOrders memory buyLeg) internal {
_processMinibatchLeg(buyLeg.buyingTokens, buyLeg.buyingAmounts, buyLeg.buyingEstimatedUnderlyingAmounts, false);
}

/// @notice Processes the next sell or buy minibatch window with resume support
/// @param tokens Leg token addresses
/// @param amounts Leg share amounts
/// @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 724 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,
bool isSell
) internal {
uint16 batchSize = executionMinibatchSize;
uint16 i0 = currentMinibatchIndex * batchSize;
uint16 i1 = i0 + batchSize;
uint256 tokenCount = tokens.length;
if (i1 > tokenCount) {
i1 = uint16(tokenCount);
}

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

Check warning on line 739 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);
return;
}

for (uint16 i = i0; i < i1; ++i) {
address token = sellLeg.sellingTokens[i];
if (token == address(underlyingAsset)) continue;
uint256 amount = sellLeg.sellingAmounts[i];
try this._executeSell(token, amount, sellLeg.sellingEstimatedUnderlyingAmounts[i]) {
// successful execution, continue.
} catch {
_failedEpochTokens.push(token);
_currentEpoch.epochStateCommitment = keccak256(
abi.encode(_buildProtocolStateHash(), _cachedAssetsHash, _cachedVaultsHash)
);
emit EventsLib.EpochStateCommitted(epochCounter, _currentEpoch.epochStateCommitment);
return;
for (uint16 i = start; i < i1; ++i) {
address token = tokens[i];
uint256 amount = amounts[i];

if (token == address(underlyingAsset)) {
++completedInCurrentMinibatch;
continue;
}
if (amount == 0) {
++completedInCurrentMinibatch;
continue;
}

if (isSell) {
try this._executeSell(token, amount, estimatedUnderlyingAmounts[i]) {
++completedInCurrentMinibatch;
} catch {
_handleMinibatchLegFailure(token);
return;
}
} else {
try this._executeBuy(token, amount, estimatedUnderlyingAmounts[i]) {
++completedInCurrentMinibatch;
} catch {
_handleMinibatchLegFailure(token);
return;
}
}
}

completedInCurrentMinibatch = 0;
++currentMinibatchIndex;
if (i1 == sellLeg.sellingTokens.length) {
currentMinibatchIndex = 0;
currentPhase = LiquidityUpkeepPhase.BuyingLeg;
}
_finalizeMinibatchLeg(isSell, i1 == tokenCount);
}

/// @notice Handles the buy action
/// @param buyLeg The buy leg orders
// slither-disable-next-line reentrancy-no-eth
function _processMinibatchBuy(BuyLegOrders memory buyLeg) internal {
uint16 i0 = currentMinibatchIndex * executionMinibatchSize;
uint16 i1 = i0 + executionMinibatchSize;
/// @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 782 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 782 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)
);
emit EventsLib.EpochStateCommitted(epochCounter, _currentEpoch.epochStateCommitment);
}

if (i1 > buyLeg.buyingTokens.length || i1 == buyLeg.buyingTokens.length) {
i1 = uint16(buyLeg.buyingTokens.length);
/// @notice Applies phase transitions after a minibatch window completes successfully
function _finalizeMinibatchLeg(bool isSell, bool legFinished) internal {

Check warning on line 791 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 791 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;
}

for (uint16 i = i0; i < i1; ++i) {
address token = buyLeg.buyingTokens[i];
if (token == address(underlyingAsset)) continue;
uint256 amount = buyLeg.buyingAmounts[i];
try this._executeBuy(token, amount, buyLeg.buyingEstimatedUnderlyingAmounts[i]) {
// successful execution, continue.
} catch {
_failedEpochTokens.push(token);
_currentEpoch.epochStateCommitment = keccak256(
abi.encode(_buildProtocolStateHash(), _cachedAssetsHash, _cachedVaultsHash)
);
emit EventsLib.EpochStateCommitted(epochCounter, _currentEpoch.epochStateCommitment);
return;
}
}
currentMinibatchIndex = 0;
completedInCurrentMinibatch = 0;

++currentMinibatchIndex;
if (i1 == buyLeg.buyingTokens.length) {
if (isSell) {
currentPhase = LiquidityUpkeepPhase.BuyingLeg;
} else {
pendingProtocolFees += _pendingEpochProtocolFees;
emit EventsLib.ProtocolFeesAccrued(_pendingEpochProtocolFees);
_pendingEpochProtocolFees = 0;
currentMinibatchIndex = 0;
currentPhase = LiquidityUpkeepPhase.ProcessVaultOperations;
}
}
Expand Down Expand Up @@ -874,6 +906,7 @@
i1 = uint16(vaultsEpoch.length);
currentPhase = LiquidityUpkeepPhase.Idle;
currentMinibatchIndex = 0;
completedInCurrentMinibatch = 0;
_nextUpdateTime = block.timestamp + epochDuration;
}

Expand All @@ -883,6 +916,7 @@

_processSingleVaultOperations(
vaultAddress,
vaultState.processRedeem,
vaultState.totalAssetsForDeposit,
vaultState.totalAssetsForRedeem,
vaultState.finalTotalAssets,
Expand All @@ -896,6 +930,7 @@

/// @notice Processes deposit and redeem operations for a single vault
/// @param vaultAddress The vault address
/// @param processRedeem When false, redeem fulfillment is skipped even if pending requests exist
/// @param totalAssetsForDeposit The total assets for deposit operations
/// @param totalAssetsForRedeem The total assets for redeem operations
/// @param finalTotalAssets The final total assets for the vault
Expand All @@ -905,6 +940,7 @@
/// @param shares The portfolio token number of shares
function _processSingleVaultOperations(
address vaultAddress,
bool processRedeem,
uint256 totalAssetsForDeposit,
uint256 totalAssetsForRedeem,
uint256 finalTotalAssets,
Expand All @@ -919,7 +955,7 @@
uint256 pendingRedeem = vaultContract.pendingRedeem(maxFulfillBatchSize);
uint256 pendingDeposit = vaultContract.pendingDeposit(maxFulfillBatchSize);

if (pendingRedeem > 0) {
if (processRedeem && pendingRedeem > 0) {
vaultContract.fulfillRedeem(totalAssetsForRedeem);
}

Expand Down Expand Up @@ -970,5 +1006,5 @@
}

/// @dev Storage gap to allow for future upgrades
uint256[47] private __gap;
uint256[46] private __gap;
}
29 changes: 10 additions & 19 deletions contracts/interfaces/ILiquidityOrchestrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,6 @@ interface ILiquidityOrchestrator {
ProcessVaultOperations
}

/// @notice Struct to hold vault state data
struct VaultStateData {
uint8[] feeTypes;
uint16[] performanceFees;
uint16[] managementFees;
uint256[] highWaterMarks;
uint256[] pendingRedeems;
uint256[] pendingDeposits;
uint256[] totalSupplies;
uint256[] totalAssets;
address[][] portfolioTokens;
uint256[][] portfolioShares;
address[][] intentTokens;
uint32[][] intentWeights;
}

struct PublicValuesStruct {
/// @notice Input state commitments
bytes32 inputCommitment;
Expand All @@ -51,6 +35,7 @@ interface ILiquidityOrchestrator {
}

struct VaultState {
bool processRedeem;
uint256 totalAssetsForRedeem;
uint256 totalAssetsForDeposit;
uint256 finalTotalAssets;
Expand All @@ -76,6 +61,10 @@ interface ILiquidityOrchestrator {
/// @return The current LiquidityUpkeepPhase
function currentPhase() external view returns (LiquidityUpkeepPhase);

/// @notice Returns how many leg slots were already processed in the active minibatch window
/// @return Leg slots completed since `currentMinibatchIndex` was set
function completedInCurrentMinibatch() external view returns (uint16);

/// @notice Returns the target buffer ratio
/// @return The target buffer ratio
function targetBufferRatio() external view returns (uint256);
Expand Down Expand Up @@ -150,13 +139,15 @@ interface ILiquidityOrchestrator {
function updateAutomationRegistry(address newAutomationRegistry) external;

/// @notice Updates the verifier contract address
/// @dev Only the owner may call this. Reverts with SystemNotIdle if an epoch is in progress,
/// ensuring no mid-epoch proof verification uses a mismatched verifier.
/// @dev Callable mid-epoch for maintenance. The owner must coordinate with the zk-orchestrator so
/// proofs submitted after this change are verified by the new contract.
/// @param newVerifier The address of the new verifier contract
function updateVerifier(address newVerifier) external;

/// @notice Updates the internal state orchestrator verification key
/// @dev Reverts with InvalidArguments if newvKey is bytes32(0).
/// @dev Callable mid-epoch for maintenance. Reverts with InvalidArguments if newvKey is bytes32(0).
/// The owner must coordinate with the zk-orchestrator so proofs submitted after this change
/// match the new verification key and current epoch commitments.
/// @param newvKey The new verification key
function updateVKey(bytes32 newvKey) external;

Expand Down
4 changes: 3 additions & 1 deletion contracts/test/LiquidityOrchestratorHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LiquidityOrchestrator } from "../LiquidityOrchestrator.sol";

/**
* @title LiquidityOrchestratorHarness
* @notice Test harness that exposes internal slippage helper functions for direct testing
* @notice Test harness that exposes internal helper functions for direct testing
*/
contract LiquidityOrchestratorHarness is LiquidityOrchestrator {
function exposed_calculateMaxWithSlippage(uint256 estimatedAmount) external view returns (uint256) {
Expand All @@ -18,6 +18,7 @@ contract LiquidityOrchestratorHarness is LiquidityOrchestrator {

function exposed_processSingleVaultOperations(
address vaultAddress,
bool processRedeem,
uint256 totalAssetsForDeposit,
uint256 totalAssetsForRedeem,
uint256 finalTotalAssets,
Expand All @@ -28,6 +29,7 @@ contract LiquidityOrchestratorHarness is LiquidityOrchestrator {
) external {
_processSingleVaultOperations(
vaultAddress,
processRedeem,
totalAssetsForDeposit,
totalAssetsForRedeem,
finalTotalAssets,
Expand Down
29 changes: 0 additions & 29 deletions contracts/test/MockERC4626WithSettableDecimals.sol

This file was deleted.

10 changes: 0 additions & 10 deletions contracts/test/MockNoDecimalsAsset.sol

This file was deleted.

Loading
Loading