From aee2d17322426e525817c4ca8e220f03754666f1 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 11 Mar 2025 11:55:26 +0700 Subject: [PATCH 01/21] add premium field in SetPriceReq and MakerOrder --- mm2src/mm2_main/src/lp_ordermatch.rs | 18 +++++++++++++++++- .../src/lp_ordermatch/my_orders_storage.rs | 2 ++ .../src/lp_ordermatch/simple_market_maker.rs | 1 + mm2src/mm2_main/src/ordermatch_tests.rs | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 3a61efb868..1d36ce16bd 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1722,6 +1722,8 @@ pub struct MakerOrder { p2p_privkey: Option, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, + #[serde(default, skip_serializing_if = "MmNumber::is_zero")] + premium: MmNumber, } pub struct MakerOrderBuilder<'a> { @@ -1735,6 +1737,7 @@ pub struct MakerOrderBuilder<'a> { conf_settings: Option, save_in_history: bool, swap_version: u8, + premium: MmNumber, } pub enum MakerOrderBuildError { @@ -1885,6 +1888,7 @@ impl<'a> MakerOrderBuilder<'a> { conf_settings: None, save_in_history: true, swap_version: SWAP_VERSION_DEFAULT, + premium: Default::default(), } } @@ -1929,6 +1933,11 @@ impl<'a> MakerOrderBuilder<'a> { /// In the future alls users will be using TPU V2 by default without "use_trading_proto_v2" configuration. pub fn set_legacy_swap_v(&mut self) { self.swap_version = legacy_swap_version() } + pub fn with_premium(mut self, premium: MmNumber) -> Self { + self.premium = premium; + self + } + /// Build MakerOrder #[allow(clippy::result_large_err)] pub fn build(self) -> Result { @@ -1986,6 +1995,7 @@ impl<'a> MakerOrderBuilder<'a> { rel_orderbook_ticker: self.rel_orderbook_ticker, p2p_privkey, swap_version: SwapVersion::from(self.swap_version), + premium: self.premium, }) } @@ -2011,6 +2021,7 @@ impl<'a> MakerOrderBuilder<'a> { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::from(self.swap_version), + premium: Default::default(), } } } @@ -2142,6 +2153,7 @@ impl From for MakerOrder { rel_orderbook_ticker: taker_order.rel_orderbook_ticker, p2p_privkey: taker_order.p2p_privkey, swap_version: taker_order.request.swap_version, + premium: Default::default(), }, // The "buy" taker order is recreated with reversed pair as Maker order is always considered as "sell" TakerAction::Buy => { @@ -2165,6 +2177,7 @@ impl From for MakerOrder { rel_orderbook_ticker: taker_order.base_orderbook_ticker, p2p_privkey: taker_order.p2p_privkey, swap_version: taker_order.request.swap_version, + premium: Default::default(), } }, } @@ -4713,6 +4726,8 @@ pub struct SetPriceReq { rel_nota: Option, #[serde(default = "get_true")] save_in_history: bool, + #[serde(default)] + premium: MmNumber, } #[derive(Deserialize)] @@ -4980,7 +4995,8 @@ pub async fn create_maker_order(ctx: &MmArc, req: SetPriceReq) -> Result mpsc::Receiver { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, None, ); @@ -1040,6 +1050,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, None, ); @@ -1063,6 +1074,7 @@ fn prepare_for_cancel_by(ctx: &MmArc) -> mpsc::Receiver { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, None, ); @@ -1255,6 +1267,7 @@ fn test_maker_order_was_updated() { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; let mut update_msg = MakerOrderUpdated::new(maker_order.uuid); update_msg.with_new_price(BigRational::from_integer(2.into())); @@ -3265,6 +3278,7 @@ fn test_maker_order_balance_loops() { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; let morty_order = MakerOrder { @@ -3285,6 +3299,7 @@ fn test_maker_order_balance_loops() { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert!(!maker_orders_ctx.balance_loop_exists(rick_ticker)); @@ -3318,6 +3333,7 @@ fn test_maker_order_balance_loops() { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; maker_orders_ctx.add_order(ctx.weak(), rick_order_2.clone(), None); From 173677432ba4174ef66f3f77234b6e8cdf512ce3 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 11 Mar 2025 17:31:49 +0700 Subject: [PATCH 02/21] wip --- mm2src/mm2_main/src/lp_ordermatch.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 1d36ce16bd..54dab4a1d8 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2065,13 +2065,21 @@ impl MakerOrder { let ticker_match = (self.base == taker.base || self.base_orderbook_ticker.as_ref() == Some(&taker.base)) && (self.rel == taker.rel || self.rel_orderbook_ticker.as_ref() == Some(&taker.rel)); + // Calculate how much the taker is actually willing to pay per unit of base let taker_price = taker_rel_amount / taker_base_amount; + // Effective price per unit of base + // effective_price = total_rel / base_amount + // = ((base_amount * self.price) + premium) / base_amount + // = self.price + (premium / base_amount) + let effective_price = &self.price + &(&self.premium / taker_base_amount); + // Calculate how much rel the maker wants in total + let total_rel_amount = &(taker_base_amount * &self.price) + &self.premium; if ticker_match && taker_base_amount <= &self.available_amount() && taker_base_amount >= &self.min_base_vol - && taker_price >= self.price + && taker_price >= effective_price { - OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) + OrderMatchResult::Matched((taker_base_amount.clone(), total_rel_amount)) } else { OrderMatchResult::NotMatched } @@ -2083,12 +2091,13 @@ impl MakerOrder { // Calculate the resulting base amount using the Maker's price instead of the Taker's. let matched_base_amount = taker_base_amount / &self.price; - let matched_rel_amount = taker_base_amount.clone(); + let matched_rel_amount = taker_base_amount + &self.premium; // maker should receive extra rel amount + let effective_price = &self.price + &(&self.premium / &matched_base_amount); if ticker_match && matched_base_amount <= self.available_amount() && matched_base_amount >= self.min_base_vol - && taker_price >= self.price + && taker_price >= effective_price { OrderMatchResult::Matched((matched_base_amount, matched_rel_amount)) } else { From 810addf150cf6243292f45aa3f19b4ce229da782 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 13 Mar 2025 17:11:12 +0700 Subject: [PATCH 03/21] comeback to prod ver --- mm2src/mm2_main/src/lp_ordermatch.rs | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 54dab4a1d8..1d36ce16bd 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2065,21 +2065,13 @@ impl MakerOrder { let ticker_match = (self.base == taker.base || self.base_orderbook_ticker.as_ref() == Some(&taker.base)) && (self.rel == taker.rel || self.rel_orderbook_ticker.as_ref() == Some(&taker.rel)); - // Calculate how much the taker is actually willing to pay per unit of base let taker_price = taker_rel_amount / taker_base_amount; - // Effective price per unit of base - // effective_price = total_rel / base_amount - // = ((base_amount * self.price) + premium) / base_amount - // = self.price + (premium / base_amount) - let effective_price = &self.price + &(&self.premium / taker_base_amount); - // Calculate how much rel the maker wants in total - let total_rel_amount = &(taker_base_amount * &self.price) + &self.premium; if ticker_match && taker_base_amount <= &self.available_amount() && taker_base_amount >= &self.min_base_vol - && taker_price >= effective_price + && taker_price >= self.price { - OrderMatchResult::Matched((taker_base_amount.clone(), total_rel_amount)) + OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) } else { OrderMatchResult::NotMatched } @@ -2091,13 +2083,12 @@ impl MakerOrder { // Calculate the resulting base amount using the Maker's price instead of the Taker's. let matched_base_amount = taker_base_amount / &self.price; - let matched_rel_amount = taker_base_amount + &self.premium; // maker should receive extra rel amount - let effective_price = &self.price + &(&self.premium / &matched_base_amount); + let matched_rel_amount = taker_base_amount.clone(); if ticker_match && matched_base_amount <= self.available_amount() && matched_base_amount >= self.min_base_vol - && taker_price >= effective_price + && taker_price >= self.price { OrderMatchResult::Matched((matched_base_amount, matched_rel_amount)) } else { From 2d996d12f31b104212a7e0e5974d3ec54ee1dbd0 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 13 Mar 2025 17:40:37 +0700 Subject: [PATCH 04/21] Update Buy and Sell actions, provide more notes --- mm2src/mm2_main/src/lp_ordermatch.rs | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 1d36ce16bd..72c459f578 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2065,15 +2065,26 @@ impl MakerOrder { let ticker_match = (self.base == taker.base || self.base_orderbook_ticker.as_ref() == Some(&taker.base)) && (self.rel == taker.rel || self.rel_orderbook_ticker.as_ref() == Some(&taker.rel)); + // taker_base_amount: the amount taker desires to buy (input.volume from SellBuyRequest) + // taker_rel_amount: the amount taker is willing to pay (input.volume * input.price, where input is SellBuyRequest) + // taker_price: the effective price offered by the taker let taker_price = taker_rel_amount / taker_base_amount; - if ticker_match - && taker_base_amount <= &self.available_amount() - && taker_base_amount >= &self.min_base_vol - && taker_price >= self.price - { + + // Reject if any basic conditions are not satisfied + let base_amount_exceeds = taker_base_amount > &self.available_amount(); + let below_min_volume = taker_base_amount < &self.min_base_vol; + let price_too_low = taker_price < self.price; + + if !ticker_match || base_amount_exceeds || below_min_volume || price_too_low { + return OrderMatchResult::NotMatched; + } + + if self.swap_version.is_legacy() || taker.swap_version.is_legacy() { + // Legacy mode: use maker's price to calculate rel amount OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) } else { - OrderMatchResult::NotMatched + // TPU mode: treat buy as a limit order using taker's base and rel amounts + OrderMatchResult::Matched((taker_base_amount.clone(), taker_rel_amount.clone())) } }, TakerAction::Sell => { @@ -2081,8 +2092,16 @@ impl MakerOrder { && (self.rel == taker.base || self.rel_orderbook_ticker.as_ref() == Some(&taker.base)); let taker_price = taker_base_amount / taker_rel_amount; - // Calculate the resulting base amount using the Maker's price instead of the Taker's. - let matched_base_amount = taker_base_amount / &self.price; + // If the total rel amount from the taker (rel is coin which should be sent by taker during swap) + // is less than the maker's premium, the trade is not possible + if taker_base_amount < &self.premium { + return OrderMatchResult::NotMatched; + } + + // Calculate the resulting base amount using the maker's price instead of the taker's. + // The maker wants to "take" an additional portion of rel as a premium, + // so we reduce the base amount the maker gives by (premium / price). + let matched_base_amount = &(taker_base_amount - &self.premium) / &self.price; let matched_rel_amount = taker_base_amount.clone(); if ticker_match @@ -5213,7 +5232,7 @@ pub async fn update_maker_order_rpc(ctx: MmArc, req: Json) -> Result Date: Mon, 24 Mar 2025 12:57:30 +0700 Subject: [PATCH 05/21] improve match_with_request Sell; add premium to start state machines --- mm2src/mm2_main/src/lp_ordermatch.rs | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 72c459f578..fbfba3bade 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2092,18 +2092,25 @@ impl MakerOrder { && (self.rel == taker.base || self.rel_orderbook_ticker.as_ref() == Some(&taker.base)); let taker_price = taker_base_amount / taker_rel_amount; - // If the total rel amount from the taker (rel is coin which should be sent by taker during swap) - // is less than the maker's premium, the trade is not possible - if taker_base_amount < &self.premium { - return OrderMatchResult::NotMatched; - } - - // Calculate the resulting base amount using the maker's price instead of the taker's. - // The maker wants to "take" an additional portion of rel as a premium, - // so we reduce the base amount the maker gives by (premium / price). - let matched_base_amount = &(taker_base_amount - &self.premium) / &self.price; - let matched_rel_amount = taker_base_amount.clone(); - + // Determine the matched amounts depending on version + let (matched_base_amount, matched_rel_amount) = + if self.swap_version.is_legacy() || taker.swap_version.is_legacy() { + // Legacy: maker calculates base amount using their own price directly + (taker_base_amount / &self.price, taker_base_amount.clone()) + } else { + // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) + // is less than the maker's premium, the trade is not possible + if taker_base_amount < &self.premium { + return OrderMatchResult::NotMatched; + } + // Calculate the resulting base amount using the maker's price instead of the taker's. + // The maker wants to "take" an additional portion of rel as a premium, + // so we reduce the base amount the maker gives by (premium / price). + let matched_base_amount = &(taker_base_amount - &self.premium) / &self.price; + (matched_base_amount, taker_base_amount.clone()) + }; + + // Match if all common conditions are met if ticker_match && matched_base_amount <= self.available_amount() && matched_base_amount >= self.min_base_vol @@ -3030,6 +3037,7 @@ struct StateMachineParams<'a> { locktime: &'a u64, maker_amount: &'a MmNumber, taker_amount: &'a MmNumber, + taker_premium: &'a MmNumber, } #[cfg_attr(test, mockable)] @@ -3142,6 +3150,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO locktime: &lock_time, maker_amount: &maker_amount, taker_amount: &taker_amount, + taker_premium: &maker_order.premium, }; let taker_p2p_pubkey = match taker_p2p_pubkey { PublicKey::Secp256k1(pubkey) => pubkey.into(), @@ -3245,7 +3254,7 @@ async fn start_maker_swap_state_machine< taker_coin: taker_coin.clone(), dex_fee: dex_fee_amount_from_taker_coin(taker_coin, maker_coin.ticker(), params.taker_amount), taker_volume: params.taker_amount.clone(), - taker_premium: Default::default(), + taker_premium: params.taker_premium.clone(), conf_settings: *params.my_conf_settings, p2p_topic: swap_v2_topic(params.uuid), uuid: *params.uuid, @@ -3375,6 +3384,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat locktime: &locktime, maker_amount: &maker_amount, taker_amount: &taker_amount, + taker_premium: &Default::default(), // TODO }; let maker_p2p_pubkey = match maker_p2p_pubkey { PublicKey::Secp256k1(pubkey) => pubkey.into(), @@ -3485,7 +3495,7 @@ async fn start_taker_swap_state_machine< taker_coin: taker_coin.clone(), dex_fee: dex_fee_amount_from_taker_coin(taker_coin, taker_order.maker_coin_ticker(), params.taker_amount), taker_volume: params.taker_amount.clone(), - taker_premium: Default::default(), + taker_premium: params.taker_premium.clone(), secret_hash_algo: *params.secret_hash_algo, conf_settings: *params.my_conf_settings, p2p_topic: swap_v2_topic(params.uuid), From 419d91ab9c3b783a8330da5f6fc7a3090b62b57d Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 24 Mar 2025 16:03:07 +0700 Subject: [PATCH 06/21] premium in MakerReserved --- mm2src/mm2_main/src/lp_ordermatch.rs | 7 ++++++- .../mm2_main/src/lp_ordermatch/new_protocol.rs | 2 ++ mm2src/mm2_main/src/ordermatch_tests.rs | 13 +++++++++++++ mm2src/mm2_main/src/wasm_tests.rs | 4 ++-- .../tests/docker_tests/docker_tests_inner.rs | 3 +++ .../tests/docker_tests/eth_docker_tests.rs | 10 +++++++++- .../tests/docker_tests/swap_proto_v2_tests.rs | 3 +++ .../tests/docker_tests/swap_watcher_tests.rs | 17 ++++++++++++++++- .../mm2_main/tests/mm2_tests/lightning_tests.rs | 6 ++++++ .../mm2_main/tests/mm2_tests/mm2_tests_inner.rs | 2 +- mm2src/mm2_test_helpers/src/for_tests.rs | 4 +++- 11 files changed, 64 insertions(+), 7 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index fbfba3bade..b92d03cf8c 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2256,6 +2256,8 @@ pub struct MakerReserved { pub rel_protocol_info: Option>, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, + #[serde(default, skip_serializing_if = "MmNumber::is_zero")] + premium: MmNumber, } impl MakerReserved { @@ -2284,6 +2286,7 @@ impl MakerReserved { base_protocol_info: message.base_protocol_info, rel_protocol_info: message.rel_protocol_info, swap_version: message.swap_version, + premium: message.premium, } } } @@ -2301,6 +2304,7 @@ impl From for new_protocol::OrdermatchMessage { base_protocol_info: maker_reserved.base_protocol_info, rel_protocol_info: maker_reserved.rel_protocol_info, swap_version: maker_reserved.swap_version, + premium: maker_reserved.premium, }) } } @@ -3384,7 +3388,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat locktime: &locktime, maker_amount: &maker_amount, taker_amount: &taker_amount, - taker_premium: &Default::default(), // TODO + taker_premium: &taker_match.reserved.premium, }; let maker_p2p_pubkey = match maker_p2p_pubkey { PublicKey::Secp256k1(pubkey) => pubkey.into(), @@ -4027,6 +4031,7 @@ async fn process_taker_request(ctx: MmArc, from_pubkey: H256Json, taker_request: base_protocol_info: Some(base_coin.coin_protocol_info(None)), rel_protocol_info: Some(rel_coin.coin_protocol_info(Some(rel_amount.clone()))), swap_version: order.swap_version, + premium: order.premium.clone(), }; let topic = order.orderbook_topic(); log::debug!("Request matched sending reserved {:?}", reserved); diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index 745b3933f6..a0841e1c17 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -285,6 +285,8 @@ pub struct MakerReserved { pub rel_protocol_info: Option>, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, + #[serde(default, skip_serializing_if = "MmNumber::is_zero")] + pub premium: MmNumber, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/mm2src/mm2_main/src/ordermatch_tests.rs b/mm2src/mm2_main/src/ordermatch_tests.rs index 6572592af7..97237b0fed 100644 --- a/mm2src/mm2_main/src/ordermatch_tests.rs +++ b/mm2src/mm2_main/src/ordermatch_tests.rs @@ -436,6 +436,7 @@ fn test_maker_order_available_amount() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, connect: None, connected: None, @@ -470,6 +471,7 @@ fn test_maker_order_available_amount() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, connect: None, connected: None, @@ -527,6 +529,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -573,6 +576,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -619,6 +623,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -665,6 +670,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::NotMatched, order.match_reserved(&reserved)); @@ -711,6 +717,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -757,6 +764,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -803,6 +811,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -849,6 +858,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::NotMatched, order.match_reserved(&reserved)); @@ -895,6 +905,7 @@ fn test_taker_match_reserved() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::Matched, order.match_reserved(&reserved)); @@ -977,6 +988,7 @@ fn test_taker_order_cancellable() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }, connect: TakerConnect { sender_pubkey: H256Json::default(), @@ -1226,6 +1238,7 @@ fn test_taker_order_match_by() { base_protocol_info: None, rel_protocol_info: None, swap_version: SwapVersion::default(), + premium: Default::default(), }; assert_eq!(MatchReservedResult::NotMatched, order.match_reserved(&reserved)); diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 6aace5bfbc..1657b3045d 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -62,7 +62,7 @@ async fn test_mm2_stops_impl(pairs: &[(&'static str, &'static str)], maker_price let rc = enable_electrum_json(&mm_alice, MORTY, true, marty_electrums()).await; log!("enable MORTY (bob): {:?}", rc); - start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; + start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; mm_alice .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) @@ -112,7 +112,7 @@ async fn trade_base_rel_electrum( let rc = enable_utxo_v2_electrum(&mm_alice, "MORTY", marty_electrums(), alice_path_to_address, 60, None).await; log!("enable MORTY (alice): {:?}", rc); - let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; + let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; wait_for_swaps_finish_and_check_status(&mut mm_bob, &mut mm_alice, &uuids, volume, maker_price).await; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index b4f074857f..f5c968a94b 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -3553,6 +3553,7 @@ fn test_locked_amount() { 1., 1., 777., + 0., )); let locked_bob = block_on(get_locked_amount(&mm_bob, "MYCOIN")); @@ -3783,6 +3784,7 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { 1., 1., 0.0001, + 0., )); // give few seconds for swap statuses to be saved @@ -3876,6 +3878,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { 1., 1., 0.0001, + 0., )); // give few seconds for swap statuses to be saved diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index fe85d2fb1d..e08b94dcbb 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -2812,7 +2812,15 @@ fn test_v2_eth_eth_kickstart() { enable_coins(&mm_bob, &[ETH, ETH1]); enable_coins(&mm_alice, &[ETH, ETH1]); - let uuids = block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[(ETH, ETH1)], 1.0, 1.0, 77.)); + let uuids = block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[(ETH, ETH1)], + 1.0, + 1.0, + 77., + 0., + )); log!("{:?}", uuids); let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 4d0a3cd167..eb90a179b2 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -649,6 +649,7 @@ fn test_v2_swap_utxo_utxo() { 1.0, 1.0, 777., + 0., )); log!("{:?}", uuids); @@ -750,6 +751,7 @@ fn test_v2_swap_utxo_utxo_kickstart() { 1.0, 1.0, 777., + 0., )); log!("{:?}", uuids); @@ -865,6 +867,7 @@ fn test_v2_swap_utxo_utxo_file_lock() { 1.0, 1.0, 100., + 0., )); log!("{:?}", uuids); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index 723ce0bfe5..c02841a0b6 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -216,6 +216,7 @@ fn start_swaps_and_get_balances( maker_price, taker_price, volume, + 0., )); if matches!(swap_flow, SwapFlow::WatcherRefundsTakerPayment) { @@ -416,6 +417,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker 25., 25., 2., + 0., )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -477,6 +479,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_ 25., 25., 2., + 0., )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -535,6 +538,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 25., 2., + 0., )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -597,6 +601,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 25., 2., + 0., )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -648,6 +653,7 @@ fn test_taker_completes_swap_after_restart() { 25., 25., 2., + 0., )); block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); @@ -693,6 +699,7 @@ fn test_taker_completes_swap_after_taker_payment_spent_while_offline() { 25., 25., 2., + 0., )); // stop taker after taker payment sent @@ -1183,7 +1190,15 @@ fn test_two_watchers_spend_maker_payment_eth_erc20() { let watcher1_eth_balance_before = block_on(my_balance(&mm_watcher1, "ETH")).balance; let watcher2_eth_balance_before = block_on(my_balance(&mm_watcher2, "ETH")).balance; - block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); + block_on(start_swaps( + &mut mm_bob, + &mut mm_alice, + &[("ETH", "JST")], + 1., + 1., + 0.01, + 0., + )); block_on(mm_alice.wait_for_log(180., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); block_on(mm_alice.stop()).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index 4fde665bd6..4403844884 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -722,6 +722,7 @@ fn test_lightning_swaps() { price, price, volume, + 0., )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -747,6 +748,7 @@ fn test_lightning_swaps() { price, price, volume, + 0., )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -807,6 +809,7 @@ fn test_lightning_taker_swap_mpp() { price, price, volume, + 0., )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -866,6 +869,7 @@ fn test_lightning_maker_swap_mpp() { price, price, volume, + 0., )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_2, @@ -919,6 +923,7 @@ fn test_lightning_taker_gets_swap_preimage_onchain() { price, price, volume, + 0., )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); @@ -982,6 +987,7 @@ fn test_lightning_taker_claims_mpp() { price, price, volume, + 0., )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 5e702371b1..8450aa7dc6 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -770,7 +770,7 @@ async fn trade_base_rel_electrum( let rc = enable_utxo_v2_electrum(&mm_alice, "MORTY", marty_electrums(), alice_path_to_address, 600, None).await; log!("enable MORTY (alice): {:?}", rc); - let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume).await; + let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; #[cfg(not(target_arch = "wasm32"))] for uuid in uuids.iter() { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index fae56dc835..f6987de498 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3599,6 +3599,7 @@ pub async fn start_swaps( maker_price: f64, taker_price: f64, volume: f64, + premium: f64, ) -> Vec { let mut uuids = vec![]; @@ -3612,7 +3613,8 @@ pub async fn start_swaps( "base": base, "rel": rel, "price": maker_price, - "volume": volume + "volume": volume, + "premium": premium })) .await .unwrap(); From 4f1002c1db3a36f3f3c0689a58e63f3d616dbdbc Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 24 Mar 2025 18:04:12 +0700 Subject: [PATCH 07/21] use old comment for resulting base amount in Sell legacy --- mm2src/mm2_main/src/lp_ordermatch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index b92d03cf8c..4fe55bbdf3 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2095,7 +2095,7 @@ impl MakerOrder { // Determine the matched amounts depending on version let (matched_base_amount, matched_rel_amount) = if self.swap_version.is_legacy() || taker.swap_version.is_legacy() { - // Legacy: maker calculates base amount using their own price directly + // Legacy: calculate the resulting base amount using the Maker's price instead of the Taker's. (taker_base_amount / &self.price, taker_base_amount.clone()) } else { // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) From cd53b6d218108903d7b77730e3fffaf5ace14c40 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 25 Mar 2025 22:58:47 +0700 Subject: [PATCH 08/21] use Option for premium, as new_protocol::MakerReserved needs BigRational, which doesn't impl std default --- mm2src/mm2_main/src/lp_ordermatch.rs | 27 ++++++++++--------- .../src/lp_ordermatch/my_orders_storage.rs | 2 +- .../src/lp_ordermatch/new_protocol.rs | 5 ++-- .../src/lp_ordermatch/simple_market_maker.rs | 2 +- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 4fe55bbdf3..123085a3e2 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1722,8 +1722,8 @@ pub struct MakerOrder { p2p_privkey: Option, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, - #[serde(default, skip_serializing_if = "MmNumber::is_zero")] - premium: MmNumber, + #[serde(default, skip_serializing_if = "Option::is_none")] + premium: Option, } pub struct MakerOrderBuilder<'a> { @@ -1737,7 +1737,7 @@ pub struct MakerOrderBuilder<'a> { conf_settings: Option, save_in_history: bool, swap_version: u8, - premium: MmNumber, + premium: Option, } pub enum MakerOrderBuildError { @@ -1933,7 +1933,7 @@ impl<'a> MakerOrderBuilder<'a> { /// In the future alls users will be using TPU V2 by default without "use_trading_proto_v2" configuration. pub fn set_legacy_swap_v(&mut self) { self.swap_version = legacy_swap_version() } - pub fn with_premium(mut self, premium: MmNumber) -> Self { + pub fn with_premium(mut self, premium: Option) -> Self { self.premium = premium; self } @@ -2091,6 +2091,7 @@ impl MakerOrder { let ticker_match = (self.base == taker.rel || self.base_orderbook_ticker.as_ref() == Some(&taker.rel)) && (self.rel == taker.base || self.rel_orderbook_ticker.as_ref() == Some(&taker.base)); let taker_price = taker_base_amount / taker_rel_amount; + let premium = self.premium.clone().unwrap_or_default(); // Determine the matched amounts depending on version let (matched_base_amount, matched_rel_amount) = @@ -2100,13 +2101,13 @@ impl MakerOrder { } else { // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) // is less than the maker's premium, the trade is not possible - if taker_base_amount < &self.premium { + if taker_base_amount < &premium { return OrderMatchResult::NotMatched; } // Calculate the resulting base amount using the maker's price instead of the taker's. // The maker wants to "take" an additional portion of rel as a premium, // so we reduce the base amount the maker gives by (premium / price). - let matched_base_amount = &(taker_base_amount - &self.premium) / &self.price; + let matched_base_amount = &(taker_base_amount - &premium) / &self.price; (matched_base_amount, taker_base_amount.clone()) }; @@ -2256,8 +2257,8 @@ pub struct MakerReserved { pub rel_protocol_info: Option>, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, - #[serde(default, skip_serializing_if = "MmNumber::is_zero")] - premium: MmNumber, + #[serde(default, skip_serializing_if = "Option::is_none")] + premium: Option, } impl MakerReserved { @@ -2286,7 +2287,7 @@ impl MakerReserved { base_protocol_info: message.base_protocol_info, rel_protocol_info: message.rel_protocol_info, swap_version: message.swap_version, - premium: message.premium, + premium: message.premium.map(MmNumber::from), } } } @@ -2304,7 +2305,7 @@ impl From for new_protocol::OrdermatchMessage { base_protocol_info: maker_reserved.base_protocol_info, rel_protocol_info: maker_reserved.rel_protocol_info, swap_version: maker_reserved.swap_version, - premium: maker_reserved.premium, + premium: maker_reserved.premium.map(|p| p.to_ratio()), }) } } @@ -3154,7 +3155,7 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO locktime: &lock_time, maker_amount: &maker_amount, taker_amount: &taker_amount, - taker_premium: &maker_order.premium, + taker_premium: &maker_order.premium.clone().unwrap_or_default(), }; let taker_p2p_pubkey = match taker_p2p_pubkey { PublicKey::Secp256k1(pubkey) => pubkey.into(), @@ -3388,7 +3389,7 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat locktime: &locktime, maker_amount: &maker_amount, taker_amount: &taker_amount, - taker_premium: &taker_match.reserved.premium, + taker_premium: &taker_match.reserved.premium.unwrap_or_default(), }; let maker_p2p_pubkey = match maker_p2p_pubkey { PublicKey::Secp256k1(pubkey) => pubkey.into(), @@ -4761,7 +4762,7 @@ pub struct SetPriceReq { #[serde(default = "get_true")] save_in_history: bool, #[serde(default)] - premium: MmNumber, + premium: Option, } #[derive(Deserialize)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs index 3f824d9dbf..a0f6a349ed 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/my_orders_storage.rs @@ -727,7 +727,7 @@ mod tests { rel_orderbook_ticker: None, p2p_privkey: None, swap_version: SwapVersion::default(), - premium: MmNumber::default(), + premium: Default::default(), } } diff --git a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs index a0841e1c17..e8f3a6755b 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/new_protocol.rs @@ -285,8 +285,9 @@ pub struct MakerReserved { pub rel_protocol_info: Option>, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, - #[serde(default, skip_serializing_if = "MmNumber::is_zero")] - pub premium: MmNumber, + /// Note: `std::default::Default` is not implemented for `num_rational::Ratio`, that's why it's preferable to use Optional + #[serde(default, skip_serializing_if = "Option::is_none")] + pub premium: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs index 5b4c2933a1..1e5e0339ea 100644 --- a/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs +++ b/mm2src/mm2_main/src/lp_ordermatch/simple_market_maker.rs @@ -601,7 +601,7 @@ async fn create_single_order( rel_confs: cfg.rel_confs, rel_nota: cfg.rel_nota, save_in_history: true, - premium: MmNumber::default(), + premium: None, }; let resp = create_maker_order(&ctx, req) From 13441669e1155be9e7b8d287296abe1fda481883 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 25 Mar 2025 23:45:27 +0700 Subject: [PATCH 09/21] Option premium field in start_swaps test function --- mm2src/mm2_main/src/wasm_tests.rs | 22 +++++++++++++++++-- .../tests/docker_tests/docker_tests_inner.rs | 6 ++--- .../tests/docker_tests/eth_docker_tests.rs | 2 +- .../tests/docker_tests/swap_proto_v2_tests.rs | 6 ++--- .../tests/docker_tests/swap_watcher_tests.rs | 16 +++++++------- .../tests/mm2_tests/lightning_tests.rs | 12 +++++----- .../tests/mm2_tests/mm2_tests_inner.rs | 11 +++++++++- mm2src/mm2_test_helpers/src/for_tests.rs | 2 +- 8 files changed, 52 insertions(+), 25 deletions(-) diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 1657b3045d..004bbb5e4c 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -62,7 +62,16 @@ async fn test_mm2_stops_impl(pairs: &[(&'static str, &'static str)], maker_price let rc = enable_electrum_json(&mm_alice, MORTY, true, marty_electrums()).await; log!("enable MORTY (bob): {:?}", rc); - start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; + start_swaps( + &mut mm_bob, + &mut mm_alice, + pairs, + maker_price, + taker_price, + volume, + None, + ) + .await; mm_alice .stop_and_wait_for_ctx_is_dropped(STOP_TIMEOUT_MS) @@ -112,7 +121,16 @@ async fn trade_base_rel_electrum( let rc = enable_utxo_v2_electrum(&mm_alice, "MORTY", marty_electrums(), alice_path_to_address, 60, None).await; log!("enable MORTY (alice): {:?}", rc); - let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; + let uuids = start_swaps( + &mut mm_bob, + &mut mm_alice, + pairs, + maker_price, + taker_price, + volume, + None, + ) + .await; wait_for_swaps_finish_and_check_status(&mut mm_bob, &mut mm_alice, &uuids, volume, maker_price).await; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index f5c968a94b..51db348b43 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -3553,7 +3553,7 @@ fn test_locked_amount() { 1., 1., 777., - 0., + None, )); let locked_bob = block_on(get_locked_amount(&mm_bob, "MYCOIN")); @@ -3784,7 +3784,7 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { 1., 1., 0.0001, - 0., + None, )); // give few seconds for swap statuses to be saved @@ -3878,7 +3878,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { 1., 1., 0.0001, - 0., + None, )); // give few seconds for swap statuses to be saved diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index e08b94dcbb..53270d6d25 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -2819,7 +2819,7 @@ fn test_v2_eth_eth_kickstart() { 1.0, 1.0, 77., - 0., + Some(0.), )); log!("{:?}", uuids); let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index eb90a179b2..bead8dc39c 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -649,7 +649,7 @@ fn test_v2_swap_utxo_utxo() { 1.0, 1.0, 777., - 0., + Some(0.), )); log!("{:?}", uuids); @@ -751,7 +751,7 @@ fn test_v2_swap_utxo_utxo_kickstart() { 1.0, 1.0, 777., - 0., + Some(0.), )); log!("{:?}", uuids); @@ -867,7 +867,7 @@ fn test_v2_swap_utxo_utxo_file_lock() { 1.0, 1.0, 100., - 0., + Some(0.), )); log!("{:?}", uuids); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index c02841a0b6..ec299ddfa3 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -216,7 +216,7 @@ fn start_swaps_and_get_balances( maker_price, taker_price, volume, - 0., + None, )); if matches!(swap_flow, SwapFlow::WatcherRefundsTakerPayment) { @@ -417,7 +417,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker 25., 25., 2., - 0., + None, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -479,7 +479,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_ 25., 25., 2., - 0., + None, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -538,7 +538,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 25., 2., - 0., + None, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -601,7 +601,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 25., 2., - 0., + None, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -653,7 +653,7 @@ fn test_taker_completes_swap_after_restart() { 25., 25., 2., - 0., + None, )); block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); @@ -699,7 +699,7 @@ fn test_taker_completes_swap_after_taker_payment_spent_while_offline() { 25., 25., 2., - 0., + None, )); // stop taker after taker payment sent @@ -1197,7 +1197,7 @@ fn test_two_watchers_spend_maker_payment_eth_erc20() { 1., 1., 0.01, - 0., + None, )); block_on(mm_alice.wait_for_log(180., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index 4403844884..6e6335c6d0 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -722,7 +722,7 @@ fn test_lightning_swaps() { price, price, volume, - 0., + None, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -748,7 +748,7 @@ fn test_lightning_swaps() { price, price, volume, - 0., + None, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -809,7 +809,7 @@ fn test_lightning_taker_swap_mpp() { price, price, volume, - 0., + None, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -869,7 +869,7 @@ fn test_lightning_maker_swap_mpp() { price, price, volume, - 0., + None, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_2, @@ -923,7 +923,7 @@ fn test_lightning_taker_gets_swap_preimage_onchain() { price, price, volume, - 0., + None, )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); @@ -987,7 +987,7 @@ fn test_lightning_taker_claims_mpp() { price, price, volume, - 0., + None, )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index 8450aa7dc6..f9479c6a22 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -770,7 +770,16 @@ async fn trade_base_rel_electrum( let rc = enable_utxo_v2_electrum(&mm_alice, "MORTY", marty_electrums(), alice_path_to_address, 600, None).await; log!("enable MORTY (alice): {:?}", rc); - let uuids = start_swaps(&mut mm_bob, &mut mm_alice, pairs, maker_price, taker_price, volume, 0.).await; + let uuids = start_swaps( + &mut mm_bob, + &mut mm_alice, + pairs, + maker_price, + taker_price, + volume, + None, + ) + .await; #[cfg(not(target_arch = "wasm32"))] for uuid in uuids.iter() { diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index f6987de498..ab34282665 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3599,7 +3599,7 @@ pub async fn start_swaps( maker_price: f64, taker_price: f64, volume: f64, - premium: f64, + premium: Option, ) -> Vec { let mut uuids = vec![]; From 99c7d413915479ac833cd154edc2391aa938a8bd Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 26 Mar 2025 18:04:10 +0700 Subject: [PATCH 10/21] provide taker_method: TakerMethod field to start_swaps fn --- mm2src/mm2_main/src/wasm_tests.rs | 6 ++- .../tests/docker_tests/docker_tests_inner.rs | 5 ++- .../tests/docker_tests/eth_docker_tests.rs | 5 ++- .../tests/docker_tests/swap_proto_v2_tests.rs | 5 ++- .../tests/docker_tests/swap_watcher_tests.rs | 10 ++++- .../tests/mm2_tests/lightning_tests.rs | 9 ++++- .../tests/mm2_tests/mm2_tests_inner.rs | 7 ++-- mm2src/mm2_test_helpers/src/for_tests.rs | 38 ++++++++++++++++--- 8 files changed, 69 insertions(+), 16 deletions(-) diff --git a/mm2src/mm2_main/src/wasm_tests.rs b/mm2src/mm2_main/src/wasm_tests.rs index 004bbb5e4c..b06daad527 100644 --- a/mm2src/mm2_main/src/wasm_tests.rs +++ b/mm2src/mm2_main/src/wasm_tests.rs @@ -9,8 +9,8 @@ use mm2_test_helpers::electrums::{doc_electrums, marty_electrums}; use mm2_test_helpers::for_tests::{check_recent_swaps, enable_electrum_json, enable_utxo_v2_electrum, enable_z_coin_light, get_wallet_names, morty_conf, pirate_conf, rick_conf, start_swaps, test_qrc20_history_impl, wait_for_swaps_finish_and_check_status, - MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, ARRR, MORTY, - PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, RICK}; + MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, TakerMethod, + ARRR, MORTY, PIRATE_ELECTRUMS, PIRATE_LIGHTWALLETD_URLS, RICK}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalance, HDAccountAddressId}; use serde_json::json; @@ -70,6 +70,7 @@ async fn test_mm2_stops_impl(pairs: &[(&'static str, &'static str)], maker_price taker_price, volume, None, + TakerMethod::Buy, ) .await; @@ -129,6 +130,7 @@ async fn trade_base_rel_electrum( taker_price, volume, None, + TakerMethod::Buy, ) .await; diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 51db348b43..ea91f8c941 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -22,7 +22,7 @@ use mm2_test_helpers::for_tests::{check_my_swap_status_amounts, disable_coin, di enable_eth_with_tokens_v2, erc20_dev_conf, eth_dev_conf, get_locked_amount, kmd_conf, max_maker_vol, mm_dump, mycoin1_conf, mycoin_conf, set_price, start_swaps, wait_for_swap_contract_negotiation, wait_for_swap_negotiation_failure, - MarketMakerIt, Mm2TestConf, DEFAULT_RPC_PASSWORD}; + MarketMakerIt, Mm2TestConf, TakerMethod, DEFAULT_RPC_PASSWORD}; use mm2_test_helpers::{get_passphrase, structs::*}; use serde_json::Value as Json; use std::collections::{HashMap, HashSet}; @@ -3554,6 +3554,7 @@ fn test_locked_amount() { 1., 777., None, + TakerMethod::Buy, )); let locked_bob = block_on(get_locked_amount(&mm_bob, "MYCOIN")); @@ -3785,6 +3786,7 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { 1., 0.0001, None, + TakerMethod::Buy, )); // give few seconds for swap statuses to be saved @@ -3879,6 +3881,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { 1., 0.0001, None, + TakerMethod::Buy, )); // give few seconds for swap statuses to be saved diff --git a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs index 53270d6d25..d6c311cc0a 100644 --- a/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs @@ -35,7 +35,7 @@ use mm2_test_helpers::for_tests::{account_balance, active_swaps, coins_needed_fo enable_erc20_token_v2, enable_eth_coin_v2, enable_eth_with_tokens_v2, erc20_dev_conf, eth1_dev_conf, eth_dev_conf, get_locked_amount, get_new_address, get_token_info, mm_dump, my_swap_status, nft_dev_conf, start_swaps, MarketMakerIt, - Mm2TestConf, SwapV2TestContracts, TestNode}; + Mm2TestConf, SwapV2TestContracts, TakerMethod, TestNode}; #[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))] use mm2_test_helpers::for_tests::{eth_sepolia_conf, sepolia_erc20_dev_conf}; use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId, @@ -2819,7 +2819,8 @@ fn test_v2_eth_eth_kickstart() { 1.0, 1.0, 77., - Some(0.), + None, + TakerMethod::Buy, )); log!("{:?}", uuids); let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index bead8dc39c..0162e52aab 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -11,7 +11,7 @@ use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{active_swaps, check_recent_swaps, coins_needed_for_kickstart, disable_coin, disable_coin_err, enable_native, get_locked_amount, mm_dump, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, wait_for_swap_finished, - wait_for_swap_status, MarketMakerIt, Mm2TestConf}; + wait_for_swap_status, MarketMakerIt, Mm2TestConf, TakerMethod}; use mm2_test_helpers::structs::MmNumberMultiRepr; use script::{Builder, Opcode}; use serialization::serialize; @@ -650,6 +650,7 @@ fn test_v2_swap_utxo_utxo() { 1.0, 777., Some(0.), + TakerMethod::Buy, )); log!("{:?}", uuids); @@ -752,6 +753,7 @@ fn test_v2_swap_utxo_utxo_kickstart() { 1.0, 777., Some(0.), + TakerMethod::Buy, )); log!("{:?}", uuids); @@ -868,6 +870,7 @@ fn test_v2_swap_utxo_utxo_file_lock() { 1.0, 100., Some(0.), + TakerMethod::Buy, )); log!("{:?}", uuids); diff --git a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs index ec299ddfa3..8aa45661b8 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -22,7 +22,7 @@ use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, erc20_dev_conf, eth_dev_conf, eth_jst_testnet_conf, mm_dump, my_balance, my_swap_status, mycoin1_conf, mycoin_conf, start_swaps, - wait_for_swaps_finish_and_check_status, MarketMakerIt, Mm2TestConf, + wait_for_swaps_finish_and_check_status, MarketMakerIt, Mm2TestConf, TakerMethod, DEFAULT_RPC_PASSWORD}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::WatcherConf; @@ -217,6 +217,7 @@ fn start_swaps_and_get_balances( taker_price, volume, None, + TakerMethod::Buy, )); if matches!(swap_flow, SwapFlow::WatcherRefundsTakerPayment) { @@ -418,6 +419,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_wait_for_taker 25., 2., None, + TakerMethod::Buy, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -480,6 +482,7 @@ fn test_taker_saves_the_swap_as_successful_after_restart_panic_at_maker_payment_ 25., 2., None, + TakerMethod::Buy, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -539,6 +542,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 2., None, + TakerMethod::Buy, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -602,6 +606,7 @@ fn test_taker_saves_the_swap_as_finished_after_restart_taker_payment_refunded_pa 25., 2., None, + TakerMethod::Buy, )); let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -654,6 +659,7 @@ fn test_taker_completes_swap_after_restart() { 25., 2., None, + TakerMethod::Buy, )); block_on(mm_alice.wait_for_log(120., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); @@ -700,6 +706,7 @@ fn test_taker_completes_swap_after_taker_payment_spent_while_offline() { 25., 2., None, + TakerMethod::Buy, )); // stop taker after taker payment sent @@ -1198,6 +1205,7 @@ fn test_two_watchers_spend_maker_payment_eth_erc20() { 1., 0.01, None, + TakerMethod::Buy, )); block_on(mm_alice.wait_for_log(180., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs index 6e6335c6d0..38d6e44197 100644 --- a/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs +++ b/mm2src/mm2_main/tests/mm2_tests/lightning_tests.rs @@ -6,7 +6,8 @@ use gstuff::now_ms; use http::StatusCode; use mm2_number::BigDecimal; use mm2_test_helpers::for_tests::{disable_coin, init_lightning, init_lightning_status, my_balance, sign_message, - start_swaps, verify_message, wait_for_swaps_finish_and_check_status, MarketMakerIt}; + start_swaps, verify_message, wait_for_swaps_finish_and_check_status, MarketMakerIt, + TakerMethod}; use mm2_test_helpers::structs::{InitLightningStatus, InitTaskResult, LightningActivationResult, RpcV2Response, SignatureResponse, VerificationResponse}; use serde_json::{self as json, json, Value as Json}; @@ -723,6 +724,7 @@ fn test_lightning_swaps() { price, volume, None, + TakerMethod::Buy, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -749,6 +751,7 @@ fn test_lightning_swaps() { price, volume, None, + TakerMethod::Buy, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -810,6 +813,7 @@ fn test_lightning_taker_swap_mpp() { price, volume, None, + TakerMethod::Buy, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_1, @@ -870,6 +874,7 @@ fn test_lightning_maker_swap_mpp() { price, volume, None, + TakerMethod::Buy, )); block_on(wait_for_swaps_finish_and_check_status( &mut mm_node_2, @@ -924,6 +929,7 @@ fn test_lightning_taker_gets_swap_preimage_onchain() { price, volume, None, + TakerMethod::Buy, )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); @@ -988,6 +994,7 @@ fn test_lightning_taker_claims_mpp() { price, volume, None, + TakerMethod::Buy, )); block_on(mm_node_1.wait_for_log(60., |log| log.contains(PAYMENT_CLAIMABLE_LOG))).unwrap(); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index f9479c6a22..71e041b41a 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -20,9 +20,9 @@ use mm2_test_helpers::for_tests::{account_balance, btc_segwit_conf, btc_with_spv test_qrc20_history_impl, tqrc20_conf, verify_message, wait_for_swaps_finish_and_check_status, wait_till_history_has_records, MarketMakerIt, Mm2InitPrivKeyPolicy, Mm2TestConf, Mm2TestConfForSwap, RaiiDump, - DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODES, ETH_MAINNET_SWAP_CONTRACT, ETH_SEPOLIA_NODES, - ETH_SEPOLIA_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, QRC20_ELECTRUMS, RICK, - RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; + TakerMethod, DOC_ELECTRUM_ADDRS, ETH_MAINNET_NODES, ETH_MAINNET_SWAP_CONTRACT, + ETH_SEPOLIA_NODES, ETH_SEPOLIA_SWAP_CONTRACT, MARTY_ELECTRUM_ADDRS, MORTY, + QRC20_ELECTRUMS, RICK, RICK_ELECTRUM_ADDRS, TBTC_ELECTRUMS, T_BCH_ELECTRUMS}; use mm2_test_helpers::get_passphrase; use mm2_test_helpers::structs::*; use serde_json::{self as json, json, Value as Json}; @@ -778,6 +778,7 @@ async fn trade_base_rel_electrum( taker_price, volume, None, + TakerMethod::Buy, ) .await; diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index ab34282665..34bfb07c8d 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3592,6 +3592,29 @@ pub async fn set_price( json::from_str(&request.1).unwrap() } +pub enum TakerMethod { + Buy, + Sell, +} + +impl TakerMethod { + /// Convert enum variant to the corresponding RPC method name. + fn as_str(&self) -> &'static str { + match self { + TakerMethod::Buy => "buy", + TakerMethod::Sell => "sell", + } + } + + /// Depending on the method, we either leave `base`/`rel` as is (buy) or swap them (sell). + fn taker_pair<'a>(&self, base: &'a str, rel: &'a str) -> (&'a str, &'a str) { + match self { + TakerMethod::Buy => (base, rel), + TakerMethod::Sell => (rel, base), + } + } +} + pub async fn start_swaps( maker: &mut MarketMakerIt, taker: &mut MarketMakerIt, @@ -3600,6 +3623,7 @@ pub async fn start_swaps( taker_price: f64, volume: f64, premium: Option, + taker_method: TakerMethod, ) -> Vec { let mut uuids = vec![]; @@ -3641,19 +3665,23 @@ pub async fn start_swaps( .unwrap(); assert!(rc.0.is_success(), "!orderbook: {}", rc.1); Timer::sleep(1.).await; - common::log::info!("Issue taker {}/{} buy request", base, rel); + + // Get the correct base/rel for the taker side, depending on buy or sell + let (taker_base, taker_rel) = taker_method.taker_pair(base, rel); + + common::log::info!("Issue taker {}/{} {} request", base, rel, taker_method.as_str()); let rc = taker .rpc(&json!({ "userpass": taker.userpass, - "method": "buy", - "base": base, - "rel": rel, + "method": taker_method.as_str(), + "base": taker_base, + "rel": taker_rel, "volume": volume, "price": taker_price })) .await .unwrap(); - assert!(rc.0.is_success(), "!buy: {}", rc.1); + assert!(rc.0.is_success(), "!{}: {}",taker_method.as_str(), rc.1); let buy_json: Json = serde_json::from_str(&rc.1).unwrap(); uuids.push(buy_json["result"]["uuid"].as_str().unwrap().to_owned()); } From c3217aeab6b51e8b722f3f195fe61a9c1acf3e70 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 27 Mar 2025 15:49:01 +0700 Subject: [PATCH 11/21] buy and sell work --- mm2src/mm2_main/src/lp_ordermatch.rs | 39 +++++-- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 10 +- .../tests/docker_tests/swap_proto_v2_tests.rs | 109 +++++++++++++++++- 3 files changed, 144 insertions(+), 14 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 123085a3e2..60b8f44b46 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1639,10 +1639,28 @@ impl TakerOrder { || self.base_orderbook_ticker.as_ref() == Some(&reserved.rel)) && (self.request.rel == reserved.base || self.rel_orderbook_ticker.as_ref() == Some(&reserved.base)); - if match_ticker && my_base_amount == other_rel_amount && my_rel_amount <= other_base_amount { - MatchReservedResult::Matched + + // Reject if any common conditions are unmet + if !match_ticker || my_base_amount != other_rel_amount { + return MatchReservedResult::NotMatched; + } + + if self.request.swap_version.is_legacy() || reserved.swap_version.is_legacy() { + if my_rel_amount <= other_base_amount { + MatchReservedResult::Matched + } else { + MatchReservedResult::NotMatched + } } else { - MatchReservedResult::NotMatched + let premium = &reserved.premium.clone().unwrap_or_default(); + let other_price = &(my_base_amount - premium) / other_base_amount; + // In match_with_request function, we allowed maker to send fewer coins for taker sell action + let result_other_base_amount = other_base_amount + &(premium / &other_price); + if my_rel_amount <= &result_other_base_amount { + MatchReservedResult::Matched + } else { + MatchReservedResult::NotMatched + } } }, } @@ -2083,8 +2101,15 @@ impl MakerOrder { // Legacy mode: use maker's price to calculate rel amount OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) } else { - // TPU mode: treat buy as a limit order using taker's base and rel amounts - OrderMatchResult::Matched((taker_base_amount.clone(), taker_rel_amount.clone())) + // taker_rel_amount must cover the premium requested by maker + let required_rel_amount = + taker_base_amount * &self.price + self.premium.clone().unwrap_or_default(); + if taker_rel_amount >= &required_rel_amount { + // TPU mode: treat buy as a limit order using taker's base and rel amounts + OrderMatchResult::Matched((taker_base_amount.clone(), taker_rel_amount.clone())) + } else { + OrderMatchResult::NotMatched + } } }, TakerAction::Sell => { @@ -2100,8 +2125,8 @@ impl MakerOrder { (taker_base_amount / &self.price, taker_base_amount.clone()) } else { // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) - // is less than the maker's premium, the trade is not possible - if taker_base_amount < &premium { + // is less than or equal to the maker's premium, the trade is not possible + if taker_base_amount <= &premium { return OrderMatchResult::NotMatched; } // Calculate the resulting base amount using the maker's price instead of the taker's. diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index ce552c580c..ce0e3a9f0b 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -862,7 +862,8 @@ impl = uuids.iter().map(|u| u.parse().unwrap()).collect(); + + let active_swaps_bob = block_on(active_swaps(&mm_bob)); + assert_eq!(active_swaps_bob.uuids, parsed_uuids); + + let active_swaps_alice = block_on(active_swaps(&mm_alice)); + assert_eq!(active_swaps_alice.uuids, parsed_uuids); + + // disabling coins used in active swaps must not work + let err = block_on(disable_coin_err(&mm_bob, MYCOIN, false)); + assert_eq!(err.active_swaps, parsed_uuids); + + let err = block_on(disable_coin_err(&mm_bob, MYCOIN1, false)); + assert_eq!(err.active_swaps, parsed_uuids); + + let err = block_on(disable_coin_err(&mm_alice, MYCOIN, false)); + assert_eq!(err.active_swaps, parsed_uuids); + + let err = block_on(disable_coin_err(&mm_alice, MYCOIN1, false)); + assert_eq!(err.active_swaps, parsed_uuids); + + // coins must be virtually locked until swap transactions are sent + let locked_bob = block_on(get_locked_amount(&mm_bob, MYCOIN)); + assert_eq!(locked_bob.coin, MYCOIN); + let expected: MmNumberMultiRepr = MmNumber::from("777.").into(); + assert_eq!(locked_bob.locked_amount, expected); + let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); assert_eq!(locked_alice.coin, MYCOIN1); let expected: MmNumberMultiRepr = MmNumber::from("778.00001").into(); From 18dc71300237e8787e4fd59df1e2b7bba82f4fdb Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 27 Mar 2025 16:49:57 +0700 Subject: [PATCH 12/21] fix utxo buy test; add burnkey_as_alice test when taker sells --- .../tests/docker_tests/swap_proto_v2_tests.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 2c4e54cfd2..212ee805dd 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -718,7 +718,11 @@ fn test_v2_swap_utxo_utxo_impl() { let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); assert_eq!(locked_alice.coin, MYCOIN1); - let expected: MmNumberMultiRepr = MmNumber::from("778.00779").into(); + let expected: MmNumberMultiRepr = if SET_BURN_PUBKEY_TO_ALICE.get() { + MmNumber::from("777.00778").into() // no dex fee if dex pubkey is alice + } else { + MmNumber::from("778.00779").into() + }; assert_eq!(locked_alice.locked_amount, expected); // amount must unlocked after funding tx is sent @@ -757,7 +761,17 @@ fn test_v2_swap_utxo_utxo_impl() { } #[test] -fn test_v2_swap_utxo_utxo_sell() { +fn test_v2_swap_utxo_utxo_sell() { test_v2_swap_utxo_utxo_sell_impl(); } + +// test a swap when taker sells and taker is burn pubkey (no dex fee should be paid) +#[test] +fn test_v2_swap_utxo_utxo_sell_burnkey_as_alice() { + SET_BURN_PUBKEY_TO_ALICE.set(true); + test_v2_swap_utxo_utxo_sell_impl(); +} + +#[test] +fn test_v2_swap_utxo_utxo_sell_impl() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN1, 1000.into()); let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); From 4aaf0bdc0acf8fbe1f8e21399aace20c0156fc35 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 27 Mar 2025 19:36:01 +0700 Subject: [PATCH 13/21] fix burnkey_as_alice test for sell --- .../tests/docker_tests/swap_proto_v2_tests.rs | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 212ee805dd..8ea3a4f02a 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -770,14 +770,30 @@ fn test_v2_swap_utxo_utxo_sell_burnkey_as_alice() { test_v2_swap_utxo_utxo_sell_impl(); } -#[test] fn test_v2_swap_utxo_utxo_sell_impl() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN1, 1000.into()); let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + let alice_pubkey_str = hex::encode( + key_pair_from_secret(&alice_priv_key) + .expect("valid test key pair") + .public() + .to_vec(), + ); + let mut envs = vec![]; + if SET_BURN_PUBKEY_TO_ALICE.get() { + envs.push(("TEST_BURN_ADDR_RAW_PUBKEY", alice_pubkey_str.as_str())); + } + let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins); - let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let mut mm_bob = block_on(MarketMakerIt::start_with_envs( + bob_conf.conf, + bob_conf.rpc_password, + None, + &envs, + )) + .unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -785,7 +801,13 @@ fn test_v2_swap_utxo_utxo_sell_impl() { Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob .ip .to_string()]); - let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + let mut mm_alice = block_on(MarketMakerIt::start_with_envs( + alice_conf.conf, + alice_conf.rpc_password, + None, + &envs, + )) + .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); log!("Alice log path: {}", mm_alice.log_path.display()); From 96594e117a630ba0aa1357b8ba77abab01ba1c63 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 28 Mar 2025 19:37:22 +0700 Subject: [PATCH 14/21] provide test_v2_swap_utxo_utxo_impl_common function for tests --- .../tests/docker_tests/swap_proto_v2_tests.rs | 302 +++++++----------- 1 file changed, 113 insertions(+), 189 deletions(-) diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index 8ea3a4f02a..c847ca92fd 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -621,21 +621,29 @@ fn send_and_refund_maker_payment_taker_secret() { log!("{:02x}", refund_tx.tx_hash_as_bytes()); } -#[test] -fn test_v2_swap_utxo_utxo_buy() { test_v2_swap_utxo_utxo_impl(); } - -// test a swap when taker is burn pubkey (no dex fee should be paid) -#[test] -fn test_v2_swap_utxo_utxo_burnkey_as_alice() { - SET_BURN_PUBKEY_TO_ALICE.set(true); - test_v2_swap_utxo_utxo_impl(); +/// A struct capturing parameters to run an UTXO-UTXO swap v2 test +struct UtxoSwapV2TestParams { + maker_price: f64, + taker_price: f64, + volume: f64, + premium: Option, + taker_method: TakerMethod, + /// Expected locked amount on Bob’s side before maker payment is sent + /// E.g. "777.00001" + expected_bob_locked_amount: &'static str, + /// Expected locked amount on Alice’s side before taker funding is sent + /// E.g. "778.00779" + expected_alice_locked_amount: &'static str, } -fn test_v2_swap_utxo_utxo_impl() { +/// This function unifies the logic for testing TPU using UTXO pair of coins +fn test_v2_swap_utxo_utxo_impl_common(params: UtxoSwapV2TestParams) { + // 1) Generate Bob and Alice UTXO coins let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN1, 1000.into()); let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); + // 2) Possibly push the burn pubkey into envs let alice_pubkey_str = hex::encode( key_pair_from_secret(&alice_priv_key) .expect("valid test key pair") @@ -647,6 +655,7 @@ fn test_v2_swap_utxo_utxo_impl() { envs.push(("TEST_BURN_ADDR_RAW_PUBKEY", alice_pubkey_str.as_str())); } + // 3) Start Bob (seednode) and Alice (light node) let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins); let mut mm_bob = block_on(MarketMakerIt::start_with_envs( bob_conf.conf, @@ -672,88 +681,86 @@ fn test_v2_swap_utxo_utxo_impl() { let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); log!("Alice log path: {}", mm_alice.log_path.display()); + // 4) Enable coins log!("{:?}", block_on(enable_native(&mm_bob, MYCOIN, &[], None))); log!("{:?}", block_on(enable_native(&mm_bob, MYCOIN1, &[], None))); log!("{:?}", block_on(enable_native(&mm_alice, MYCOIN, &[], None))); log!("{:?}", block_on(enable_native(&mm_alice, MYCOIN1, &[], None))); + // 5) Start swaps let uuids = block_on(start_swaps( &mut mm_bob, &mut mm_alice, &[(MYCOIN, MYCOIN1)], - 1.0, - 1.00001, - 777., - Some(0.007), - TakerMethod::Buy, + params.maker_price, + params.taker_price, + params.volume, + params.premium, + params.taker_method, )); - log!("{:?}", uuids); + log!("Started swaps with uuids: {:?}", uuids); + // 6) Validate active swaps let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); - let active_swaps_bob = block_on(active_swaps(&mm_bob)); assert_eq!(active_swaps_bob.uuids, parsed_uuids); let active_swaps_alice = block_on(active_swaps(&mm_alice)); assert_eq!(active_swaps_alice.uuids, parsed_uuids); - // disabling coins used in active swaps must not work - let err = block_on(disable_coin_err(&mm_bob, MYCOIN, false)); - assert_eq!(err.active_swaps, parsed_uuids); + // 7) Disabling coins that are in active swaps must not work + for coin in [MYCOIN, MYCOIN1] { + let err = block_on(disable_coin_err(&mm_bob, MYCOIN, false)); + assert_eq!(err.active_swaps, parsed_uuids); - let err = block_on(disable_coin_err(&mm_bob, MYCOIN1, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - let err = block_on(disable_coin_err(&mm_alice, MYCOIN, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - let err = block_on(disable_coin_err(&mm_alice, MYCOIN1, false)); - assert_eq!(err.active_swaps, parsed_uuids); + let err = block_on(disable_coin_err(&mm_alice, coin, false)); + assert_eq!(err.active_swaps, parsed_uuids); + } - // coins must be virtually locked until swap transactions are sent + // 8) Coins must be virtually locked until swap transactions are sent + // Bob’s side let locked_bob = block_on(get_locked_amount(&mm_bob, MYCOIN)); assert_eq!(locked_bob.coin, MYCOIN); - let expected: MmNumberMultiRepr = MmNumber::from("777.00001").into(); - assert_eq!(locked_bob.locked_amount, expected); - - let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); - assert_eq!(locked_alice.coin, MYCOIN1); - let expected: MmNumberMultiRepr = if SET_BURN_PUBKEY_TO_ALICE.get() { - MmNumber::from("777.00778").into() // no dex fee if dex pubkey is alice - } else { - MmNumber::from("778.00779").into() - }; - assert_eq!(locked_alice.locked_amount, expected); + let expected_bob_before: MmNumberMultiRepr = MmNumber::from(params.expected_bob_locked_amount).into(); + assert_eq!(locked_bob.locked_amount, expected_bob_before); - // amount must unlocked after funding tx is sent - block_on(mm_alice.wait_for_log(20., |log| log.contains("Sent taker funding"))).unwrap(); + // Alice’s side let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); assert_eq!(locked_alice.coin, MYCOIN1); - let expected: MmNumberMultiRepr = MmNumber::from("0").into(); - assert_eq!(locked_alice.locked_amount, expected); - - // amount must unlocked after maker payment is sent - block_on(mm_bob.wait_for_log(20., |log| log.contains("Sent maker payment"))).unwrap(); - let locked_bob = block_on(get_locked_amount(&mm_bob, MYCOIN)); - assert_eq!(locked_bob.coin, MYCOIN); - let expected: MmNumberMultiRepr = MmNumber::from("0").into(); - assert_eq!(locked_bob.locked_amount, expected); - - for uuid in uuids { - block_on(wait_for_swap_finished(&mm_bob, &uuid, 60)); - block_on(wait_for_swap_finished(&mm_alice, &uuid, 30)); + let expected_alice_before: MmNumberMultiRepr = MmNumber::from(params.expected_alice_locked_amount).into(); + assert_eq!(locked_alice.locked_amount, expected_alice_before); + + // 9) After taker funding is sent, the amount must be unlocked, Alice’s locked amount should be zero + block_on(mm_alice.wait_for_log(20., |log| log.contains("Sent taker funding"))) + .expect("Timeout waiting for taker to send funding"); + let locked_alice_after = block_on(get_locked_amount(&mm_alice, MYCOIN1)); + assert_eq!(locked_alice_after.coin, MYCOIN1); + assert_eq!(locked_alice_after.locked_amount, MmNumber::from("0").into()); + + // 10) After maker payment is sent, the amount must be unlocked, Bob’s locked amount should be zero + block_on(mm_bob.wait_for_log(20., |log| log.contains("Sent maker payment"))) + .expect("Timeout waiting for maker to send payment"); + let locked_bob_after = block_on(get_locked_amount(&mm_bob, MYCOIN)); + assert_eq!(locked_bob_after.coin, MYCOIN); + assert_eq!(locked_bob_after.locked_amount, MmNumber::from("0").into()); + + // 11) Wait for the swaps to finish and check statuses + for uuid in uuids.iter() { + block_on(wait_for_swap_finished(&mm_bob, uuid, 60)); + block_on(wait_for_swap_finished(&mm_alice, uuid, 30)); - let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); - log!("{:?}", maker_swap_status); + let maker_swap_status = block_on(my_swap_status(&mm_bob, uuid)); + log!("Maker swap status for {}: {:?}", uuid, maker_swap_status); - let taker_swap_status = block_on(my_swap_status(&mm_alice, &uuid)); - log!("{:?}", taker_swap_status); + let taker_swap_status = block_on(my_swap_status(&mm_alice, uuid)); + log!("Taker swap status for {}: {:?}", uuid, taker_swap_status); } + // 12) Check the recent swaps block_on(check_recent_swaps(&mm_bob, 1)); block_on(check_recent_swaps(&mm_alice, 1)); - // Disabling coins on both nodes should be successful at this point + // 13) Disabling coins on both nodes should be successful at this point block_on(disable_coin(&mm_bob, MYCOIN, false)); block_on(disable_coin(&mm_bob, MYCOIN1, false)); block_on(disable_coin(&mm_alice, MYCOIN, false)); @@ -761,142 +768,59 @@ fn test_v2_swap_utxo_utxo_impl() { } #[test] -fn test_v2_swap_utxo_utxo_sell() { test_v2_swap_utxo_utxo_sell_impl(); } +fn test_v2_swap_utxo_utxo() { + test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { + maker_price: 1.0, + taker_price: 1.00001, + volume: 777.0, + premium: Some(0.007), + taker_method: TakerMethod::Buy, + expected_bob_locked_amount: "777.00001", + expected_alice_locked_amount: "778.00779", + }); +} -// test a swap when taker sells and taker is burn pubkey (no dex fee should be paid) +// test a swap when taker buys and taker is burn pubkey (no dex fee should be paid) #[test] -fn test_v2_swap_utxo_utxo_sell_burnkey_as_alice() { +fn test_v2_swap_utxo_utxo_burnkey_as_alice() { SET_BURN_PUBKEY_TO_ALICE.set(true); - test_v2_swap_utxo_utxo_sell_impl(); + test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { + maker_price: 1.0, + taker_price: 1.00001, + volume: 777.0, + premium: Some(0.007), + taker_method: TakerMethod::Buy, + expected_bob_locked_amount: "777.00001", + expected_alice_locked_amount: "777.00778", // no dex fee if dex pubkey is alice + }); } -fn test_v2_swap_utxo_utxo_sell_impl() { - let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN, 1000.into()); - let (_ctx, _, alice_priv_key) = generate_utxo_coin_with_random_privkey(MYCOIN1, 1000.into()); - let coins = json!([mycoin_conf(1000), mycoin1_conf(1000)]); - - let alice_pubkey_str = hex::encode( - key_pair_from_secret(&alice_priv_key) - .expect("valid test key pair") - .public() - .to_vec(), - ); - let mut envs = vec![]; - if SET_BURN_PUBKEY_TO_ALICE.get() { - envs.push(("TEST_BURN_ADDR_RAW_PUBKEY", alice_pubkey_str.as_str())); - } - - let bob_conf = Mm2TestConf::seednode_trade_v2(&format!("0x{}", hex::encode(bob_priv_key)), &coins); - let mut mm_bob = block_on(MarketMakerIt::start_with_envs( - bob_conf.conf, - bob_conf.rpc_password, - None, - &envs, - )) - .unwrap(); - let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); - log!("Bob log path: {}", mm_bob.log_path.display()); - - let alice_conf = - Mm2TestConf::light_node_trade_v2(&format!("0x{}", hex::encode(alice_priv_key)), &coins, &[&mm_bob - .ip - .to_string()]); - let mut mm_alice = block_on(MarketMakerIt::start_with_envs( - alice_conf.conf, - alice_conf.rpc_password, - None, - &envs, - )) - .unwrap(); - let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - log!("Alice log path: {}", mm_alice.log_path.display()); - - log!("{:?}", block_on(enable_native(&mm_bob, MYCOIN, &[], None))); - log!("{:?}", block_on(enable_native(&mm_bob, MYCOIN1, &[], None))); - log!("{:?}", block_on(enable_native(&mm_alice, MYCOIN, &[], None))); - log!("{:?}", block_on(enable_native(&mm_alice, MYCOIN1, &[], None))); - - let uuids = block_on(start_swaps( - &mut mm_bob, - &mut mm_alice, - &[(MYCOIN, MYCOIN1)], - 1.0, - 1., - 777., - Some(0.00001), - TakerMethod::Sell, - )); - log!("{:?}", uuids); - - let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); - - let active_swaps_bob = block_on(active_swaps(&mm_bob)); - assert_eq!(active_swaps_bob.uuids, parsed_uuids); - - let active_swaps_alice = block_on(active_swaps(&mm_alice)); - assert_eq!(active_swaps_alice.uuids, parsed_uuids); - - // disabling coins used in active swaps must not work - let err = block_on(disable_coin_err(&mm_bob, MYCOIN, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - let err = block_on(disable_coin_err(&mm_bob, MYCOIN1, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - let err = block_on(disable_coin_err(&mm_alice, MYCOIN, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - let err = block_on(disable_coin_err(&mm_alice, MYCOIN1, false)); - assert_eq!(err.active_swaps, parsed_uuids); - - // coins must be virtually locked until swap transactions are sent - let locked_bob = block_on(get_locked_amount(&mm_bob, MYCOIN)); - assert_eq!(locked_bob.coin, MYCOIN); - let expected: MmNumberMultiRepr = MmNumber::from("777.").into(); - assert_eq!(locked_bob.locked_amount, expected); - - let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); - assert_eq!(locked_alice.coin, MYCOIN1); - let expected: MmNumberMultiRepr = if SET_BURN_PUBKEY_TO_ALICE.get() { - MmNumber::from("777.00001").into() // no dex fee if dex pubkey is alice - } else { - MmNumber::from("778.00001").into() - }; - assert_eq!(locked_alice.locked_amount, expected); - - // amount must unlocked after funding tx is sent - block_on(mm_alice.wait_for_log(20., |log| log.contains("Sent taker funding"))).unwrap(); - let locked_alice = block_on(get_locked_amount(&mm_alice, MYCOIN1)); - assert_eq!(locked_alice.coin, MYCOIN1); - let expected: MmNumberMultiRepr = MmNumber::from("0").into(); - assert_eq!(locked_alice.locked_amount, expected); - - // amount must unlocked after maker payment is sent - block_on(mm_bob.wait_for_log(20., |log| log.contains("Sent maker payment"))).unwrap(); - let locked_bob = block_on(get_locked_amount(&mm_bob, MYCOIN)); - assert_eq!(locked_bob.coin, MYCOIN); - let expected: MmNumberMultiRepr = MmNumber::from("0").into(); - assert_eq!(locked_bob.locked_amount, expected); - - for uuid in uuids { - block_on(wait_for_swap_finished(&mm_bob, &uuid, 60)); - block_on(wait_for_swap_finished(&mm_alice, &uuid, 30)); - - let maker_swap_status = block_on(my_swap_status(&mm_bob, &uuid)); - log!("{:?}", maker_swap_status); - - let taker_swap_status = block_on(my_swap_status(&mm_alice, &uuid)); - log!("{:?}", taker_swap_status); - } - - block_on(check_recent_swaps(&mm_bob, 1)); - block_on(check_recent_swaps(&mm_alice, 1)); +#[test] +fn test_v2_swap_utxo_utxo_sell() { + test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { + maker_price: 1.0, + taker_price: 1.0, + volume: 777.0, + premium: Some(0.00001), + taker_method: TakerMethod::Sell, + expected_bob_locked_amount: "777", + expected_alice_locked_amount: "778.00001", + }); +} - // Disabling coins on both nodes should be successful at this point - block_on(disable_coin(&mm_bob, MYCOIN, false)); - block_on(disable_coin(&mm_bob, MYCOIN1, false)); - block_on(disable_coin(&mm_alice, MYCOIN, false)); - block_on(disable_coin(&mm_alice, MYCOIN1, false)); +// test a swap when taker sells and taker is burn pubkey (no dex fee should be paid) +#[test] +fn test_v2_swap_utxo_utxo_sell_burnkey_as_alice() { + SET_BURN_PUBKEY_TO_ALICE.set(true); + test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { + maker_price: 1.0, + taker_price: 1.0, + volume: 777.0, + premium: Some(0.00001), + taker_method: TakerMethod::Sell, + expected_bob_locked_amount: "777", + expected_alice_locked_amount: "777.00001", // no dex fee if dex pubkey is alice + }); } #[test] From 7d2f22c358a81dd1f8c0d8112aa7163e107fb280 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 1 Apr 2025 14:26:47 +0700 Subject: [PATCH 15/21] Add a clarification doc comment for the premium field --- mm2src/mm2_main/src/lp_ordermatch.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index d66d1e3fde..ce87ae9570 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2282,6 +2282,9 @@ pub struct MakerReserved { pub rel_protocol_info: Option>, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, + /// Note: `std::default::Default` is not implemented for `num_rational::Ratio` + /// in the [new_protocol::MakerReserved] structure. As a result, we use `Option` there. + /// It is preferable to follow this same approach in the current structure for consistency. #[serde(default, skip_serializing_if = "Option::is_none")] premium: Option, } From 011798aca13b0cbe582a08fd6f1509f4c08f6b44 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 1 Apr 2025 16:20:23 +0700 Subject: [PATCH 16/21] review: create is_legacy variable --- mm2src/mm2_main/src/lp_ordermatch.rs | 35 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index ce87ae9570..c26522fe63 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2078,6 +2078,8 @@ impl MakerOrder { return OrderMatchResult::NotMatched; } + let is_legacy = self.swap_version.is_legacy() || taker.swap_version.is_legacy(); + match taker.action { TakerAction::Buy => { let ticker_match = (self.base == taker.base @@ -2097,7 +2099,7 @@ impl MakerOrder { return OrderMatchResult::NotMatched; } - if self.swap_version.is_legacy() || taker.swap_version.is_legacy() { + if is_legacy { // Legacy mode: use maker's price to calculate rel amount OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) } else { @@ -2119,22 +2121,21 @@ impl MakerOrder { let premium = self.premium.clone().unwrap_or_default(); // Determine the matched amounts depending on version - let (matched_base_amount, matched_rel_amount) = - if self.swap_version.is_legacy() || taker.swap_version.is_legacy() { - // Legacy: calculate the resulting base amount using the Maker's price instead of the Taker's. - (taker_base_amount / &self.price, taker_base_amount.clone()) - } else { - // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) - // is less than or equal to the maker's premium, the trade is not possible - if taker_base_amount <= &premium { - return OrderMatchResult::NotMatched; - } - // Calculate the resulting base amount using the maker's price instead of the taker's. - // The maker wants to "take" an additional portion of rel as a premium, - // so we reduce the base amount the maker gives by (premium / price). - let matched_base_amount = &(taker_base_amount - &premium) / &self.price; - (matched_base_amount, taker_base_amount.clone()) - }; + let (matched_base_amount, matched_rel_amount) = if is_legacy { + // Legacy: calculate the resulting base amount using the Maker's price instead of the Taker's. + (taker_base_amount / &self.price, taker_base_amount.clone()) + } else { + // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) + // is less than or equal to the maker's premium, the trade is not possible + if taker_base_amount <= &premium { + return OrderMatchResult::NotMatched; + } + // Calculate the resulting base amount using the maker's price instead of the taker's. + // The maker wants to "take" an additional portion of rel as a premium, + // so we reduce the base amount the maker gives by (premium / price). + let matched_base_amount = &(taker_base_amount - &premium) / &self.price; + (matched_base_amount, taker_base_amount.clone()) + }; // Match if all common conditions are met if ticker_match From e19e1c563c2547f37855d0990c4791a4e957fe03 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 2 Apr 2025 11:24:51 +0700 Subject: [PATCH 17/21] review: move base amount calc to a variable --- mm2src/mm2_main/src/lp_ordermatch.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index c26522fe63..508a2ae300 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1645,22 +1645,19 @@ impl TakerOrder { return MatchReservedResult::NotMatched; } - if self.request.swap_version.is_legacy() || reserved.swap_version.is_legacy() { - if my_rel_amount <= other_base_amount { - MatchReservedResult::Matched - } else { - MatchReservedResult::NotMatched - } + let other_base_amount = if self.request.swap_version.is_legacy() || reserved.swap_version.is_legacy() { + other_base_amount.clone() } else { let premium = &reserved.premium.clone().unwrap_or_default(); let other_price = &(my_base_amount - premium) / other_base_amount; // In match_with_request function, we allowed maker to send fewer coins for taker sell action - let result_other_base_amount = other_base_amount + &(premium / &other_price); - if my_rel_amount <= &result_other_base_amount { - MatchReservedResult::Matched - } else { - MatchReservedResult::NotMatched - } + other_base_amount + &(premium / &other_price) + }; + + if my_rel_amount <= &other_base_amount { + MatchReservedResult::Matched + } else { + MatchReservedResult::NotMatched } }, } From 385b3824e9bd8d0e931e2edd6ea997a23d94911d Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 4 Apr 2025 15:30:00 +0700 Subject: [PATCH 18/21] Don't force the taker to send all amount they offered in the Buy action --- mm2src/mm2_main/src/lp_ordermatch.rs | 11 ++++++----- .../tests/docker_tests/swap_proto_v2_tests.rs | 16 +++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 508a2ae300..682030eea4 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2096,16 +2096,17 @@ impl MakerOrder { return OrderMatchResult::NotMatched; } + let result_rel_amount = taker_base_amount * &self.price; + if is_legacy { // Legacy mode: use maker's price to calculate rel amount - OrderMatchResult::Matched((taker_base_amount.clone(), taker_base_amount * &self.price)) + OrderMatchResult::Matched((taker_base_amount.clone(), result_rel_amount)) } else { // taker_rel_amount must cover the premium requested by maker - let required_rel_amount = - taker_base_amount * &self.price + self.premium.clone().unwrap_or_default(); + let required_rel_amount = result_rel_amount + self.premium.clone().unwrap_or_default(); if taker_rel_amount >= &required_rel_amount { - // TPU mode: treat buy as a limit order using taker's base and rel amounts - OrderMatchResult::Matched((taker_base_amount.clone(), taker_rel_amount.clone())) + // TPU mode: treat buy as a limit order using taker's base amount and required_rel_amount + OrderMatchResult::Matched((taker_base_amount.clone(), required_rel_amount)) } else { OrderMatchResult::NotMatched } diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index c847ca92fd..f76e503533 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -629,10 +629,8 @@ struct UtxoSwapV2TestParams { premium: Option, taker_method: TakerMethod, /// Expected locked amount on Bob’s side before maker payment is sent - /// E.g. "777.00001" expected_bob_locked_amount: &'static str, /// Expected locked amount on Alice’s side before taker funding is sent - /// E.g. "778.00779" expected_alice_locked_amount: &'static str, } @@ -771,12 +769,12 @@ fn test_v2_swap_utxo_utxo_impl_common(params: UtxoSwapV2TestParams) { fn test_v2_swap_utxo_utxo() { test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { maker_price: 1.0, - taker_price: 1.00001, - volume: 777.0, - premium: Some(0.007), + taker_price: 1.001, + volume: 777., + premium: Some(0.777), taker_method: TakerMethod::Buy, expected_bob_locked_amount: "777.00001", - expected_alice_locked_amount: "778.00779", + expected_alice_locked_amount: "778.77801", }); } @@ -786,12 +784,12 @@ fn test_v2_swap_utxo_utxo_burnkey_as_alice() { SET_BURN_PUBKEY_TO_ALICE.set(true); test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { maker_price: 1.0, - taker_price: 1.00001, + taker_price: 1.001, volume: 777.0, - premium: Some(0.007), + premium: Some(0.777), taker_method: TakerMethod::Buy, expected_bob_locked_amount: "777.00001", - expected_alice_locked_amount: "777.00778", // no dex fee if dex pubkey is alice + expected_alice_locked_amount: "777.77701", // no dex fee if dex pubkey is alice }); } From 285b9ef2b24b75e2ef046a77da24ca082c460ee1 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 9 Apr 2025 13:15:11 +0700 Subject: [PATCH 19/21] review: create premium variable before taker action matching --- mm2src/mm2_main/src/lp_ordermatch.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 682030eea4..cc907502c6 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2076,6 +2076,7 @@ impl MakerOrder { } let is_legacy = self.swap_version.is_legacy() || taker.swap_version.is_legacy(); + let premium = self.premium.clone().unwrap_or_default(); match taker.action { TakerAction::Buy => { @@ -2103,7 +2104,7 @@ impl MakerOrder { OrderMatchResult::Matched((taker_base_amount.clone(), result_rel_amount)) } else { // taker_rel_amount must cover the premium requested by maker - let required_rel_amount = result_rel_amount + self.premium.clone().unwrap_or_default(); + let required_rel_amount = result_rel_amount + premium; if taker_rel_amount >= &required_rel_amount { // TPU mode: treat buy as a limit order using taker's base amount and required_rel_amount OrderMatchResult::Matched((taker_base_amount.clone(), required_rel_amount)) @@ -2116,7 +2117,6 @@ impl MakerOrder { let ticker_match = (self.base == taker.rel || self.base_orderbook_ticker.as_ref() == Some(&taker.rel)) && (self.rel == taker.base || self.rel_orderbook_ticker.as_ref() == Some(&taker.base)); let taker_price = taker_base_amount / taker_rel_amount; - let premium = self.premium.clone().unwrap_or_default(); // Determine the matched amounts depending on version let (matched_base_amount, matched_rel_amount) = if is_legacy { From 74c126d4e89a91bcb31fae33ff2f70fd18847bd5 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 10 Apr 2025 20:10:09 +0700 Subject: [PATCH 20/21] add safe check for taker real price --- mm2src/mm2_main/src/lp_ordermatch.rs | 14 +++++++++----- .../tests/docker_tests/swap_proto_v2_tests.rs | 6 +++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index cc907502c6..720ce9540d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2123,16 +2123,20 @@ impl MakerOrder { // Legacy: calculate the resulting base amount using the Maker's price instead of the Taker's. (taker_base_amount / &self.price, taker_base_amount.clone()) } else { - // For TPU, if the total rel amount from the taker (rel is coin which should be sent by taker during swap) - // is less than or equal to the maker's premium, the trade is not possible + // this check prevents division by zero if taker_base_amount <= &premium { return OrderMatchResult::NotMatched; } // Calculate the resulting base amount using the maker's price instead of the taker's. - // The maker wants to "take" an additional portion of rel as a premium, + // For TPU, in the taker sell action the maker wants to "take" an additional portion of rel as a premium, // so we reduce the base amount the maker gives by (premium / price). - let matched_base_amount = &(taker_base_amount - &premium) / &self.price; - (matched_base_amount, taker_base_amount.clone()) + let result_base_amount = &(taker_base_amount - &premium) / &self.price; + let real_price_for_taker = taker_base_amount / &result_base_amount; + // Ensure the taker doesn't end up paying a higher price (including premium) + if real_price_for_taker > taker_price { + return OrderMatchResult::NotMatched; + } + (result_base_amount, taker_base_amount.clone()) }; // Match if all common conditions are met diff --git a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs index f76e503533..1919ef3c5b 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_proto_v2_tests.rs @@ -769,7 +769,7 @@ fn test_v2_swap_utxo_utxo_impl_common(params: UtxoSwapV2TestParams) { fn test_v2_swap_utxo_utxo() { test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { maker_price: 1.0, - taker_price: 1.001, + taker_price: 1.001, // the price in rel the taker is willing to pay per one unit of the base coin volume: 777., premium: Some(0.777), taker_method: TakerMethod::Buy, @@ -797,7 +797,7 @@ fn test_v2_swap_utxo_utxo_burnkey_as_alice() { fn test_v2_swap_utxo_utxo_sell() { test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { maker_price: 1.0, - taker_price: 1.0, + taker_price: 0.98, // the price in rel the taker is willing to receive per one unit of the base coin volume: 777.0, premium: Some(0.00001), taker_method: TakerMethod::Sell, @@ -812,7 +812,7 @@ fn test_v2_swap_utxo_utxo_sell_burnkey_as_alice() { SET_BURN_PUBKEY_TO_ALICE.set(true); test_v2_swap_utxo_utxo_impl_common(UtxoSwapV2TestParams { maker_price: 1.0, - taker_price: 1.0, + taker_price: 0.98, volume: 777.0, premium: Some(0.00001), taker_method: TakerMethod::Sell, From 1332c219d1920a331a8d61f431946b722fda5da1 Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 14 Apr 2025 15:50:57 +0700 Subject: [PATCH 21/21] review: doc com for premium --- mm2src/mm2_main/src/lp_ordermatch.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 720ce9540d..57c65ee39c 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -1737,6 +1737,7 @@ pub struct MakerOrder { p2p_privkey: Option, #[serde(default, skip_serializing_if = "SwapVersion::is_legacy")] pub swap_version: SwapVersion, + /// Fixed extra amount of maker rel coin, requested by the maker and paid by the taker #[serde(default, skip_serializing_if = "Option::is_none")] premium: Option, }