Skip to content

Commit

Permalink
Added multiindex for #20
Browse files Browse the repository at this point in the history
  • Loading branch information
crnbarr93 committed Feb 17, 2024
1 parent d2fc8de commit e1f7287
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 26 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"rust-analyzer.showUnlinkedFileNotification": false
"rust-analyzer.showUnlinkedFileNotification": false,
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer", // Makes the magic
"editor.formatOnSave": true // Optional
},
}
10 changes: 2 additions & 8 deletions contracts/orderbook/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use cw2::set_contract_version;
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};

use crate::orderbook;
use crate::order;
use crate::orderbook;

// version info for migration info
const CONTRACT_NAME: &str = "crates.io:orderbook";
Expand Down Expand Up @@ -60,13 +60,7 @@ pub fn execute(
ExecuteMsg::CreateOrderbook {
quote_denom,
base_denom,
} => orderbook::create_orderbook(
_deps,
_env,
_info,
quote_denom,
base_denom,
),
} => orderbook::create_orderbook(_deps, _env, _info, quote_denom, base_denom),

// Places limit order on given market
ExecuteMsg::PlaceLimit => order::place_limit(_deps, _env, _info),
Expand Down
26 changes: 20 additions & 6 deletions contracts/orderbook/src/orderbook.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::error::ContractError;
use crate::state::{new_orderbook_id, MAX_TICK, MIN_TICK, ORDERBOOKS};
use crate::types::Orderbook;
use crate::state::{new_orderbook_id, ORDERBOOKS, MIN_TICK, MAX_TICK};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};

pub fn create_orderbook(
Expand All @@ -25,7 +25,9 @@ pub fn create_orderbook(

ORDERBOOKS.save(deps.storage, &book_id, &_book)?;

Ok(Response::new().add_attribute("method", "createOrderbook").add_attribute("book_id", &book_id.to_string()))
Ok(Response::new()
.add_attribute("method", "createOrderbook")
.add_attribute("book_id", book_id.to_string()))
}

#[cfg(test)]
Expand All @@ -42,15 +44,27 @@ mod tests {
// Attempt to create an orderbook
let quote_denom = "quote".to_string();
let base_denom = "base".to_string();
let create_response = create_orderbook(deps.as_mut(), env, info, quote_denom.clone(), base_denom.clone()).unwrap();

let create_response = create_orderbook(
deps.as_mut(),
env,
info,
quote_denom.clone(),
base_denom.clone(),
)
.unwrap();

// Verify response
let expected_book_id: u64 = 0;
assert_eq!(create_response.attributes[0], ("method", "createOrderbook"));
assert_eq!(create_response.attributes[1], ("book_id", &expected_book_id.to_string()));
assert_eq!(
create_response.attributes[1],
("book_id", &expected_book_id.to_string())
);

// Verify orderbook is saved correctly
let orderbook = ORDERBOOKS.load(deps.as_ref().storage, &expected_book_id).unwrap();
let orderbook = ORDERBOOKS
.load(deps.as_ref().storage, &expected_book_id)
.unwrap();
assert_eq!(orderbook.quote_denom, quote_denom);
assert_eq!(orderbook.base_denom, base_denom);
assert_eq!(orderbook.current_tick, 0);
Expand Down
213 changes: 205 additions & 8 deletions contracts/orderbook/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
use crate::types::{LimitOrder, Orderbook};
use crate::types::{FilterOwnerOrders, LimitOrder, Orderbook};
use crate::ContractError;
use cosmwasm_std::{Storage, Uint128};
use cw_storage_plus::{Item, Map};
use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128};
use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex};

pub const MIN_TICK: i64 = -108000000;
pub const MAX_TICK: i64 = 342000000;


pub const ORDERBOOKS: Map<&u64, Orderbook> = Map::new("orderbooks");
/// Key: (orderbook_id, tick)
pub const TICK_LIQUIDITY: Map<&(u64, i64), Uint128> = Map::new("tick_liquidity");

