Skip to content

Commit

Permalink
Added pagination to orders query
Browse files Browse the repository at this point in the history
  • Loading branch information
crnbarr93 committed Feb 17, 2024
1 parent 3619519 commit 62a62d9
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 25 deletions.
65 changes: 42 additions & 23 deletions contracts/orderbook/src/state.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -71,36 +71,55 @@ pub fn new_order_id(storage: &mut dyn Storage) -> Result<u64, ContractError> {
// 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<u8>,
) -> StdResult<Vec<LimitOrder>> {
let orders: Vec<LimitOrder> = 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<LimitOrder> = iter
.range(storage, min, max, Order::Ascending)
.take(page_size)
.filter_map(|item| item.ok())
.map(|(_, order)| order)
.collect();
Ok(orders)
}
81 changes: 79 additions & 2 deletions contracts/orderbook/src/tests/test_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,14 @@ fn test_get_orders_by_owner_all() {
.unwrap();
});

let owner_orders: Vec<LimitOrder> =
get_orders_by_owner(&storage, FilterOwnerOrders::All(Addr::unchecked(owner))).unwrap();
let owner_orders: Vec<LimitOrder> = 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| {
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand All @@ -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
);
}
1 change: 1 addition & 0 deletions contracts/orderbook/src/types/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down

0 comments on commit 62a62d9

Please sign in to comment.