creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "contracts/protocol/interfaces/ILendingMarket.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nimport \"../types/ProtocolTypes.sol\";\nimport {ItayoseLog} from \"../storages/LendingMarketStorage.sol\";\nimport {OrderBookLib, FilledOrder, PartiallyFilledOrder} from \"../libraries/OrderBookLib.sol\";\n\ninterface ILendingMarket {\n    error NoOrderExists();\n    error CallerNotMaker();\n    error MarketNotOpened();\n    error AlreadyItayosePeriod();\n    error NotItayosePeriod();\n    error NotPreOrderPeriod();\n\n    function getOrderBookDetail(\n        uint8 orderBookId\n    )\n        external\n        view\n        returns (bytes32 ccy, uint256 maturity, uint256 openingDate, uint256 preOpeningDate);\n\n    function getCircuitBreakerThresholds(\n        uint8 orderBookId\n    )\n        external\n        view\n        returns (uint256 lendCircuitBreakerThreshold, uint256 borrowCircuitBreakerThreshold);\n\n    function getBestLendUnitPrice(uint8 orderBookId) external view returns (uint256 unitPrice);\n\n    function getBestLendUnitPrices(\n        uint8[] calldata orderBookIds\n    ) external view returns (uint256[] memory);\n\n    function getBestBorrowUnitPrice(uint8 orderBookId) external view returns (uint256 unitPrice);\n\n    function getBestBorrowUnitPrices(\n        uint8[] calldata orderBookIds\n    ) external view returns (uint256[] memory);\n\n    function getMarketUnitPrice(uint8 orderBookId) external view returns (uint256);\n\n    function getLastOrderTimestamp(uint8 orderBookId) external view returns (uint48);\n\n    function getBlockUnitPriceHistory(\n        uint8 orderBookId\n    ) external view returns (uint256[] memory unitPrices, uint48 timestamp);\n\n    function getBlockUnitPriceAverage(\n        uint8 orderBookId,\n        uint256 count\n    ) external view returns (uint256);\n\n    function getBorrowOrderBook(\n        uint8 orderBookId,\n        uint256 start,\n        uint256 limit\n    )\n        external\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        );\n\n    function getLendOrderBook(\n        uint8 orderBookId,\n        uint256 start,\n        uint256 limit\n    )\n        external\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        );\n\n    function getItayoseEstimation(\n        uint8 orderBookId\n    )\n        external\n        view\n        returns (\n            uint256 openingUnitPrice,\n            uint256 lastLendUnitPrice,\n            uint256 lastBorrowUnitPrice,\n            uint256 totalOffsetAmount\n        );\n\n    function getMaturity(uint8 orderBookId) external view returns (uint256);\n\n    function getMaturities(\n        uint8[] calldata orderBookIds\n    ) external view returns (uint256[] memory maturities);\n\n    function getCurrency() external view returns (bytes32);\n\n    function getOrderFeeRate() external view returns (uint256);\n\n    function getCircuitBreakerLimitRange() external view returns (uint256);\n\n    function getOpeningDate(uint8 orderBookId) external view returns (uint256);\n\n    function isReady(uint8 orderBookId) external view returns (bool);\n\n    function isMatured(uint8 orderBookId) external view returns (bool);\n\n    function isOpened(uint8 orderBookId) external view returns (bool);\n\n    function isItayosePeriod(uint8 orderBookId) external view returns (bool);\n\n    function isPreOrderPeriod(uint8 orderBookId) external returns (bool);\n\n    function getItayoseLog(uint256 maturity) external view returns (ItayoseLog memory);\n\n    function getOrder(\n        uint8 orderBookId,\n        uint48 orderId\n    )\n        external\n        view\n        returns (\n            ProtocolTypes.Side,\n            uint256 unitPrice,\n            uint256 maturity,\n            address maker,\n            uint256 amount,\n            uint256 timestamp,\n            bool isPreOrder\n        );\n\n    function getTotalAmountFromLendOrders(\n        uint8 orderBookId,\n        address user\n    )\n        external\n        view\n        returns (\n            uint256 activeAmount,\n            uint256 inactiveAmount,\n            uint256 inactiveFutureValue,\n            uint256 maturity\n        );\n\n    function getTotalAmountFromBorrowOrders(\n        uint8 orderBookId,\n        address user,\n        uint256 _minUnitPrice\n    )\n        external\n        view\n        returns (\n            uint256 activeAmount,\n            uint256 inactiveAmount,\n            uint256 inactiveFutureValue,\n            uint256 maturity\n        );\n\n    function getLendOrderIds(\n        uint8 orderBookId,\n        address user\n    ) external view returns (uint48[] memory activeOrderIds, uint48[] memory inActiveOrderIds);\n\n    function getBorrowOrderIds(\n        uint8 orderBookId,\n        address user\n    ) external view returns (uint48[] memory activeOrderIds, uint48[] memory inActiveOrderIds);\n\n    function calculateFilledAmount(\n        uint8 orderBookId,\n        ProtocolTypes.Side side,\n        uint256 amount,\n        uint256 unitPrice\n    )\n        external\n        view\n        returns (\n            uint256 lastUnitPrice,\n            uint256 filledAmount,\n            uint256 filledAmountInFV,\n            uint256 feeInFV,\n            uint256 placedAmount\n        );\n\n    function createOrderBook(\n        uint256 maturity,\n        uint256 openingDate,\n        uint256 preOpeningDate\n    ) external returns (uint8 orderBookId);\n\n    function executeAutoRoll(\n        uint8 maturedOrderBookId,\n        uint8 newNearestOrderBookId,\n        uint256 autoRollUnitPrice\n    ) external;\n\n    function cancelOrder(uint8 orderBookId, address user, uint48 orderId) external;\n\n    function executeOrder(\n        uint8 orderBookId,\n        ProtocolTypes.Side side,\n        address account,\n        uint256 amount,\n        uint256 unitPrice\n    )\n        external\n        returns (\n            FilledOrder memory filledOrder,\n            PartiallyFilledOrder memory partiallyFilledOrder,\n            uint256 feeInFV\n        );\n\n    function executePreOrder(\n        uint8 orderBookId,\n        ProtocolTypes.Side side,\n        address user,\n        uint256 amount,\n        uint256 unitPrice\n    ) external;\n\n    function unwindPosition(\n        uint8 orderBookId,\n        ProtocolTypes.Side side,\n        address user,\n        uint256 futureValue\n    )\n        external\n        returns (\n            FilledOrder memory filledOrder,\n            PartiallyFilledOrder memory partiallyFilledOrder,\n            uint256 feeInFV\n        );\n\n    function executeItayoseCall(\n        uint8 orderBookId\n    )\n        external\n        returns (\n            uint256 openingUnitPrice,\n            uint256 totalOffsetAmount,\n            uint256 openingDate,\n            PartiallyFilledOrder memory partiallyFilledLendingOrder,\n            PartiallyFilledOrder memory partiallyFilledBorrowingOrder\n        );\n\n    function cleanUpOrders(\n        uint8 orderBookId,\n        address user\n    )\n        external\n        returns (\n            uint256 activeLendOrderCount,\n            uint256 activeBorrowOrderCount,\n            uint256 removedLendOrderFutureValue,\n            uint256 removedBorrowOrderFutureValue,\n            uint256 removedLendOrderAmount,\n            uint256 removedBorrowOrderAmount,\n            uint256 maturity\n        );\n\n    function updateOrderFeeRate(uint256 orderFeeRate) external;\n\n    function updateCircuitBreakerLimitRange(uint256 limitRange) external;\n\n    function pause() external;\n\n    function unpause() external;\n}\n"
      },
      "contracts/protocol/libraries/Constants.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nlibrary Constants {\n    /// @dev Used for price digits in the basis (10000 -> 1)\n    uint256 internal constant PRICE_DIGIT = 10000;\n\n    /// @dev Used for percentage digits in the basis (10000 -> 100%)\n    uint256 internal constant PCT_DIGIT = 10000;\n\n    /// @dev Used for seconds in year (60 * 60 * 24 * 365)\n    uint256 internal constant SECONDS_IN_YEAR = 31536000;\n\n    /// @dev Used for maximum order count per currency\n    uint256 internal constant MAXIMUM_ORDER_COUNT = 20;\n}\n"
      },
      "contracts/protocol/libraries/logics/OrderBookLogic.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nimport {Constants} from \"../Constants.sol\";\nimport {OrderBookLib, FilledOrder, PartiallyFilledOrder} from \"../OrderBookLib.sol\";\nimport {ProtocolTypes} from \"../../types/ProtocolTypes.sol\";\nimport {LendingMarketStorage as Storage, ItayoseLog} from \"../../storages/LendingMarketStorage.sol\";\nimport {RoundingUint256} from \"../math/RoundingUint256.sol\";\nimport {ILendingMarket} from \"../../interfaces/ILendingMarket.sol\";\n\nlibrary OrderBookLogic {\n    using OrderBookLib for OrderBookLib.OrderBook;\n    using RoundingUint256 for uint256;\n\n    error InvalidOrderFeeRate();\n    error InvalidCircuitBreakerLimitRange();\n    error OrderBookNotMatured();\n\n    event OrderFeeRateUpdated(bytes32 ccy, uint256 previousRate, uint256 rate);\n    event CircuitBreakerLimitRangeUpdated(bytes32 ccy, uint256 previousRate, uint256 rate);\n    event OrderBookCreated(uint8 orderBookId, uint256 maturity, uint256 openingDate);\n\n    event ItayoseExecuted(\n        bytes32 ccy,\n        uint256 maturity,\n        uint256 openingUnitPrice,\n        uint256 lastLendUnitPrice,\n        uint256 lastBorrowUnitPrice,\n        uint256 offsetAmount\n    );\n\n    function isReady(uint8 _orderBookId) public view returns (bool) {\n        return Storage.slot().isReady[_getOrderBook(_orderBookId).maturity];\n    }\n\n    function isMatured(uint8 _orderBookId) public view returns (bool) {\n        return _getOrderBook(_orderBookId).isMatured();\n    }\n\n    function isOpened(uint8 _orderBookId) public view returns (bool) {\n        return\n            isReady(_orderBookId) &&\n            !isMatured(_orderBookId) &&\n            block.timestamp >= _getOrderBook(_orderBookId).openingDate;\n    }\n\n    function isItayosePeriod(uint8 _orderBookId) public view returns (bool) {\n        return\n            block.timestamp >=\n            (_getOrderBook(_orderBookId).openingDate - OrderBookLib.ITAYOSE_PERIOD) &&\n            !isReady(_orderBookId);\n    }\n\n    function isPreOrderPeriod(uint8 _orderBookId) public view returns (bool) {\n        OrderBookLib.OrderBook storage orderBook = _getOrderBook(_orderBookId);\n        return\n            block.timestamp >= orderBook.preOpeningDate &&\n            block.timestamp < (orderBook.openingDate - OrderBookLib.ITAYOSE_PERIOD);\n    }\n\n    function getOrderBookDetail(\n        uint8 _orderBookId\n    )\n        public\n        view\n        returns (bytes32 ccy, uint256 maturity, uint256 openingDate, uint256 preOpeningDate)\n    {\n        OrderBookLib.OrderBook storage orderBook = _getOrderBook(_orderBookId);\n\n        ccy = Storage.slot().ccy;\n        maturity = orderBook.maturity;\n        openingDate = orderBook.openingDate;\n        preOpeningDate = orderBook.preOpeningDate;\n    }\n\n    function getLastOrderTimestamp(uint8 _orderBookId) external view returns (uint48) {\n        return _getOrderBook(_orderBookId).lastOrderTimestamp;\n    }\n\n    function getBlockUnitPriceHistory(\n        uint8 _orderBookId\n    ) external view returns (uint256[] memory unitPrices, uint48 timestamp) {\n        return _getOrderBook(_orderBookId).getBlockUnitPriceHistory(true);\n    }\n\n    function getMarketUnitPrice(uint8 _orderBookId) external view returns (uint256) {\n        return _getOrderBook(_orderBookId).getMarketUnitPrice(true);\n    }\n\n    function getBlockUnitPriceAverage(\n        uint8 _orderBookId,\n        uint256 _count\n    ) external view returns (uint256) {\n        return _getOrderBook(_orderBookId).getBlockUnitPriceAverage(_count, true);\n    }\n\n    function getCircuitBreakerThresholds(\n        uint8 _orderBookId\n    ) external view returns (uint256 maxLendUnitPrice, uint256 minBorrowUnitPrice) {\n        maxLendUnitPrice = _getOrderBook(_orderBookId).getLendCircuitBreakerThreshold(\n            Storage.slot().circuitBreakerLimitRange,\n            true\n        );\n        minBorrowUnitPrice = _getOrderBook(_orderBookId).getBorrowCircuitBreakerThreshold(\n            Storage.slot().circuitBreakerLimitRange,\n            true\n        );\n    }\n\n    function getBestLendUnitPrice(uint8 _orderBookId) public view returns (uint256) {\n        return _getOrderBook(_orderBookId).getBestLendUnitPrice();\n    }\n\n    function getBestLendUnitPrices(\n        uint8[] memory _orderBookIds\n    ) external view returns (uint256[] memory unitPrices) {\n        unitPrices = new uint256[](_orderBookIds.length);\n\n        for (uint256 i; i < _orderBookIds.length; i++) {\n            unitPrices[i] = _getOrderBook(_orderBookIds[i]).getBestLendUnitPrice();\n        }\n    }\n\n    function getBestBorrowUnitPrice(uint8 _orderBookId) public view returns (uint256) {\n        return _getOrderBook(_orderBookId).getBestBorrowUnitPrice();\n    }\n\n    function getBestBorrowUnitPrices(\n        uint8[] memory _orderBookIds\n    ) external view returns (uint256[] memory unitPrices) {\n        unitPrices = new uint256[](_orderBookIds.length);\n\n        for (uint256 i; i < _orderBookIds.length; i++) {\n            unitPrices[i] = _getOrderBook(_orderBookIds[i]).getBestBorrowUnitPrice();\n        }\n    }\n\n    function getBorrowOrderBook(\n        uint8 _orderBookId,\n        uint256 _start,\n        uint256 _limit\n    )\n        external\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        )\n    {\n        return _getOrderBook(_orderBookId).getBorrowOrderBook(_start, _limit);\n    }\n\n    function getLendOrderBook(\n        uint8 _orderBookId,\n        uint256 _start,\n        uint256 _limit\n    )\n        external\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        )\n    {\n        return _getOrderBook(_orderBookId).getLendOrderBook(_start, _limit);\n    }\n\n    function getItayoseEstimation(\n        uint8 _orderBookId\n    )\n        external\n        view\n        returns (\n            uint256 openingUnitPrice,\n            uint256 lastLendUnitPrice,\n            uint256 lastBorrowUnitPrice,\n            uint256 totalOffsetAmount\n        )\n    {\n        return _getOrderBook(_orderBookId).calculateItayoseResult();\n    }\n\n    function getMaturities(\n        uint8[] memory _orderBookIds\n    ) public view returns (uint256[] memory maturities) {\n        maturities = new uint256[](_orderBookIds.length);\n\n        for (uint256 i; i < _orderBookIds.length; i++) {\n            maturities[i] = _getOrderBook(_orderBookIds[i]).maturity;\n        }\n    }\n\n    function updateOrderFeeRate(uint256 _orderFeeRate) external {\n        if (_orderFeeRate >= Constants.PCT_DIGIT) revert InvalidOrderFeeRate();\n\n        uint256 previousRate = Storage.slot().orderFeeRate;\n\n        if (_orderFeeRate != previousRate) {\n            Storage.slot().orderFeeRate = _orderFeeRate;\n\n            emit OrderFeeRateUpdated(Storage.slot().ccy, previousRate, _orderFeeRate);\n        }\n    }\n\n    function updateCircuitBreakerLimitRange(uint256 _cbLimitRange) external {\n        if (_cbLimitRange >= Constants.PCT_DIGIT) revert InvalidCircuitBreakerLimitRange();\n\n        uint256 previousRange = Storage.slot().circuitBreakerLimitRange;\n\n        if (_cbLimitRange != previousRange) {\n            Storage.slot().circuitBreakerLimitRange = _cbLimitRange;\n\n            emit CircuitBreakerLimitRangeUpdated(Storage.slot().ccy, previousRange, _cbLimitRange);\n        }\n    }\n\n    function createOrderBook(\n        uint256 _maturity,\n        uint256 _openingDate,\n        uint256 _preOpeningDate\n    ) public returns (uint8 orderBookId) {\n        orderBookId = _nextOrderBookId();\n\n        Storage.slot().isReady[_maturity] = _getOrderBook(orderBookId).initialize(\n            _maturity,\n            _openingDate,\n            _preOpeningDate\n        );\n\n        emit OrderBookCreated(orderBookId, _maturity, _openingDate);\n    }\n\n    function executeAutoRoll(\n        uint8 _maturedOrderBookId,\n        uint8 _destinationOrderBookId,\n        uint256 _autoRollUnitPrice\n    ) external {\n        OrderBookLib.OrderBook storage maturedOrderBook = Storage.slot().orderBooks[\n            _maturedOrderBookId\n        ];\n\n        if (!maturedOrderBook.isMatured()) revert OrderBookNotMatured();\n\n        OrderBookLib.OrderBook storage destinationOrderBook = Storage.slot().orderBooks[\n            _destinationOrderBookId\n        ];\n\n        // NOTE: The auto-roll destination order book has no market unit price if the order has never been filled before.\n        // In this case, the market unit price is updated with the unit price of the auto-roll.\n        if (destinationOrderBook.getMarketUnitPrice(false) == 0) {\n            destinationOrderBook.setInitialBlockUnitPrice(_autoRollUnitPrice);\n        }\n    }\n\n    function executeItayoseCall(\n        uint8 _orderBookId\n    )\n        external\n        returns (\n            uint256 openingUnitPrice,\n            uint256 totalOffsetAmount,\n            uint256 openingDate,\n            PartiallyFilledOrder memory partiallyFilledLendingOrder,\n            PartiallyFilledOrder memory partiallyFilledBorrowingOrder\n        )\n    {\n        uint256 lastLendUnitPrice;\n        uint256 lastBorrowUnitPrice;\n        OrderBookLib.OrderBook storage orderBook = _getOrderBook(_orderBookId);\n\n        (openingUnitPrice, lastLendUnitPrice, lastBorrowUnitPrice, totalOffsetAmount) = orderBook\n            .calculateItayoseResult();\n\n        if (totalOffsetAmount > 0) {\n            ProtocolTypes.Side[2] memory sides = [\n                ProtocolTypes.Side.LEND,\n                ProtocolTypes.Side.BORROW\n            ];\n\n            for (uint256 i; i < sides.length; i++) {\n                ProtocolTypes.Side partiallyFilledOrderSide;\n                PartiallyFilledOrder memory partiallyFilledOrder;\n                FilledOrder memory filledOrder;\n                (filledOrder, partiallyFilledOrder, , ) = orderBook.fillOrders(\n                    sides[i],\n                    totalOffsetAmount,\n                    0,\n                    0\n                );\n\n                if (filledOrder.futureValue > 0) {\n                    orderBook.setInitialBlockUnitPrice(openingUnitPrice);\n                }\n\n                if (partiallyFilledOrder.futureValue > 0) {\n                    if (sides[i] == ProtocolTypes.Side.LEND) {\n                        partiallyFilledOrderSide = ProtocolTypes.Side.BORROW;\n                        partiallyFilledBorrowingOrder = partiallyFilledOrder;\n                    } else {\n                        partiallyFilledOrderSide = ProtocolTypes.Side.LEND;\n                        partiallyFilledLendingOrder = partiallyFilledOrder;\n                    }\n                }\n            }\n\n            emit ItayoseExecuted(\n                Storage.slot().ccy,\n                orderBook.maturity,\n                openingUnitPrice,\n                lastLendUnitPrice,\n                lastBorrowUnitPrice,\n                totalOffsetAmount\n            );\n        }\n\n        Storage.slot().isReady[orderBook.maturity] = true;\n        Storage.slot().itayoseLogs[orderBook.maturity] = ItayoseLog(\n            openingUnitPrice,\n            lastLendUnitPrice,\n            lastBorrowUnitPrice\n        );\n        openingDate = orderBook.openingDate;\n    }\n\n    function _nextOrderBookId() internal returns (uint8) {\n        // NOTE: Originally, matured ordebooks were reused as new orderbooks after auto-rolls, but this reusing logic has been removed.\n        // This means that `orderBookId` is increased in proportion to the number of auto-rolls executed. To avoid overflow of `orderBookId`,\n        // `orderBookId` circulate between 1 and 255.\n        if (Storage.slot().lastOrderBookId == type(uint8).max) {\n            Storage.slot().lastOrderBookId = 1;\n        } else {\n            Storage.slot().lastOrderBookId++;\n        }\n        return Storage.slot().lastOrderBookId;\n    }\n\n    function _getOrderBook(\n        uint8 _orderBookId\n    ) private view returns (OrderBookLib.OrderBook storage) {\n        return Storage.slot().orderBooks[_orderBookId];\n    }\n}\n"
      },
      "contracts/protocol/libraries/math/RoundingUint256.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nlibrary RoundingUint256 {\n    function div(uint256 a, uint256 b) internal pure returns (uint256) {\n        require(b > 0);\n\n        if ((a * 10) / b - (a / b) * 10 < 5) {\n            return a / b;\n        } else {\n            return (a / b) + 1;\n        }\n    }\n}\n"
      },
      "contracts/protocol/libraries/OrderBookLib.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nimport {Constants} from \"./Constants.sol\";\nimport {ProtocolTypes} from \"../types/ProtocolTypes.sol\";\nimport {OrderStatisticsTreeLib, PartiallyRemovedOrder} from \"./OrderStatisticsTreeLib.sol\";\nimport {RoundingUint256} from \"./math/RoundingUint256.sol\";\n\nstruct PlacedOrder {\n    ProtocolTypes.Side side;\n    uint256 unitPrice;\n    uint256 maturity;\n    uint256 timestamp;\n}\n\nstruct FilledOrder {\n    uint256 amount;\n    uint256 unitPrice;\n    uint256 futureValue;\n    uint256 ignoredAmount;\n}\n\nstruct PartiallyFilledOrder {\n    uint48 orderId;\n    address maker;\n    uint256 amount;\n    uint256 futureValue;\n}\n\nlibrary OrderBookLib {\n    using OrderStatisticsTreeLib for OrderStatisticsTreeLib.Tree;\n    using RoundingUint256 for uint256;\n\n    uint256 public constant PRE_ORDER_BASE_PERIOD = 7 days;\n    uint256 public constant ITAYOSE_PERIOD = 1 hours;\n    uint256 public constant CIRCUIT_BREAKER_MINIMUM_LEND_RANGE = 700;\n    uint256 public constant CIRCUIT_BREAKER_MINIMUM_BORROW_RANGE = 200;\n\n    error EmptyOrderBook();\n    error PastMaturityOrderExists();\n\n    struct OrderBook {\n        uint256 maturity;\n        uint256 openingDate;\n        uint256 preOpeningDate;\n        uint48 lastOrderId;\n        uint48 lastOrderTimestamp;\n        bool isReliableBlock;\n        // Micro slots for block unit price history\n        uint80 blockUnitPriceHistory;\n        uint48 lastBlockUnitPriceTimestamp;\n        uint256 blockTotalAmount;\n        uint256 blockTotalFutureValue;\n        mapping(address user => uint48[] orderIds) activeLendOrderIds;\n        mapping(address user => uint48[] orderIds) activeBorrowOrderIds;\n        // Maturity when user last executes order\n        mapping(address user => uint256 maturity) userCurrentMaturities;\n        // Micro slots for order\n        mapping(uint48 orderId => uint256 slots) orders;\n        mapping(uint48 orderId => bool isPreOrder) isPreOrder;\n        mapping(uint256 maturity => OrderStatisticsTreeLib.Tree orders) lendOrders;\n        mapping(uint256 maturity => OrderStatisticsTreeLib.Tree orders) borrowOrders;\n    }\n\n    function initialize(\n        OrderBook storage self,\n        uint256 _maturity,\n        uint256 _openingDate,\n        uint256 _preOpeningDate\n    ) internal returns (bool isReady) {\n        self.maturity = _maturity;\n        self.openingDate = _openingDate;\n        self.preOpeningDate = _preOpeningDate;\n\n        self.blockTotalAmount = 0;\n        self.blockTotalFutureValue = 0;\n        self.blockUnitPriceHistory = 0;\n        self.lastOrderTimestamp = 0;\n        self.lastBlockUnitPriceTimestamp = 0;\n        self.isReliableBlock = false;\n\n        if (block.timestamp >= (_openingDate - ITAYOSE_PERIOD)) {\n            isReady = true;\n        }\n    }\n\n    function isMatured(OrderBook storage self) internal view returns (bool) {\n        return block.timestamp >= self.maturity;\n    }\n\n    function getBestBorrowUnitPrice(OrderBook storage self) internal view returns (uint256) {\n        return self.lendOrders[self.maturity].last();\n    }\n\n    function getBestLendUnitPrice(OrderBook storage self) internal view returns (uint256) {\n        uint256 unitPrice = self.borrowOrders[self.maturity].first();\n        return unitPrice == 0 ? Constants.PRICE_DIGIT : unitPrice;\n    }\n\n    function hasBorrowOrder(OrderBook storage self, address _user) internal view returns (bool) {\n        return self.activeBorrowOrderIds[_user].length != 0;\n    }\n\n    function hasLendOrder(OrderBook storage self, address _user) internal view returns (bool) {\n        return self.activeLendOrderIds[_user].length != 0;\n    }\n\n    function getOrder(\n        OrderBook storage self,\n        uint48 _orderId\n    ) internal view returns (PlacedOrder memory order) {\n        (\n            ProtocolTypes.Side side,\n            uint256 unitPrice,\n            uint256 maturity,\n            uint256 timestamp\n        ) = _unpackOrder(self.orders[_orderId]);\n        order = PlacedOrder(side, unitPrice, maturity, timestamp);\n    }\n\n    function getBlockUnitPriceHistory(\n        OrderBook storage self,\n        bool _isReadOnly\n    ) internal view returns (uint256[] memory prices, uint48 timestamp) {\n        prices = _unpackBlockUnitPriceHistory(self.blockUnitPriceHistory);\n\n        // NOTE: If an order is in the first block of the order book, the block unit price history is empty.\n        // In this case, the first history record is calculated from the current block total amount and total future value\n        // along with the `getMarketUnitPrice` function logic.\n        if (\n            (self.lastOrderTimestamp != block.timestamp || prices[0] == 0 || _isReadOnly) &&\n            self.isReliableBlock\n        ) {\n            for (uint256 i = prices.length - 1; i > 0; i--) {\n                prices[i] = prices[i - 1];\n            }\n\n            prices[0] = (self.blockTotalAmount * Constants.PRICE_DIGIT).div(\n                self.blockTotalFutureValue\n            );\n            timestamp = self.lastOrderTimestamp;\n        } else {\n            timestamp = self.lastBlockUnitPriceTimestamp;\n        }\n    }\n\n    function getMarketUnitPrice(\n        OrderBook storage self,\n        bool _isReadOnly\n    ) internal view returns (uint256 unitPrice) {\n        unitPrice = _unpackBlockUnitPriceHistory(self.blockUnitPriceHistory)[0];\n\n        // NOTE: If an order is in the first block of the order book, the block unit price history is empty.\n        // In this case, the market unit price is calculated from the current block total amount and total future value\n        // to avoid unwinding or liquidation the order in the same block using 0 as the market unit price.\n        if (\n            (self.lastOrderTimestamp != block.timestamp || unitPrice == 0 || _isReadOnly) &&\n            self.isReliableBlock\n        ) {\n            unitPrice = (self.blockTotalAmount * Constants.PRICE_DIGIT).div(\n                self.blockTotalFutureValue\n            );\n        }\n    }\n\n    function getBlockUnitPriceAverage(\n        OrderBook storage self,\n        uint256 maxCount,\n        bool _isReadOnly\n    ) internal view returns (uint256 unitPrice) {\n        uint256[] memory unitPrices = _unpackBlockUnitPriceHistory(self.blockUnitPriceHistory);\n        uint256 length = unitPrices.length;\n        uint256 sum;\n        uint256 count;\n\n        if ((self.lastOrderTimestamp != block.timestamp || _isReadOnly) && self.isReliableBlock) {\n            sum = (self.blockTotalAmount * Constants.PRICE_DIGIT).div(self.blockTotalFutureValue);\n            count = 1;\n            maxCount--;\n        }\n\n        for (uint256 i; i < maxCount; i++) {\n            if (i >= length || unitPrices[i] == 0) {\n                break;\n            }\n\n            sum += unitPrices[i];\n            count++;\n        }\n\n        unitPrice = count > 0 ? sum.div(count) : 0;\n    }\n\n    function getLendOrderBook(\n        OrderBook storage self,\n        uint256 _start,\n        uint256 _limit\n    )\n        internal\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        )\n    {\n        unitPrices = new uint256[](_limit);\n        amounts = new uint256[](_limit);\n        quantities = new uint256[](_limit);\n\n        uint256 unitPrice;\n\n        if (_start == 0) {\n            unitPrice = self.lendOrders[self.maturity].last();\n        } else {\n            (bool exists, uint256 parent) = self.lendOrders[self.maturity].search(_start);\n\n            if (exists) {\n                unitPrice = _start;\n            } else if (parent < _start) {\n                unitPrice = parent;\n            } else {\n                unitPrice = self.lendOrders[self.maturity].prev(parent);\n            }\n        }\n\n        unitPrices[0] = unitPrice;\n        amounts[0] = self.lendOrders[self.maturity].getNodeTotalAmount(unitPrice);\n        quantities[0] = self.lendOrders[self.maturity].getNodeCount(unitPrice);\n\n        for (uint256 i = 1; i < unitPrices.length; i++) {\n            if (unitPrice == 0) {\n                break;\n            }\n\n            unitPrice = self.lendOrders[self.maturity].prev(unitPrice);\n            unitPrices[i] = unitPrice;\n            amounts[i] = self.lendOrders[self.maturity].getNodeTotalAmount(unitPrice);\n            quantities[i] = self.lendOrders[self.maturity].getNodeCount(unitPrice);\n        }\n\n        if (unitPrice != 0) {\n            next = self.lendOrders[self.maturity].prev(unitPrice);\n        }\n    }\n\n    function getBorrowOrderBook(\n        OrderBook storage self,\n        uint256 _start,\n        uint256 _limit\n    )\n        internal\n        view\n        returns (\n            uint256[] memory unitPrices,\n            uint256[] memory amounts,\n            uint256[] memory quantities,\n            uint256 next\n        )\n    {\n        unitPrices = new uint256[](_limit);\n        amounts = new uint256[](_limit);\n        quantities = new uint256[](_limit);\n\n        uint256 unitPrice;\n\n        if (_start == 0) {\n            unitPrice = self.borrowOrders[self.maturity].first();\n        } else {\n            (bool exists, uint256 parent) = self.borrowOrders[self.maturity].search(_start);\n\n            if (exists) {\n                unitPrice = _start;\n            } else if (parent > _start) {\n                unitPrice = parent;\n            } else {\n                unitPrice = self.borrowOrders[self.maturity].next(parent);\n            }\n        }\n\n        unitPrices[0] = unitPrice;\n        amounts[0] = self.borrowOrders[self.maturity].getNodeTotalAmount(unitPrice);\n        quantities[0] = self.borrowOrders[self.maturity].getNodeCount(unitPrice);\n\n        for (uint256 i = 1; i < unitPrices.length; i++) {\n            if (unitPrice == 0) {\n                break;\n            }\n\n            unitPrice = self.borrowOrders[self.maturity].next(unitPrice);\n            unitPrices[i] = unitPrice;\n            amounts[i] = self.borrowOrders[self.maturity].getNodeTotalAmount(unitPrice);\n            quantities[i] = self.borrowOrders[self.maturity].getNodeCount(unitPrice);\n        }\n\n        if (unitPrice != 0) {\n            next = self.borrowOrders[self.maturity].next(unitPrice);\n        }\n    }\n\n    function getLendOrderIds(\n        OrderBook storage self,\n        address _user\n    ) internal view returns (uint48[] memory activeOrderIds, uint48[] memory inActiveOrderIds) {\n        uint256 activeOrderCount = 0;\n        uint256 inActiveOrderCount = 0;\n        uint256 userMaturity = self.userCurrentMaturities[_user];\n        bool isPastMaturity = block.timestamp >= userMaturity;\n\n        uint48[] memory orderIds = self.activeLendOrderIds[_user];\n        uint256 orderIdLength = orderIds.length;\n        activeOrderIds = new uint48[](isPastMaturity ? 0 : orderIdLength);\n        inActiveOrderIds = new uint48[](orderIdLength);\n\n        for (uint256 i; i < orderIdLength; i++) {\n            uint48 orderId = orderIds[i];\n            (, uint256 unitPrice, , ) = _unpackOrder(self.orders[orderId]);\n\n            if (!self.lendOrders[userMaturity].isActiveOrderId(unitPrice, orderId)) {\n                unchecked {\n                    inActiveOrderCount += 1;\n                }\n                inActiveOrderIds[i - activeOrderCount] = orderId;\n                if (!isPastMaturity) {\n                    assembly {\n                        mstore(activeOrderIds, sub(mload(activeOrderIds), 1))\n                    }\n                }\n            } else {\n                unchecked {\n                    activeOrderCount += 1;\n                }\n                if (!isPastMaturity) {\n                    activeOrderIds[i - inActiveOrderCount] = orderId;\n                }\n                assembly {\n                    mstore(inActiveOrderIds, sub(mload(inActiveOrderIds), 1))\n                }\n            }\n        }\n    }\n\n    function getBorrowOrderIds(\n        OrderBook storage self,\n        address _user\n    ) internal view returns (uint48[] memory activeOrderIds, uint48[] memory inActiveOrderIds) {\n        uint256 activeOrderCount = 0;\n        uint256 inActiveOrderCount = 0;\n        uint256 userMaturity = self.userCurrentMaturities[_user];\n        bool isPastMaturity = block.timestamp >= userMaturity;\n\n        uint48[] memory orderIds = self.activeBorrowOrderIds[_user];\n        uint256 orderIdLength = orderIds.length;\n        activeOrderIds = new uint48[](isPastMaturity ? 0 : orderIdLength);\n        inActiveOrderIds = new uint48[](orderIdLength);\n\n        for (uint256 i; i < orderIdLength; i++) {\n            uint48 orderId = orderIds[i];\n            (, uint256 unitPrice, , ) = _unpackOrder(self.orders[orderId]);\n\n            if (!self.borrowOrders[userMaturity].isActiveOrderId(unitPrice, orderId)) {\n                unchecked {\n                    inActiveOrderCount += 1;\n                }\n                inActiveOrderIds[i - activeOrderCount] = orderId;\n                if (!isPastMaturity) {\n                    assembly {\n                        mstore(activeOrderIds, sub(mload(activeOrderIds), 1))\n                    }\n                }\n            } else {\n                unchecked {\n                    activeOrderCount += 1;\n                }\n                if (!isPastMaturity) {\n                    activeOrderIds[i - inActiveOrderCount] = orderId;\n                }\n                assembly {\n                    mstore(inActiveOrderIds, sub(mload(inActiveOrderIds), 1))\n                }\n            }\n        }\n    }\n\n    function calculateFilledAmount(\n        OrderBook storage self,\n        ProtocolTypes.Side _side,\n        uint256 _amount,\n        uint256 _unitPrice\n    )\n        internal\n        view\n        returns (uint256 lastUnitPrice, uint256 filledAmount, uint256 filledAmountInFV)\n    {\n        if (_amount == 0) return (0, 0, 0);\n\n        if (_side == ProtocolTypes.Side.LEND) {\n            return\n                self.borrowOrders[self.maturity].calculateDroppedAmountFromLeft(\n                    _amount,\n                    0,\n                    _unitPrice\n                );\n        } else {\n            return\n                self.lendOrders[self.maturity].calculateDroppedAmountFromRight(\n                    _amount,\n                    0,\n                    _unitPrice\n                );\n        }\n    }\n\n    function updateUserMaturity(OrderBook storage self, address _user) internal {\n        uint256 userMaturity = self.userCurrentMaturities[_user];\n        uint256 orderBookMaturity = self.maturity;\n\n        if (userMaturity != orderBookMaturity) {\n            if (\n                self.activeLendOrderIds[_user].length > 0 ||\n                self.activeBorrowOrderIds[_user].length > 0\n            ) {\n                revert PastMaturityOrderExists();\n            }\n\n            self.userCurrentMaturities[_user] = orderBookMaturity;\n        }\n    }\n\n    function placeOrder(\n        OrderBook storage self,\n        ProtocolTypes.Side _side,\n        address _user,\n        uint256 _amount,\n        uint256 _unitPrice\n    ) internal returns (uint48 orderId) {\n        orderId = _nextOrderId(self);\n        self.orders[orderId] = _packOrder(_side, _unitPrice, self.maturity, block.timestamp);\n\n        if (_side == ProtocolTypes.Side.LEND) {\n            self.lendOrders[self.maturity].insertOrder(_unitPrice, orderId, _user, _amount);\n            self.activeLendOrderIds[_user].push(orderId);\n        } else if (_side == ProtocolTypes.Side.BORROW) {\n            self.borrowOrders[self.maturity].insertOrder(_unitPrice, orderId, _user, _amount);\n            self.activeBorrowOrderIds[_user].push(orderId);\n        }\n    }\n\n    function fillOrders(\n        OrderBook storage self,\n        ProtocolTypes.Side _side,\n        uint256 _amount,\n        uint256 _amountInFV,\n        uint256 _unitPrice\n    )\n        internal\n        returns (\n            FilledOrder memory filledOrder,\n            PartiallyFilledOrder memory partiallyFilledOrder,\n            uint256 remainingAmount,\n            bool orderExists\n        )\n    {\n        PartiallyRemovedOrder memory partiallyRemovedOrder;\n\n        if (_side == ProtocolTypes.Side.BORROW) {\n            OrderStatisticsTreeLib.Tree storage orders = self.lendOrders[self.maturity];\n            (\n                filledOrder.unitPrice,\n                filledOrder.amount,\n                filledOrder.futureValue,\n                remainingAmount,\n                partiallyRemovedOrder\n            ) = orders.dropRight(_amount, _amountInFV, _unitPrice);\n            orderExists = orders.hasOrders();\n        } else if (_side == ProtocolTypes.Side.LEND) {\n            OrderStatisticsTreeLib.Tree storage orders = self.borrowOrders[self.maturity];\n            (\n                filledOrder.unitPrice,\n                filledOrder.amount,\n                filledOrder.futureValue,\n                remainingAmount,\n                partiallyRemovedOrder\n            ) = orders.dropLeft(_amount, _amountInFV, _unitPrice);\n            orderExists = orders.hasOrders();\n        }\n\n        partiallyFilledOrder = PartiallyFilledOrder(\n            partiallyRemovedOrder.orderId,\n            partiallyRemovedOrder.maker,\n            partiallyRemovedOrder.amount,\n            partiallyRemovedOrder.futureValue\n        );\n    }\n\n    function setInitialBlockUnitPrice(OrderBook storage self, uint256 _unitPrice) internal {\n        self.blockUnitPriceHistory = uint16(_unitPrice);\n        self.lastBlockUnitPriceTimestamp = uint48(block.timestamp);\n        self.lastOrderTimestamp = uint48(block.timestamp);\n    }\n\n    function updateBlockUnitPriceHistory(\n        OrderBook storage self,\n        uint256 _filledAmount,\n        uint256 _filledFutureValue,\n        uint256 _minimumReliableAmount\n    ) internal returns (uint256 latestBlockUnitPrice, bool isUpdated) {\n        latestBlockUnitPrice = _unpackBlockUnitPriceHistory(self.blockUnitPriceHistory)[0];\n\n        if (self.lastOrderTimestamp != block.timestamp) {\n            if (self.isReliableBlock) {\n                latestBlockUnitPrice = (self.blockTotalAmount * Constants.PRICE_DIGIT).div(\n                    self.blockTotalFutureValue\n                );\n\n                // Remove the oldest block unit price and add the latest block unit price\n                self.blockUnitPriceHistory =\n                    uint16(latestBlockUnitPrice) |\n                    (self.blockUnitPriceHistory << 16);\n                self.lastBlockUnitPriceTimestamp = uint48(block.timestamp);\n                isUpdated = true;\n            }\n\n            self.lastOrderTimestamp = uint48(block.timestamp);\n            self.blockTotalAmount = _filledAmount;\n            self.blockTotalFutureValue = _filledFutureValue;\n            self.isReliableBlock = false;\n        } else {\n            self.blockTotalAmount += _filledAmount;\n            self.blockTotalFutureValue += _filledFutureValue;\n        }\n\n        if (\n            self.blockTotalAmount >= _minimumReliableAmount ||\n            (self.blockTotalAmount > 0 && latestBlockUnitPrice == 0)\n        ) {\n            self.isReliableBlock = true;\n        }\n    }\n\n    function removeOrder(\n        OrderBook storage self,\n        address _user,\n        uint48 _orderId\n    ) internal returns (ProtocolTypes.Side, uint256, uint256) {\n        (ProtocolTypes.Side side, uint256 unitPrice, , ) = _unpackOrder(self.orders[_orderId]);\n        uint256 removedAmount;\n\n        if (side == ProtocolTypes.Side.LEND) {\n            removedAmount = self.lendOrders[self.maturity].removeOrder(unitPrice, _orderId);\n            _removeOrderIdFromOrders(self.activeLendOrderIds[_user], _orderId);\n        } else if (side == ProtocolTypes.Side.BORROW) {\n            removedAmount = self.borrowOrders[self.maturity].removeOrder(unitPrice, _orderId);\n            _removeOrderIdFromOrders(self.activeBorrowOrderIds[_user], _orderId);\n        }\n\n        delete self.orders[_orderId];\n\n        return (side, removedAmount, unitPrice);\n    }\n\n    function calculateItayoseResult(\n        OrderBook storage self\n    )\n        internal\n        view\n        returns (\n            uint256 openingUnitPrice,\n            uint256 lastLendUnitPrice,\n            uint256 lastBorrowUnitPrice,\n            uint256 totalOffsetAmount\n        )\n    {\n        uint256 lendUnitPrice = self.lendOrders[self.maturity].last();\n        uint256 borrowUnitPrice = self.borrowOrders[self.maturity].first();\n        uint256 lendAmount = self.lendOrders[self.maturity].getNodeTotalAmount(lendUnitPrice);\n        uint256 borrowAmount = self.borrowOrders[self.maturity].getNodeTotalAmount(borrowUnitPrice);\n\n        OrderStatisticsTreeLib.Tree storage borrowOrders = self.borrowOrders[self.maturity];\n        OrderStatisticsTreeLib.Tree storage lendOrders = self.lendOrders[self.maturity];\n\n        // Return 0 if no orders is filled\n        if (borrowUnitPrice > lendUnitPrice || borrowUnitPrice == 0 || lendUnitPrice == 0) {\n            return (0, 0, 0, 0);\n        }\n\n        while (borrowUnitPrice <= lendUnitPrice && borrowUnitPrice > 0 && lendUnitPrice > 0) {\n            lastLendUnitPrice = lendUnitPrice;\n            lastBorrowUnitPrice = borrowUnitPrice;\n\n            if (lendAmount > borrowAmount) {\n                openingUnitPrice = lendUnitPrice;\n                totalOffsetAmount += borrowAmount;\n                lendAmount -= borrowAmount;\n                borrowUnitPrice = borrowOrders.next(borrowUnitPrice);\n                borrowAmount = borrowOrders.getNodeTotalAmount(borrowUnitPrice);\n            } else if (lendAmount < borrowAmount) {\n                openingUnitPrice = borrowUnitPrice;\n                totalOffsetAmount += lendAmount;\n                borrowAmount -= lendAmount;\n                lendUnitPrice = lendOrders.prev(lendUnitPrice);\n                lendAmount = lendOrders.getNodeTotalAmount(lendUnitPrice);\n            } else {\n                openingUnitPrice = (lendUnitPrice + borrowUnitPrice).div(2);\n                totalOffsetAmount += lendAmount;\n                lendUnitPrice = lendOrders.prev(lendUnitPrice);\n                borrowUnitPrice = borrowOrders.next(borrowUnitPrice);\n                lendAmount = lendOrders.getNodeTotalAmount(lendUnitPrice);\n                borrowAmount = borrowOrders.getNodeTotalAmount(borrowUnitPrice);\n            }\n        }\n    }\n\n    function getOrderExecutionConditions(\n        OrderBook storage self,\n        ProtocolTypes.Side _side,\n        uint256 _unitPrice,\n        uint256 _circuitBreakerLimitRange,\n        bool _isReadOnly\n    )\n        internal\n        view\n        returns (\n            bool isFilled,\n            uint256 executedUnitPrice,\n            bool ignoreRemainingAmount,\n            bool orderExists\n        )\n    {\n        bool isLend = _side == ProtocolTypes.Side.LEND;\n        uint256 cbThresholdUnitPrice;\n        uint256 bestUnitPrice;\n\n        if (isLend) {\n            bestUnitPrice = self.borrowOrders[self.maturity].first();\n            cbThresholdUnitPrice = getLendCircuitBreakerThreshold(\n                self,\n                _circuitBreakerLimitRange,\n                _isReadOnly\n            );\n        } else {\n            bestUnitPrice = self.lendOrders[self.maturity].last();\n            cbThresholdUnitPrice = getBorrowCircuitBreakerThreshold(\n                self,\n                _circuitBreakerLimitRange,\n                _isReadOnly\n            );\n        }\n\n        orderExists = bestUnitPrice != 0;\n\n        if (\n            _unitPrice == 0 ||\n            (orderExists &&\n                cbThresholdUnitPrice != 0 &&\n                ((isLend && _unitPrice > cbThresholdUnitPrice) ||\n                    (!isLend && _unitPrice < cbThresholdUnitPrice)))\n        ) {\n            executedUnitPrice = cbThresholdUnitPrice;\n            ignoreRemainingAmount = true;\n        } else {\n            executedUnitPrice = _unitPrice;\n            ignoreRemainingAmount = false;\n        }\n\n        if (orderExists) {\n            isFilled = isLend\n                ? bestUnitPrice <= executedUnitPrice\n                : bestUnitPrice >= executedUnitPrice;\n        }\n    }\n\n    function getLendCircuitBreakerThreshold(\n        OrderBook storage self,\n        uint256 _circuitBreakerLimitRange,\n        bool _isReadOnly\n    ) internal view returns (uint256 cbThresholdUnitPrice) {\n        uint256 blockUnitPriceAverage = getBlockUnitPriceAverage(self, 3, _isReadOnly);\n        cbThresholdUnitPrice = (blockUnitPriceAverage *\n            (Constants.PCT_DIGIT + _circuitBreakerLimitRange * 2)).div(Constants.PCT_DIGIT);\n\n        if (cbThresholdUnitPrice > Constants.PRICE_DIGIT || blockUnitPriceAverage == 0) {\n            cbThresholdUnitPrice = Constants.PRICE_DIGIT;\n        } else if (\n            cbThresholdUnitPrice < blockUnitPriceAverage + CIRCUIT_BREAKER_MINIMUM_LEND_RANGE\n        ) {\n            cbThresholdUnitPrice = blockUnitPriceAverage + CIRCUIT_BREAKER_MINIMUM_LEND_RANGE;\n        }\n    }\n\n    function getBorrowCircuitBreakerThreshold(\n        OrderBook storage self,\n        uint256 _circuitBreakerLimitRange,\n        bool _isReadOnly\n    ) internal view returns (uint256 cbThresholdUnitPrice) {\n        uint256 blockUnitPriceAverage = getBlockUnitPriceAverage(self, 5, _isReadOnly);\n        cbThresholdUnitPrice = (blockUnitPriceAverage *\n            (Constants.PCT_DIGIT - _circuitBreakerLimitRange)).div(Constants.PCT_DIGIT);\n\n        if (\n            cbThresholdUnitPrice == 0 ||\n            blockUnitPriceAverage == 0 ||\n            blockUnitPriceAverage <= CIRCUIT_BREAKER_MINIMUM_BORROW_RANGE\n        ) {\n            cbThresholdUnitPrice = 1;\n        } else if (\n            blockUnitPriceAverage < cbThresholdUnitPrice + CIRCUIT_BREAKER_MINIMUM_BORROW_RANGE\n        ) {\n            cbThresholdUnitPrice = blockUnitPriceAverage - CIRCUIT_BREAKER_MINIMUM_BORROW_RANGE;\n        }\n    }\n\n    /**\n     * @notice Increases and returns id of last order in order book.\n     * @return The new order id\n     */\n    function _nextOrderId(OrderBook storage self) private returns (uint48) {\n        self.lastOrderId++;\n        return self.lastOrderId;\n    }\n\n    function _removeOrderIdFromOrders(uint48[] storage orders, uint256 orderId) private {\n        uint256 lastOrderIndex = orders.length - 1;\n        for (uint256 i; i <= lastOrderIndex; i++) {\n            if (orders[i] == orderId) {\n                if (i != lastOrderIndex) {\n                    uint48 lastOrderId = orders[lastOrderIndex];\n                    orders[i] = lastOrderId;\n                }\n\n                orders.pop();\n                break;\n            }\n        }\n    }\n\n    /**\n     * @notice Packs order parameters into uint256\n     */\n    function _packOrder(\n        ProtocolTypes.Side _side,\n        uint256 _unitPrice,\n        uint256 _maturity,\n        uint256 _timestamp\n    ) private pure returns (uint256) {\n        return uint256(_side) | (_unitPrice << 8) | (_maturity << 24) | (_timestamp << 88);\n    }\n\n    /**\n     * @notice Unpacks order parameters from uint256\n     */\n    function _unpackOrder(\n        uint256 _order\n    )\n        private\n        pure\n        returns (ProtocolTypes.Side side, uint256 unitPrice, uint256 maturity, uint256 timestamp)\n    {\n        side = ProtocolTypes.Side(uint8(_order));\n        unitPrice = uint16(_order >> 8);\n        maturity = uint64(_order >> 24);\n        timestamp = uint64(_order >> 88);\n    }\n\n    function _unpackBlockUnitPriceHistory(\n        uint80 _blockUnitPriceHistory\n    ) private pure returns (uint256[] memory prices) {\n        prices = new uint256[](5);\n\n        prices[0] = uint16(_blockUnitPriceHistory);\n        prices[1] = uint16(_blockUnitPriceHistory >> 16);\n        prices[2] = uint16(_blockUnitPriceHistory >> 32);\n        prices[3] = uint16(_blockUnitPriceHistory >> 48);\n        prices[4] = uint16(_blockUnitPriceHistory >> 64);\n    }\n}\n"
      },
      "contracts/protocol/libraries/OrderStatisticsTreeLib.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\n// libraries\nimport {Constants} from \"../libraries/Constants.sol\";\n// types\nimport {RoundingUint256} from \"./math/RoundingUint256.sol\";\n\nstruct RemainingOrder {\n    uint48 orderId;\n    address maker;\n    uint256 amount;\n    uint256 unitPrice;\n}\n\nstruct PartiallyRemovedOrder {\n    uint48 orderId;\n    address maker;\n    uint256 amount;\n    uint256 futureValue;\n}\n\n/**\n * @notice OrderStatisticsTreeLib is a Red-Black Tree binary search library\n * based on the following library that is extended to manage order data.\n *\n * https://github.com/rob-Hitchens/OrderStatisticsTree\n *\n */\nlibrary OrderStatisticsTreeLib {\n    using RoundingUint256 for uint256;\n    uint256 private constant EMPTY = 0;\n\n    struct Node {\n        uint256 parent;\n        uint256 left;\n        uint256 right;\n        bool red;\n        uint48 head;\n        uint48 tail;\n        uint256 orderCounter;\n        uint256 orderTotalAmount;\n        mapping(uint48 orderId => OrderItem) orders;\n    }\n\n    struct Tree {\n        uint256 root;\n        mapping(uint256 value => Node) nodes;\n    }\n\n    struct OrderItem {\n        uint48 orderId;\n        uint48 next;\n        uint48 prev;\n        address maker;\n        uint256 amount;\n    }\n\n    function first(Tree storage self) internal view returns (uint256 value) {\n        value = self.root;\n        if (value == EMPTY) return 0;\n        while (self.nodes[value].left != EMPTY) {\n            value = self.nodes[value].left;\n        }\n    }\n\n    function last(Tree storage self) internal view returns (uint256 value) {\n        value = self.root;\n        if (value == EMPTY) return 0;\n        while (self.nodes[value].right != EMPTY) {\n            value = self.nodes[value].right;\n        }\n    }\n\n    function hasOrders(Tree storage self) internal view returns (bool) {\n        return self.root != EMPTY;\n    }\n\n    function next(Tree storage self, uint256 value) internal view returns (uint256 cursor) {\n        require(value != EMPTY, \"OrderStatisticsTreeLib: Starting value cannot be zero\");\n        if (self.nodes[value].right != EMPTY) {\n            cursor = treeMinimum(self, self.nodes[value].right);\n        } else {\n            cursor = self.nodes[value].parent;\n            while (cursor != EMPTY && value == self.nodes[cursor].right) {\n                value = cursor;\n                cursor = self.nodes[cursor].parent;\n            }\n        }\n    }\n\n    function prev(Tree storage self, uint256 value) internal view returns (uint256 cursor) {\n        require(value != EMPTY, \"OrderStatisticsTreeLib: Starting value cannot be zero\");\n        if (self.nodes[value].left != EMPTY) {\n            cursor = treeMaximum(self, self.nodes[value].left);\n        } else {\n            cursor = self.nodes[value].parent;\n            while (cursor != EMPTY && value == self.nodes[cursor].left) {\n                value = cursor;\n                cursor = self.nodes[cursor].parent;\n            }\n        }\n    }\n\n    function search(\n        Tree storage self,\n        uint256 value\n    ) internal view returns (bool valueExists, uint256 parent) {\n        uint256 cursor = self.root;\n\n        while (cursor != EMPTY) {\n            if (value < cursor) {\n                parent = cursor;\n                cursor = self.nodes[cursor].left;\n            } else if (value > cursor) {\n                parent = cursor;\n                cursor = self.nodes[cursor].right;\n            }\n\n            if (value == cursor) {\n                break;\n            }\n        }\n\n        valueExists = cursor != EMPTY;\n    }\n\n    function exists(Tree storage self, uint256 value) internal view returns (bool) {\n        if (value == self.root) return true;\n\n        uint256 cursor = value;\n        while (self.nodes[cursor].parent != EMPTY) {\n            uint256 parent = self.nodes[cursor].parent;\n            Node storage gn = self.nodes[parent];\n            if (gn.left != cursor && gn.right != cursor) {\n                return false;\n            }\n            if (parent == self.root) {\n                return true;\n            }\n            cursor = parent;\n        }\n        return false;\n    }\n\n    function isActiveOrderId(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal view returns (bool) {\n        return orderIdExists(self, value, orderId) && exists(self, value);\n    }\n\n    function getNode(\n        Tree storage self,\n        uint256 value\n    ) internal view returns (uint256, uint256, uint256, bool, uint256, uint256, uint256, uint256) {\n        require(exists(self, value), \"OrderStatisticsTreeLib: Value does not exist\");\n        Node storage gn = self.nodes[value];\n        return (\n            gn.parent,\n            gn.left,\n            gn.right,\n            gn.red,\n            gn.head,\n            gn.tail,\n            gn.orderCounter,\n            gn.orderTotalAmount\n        );\n    }\n\n    function getNodeCount(Tree storage self, uint256 value) internal view returns (uint256) {\n        Node storage gn = self.nodes[value];\n        return gn.orderCounter;\n    }\n\n    function getNodeTotalAmount(\n        Tree storage self,\n        uint256 value\n    ) internal view returns (uint256 totalAmount) {\n        return self.nodes[value].orderTotalAmount;\n    }\n\n    function getNodeOrderIds(\n        Tree storage self,\n        uint256 value\n    ) internal view returns (uint48[] memory orderIds) {\n        Node storage gn = self.nodes[value];\n        OrderItem memory order = gn.orders[gn.head];\n        orderIds = new uint48[](gn.orderCounter);\n\n        for (uint256 i; i < gn.orderCounter; i++) {\n            orderIds[i] = order.orderId;\n            order = gn.orders[order.next];\n        }\n    }\n\n    function count(Tree storage self) internal view returns (uint256) {\n        return getNodeCount(self, self.root);\n    }\n\n    function insert(Tree storage self, uint256 value) internal {\n        require(value != EMPTY, \"OrderStatisticsTreeLib: Value to insert cannot be zero\");\n        uint256 cursor;\n        uint256 probe = self.root;\n        while (probe != EMPTY) {\n            cursor = probe;\n            if (value < probe) {\n                probe = self.nodes[probe].left;\n            } else if (value > probe) {\n                probe = self.nodes[probe].right;\n            } else if (value == probe) {\n                return;\n            }\n        }\n\n        Node storage nValue = self.nodes[value];\n        // Update order info as a new one if there is already an old node\n        if (self.root == EMPTY || nValue.orderCounter != 0) {\n            nValue.orderCounter = 0;\n            nValue.orderTotalAmount = 0;\n            _setHead(self, value, 0);\n            _setTail(self, value, 0);\n        }\n        nValue.parent = cursor;\n        nValue.left = EMPTY;\n        nValue.right = EMPTY;\n        nValue.red = true;\n\n        if (cursor == EMPTY) {\n            self.root = value;\n        } else if (value < cursor) {\n            self.nodes[cursor].left = value;\n        } else {\n            self.nodes[cursor].right = value;\n        }\n        insertFixup(self, value);\n    }\n\n    function remove(Tree storage self, uint256 value) internal {\n        require(value != EMPTY, \"OrderStatisticsTreeLib: Value to remove cannot be zero\");\n        Node storage nValue = self.nodes[value];\n        uint256 probe;\n        uint256 cursor;\n        if (nValue.orderCounter == 0) {\n            if (self.nodes[value].left == EMPTY || self.nodes[value].right == EMPTY) {\n                cursor = value;\n            } else {\n                cursor = self.nodes[value].right;\n                while (self.nodes[cursor].left != EMPTY) {\n                    cursor = self.nodes[cursor].left;\n                }\n            }\n            if (self.nodes[cursor].left != EMPTY) {\n                probe = self.nodes[cursor].left;\n            } else {\n                probe = self.nodes[cursor].right;\n            }\n            uint256 cursorParent = self.nodes[cursor].parent;\n            self.nodes[probe].parent = cursorParent;\n            if (cursorParent != EMPTY) {\n                if (cursor == self.nodes[cursorParent].left) {\n                    self.nodes[cursorParent].left = probe;\n                } else {\n                    self.nodes[cursorParent].right = probe;\n                }\n            } else {\n                self.root = probe;\n            }\n            bool doFixup = !self.nodes[cursor].red;\n            if (cursor != value) {\n                replaceParent(self, cursor, value);\n                self.nodes[cursor].left = self.nodes[value].left;\n                self.nodes[self.nodes[cursor].left].parent = cursor;\n                self.nodes[cursor].right = self.nodes[value].right;\n                self.nodes[self.nodes[cursor].right].parent = cursor;\n                self.nodes[cursor].red = self.nodes[value].red;\n                (cursor, value) = (value, cursor);\n            }\n            if (doFixup) {\n                removeFixup(self, probe);\n            }\n            delete self.nodes[cursor];\n        }\n    }\n\n    function treeMinimum(Tree storage self, uint256 value) private view returns (uint256) {\n        while (self.nodes[value].left != EMPTY) {\n            value = self.nodes[value].left;\n        }\n        return value;\n    }\n\n    function treeMaximum(Tree storage self, uint256 value) private view returns (uint256) {\n        while (self.nodes[value].right != EMPTY) {\n            value = self.nodes[value].right;\n        }\n        return value;\n    }\n\n    function rotateLeft(Tree storage self, uint256 value) private {\n        uint256 cursor = self.nodes[value].right;\n        uint256 parent = self.nodes[value].parent;\n        uint256 cursorLeft = self.nodes[cursor].left;\n        self.nodes[value].right = cursorLeft;\n        if (cursorLeft != EMPTY) {\n            self.nodes[cursorLeft].parent = value;\n        }\n        self.nodes[cursor].parent = parent;\n        if (parent == EMPTY) {\n            self.root = cursor;\n        } else if (value == self.nodes[parent].left) {\n            self.nodes[parent].left = cursor;\n        } else {\n            self.nodes[parent].right = cursor;\n        }\n        self.nodes[cursor].left = value;\n        self.nodes[value].parent = cursor;\n    }\n\n    function rotateRight(Tree storage self, uint256 value) private {\n        uint256 cursor = self.nodes[value].left;\n        uint256 parent = self.nodes[value].parent;\n        uint256 cursorRight = self.nodes[cursor].right;\n        self.nodes[value].left = cursorRight;\n        if (cursorRight != EMPTY) {\n            self.nodes[cursorRight].parent = value;\n        }\n        self.nodes[cursor].parent = parent;\n        if (parent == EMPTY) {\n            self.root = cursor;\n        } else if (value == self.nodes[parent].right) {\n            self.nodes[parent].right = cursor;\n        } else {\n            self.nodes[parent].left = cursor;\n        }\n        self.nodes[cursor].right = value;\n        self.nodes[value].parent = cursor;\n    }\n\n    function insertFixup(Tree storage self, uint256 value) private {\n        uint256 cursor;\n        while (value != self.root && self.nodes[self.nodes[value].parent].red) {\n            uint256 valueParent = self.nodes[value].parent;\n            if (valueParent == self.nodes[self.nodes[valueParent].parent].left) {\n                cursor = self.nodes[self.nodes[valueParent].parent].right;\n                if (self.nodes[cursor].red) {\n                    self.nodes[valueParent].red = false;\n                    self.nodes[cursor].red = false;\n                    self.nodes[self.nodes[valueParent].parent].red = true;\n                    value = self.nodes[valueParent].parent;\n                } else {\n                    if (value == self.nodes[valueParent].right) {\n                        value = valueParent;\n                        rotateLeft(self, value);\n                    }\n                    valueParent = self.nodes[value].parent;\n                    self.nodes[valueParent].red = false;\n                    self.nodes[self.nodes[valueParent].parent].red = true;\n                    rotateRight(self, self.nodes[valueParent].parent);\n                }\n            } else {\n                cursor = self.nodes[self.nodes[valueParent].parent].left;\n                if (self.nodes[cursor].red) {\n                    self.nodes[valueParent].red = false;\n                    self.nodes[cursor].red = false;\n                    self.nodes[self.nodes[valueParent].parent].red = true;\n                    value = self.nodes[valueParent].parent;\n                } else {\n                    if (value == self.nodes[valueParent].left) {\n                        value = valueParent;\n                        rotateRight(self, value);\n                    }\n                    valueParent = self.nodes[value].parent;\n                    self.nodes[valueParent].red = false;\n                    self.nodes[self.nodes[valueParent].parent].red = true;\n                    rotateLeft(self, self.nodes[valueParent].parent);\n                }\n            }\n        }\n        self.nodes[self.root].red = false;\n    }\n\n    function replaceParent(Tree storage self, uint256 a, uint256 b) private {\n        uint256 bParent = self.nodes[b].parent;\n        self.nodes[a].parent = bParent;\n        if (bParent == EMPTY) {\n            self.root = a;\n        } else {\n            if (b == self.nodes[bParent].left) {\n                self.nodes[bParent].left = a;\n            } else {\n                self.nodes[bParent].right = a;\n            }\n        }\n    }\n\n    function removeFixup(Tree storage self, uint256 value) private {\n        uint256 cursor;\n        while (value != self.root && !self.nodes[value].red) {\n            uint256 valueParent = self.nodes[value].parent;\n            if (value == self.nodes[valueParent].left) {\n                cursor = self.nodes[valueParent].right;\n                if (self.nodes[cursor].red) {\n                    self.nodes[cursor].red = false;\n                    self.nodes[valueParent].red = true;\n                    rotateLeft(self, valueParent);\n                    cursor = self.nodes[valueParent].right;\n                }\n                if (\n                    !self.nodes[self.nodes[cursor].left].red &&\n                    !self.nodes[self.nodes[cursor].right].red\n                ) {\n                    self.nodes[cursor].red = true;\n                    value = valueParent;\n                } else {\n                    if (!self.nodes[self.nodes[cursor].right].red) {\n                        self.nodes[self.nodes[cursor].left].red = false;\n                        self.nodes[cursor].red = true;\n                        rotateRight(self, cursor);\n                        cursor = self.nodes[valueParent].right;\n                    }\n                    self.nodes[cursor].red = self.nodes[valueParent].red;\n                    self.nodes[valueParent].red = false;\n                    self.nodes[self.nodes[cursor].right].red = false;\n                    rotateLeft(self, valueParent);\n                    value = self.root;\n                }\n            } else {\n                cursor = self.nodes[valueParent].left;\n                if (self.nodes[cursor].red) {\n                    self.nodes[cursor].red = false;\n                    self.nodes[valueParent].red = true;\n                    rotateRight(self, valueParent);\n                    cursor = self.nodes[valueParent].left;\n                }\n                if (\n                    !self.nodes[self.nodes[cursor].right].red &&\n                    !self.nodes[self.nodes[cursor].left].red\n                ) {\n                    self.nodes[cursor].red = true;\n                    value = valueParent;\n                } else {\n                    if (!self.nodes[self.nodes[cursor].left].red) {\n                        self.nodes[self.nodes[cursor].right].red = false;\n                        self.nodes[cursor].red = true;\n                        rotateLeft(self, cursor);\n                        cursor = self.nodes[valueParent].left;\n                    }\n                    self.nodes[cursor].red = self.nodes[valueParent].red;\n                    self.nodes[valueParent].red = false;\n                    self.nodes[self.nodes[cursor].left].red = false;\n                    rotateRight(self, valueParent);\n                    value = self.root;\n                }\n            }\n        }\n        self.nodes[value].red = false;\n    }\n\n    function calculateDroppedAmountFromLeft(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue\n    )\n        internal\n        view\n        returns (uint256 droppedValue, uint256 droppedAmount, uint256 droppedAmountInFV)\n    {\n        (droppedValue, , , droppedAmount, droppedAmountInFV, , ) = _calculateDroppedAmountFromLeft(\n            self,\n            amount,\n            amountInFV,\n            limitValue,\n            first(self)\n        );\n    }\n\n    function calculateDroppedAmountFromRight(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue\n    )\n        internal\n        view\n        returns (uint256 droppedValue, uint256 droppedAmount, uint256 droppedAmountInFV)\n    {\n        (droppedValue, , , droppedAmount, droppedAmountInFV, , ) = _calculateDroppedAmountFromRight(\n            self,\n            amount,\n            amountInFV,\n            limitValue,\n            last(self)\n        );\n    }\n\n    function dropLeft(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue\n    )\n        internal\n        returns (\n            uint256 droppedValue,\n            uint256 droppedAmount,\n            uint256 droppedAmountInFV,\n            uint256 remainingAmount,\n            PartiallyRemovedOrder memory partiallyRemovedOrder\n        )\n    {\n        uint256 cursor = first(self);\n        uint256 cursorNodeAmount;\n        uint256 exceededAmount;\n        uint256 exceededAmountInFV;\n\n        require(cursor <= limitValue || limitValue == 0, \"Insufficient limit value\");\n\n        (\n            droppedValue,\n            cursor,\n            cursorNodeAmount,\n            droppedAmount,\n            droppedAmountInFV,\n            exceededAmount,\n            exceededAmountInFV\n        ) = _calculateDroppedAmountFromLeft(self, amount, amountInFV, limitValue, cursor);\n\n        uint256 totalNodeAmount = droppedAmount + exceededAmount;\n\n        if (totalNodeAmount > 0) {\n            if (exceededAmount > 0) {\n                cursor = droppedValue;\n                // Update order ids in the node.\n                partiallyRemovedOrder = removeOrders(\n                    self,\n                    cursor,\n                    cursorNodeAmount - exceededAmount\n                );\n            } else if (exceededAmountInFV > 0) {\n                cursor = droppedValue;\n                // Update order ids in the node.\n                partiallyRemovedOrder = removeOrders(\n                    self,\n                    cursor,\n                    cursorNodeAmount - _calculatePresentValue(cursor, exceededAmountInFV)\n                );\n            }\n\n            self.nodes[cursor].left = 0;\n\n            uint256 parent = self.nodes[cursor].parent;\n\n            if (cursor != EMPTY) {\n                while (parent != EMPTY) {\n                    if (parent > cursor) {\n                        // Relink the nodes\n                        if (self.nodes[cursor].parent != parent) {\n                            self.nodes[cursor].parent = parent;\n                            self.nodes[parent].left = cursor;\n                        }\n\n                        cursor = parent;\n                    }\n\n                    parent = self.nodes[parent].parent;\n                }\n            }\n        }\n\n        if (amount > totalNodeAmount) {\n            remainingAmount = amount - totalNodeAmount;\n        }\n\n        uint256 lastNode = last(self);\n\n        if (lastNode == droppedValue && self.nodes[lastNode].orderTotalAmount == 0) {\n            // The case that all node is dropped.\n            self.root = EMPTY;\n        } else if (\n            droppedValue > self.root ||\n            (droppedValue == self.root && droppedAmount == totalNodeAmount)\n        ) {\n            // The case that the root node is dropped\n            self.root = cursor;\n            self.nodes[cursor].parent = 0;\n        }\n\n        rotateTreeToLeft(self);\n    }\n\n    function dropRight(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue\n    )\n        internal\n        returns (\n            uint256 droppedValue,\n            uint256 droppedAmount,\n            uint256 droppedAmountInFV,\n            uint256 remainingAmount,\n            PartiallyRemovedOrder memory partiallyRemovedOrder\n        )\n    {\n        uint256 cursor = last(self);\n        uint256 cursorNodeAmount;\n        uint256 exceededAmount;\n        uint256 exceededAmountInFV;\n\n        require(cursor >= limitValue || limitValue == 0, \"Insufficient limit value\");\n\n        (\n            droppedValue,\n            cursor,\n            cursorNodeAmount,\n            droppedAmount,\n            droppedAmountInFV,\n            exceededAmount,\n            exceededAmountInFV\n        ) = _calculateDroppedAmountFromRight(self, amount, amountInFV, limitValue, cursor);\n\n        uint256 totalNodeAmount = droppedAmount + exceededAmount;\n\n        if (totalNodeAmount > 0) {\n            if (exceededAmount > 0) {\n                cursor = droppedValue;\n                // Update order ids in the node.\n                partiallyRemovedOrder = removeOrders(\n                    self,\n                    cursor,\n                    cursorNodeAmount - exceededAmount\n                );\n            } else if (exceededAmountInFV > 0) {\n                cursor = droppedValue;\n                // Update order ids in the node.\n                partiallyRemovedOrder = removeOrders(\n                    self,\n                    cursor,\n                    cursorNodeAmount - _calculatePresentValue(cursor, exceededAmountInFV)\n                );\n            }\n\n            self.nodes[cursor].right = 0;\n\n            uint256 parent = self.nodes[cursor].parent;\n\n            if (cursor != EMPTY) {\n                while (parent != EMPTY) {\n                    if (parent < cursor) {\n                        // Relink the nodes\n                        if (self.nodes[cursor].parent != parent) {\n                            self.nodes[cursor].parent = parent;\n                            self.nodes[parent].right = cursor;\n                        }\n\n                        cursor = parent;\n                    }\n\n                    parent = self.nodes[parent].parent;\n                }\n            }\n        }\n\n        if (amount > totalNodeAmount) {\n            remainingAmount = amount - totalNodeAmount;\n        }\n\n        uint256 firstNode = first(self);\n\n        if (firstNode == droppedValue && self.nodes[firstNode].orderTotalAmount == 0) {\n            // The case that all node is dropped.\n            self.root = EMPTY;\n        } else if (\n            droppedValue < self.root ||\n            (droppedValue == self.root && droppedAmount == totalNodeAmount)\n        ) {\n            // The case that the root node is dropped\n            self.root = cursor;\n            self.nodes[cursor].parent = 0;\n        }\n\n        rotateTreeToRight(self);\n    }\n\n    function rotateTreeToLeft(Tree storage self) internal {\n        if (self.nodes[self.root].left == 0 && self.nodes[self.root].right != 0) {\n            if (self.nodes[self.nodes[self.root].right].left != 0) {\n                rotateRight(self, self.nodes[self.root].right);\n            }\n            rotateLeft(self, self.root);\n        }\n\n        if (self.nodes[self.root].red) {\n            self.nodes[self.root].red = false;\n        }\n    }\n\n    function rotateTreeToRight(Tree storage self) internal {\n        if (self.nodes[self.root].right == 0 && self.nodes[self.root].left != 0) {\n            if (self.nodes[self.nodes[self.root].left].right != 0) {\n                rotateLeft(self, self.nodes[self.root].left);\n            }\n            rotateRight(self, self.root);\n        }\n\n        if (self.nodes[self.root].red) {\n            self.nodes[self.root].red = false;\n        }\n    }\n\n    function getFutureValue(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal view returns (uint256) {\n        return _calculateFutureValue(value, self.nodes[value].orders[orderId].amount);\n    }\n\n    // Double linked list functions\n    /**\n     * @dev Retrieves the Object denoted by `_id`.\n     */\n    function getOrderById(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal view returns (address maker, uint256 amount) {\n        Node storage gn = self.nodes[value];\n        OrderItem memory order = gn.orders[orderId];\n\n        maker = order.maker;\n        amount = order.amount;\n    }\n\n    /**\n     * @dev Return boolean if value, amount and orderId exist in doubly linked list\n     */\n    function orderIdExists(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal view returns (bool) {\n        uint48 cursor = orderId;\n        Node storage gn = self.nodes[value];\n        OrderItem memory order = gn.orders[cursor];\n\n        if (order.orderId != cursor) {\n            return false;\n        }\n\n        while (order.prev != EMPTY) {\n            cursor = order.prev;\n            order = gn.orders[cursor];\n        }\n\n        return cursor == gn.head;\n    }\n\n    function insertOrder(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId,\n        address user,\n        uint256 amount\n    ) internal {\n        require(amount > 0, \"Insufficient amount\");\n        require(value <= Constants.PRICE_DIGIT, \"Value too high\");\n        insert(self, value);\n\n        addTail(self, value, orderId, user, amount);\n    }\n\n    function removeOrder(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal returns (uint256 amount) {\n        amount = _removeOrder(self, value, orderId);\n        remove(self, value);\n    }\n\n    function removeOrders(\n        Tree storage self,\n        uint256 value,\n        uint256 _amount\n    ) internal returns (PartiallyRemovedOrder memory partiallyRemovedOrder) {\n        Node storage gn = self.nodes[value];\n\n        require(\n            gn.orderTotalAmount >= _amount,\n            \"OrderStatisticsTreeLib: Amount to remove is insufficient\"\n        );\n\n        uint256 remainingAmount = _amount;\n        OrderItem memory currentOrder = gn.orders[gn.head];\n        uint48 orderId = gn.head;\n\n        while (orderId != 0 && remainingAmount != 0) {\n            currentOrder = gn.orders[orderId];\n\n            if (currentOrder.amount <= remainingAmount) {\n                remainingAmount -= currentOrder.amount;\n                orderId = currentOrder.next;\n            } else {\n                partiallyRemovedOrder = PartiallyRemovedOrder(\n                    currentOrder.orderId,\n                    currentOrder.maker,\n                    remainingAmount,\n                    _calculateFutureValue(value, remainingAmount)\n                );\n                currentOrder = gn.orders[currentOrder.prev];\n                break;\n            }\n        }\n\n        if (currentOrder.orderId != 0) {\n            _removeOrders(self, value, currentOrder.orderId);\n        }\n\n        if (partiallyRemovedOrder.amount > 0) {\n            self.nodes[value].orders[partiallyRemovedOrder.orderId].amount -= partiallyRemovedOrder\n                .amount;\n            self.nodes[value].orderTotalAmount -= partiallyRemovedOrder.amount;\n        }\n    }\n\n    /**\n     * @dev Insert a new OrderItem as the new Head with `_amount` in the amount field, and orderId.\n     */\n    function addHead(\n        Tree storage self,\n        uint256 _value,\n        uint48 _orderId,\n        address _user,\n        uint256 _amount\n    ) internal {\n        Node storage gn = self.nodes[_value];\n        uint48 orderId = _createOrder(self, _value, _orderId, _user, _amount);\n        _link(self, _value, orderId, gn.head);\n        _setHead(self, _value, orderId);\n        if (gn.tail == 0) _setTail(self, _value, orderId);\n    }\n\n    /**\n     * @dev Insert a new OrderItem as the new Tail with `_amount` in the amount field, and orderId.\n     */\n    function addTail(\n        Tree storage self,\n        uint256 _value,\n        uint48 _orderId,\n        address _user,\n        uint256 _amount\n    ) internal {\n        Node storage gn = self.nodes[_value];\n\n        if (gn.head == 0) {\n            addHead(self, _value, _orderId, _user, _amount);\n        } else {\n            uint48 orderId = _createOrder(self, _value, _orderId, _user, _amount);\n            _link(self, _value, gn.tail, orderId);\n            _setTail(self, _value, orderId);\n        }\n    }\n\n    function _calculateDroppedAmountFromLeft(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue,\n        uint256 firstValue\n    )\n        private\n        view\n        returns (\n            uint256 droppedValue,\n            uint256 cursor,\n            uint256 cursorNodeAmount,\n            uint256 droppedAmount,\n            uint256 droppedAmountInFV,\n            uint256 exceededAmount,\n            uint256 exceededAmountInFV\n        )\n    {\n        cursor = firstValue;\n\n        while (\n            (droppedAmount < amount || amount == EMPTY) &&\n            (droppedAmountInFV < amountInFV || amountInFV == EMPTY) &&\n            cursor != EMPTY &&\n            (cursor <= limitValue || limitValue == EMPTY)\n        ) {\n            cursorNodeAmount = self.nodes[cursor].orderTotalAmount;\n            droppedValue = cursor;\n\n            droppedAmountInFV += _calculateFutureValue(cursor, cursorNodeAmount);\n            droppedAmount += cursorNodeAmount;\n\n            if (droppedAmount > amount && amount != EMPTY) {\n                exceededAmount = droppedAmount - amount;\n                exceededAmountInFV = _calculateFutureValue(cursor, exceededAmount);\n            } else if (droppedAmountInFV > amountInFV && amountInFV != EMPTY) {\n                exceededAmountInFV = droppedAmountInFV - amountInFV;\n                exceededAmount = _calculatePresentValue(cursor, exceededAmountInFV);\n            }\n\n            cursor = next(self, cursor);\n        }\n\n        if (exceededAmount > 0) {\n            droppedAmount -= exceededAmount;\n        }\n        if (exceededAmountInFV > 0) {\n            droppedAmountInFV -= exceededAmountInFV;\n        }\n    }\n\n    function _calculateDroppedAmountFromRight(\n        Tree storage self,\n        uint256 amount,\n        uint256 amountInFV,\n        uint256 limitValue,\n        uint256 lastValue\n    )\n        private\n        view\n        returns (\n            uint256 droppedValue,\n            uint256 cursor,\n            uint256 cursorNodeAmount,\n            uint256 droppedAmount,\n            uint256 droppedAmountInFV,\n            uint256 exceededAmount,\n            uint256 exceededAmountInFV\n        )\n    {\n        cursor = lastValue;\n\n        while (\n            (droppedAmount < amount || amount == EMPTY) &&\n            (droppedAmountInFV < amountInFV || amountInFV == EMPTY) &&\n            cursor != EMPTY &&\n            (cursor >= limitValue || limitValue == EMPTY)\n        ) {\n            cursorNodeAmount = self.nodes[cursor].orderTotalAmount;\n            droppedValue = cursor;\n\n            droppedAmountInFV += _calculateFutureValue(cursor, cursorNodeAmount);\n            droppedAmount += cursorNodeAmount;\n\n            if (droppedAmount > amount && amount != EMPTY) {\n                exceededAmount = droppedAmount - amount;\n                exceededAmountInFV = _calculateFutureValue(cursor, exceededAmount);\n            } else if (droppedAmountInFV > amountInFV && amountInFV != EMPTY) {\n                exceededAmountInFV = droppedAmountInFV - amountInFV;\n                exceededAmount = _calculatePresentValue(cursor, exceededAmountInFV);\n            }\n\n            cursor = prev(self, cursor);\n        }\n\n        if (exceededAmount > 0) {\n            droppedAmount -= exceededAmount;\n        }\n        if (exceededAmountInFV > 0) {\n            droppedAmountInFV -= exceededAmountInFV;\n        }\n    }\n\n    /**\n     * @dev Internal function to create an unlinked Order.\n     */\n    function _createOrder(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId,\n        address user,\n        uint256 amount\n    ) internal returns (uint48) {\n        Node storage gn = self.nodes[value];\n        require(\n            gn.orders[orderId].maker == address(0),\n            \"OrderStatisticsTreeLib: Order id already exists\"\n        );\n\n        gn.orderCounter += 1;\n        gn.orderTotalAmount += amount;\n        OrderItem memory order = OrderItem(orderId, 0, 0, user, amount);\n        gn.orders[orderId] = order;\n        return order.orderId;\n    }\n\n    /**\n     * @dev Remove the OrderItem denoted by `_id` from the list.\n     */\n    function _removeOrder(\n        Tree storage self,\n        uint256 value,\n        uint48 orderId\n    ) internal returns (uint256 amount) {\n        require(\n            isActiveOrderId(self, value, orderId),\n            \"OrderStatisticsTreeLib: Order does not exist\"\n        );\n        Node storage gn = self.nodes[value];\n\n        OrderItem memory order = gn.orders[orderId];\n        amount = order.amount;\n\n        if (gn.head == orderId && gn.tail == orderId) {\n            _setHead(self, value, 0);\n            _setTail(self, value, 0);\n        } else if (gn.head == orderId) {\n            _setHead(self, value, order.next);\n            gn.orders[order.next].prev = 0;\n        } else if (gn.tail == orderId) {\n            _setTail(self, value, order.prev);\n            gn.orders[order.prev].next = 0;\n        } else {\n            _link(self, value, order.prev, order.next);\n        }\n        delete gn.orders[order.orderId];\n        gn.orderCounter -= 1;\n        gn.orderTotalAmount -= order.amount;\n    }\n\n    /**\n     * @dev Remove the OrderItems older than or equal `orderId` from the list\n     */\n    function _removeOrders(Tree storage self, uint256 value, uint48 orderId) internal {\n        require(\n            isActiveOrderId(self, value, orderId),\n            \"OrderStatisticsTreeLib: Order does not exist\"\n        );\n        Node storage gn = self.nodes[value];\n\n        OrderItem memory order = gn.orders[orderId];\n        uint48 cursor = gn.head;\n        uint256 removedCount = 1;\n        uint256 removedAmount = gn.orders[cursor].amount;\n\n        while (cursor != orderId) {\n            cursor = gn.orders[cursor].next;\n            removedCount++;\n            removedAmount += gn.orders[cursor].amount;\n        }\n\n        if (gn.tail == orderId) {\n            _setHead(self, value, 0);\n            _setTail(self, value, 0);\n        } else {\n            _setHead(self, value, order.next);\n            gn.orders[order.next].prev = 0;\n        }\n\n        gn.orderCounter -= removedCount;\n        gn.orderTotalAmount -= removedAmount;\n    }\n\n    /**\n     * @dev Internal function to update the Head pointer.\n     */\n    function _setHead(Tree storage self, uint256 value, uint48 orderId) internal {\n        Node storage gn = self.nodes[value];\n\n        gn.head = orderId;\n    }\n\n    /**\n     * @dev Internal function to update the Tail pointer.\n     */\n    function _setTail(Tree storage self, uint256 value, uint48 orderId) internal {\n        Node storage gn = self.nodes[value];\n\n        gn.tail = orderId;\n    }\n\n    /**\n     * @dev Internal function to link an Object to another.\n     */\n    function _link(Tree storage self, uint256 value, uint48 prevId, uint48 nextId) internal {\n        Node storage gn = self.nodes[value];\n\n        gn.orders[prevId].next = nextId;\n        gn.orders[nextId].prev = prevId;\n    }\n\n    function _calculateFutureValue(\n        uint256 unitPrice,\n        uint256 amount\n    ) internal pure returns (uint256) {\n        return (amount * Constants.PRICE_DIGIT).div(unitPrice);\n    }\n\n    function _calculatePresentValue(\n        uint256 unitPrice,\n        uint256 amount\n    ) internal pure returns (uint256) {\n        return (amount * unitPrice).div(Constants.PRICE_DIGIT);\n    }\n}\n"
      },
      "contracts/protocol/storages/LendingMarketStorage.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\nimport {ProtocolTypes} from \"../types/ProtocolTypes.sol\";\nimport {OrderBookLib} from \"../libraries/OrderBookLib.sol\";\n\nstruct ItayoseLog {\n    uint256 openingUnitPrice;\n    uint256 lastLendUnitPrice;\n    uint256 lastBorrowUnitPrice;\n}\n\nlibrary LendingMarketStorage {\n    bytes32 internal constant STORAGE_SLOT =\n        bytes32(uint256(keccak256(\"sf.storage.lendingMarket\")) - 1);\n\n    struct Storage {\n        bytes32 ccy;\n        uint8 lastOrderBookId;\n        // Order fee rate received by protocol (in basis point)\n        uint256 orderFeeRate;\n        // Rate limit range of yield for the circuit breaker\n        uint256 circuitBreakerLimitRange;\n        // Mapping from order book id to order book\n        mapping(uint8 orderBookId => OrderBookLib.OrderBook orderBook) orderBooks;\n        mapping(uint256 maturity => bool isReady) isReady;\n        mapping(uint256 maturity => ItayoseLog log) itayoseLogs;\n    }\n\n    function slot() internal pure returns (Storage storage r) {\n        bytes32 _slot = STORAGE_SLOT;\n        /// @solidity memory-safe-assembly\n        assembly {\n            r.slot := _slot\n        }\n    }\n}\n"
      },
      "contracts/protocol/types/ProtocolTypes.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\n/**\n * @dev ProtocolTypes is a base-level contract that holds common Secured Finance protocol types\n * @author Secured Finance\n */\nlibrary ProtocolTypes {\n    enum Side {\n        LEND,\n        BORROW\n    }\n}\n"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "metadata": {
        "useLiteralContent": true
      },
      "libraries": {}
    }
  }}