// TODO: Check additional gas fee for adding more indexes
pub struct OrderIndexes {
// Index by owner; Generic types: MultiIndex<Index Key: owner, Input Data: LimitOrder, Map Key: (orderbook_id, tick, order_id)>
pub owner: MultiIndex<'static, Addr, LimitOrder, (u64, i64, u64)>,
// Index by book and owner; Generic types: MultiIndex<Index Key: (book_id, owner), Input Data: LimitOrder, Map Key: (orderbook_id, tick, order_id)>
pub book_and_owner: MultiIndex<'static, (u64, Addr), LimitOrder, (u64, i64, u64)>,
// Index by tick and owner; Generic types: MultiIndex<Index Key: (book_id, tick_id, owner), Input Data: LimitOrder, Map Key: (orderbook_id, tick, order_id)>
pub tick_and_owner: MultiIndex<'static, (u64, i64, Addr), LimitOrder, (u64, i64, u64)>,
}

impl IndexList<LimitOrder> for OrderIndexes {
fn get_indexes(&'_ self) -> Box<dyn Iterator<Item = &'_ dyn Index<LimitOrder>> + '_> {
let v: Vec<&dyn Index<LimitOrder>> =
vec![&self.owner, &self.book_and_owner, &self.tick_and_owner];
Box::new(v.into_iter())
}
}

/// Key: (orderbook_id, tick, order_id)
pub const ORDERS: Map<&(u64, i64, u64), LimitOrder> = Map::new("tick_orders");
pub fn orders() -> IndexedMap<'static, &'static (u64, i64, u64), LimitOrder, OrderIndexes> {
IndexedMap::new(
"orders",
OrderIndexes {
owner: MultiIndex::new(
|_, d: &LimitOrder| d.owner.clone(),
"orders",
"orders_owner",
),
book_and_owner: MultiIndex::new(
|_, d: &LimitOrder| (d.book_id, d.owner.clone()),
"orders",
"orders_book_and_owner",
),
tick_and_owner: MultiIndex::new(
|_, d: &LimitOrder| (d.book_id, d.tick_id, d.owner.clone()),
"orders",
"orders_tick_and_owner",
),
},
)
}

// Counters for ID tracking
pub const ORDER_ID: Item<u64> = Item::new("order_id");
Expand All @@ -29,6 +68,43 @@ pub fn new_order_id(storage: &mut dyn Storage) -> Result<u64, ContractError> {
Ok(id)
}

// TODO: Add pagination
// TODO: How finite do we need queries?

/// Retrieves a list of `LimitOrder` filtered by the specified `FilterOwnerOrders`.
pub fn get_orders_by_owner(
storage: &dyn Storage,
filter: FilterOwnerOrders,
) -> 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(),
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(),
};
Ok(orders)
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -91,16 +167,16 @@ mod test {
tick_id: tick,
book_id,
order_id,
owner: Addr::unchecked(format!("maker{}", i)),
owner: Addr::unchecked(format!("maker{i}")),
quantity: Uint128::new(i as u128),
order_direction: OrderDirection::Ask,
};
ORDERS
orders()
.save(&mut storage, &(book_id, tick, i), &order)
.unwrap();
}

let tick_orders = ORDERS.prefix((book_id, tick));
let tick_orders = orders().prefix((book_id, tick));
let orders_desc: Vec<LimitOrder> = tick_orders
.range(&storage, None, None, Order::Descending)
.map(|result| result.unwrap().1)
Expand All @@ -114,4 +190,125 @@ mod test {
assert_eq!(orders_asc[i as usize].order_id, i);
}
}

