diff --git a/contracts/orderbook/src/state.rs b/contracts/orderbook/src/state.rs index d0d3c84..2d7e6dc 100644 --- a/contracts/orderbook/src/state.rs +++ b/contracts/orderbook/src/state.rs @@ -1,7 +1,7 @@ use crate::types::{FilterOwnerOrders, LimitOrder, Orderbook}; use crate::ContractError; use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128}; -use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; +use cw_storage_plus::{Bound, Index, IndexList, IndexedMap, Item, Map, MultiIndex}; pub const MIN_TICK: i64 = -108000000; pub const MAX_TICK: i64 = 342000000; @@ -71,36 +71,55 @@ pub fn new_order_id(storage: &mut dyn Storage) -> Result { // TODO: Add pagination // TODO: How finite do we need queries? +const MAX_PAGE_SIZE: u8 = 100; +const DEFAULT_PAGE_SIZE: u8 = 50; + /// Retrieves a list of `LimitOrder` filtered by the specified `FilterOwnerOrders`. +/// +/// This function allows for filtering orders based on the owner's address, optionally further +/// filtering by book ID or tick ID. It supports pagination through `min`, `max`, and `page_size` parameters. +/// +/// ## Arguments +/// +/// * `storage` - CosmWasm Storage struct +/// * `filter` - Specifies how to filter orders based on the owner. Can be by all orders of the owner, +/// by a specific book, or by a specific tick within a book. +/// * `min` - An optional minimum bound (exclusive) for the order key (orderbook_id, tick, order_id) to start the query. +/// * `max` - An optional maximum bound (exclusive) for the order key to end the query. +/// * `page_size` - An optional maximum number of orders to return. Limited by `MAX_PAGE_SIZE = 100` defaults to `DEFAULT_PAGE_SIZE = 50`. +/// +/// ## Returns +/// +/// A result containing either a vector of `LimitOrder` matching the criteria or an error. pub fn get_orders_by_owner( storage: &dyn Storage, filter: FilterOwnerOrders, + min: Option<(u64, i64, u64)>, + max: Option<(u64, i64, u64)>, + page_size: Option, ) -> StdResult> { - let orders: Vec = match filter { - FilterOwnerOrders::All(owner) => orders() - .idx - .owner - .prefix(owner) - .range(storage, None, None, Order::Ascending) - .filter_map(|item| item.ok()) - .map(|(_, order)| order) - .collect(), - FilterOwnerOrders::ByBook(book_id, owner) => orders() - .idx - .book_and_owner - .prefix((book_id, owner)) - .range(storage, None, None, Order::Ascending) - .filter_map(|item| item.ok()) - .map(|(_, order)| order) - .collect(), + let page_size = page_size.unwrap_or(DEFAULT_PAGE_SIZE).min(MAX_PAGE_SIZE) as usize; + let min = min.map(Bound::exclusive); + let max = max.map(Bound::exclusive); + + // Define the prefix iterator based on the filter + let iter = match filter { + FilterOwnerOrders::All(owner) => orders().idx.owner.prefix(owner), + FilterOwnerOrders::ByBook(book_id, owner) => { + orders().idx.book_and_owner.prefix((book_id, owner)) + } FilterOwnerOrders::ByTick(book_id, tick_id, owner) => orders() .idx .tick_and_owner - .prefix((book_id, tick_id, owner)) - .range(storage, None, None, Order::Ascending) - .filter_map(|item| item.ok()) - .map(|(_, order)| order) - .collect(), + .prefix((book_id, tick_id, owner)), }; + + // Get orders based on pagination + let orders: Vec = iter + .range(storage, min, max, Order::Ascending) + .take(page_size) + .filter_map(|item| item.ok()) + .map(|(_, order)| order) + .collect(); Ok(orders) } diff --git a/contracts/orderbook/src/tests/test_state.rs b/contracts/orderbook/src/tests/test_state.rs index 7108a35..3e22c12 100644 --- a/contracts/orderbook/src/tests/test_state.rs +++ b/contracts/orderbook/src/tests/test_state.rs @@ -109,8 +109,14 @@ fn test_get_orders_by_owner_all() { .unwrap(); }); - let owner_orders: Vec = - get_orders_by_owner(&storage, FilterOwnerOrders::All(Addr::unchecked(owner))).unwrap(); + let owner_orders: Vec = get_orders_by_owner( + &storage, + FilterOwnerOrders::All(Addr::unchecked(owner)), + None, + None, + None, + ) + .unwrap(); assert_eq!(owner_orders.len(), order_amount / 2 + 1); owner_orders.iter().for_each(|order| { @@ -152,6 +158,9 @@ fn test_get_orders_by_owner_by_book() { let owner_orders = get_orders_by_owner( &storage, FilterOwnerOrders::ByBook(book_id, Addr::unchecked(owner)), + None, + None, + None, ) .unwrap(); assert!(!owner_orders.is_empty()); @@ -193,6 +202,9 @@ fn test_get_orders_by_owner_by_tick() { let owner_orders = get_orders_by_owner( &storage, FilterOwnerOrders::ByTick(book_id, tick, Addr::unchecked(owner)), + None, + None, + None, ) .unwrap(); assert!(!owner_orders.is_empty()); @@ -202,3 +214,68 @@ fn test_get_orders_by_owner_by_tick() { }); }); } + +#[test] +fn test_get_orders_by_owner_with_pagination() { + let mut storage = MockStorage::new(); + let order_amount = 100; + let ticks = [0, 1, 2]; + let owner = "owner1"; + let book_id = new_orderbook_id(&mut storage).unwrap(); + + // Create orders for a single owner across different ticks + (0..order_amount).for_each(|i| { + let order_id = new_order_id(&mut storage).unwrap(); + let tick = ticks[i % 3]; + let order = LimitOrder::new( + book_id, + tick, + order_id, + OrderDirection::Ask, + Addr::unchecked(owner), + Uint128::new(i as u128), + ); + orders() + .save(&mut storage, &(book_id, tick, i as u64), &order) + .unwrap(); + }); + + // Test pagination by fetching orders in batches + let mut start_after = None; + let page_size = 8; + let mut fetched_orders = 0; + let mut total_pages = 0; + + loop { + let owner_orders = get_orders_by_owner( + &storage, + FilterOwnerOrders::All(Addr::unchecked(owner)), + start_after, + None, + Some(page_size), + ) + .unwrap(); + + let orders_count = owner_orders.len() as u64; + + fetched_orders += orders_count; + if orders_count == 0 { + break; + } + + total_pages += 1; + + start_after = Some( + owner_orders + .last() + .map(|order| (order.book_id, order.tick_id, order.order_id)) + .unwrap(), + ); + } + + assert_eq!(fetched_orders, order_amount as u64); + assert_eq!( + total_pages, + (order_amount as f64 / page_size as f64).ceil() as u64 + ); +} diff --git a/contracts/orderbook/src/types/order.rs b/contracts/orderbook/src/types/order.rs index 4be802c..f9314a5 100644 --- a/contracts/orderbook/src/types/order.rs +++ b/contracts/orderbook/src/types/order.rs @@ -39,6 +39,7 @@ impl LimitOrder { // TODO: Unnecessary if finite queries not required /// Defines the different way an owners orders can be filtered, all enums filter by owner with each getting more finite +#[derive(Clone)] pub enum FilterOwnerOrders { All(Addr), ByBook(u64, Addr),