Skip to content

Commit

Permalink
Market and Limit Oder Filling Logic (#40)
Browse files Browse the repository at this point in the history
* Added cancel limit

* Added liquidity tracking to place/cancel limit

* Shortened get_expected_denom name

* Changed reply to use ensure

* Added order fulfilment process

* Added test for resolve_fulfilments

* Added test for market order [WIP]

* Added run market order tests

* Added run limit order test

* Added limits for tick bound in run market order

* fix typo fulfil -> fulfill across files

* Renamed fulfill method

* Remove run limit order

---------

Co-authored-by: alpo <[email protected]>
  • Loading branch information
crnbarr93 and AlpinYukseloglu authored Feb 24, 2024
1 parent 140f5df commit c6b008e
Show file tree
Hide file tree
Showing 7 changed files with 1,968 additions and 11 deletions.
12 changes: 12 additions & 0 deletions contracts/orderbook/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,16 @@ pub enum ContractError {

#[error("Reply error: {id:?}, {error:?}")]
ReplyError { id: u64, error: String },

#[error("Cannot fulfill order. Order ID: {order_id:?}, Book ID: {book_id:?}, Amount Required: {amount_required:?}, Amount Remaining: {amount_remaining:?} {reason:?}")]
InvalidFulfillment {
order_id: u64,
book_id: u64,
amount_required: Uint128,
amount_remaining: Uint128,
reason: Option<String>,
},

#[error("Mismatched order direction")]
MismatchedOrderDirection {},
}
173 changes: 170 additions & 3 deletions contracts/orderbook/src/order.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use crate::error::ContractError;
use crate::state::*;
use crate::state::{MAX_TICK, MIN_TICK, ORDERBOOKS};
use crate::types::{LimitOrder, OrderDirection, REPLY_ID_REFUND};
use crate::types::{Fulfillment, LimitOrder, MarketOrder, OrderDirection, REPLY_ID_REFUND};
use cosmwasm_std::{
coin, ensure, ensure_eq, BankMsg, DepsMut, Env, MessageInfo, Response, SubMsg, Uint128,
coin, ensure, ensure_eq, ensure_ne, BankMsg, Decimal, DepsMut, Env, MessageInfo, Order,
Response, Storage, SubMsg, Uint128,
};
use cw_storage_plus::Bound;
use cw_utils::{must_pay, nonpayable};

#[allow(clippy::manual_range_contains)]
pub fn place_limit(
deps: DepsMut,
env: Env,
_env: Env,
info: MessageInfo,
book_id: u64,
tick_id: i64,
Expand Down Expand Up @@ -146,3 +148,168 @@ pub fn place_market(
.add_attribute("method", "placeMarket")
.add_attribute("owner", info.sender))
}

#[allow(clippy::manual_range_contains)]
pub fn run_market_order(
storage: &mut dyn Storage,
order: &mut MarketOrder,
tick_bound: Option<i64>,
) -> Result<(Vec<Fulfillment>, BankMsg), ContractError> {
let mut fulfillments: Vec<Fulfillment> = vec![];
let mut amount_fulfilled: Uint128 = Uint128::zero();
let orderbook = ORDERBOOKS.load(storage, &order.book_id)?;
let placed_order_denom = orderbook.get_expected_denom(&order.order_direction);

let (min_tick, max_tick, ordering) = match order.order_direction {
OrderDirection::Ask => {
if let Some(tick_bound) = tick_bound {
ensure!(
tick_bound <= orderbook.next_bid_tick
&& tick_bound <= MAX_TICK
&& tick_bound >= MIN_TICK,
ContractError::InvalidTickId {
tick_id: tick_bound
}
);
}
(tick_bound, Some(orderbook.next_bid_tick), Order::Descending)
}
OrderDirection::Bid => {
if let Some(tick_bound) = tick_bound {
ensure!(
tick_bound >= orderbook.next_ask_tick
&& tick_bound <= MAX_TICK
&& tick_bound >= MIN_TICK,
ContractError::InvalidTickId {
tick_id: tick_bound
}
);
}
(Some(orderbook.next_ask_tick), tick_bound, Order::Ascending)
}
};

// Create ticks iterator between first tick and requested tick
let ticks = TICK_LIQUIDITY.prefix(order.book_id).range(
storage,
min_tick.map(Bound::inclusive),
max_tick.map(Bound::inclusive),
ordering,
);

for maybe_current_tick in ticks {
let current_tick = maybe_current_tick?.0;

// Create orders iterator for all orders on current tick
let tick_orders = orders().prefix((order.book_id, current_tick)).range(
storage,
None,
None,
Order::Ascending,
);

for maybe_current_order in tick_orders {
let current_order = maybe_current_order?.1;
ensure_ne!(
current_order.order_direction,
order.order_direction,
ContractError::MismatchedOrderDirection {}
);
let fill_quantity = order.quantity.min(current_order.quantity);
// Add to total amount fulfilled from placed order
amount_fulfilled = amount_fulfilled.checked_add(fill_quantity)?;
// Generate fulfillment for current order
let fulfillment = Fulfillment::new(current_order, fill_quantity);
fulfillments.push(fulfillment);

// Update remaining order quantity
order.quantity = order.quantity.checked_sub(fill_quantity)?;
// TODO: Price detection
if order.quantity.is_zero() {
return Ok((
fulfillments,
BankMsg::Send {
to_address: order.owner.to_string(),
amount: vec![coin(amount_fulfilled.u128(), placed_order_denom)],
},
));
}
}

// TODO: Price detection
if order.quantity.is_zero() {
return Ok((
fulfillments,
BankMsg::Send {
to_address: order.owner.to_string(),
amount: vec![coin(amount_fulfilled.u128(), placed_order_denom)],
},
));
}
}

// TODO: Price detection
Ok((
fulfillments,
BankMsg::Send {
to_address: order.owner.to_string(),
amount: vec![coin(amount_fulfilled.u128(), placed_order_denom)],
},
))
}

pub fn resolve_fulfillments(
storage: &mut dyn Storage,
fulfillments: Vec<Fulfillment>,
) -> Result<Vec<BankMsg>, ContractError> {
let mut msgs: Vec<BankMsg> = vec![];
let orderbook = ORDERBOOKS.load(storage, &fulfillments[0].order.book_id)?;
for mut fulfillment in fulfillments {
ensure_eq!(
fulfillment.order.book_id,
orderbook.book_id,
// TODO: Error not expressive
ContractError::InvalidFulfillment {
order_id: fulfillment.order.order_id,
book_id: fulfillment.order.book_id,
amount_required: fulfillment.amount,
amount_remaining: fulfillment.order.quantity,
reason: Some("Fulfillment is part of another order book".to_string()),
}
);
let denom = orderbook.get_expected_denom(&fulfillment.order.order_direction);
// TODO: Add price detection for tick
let msg = fulfillment
.order
.fill(&denom, fulfillment.amount, Decimal::one())?;
msgs.push(msg);
if fulfillment.order.quantity.is_zero() {
orders().remove(
storage,
&(
fulfillment.order.book_id,
fulfillment.order.tick_id,
fulfillment.order.order_id,
),
)?;
} else {
orders().save(
storage,
&(
fulfillment.order.book_id,
fulfillment.order.tick_id,
fulfillment.order.order_id,
),
&fulfillment.order,
)?;
}
// TODO: possible optimization by grouping tick/liquidity and calling this once per tick?
reduce_tick_liquidity(
storage,
fulfillment.order.book_id,
fulfillment.order.tick_id,
fulfillment.amount,
)?;
}
Ok(msgs)
}
Loading

0 comments on commit c6b008e

Please sign in to comment.