// SPDX-License-Identifier: Unlicense pragma solidity 0.8.35; import { IWeth9 } from './interfaces/IWeth9.sol'; import { OpenOracle } from './openOracle/OpenOracle.sol'; import { ReputationToken } from '../ReputationToken.sol'; import { ISecurityPool } from './interfaces/ISecurityPool.sol'; import { SecurityPoolUtils } from './SecurityPoolUtils.sol'; // price oracle uint256 constant PRICE_VALID_FOR_SECONDS = 5 minutes; uint256 constant PRICE_PRECISION = 1e18; uint256 constant MAX_OPERATION_VALID_FOR_SECONDS = 5 minutes; enum OperationType { Liquidation, WithdrawRep, SetSecurityBondsAllowance } struct StagedOperation { OperationType operation; address initiatorVault; address targetVault; uint256 amount; uint256 queuedAt; uint256 validForSeconds; uint256 snapshotTargetOwnership; uint256 snapshotTargetAllowance; uint256 snapshotTotalRep; uint256 snapshotDenominator; } contract OpenOraclePriceCoordinator { uint256 public constant MAX_PENDING_SETTLEMENT_OPERATIONS = 4; string private constant STAGED_OPERATION_EXECUTION_OK = ''; string private constant STAGED_OPERATION_ERROR_EXPIRED = 'staged operation expired'; string private constant STAGED_OPERATION_ERROR_STALE_LIQUIDATION = 'stale liquidation'; string private constant STAGED_OPERATION_ERROR_ZERO_WITHDRAW = 'withdraw amount has no effect'; string private constant STAGED_OPERATION_ERROR_MIN_LIQUIDATION_DISTANCE = 'liquidation too close to threshold'; string private constant STAGED_OPERATION_ERROR_PANIC = 'Panic'; string private constant STAGED_OPERATION_ERROR_UNKNOWN = 'Unknown error'; uint256 public pendingReportId; address public pendingReportSponsor; uint256 public pendingOperationSlotId; uint256 public lastSettlementTimestamp; uint256 public lastPrice; // (REP * PRICE_PRECISION) / ETH; ReputationToken immutable reputationToken; ISecurityPool public securityPool; OpenOracle public immutable openOracle; IWeth9 public immutable weth; uint256 public immutable gasConsumedOpenOracleReportPrice; uint32 public immutable gasConsumedSettlement; uint256 public immutable exactToken1Report; uint48 public immutable settlementTime; uint24 public immutable disputeDelay; uint24 public immutable protocolFee; uint24 public immutable feePercentage; uint16 public immutable multiplier; bool public immutable timeType; bool public immutable trackDisputes; address public immutable protocolFeeRecipient; uint256 public immutable escalationHaltMultiplierBps; uint256 public immutable maxSettlementBaseFeeMultiplierBps; uint256 public immutable minLiquidationPriceDistanceBps; uint256 public pendingReportMaxSettlementBaseFee; event SecurityPoolSet(ISecurityPool securityPool); event RepEthPriceSet(uint256 price); event PriceRequested(uint256 reportId, uint256 pendingReportMaxSettlementBaseFee); event PriceReportRejected( uint256 reportId, string reason, uint256 pendingReportId, uint256 pendingReportMaxSettlementBaseFee, uint256 lastPrice, uint256 lastSettlementTimestamp ); event PriceReported(uint256 reportId, uint256 price, uint256 lastSettlementTimestamp); event PendingReportRecovered( uint256 reportId, uint256 settlementTimestamp, uint256 pendingReportId, uint256 pendingReportMaxSettlementBaseFee, uint256 lastPrice, uint256 lastSettlementTimestamp ); event PendingOperationRecoveryConsumed(uint256 operationId, OperationType operation); event StagedOperationQueued( uint256 operationId, OperationType operation, address initiatorVault, address targetVault, uint256 amount, uint256 queuedAt, uint256 validForSeconds, uint256 snapshotTargetOwnership, uint256 snapshotTargetAllowance, uint256 snapshotTotalRep, uint256 snapshotDenominator, bool isPendingSlot ); event ExecutedStagedOperation(uint256 operationId, OperationType operation, bool success, string errorMessage); // This is not a FIFO queue. We keep append-only operation records plus a bounded // pending settlement list that auto-executes once a fresh oracle price arrives. // Active-operation paging is newest-first so UI previews remain stable after // execution removes older entries from the set. uint256 public stagedOperationCounter; mapping(uint256 => StagedOperation) public stagedOperations; uint256 private activeStagedOperationCount; uint256 private latestActiveStagedOperationId; mapping(uint256 => uint256) private olderActiveStagedOperationIds; mapping(uint256 => uint256) private newerActiveStagedOperationIds; mapping(uint256 => bool) private isActiveStagedOperation; uint256[] private pendingSettlementOperationIds; constructor( OpenOracle _openOracle, ReputationToken _reputationToken, IWeth9 _weth, uint256 _gasConsumedOpenOracleReportPrice, uint32 _gasConsumedSettlement, uint256 _exactToken1Report, uint48 _settlementTime, uint24 _disputeDelay, uint24 _protocolFee, uint24 _feePercentage, uint16 _multiplier, bool _timeType, bool _trackDisputes, address _protocolFeeRecipient, uint256 _escalationHaltMultiplierBps, uint256 _maxSettlementBaseFeeMultiplierBps, uint256 _minLiquidationPriceDistanceBps ) { reputationToken = _reputationToken; openOracle = _openOracle; weth = _weth; gasConsumedOpenOracleReportPrice = _gasConsumedOpenOracleReportPrice; gasConsumedSettlement = _gasConsumedSettlement; exactToken1Report = _exactToken1Report; settlementTime = _settlementTime; disputeDelay = _disputeDelay; protocolFee = _protocolFee; feePercentage = _feePercentage; multiplier = _multiplier; timeType = _timeType; trackDisputes = _trackDisputes; protocolFeeRecipient = _protocolFeeRecipient; require(_escalationHaltMultiplierBps > 0, 'Escalation halt multiplier must be greater than zero'); escalationHaltMultiplierBps = _escalationHaltMultiplierBps; require( _maxSettlementBaseFeeMultiplierBps >= SecurityPoolUtils.BPS_DENOMINATOR, 'Max settlement base fee multiplier must be at least one hundred percent' ); require( _minLiquidationPriceDistanceBps <= SecurityPoolUtils.BPS_DENOMINATOR, 'Minimum liquidation price distance cannot exceed one hundred percent' ); maxSettlementBaseFeeMultiplierBps = _maxSettlementBaseFeeMultiplierBps; minLiquidationPriceDistanceBps = _minLiquidationPriceDistanceBps; } function setSecurityPool(ISecurityPool _securityPool) public { require(address(securityPool) == address(0x0), 'Security pool has already been set on the oracle coordinator'); securityPool = _securityPool; emit SecurityPoolSet(securityPool); } function setRepEthPrice(uint256 _lastPrice) public { require(msg.sender == address(securityPool), 'Only the security pool can seed the REP/ETH price'); lastPrice = _lastPrice; emit RepEthPriceSet(lastPrice); } function getRequestPriceEthCost() public view returns (uint256) { uint256 ethCost = block.basefee * 4 * (getSettlementCallbackGasLimit() + gasConsumedOpenOracleReportPrice) + 101; return ethCost; } function getQueuedOperationEthCost() public pure returns (uint256) { return 0; } function getSettlementCallbackGasLimit() public view returns (uint32) { uint256 callbackGasLimit = uint256(gasConsumedSettlement) * MAX_PENDING_SETTLEMENT_OPERATIONS; require(callbackGasLimit <= type(uint32).max, 'Settlement callback gas limit exceeds uint32 maximum'); return uint32(callbackGasLimit); } function requestPrice() public payable { uint256 ethCost = getRequestPriceEthCost(); require(msg.value >= ethCost, 'ETH bounty is too small to request a fresh oracle price'); _requestPrice(msg.sender, ethCost); uint256 excess = msg.value - ethCost; if (excess > 0) { (bool sent, ) = payable(msg.sender).call{ value: excess }(''); require(sent, 'Oracle coordinator failed to refund excess ETH bounty'); } } function _requestPrice(address sponsor, uint256 ethCost) private { require(pendingReportId == 0, 'Oracle price request is already pending'); uint256 escalationHalt = (exactToken1Report * escalationHaltMultiplierBps) / SecurityPoolUtils.BPS_DENOMINATOR; uint256 settlerReward = block.basefee * 2 * gasConsumedOpenOracleReportPrice; require(exactToken1Report <= type(uint128).max, 'Oracle exact token1 report amount exceeds uint128 maximum'); require(escalationHalt <= type(uint128).max, 'Oracle escalation halt amount exceeds uint128 maximum'); require(settlerReward <= type(uint96).max, 'Oracle settler reward exceeds uint96 maximum'); pendingReportMaxSettlementBaseFee = (block.basefee * maxSettlementBaseFeeMultiplierBps) / SecurityPoolUtils.BPS_DENOMINATOR; OpenOracle.CreateReportParams memory reportparams = OpenOracle.CreateReportParams({ exactToken1Report: uint128(exactToken1Report), escalationHalt: uint128(escalationHalt), // amount of token1 past which escalation stops but disputes can still happen settlerReward: uint96(settlerReward), // eth paid to settler in wei token1Address: address(reputationToken), // address of token1 in the oracle report instance settlementTime: settlementTime, disputeDelay: disputeDelay, protocolFee: protocolFee, token2Address: address(weth), // address of token2 in the oracle report instance callbackGasLimit: getSettlementCallbackGasLimit(), // gas the settlement callback must use feePercentage: feePercentage, multiplier: multiplier, timeType: timeType, trackDisputes: trackDisputes, callbackContract: address(this), // contract address for settle to call back into protocolFeeRecipient: protocolFeeRecipient }); pendingReportSponsor = sponsor; pendingReportId = openOracle.createReportInstance{ value: ethCost }(reportparams); emit PriceRequested(pendingReportId, pendingReportMaxSettlementBaseFee); } function recoverSettledPendingReport() public { uint256 reportId = pendingReportId; require(reportId != 0, 'No pending oracle price request can be recovered'); (, uint256 settlementTimestamp) = openOracle.getSettlementData(reportId); pendingReportId = 0; pendingReportSponsor = address(0); pendingReportMaxSettlementBaseFee = 0; _consumeRecoveredPendingOperation(); emit PendingReportRecovered( reportId, settlementTimestamp, pendingReportId, pendingReportMaxSettlementBaseFee, lastPrice, lastSettlementTimestamp ); } function _consumeRecoveredPendingOperation() private { uint256 operationId = pendingOperationSlotId; if (operationId == 0) return; pendingOperationSlotId = 0; StagedOperation memory stagedOperation = stagedOperations[operationId]; if (stagedOperation.initiatorVault == address(0)) return; _consumeStagedOperation(operationId); emit PendingOperationRecoveryConsumed(operationId, stagedOperation.operation); } function openOracleCallback( uint256 reportId, uint256 amount1, uint256 amount2, uint256, address, address ) external { require(msg.sender == address(openOracle), 'Only OpenOracle can call the security pool oracle callback'); require(reportId == pendingReportId, 'Oracle callback report id does not match the pending request'); pendingReportId = 0; pendingReportSponsor = address(0); if (block.basefee > pendingReportMaxSettlementBaseFee) { pendingReportMaxSettlementBaseFee = 0; _emitPriceReportRejected(reportId, 'Base fee too high'); return; } pendingReportMaxSettlementBaseFee = 0; if (amount1 == 0 || amount2 == 0) { _emitPriceReportRejected(reportId, 'Empty oracle settlement'); return; } uint256 price = (amount1 * PRICE_PRECISION) / amount2; if (price == 0) { _emitPriceReportRejected(reportId, 'Oracle price is zero'); return; } lastSettlementTimestamp = block.timestamp; lastPrice = price; emit PriceReported(reportId, lastPrice, lastSettlementTimestamp); if (pendingSettlementOperationIds.length != 0) { uint256[] memory operationIds = pendingSettlementOperationIds; delete pendingSettlementOperationIds; pendingOperationSlotId = 0; for (uint256 index = 0; index < operationIds.length; index++) { if (stagedOperations[operationIds[index]].initiatorVault != address(0)) { executeStagedOperation(operationIds[index]); } } } } function _emitPriceReportRejected(uint256 reportId, string memory reason) private { emit PriceReportRejected( reportId, reason, pendingReportId, pendingReportMaxSettlementBaseFee, lastPrice, lastSettlementTimestamp ); } function isPriceValid() public view returns (bool) { return lastPrice > 0 && lastSettlementTimestamp != 0 && lastSettlementTimestamp + PRICE_VALID_FOR_SECONDS > block.timestamp; } function requestPriceIfNeededAndStageOperation( OperationType operation, address targetVault, uint256 amount, uint256 validForSeconds ) public payable { if (operation != OperationType.SetSecurityBondsAllowance) { require(amount > 0, 'Staged operation amount must be non-zero'); } require(validForSeconds > 0, 'Staged operation timeout must be positive'); require( validForSeconds <= MAX_OPERATION_VALID_FOR_SECONDS, 'Staged operation timeout exceeds the maximum allowed' ); if (operation != OperationType.Liquidation) { require(targetVault == msg.sender, 'Self-targeted staged operation target must match the initiator vault'); } else { require(targetVault != msg.sender, 'Caller bad'); } require( !securityPool.isEscalationResolved(), 'question already resolved, so staged operations are unavailable' ); if (pendingReportId != 0) { require( msg.sender == pendingReportSponsor, 'Only the pending report sponsor can queue more operations until settlement' ); } if (operation == OperationType.WithdrawRep) { (, uint256 withdrawRepAmount) = _previewWithdrawRep(msg.sender, amount); require(withdrawRepAmount > 0, 'Withdraw amount has no effect'); } stagedOperationCounter++; uint256 operationId = stagedOperationCounter; // Capture the target vault state at queue time. Liquidation may still execute if // the target deposits more REP after staging, but allowance changes or ownership // decreases make a liquidation snapshot stale. Non-liquidation operations keep // the snapshot for history and execution-event context, but price validity no // longer meters operations by snapshot or live external-value exposure. // Liquidation should value the vault's full collateral claim. That means using the // pool's total REP balance here rather than only the currently withdrawable balance. (uint256 snapshotTargetOwnership, uint256 snapshotTargetAllowance, , ) = securityPool.securityVaults( targetVault ); uint256 snapshotTotalRep = securityPool.getTotalRepBalance(); uint256 snapshotDenominator = securityPool.poolOwnershipDenominator(); stagedOperations[operationId] = StagedOperation({ operation: operation, initiatorVault: msg.sender, targetVault: targetVault, amount: amount, queuedAt: block.timestamp, validForSeconds: validForSeconds, snapshotTargetOwnership: snapshotTargetOwnership, snapshotTargetAllowance: snapshotTargetAllowance, snapshotTotalRep: snapshotTotalRep, snapshotDenominator: snapshotDenominator }); _trackActiveStagedOperation(operationId); uint256 retained = 0; // amount to retain from msg.value (cost incurred) if (isPriceValid()) { _emitStagedOperationQueued(operationId, false); executeStagedOperation(operationId); // no cost when price is valid } else { bool shouldRequestPrice = pendingReportId == 0 && pendingSettlementOperationIds.length == 0; bool isPendingSettlementOperationId = _trackPendingSettlementOperation(operationId); _emitStagedOperationQueued(operationId, isPendingSettlementOperationId); if (shouldRequestPrice && isPendingSettlementOperationId) { uint256 ethCost = getRequestPriceEthCost(); require(msg.value >= ethCost, 'Not enough ETH was provided to request a fresh oracle price'); retained += ethCost; _requestPrice(msg.sender, ethCost); } } // Refund the excess of msg.value that was not retained uint256 refund = msg.value - retained; if (refund > 0) { (bool sent, ) = payable(msg.sender).call{ value: refund }(''); require(sent, 'Oracle coordinator failed to return unused ETH'); } } function executeStagedOperation(uint256 operationId) public { StagedOperation memory stagedOperation = stagedOperations[operationId]; require( stagedOperation.initiatorVault != address(0), 'Staged operation does not exist or was already consumed' ); require(isPriceValid(), 'A valid oracle price is required before executing staged operations'); if (block.timestamp > stagedOperation.queuedAt + settlementTime + stagedOperation.validForSeconds) { _consumeAndEmitExecutedStagedOperation( operationId, stagedOperation.operation, false, STAGED_OPERATION_ERROR_EXPIRED ); return; } if (stagedOperation.operation == OperationType.Liquidation) { (uint256 currentTargetOwnership, uint256 currentTargetAllowance, , ) = securityPool.securityVaults( stagedOperation.targetVault ); if ( currentTargetOwnership < stagedOperation.snapshotTargetOwnership || currentTargetAllowance != stagedOperation.snapshotTargetAllowance ) { _consumeAndEmitExecutedStagedOperation( operationId, stagedOperation.operation, false, STAGED_OPERATION_ERROR_STALE_LIQUIDATION ); return; } } if (stagedOperation.operation == OperationType.WithdrawRep && !_hasWithdrawEffect(stagedOperation)) { _consumeAndEmitExecutedStagedOperation( operationId, stagedOperation.operation, false, STAGED_OPERATION_ERROR_ZERO_WITHDRAW ); return; } if (stagedOperation.operation == OperationType.Liquidation) { if (!_isLiquidationBeyondMinPriceDistance(stagedOperation)) { _consumeAndEmitExecutedStagedOperation( operationId, stagedOperation.operation, false, STAGED_OPERATION_ERROR_MIN_LIQUIDATION_DISTANCE ); return; } _executeLiquidationStagedOperation(operationId, stagedOperation); } else if (stagedOperation.operation == OperationType.WithdrawRep) { _executeWithdrawRepStagedOperation(operationId, stagedOperation); } else { _executeSetSecurityBondAllowanceStagedOperation(operationId, stagedOperation); } } function _emitExecutedStagedOperation( uint256 operationId, OperationType operation, bool success, string memory errorMessage ) private { emit ExecutedStagedOperation(operationId, operation, success, errorMessage); } function _consumeAndEmitExecutedStagedOperation( uint256 operationId, OperationType operation, bool success, string memory errorMessage ) private { _consumeStagedOperation(operationId); _emitExecutedStagedOperation(operationId, operation, success, errorMessage); } function _completeExecutedStagedOperation(uint256 operationId, OperationType operation) private { _emitExecutedStagedOperation(operationId, operation, true, STAGED_OPERATION_EXECUTION_OK); } function _emitExecutedStagedOperationFailure( uint256 operationId, OperationType operation, string memory reason ) private { _emitExecutedStagedOperation(operationId, operation, false, reason); } function _executeLiquidationStagedOperation(uint256 operationId, StagedOperation memory stagedOperation) private { _consumeStagedOperation(operationId); try securityPool.performLiquidation( stagedOperation.initiatorVault, stagedOperation.targetVault, stagedOperation.amount, stagedOperation.snapshotTargetOwnership, stagedOperation.snapshotTargetAllowance, stagedOperation.snapshotTotalRep, stagedOperation.snapshotDenominator ) { _completeExecutedStagedOperation(operationId, stagedOperation.operation); } catch Error(string memory reason) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, reason); } catch Panic(uint256) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_PANIC); } catch (bytes memory) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_UNKNOWN); } } function _executeWithdrawRepStagedOperation(uint256 operationId, StagedOperation memory stagedOperation) private { _consumeStagedOperation(operationId); try securityPool.performWithdrawRep(stagedOperation.initiatorVault, stagedOperation.amount) { _completeExecutedStagedOperation(operationId, stagedOperation.operation); } catch Error(string memory reason) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, reason); } catch Panic(uint256) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_PANIC); } catch (bytes memory) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_UNKNOWN); } } function _executeSetSecurityBondAllowanceStagedOperation( uint256 operationId, StagedOperation memory stagedOperation ) private { _consumeStagedOperation(operationId); try securityPool.performSetSecurityBondsAllowance(stagedOperation.initiatorVault, stagedOperation.amount) { _completeExecutedStagedOperation(operationId, stagedOperation.operation); } catch Error(string memory reason) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, reason); } catch Panic(uint256) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_PANIC); } catch (bytes memory) { _emitExecutedStagedOperationFailure(operationId, stagedOperation.operation, STAGED_OPERATION_ERROR_UNKNOWN); } } function _emitStagedOperationQueued(uint256 operationId, bool isPendingSlot) private { StagedOperation memory stagedOperation = stagedOperations[operationId]; emit StagedOperationQueued( operationId, stagedOperation.operation, stagedOperation.initiatorVault, stagedOperation.targetVault, stagedOperation.amount, stagedOperation.queuedAt, stagedOperation.validForSeconds, stagedOperation.snapshotTargetOwnership, stagedOperation.snapshotTargetAllowance, stagedOperation.snapshotTotalRep, stagedOperation.snapshotDenominator, isPendingSlot ); } function _previewWithdrawRep( address vault, uint256 repAmount ) private view returns (uint256 withdrawOwnership, uint256 withdrawRepAmount) { if (repAmount == 0) return (0, 0); (uint256 vaultOwnership, , , ) = securityPool.securityVaults(vault); uint256 ownershipToWithdraw = securityPool.repToPoolOwnership(repAmount); uint256 minimumRemainingOwnership = securityPool.repToPoolOwnership(SecurityPoolUtils.MIN_REP_DEPOSIT); withdrawOwnership = ownershipToWithdraw + minimumRemainingOwnership > vaultOwnership ? vaultOwnership : ownershipToWithdraw; withdrawRepAmount = securityPool.poolOwnershipToRep(withdrawOwnership); } function _hasWithdrawEffect(StagedOperation memory stagedOperation) private view returns (bool) { (, uint256 withdrawRepAmount) = _previewWithdrawRep(stagedOperation.initiatorVault, stagedOperation.amount); return withdrawRepAmount > 0; } function _getSnapshotVaultRep(StagedOperation memory stagedOperation) private pure returns (uint256) { if (stagedOperation.snapshotDenominator == 0) { return stagedOperation.snapshotTargetOwnership / PRICE_PRECISION; } return (stagedOperation.snapshotTargetOwnership * stagedOperation.snapshotTotalRep) / stagedOperation.snapshotDenominator; } function _isLiquidationBeyondMinPriceDistance(StagedOperation memory stagedOperation) private view returns (bool) { if (minLiquidationPriceDistanceBps == 0) return true; uint256 snapshotTargetAllowance = stagedOperation.snapshotTargetAllowance; if (snapshotTargetAllowance == 0) return false; uint256 currentPrice = lastPrice; if (currentPrice == 0) return false; uint256 vaultRep = _getSnapshotVaultRep(stagedOperation); uint256 securityMultiplier = securityPool.securityMultiplier(); uint256 thresholdPrice = (vaultRep * PRICE_PRECISION) / (snapshotTargetAllowance * securityMultiplier); if (currentPrice <= thresholdPrice) return false; return ((currentPrice - thresholdPrice) * SecurityPoolUtils.BPS_DENOMINATOR) / currentPrice >= minLiquidationPriceDistanceBps; } function _consumeStagedOperation(uint256 operationId) private { _consumePendingSettlementOperation(operationId); _consumeActiveStagedOperation(operationId); stagedOperations[operationId].initiatorVault = address(0); } function getPendingOperationSlot() public view returns (StagedOperation memory) { return stagedOperations[pendingOperationSlotId]; } function getActiveStagedOperationCount() public view returns (uint256) { return activeStagedOperationCount; } function getPendingSettlementOperationCount() public view returns (uint256) { return pendingSettlementOperationIds.length; } function getPendingSettlementOperationIds() public view returns (uint256[] memory) { return pendingSettlementOperationIds; } function getActiveStagedOperations( uint256 startIndex, uint256 count ) public view returns (uint256[] memory operationIds, StagedOperation[] memory operations) { if (count == 0 || startIndex >= activeStagedOperationCount) { return (new uint256[](0), new StagedOperation[](0)); } uint256 availableCount = activeStagedOperationCount - startIndex; uint256 resultCount = count < availableCount ? count : availableCount; operationIds = new uint256[](resultCount); operations = new StagedOperation[](resultCount); uint256 operationId = latestActiveStagedOperationId; for (uint256 skipped = 0; skipped < startIndex && operationId != 0; skipped++) { operationId = olderActiveStagedOperationIds[operationId]; } for (uint256 index = 0; index < resultCount && operationId != 0; index++) { operationIds[index] = operationId; operations[index] = stagedOperations[operationId]; operationId = olderActiveStagedOperationIds[operationId]; } } function _trackActiveStagedOperation(uint256 operationId) private { if (isActiveStagedOperation[operationId]) return; isActiveStagedOperation[operationId] = true; activeStagedOperationCount++; if (latestActiveStagedOperationId != 0) { olderActiveStagedOperationIds[operationId] = latestActiveStagedOperationId; newerActiveStagedOperationIds[latestActiveStagedOperationId] = operationId; } latestActiveStagedOperationId = operationId; } function _trackPendingSettlementOperation(uint256 operationId) private returns (bool) { if (pendingSettlementOperationIds.length >= MAX_PENDING_SETTLEMENT_OPERATIONS) return false; pendingSettlementOperationIds.push(operationId); if (pendingOperationSlotId == 0) { pendingOperationSlotId = operationId; } return true; } function _consumePendingSettlementOperation(uint256 operationId) private { uint256 operationCount = pendingSettlementOperationIds.length; for (uint256 index = 0; index < operationCount; index++) { if (pendingSettlementOperationIds[index] != operationId) continue; for (uint256 shiftIndex = index + 1; shiftIndex < operationCount; shiftIndex++) { pendingSettlementOperationIds[shiftIndex - 1] = pendingSettlementOperationIds[shiftIndex]; } pendingSettlementOperationIds.pop(); pendingOperationSlotId = pendingSettlementOperationIds.length == 0 ? 0 : pendingSettlementOperationIds[0]; return; } } function _consumeActiveStagedOperation(uint256 operationId) private { if (!isActiveStagedOperation[operationId]) return; uint256 olderOperationId = olderActiveStagedOperationIds[operationId]; uint256 newerOperationId = newerActiveStagedOperationIds[operationId]; if (newerOperationId != 0) { olderActiveStagedOperationIds[newerOperationId] = olderOperationId; } else { latestActiveStagedOperationId = olderOperationId; } if (olderOperationId != 0) { newerActiveStagedOperationIds[olderOperationId] = newerOperationId; } delete olderActiveStagedOperationIds[operationId]; delete newerActiveStagedOperationIds[operationId]; delete isActiveStagedOperation[operationId]; activeStagedOperationCount--; } }