diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 53d7bd3b20..971bc7a65e 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -699,6 +699,11 @@ pub fn active_swaps(ctx: &MmArc) -> Result, String> { uuids.push(*swap.uuid()) } } + + drop(swaps); + + let swaps_v2 = swap_ctx.active_swaps_v2_infos.lock().unwrap(); + uuids.extend(swaps_v2.keys()); Ok(uuids) } @@ -1549,6 +1554,8 @@ struct ActiveSwapsRes { statuses: Option>, } +/// This RPC does not support including statuses of v2 (Trading Protocol Upgrade) swaps. +/// It returns only uuids for these. pub async fn active_swaps_rpc(ctx: MmArc, req: Json) -> Result>, String> { let req: ActiveSwapsReq = try_s!(json::from_value(req)); let uuids = try_s!(active_swaps(&ctx)); diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index ff0a60ad8c..bcd5839f5f 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -655,7 +655,14 @@ impl Storable maker_coin_locked.retain(|locked| locked.swap_uuid != self.uuid); }; }, - _ => (), + MakerSwapEvent::WaitingForTakerFunding { .. } + | MakerSwapEvent::TakerFundingReceived { .. } + | MakerSwapEvent::MakerPaymentRefundRequired { .. } + | MakerSwapEvent::MakerPaymentRefunded { .. } + | MakerSwapEvent::TakerPaymentConfirmed { .. } + | MakerSwapEvent::TakerPaymentSpent { .. } + | MakerSwapEvent::Aborted { .. } + | MakerSwapEvent::Completed => (), } } @@ -663,7 +670,45 @@ impl Storable &mut self, event: <::DbRepr as StateMachineDbRepr>::Event, ) { - todo!() + match event { + MakerSwapEvent::Initialized { + maker_payment_trade_fee, + .. + } + | MakerSwapEvent::WaitingForTakerFunding { + maker_payment_trade_fee, + .. + } + | MakerSwapEvent::TakerFundingReceived { + maker_payment_trade_fee, + .. + } => { + let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); + let maker_coin_ticker: String = self.maker_coin.ticker().into(); + let new_locked = LockedAmountInfo { + swap_uuid: self.uuid, + locked_amount: LockedAmount { + coin: maker_coin_ticker.clone(), + amount: self.maker_volume.clone(), + trade_fee: Some(maker_payment_trade_fee.clone().into()), + }, + }; + swaps_ctx + .locked_amounts + .lock() + .unwrap() + .entry(maker_coin_ticker) + .or_insert_with(Vec::new) + .push(new_locked); + }, + MakerSwapEvent::MakerPaymentSentFundingSpendGenerated { .. } + | MakerSwapEvent::MakerPaymentRefundRequired { .. } + | MakerSwapEvent::MakerPaymentRefunded { .. } + | MakerSwapEvent::TakerPaymentConfirmed { .. } + | MakerSwapEvent::TakerPaymentSpent { .. } + | MakerSwapEvent::Aborted { .. } + | MakerSwapEvent::Completed => (), + } } } diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs index 89c32166dd..0fdd9da2b6 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_v2_rpcs.rs @@ -13,6 +13,7 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_number::{MmNumber, MmNumberMultiRepr}; use serde::de::DeserializeOwned; +use std::collections::HashMap; use std::num::NonZeroUsize; use uuid::Uuid; @@ -438,3 +439,36 @@ pub(crate) async fn my_recent_swaps_rpc( found_records: db_result.uuids_and_types.len(), }) } + +#[derive(Deserialize)] +pub(crate) struct ActiveSwapsRequest { + #[serde(default)] + include_status: bool, +} + +#[derive(Display, Serialize, SerializeErrorType)] +#[serde(tag = "error_type", content = "error_data")] +pub(crate) enum ActiveSwapsErr { + Internal(String), +} + +impl HttpStatusCode for ActiveSwapsErr { + fn status_code(&self) -> StatusCode { + match self { + ActiveSwapsErr::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +#[derive(Serialize)] +pub(crate) struct ActiveSwapsResponse { + uuids: Vec, + statuses: HashMap, +} + +pub(crate) async fn active_swaps_rpc( + _ctx: MmArc, + _req: ActiveSwapsRequest, +) -> MmResult { + unimplemented!() +} 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 cefab795a8..8346022882 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -71,6 +71,8 @@ pub enum TakerSwapEvent { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, + taker_payment_fee: SavedTradeFee, + maker_payment_spend_fee: SavedTradeFee, }, /// Sent taker funding tx. TakerFundingSent { @@ -478,6 +480,8 @@ impl Storable maker_coin_start_block, taker_coin_start_block, negotiation_data, + taker_payment_fee, + maker_payment_spend_fee, } => Box::new(Negotiated { maker_coin_start_block, taker_coin_start_block, @@ -486,6 +490,8 @@ impl Storable &recreate_ctx.maker_coin, &recreate_ctx.taker_coin, )?, + taker_payment_fee, + maker_payment_spend_fee, }), TakerSwapEvent::TakerFundingSent { maker_coin_start_block, @@ -765,7 +771,40 @@ impl Storable &mut self, event: <::DbRepr as StateMachineDbRepr>::Event, ) { - todo!() + match event { + TakerSwapEvent::Initialized { taker_payment_fee, .. } + | TakerSwapEvent::Negotiated { taker_payment_fee, .. } => { + let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); + let taker_coin_ticker: String = self.taker_coin.ticker().into(); + let new_locked = LockedAmountInfo { + swap_uuid: self.uuid, + locked_amount: LockedAmount { + coin: taker_coin_ticker.clone(), + amount: &(&self.taker_volume + &self.dex_fee) + &self.taker_premium, + trade_fee: Some(taker_payment_fee.into()), + }, + }; + swaps_ctx + .locked_amounts + .lock() + .unwrap() + .entry(taker_coin_ticker) + .or_insert_with(Vec::new) + .push(new_locked); + }, + TakerSwapEvent::TakerFundingSent { .. } + | TakerSwapEvent::TakerFundingRefundRequired { .. } + | TakerSwapEvent::MakerPaymentAndFundingSpendPreimgReceived { .. } + | TakerSwapEvent::TakerPaymentSent { .. } + | TakerSwapEvent::TakerPaymentRefundRequired { .. } + | TakerSwapEvent::MakerPaymentConfirmed { .. } + | TakerSwapEvent::TakerPaymentSpent { .. } + | TakerSwapEvent::MakerPaymentSpent { .. } + | TakerSwapEvent::TakerFundingRefunded { .. } + | TakerSwapEvent::TakerPaymentRefunded { .. } + | TakerSwapEvent::Aborted { .. } + | TakerSwapEvent::Completed => (), + } } } @@ -1020,6 +1059,8 @@ impl State fo maker_coin_swap_contract: maker_negotiation.maker_coin_swap_contract, taker_coin_swap_contract: maker_negotiation.taker_coin_swap_contract, }, + taker_payment_fee: self.taker_payment_fee, + maker_payment_spend_fee: self.maker_payment_spend_fee, }; Self::change_state(next_state, state_machine).await } @@ -1070,6 +1111,8 @@ struct Negotiated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, + taker_payment_fee: SavedTradeFee, + maker_payment_spend_fee: SavedTradeFee, } impl TransitionFrom> @@ -1127,6 +1170,8 @@ impl Storable maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data.to_stored_data(), + taker_payment_fee: self.taker_payment_fee.clone(), + maker_payment_spend_fee: self.maker_payment_spend_fee.clone(), } } } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 328b8e074d..3db979c4c7 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -4,7 +4,7 @@ use crate::mm2::lp_native_dex::init_hw::{cancel_init_trezor, init_trezor, init_t use crate::mm2::lp_native_dex::init_metamask::{cancel_connect_metamask, connect_metamask, connect_metamask_status}; use crate::mm2::lp_ordermatch::{best_orders_rpc_v2, orderbook_rpc_v2, start_simple_market_maker_bot, stop_simple_market_maker_bot}; -use crate::mm2::lp_swap::swap_v2_rpcs::{my_recent_swaps_rpc, my_swap_status_rpc}; +use crate::mm2::lp_swap::swap_v2_rpcs::{active_swaps_rpc, my_recent_swaps_rpc, my_swap_status_rpc}; use crate::mm2::rpc::rate_limiter::{process_rate_limit, RateLimitContext}; use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_stat, start_version_stat_collection, stop_version_stat_collection, update_version_stat_collection}, @@ -154,6 +154,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, account_balance).await, + "active_swaps" => handle_mmrpc(ctx, request, active_swaps_rpc).await, "add_delegation" => handle_mmrpc(ctx, request, add_delegation).await, "add_node_to_version_stat" => handle_mmrpc(ctx, request, add_node_to_version_stat).await, "best_orders" => handle_mmrpc(ctx, request, best_orders_rpc_v2).await, 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 6f7b409233..70bc6630aa 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 @@ -5,10 +5,10 @@ use coins::{GenTakerFundingSpendArgs, RefundFundingSecretArgs, RefundPaymentArgs Transaction, ValidateTakerFundingArgs}; use common::{block_on, now_sec}; use mm2_number::MmNumber; -use mm2_test_helpers::for_tests::{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}; +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}; use mm2_test_helpers::structs::MmNumberMultiRepr; use script::{Builder, Opcode}; use serialization::serialize; @@ -230,6 +230,13 @@ fn test_v2_swap_utxo_utxo() { println!("{:?}", 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); @@ -319,10 +326,12 @@ fn test_v2_swap_utxo_utxo_kickstart() { &[(MYCOIN, MYCOIN1)], 1.0, 1.0, - 100., + 777., )); println!("{:?}", uuids); + let parsed_uuids: Vec = uuids.iter().map(|u| u.parse().unwrap()).collect(); + for uuid in uuids.iter() { let maker_swap_status = block_on(my_swap_status(&mm_bob, uuid)); println!("Maker swap {} status before stop {:?}", uuid, maker_swap_status); @@ -337,7 +346,7 @@ fn test_v2_swap_utxo_utxo_kickstart() { bob_conf.conf["dbdir"] = mm_bob.folder.join("DB").to_str().unwrap().into(); bob_conf.conf["log"] = mm_bob.folder.join("mm2_dup.log").to_str().unwrap().into(); - let mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -345,7 +354,7 @@ fn test_v2_swap_utxo_utxo_kickstart() { alice_conf.conf["log"] = mm_alice.folder.join("mm2_dup.log").to_str().unwrap().into(); alice_conf.conf["seednodes"] = vec![mm_bob.ip.to_string()].into(); - let mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf, alice_conf.rpc_password, None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); log!("Alice log path: {}", mm_alice.log_path.display()); @@ -365,17 +374,37 @@ fn test_v2_swap_utxo_utxo_kickstart() { // give swaps 1 second to restart std::thread::sleep(Duration::from_secs(1)); + 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); + // coins must be virtually locked after kickstart until swap transactions are sent + 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(); + assert_eq!(locked_alice.locked_amount, expected); + 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); + // 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("778.00001").into(); + 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)); diff --git a/mm2src/mm2_state_machine/src/storable_state_machine.rs b/mm2src/mm2_state_machine/src/storable_state_machine.rs index dd4393df3f..49b57af532 100644 --- a/mm2src/mm2_state_machine/src/storable_state_machine.rs +++ b/mm2src/mm2_state_machine/src/storable_state_machine.rs @@ -116,28 +116,6 @@ impl + Send> Restore fn into_state(self: Box) -> Box> { self } } -trait Trait1 { - fn method1(&self); -} - -trait Trait2: Trait1 { - fn method2(&self); -} - -struct MyStruct; - -impl Trait1 for MyStruct { - fn method1(&self) { - println!("Calling method1"); - } -} - -impl Trait2 for MyStruct { - fn method2(&self) { - println!("Calling method2"); - } -} - /// A struct representing a restored state machine. pub struct RestoredMachine { machine: M, diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 7d7a21f0f8..cdab017a48 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -3178,3 +3178,14 @@ fn test_parse_env_file() { ) ); } + +pub async fn active_swaps(mm: &MarketMakerIt) -> ActiveSwapsResponse { + let request = json!({ + "userpass": mm.userpass, + "method": "active_swaps", + "params": [] + }); + let response = mm.rpc(&request).await.unwrap(); + assert_eq!(response.0, StatusCode::OK, "'active_swaps' failed: {}", response.1); + json::from_str(&response.1).unwrap() +} diff --git a/mm2src/mm2_test_helpers/src/structs.rs b/mm2src/mm2_test_helpers/src/structs.rs index bc9e7214a7..e36af8ba60 100644 --- a/mm2src/mm2_test_helpers/src/structs.rs +++ b/mm2src/mm2_test_helpers/src/structs.rs @@ -1067,3 +1067,10 @@ pub struct DisableCoinOrders { pub struct CoinsNeededForKickstartResponse { pub result: Vec, } + +#[derive(Serialize, Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct ActiveSwapsResponse { + pub uuids: Vec, + pub statuses: Option>, +}