diff --git a/contracts/oraiswap_limit_order/src/contract.rs b/contracts/oraiswap_limit_order/src/contract.rs index acacd927..6fab689d 100644 --- a/contracts/oraiswap_limit_order/src/contract.rs +++ b/contracts/oraiswap_limit_order/src/contract.rs @@ -31,7 +31,6 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); // default commission rate = 0.1 % const DEFAULT_COMMISSION_RATE: &str = "0.001"; const REWARD_WALLET: &str = "orai16stq6f4pnrfpz75n9ujv6qg3czcfa4qyjux5en"; -const SPREAD_WALLET: &str = "orai139tjpfj0h6ld3wff7v2x92ntdewungfss0ml3n"; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -42,7 +41,6 @@ pub fn instantiate( ) -> StdResult { let creator = deps.api.addr_canonicalize(info.sender.as_str())?; let default_reward_address = deps.api.addr_canonicalize(REWARD_WALLET)?; - let default_spread_address = deps.api.addr_canonicalize(SPREAD_WALLET)?; let config = ContractInfo { name: msg.name.unwrap_or(CONTRACT_NAME.to_string()), version: msg.version.unwrap_or(CONTRACT_VERSION.to_string()), @@ -61,11 +59,6 @@ pub fn instantiate( } else { default_reward_address }, - spread_address: if let Some(spread_address) = msg.spread_address { - deps.api.addr_canonicalize(spread_address.as_str())? - } else { - default_spread_address - }, }; store_config(deps.storage, &config)?; @@ -87,9 +80,8 @@ pub fn execute( ExecuteMsg::UpdateAdmin { admin } => execute_update_admin(deps, info, admin), ExecuteMsg::UpdateConfig { reward_address, - spread_address, commission_rate, - } => execute_update_config(deps, info, reward_address, spread_address, commission_rate), + } => execute_update_config(deps, info, reward_address, commission_rate), ExecuteMsg::CreateOrderBookPair { base_coin_info, quote_coin_info, @@ -218,7 +210,6 @@ pub fn execute_update_config( deps: DepsMut, info: MessageInfo, reward_address: Option, - spread_address: Option, commission_rate: Option, ) -> Result { let mut contract_info = read_config(deps.storage)?; @@ -234,18 +225,12 @@ pub fn execute_update_config( contract_info.reward_address = deps.api.addr_canonicalize(reward_address.as_str())?; } - // update new reward address - if let Some(spread_address) = spread_address { - contract_info.reward_address = deps.api.addr_canonicalize(spread_address.as_str())?; - } - // update new commission rate if let Some(commission_rate) = commission_rate { contract_info.commission_rate = commission_rate; } store_config(deps.storage, &contract_info)?; - Ok(Response::new().add_attributes(vec![("action", "execute_update_config")])) } @@ -450,6 +435,47 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::OrderBookMatchable { asset_infos } => { to_binary(&query_orderbook_is_matchable(deps, asset_infos)?) } + // TODO: add test cases + QueryMsg::MidPrice { asset_infos } => { + let pair_key = pair_key(&[ + asset_infos[0].to_raw(deps.api)?, + asset_infos[1].to_raw(deps.api)?, + ]); + let best_buy = query_ticks_with_end( + deps.storage, + &pair_key, + OrderDirection::Buy, + None, + None, + Some(1), + Some(2), + )?; + let best_sell = query_ticks_with_end( + deps.storage, + &pair_key, + OrderDirection::Sell, + None, + None, + Some(1), + Some(1), + )?; + let best_buy_price = if best_buy.ticks.len() == 0 { + Decimal::zero() + } else { + best_buy.ticks[0].price + }; + let best_sell_price = if best_sell.ticks.len() == 0 { + Decimal::zero() + } else { + best_sell.ticks[0].price + }; + let mid_price = best_buy_price + .checked_add(best_sell_price) + .unwrap_or_default() + .checked_div(Decimal::from_ratio(2u128, 1u128)) + .unwrap_or_default(); + to_binary(&mid_price) + } } } @@ -459,6 +485,8 @@ pub fn query_contract_info(deps: Deps) -> StdResult { version: info.version, name: info.name, admin: deps.api.addr_humanize(&info.admin)?, + commission_rate: info.commission_rate, + reward_address: deps.api.addr_humanize(&info.reward_address)?, }) } diff --git a/contracts/oraiswap_limit_order/src/order.rs b/contracts/oraiswap_limit_order/src/order.rs index 2b2dd163..0e1610a8 100644 --- a/contracts/oraiswap_limit_order/src/order.rs +++ b/contracts/oraiswap_limit_order/src/order.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use std::str::FromStr; -use crate::orderbook::{BulkOrders, Executor, Order, OrderBook}; +use crate::orderbook::{BulkOrders, Executor, Order, OrderBook, OrderWithFee}; use crate::state::{ increase_last_order_id, read_config, read_last_order_id, read_order, read_orderbook, read_orderbooks, read_orders, read_orders_with_indexer, read_reward, remove_order, @@ -10,7 +10,7 @@ use crate::state::{ }; use cosmwasm_std::{ attr, Addr, Attribute, CanonicalAddr, CosmosMsg, Decimal, Deps, DepsMut, Event, MessageInfo, - Order as OrderBy, Response, StdResult, Storage, Uint128, + Order as OrderBy, Response, StdError, StdResult, Storage, Uint128, }; use cosmwasm_storage::ReadonlyBucket; @@ -108,9 +108,11 @@ pub fn cancel_order( // Build refund msg let messages = if left_offer_amount > Uint128::zero() { - vec![bidder_refund - .clone() - .into_msg(None, &deps.querier, deps.api.addr_humanize(&order.bidder_addr)?)?] + vec![bidder_refund.clone().into_msg( + None, + &deps.querier, + deps.api.addr_humanize(&order.bidder_addr)?, + )?] } else { vec![] }; @@ -140,7 +142,7 @@ pub fn cancel_order( ])) } -fn to_events(order: &Order, human_bidder: String, fee: String) -> Event { +fn to_events(order: &OrderWithFee, human_bidder: String) -> Event { let attrs: Vec = [ attr("status", format!("{:?}", order.status)), attr("bidder_addr", human_bidder), @@ -150,7 +152,8 @@ fn to_events(order: &Order, human_bidder: String, fee: String) -> Event { attr("filled_offer_amount", order.filled_offer_amount.to_string()), attr("ask_amount", order.ask_amount.to_string()), attr("filled_ask_amount", order.filled_ask_amount.to_string()), - attr("fee", fee), + attr("reward_fee", order.reward_fee), + attr("relayer_fee", order.relayer_fee), ] .to_vec(); Event::new("matched_order").add_attributes(attrs) @@ -174,22 +177,19 @@ fn transfer_reward( executor: &mut Executor, total_reward: &mut Vec, messages: &mut Vec, -) { +) -> StdResult<()> { for reward_asset in executor.reward_assets.iter_mut() { if Uint128::from(reward_asset.amount) >= Uint128::from(1000000u128) { - messages.push( - reward_asset - .into_msg( - None, - &deps.querier, - deps.api.addr_humanize(&executor.address).unwrap(), - ) - .unwrap(), - ); + messages.push(reward_asset.into_msg( + None, + &deps.querier, + deps.api.addr_humanize(&executor.address)?, + )?); total_reward.push(reward_asset.to_string()); reward_asset.amount = Uint128::zero(); } } + Ok(()) } fn process_list_trader( @@ -222,71 +222,25 @@ fn process_list_trader( Ok(()) } -fn transfer_spread( - deps: &DepsMut, - orderbook_pair: &OrderBook, - direction: OrderDirection, - bulk_orders: &mut Vec, - messages: &mut Vec, -) { - let contract_info = read_config(deps.storage).unwrap(); - let spread_address = contract_info.spread_address; - let mut total_spread = Uint128::zero(); - - for orders in bulk_orders.iter_mut() { - if !orders.spread_volume.is_zero() { - total_spread += orders.spread_volume; - orders.spread_volume = Uint128::zero(); - } else { - continue; - } - } - - let spread_payment: Payment = Payment { - address: deps.api.addr_humanize(&spread_address).unwrap(), - asset: Asset { - info: match direction { - OrderDirection::Buy => orderbook_pair.base_coin_info.to_normal(deps.api).unwrap(), - OrderDirection::Sell => orderbook_pair.quote_coin_info.to_normal(deps.api).unwrap(), - }, - amount: total_spread, - }, - }; - - // Build refund msg - if !total_spread.is_zero() { - messages.push( - spread_payment - .asset - .into_msg(None, &deps.querier, spread_payment.address) - .unwrap(), - ); - } -} - fn execute_bulk_orders( deps: &DepsMut, orderbook_pair: OrderBook, limit: Option, ) -> StdResult<(Vec, Vec)> { let pair_key = &orderbook_pair.get_pair_key(); - let buy_position_bucket: ReadonlyBucket = ReadonlyBucket::multilevel( deps.storage, &[PREFIX_TICK, pair_key, OrderDirection::Buy.as_bytes()], ); - let mut buy_cursor = buy_position_bucket.range(None, None, OrderBy::Descending); let sell_position_bucket: ReadonlyBucket = ReadonlyBucket::multilevel( deps.storage, &[PREFIX_TICK, pair_key, OrderDirection::Sell.as_bytes()], ); - let mut sell_cursor = sell_position_bucket.range(None, None, OrderBy::Ascending); let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize; - let mut i = 0; let mut j = 0; let min_vol = Uint128::from(10u128); @@ -299,7 +253,10 @@ fn execute_bulk_orders( while i < limit && j < limit { if best_sell_price_list.len() <= j { if let Some(Ok((k, _))) = sell_cursor.next() { - let price = Decimal::raw(u128::from_be_bytes(k.try_into().unwrap())); + let price = + Decimal::raw(u128::from_be_bytes(k.try_into().map_err(|_| { + StdError::generic_err("Error converting bytes to u128") + })?)); best_sell_price_list.push(price); } else { break; @@ -309,21 +266,19 @@ fn execute_bulk_orders( if best_buy_price_list.len() <= i { if let Some(Ok((k, _))) = buy_cursor.next() { - let price = Decimal::raw(u128::from_be_bytes(k.try_into().unwrap())); + let price = + Decimal::raw(u128::from_be_bytes(k.try_into().map_err(|_| { + StdError::generic_err("Error converting bytes to u128") + })?)); best_buy_price_list.push(price); } else { break; } } - let buy_price = best_buy_price_list[i]; - if buy_price < sell_price { break; } - - let match_price = buy_price; - if buy_bulk_orders_list.len() <= i { if let Some(orders) = orderbook_pair.query_orders_by_price_and_direction( deps.as_ref().storage, @@ -358,62 +313,91 @@ fn execute_bulk_orders( } }; + // list of buy orders and sell orders let buy_bulk_orders = &mut buy_bulk_orders_list[i]; let sell_bulk_orders = &mut sell_bulk_orders_list[j]; - let lef_sell_offer = sell_bulk_orders.volume; - let lef_sell_ask = Uint128::from(lef_sell_offer * match_price); + // match price + let match_price = if buy_bulk_orders.average_order_id >= sell_bulk_orders.average_order_id { + buy_price + } else { + sell_price + }; - let sell_ask_amount = Uint128::min(buy_bulk_orders.volume, lef_sell_ask); + // remaining_sell_ask_volume = remaining_sell_volume * match_price + let remaining_sell_volume = sell_bulk_orders.remaining_volume; + let remaining_sell_ask_volume = remaining_sell_volume * match_price; + let remaining_buy_volume = + Uint128::min(buy_bulk_orders.remaining_volume, remaining_sell_ask_volume); // multiply by decimal atomics because we want to get good round values - let sell_offer_amount = Uint128::min( - Uint128::from(sell_ask_amount * Decimal::one().atomics()) - .checked_div(match_price.atomics()) - .unwrap(), - lef_sell_offer, - ); - - if sell_ask_amount.is_zero() || sell_offer_amount.is_zero() { - continue; - } + // remaining_buy_ask_volume = remaining_buy_volume / match_price + let remaining_buy_ask_volume = + Uint128::from(remaining_buy_volume * Decimal::one().atomics()) + .checked_div(match_price.atomics())?; - sell_bulk_orders.filled_volume += sell_offer_amount; - sell_bulk_orders.filled_ask_volume += sell_ask_amount; + if remaining_buy_ask_volume.is_zero() { + // buy out + i += 1; + } + if remaining_sell_ask_volume.is_zero() { + // sell out + j += 1; + } - buy_bulk_orders.filled_volume += sell_ask_amount; - buy_bulk_orders.filled_ask_volume += sell_offer_amount; + // fill_base_volume = min(remaining_sell_volume, remaining_buy_ask_volume) + // fill_quote_volume = fill_base_volume * match_price + let fill_base_volume = Uint128::min(remaining_sell_volume, remaining_buy_ask_volume); + let fill_quote_volume = Uint128::from(fill_base_volume * match_price); - buy_bulk_orders.volume = buy_bulk_orders.volume.checked_sub(sell_ask_amount)?; - sell_bulk_orders.volume = sell_bulk_orders.volume.checked_sub(sell_offer_amount)?; + if fill_base_volume.is_zero() || fill_quote_volume.is_zero() { + continue; + } - if buy_bulk_orders.filled_ask_volume >= buy_bulk_orders.ask_volume { - buy_bulk_orders.spread_volume = buy_bulk_orders + // In sell side + // filled_volume = filled_volume + fill_base_volume + // filled_ask_volume = filled_ask_volume + fill_quote_volume + sell_bulk_orders.filled_volume += fill_base_volume; + sell_bulk_orders.filled_ask_volume += fill_quote_volume; + + // In buy side + // filled_volume = filled_volume + fill_quote_volume + // filled_ask_volume = filled_ask_volume + fill_base_volume + buy_bulk_orders.filled_volume += fill_quote_volume; + buy_bulk_orders.filled_ask_volume += fill_base_volume; + + // In buy side + // remaining_volume = remaining_volume - fill_quote_volume + buy_bulk_orders.remaining_volume = buy_bulk_orders + .remaining_volume + .checked_sub(fill_quote_volume)?; + + // In sell side + // remaining_volume = remaining_volume - fill_base_volume + sell_bulk_orders.remaining_volume = sell_bulk_orders + .remaining_volume + .checked_sub(fill_base_volume)?; + // get spread volume in buy side + if buy_bulk_orders.filled_ask_volume > buy_bulk_orders.ask_volume { + buy_bulk_orders.spread_volume += buy_bulk_orders .filled_ask_volume .checked_sub(buy_bulk_orders.ask_volume)?; - buy_bulk_orders.filled_ask_volume = buy_bulk_orders - .filled_ask_volume - .checked_sub(buy_bulk_orders.spread_volume)?; - buy_bulk_orders.ask_volume = Uint128::zero(); + buy_bulk_orders.filled_ask_volume = buy_bulk_orders.ask_volume; } - if sell_bulk_orders.filled_ask_volume >= sell_bulk_orders.ask_volume { - sell_bulk_orders.spread_volume = sell_bulk_orders + // get spread volume in sell side + if sell_bulk_orders.filled_ask_volume > sell_bulk_orders.ask_volume { + sell_bulk_orders.spread_volume += sell_bulk_orders .filled_ask_volume .checked_sub(sell_bulk_orders.ask_volume)?; - sell_bulk_orders.filled_ask_volume = sell_bulk_orders - .filled_ask_volume - .checked_sub(sell_bulk_orders.spread_volume)?; - sell_bulk_orders.ask_volume = Uint128::zero(); + sell_bulk_orders.filled_ask_volume = sell_bulk_orders.ask_volume; } - if buy_bulk_orders.volume <= min_vol { + if buy_bulk_orders.remaining_volume <= min_vol { // buy out - buy_bulk_orders.ask_volume = Uint128::zero(); i += 1; } - if sell_bulk_orders.volume <= min_vol { + if sell_bulk_orders.remaining_volume <= min_vol { // sell out - sell_bulk_orders.ask_volume = Uint128::zero(); j += 1; } } @@ -421,19 +405,20 @@ fn execute_bulk_orders( return Ok((buy_bulk_orders_list, sell_bulk_orders_list)); } +// TODO: write test cases for this function fn calculate_fee( deps: &DepsMut, amount: Uint128, - relayer_usdt_fee: Uint128, + relayer_quote_fee: Uint128, direction: OrderDirection, trader_ask_asset: &mut Asset, reward: &mut Executor, relayer: &mut Executor, -) -> Uint128 { +) -> StdResult<(Uint128, Uint128)> { let reward_fee: Uint128; let relayer_fee: Uint128; - let contract_info = read_config(deps.storage).unwrap(); - let commission_rate = Decimal::from_str(&contract_info.commission_rate).unwrap(); + let contract_info = read_config(deps.storage)?; + let commission_rate = Decimal::from_str(&contract_info.commission_rate)?; reward_fee = amount * commission_rate; @@ -445,7 +430,7 @@ fn calculate_fee( relayer.reward_assets[0].amount += relayer_fee; } OrderDirection::Sell => { - relayer_fee = Uint128::min(relayer_usdt_fee, amount); + relayer_fee = Uint128::min(relayer_quote_fee, amount); reward.reward_assets[1].amount += reward_fee; relayer.reward_assets[1].amount += relayer_fee; @@ -455,8 +440,8 @@ fn calculate_fee( trader_ask_asset.amount = trader_ask_asset .amount .checked_sub(reward_fee + relayer_fee) - .unwrap(); - return relayer_fee + reward_fee; + .unwrap_or_default(); + return Ok((reward_fee, relayer_fee)); } fn process_orders( @@ -466,31 +451,33 @@ fn process_orders( bulk_traders: &mut Vec, reward: &mut Executor, relayer: &mut Executor, -) { +) -> StdResult<()> { for bulk in bulk_orders.iter_mut() { let mut trader_ask_asset = Asset { info: match bulk.direction { - OrderDirection::Buy => orderbook_pair.base_coin_info.to_normal(deps.api).unwrap(), - OrderDirection::Sell => orderbook_pair.quote_coin_info.to_normal(deps.api).unwrap(), + OrderDirection::Buy => orderbook_pair.base_coin_info.to_normal(deps.api)?, + OrderDirection::Sell => orderbook_pair.quote_coin_info.to_normal(deps.api)?, }, amount: Uint128::zero(), }; - let relayer_usdt_fee = Uint128::from(RELAY_FEE) * bulk.price; + let relayer_quote_fee = Uint128::from(RELAY_FEE) * bulk.price; for order in bulk.orders.iter_mut() { + // filled_offer = min(remain_offer, filled_volume) let filled_offer = Uint128::min( order .offer_amount .checked_sub(order.filled_offer_amount) - .unwrap(), + .unwrap_or_default(), bulk.filled_volume, ); + // filled_offer = min(remain_ask, filled_ask_volume) let filled_ask = Uint128::min( order .ask_amount .checked_sub(order.filled_ask_amount) - .unwrap(), + .unwrap_or_default(), bulk.filled_ask_volume, ); @@ -498,25 +485,38 @@ fn process_orders( continue; } - bulk.filled_volume = bulk.filled_volume.checked_sub(filled_offer).unwrap(); - bulk.filled_ask_volume = bulk.filled_ask_volume.checked_sub(filled_ask).unwrap(); + // filled_volume = filled_volume - filled_offer + bulk.filled_volume = bulk + .filled_volume + .checked_sub(filled_offer) + .unwrap_or_default(); + + // filled_ask_volume = filled_ask_volume - filled_ask + bulk.filled_ask_volume = bulk + .filled_ask_volume + .checked_sub(filled_ask) + .unwrap_or_default(); + // fill order order.fill_order(filled_ask, filled_offer); + // calculate fee if !filled_ask.is_zero() { trader_ask_asset.amount = filled_ask; - calculate_fee( + let (reward_fee, relayer_fee) = calculate_fee( deps, filled_ask, - relayer_usdt_fee, + relayer_quote_fee, bulk.direction, &mut trader_ask_asset, reward, relayer, - ); + )?; + order.reward_fee = reward_fee; + order.relayer_fee = relayer_fee; if !trader_ask_asset.amount.is_zero() { let trader_payment: Payment = Payment { - address: deps.api.addr_humanize(&order.bidder_addr).unwrap(), + address: deps.api.addr_humanize(&order.bidder_addr)?, asset: Asset { info: trader_ask_asset.info.clone(), amount: trader_ask_asset.amount, @@ -527,6 +527,7 @@ fn process_orders( } } } + Ok(()) } pub fn execute_matching_orders( @@ -542,9 +543,6 @@ pub fn execute_matching_orders( asset_infos[1].to_raw(deps.api)?, ]); let orderbook_pair = read_orderbook(deps.storage, &pair_key)?; - - let reward_wallet = contract_info.reward_address; - let reward_assets = [ Asset { info: orderbook_pair.base_coin_info.to_normal(deps.api)?, @@ -558,20 +556,17 @@ pub fn execute_matching_orders( let mut reward = process_reward( deps.storage, &pair_key, - reward_wallet, + contract_info.reward_address, reward_assets.clone(), ); let mut relayer = process_reward(deps.storage, &pair_key, relayer_addr, reward_assets); let mut messages: Vec = vec![]; - let mut list_bidder: Vec = vec![]; let mut list_asker: Vec = vec![]; - let mut ret_events: Vec = vec![]; let mut total_reward: Vec = Vec::new(); - let mut total_orders: u64 = 0; let (mut buy_list, mut sell_list) = execute_bulk_orders(&deps, orderbook_pair.clone(), limit)?; @@ -583,8 +578,7 @@ pub fn execute_matching_orders( &mut list_bidder, &mut reward, &mut relayer, - ); - + )?; process_orders( &deps, &orderbook_pair, @@ -592,17 +586,16 @@ pub fn execute_matching_orders( &mut list_asker, &mut reward, &mut relayer, - ); + )?; for bulk in buy_list.iter_mut() { for buy_order in bulk.orders.iter_mut() { if buy_order.status != OrderStatus::Open { total_orders += 1; - buy_order.match_order(deps.storage, &pair_key).unwrap(); + buy_order.match_order(deps.storage, &pair_key)?; ret_events.push(to_events( &buy_order, deps.api.addr_humanize(&buy_order.bidder_addr)?.to_string(), - format!("{} {}", "1000", &reward.reward_assets[0].info), )); } } @@ -612,29 +605,20 @@ pub fn execute_matching_orders( for sell_order in bulk.orders.iter_mut() { if sell_order.status != OrderStatus::Open { total_orders += 1; - sell_order.match_order(deps.storage, &pair_key).unwrap(); + sell_order.match_order(deps.storage, &pair_key)?; ret_events.push(to_events( &sell_order, deps.api.addr_humanize(&sell_order.bidder_addr)?.to_string(), - format!("{} {}", "2000", &reward.reward_assets[1].info), )); } } } - transfer_spread( - &deps, - &orderbook_pair, - OrderDirection::Sell, - &mut sell_list, - &mut messages, - ); - process_list_trader(&deps, list_bidder, &mut messages)?; process_list_trader(&deps, list_asker, &mut messages)?; - transfer_reward(&deps, &mut reward, &mut total_reward, &mut messages); - transfer_reward(&deps, &mut relayer, &mut total_reward, &mut messages); + transfer_reward(&deps, &mut reward, &mut total_reward, &mut messages)?; + transfer_reward(&deps, &mut relayer, &mut total_reward, &mut messages)?; store_reward(deps.storage, &pair_key, &reward)?; store_reward(deps.storage, &pair_key, &relayer)?; diff --git a/contracts/oraiswap_limit_order/src/orderbook.rs b/contracts/oraiswap_limit_order/src/orderbook.rs index c28aee86..55e04371 100644 --- a/contracts/oraiswap_limit_order/src/orderbook.rs +++ b/contracts/oraiswap_limit_order/src/orderbook.rs @@ -29,6 +29,20 @@ pub struct Order { pub filled_ask_amount: Uint128, } +#[cw_serde] +pub struct OrderWithFee { + pub order_id: u64, + pub status: OrderStatus, + pub direction: OrderDirection, // if direction is sell then offer => sell asset, ask => buy asset + pub bidder_addr: CanonicalAddr, + pub offer_amount: Uint128, + pub ask_amount: Uint128, + pub filled_offer_amount: Uint128, + pub filled_ask_amount: Uint128, + pub reward_fee: Uint128, + pub relayer_fee: Uint128, +} + #[cw_serde] pub struct Executor { pub address: CanonicalAddr, @@ -67,8 +81,9 @@ impl Order { self.filled_ask_amount += ask_amount; self.filled_offer_amount += offer_amount; - if self.filled_offer_amount == self.offer_amount || - self.filled_ask_amount == self.ask_amount { + if self.filled_offer_amount == self.offer_amount + || self.filled_ask_amount == self.ask_amount + { self.status = OrderStatus::Fulfilled; } else { self.status = OrderStatus::PartialFilled; @@ -124,6 +139,42 @@ impl Order { } } +impl OrderWithFee { + // create new order given a price and an offer amount + pub fn fill_order(&mut self, ask_amount: Uint128, offer_amount: Uint128) { + self.filled_ask_amount += ask_amount; + self.filled_offer_amount += offer_amount; + + if self.filled_offer_amount == self.offer_amount + || self.filled_ask_amount == self.ask_amount + { + self.status = OrderStatus::Fulfilled; + } else { + self.status = OrderStatus::PartialFilled; + } + } + + pub fn match_order(&mut self, storage: &mut dyn Storage, pair_key: &[u8]) -> StdResult { + let order = Order { + order_id: self.order_id, + status: self.status, + direction: self.direction, + bidder_addr: self.bidder_addr.to_owned(), + offer_amount: self.offer_amount, + ask_amount: self.ask_amount, + filled_offer_amount: self.filled_offer_amount, + filled_ask_amount: self.filled_ask_amount, + }; + if self.status == OrderStatus::Fulfilled { + // When status is Fulfilled, remove order + remove_order(storage, pair_key, &order) + } else { + // update order + store_order(storage, pair_key, &order, false) + } + } +} + /// Ticks are stored in Ordered database, so we just need to process at 50 recent ticks is ok #[cw_serde] pub struct OrderBook { @@ -333,76 +384,31 @@ impl OrderBook { return None; } - let mut best_buy_price_list: Vec = Vec::new(); - let mut best_sell_price_list: Vec = Vec::new(); - - // if there is spread, find the best list sell price - if let Some(spread) = self.spread { - let spread_factor = Decimal::one() + spread; - for sell_price in sell_price_list { - let sell_price_with_spread = - sell_price.checked_mul(spread_factor).unwrap_or_default(); - if sell_price_with_spread.is_zero() { - continue; - } - let start_after = if let Some(start_after) = Decimal::from_atomics( - sell_price - .atomics() - .checked_sub(Uint128::from(1u64)) - .unwrap_or_default(), // sub 1 because we want to get buy price at the smallest sell price as well, not skip it - Decimal::DECIMAL_PLACES, - ) - .ok() - { - Some(start_after) - } else { - None - }; - let suitable_buy_price_list = query_ticks_prices_with_end( - storage, - pair_key, - OrderDirection::Buy, - start_after, - Some(sell_price_with_spread), - Some(1), // limit 1 because we only need to get the closest buy price possible to the sell price - Some(1), - ); - - // cannot find suitable buy price list for the given sell price - if suitable_buy_price_list.len() == 0 { - continue; - } - // we loop sell price from smallest to highest, matching buy price must go from highest to lowest => always insert highest into the first element - best_buy_price_list.insert(0, suitable_buy_price_list[0]); - best_sell_price_list.push(sell_price); - } + let start_after = if let Some(start_after) = Decimal::from_atomics( + sell_price_list[0] + .atomics() + .checked_sub(Uint128::from(1u64)) + .unwrap_or_default(), // sub 1 because we want to get buy price at the smallest sell price as well, not skip it + Decimal::DECIMAL_PLACES, + ) + .ok() + { + Some(start_after) } else { - let start_after = if let Some(start_after) = Decimal::from_atomics( - sell_price_list[0] - .atomics() - .checked_sub(Uint128::from(1u64)) - .unwrap_or_default(), // sub 1 because we want to get buy price at the smallest sell price as well, not skip it - Decimal::DECIMAL_PLACES, - ) - .ok() - { - Some(start_after) - } else { - None - }; - // desc, all items in this list are ge than the first item in sell list - best_buy_price_list = query_ticks_prices_with_end( - storage, - pair_key, - OrderDirection::Buy, - None, - start_after, - limit, - Some(2i32), - ); - // both price lists are applicable because buy list is always larger than the first item of sell list - best_sell_price_list = sell_price_list; - } + None + }; + // desc, all items in this list are ge than the first item in sell list + let best_buy_price_list = query_ticks_prices_with_end( + storage, + pair_key, + OrderDirection::Buy, + None, + start_after, + limit, + Some(2i32), + ); + // both price lists are applicable because buy list is always larger than the first item of sell list + let best_sell_price_list = sell_price_list; if best_buy_price_list.len() == 0 || best_sell_price_list.len() == 0 { return None; @@ -468,12 +474,19 @@ impl Executor { } pub struct BulkOrders { - pub orders: Vec, + pub orders: Vec, + pub average_order_id: Uint128, pub direction: OrderDirection, pub price: Decimal, + // offer volume pub volume: Uint128, + // remaining volume + pub remaining_volume: Uint128, + // filled volume pub filled_volume: Uint128, + // ask volume pub ask_volume: Uint128, + // filled ask volume pub filled_ask_volume: Uint128, pub spread_volume: Uint128, } @@ -482,25 +495,59 @@ impl BulkOrders { /// Calculate sum of orders base on direction pub fn from_orders(orders: &Vec, price: Decimal, direction: OrderDirection) -> Self { let mut volume = Uint128::zero(); + let mut remaining_volume = Uint128::zero(); let mut ask_volume = Uint128::zero(); - let filled_volume = Uint128::zero(); - let filled_ask_volume = Uint128::zero(); + let mut filled_volume = Uint128::zero(); + let mut filled_ask_volume = Uint128::zero(); + let mut sum_order_id = Uint128::zero(); + let mut average_order_id = Uint128::zero(); let spread_volume = Uint128::zero(); for order in orders { - volume += order.offer_amount.checked_sub(order.filled_offer_amount).unwrap(); - ask_volume += order.ask_amount.checked_sub(order.filled_ask_amount).unwrap(); + sum_order_id += Uint128::from(order.order_id); + volume += order.offer_amount; + ask_volume += order.ask_amount; + + remaining_volume += order + .offer_amount + .checked_sub(order.filled_offer_amount) + .unwrap_or_default(); + + filled_volume += order.filled_offer_amount; + filled_ask_volume += order.filled_ask_amount; } + if orders.len() > 0 { + average_order_id = sum_order_id + .checked_div((orders.len() as u64).into()) + .unwrap(); + } return Self { direction, price, - orders: orders.clone(), - volume, + orders: orders + .clone() + .into_iter() + .map(|order| OrderWithFee { + order_id: order.order_id, + status: order.status, + direction: order.direction, + bidder_addr: order.bidder_addr, + offer_amount: order.offer_amount, + ask_amount: order.ask_amount, + filled_offer_amount: order.filled_offer_amount, + filled_ask_amount: order.filled_ask_amount, + relayer_fee: Uint128::zero(), + reward_fee: Uint128::zero(), + }) + .collect(), + remaining_volume, filled_volume, - ask_volume, filled_ask_volume, spread_volume, + volume, + ask_volume, + average_order_id, }; } } diff --git a/contracts/oraiswap_limit_order/src/testing/contract_test.rs b/contracts/oraiswap_limit_order/src/testing/contract_test.rs index 2748acfb..b326649a 100644 --- a/contracts/oraiswap_limit_order/src/testing/contract_test.rs +++ b/contracts/oraiswap_limit_order/src/testing/contract_test.rs @@ -14,6 +14,198 @@ use oraiswap::limit_order::{ use crate::jsonstr; const USDT_DENOM: &str = "usdt"; +fn basic_fixture() -> (MockApp, Addr) { + let mut app = MockApp::new(&[ + ( + &"addr0000".to_string(), + &[ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(1000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(1000000000u128), + }, + ], + ), + ( + &"addr0001".to_string(), + &[ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(1000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(1000000000u128), + }, + ], + ), + ]); + + app.set_token_contract(Box::new(create_entry_points_testing!(oraiswap_token))); + + app.set_token_balances(&[( + &"asset".to_string(), + &[(&"addr0000".to_string(), &Uint128::from(1000000000u128))], + )]); + + let msg = InstantiateMsg { + name: None, + version: None, + admin: None, + commission_rate: None, + reward_address: None, + }; + let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); + let limit_order_addr = app + .instantiate( + code_id, + Addr::unchecked("addr0000"), + &msg, + &[], + "limit order", + ) + .unwrap(); + + // create order book for pair [orai, usdt] + let msg = ExecuteMsg::CreateOrderBookPair { + base_coin_info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + quote_coin_info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + spread: None, + min_quote_coin_amount: Uint128::from(10u128), + }; + let _res = app + .execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[], + ) + .unwrap(); + (app, limit_order_addr) +} + +#[test] +fn test_query_mid_price() { + let (mut app, limit_order_addr) = basic_fixture(); + let mid_price = app + .query::( + limit_order_addr.clone(), + &QueryMsg::MidPrice { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + }, + ) + .unwrap(); + assert_eq!(mid_price, Decimal::zero()); + // paid 300 usdt to get 150 orai -> 1 ORAI = 2 USD + let msg = ExecuteMsg::SubmitOrder { + direction: OrderDirection::Buy, + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + amount: Uint128::from(150u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + amount: Uint128::from(300u128), + }, + ], + }; + + let _ = app + .execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(300u128), + }], + ) + .unwrap(); + + let mid_price = app + .query::( + limit_order_addr.clone(), + &QueryMsg::MidPrice { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + }, + ) + .unwrap(); + assert_eq!(mid_price, Decimal::from_ratio(1u128, 1u128)); + // now we sell to get a different mid price + let msg = ExecuteMsg::SubmitOrder { + direction: OrderDirection::Sell, + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + amount: Uint128::from(150u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + amount: Uint128::from(1500u128), + }, + ], + }; + + let _ = app + .execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(150u128), + }], + ) + .unwrap(); + + let mid_price = app + .query::( + limit_order_addr.clone(), + &QueryMsg::MidPrice { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + }, + ) + .unwrap(); + assert_eq!(mid_price, Decimal::from_ratio(6u128, 1u128)); +} + #[test] fn submit_order() { let mut app = MockApp::new(&[ @@ -60,7 +252,6 @@ fn submit_order() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -613,7 +804,6 @@ fn cancel_order_native_token() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -952,7 +1142,6 @@ fn cancel_order_token() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -1270,7 +1459,6 @@ fn execute_pair_native_token() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -2275,11 +2463,6 @@ fn execute_pair_native_token() { "orai16stq6f4pnrfpz75n9ujv6qg3czcfa4qyjux5en", )) .unwrap(); - let mut spread_balances = app - .query_all_balances(Addr::unchecked( - "orai139tjpfj0h6ld3wff7v2x92ntdewungfss0ml3n", - )) - .unwrap(); println!("round 0 - address0's balances: {:?}", address0_balances); println!("round 0 - address1's balances: {:?}", address1_balances); @@ -2288,10 +2471,6 @@ fn execute_pair_native_token() { "round 0 - reward_balances's balances: {:?}", reward_balances ); - println!( - "round 0 - spread_balances's balances: {:?}\n\n", - spread_balances - ); let mut expected_balances: Vec = [ Coin { @@ -2329,8 +2508,6 @@ fn execute_pair_native_token() { ] .to_vec(); assert_eq!(address2_balances, expected_balances,); - expected_balances = [].to_vec(); - assert_eq!(spread_balances, expected_balances); // assertion; native asset balance let msg = ExecuteMsg::ExecuteOrderBookPair { @@ -2385,11 +2562,6 @@ fn execute_pair_native_token() { "orai16stq6f4pnrfpz75n9ujv6qg3czcfa4qyjux5en", )) .unwrap(); - spread_balances = app - .query_all_balances(Addr::unchecked( - "orai139tjpfj0h6ld3wff7v2x92ntdewungfss0ml3n", - )) - .unwrap(); println!("round 1 - address0's balances: {:?}", address0_balances); println!("round 1 - address1's balances: {:?}", address1_balances); @@ -2398,10 +2570,6 @@ fn execute_pair_native_token() { "round 1 - reward_balances's balances: {:?}", reward_balances ); - println!( - "round 1 - spread_balances's balances: {:?}\n\n", - spread_balances - ); expected_balances = [ Coin { @@ -2410,7 +2578,7 @@ fn execute_pair_native_token() { }, Coin { denom: USDT_DENOM.to_string(), - amount: Uint128::from(977693u128), + amount: Uint128::from(984184u128), }, ] .to_vec(); @@ -2422,7 +2590,7 @@ fn execute_pair_native_token() { }, Coin { denom: USDT_DENOM.to_string(), - amount: Uint128::from(963224u128), + amount: Uint128::from(965356u128), }, ] .to_vec(); @@ -2440,13 +2608,6 @@ fn execute_pair_native_token() { .to_vec(); assert_eq!(address2_balances, expected_balances); - expected_balances = [Coin { - denom: USDT_DENOM.to_string(), - amount: Uint128::from(8400u128), - }] - .to_vec(); - assert_eq!(spread_balances, expected_balances); - let res = app .query::( limit_order_addr.clone(), @@ -2509,7 +2670,6 @@ fn execute_pair_cw20_token() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -3690,7 +3850,6 @@ fn spread_test() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -4143,7 +4302,6 @@ fn reward_to_executor_test() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -4398,13 +4556,349 @@ fn reward_to_executor_test() { }, Coin { denom: USDT_DENOM.to_string(), - amount: Uint128::from(1000101135u128), + amount: Uint128::from(1000102799u128), }, ] .to_vec(); assert_eq!(address1_balances, expected_balances,); } +#[test] +fn simple_matching_test() { + let mut app = MockApp::new(&[ + ( + &"addr0000".to_string(), + &[ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + ], + ), + ( + &"addr0001".to_string(), + &[ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + ], + ), + ( + &"addr0002".to_string(), + &[ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + ], + ), + ]); + + let msg = InstantiateMsg { + name: None, + version: None, + admin: None, + commission_rate: None, + reward_address: None, + }; + let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); + let limit_order_addr = app + .instantiate( + code_id, + Addr::unchecked("addr0000"), + &msg, + &[], + "limit order", + ) + .unwrap(); + + // Create pair [orai, usdt] for order book + let msg = ExecuteMsg::CreateOrderBookPair { + base_coin_info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + quote_coin_info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + spread: Some(Decimal::percent(1)), + min_quote_coin_amount: Uint128::from(10u128), + }; + + let _res = app.execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[], + ); + + /* <----------------------------------- order 0 -----------------------------------> */ + let msg = ExecuteMsg::SubmitOrder { + direction: OrderDirection::Buy, + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + amount: Uint128::from(1000000u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + amount: Uint128::from(261500000u128), + }, + ], + }; + + // offer usdt, ask for orai + let _res = app + .execute( + Addr::unchecked("addr0002"), + limit_order_addr.clone(), + &msg, + &[Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(261500000u128), + }], + ) + .unwrap(); + + /* <----------------------------------- order 1 -----------------------------------> */ + let msg = ExecuteMsg::SubmitOrder { + direction: OrderDirection::Sell, + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + amount: Uint128::from(10000000u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + amount: Uint128::from(75000000u128), + }, + ], + }; + + let _res = app + .execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000u128), + }], + ) + .unwrap(); + + /* <----------------------------------- order 2 -----------------------------------> */ + let msg = ExecuteMsg::SubmitOrder { + direction: OrderDirection::Buy, + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + amount: Uint128::from(1000000u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + amount: Uint128::from(261500000u128), + }, + ], + }; + + // offer usdt, ask for orai + let _res = app + .execute( + Addr::unchecked("addr0002"), + limit_order_addr.clone(), + &msg, + &[Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(261500000u128), + }], + ) + .unwrap(); + + let mut address0_balances = app.query_all_balances(Addr::unchecked("addr0000")).unwrap(); + let mut address1_balances = app.query_all_balances(Addr::unchecked("addr0001")).unwrap(); + let mut address2_balances = app.query_all_balances(Addr::unchecked("addr0002")).unwrap(); + println!("round 0 - address0's balances: {:?}", address0_balances); + println!("round 0 - address1's balances: {:?}", address1_balances); + println!("round 0 - address2's balances: {:?}\n\n", address2_balances); + + let mut expected_balances: Vec = [ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(9990000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + ] + .to_vec(); + assert_eq!(address0_balances, expected_balances,); + expected_balances = [ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + ] + .to_vec(); + assert_eq!(address1_balances, expected_balances,); + expected_balances = [ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10000000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(9477000000u128), + }, + ] + .to_vec(); + assert_eq!(address2_balances, expected_balances); + + let msg = ExecuteMsg::ExecuteOrderBookPair { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + ], + limit: None, + }; + + // Native token balance mismatch between the argument and the transferred + let res = app.execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[], + ); + app.assert_fail(res); + + let res = app + .query::( + limit_order_addr.clone(), + &QueryMsg::OrderBookMatchable { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + }, + ) + .unwrap(); + + let expected_res = OrderBookMatchableResponse { is_matchable: true }; + assert_eq!(res, expected_res); + + // Excecute all orders + let msg = ExecuteMsg::ExecuteOrderBookPair { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + limit: None, + }; + + let _res = app + .execute( + Addr::unchecked("addr0000"), + limit_order_addr.clone(), + &msg, + &[], + ) + .unwrap(); + println!("[LOG] attribute - round 1 - {:?}", _res); + + address0_balances = app.query_all_balances(Addr::unchecked("addr0000")).unwrap(); + address1_balances = app.query_all_balances(Addr::unchecked("addr0001")).unwrap(); + address2_balances = app.query_all_balances(Addr::unchecked("addr0002")).unwrap(); + println!("round 1 - address0's balances: {:?}", address0_balances); + println!("round 1 - address1's balances: {:?}", address1_balances); + println!("round 1 - address2's balances: {:?}\n\n", address2_balances); + + expected_balances = [ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(9990000000u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(10074922750u128), + }, + ] + .to_vec(); + assert_eq!(address0_balances, expected_balances); + expected_balances = [ + Coin { + denom: ORAI_DENOM.to_string(), + amount: Uint128::from(10001997400u128), + }, + Coin { + denom: USDT_DENOM.to_string(), + amount: Uint128::from(9477000000u128), + }, + ] + .to_vec(); + assert_eq!(address2_balances, expected_balances); + let res = app + .query::( + limit_order_addr.clone(), + &QueryMsg::OrderBookMatchable { + asset_infos: [ + AssetInfo::NativeToken { + denom: ORAI_DENOM.to_string(), + }, + AssetInfo::NativeToken { + denom: USDT_DENOM.to_string(), + }, + ], + }, + ) + .unwrap(); + + let expected_res = OrderBookMatchableResponse { + is_matchable: false, + }; + assert_eq!(res, expected_res); +} + fn mock_basic_query_data() -> (MockApp, Addr) { let mut app = MockApp::new(&[ ( @@ -4454,7 +4948,6 @@ fn mock_basic_query_data() -> (MockApp, Addr) { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -4624,9 +5117,7 @@ fn query_matchable() { ) .unwrap(); - let expected_res = OrderBookMatchableResponse { - is_matchable: false, - }; + let expected_res = OrderBookMatchableResponse { is_matchable: true }; assert_eq!(res, expected_res); println!("[LOG] [2] orderbook matchable: {}", jsonstr!(res)); @@ -4733,7 +5224,6 @@ fn remove_orderbook_pair() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); @@ -5062,7 +5552,6 @@ fn orders_querier() { admin: None, commission_rate: None, reward_address: None, - spread_address: None, }; let code_id = app.upload(Box::new(create_entry_points_testing!(crate))); let limit_order_addr = app @@ -5083,7 +5572,7 @@ fn orders_querier() { quote_coin_info: AssetInfo::NativeToken { denom: ORAI_DENOM.to_string(), }, - spread: Some(Decimal::percent(10)), + spread: Some(Decimal::percent(1)), min_quote_coin_amount: Uint128::from(10u128), }; let _res = app.execute( diff --git a/packages/oraiswap/src/limit_order.rs b/packages/oraiswap/src/limit_order.rs index 37b57272..41786557 100644 --- a/packages/oraiswap/src/limit_order.rs +++ b/packages/oraiswap/src/limit_order.rs @@ -11,7 +11,6 @@ pub struct ContractInfo { pub admin: CanonicalAddr, pub commission_rate: String, pub reward_address: CanonicalAddr, - pub spread_address: CanonicalAddr, } #[cw_serde] @@ -62,7 +61,6 @@ pub struct InstantiateMsg { pub admin: Option, pub commission_rate: Option, pub reward_address: Option, - pub spread_address: Option, } #[cw_serde] @@ -75,7 +73,6 @@ pub enum ExecuteMsg { UpdateConfig { reward_address: Option, - spread_address: Option, commission_rate: Option, }, @@ -121,10 +118,10 @@ pub enum Cw20HookMsg { #[cw_serde] pub enum OrderFilter { - Bidder(String), // filter by bidder - Price(Decimal), // filter by price - Tick, // filter by direction - None, // no filter + Bidder(String), // filter by bidder + Price(Decimal), // filter by price + Tick, // filter by direction + None, // no filter } #[cw_serde] @@ -173,6 +170,8 @@ pub enum QueryMsg { LastOrderId {}, #[returns(OrderBookMatchableResponse)] OrderBookMatchable { asset_infos: [AssetInfo; 2] }, + #[returns(Decimal)] + MidPrice { asset_infos: [AssetInfo; 2] }, } #[cw_serde] @@ -182,6 +181,8 @@ pub struct ContractInfoResponse { // admin can update the parameter, may be multisig pub admin: Addr, + pub commission_rate: String, + pub reward_address: Addr, } #[cw_serde]