From 5f44a3f914464179d7c7f67cae8942ce46d21aae Mon Sep 17 00:00:00 2001 From: Li Smith Date: Mon, 8 Jul 2024 18:10:48 +0800 Subject: [PATCH 01/14] 1 add make_multiple_orders 2 update take_order multiple order logic --- pallet/dex/src/lib.rs | 301 +++++++++++++++++++++++++++++------------- 1 file changed, 211 insertions(+), 90 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index 4aeb9dfa..11e00ae1 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -33,6 +33,7 @@ use frame_support::{ fungibles::{Balanced, Mutate}, tokens::Preservation, }, + BoundedVec, }; #[cfg(test)] @@ -183,6 +184,12 @@ pub struct Trade { maker_order: Order, } +#[derive(Encode, Decode, Default, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo)] +pub struct MultipleOrderInfo { + order_id_list: BoundedVec>, + status: OrderStatus, +} + #[allow(clippy::unused_unit)] #[frame_support::pallet] pub mod pallet { @@ -339,6 +346,30 @@ pub mod pallet { #[pallet::getter(fn native_asset_id)] pub type NativeAssetId = StorageValue<_, u32, ValueQuery>; + #[pallet::storage] + #[pallet::getter(fn multiple_order_infos)] + pub type MultipleOrderInfos = StorageMap< + _, + Blake2_128Concat, + u64, //multiple order infos id + MultipleOrderInfo, + ValueQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn next_mutiple_order_info_index)] + pub(super) type NextMultipleOrderInfoIndex = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn map_mutiple_order_id)] + pub type MapMultipleOrderID = StorageMap< + _, + Blake2_128Concat, + u64, //order index, + u64, //multiple order infos id + ValueQuery, + >; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -398,6 +429,7 @@ pub mod pallet { PriceDoNotMatchOfferedRequestedAmount, DivOverflow, MulOverflow, + MultipleOrderInfoNotFound, } #[pallet::hooks] @@ -643,96 +675,15 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let (asset_id_1, asset_id_2, order_type) = if asset_id_1 > asset_id_2 { - (asset_id_2, asset_id_1, order_type.get_opposite()) - } else { - (asset_id_1, asset_id_2, order_type) - }; - - ensure!( - asset_id_1 != asset_id_2, - Error::::PairAssetIdMustNotEqual - ); - - ensure!( - expiration_block > frame_system::Pallet::::block_number(), - Error::::ExpirationMustBeInFuture - ); - - let (a, b) = match order_type { - OrderType::SELL => (requested_amount, offered_amount), - OrderType::BUY => (offered_amount, requested_amount), - }; - - // because price is an integer, we need to check if the division is exact - // (does not have a remainder) - let price = a - .checked_div(&b) - .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)?; - - // do the check - if price - .checked_mul(&b) - .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)? - != a - { - return Err(Error::::PriceDoNotMatchOfferedRequestedAmount.into()); - } - - NextOrderIndex::::try_mutate(|index| -> DispatchResult { - let order_index = *index; - - let order = Order { - counter: order_index, - pair: (asset_id_1, asset_id_2), - expiration_block, - order_type, - address: who.clone(), - amount_offered: offered_amount, - amout_requested: requested_amount, - price, - unfilled_offered: offered_amount, - unfilled_requested: requested_amount, - order_status: OrderStatus::Pending, - }; - - let update_asset_id = match order.order_type { - OrderType::SELL => asset_id_1, - OrderType::BUY => asset_id_2, - }; - let mut info = UserTokenInfoes::::get(who.clone(), update_asset_id); - info.amount = info - .amount - .checked_sub(&order.amount_offered) - .ok_or(Error::::NotEnoughBalance)?; - info.reserved = info - .reserved - .checked_add(&order.amount_offered) - .ok_or(Error::::TokenBalanceOverflow)?; - UserTokenInfoes::::insert(who.clone(), update_asset_id, info); - - *index = index - .checked_add(One::one()) - .ok_or(Error::::OrderIndexOverflow)?; - - Orders::::insert(order_index, &order); - UserOrders::::insert(who.clone(), order_index, ()); - - let mut expiration_orders = OrderExpiration::::get(expiration_block); - expiration_orders - .try_push(order_index) - .expect("Max expiration_orders"); - OrderExpiration::::insert(expiration_block, expiration_orders); - - let mut bounded_pair_orders = PairOrders::::get((asset_id_1, asset_id_2)); - bounded_pair_orders - .try_push(order_index) - .expect("Max bounded_pair_orders"); - PairOrders::::insert((asset_id_1, asset_id_2), bounded_pair_orders); - - Self::deposit_event(Event::OrderCreated { order_index, order }); - Ok(()) - })?; + Self::create_order_impl( + who, + asset_id_1, + asset_id_2, + offered_amount, + requested_amount, + order_type, + expiration_block, + )?; Ok(().into()) } @@ -803,6 +754,27 @@ pub mod pallet { } } + if MapMultipleOrderID::::contains_key(order_index) { + let multiple_order_id = MapMultipleOrderID::::get(order_index); + + MultipleOrderInfos::::try_mutate_exists( + multiple_order_id, + |may_multiple_order_info| -> DispatchResult { + let multiple_order_info = may_multiple_order_info + .as_mut() + .ok_or(Error::::MultipleOrderInfoNotFound)?; + + for id in &multiple_order_info.order_id_list { + Self::cancel_order_impl(*id)?; + } + + multiple_order_info.status = OrderStatus::FullyFilled; + + Ok(()) + }, + )?; + } + Self::deposit_event(Event::OrderTaken { account: who, order_index, @@ -983,6 +955,52 @@ pub mod pallet { Ok(()) } + + #[pallet::weight({9})] + #[pallet::call_index(9)] + pub fn make_multiple_orders( + origin: OriginFor, + orders: Vec<( + u32, + u32, + BalanceOf, + BalanceOf, + OrderType, + BlockNumberFor, + )>, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let next_multiple_order_info_index = NextMultipleOrderInfoIndex::::get(); + + let mut order_id_list = BoundedVec::>::new(); + for order in orders { + let order_id = NextOrderIndex::::get(); + Self::create_order_impl( + who.clone(), + order.0, + order.1, + order.2, + order.3, + order.4, + order.5, + )?; + + let _ = order_id_list.try_push(order_id); + + MapMultipleOrderID::::insert(order_id, next_multiple_order_info_index); + } + + MultipleOrderInfos::::insert( + next_multiple_order_info_index, + MultipleOrderInfo { + order_id_list: order_id_list.into(), + status: OrderStatus::Pending, + }, + ); + + Ok(().into()) + } } impl Pallet { @@ -1096,6 +1114,109 @@ pub mod pallet { }) } + fn create_order_impl( + who: T::AccountId, + asset_id_1: u32, + asset_id_2: u32, + offered_amount: BalanceOf, + requested_amount: BalanceOf, + order_type: OrderType, + expiration_block: BlockNumberFor, + ) -> DispatchResult { + let (asset_id_1, asset_id_2, order_type) = if asset_id_1 > asset_id_2 { + (asset_id_2, asset_id_1, order_type.get_opposite()) + } else { + (asset_id_1, asset_id_2, order_type) + }; + + ensure!( + asset_id_1 != asset_id_2, + Error::::PairAssetIdMustNotEqual + ); + + ensure!( + expiration_block > frame_system::Pallet::::block_number(), + Error::::ExpirationMustBeInFuture + ); + + let (a, b) = match order_type { + OrderType::SELL => (requested_amount, offered_amount), + OrderType::BUY => (offered_amount, requested_amount), + }; + + // because price is an integer, we need to check if the division is exact + // (does not have a remainder) + let price = a + .checked_div(&b) + .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)?; + + // do the check + if price + .checked_mul(&b) + .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)? + != a + { + return Err(Error::::PriceDoNotMatchOfferedRequestedAmount.into()); + } + + NextOrderIndex::::try_mutate(|index| -> DispatchResult { + let order_index = *index; + + let order = Order { + counter: order_index, + pair: (asset_id_1, asset_id_2), + expiration_block, + order_type, + address: who.clone(), + amount_offered: offered_amount, + amout_requested: requested_amount, + price, + unfilled_offered: offered_amount, + unfilled_requested: requested_amount, + order_status: OrderStatus::Pending, + }; + + let update_asset_id = match order.order_type { + OrderType::SELL => asset_id_1, + OrderType::BUY => asset_id_2, + }; + let mut info = UserTokenInfoes::::get(who.clone(), update_asset_id); + info.amount = info + .amount + .checked_sub(&order.amount_offered) + .ok_or(Error::::NotEnoughBalance)?; + info.reserved = info + .reserved + .checked_add(&order.amount_offered) + .ok_or(Error::::TokenBalanceOverflow)?; + UserTokenInfoes::::insert(who.clone(), update_asset_id, info); + + *index = index + .checked_add(One::one()) + .ok_or(Error::::OrderIndexOverflow)?; + + Orders::::insert(order_index, &order); + UserOrders::::insert(who.clone(), order_index, ()); + + let mut expiration_orders = OrderExpiration::::get(expiration_block); + expiration_orders + .try_push(order_index) + .expect("Max expiration_orders"); + OrderExpiration::::insert(expiration_block, expiration_orders); + + let mut bounded_pair_orders = PairOrders::::get((asset_id_1, asset_id_2)); + bounded_pair_orders + .try_push(order_index) + .expect("Max bounded_pair_orders"); + PairOrders::::insert((asset_id_1, asset_id_2), bounded_pair_orders); + + Self::deposit_event(Event::OrderCreated { order_index, order }); + Ok(()) + })?; + + Ok(()) + } + pub fn update_order_from_trade_order(order: &OrderOf) -> Result<(), DispatchError> { Orders::::insert(order.counter, order); Ok(()) From 3076303d2ca45c70e9539882b9f924d22bb316d1 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Tue, 9 Jul 2024 20:28:08 +0800 Subject: [PATCH 02/14] 1 remove other order when one multiple order is fullyfilled --- pallet/dex/src/lib.rs | 116 ++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 26 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index 11e00ae1..5536436b 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -6,7 +6,7 @@ use frame_support::{ pallet_prelude::{ConstU32, DispatchResult}, sp_std::{convert::TryInto, prelude::*}, traits::{Currency, ExistenceRequirement::AllowDeath, Get, ReservableCurrency}, - BoundedBTreeMap, PalletId, RuntimeDebug, + BoundedBTreeMap, BoundedBTreeSet, PalletId, RuntimeDebug, }; use frame_system::offchain::SendTransactionTypes; @@ -33,7 +33,6 @@ use frame_support::{ fungibles::{Balanced, Mutate}, tokens::Preservation, }, - BoundedVec, }; #[cfg(test)] @@ -186,7 +185,7 @@ pub struct Trade { #[derive(Encode, Decode, Default, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo)] pub struct MultipleOrderInfo { - order_id_list: BoundedVec>, + order_id_set: BoundedBTreeSet>, status: OrderStatus, } @@ -754,26 +753,7 @@ pub mod pallet { } } - if MapMultipleOrderID::::contains_key(order_index) { - let multiple_order_id = MapMultipleOrderID::::get(order_index); - - MultipleOrderInfos::::try_mutate_exists( - multiple_order_id, - |may_multiple_order_info| -> DispatchResult { - let multiple_order_info = may_multiple_order_info - .as_mut() - .ok_or(Error::::MultipleOrderInfoNotFound)?; - - for id in &multiple_order_info.order_id_list { - Self::cancel_order_impl(*id)?; - } - - multiple_order_info.status = OrderStatus::FullyFilled; - - Ok(()) - }, - )?; - } + Self::set_other_multiple_order_cancel(order_index)?; Self::deposit_event(Event::OrderTaken { account: who, @@ -945,6 +925,9 @@ pub mod pallet { )?; } + Self::set_other_multiple_order_cancel(taker_order.counter)?; + Self::set_other_multiple_order_cancel(maker_order.counter)?; + Self::deposit_event(Event::OrderMatched { quantity_base: trade.quantity_base, quantity_quote: trade.quantity_quote, @@ -973,7 +956,7 @@ pub mod pallet { let next_multiple_order_info_index = NextMultipleOrderInfoIndex::::get(); - let mut order_id_list = BoundedVec::>::new(); + let mut order_id_set = BoundedBTreeSet::>::new(); for order in orders { let order_id = NextOrderIndex::::get(); Self::create_order_impl( @@ -986,7 +969,7 @@ pub mod pallet { order.5, )?; - let _ = order_id_list.try_push(order_id); + let _ = order_id_set.try_insert(order_id); MapMultipleOrderID::::insert(order_id, next_multiple_order_info_index); } @@ -994,7 +977,7 @@ pub mod pallet { MultipleOrderInfos::::insert( next_multiple_order_info_index, MultipleOrderInfo { - order_id_list: order_id_list.into(), + order_id_set: order_id_set.into(), status: OrderStatus::Pending, }, ); @@ -1324,6 +1307,11 @@ pub mod pallet { let (matched_quantity_requested, matched_quantity_offered) = match taker_unfilled_quantity_requested.cmp(&maker_order.unfilled_offered) { Ordering::Greater => { + if MapMultipleOrderID::::contains_key(taker_order.counter) { + // multiple order must be FullyFilled + continue; + } + //taker request amout > maker offer amout (maker_order.unfilled_offered, maker_order.unfilled_requested) } @@ -1355,6 +1343,11 @@ pub mod pallet { } } Ordering::Less => { + if MapMultipleOrderID::::contains_key(maker_order.counter) { + // multiple order must be FullyFilled + continue; + } + //taker request amout < maker offer amout match taker_order.order_type { OrderType::BUY => { @@ -1408,6 +1401,18 @@ pub mod pallet { || taker_unfilled_quantity_offered == BalanceOf::::default() { taker_order.order_status = OrderStatus::FullyFilled; + + if MapMultipleOrderID::::contains_key(taker_order.counter) { + // remove from order book, and cancel other order + Self::remove_other_multiple_order_in_order_book( + taker_order.counter, + maker_book, + ); + Self::remove_other_multiple_order_in_order_book( + taker_order.counter, + another_book, + ); + } } else if taker_unfilled_quantity_requested != taker_order.amout_requested { taker_order.order_status = OrderStatus::PartialFilled; } @@ -1415,6 +1420,20 @@ pub mod pallet { if maker_unfilled_quantity_offered == BalanceOf::::default() { maker_order.order_status = OrderStatus::FullyFilled; + if MapMultipleOrderID::::contains_key(maker_order.counter) { + // remove from order book, and cancel other order + // remove from order book, and cancel other order + Self::remove_other_multiple_order_in_order_book( + maker_order.counter, + maker_book, + ); + Self::remove_other_multiple_order_in_order_book( + maker_order.counter, + another_book, + ); + continue; + } + match_result.match_details.push(Trade { price: maker_order.price, quantity_base: matched_quantity_base, @@ -1464,6 +1483,51 @@ pub mod pallet { Ok(match_result) } + fn remove_other_multiple_order_in_order_book( + matched_order_id: u64, + //order_id_set: BoundedBTreeSet>, + order_book: &mut BoundedBTreeMap< + OrderBookKey>, + OrderOf, + ConstU32<{ u32::MAX }>, + >, + ) { + let multiple_order_id = MapMultipleOrderID::::get(matched_order_id); + let info = MultipleOrderInfos::::get(multiple_order_id); + + let mut order_id_set = info.order_id_set.clone(); + order_id_set.remove(&matched_order_id); + order_book.retain(|k, _| !order_id_set.contains(&k.order_id)); + } + + fn set_other_multiple_order_cancel(order_index: u64) -> DispatchResult { + if MapMultipleOrderID::::contains_key(order_index) { + let multiple_order_id = MapMultipleOrderID::::get(order_index); + + MultipleOrderInfos::::try_mutate_exists( + multiple_order_id, + |may_multiple_order_info| -> DispatchResult { + let multiple_order_info = may_multiple_order_info + .as_mut() + .ok_or(Error::::MultipleOrderInfoNotFound)?; + + let mut order_id_set = multiple_order_info.order_id_set.clone(); + order_id_set.remove(&order_index); + + for id in &order_id_set { + Self::cancel_order_impl(*id)?; + } + + multiple_order_info.status = OrderStatus::FullyFilled; + + Ok(()) + }, + )?; + } + + Ok(()) + } + fn offchain_unsigned_tx( match_result: MatchResult, OrderOf>, ) -> Result<(), Error> { From 2dd417a330817beb2490e7a157328f18a3f7ddd7 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Wed, 10 Jul 2024 19:59:48 +0800 Subject: [PATCH 03/14] add test_multiple_orders --- pallet/dex/src/tests.rs | 112 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index bf107f51..1751a60d 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -968,3 +968,115 @@ fn test_offchain_worker_order_matching() { assert_eq!(buy_order_book.get(&208111).unwrap(), &(1456777, 7)); }) } + +#[test] +fn test_multiple_orders() { + new_test_ext().execute_with(|| { + let block = 1; + System::set_block_number(block); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 777, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 888, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 999, 100)); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 777, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 888, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 999, 100)); + + assert_ok!(Dex::make_multiple_orders( + RuntimeOrigin::signed(1), + vec![ + (777, 888, 100, 1, OrderType::BUY, 6), + (777, 999, 100, 1, OrderType::BUY, 6), + (888, 999, 100, 1, OrderType::BUY, 6), + ], + )); + + Dex::offchain_worker(block); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(1), + 777, + 999, + 1, + 100, + OrderType::SELL, + 6 + )); + + Dex::offchain_worker(block); + + //order_book price=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + for (_, order) in Orders::::iter() { + if order.order_status != OrderStatus::FullyFilled { + if order.order_type == OrderType::BUY { + if !buy_order_book.contains_key(&order.price) { + buy_order_book.insert( + order.price, + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = buy_order_book.get(&order.price).unwrap(); + + buy_order_book.insert( + order.price, + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } else { + if !sell_order_book.contains_key(&order.price) { + sell_order_book.insert( + order.price, + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = sell_order_book.get(&order.price).unwrap(); + sell_order_book.insert( + order.price, + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } + } + } + + assert_eq!(sell_order_book.len(), 0); + assert_eq!(buy_order_book.len(), 0); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 0, + reserved: 0, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 101, + reserved: 0, + } + ); + + assert_eq!( + Orders::::get(0).unwrap().order_status, + OrderStatus::FullyCancelled, + ); + assert_eq!( + Orders::::get(1).unwrap().order_status, + OrderStatus::FullyFilled, + ); + assert_eq!( + Orders::::get(2).unwrap().order_status, + OrderStatus::FullyCancelled, + ); + assert_eq!( + Orders::::get(4).unwrap().order_status, + OrderStatus::FullyFilled, + ); + }); +} From e25b9ff0e643d7a03c191d9032abc61cc6cf0b67 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Wed, 10 Jul 2024 21:18:49 +0800 Subject: [PATCH 04/14] fix test build error --- Cargo.lock | 1 + pallet/dex/Cargo.toml | 2 ++ pallet/dex/src/lib.rs | 55 +++++++++++++++++------------------------ pallet/dex/src/tests.rs | 31 +++++++++++++++-------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f63cbfab..5a59708c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8623,6 +8623,7 @@ dependencies = [ "sp-core 7.0.0", "sp-io 7.0.0", "sp-runtime 7.0.0", + "sp-std 5.0.0 (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.43)", ] [[package]] diff --git a/pallet/dex/Cargo.toml b/pallet/dex/Cargo.toml index b9e8dfde..10f183fa 100644 --- a/pallet/dex/Cargo.toml +++ b/pallet/dex/Cargo.toml @@ -16,6 +16,7 @@ scale-codec = { package = "parity-scale-codec", workspace = true, features = ["m scale-info.workspace = true sp-runtime.workspace = true sp-io.workspace = true +sp-std.workspace = true [dev-dependencies] pallet-assets.workspace = true @@ -34,6 +35,7 @@ std = [ "scale-codec/std", "scale-info/std", "sp-core/std", + "sp-std/std", "sp-runtime/std", ] diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index 5536436b..d6d345d8 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -199,6 +199,7 @@ pub mod pallet { Blake2_128Concat, }; use frame_system::offchain::SubmitTransaction; + use sp_std::collections::btree_set::BTreeSet; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -1268,6 +1269,9 @@ pub mod pallet { taker_order: taker_order.clone(), match_details: vec![], }; + + let mut disable_multiple_order_id_in_group = BTreeSet::::new(); + let mut taker_unfilled_quantity_requested = taker_order.amout_requested; let mut taker_unfilled_quantity_offered = taker_order.amount_offered; loop { @@ -1292,6 +1296,10 @@ pub mod pallet { let maker_order = maker_book.get_mut(&maker_order_key).unwrap(); + if disable_multiple_order_id_in_group.contains(&maker_order.counter) { + break; + } + if taker_order.order_type == OrderType::BUY && taker_order.price < maker_order.price { break; @@ -1309,7 +1317,7 @@ pub mod pallet { Ordering::Greater => { if MapMultipleOrderID::::contains_key(taker_order.counter) { // multiple order must be FullyFilled - continue; + break; } //taker request amout > maker offer amout @@ -1345,7 +1353,7 @@ pub mod pallet { Ordering::Less => { if MapMultipleOrderID::::contains_key(maker_order.counter) { // multiple order must be FullyFilled - continue; + break; } //taker request amout < maker offer amout @@ -1403,15 +1411,10 @@ pub mod pallet { taker_order.order_status = OrderStatus::FullyFilled; if MapMultipleOrderID::::contains_key(taker_order.counter) { - // remove from order book, and cancel other order - Self::remove_other_multiple_order_in_order_book( - taker_order.counter, - maker_book, - ); - Self::remove_other_multiple_order_in_order_book( - taker_order.counter, - another_book, - ); + let mut order_id_set = + Self::get_disable_multiple_order_id_in_group(taker_order.counter); + + disable_multiple_order_id_in_group.append(&mut order_id_set); } } else if taker_unfilled_quantity_requested != taker_order.amout_requested { taker_order.order_status = OrderStatus::PartialFilled; @@ -1421,17 +1424,10 @@ pub mod pallet { maker_order.order_status = OrderStatus::FullyFilled; if MapMultipleOrderID::::contains_key(maker_order.counter) { - // remove from order book, and cancel other order - // remove from order book, and cancel other order - Self::remove_other_multiple_order_in_order_book( - maker_order.counter, - maker_book, - ); - Self::remove_other_multiple_order_in_order_book( - maker_order.counter, - another_book, - ); - continue; + let mut order_id_set = + Self::get_disable_multiple_order_id_in_group(maker_order.counter); + + disable_multiple_order_id_in_group.append(&mut order_id_set); } match_result.match_details.push(Trade { @@ -1480,24 +1476,19 @@ pub mod pallet { } } + maker_book.retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); + another_book.retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); + Ok(match_result) } - fn remove_other_multiple_order_in_order_book( - matched_order_id: u64, - //order_id_set: BoundedBTreeSet>, - order_book: &mut BoundedBTreeMap< - OrderBookKey>, - OrderOf, - ConstU32<{ u32::MAX }>, - >, - ) { + fn get_disable_multiple_order_id_in_group(matched_order_id: u64) -> BTreeSet { let multiple_order_id = MapMultipleOrderID::::get(matched_order_id); let info = MultipleOrderInfos::::get(multiple_order_id); let mut order_id_set = info.order_id_set.clone(); order_id_set.remove(&matched_order_id); - order_book.retain(|k, _| !order_id_set.contains(&k.order_id)); + order_id_set.into() } fn set_other_multiple_order_cancel(order_index: u64) -> DispatchResult { diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index 1751a60d..88d6e884 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -971,17 +971,28 @@ fn test_offchain_worker_order_matching() { #[test] fn test_multiple_orders() { - new_test_ext().execute_with(|| { + use frame_support::traits::OffchainWorker; + let mut ext = new_test_ext(); + + ext.execute_with(|| add_blocks(1)); + ext.persist_offchain_overlay(); + + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.execute_with(|| { let block = 1; System::set_block_number(block); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 777, 100)); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 888, 100)); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 999, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 999, 1000)); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 777, 100)); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 888, 100)); - assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 999, 100)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 999, 1000)); assert_ok!(Dex::make_multiple_orders( RuntimeOrigin::signed(1), @@ -1049,7 +1060,7 @@ fn test_multiple_orders() { assert_eq!( UserTokenInfoes::::get(1, 777), TokenInfo { - amount: 0, + amount: 900, reserved: 0, } ); @@ -1057,7 +1068,7 @@ fn test_multiple_orders() { assert_eq!( UserTokenInfoes::::get(1, 999), TokenInfo { - amount: 101, + amount: 1001, reserved: 0, } ); @@ -1078,5 +1089,5 @@ fn test_multiple_orders() { Orders::::get(4).unwrap().order_status, OrderStatus::FullyFilled, ); - }); + }) } From f4546c7033018981877695108899ae7394b127df Mon Sep 17 00:00:00 2001 From: Li Smith Date: Thu, 11 Jul 2024 11:55:25 +0800 Subject: [PATCH 05/14] fix test case --- pallet/dex/src/lib.rs | 38 +++++-- pallet/dex/src/tests.rs | 229 +++++++++++++++++++++++++++------------- 2 files changed, 183 insertions(+), 84 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index d6d345d8..dd04e4b7 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -1274,11 +1274,26 @@ pub mod pallet { let mut taker_unfilled_quantity_requested = taker_order.amout_requested; let mut taker_unfilled_quantity_offered = taker_order.amount_offered; - loop { + + let max_loop_step: usize = maker_book.len(); + for _n in 0..max_loop_step { if maker_book.is_empty() { break; } + { + if !disable_multiple_order_id_in_group.is_empty() { + maker_book.retain(|k, _| { + !disable_multiple_order_id_in_group.contains(&k.order_id) + }); + another_book.retain(|k, _| { + !disable_multiple_order_id_in_group.contains(&k.order_id) + }); + + disable_multiple_order_id_in_group.clear(); + } + } + let maker_order_key = match maker_book_type { OrderType::BUY => match maker_book.last_key_value() { Some((k, _)) => k.clone(), @@ -1296,10 +1311,6 @@ pub mod pallet { let maker_order = maker_book.get_mut(&maker_order_key).unwrap(); - if disable_multiple_order_id_in_group.contains(&maker_order.counter) { - break; - } - if taker_order.order_type == OrderType::BUY && taker_order.price < maker_order.price { break; @@ -1316,8 +1327,8 @@ pub mod pallet { match taker_unfilled_quantity_requested.cmp(&maker_order.unfilled_offered) { Ordering::Greater => { if MapMultipleOrderID::::contains_key(taker_order.counter) { - // multiple order must be FullyFilled - break; + // multiple order must be FullyFilled, skip PartialFilled + continue; } //taker request amout > maker offer amout @@ -1352,8 +1363,8 @@ pub mod pallet { } Ordering::Less => { if MapMultipleOrderID::::contains_key(maker_order.counter) { - // multiple order must be FullyFilled - break; + // multiple order must be FullyFilled, skip PartialFilled + continue; } //taker request amout < maker offer amout @@ -1476,8 +1487,13 @@ pub mod pallet { } } - maker_book.retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); - another_book.retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); + if !disable_multiple_order_id_in_group.is_empty() { + maker_book.retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); + another_book + .retain(|k, _| !disable_multiple_order_id_in_group.contains(&k.order_id)); + + disable_multiple_order_id_in_group.clear(); + } Ok(match_result) } diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index 88d6e884..a5599a69 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -997,97 +997,180 @@ fn test_multiple_orders() { assert_ok!(Dex::make_multiple_orders( RuntimeOrigin::signed(1), vec![ - (777, 888, 100, 1, OrderType::BUY, 6), - (777, 999, 100, 1, OrderType::BUY, 6), - (888, 999, 100, 1, OrderType::BUY, 6), + (777, 888, 100, 10, OrderType::BUY, 6), + (777, 999, 100, 10, OrderType::BUY, 6), + (888, 999, 100, 10, OrderType::BUY, 6), ], )); - Dex::offchain_worker(block); - - assert_ok!(Dex::make_order( - RuntimeOrigin::signed(1), - 777, - 999, - 1, - 100, - OrderType::SELL, - 6 - )); + // not full filled + { + Dex::offchain_worker(block); - Dex::offchain_worker(block); + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 5, + 50, + OrderType::SELL, + 6 + )); - //order_book price=> (total_offered_amount, total_requested_amount) - let mut sell_order_book = BTreeMap::new(); - let mut buy_order_book = BTreeMap::new(); + Dex::offchain_worker(block); - for (_, order) in Orders::::iter() { - if order.order_status != OrderStatus::FullyFilled { - if order.order_type == OrderType::BUY { - if !buy_order_book.contains_key(&order.price) { - buy_order_book.insert( - order.price, - (order.unfilled_offered, order.unfilled_requested), - ); - } else { - let v = buy_order_book.get(&order.price).unwrap(); + let mut txs = vec![]; + while !pool_state.read().transactions.is_empty() { + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + txs.insert(0, tx); + } - buy_order_book.insert( - order.price, - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); + for tx in txs { + match tx.call { + RuntimeCall::Dex(crate::Call::update_match_order_unsigned { + match_result: m, + }) => { + let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); } - } else { - if !sell_order_book.contains_key(&order.price) { - sell_order_book.insert( - order.price, - (order.unfilled_offered, order.unfilled_requested), - ); + _ => { + assert_eq!(2, 3); + } + }; + } + + //order_book price=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + for (_, order) in Orders::::iter() { + if order.order_status != OrderStatus::FullyFilled { + if order.order_type == OrderType::BUY { + if !buy_order_book.contains_key(&(order.price, order.pair)) { + buy_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = buy_order_book.get(&(order.price, order.pair)).unwrap(); + + buy_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } } else { - let v = sell_order_book.get(&order.price).unwrap(); - sell_order_book.insert( - order.price, - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); + if !sell_order_book.contains_key(&(order.price, order.pair)) { + sell_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = sell_order_book.get(&(order.price, order.pair)).unwrap(); + sell_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } } } } + + assert_eq!(sell_order_book.len(), 1); + assert_eq!(buy_order_book.len(), 3); } - assert_eq!(sell_order_book.len(), 0); - assert_eq!(buy_order_book.len(), 0); + // full filled + { + Dex::offchain_worker(block); - assert_eq!( - UserTokenInfoes::::get(1, 777), - TokenInfo { - amount: 900, - reserved: 0, + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 10, + 100, + OrderType::SELL, + 6 + )); + + Dex::offchain_worker(block); + + let mut txs = vec![]; + while !pool_state.read().transactions.is_empty() { + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + txs.insert(0, tx); } - ); - assert_eq!( - UserTokenInfoes::::get(1, 999), - TokenInfo { - amount: 1001, - reserved: 0, + for tx in txs { + match tx.call { + RuntimeCall::Dex(crate::Call::update_match_order_unsigned { + match_result: m, + }) => { + let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); + } + _ => { + assert_eq!(2, 3); + } + }; } - ); - assert_eq!( - Orders::::get(0).unwrap().order_status, - OrderStatus::FullyCancelled, - ); - assert_eq!( - Orders::::get(1).unwrap().order_status, - OrderStatus::FullyFilled, - ); - assert_eq!( - Orders::::get(2).unwrap().order_status, - OrderStatus::FullyCancelled, - ); - assert_eq!( - Orders::::get(4).unwrap().order_status, - OrderStatus::FullyFilled, - ); + //order_book price=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + for (_, order) in Orders::::iter() { + if order.order_status != OrderStatus::FullyFilled { + if order.order_type == OrderType::BUY { + if !buy_order_book.contains_key(&(order.price, order.pair)) { + buy_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = buy_order_book.get(&(order.price, order.pair)).unwrap(); + + buy_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } else { + if !sell_order_book.contains_key(&(order.price, order.pair)) { + sell_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = sell_order_book.get(&(order.price, order.pair)).unwrap(); + sell_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } + } + } + + assert_eq!(sell_order_book.len(), 1); + assert_eq!(buy_order_book.len(), 0); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 1010, + reserved: 0, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 900, + reserved: 0, + } + ); + } }) } From b4013fdb5001ae266ba0ae2a3250e774df413c83 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Thu, 11 Jul 2024 12:47:27 +0800 Subject: [PATCH 06/14] fix cargo clippy --- pallet/dex/src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index dd04e4b7..8e97de70 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -210,6 +210,15 @@ pub mod pallet { type MapMatchEnginesOf = BoundedBTreeMap<(u32, u32), MatchEngine, BalanceOf>, ConstU32<{ u32::MAX }>>; + type OrderInput = ( + u32, + u32, + BalanceOf, + BalanceOf, + OrderType, + BlockNumberFor, + ); + #[pallet::genesis_config] #[derive(Default)] pub struct GenesisConfig { @@ -944,14 +953,7 @@ pub mod pallet { #[pallet::call_index(9)] pub fn make_multiple_orders( origin: OriginFor, - orders: Vec<( - u32, - u32, - BalanceOf, - BalanceOf, - OrderType, - BlockNumberFor, - )>, + orders: Vec>, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -978,7 +980,7 @@ pub mod pallet { MultipleOrderInfos::::insert( next_multiple_order_info_index, MultipleOrderInfo { - order_id_set: order_id_set.into(), + order_id_set, status: OrderStatus::Pending, }, ); From 0bad4392eda8445bd45518c8ac8add3d8384ef41 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Thu, 11 Jul 2024 12:53:53 +0800 Subject: [PATCH 07/14] increase NextMultipleOrderInfoIndex after use --- pallet/dex/src/lib.rs | 56 ++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index 8e97de70..28fac350 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -957,33 +957,41 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let next_multiple_order_info_index = NextMultipleOrderInfoIndex::::get(); - - let mut order_id_set = BoundedBTreeSet::>::new(); - for order in orders { - let order_id = NextOrderIndex::::get(); - Self::create_order_impl( - who.clone(), - order.0, - order.1, - order.2, - order.3, - order.4, - order.5, - )?; + NextMultipleOrderInfoIndex::::try_mutate(|index| -> DispatchResult { + let next_multiple_order_info_index = *index; + + let mut order_id_set = BoundedBTreeSet::>::new(); + for order in orders { + let order_id = NextOrderIndex::::get(); + Self::create_order_impl( + who.clone(), + order.0, + order.1, + order.2, + order.3, + order.4, + order.5, + )?; - let _ = order_id_set.try_insert(order_id); + let _ = order_id_set.try_insert(order_id); - MapMultipleOrderID::::insert(order_id, next_multiple_order_info_index); - } + MapMultipleOrderID::::insert(order_id, next_multiple_order_info_index); + } - MultipleOrderInfos::::insert( - next_multiple_order_info_index, - MultipleOrderInfo { - order_id_set, - status: OrderStatus::Pending, - }, - ); + MultipleOrderInfos::::insert( + next_multiple_order_info_index, + MultipleOrderInfo { + order_id_set, + status: OrderStatus::Pending, + }, + ); + + *index = index + .checked_add(One::one()) + .ok_or(Error::::OrderIndexOverflow)?; + + Ok(()) + })?; Ok(().into()) } From a55a327fa9932577d9de1a34e921c2d038ba585d Mon Sep 17 00:00:00 2001 From: Li Smith Date: Thu, 11 Jul 2024 15:49:02 +0800 Subject: [PATCH 08/14] add test_multiple_orders_sell --- Cargo.lock | 1 + pallet/dex/Cargo.toml | 3 + pallet/dex/src/mock.rs | 64 ++++++++++- pallet/dex/src/tests.rs | 238 ++++++++++++++++++++-------------------- 4 files changed, 183 insertions(+), 123 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5a59708c..f2c19ed5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8619,6 +8619,7 @@ dependencies = [ "pallet-balances", "pallet-timestamp", "parity-scale-codec", + "parking_lot 0.12.1", "scale-info", "sp-core 7.0.0", "sp-io 7.0.0", diff --git a/pallet/dex/Cargo.toml b/pallet/dex/Cargo.toml index 10f183fa..b4b96441 100644 --- a/pallet/dex/Cargo.toml +++ b/pallet/dex/Cargo.toml @@ -12,12 +12,14 @@ targets = ["x86_64-unknown-linux-gnu"] frame-support.workspace = true frame-system.workspace = true log.workspace = true +parking_lot = { version = "0.12.1", optional = true } scale-codec = { package = "parity-scale-codec", workspace = true, features = ["max-encoded-len"] } scale-info.workspace = true sp-runtime.workspace = true sp-io.workspace = true sp-std.workspace = true + [dev-dependencies] pallet-assets.workspace = true pallet-balances.workspace = true @@ -27,6 +29,7 @@ sp-core.workspace = true [features] default = ["std"] std = [ + "parking_lot", "frame-support/std", "frame-system/std", "pallet-assets/std", diff --git a/pallet/dex/src/mock.rs b/pallet/dex/src/mock.rs index 57757d14..a8f53564 100644 --- a/pallet/dex/src/mock.rs +++ b/pallet/dex/src/mock.rs @@ -1,5 +1,7 @@ use crate as pallet_dex; +use std::{collections::BTreeMap, sync::Arc}; +use crate::{OrderStatus, OrderType, Orders}; use frame_support::{ pallet_prelude::Weight, parameter_types, sp_io, @@ -7,8 +9,10 @@ use frame_support::{ weights::constants::RocksDbWeight, PalletId, }; +use parking_lot::RwLock; +use scale_codec::Decode; use sp_core::{ConstU128, ConstU32, ConstU64, H256}; -use sp_runtime::{testing::Header, traits::IdentityLookup}; +use sp_runtime::{offchain::testing::PoolState, testing::Header, traits::IdentityLookup}; pub type AccountId = u128; pub type Balance = u128; @@ -240,3 +244,61 @@ pub fn add_blocks(blocks: usize) { new_block(); } } + +pub fn call_offchain_worker_function_in_transactions(pool_state: &Arc>) { + let mut txs = vec![]; + while !pool_state.read().transactions.is_empty() { + let tx = pool_state.write().transactions.pop().unwrap(); + let tx = Extrinsic::decode(&mut &*tx).unwrap(); + txs.insert(0, tx); + } + + for tx in txs { + match tx.call { + RuntimeCall::Dex(crate::Call::update_match_order_unsigned { match_result: m }) => { + let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); + } + _ => { + assert_eq!(2, 3); + } + }; + } +} + +pub fn create_order_book_map_by_price( + sell_order_book: &mut BTreeMap<(Balance, (u32, u32)), (Balance, Balance)>, + buy_order_book: &mut BTreeMap<(Balance, (u32, u32)), (Balance, Balance)>, +) { + for (_, order) in Orders::::iter() { + if order.order_status != OrderStatus::FullyFilled { + if order.order_type == OrderType::BUY { + if !buy_order_book.contains_key(&(order.price, order.pair)) { + buy_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = buy_order_book.get(&(order.price, order.pair)).unwrap(); + + buy_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } else { + if !sell_order_book.contains_key(&(order.price, order.pair)) { + sell_order_book.insert( + (order.price, order.pair), + (order.unfilled_offered, order.unfilled_requested), + ); + } else { + let v = sell_order_book.get(&(order.price, order.pair)).unwrap(); + sell_order_book.insert( + (order.price, order.pair), + (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), + ); + } + } + } + } +} diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index a5599a69..ee83a0a8 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -901,23 +901,7 @@ fn test_offchain_worker_order_matching() { Dex::offchain_worker(block); - let mut txs = vec![]; - while !pool_state.read().transactions.is_empty() { - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - txs.insert(0, tx); - } - - for tx in txs { - match tx.call { - RuntimeCall::Dex(crate::Call::update_match_order_unsigned { match_result: m }) => { - let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); - } - _ => { - assert_eq!(2, 3); - } - }; - } + call_offchain_worker_function_in_transactions(&pool_state); //order_book price=> (total_offered_amount, total_requested_amount) let mut sell_order_book = BTreeMap::new(); @@ -970,7 +954,7 @@ fn test_offchain_worker_order_matching() { } #[test] -fn test_multiple_orders() { +fn test_multiple_orders_buy() { use frame_support::traits::OffchainWorker; let mut ext = new_test_ext(); @@ -1019,62 +1003,13 @@ fn test_multiple_orders() { Dex::offchain_worker(block); - let mut txs = vec![]; - while !pool_state.read().transactions.is_empty() { - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - txs.insert(0, tx); - } + call_offchain_worker_function_in_transactions(&pool_state); - for tx in txs { - match tx.call { - RuntimeCall::Dex(crate::Call::update_match_order_unsigned { - match_result: m, - }) => { - let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); - } - _ => { - assert_eq!(2, 3); - } - }; - } - - //order_book price=> (total_offered_amount, total_requested_amount) + //order_book price, pair=> (total_offered_amount, total_requested_amount) let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - for (_, order) in Orders::::iter() { - if order.order_status != OrderStatus::FullyFilled { - if order.order_type == OrderType::BUY { - if !buy_order_book.contains_key(&(order.price, order.pair)) { - buy_order_book.insert( - (order.price, order.pair), - (order.unfilled_offered, order.unfilled_requested), - ); - } else { - let v = buy_order_book.get(&(order.price, order.pair)).unwrap(); - - buy_order_book.insert( - (order.price, order.pair), - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); - } - } else { - if !sell_order_book.contains_key(&(order.price, order.pair)) { - sell_order_book.insert( - (order.price, order.pair), - (order.unfilled_offered, order.unfilled_requested), - ); - } else { - let v = sell_order_book.get(&(order.price, order.pair)).unwrap(); - sell_order_book.insert( - (order.price, order.pair), - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); - } - } - } - } + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); assert_eq!(sell_order_book.len(), 1); assert_eq!(buy_order_book.len(), 3); @@ -1096,62 +1031,13 @@ fn test_multiple_orders() { Dex::offchain_worker(block); - let mut txs = vec![]; - while !pool_state.read().transactions.is_empty() { - let tx = pool_state.write().transactions.pop().unwrap(); - let tx = Extrinsic::decode(&mut &*tx).unwrap(); - txs.insert(0, tx); - } + call_offchain_worker_function_in_transactions(&pool_state); - for tx in txs { - match tx.call { - RuntimeCall::Dex(crate::Call::update_match_order_unsigned { - match_result: m, - }) => { - let _ = Dex::update_match_order_unsigned(RuntimeOrigin::none(), m); - } - _ => { - assert_eq!(2, 3); - } - }; - } - - //order_book price=> (total_offered_amount, total_requested_amount) + //order_book price, pair=> (total_offered_amount, total_requested_amount) let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - for (_, order) in Orders::::iter() { - if order.order_status != OrderStatus::FullyFilled { - if order.order_type == OrderType::BUY { - if !buy_order_book.contains_key(&(order.price, order.pair)) { - buy_order_book.insert( - (order.price, order.pair), - (order.unfilled_offered, order.unfilled_requested), - ); - } else { - let v = buy_order_book.get(&(order.price, order.pair)).unwrap(); - - buy_order_book.insert( - (order.price, order.pair), - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); - } - } else { - if !sell_order_book.contains_key(&(order.price, order.pair)) { - sell_order_book.insert( - (order.price, order.pair), - (order.unfilled_offered, order.unfilled_requested), - ); - } else { - let v = sell_order_book.get(&(order.price, order.pair)).unwrap(); - sell_order_book.insert( - (order.price, order.pair), - (v.0 + order.unfilled_offered, v.1 + order.unfilled_requested), - ); - } - } - } - } + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); assert_eq!(sell_order_book.len(), 1); assert_eq!(buy_order_book.len(), 0); @@ -1174,3 +1060,111 @@ fn test_multiple_orders() { } }) } + +#[test] +fn test_multiple_orders_sell() { + use frame_support::traits::OffchainWorker; + let mut ext = new_test_ext(); + + ext.execute_with(|| add_blocks(1)); + ext.persist_offchain_overlay(); + + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.execute_with(|| { + let block = 1; + System::set_block_number(block); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 999, 1000)); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 999, 1000)); + + assert_ok!(Dex::make_multiple_orders( + RuntimeOrigin::signed(1), + vec![ + (777, 888, 10, 100, OrderType::SELL, 6), + (777, 999, 10, 100, OrderType::SELL, 6), + (888, 999, 10, 100, OrderType::SELL, 6), + ], + )); + + // not full filled + { + Dex::offchain_worker(block); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 50, + 5, + OrderType::BUY, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_book.len(), 3); + assert_eq!(buy_order_book.len(), 1); + } + + // full filled + { + Dex::offchain_worker(block); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 100, + 10, + OrderType::BUY, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_book.len(), 0); + assert_eq!(buy_order_book.len(), 1); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 990, + reserved: 0, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 1100, + reserved: 0, + } + ); + } + }) +} From 2d998efe56d09b79be5f7a3a12d68d9a06dbda7b Mon Sep 17 00:00:00 2001 From: Li Smith Date: Thu, 11 Jul 2024 18:49:34 +0800 Subject: [PATCH 09/14] update metadata --- .../scale/eth_light_client_brooklyn.scale | Bin 362573 -> 363017 bytes .../data/scale/eth_light_client_sydney.scale | Bin 362559 -> 363003 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/node/tests/data/scale/eth_light_client_brooklyn.scale b/node/tests/data/scale/eth_light_client_brooklyn.scale index 001cdf472473ba5d034a0293dfa65f8ddf581a1e..60f509a50641ca93cea3f75712b0096d94836939 100644 GIT binary patch delta 11930 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 delta 11590 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 From ce761f073de79b6b83c9c3ac694b21a95cb70398 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Mon, 15 Jul 2024 21:24:25 +0800 Subject: [PATCH 10/14] add unfilled_order_id_set ,reserved_asset_id, reserved, unuse_reserved to multiple orders info --- pallet/dex/src/lib.rs | 238 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 219 insertions(+), 19 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index 28fac350..feebacf2 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -184,9 +184,13 @@ pub struct Trade { } #[derive(Encode, Decode, Default, Eq, PartialEq, Clone, RuntimeDebug, TypeInfo)] -pub struct MultipleOrderInfo { +pub struct MultipleOrderInfo { order_id_set: BoundedBTreeSet>, + unfilled_order_id_set: BoundedBTreeSet>, status: OrderStatus, + reserved_asset_id: u32, + reserved: Balance, + unuse_reserved: Balance, } #[allow(clippy::unused_unit)] @@ -199,7 +203,7 @@ pub mod pallet { Blake2_128Concat, }; use frame_system::offchain::SubmitTransaction; - use sp_std::collections::btree_set::BTreeSet; + use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -361,7 +365,7 @@ pub mod pallet { _, Blake2_128Concat, u64, //multiple order infos id - MultipleOrderInfo, + MultipleOrderInfo>, ValueQuery, >; @@ -439,6 +443,8 @@ pub mod pallet { DivOverflow, MulOverflow, MultipleOrderInfoNotFound, + OfferAssetMustBeReservedAsset, + OfferedAmountMustBeLessThanReservedAmount, } #[pallet::hooks] @@ -763,7 +769,7 @@ pub mod pallet { } } - Self::set_other_multiple_order_cancel(order_index)?; + Self::update_multiple_order_in_group(order_index, order.unfilled_offered)?; Self::deposit_event(Event::OrderTaken { account: who, @@ -935,8 +941,14 @@ pub mod pallet { )?; } - Self::set_other_multiple_order_cancel(taker_order.counter)?; - Self::set_other_multiple_order_cancel(maker_order.counter)?; + Self::update_multiple_order_in_group( + taker_order.counter, + taker_order.unfilled_offered, + )?; + Self::update_multiple_order_in_group( + maker_order.counter, + maker_order.unfilled_offered, + )?; Self::deposit_event(Event::OrderMatched { quantity_base: trade.quantity_base, @@ -954,16 +966,57 @@ pub mod pallet { pub fn make_multiple_orders( origin: OriginFor, orders: Vec>, + reserved_asset_id: u32, + reserved_amount: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; + for order in &orders { + let ( + asset_id_1, + asset_id_2, + offered_amount, + _requested_amount, + order_type, + _expiration_block, + ) = order; + + let offer_asset = match order_type { + OrderType::SELL => asset_id_1, + OrderType::BUY => asset_id_2, + }; + + ensure!( + offer_asset == &reserved_asset_id, + Error::::OfferAssetMustBeReservedAsset + ); + + ensure!( + offered_amount <= &reserved_amount, + Error::::OfferedAmountMustBeLessThanReservedAmount + ); + } + + let mut info = UserTokenInfoes::::get(who.clone(), reserved_asset_id); + info.amount = info + .amount + .checked_sub(&reserved_amount) + .ok_or(Error::::NotEnoughBalance)?; + + info.reserved = info + .reserved + .checked_add(&reserved_amount) + .ok_or(Error::::TokenBalanceOverflow)?; + UserTokenInfoes::::insert(who.clone(), reserved_asset_id, info); + NextMultipleOrderInfoIndex::::try_mutate(|index| -> DispatchResult { let next_multiple_order_info_index = *index; let mut order_id_set = BoundedBTreeSet::>::new(); for order in orders { let order_id = NextOrderIndex::::get(); - Self::create_order_impl( + + Self::create_multiple_order_impl( who.clone(), order.0, order.1, @@ -981,8 +1034,12 @@ pub mod pallet { MultipleOrderInfos::::insert( next_multiple_order_info_index, MultipleOrderInfo { - order_id_set, + order_id_set: order_id_set.clone(), + unfilled_order_id_set: order_id_set.clone(), status: OrderStatus::Pending, + reserved_asset_id, + reserved: reserved_amount, + unuse_reserved: reserved_amount, }, ); @@ -1211,6 +1268,94 @@ pub mod pallet { Ok(()) } + fn create_multiple_order_impl( + who: T::AccountId, + asset_id_1: u32, + asset_id_2: u32, + offered_amount: BalanceOf, + requested_amount: BalanceOf, + order_type: OrderType, + expiration_block: BlockNumberFor, + ) -> DispatchResult { + let (asset_id_1, asset_id_2, order_type) = if asset_id_1 > asset_id_2 { + (asset_id_2, asset_id_1, order_type.get_opposite()) + } else { + (asset_id_1, asset_id_2, order_type) + }; + + ensure!( + asset_id_1 != asset_id_2, + Error::::PairAssetIdMustNotEqual + ); + + ensure!( + expiration_block > frame_system::Pallet::::block_number(), + Error::::ExpirationMustBeInFuture + ); + + let (a, b) = match order_type { + OrderType::SELL => (requested_amount, offered_amount), + OrderType::BUY => (offered_amount, requested_amount), + }; + + // because price is an integer, we need to check if the division is exact + // (does not have a remainder) + let price = a + .checked_div(&b) + .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)?; + + // do the check + if price + .checked_mul(&b) + .ok_or(Error::::PriceDoNotMatchOfferedRequestedAmount)? + != a + { + return Err(Error::::PriceDoNotMatchOfferedRequestedAmount.into()); + } + + NextOrderIndex::::try_mutate(|index| -> DispatchResult { + let order_index = *index; + + let order = Order { + counter: order_index, + pair: (asset_id_1, asset_id_2), + expiration_block, + order_type, + address: who.clone(), + amount_offered: offered_amount, + amout_requested: requested_amount, + price, + unfilled_offered: offered_amount, + unfilled_requested: requested_amount, + order_status: OrderStatus::Pending, + }; + + *index = index + .checked_add(One::one()) + .ok_or(Error::::OrderIndexOverflow)?; + + Orders::::insert(order_index, &order); + UserOrders::::insert(who.clone(), order_index, ()); + + let mut expiration_orders = OrderExpiration::::get(expiration_block); + expiration_orders + .try_push(order_index) + .expect("Max expiration_orders"); + OrderExpiration::::insert(expiration_block, expiration_orders); + + let mut bounded_pair_orders = PairOrders::::get((asset_id_1, asset_id_2)); + bounded_pair_orders + .try_push(order_index) + .expect("Max bounded_pair_orders"); + PairOrders::::insert((asset_id_1, asset_id_2), bounded_pair_orders); + + Self::deposit_event(Event::OrderCreated { order_index, order }); + Ok(()) + })?; + + Ok(()) + } + pub fn update_order_from_trade_order(order: &OrderOf) -> Result<(), DispatchError> { Orders::::insert(order.counter, order); Ok(()) @@ -1285,6 +1430,9 @@ pub mod pallet { let mut taker_unfilled_quantity_requested = taker_order.amout_requested; let mut taker_unfilled_quantity_offered = taker_order.amount_offered; + let mut multiple_order_group_cache = + BTreeMap::>>::new(); + let max_loop_step: usize = maker_book.len(); for _n in 0..max_loop_step { if maker_book.is_empty() { @@ -1432,8 +1580,10 @@ pub mod pallet { taker_order.order_status = OrderStatus::FullyFilled; if MapMultipleOrderID::::contains_key(taker_order.counter) { - let mut order_id_set = - Self::get_disable_multiple_order_id_in_group(taker_order.counter); + let mut order_id_set = Self::get_disable_multiple_order_id_in_group( + taker_order.counter, + &mut multiple_order_group_cache, + ); disable_multiple_order_id_in_group.append(&mut order_id_set); } @@ -1445,8 +1595,10 @@ pub mod pallet { maker_order.order_status = OrderStatus::FullyFilled; if MapMultipleOrderID::::contains_key(maker_order.counter) { - let mut order_id_set = - Self::get_disable_multiple_order_id_in_group(maker_order.counter); + let mut order_id_set = Self::get_disable_multiple_order_id_in_group( + maker_order.counter, + &mut multiple_order_group_cache, + ); disable_multiple_order_id_in_group.append(&mut order_id_set); } @@ -1508,16 +1660,47 @@ pub mod pallet { Ok(match_result) } - fn get_disable_multiple_order_id_in_group(matched_order_id: u64) -> BTreeSet { + fn get_disable_multiple_order_id_in_group( + matched_order_id: u64, + map_infos: &mut BTreeMap>>, + ) -> BTreeSet { let multiple_order_id = MapMultipleOrderID::::get(matched_order_id); - let info = MultipleOrderInfos::::get(multiple_order_id); + + if !map_infos.contains_key(&multiple_order_id) { + map_infos.insert( + multiple_order_id, + MultipleOrderInfos::::get(multiple_order_id), + ); + } + let info = map_infos.get_mut(&multiple_order_id).unwrap(); + let order = Orders::::get(matched_order_id).unwrap(); let mut order_id_set = info.order_id_set.clone(); order_id_set.remove(&matched_order_id); - order_id_set.into() + + let mut new_order_id_set = BTreeSet::::new(); + + if info.unuse_reserved < order.unfilled_offered { + info.unuse_reserved = Default::default(); + } else { + info.unuse_reserved = info.unuse_reserved - order.unfilled_offered; + } + + for id in &order_id_set { + let order = Orders::::get(id).unwrap(); + + if order.unfilled_offered > info.unuse_reserved { + new_order_id_set.insert(*id); + } + } + + new_order_id_set.into() } - fn set_other_multiple_order_cancel(order_index: u64) -> DispatchResult { + fn update_multiple_order_in_group( + order_index: u64, + offered_amount: BalanceOf, + ) -> DispatchResult { if MapMultipleOrderID::::contains_key(order_index) { let multiple_order_id = MapMultipleOrderID::::get(order_index); @@ -1528,14 +1711,31 @@ pub mod pallet { .as_mut() .ok_or(Error::::MultipleOrderInfoNotFound)?; - let mut order_id_set = multiple_order_info.order_id_set.clone(); + let mut order_id_set = multiple_order_info.unfilled_order_id_set.clone(); order_id_set.remove(&order_index); + multiple_order_info.unuse_reserved = multiple_order_info + .unuse_reserved + .checked_sub(&offered_amount) + .ok_or(Error::::NotEnoughBalance)?; + for id in &order_id_set { - Self::cancel_order_impl(*id)?; + if !Orders::::contains_key(id) { + multiple_order_info.unfilled_order_id_set.remove(id); + continue; + } + + let order = Orders::::get(id).unwrap(); + + if order.unfilled_offered > multiple_order_info.unuse_reserved { + Self::cancel_order_impl(*id)?; + multiple_order_info.unfilled_order_id_set.remove(id); + } } - multiple_order_info.status = OrderStatus::FullyFilled; + if multiple_order_info.unfilled_order_id_set.is_empty() { + multiple_order_info.status = OrderStatus::FullyFilled; + } Ok(()) }, From c5ea59f43cf4a7afaf253b840528ed687bfd5c5f Mon Sep 17 00:00:00 2001 From: Li Smith Date: Tue, 16 Jul 2024 19:40:36 +0800 Subject: [PATCH 11/14] fix testcase --- pallet/dex/src/lib.rs | 93 +++++++++++++++-- pallet/dex/src/mock.rs | 8 +- pallet/dex/src/tests.rs | 220 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 295 insertions(+), 26 deletions(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index feebacf2..cc3f9a7e 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -769,7 +769,7 @@ pub mod pallet { } } - Self::update_multiple_order_in_group(order_index, order.unfilled_offered)?; + Self::update_multiple_order_in_group(order_index, order.amount_offered)?; Self::deposit_event(Event::OrderTaken { account: who, @@ -943,11 +943,11 @@ pub mod pallet { Self::update_multiple_order_in_group( taker_order.counter, - taker_order.unfilled_offered, + taker_order.amount_offered, )?; Self::update_multiple_order_in_group( maker_order.counter, - maker_order.unfilled_offered, + maker_order.amount_offered, )?; Self::deposit_event(Event::OrderMatched { @@ -1165,6 +1165,58 @@ pub mod pallet { }) } + fn cancel_order_impl_no_update_reserved(order_index: u64) -> DispatchResult { + Orders::::try_mutate_exists(order_index, |order| -> DispatchResult { + let order = order.take().ok_or(Error::::InvalidOrderIndex)?; + + UserOrders::::remove(order.address, order_index); + + PairOrders::::try_mutate_exists( + order.pair, + |bounded_pair_orders| -> DispatchResult { + let pair_orders = bounded_pair_orders + .as_mut() + .ok_or(Error::::PairOrderNotFound)?; + let rt = pair_orders.binary_search(&order_index); + if let Ok(rt_index) = rt { + pair_orders.remove(rt_index); + } + + PairOrders::::insert(order.pair, pair_orders); + Ok(()) + }, + )?; + + Self::deposit_event(Event::OrderCanceled { order_index }); + + Ok(()) + }) + } + + fn close_multiple_order(multiple_order_id: u64, owner: T::AccountId) -> DispatchResult { + if MapMultipleOrderID::::contains_key(multiple_order_id) { + let multiple_order_info = MultipleOrderInfos::::get(multiple_order_id); + + let mut info = + UserTokenInfoes::::get(owner.clone(), multiple_order_info.reserved_asset_id); + info.amount = info + .amount + .checked_add(&multiple_order_info.unuse_reserved) + .ok_or(Error::::TokenBalanceOverflow)?; + info.reserved = info + .reserved + .checked_sub(&multiple_order_info.unuse_reserved) + .ok_or(Error::::NotEnoughBalance)?; + UserTokenInfoes::::insert( + owner.clone(), + multiple_order_info.reserved_asset_id, + info, + ); + } + + Ok(()) + } + fn create_order_impl( who: T::AccountId, asset_id_1: u32, @@ -1433,6 +1485,12 @@ pub mod pallet { let mut multiple_order_group_cache = BTreeMap::>>::new(); + let mut skip_book = BoundedBTreeMap::< + OrderBookKey>, + OrderOf, + ConstU32<{ u32::MAX }>, + >::new(); + let max_loop_step: usize = maker_book.len(); for _n in 0..max_loop_step { if maker_book.is_empty() { @@ -1486,6 +1544,9 @@ pub mod pallet { Ordering::Greater => { if MapMultipleOrderID::::contains_key(taker_order.counter) { // multiple order must be FullyFilled, skip PartialFilled + let _ = skip_book + .try_insert(maker_order_key.clone(), maker_order.clone()); + maker_book.remove(&maker_order_key); continue; } @@ -1522,6 +1583,9 @@ pub mod pallet { Ordering::Less => { if MapMultipleOrderID::::contains_key(maker_order.counter) { // multiple order must be FullyFilled, skip PartialFilled + let _ = skip_book + .try_insert(maker_order_key.clone(), maker_order.clone()); + maker_book.remove(&maker_order_key); continue; } @@ -1632,6 +1696,10 @@ pub mod pallet { } } + for key in skip_book.keys() { + let _ = maker_book.try_insert(key.clone(), skip_book.get(key).unwrap().clone()); + } + if taker_unfilled_quantity_requested != BalanceOf::::default() && taker_unfilled_quantity_offered != BalanceOf::::default() { @@ -1711,14 +1779,16 @@ pub mod pallet { .as_mut() .ok_or(Error::::MultipleOrderInfoNotFound)?; - let mut order_id_set = multiple_order_info.unfilled_order_id_set.clone(); - order_id_set.remove(&order_index); - + multiple_order_info + .unfilled_order_id_set + .remove(&order_index); multiple_order_info.unuse_reserved = multiple_order_info .unuse_reserved .checked_sub(&offered_amount) .ok_or(Error::::NotEnoughBalance)?; + let order_id_set = multiple_order_info.unfilled_order_id_set.clone(); + for id in &order_id_set { if !Orders::::contains_key(id) { multiple_order_info.unfilled_order_id_set.remove(id); @@ -1728,12 +1798,21 @@ pub mod pallet { let order = Orders::::get(id).unwrap(); if order.unfilled_offered > multiple_order_info.unuse_reserved { - Self::cancel_order_impl(*id)?; + Self::cancel_order_impl_no_update_reserved(*id)?; + multiple_order_info.unfilled_order_id_set.remove(id); + } + } + + if multiple_order_info.reserved == Default::default() { + for id in &order_id_set { + Self::cancel_order_impl_no_update_reserved(*id)?; multiple_order_info.unfilled_order_id_set.remove(id); } } if multiple_order_info.unfilled_order_id_set.is_empty() { + let order = Orders::::get(order_index).unwrap(); + Self::close_multiple_order(order_index, order.address.clone())?; multiple_order_info.status = OrderStatus::FullyFilled; } diff --git a/pallet/dex/src/mock.rs b/pallet/dex/src/mock.rs index a8f53564..4ee0cef7 100644 --- a/pallet/dex/src/mock.rs +++ b/pallet/dex/src/mock.rs @@ -268,10 +268,13 @@ pub fn call_offchain_worker_function_in_transactions(pool_state: &Arc, buy_order_book: &mut BTreeMap<(Balance, (u32, u32)), (Balance, Balance)>, -) { +) -> (u32, u32) { + let mut sell_order_count = 0; + let mut buy_order_count = 0; for (_, order) in Orders::::iter() { if order.order_status != OrderStatus::FullyFilled { if order.order_type == OrderType::BUY { + buy_order_count += 1; if !buy_order_book.contains_key(&(order.price, order.pair)) { buy_order_book.insert( (order.price, order.pair), @@ -286,6 +289,7 @@ pub fn create_order_book_map_by_price( ); } } else { + sell_order_count += 1; if !sell_order_book.contains_key(&(order.price, order.pair)) { sell_order_book.insert( (order.price, order.pair), @@ -301,4 +305,6 @@ pub fn create_order_book_map_by_price( } } } + + (sell_order_count, buy_order_count) } diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index ee83a0a8..6ae1e1c4 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -981,12 +981,44 @@ fn test_multiple_orders_buy() { assert_ok!(Dex::make_multiple_orders( RuntimeOrigin::signed(1), vec![ - (777, 888, 100, 10, OrderType::BUY, 6), (777, 999, 100, 10, OrderType::BUY, 6), - (888, 999, 100, 10, OrderType::BUY, 6), + (777, 999, 100, 10, OrderType::BUY, 6), + (777, 999, 200, 20, OrderType::BUY, 6), ], + 999, + 300, )); + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 700, + reserved: 300, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let mut unfilled_order_id_set = BoundedBTreeSet::>::new(); + let _ = unfilled_order_id_set.try_insert(0); + let _ = unfilled_order_id_set.try_insert(1); + let _ = unfilled_order_id_set.try_insert(2); + + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::Pending, + reserved_asset_id: 999, + reserved: 300, + unuse_reserved: 300, + } + ); + // not full filled { Dex::offchain_worker(block); @@ -1009,10 +1041,11 @@ fn test_multiple_orders_buy() { let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); - assert_eq!(sell_order_book.len(), 1); - assert_eq!(buy_order_book.len(), 3); + assert_eq!(sell_order_count, 1); + assert_eq!(buy_order_count, 3); } // full filled @@ -1037,10 +1070,11 @@ fn test_multiple_orders_buy() { let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); - assert_eq!(sell_order_book.len(), 1); - assert_eq!(buy_order_book.len(), 0); + assert_eq!(sell_order_count, 1); + assert_eq!(buy_order_count, 2); assert_eq!( UserTokenInfoes::::get(1, 777), @@ -1053,10 +1087,68 @@ fn test_multiple_orders_buy() { assert_eq!( UserTokenInfoes::::get(1, 999), TokenInfo { - amount: 900, + amount: 700, + reserved: 200, + } + ); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 20, + 200, + OrderType::SELL, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_count, 1); + assert_eq!(buy_order_count, 0); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 1030, reserved: 0, } ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 700, + reserved: 0, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let unfilled_order_id_set = BoundedBTreeSet::>::new(); + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::FullyFilled, + reserved_asset_id: 999, + reserved: 300, + unuse_reserved: 0, + } + ); } }) } @@ -1091,10 +1183,42 @@ fn test_multiple_orders_sell() { vec![ (777, 888, 10, 100, OrderType::SELL, 6), (777, 999, 10, 100, OrderType::SELL, 6), - (888, 999, 10, 100, OrderType::SELL, 6), + (777, 999, 20, 200, OrderType::SELL, 6), ], + 777, + 30, )); + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 970, + reserved: 30, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let mut unfilled_order_id_set = BoundedBTreeSet::>::new(); + let _ = unfilled_order_id_set.try_insert(0); + let _ = unfilled_order_id_set.try_insert(1); + let _ = unfilled_order_id_set.try_insert(2); + + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::Pending, + reserved_asset_id: 777, + reserved: 30, + unuse_reserved: 30, + } + ); + // not full filled { Dex::offchain_worker(block); @@ -1117,10 +1241,11 @@ fn test_multiple_orders_sell() { let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); - assert_eq!(sell_order_book.len(), 3); - assert_eq!(buy_order_book.len(), 1); + assert_eq!(sell_order_count, 3); + assert_eq!(buy_order_count, 1); } // full filled @@ -1145,16 +1270,17 @@ fn test_multiple_orders_sell() { let mut sell_order_book = BTreeMap::new(); let mut buy_order_book = BTreeMap::new(); - create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); - assert_eq!(sell_order_book.len(), 0); - assert_eq!(buy_order_book.len(), 1); + assert_eq!(sell_order_count, 2); + assert_eq!(buy_order_count, 1); assert_eq!( UserTokenInfoes::::get(1, 777), TokenInfo { - amount: 990, - reserved: 0, + amount: 970, + reserved: 20, } ); @@ -1165,6 +1291,64 @@ fn test_multiple_orders_sell() { reserved: 0, } ); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 200, + 20, + OrderType::BUY, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_count, 0); + assert_eq!(buy_order_count, 1); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 970, + reserved: 0, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 1300, + reserved: 0, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let unfilled_order_id_set = BoundedBTreeSet::>::new(); + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::FullyFilled, + reserved_asset_id: 777, + reserved: 30, + unuse_reserved: 0, + } + ); } }) } From 1936bfd93a9d21d65590e89a5b1f26efb0449565 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Tue, 16 Jul 2024 19:52:04 +0800 Subject: [PATCH 12/14] cargo clippy --- .../scale/eth_light_client_brooklyn.scale | Bin 363017 -> 363347 bytes .../data/scale/eth_light_client_sydney.scale | Bin 363003 -> 363333 bytes pallet/dex/src/lib.rs | 11 +++-------- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/node/tests/data/scale/eth_light_client_brooklyn.scale b/node/tests/data/scale/eth_light_client_brooklyn.scale index 60f509a50641ca93cea3f75712b0096d94836939..e6780ff8125439230ca2e82a8741d489fa500681 100644 GIT binary patch delta 308 zcmeC2B6fM2SVIfr7AD6#E(h{2xJ39tOm!j0- z)S|M~l=#Hr;?$D(%oG7eo>F5Y21Xm0%=CjP%;N2dcbK*(-eJDd%gZ9;l$ev4mz>Hd z!5}f+B8$a=E2K0pEi)$vXv%b}ES6A7oW@SpWfYgRD9tM^PK}3|FTn^iaC%=B%LK-N z?ef_yI~n~-{L|7>iyT4D@+~bcaY_w>Iu$I$AjGf)tROYTF*m<7530r|wYWGWBQX!I n7A(gg%rO1qUgm;!k6f1R9=WXRr5Wc;zu3s4vHf=kD<2~O3UzEz delta 121 zcmcaSO{{Z@SVIfr7AD6#iY$y0`9&$IMa5g$7(K#LlWqJ#JkPv>(h{2xJ39u>>5j5Y zzU{B>Fl~QzhqS7gS1ONmXDEa^Z diff --git a/node/tests/data/scale/eth_light_client_sydney.scale b/node/tests/data/scale/eth_light_client_sydney.scale index f4cb55cd2b423c1a7b2749fd8447674e622f1c05..8689aa0c303cbbd90fb5f37b3bdcb19af2a413f7 100644 GIT binary patch delta 288 zcmex8S?uUEv4$4LElj3&7efVst4=Elw>eOHGMSEG|whiO)vx&4a4(Ni8l8$wv^n;Bo8r$D?u<|hi0NILUF#rGn delta 89 zcmV-f0H*)NmKOVy7J!5Sv;sBU0R)#e+yWnmklX^dklX{(jRODx2$yea1VESfY6KSp v0L2ZLVR!=-mvCzYcL7MZ=xYSI0hjTU18|35ZUnbqZUwI!0SLD=iUkn?{Nx}e diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index cc3f9a7e..fa962848 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -1734,12 +1734,7 @@ pub mod pallet { ) -> BTreeSet { let multiple_order_id = MapMultipleOrderID::::get(matched_order_id); - if !map_infos.contains_key(&multiple_order_id) { - map_infos.insert( - multiple_order_id, - MultipleOrderInfos::::get(multiple_order_id), - ); - } + map_infos.entry(multiple_order_id).or_insert_with(|| MultipleOrderInfos::::get(multiple_order_id)); let info = map_infos.get_mut(&multiple_order_id).unwrap(); let order = Orders::::get(matched_order_id).unwrap(); @@ -1751,7 +1746,7 @@ pub mod pallet { if info.unuse_reserved < order.unfilled_offered { info.unuse_reserved = Default::default(); } else { - info.unuse_reserved = info.unuse_reserved - order.unfilled_offered; + info.unuse_reserved -= order.unfilled_offered; } for id in &order_id_set { @@ -1762,7 +1757,7 @@ pub mod pallet { } } - new_order_id_set.into() + new_order_id_set } fn update_multiple_order_in_group( From 9bdcfccf8d0690d7afee277f5860a89cffa8a8f4 Mon Sep 17 00:00:00 2001 From: Li Smith Date: Tue, 16 Jul 2024 19:58:39 +0800 Subject: [PATCH 13/14] cargo fmt --- pallet/dex/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pallet/dex/src/lib.rs b/pallet/dex/src/lib.rs index fa962848..04bbf34e 100644 --- a/pallet/dex/src/lib.rs +++ b/pallet/dex/src/lib.rs @@ -1734,7 +1734,9 @@ pub mod pallet { ) -> BTreeSet { let multiple_order_id = MapMultipleOrderID::::get(matched_order_id); - map_infos.entry(multiple_order_id).or_insert_with(|| MultipleOrderInfos::::get(multiple_order_id)); + map_infos + .entry(multiple_order_id) + .or_insert_with(|| MultipleOrderInfos::::get(multiple_order_id)); let info = map_infos.get_mut(&multiple_order_id).unwrap(); let order = Orders::::get(matched_order_id).unwrap(); From db58b181df8a80f5bb049a14ce9303f83aff399b Mon Sep 17 00:00:00 2001 From: Li Smith Date: Wed, 17 Jul 2024 20:04:03 +0800 Subject: [PATCH 14/14] add testcase test_multiple_orders_different_pricess --- pallet/dex/src/tests.rs | 203 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/pallet/dex/src/tests.rs b/pallet/dex/src/tests.rs index 6ae1e1c4..69c7bdef 100644 --- a/pallet/dex/src/tests.rs +++ b/pallet/dex/src/tests.rs @@ -1352,3 +1352,206 @@ fn test_multiple_orders_sell() { } }) } + +#[test] +fn test_multiple_orders_different_pricess() { + use frame_support::traits::OffchainWorker; + let mut ext = new_test_ext(); + + ext.execute_with(|| add_blocks(1)); + ext.persist_offchain_overlay(); + + let (offchain, _offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + ext.execute_with(|| { + let block = 1; + System::set_block_number(block); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(1), 999, 1000)); + + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 777, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 888, 1000)); + assert_ok!(Dex::deposit(RuntimeOrigin::signed(2), 999, 1000)); + + assert_ok!(Dex::make_multiple_orders( + RuntimeOrigin::signed(1), + vec![ + (777, 888, 10, 100, OrderType::SELL, 6), + (777, 999, 10, 100, OrderType::SELL, 6), + (777, 999, 20, 400, OrderType::SELL, 6), + ], + 777, + 30, + )); + + assert_ok!(Dex::make_multiple_orders( + RuntimeOrigin::signed(1), + vec![ + (777, 888, 10, 200, OrderType::SELL, 6), + (777, 999, 10, 200, OrderType::SELL, 6), + (777, 999, 20, 600, OrderType::SELL, 6), + ], + 777, + 30, + )); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 940, + reserved: 60, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let mut unfilled_order_id_set = BoundedBTreeSet::>::new(); + let _ = unfilled_order_id_set.try_insert(0); + let _ = unfilled_order_id_set.try_insert(1); + let _ = unfilled_order_id_set.try_insert(2); + + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::Pending, + reserved_asset_id: 777, + reserved: 30, + unuse_reserved: 30, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(3); + let _ = order_id_set.try_insert(4); + let _ = order_id_set.try_insert(5); + + let mut unfilled_order_id_set = BoundedBTreeSet::>::new(); + let _ = unfilled_order_id_set.try_insert(3); + let _ = unfilled_order_id_set.try_insert(4); + let _ = unfilled_order_id_set.try_insert(5); + assert_eq!( + MultipleOrderInfos::::get(1), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::Pending, + reserved_asset_id: 777, + reserved: 30, + unuse_reserved: 30, + } + ); + + // full filled + { + Dex::offchain_worker(block); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 100, + 10, + OrderType::BUY, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_count, 5); + assert_eq!(buy_order_count, 0); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 940, + reserved: 50, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 1100, + reserved: 0, + } + ); + + assert_ok!(Dex::make_order( + RuntimeOrigin::signed(2), + 777, + 999, + 400, + 20, + OrderType::BUY, + 6 + )); + + Dex::offchain_worker(block); + + call_offchain_worker_function_in_transactions(&pool_state); + + //order_book price, pair=> (total_offered_amount, total_requested_amount) + let mut sell_order_book = BTreeMap::new(); + let mut buy_order_book = BTreeMap::new(); + + let (sell_order_count, buy_order_count) = + create_order_book_map_by_price(&mut sell_order_book, &mut buy_order_book); + + assert_eq!(sell_order_count, 3); + assert_eq!(buy_order_count, 0); + + assert_eq!( + UserTokenInfoes::::get(1, 777), + TokenInfo { + amount: 940, + reserved: 30, + } + ); + + assert_eq!( + UserTokenInfoes::::get(1, 999), + TokenInfo { + amount: 1500, + reserved: 0, + } + ); + + let mut order_id_set = BoundedBTreeSet::>::new(); + let _ = order_id_set.try_insert(0); + let _ = order_id_set.try_insert(1); + let _ = order_id_set.try_insert(2); + + let unfilled_order_id_set = BoundedBTreeSet::>::new(); + assert_eq!( + MultipleOrderInfos::::get(0), + MultipleOrderInfo { + order_id_set, + unfilled_order_id_set, + status: OrderStatus::FullyFilled, + reserved_asset_id: 777, + reserved: 30, + unuse_reserved: 0, + } + ); + } + }) +}