#[test]
fn test_get_orders_by_owner_all() {
let mut storage = MockStorage::new();
let order_amount = 10;
let owner = "owner1";

let book_ids: Vec<u64> = (0..3)
.map(|_| new_orderbook_id(&mut storage).unwrap())
.collect();

(0..order_amount).for_each(|i| {
let order_id = new_order_id(&mut storage).unwrap();
let other_owner = &format!("owner{i}");
let current_owner = Addr::unchecked(if i % 2 == 0 { owner } else { other_owner });
let order = LimitOrder::new(
book_ids[i % 3],
0,
order_id,
OrderDirection::Ask,
current_owner,
Uint128::new(i as u128),
);
orders()
.save(&mut storage, &(order.book_id, 0, i as u64), &order)
.unwrap();
});

let owner_orders: Vec<LimitOrder> =
get_orders_by_owner(&storage, FilterOwnerOrders::All(Addr::unchecked(owner))).unwrap();

assert_eq!(owner_orders.len(), order_amount / 2 + 1);
owner_orders.iter().for_each(|order| {
assert_eq!(order.owner, Addr::unchecked(owner));
});
}

#[test]
fn test_get_orders_by_owner_by_book() {
let mut storage = MockStorage::new();
let order_amount = 100;
let owner = "owner1";

// Generate three new book IDs
let book_ids: Vec<u64> = (0..3)
.map(|_| new_orderbook_id(&mut storage).unwrap())
.collect();

// Create orders alternating ownership between `owner` and dynamically generated owners amongst all books evenly
(0..order_amount).for_each(|i| {
let order_id = new_order_id(&mut storage).unwrap();
let other_owner = &format!("owner{i}");
let current_owner = Addr::unchecked(if i % 2 == 0 { owner } else { other_owner });
let order = LimitOrder::new(
book_ids[i % 3],
0,
order_id,
OrderDirection::Ask,
current_owner,
Uint128::new(i as u128),
);
orders()
.save(&mut storage, &(order.book_id, 0, i as u64), &order)
.unwrap();
});

// Verify orders by book ID
book_ids.iter().for_each(|&book_id| {
let owner_orders = get_orders_by_owner(
&storage,
FilterOwnerOrders::ByBook(book_id, Addr::unchecked(owner)),
)
.unwrap();
assert!(!owner_orders.is_empty());
owner_orders.iter().for_each(|order| {
assert_eq!(order.owner, Addr::unchecked(owner));
assert_eq!(order.book_id, book_id);
});
});
}

#[test]
fn test_get_orders_by_owner_by_tick() {
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 alternating ownership between `owner` and dynamically generated owners amongst all ticks evenly
(0..order_amount).for_each(|i| {
let order_id = new_order_id(&mut storage).unwrap();
let other_owner = &format!("owner{i}");
let current_owner = Addr::unchecked(if i % 2 == 0 { owner } else { other_owner });
let tick = ticks[i % 3];
let order = LimitOrder::new(
book_id,
tick,
order_id,
OrderDirection::Ask,
current_owner,
Uint128::new(i as u128),
);
orders()
.save(&mut storage, &(book_id, tick, i as u64), &order)
.unwrap();
});

ticks.iter().for_each(|&tick| {
let owner_orders = get_orders_by_owner(
&storage,
FilterOwnerOrders::ByTick(book_id, tick, Addr::unchecked(owner)),
)
.unwrap();
assert!(!owner_orders.is_empty());
owner_orders.iter().for_each(|order| {
assert_eq!(order.owner, Addr::unchecked(owner));
assert_eq!(order.tick_id, tick);
});
});
}
}
6 changes: 3 additions & 3 deletions contracts/orderbook/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod orderbook;
mod order;
mod orderbook;

pub use orderbook::*;
pub use order::*;
pub use self::order::*;
pub use self::orderbook::*;
22 changes: 22 additions & 0 deletions contracts/orderbook/src/types/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,25 @@ 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
pub enum FilterOwnerOrders {
All(Addr),
ByBook(u64, Addr),
ByTick(u64, i64, Addr),
}

impl FilterOwnerOrders {
pub fn all(owner: Addr) -> Self {
FilterOwnerOrders::All(owner)
}

pub fn by_book(book_id: u64, owner: Addr) -> Self {
FilterOwnerOrders::ByBook(book_id, owner)
}

pub fn by_tick(book_id: u64, tick_id: i64, owner: Addr) -> Self {
FilterOwnerOrders::ByTick(book_id, tick_id, owner)
}
}

0 comments on commit e1f7287

Please sign in to comment.