From 9059f930ef05f5e554b80544fec597d0731dc18a Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Tue, 14 Jan 2025 06:09:39 +0100 Subject: [PATCH 1/9] impl zero dex fee for KMD --- mm2src/coins/lp_coins.rs | 6 +- mm2src/coins/utxo/utxo_common.rs | 90 ++++++++++++-------- mm2src/mm2_main/src/lp_swap.rs | 79 ++++++----------- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 5 +- 4 files changed, 87 insertions(+), 93 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 16ade17a6b..cff077a416 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3624,6 +3624,8 @@ pub enum DexFee { fee_amount: MmNumber, burn_amount: MmNumber, }, + /// Zero dex fee, exclusive to KMD only. + Zero, } impl DexFee { @@ -3640,13 +3642,14 @@ impl DexFee { match self { DexFee::Standard(t) => t.clone(), DexFee::WithBurn { fee_amount, .. } => fee_amount.clone(), + DexFee::Zero => MmNumber::default(), } } /// Gets the burn amount associated with the dex fee, if applicable. pub fn burn_amount(&self) -> Option { match self { - DexFee::Standard(_) => None, + DexFee::Standard(_) | DexFee::Zero => None, DexFee::WithBurn { burn_amount, .. } => Some(burn_amount.clone()), } } @@ -3659,6 +3662,7 @@ impl DexFee { fee_amount, burn_amount, } => fee_amount + burn_amount, + DexFee::Zero => MmNumber::default(), } } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 70c8522b58..0c1a2ea636 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1243,39 +1243,52 @@ async fn gen_taker_payment_spend_preimage( args: &GenTakerPaymentSpendArgs<'_, T>, n_time: NTimeSetting, ) -> GenPreimageResInner { - let dex_fee_address = address_from_raw_pubkey( - args.dex_fee_pub, - coin.as_ref().conf.address_prefixes.clone(), - coin.as_ref().conf.checksum_type, - coin.as_ref().conf.bech32_hrp.clone(), - coin.addr_format().clone(), - ) - .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive dex_fee_address: {}", e)))?; - - let mut outputs = generate_taker_fee_tx_outputs(coin.as_ref().decimals, dex_fee_address.hash(), args.dex_fee)?; - if let DexFee::WithBurn { .. } = args.dex_fee { - let script = output_script(args.maker_address).map_to_mm(|e| { - TxGenError::Other(format!( - "Couldn't generate output script for maker address {}, error {}", - args.maker_address, e - )) - })?; - let tx_fee = coin - .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await?; - let maker_value = args - .taker_tx - .first_output() - .map_to_mm(|e| TxGenError::PrevTxIsNotValid(e.to_string()))? - .value - - outputs[0].value - - outputs[1].value - - tx_fee; - outputs.push(TransactionOutput { - value: maker_value, - script_pubkey: script.to_bytes(), - }) - } + let mut outputs: Vec = Vec::with_capacity(3); + match args.dex_fee { + // KMD Zero DexFee: Don't include dex fee output for KMD ticker. + DexFee::Zero => (), + DexFee::WithBurn { .. } => { + if let DexFee::WithBurn { .. } = args.dex_fee { + let script = output_script(args.maker_address).map_to_mm(|e| { + TxGenError::Other(format!( + "Couldn't generate output script for maker address {}, error {}", + args.maker_address, e + )) + })?; + let tx_fee = coin + .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) + .await?; + let maker_value = args + .taker_tx + .first_output() + .map_to_mm(|e| TxGenError::PrevTxIsNotValid(e.to_string()))? + .value + - outputs[0].value + - outputs[1].value + - tx_fee; + + outputs.push(TransactionOutput { + value: maker_value, + script_pubkey: script.to_bytes(), + }) + } + }, + DexFee::Standard(_) => { + let dex_fee_address = address_from_raw_pubkey( + args.dex_fee_pub, + coin.as_ref().conf.address_prefixes.clone(), + coin.as_ref().conf.checksum_type, + coin.as_ref().conf.bech32_hrp.clone(), + coin.addr_format().clone(), + ) + .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive dex_fee_address: {}", e)))?; + outputs.extend_from_slice(&generate_taker_fee_tx_outputs( + coin.as_ref().decimals, + dex_fee_address.hash(), + args.dex_fee, + )?); + }, + }; p2sh_spending_tx_preimage( coin, @@ -1306,7 +1319,7 @@ pub async fn gen_and_sign_taker_payment_spend_preimage( let sig_hash_type = match args.dex_fee { DexFee::Standard(_) => SIGHASH_SINGLE, - DexFee::WithBurn { .. } => SIGHASH_ALL, + DexFee::WithBurn { .. } | DexFee::Zero => SIGHASH_ALL, }; let signature = calc_and_sign_sighash( @@ -1349,7 +1362,7 @@ pub async fn validate_taker_payment_spend_preimage( let sig_hash_type = match gen_args.dex_fee { DexFee::Standard(_) => SIGHASH_SINGLE, - DexFee::WithBurn { .. } => SIGHASH_ALL, + DexFee::WithBurn { .. } | DexFee::Zero => SIGHASH_ALL, }; let sig_hash = signature_hash_to_sign( @@ -1433,7 +1446,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( let mut taker_signature_with_sighash = preimage.signature.to_vec(); let taker_sig_hash = match gen_args.dex_fee { DexFee::Standard(_) => (SIGHASH_SINGLE | coin.as_ref().conf.fork_id) as u8, - DexFee::WithBurn { .. } => (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8, + DexFee::WithBurn { .. } | DexFee::Zero => (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8, }; taker_signature_with_sighash.push(taker_sig_hash); @@ -1486,6 +1499,11 @@ fn generate_taker_fee_tx_outputs( address_hash: &AddressHashEnum, dex_fee: &DexFee, ) -> Result, MmError> { + // Don't add outputs for Zero DexFee. + if let DexFee::Zero = dex_fee { + return Ok(vec![]); + } + let fee_amount = dex_fee.fee_uamount(decimals)?; let mut outputs = vec![TransactionOutput { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index fc8fcfc277..21dd172929 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -809,6 +809,11 @@ fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { } pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amount: &MmNumber) -> DexFee { + // if base or rel coin is KMD, dex fee should be set zero + if base == "KMD" || rel == "KMD" { + return DexFee::Zero; + } + let rate = dex_fee_rate(base, rel); let fee = trade_amount * &rate; @@ -816,31 +821,6 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo return DexFee::Standard(min_tx_amount.clone()); } - if base == "KMD" { - // Drop the fee by 25%, which will be burned during the taker fee payment. - // - // This cut will be dropped before return if the final amount is less than - // the minimum transaction amount. - - // Fee with 25% cut - let new_fee = &fee * &MmNumber::from("0.75"); - - let (fee, burn) = if &new_fee >= min_tx_amount { - // Use the max burn value, which is 25%. - let burn_amount = &fee - &new_fee; - - (new_fee, burn_amount) - } else { - // Burn only the exceed amount because fee after 25% cut is less - // than `min_tx_amount`. - let burn_amount = &fee - min_tx_amount; - - (min_tx_amount.clone(), burn_amount) - }; - - return DexFee::with_burn(fee, burn); - } - DexFee::Standard(fee) } @@ -1864,9 +1844,7 @@ mod lp_swap_tests { let rel = "ETH"; let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - let expected_fee = amount.clone() * (9, 7770).into() * MmNumber::from("0.75"); - let expected_burn_amount = amount * (9, 7770).into() * MmNumber::from("0.25"); - assert_eq!(DexFee::with_burn(expected_fee, expected_burn_amount), actual_fee); + assert_eq!(DexFee::Zero, actual_fee); // check the case when KMD taker fee is close to dust let base = "KMD"; @@ -1874,26 +1852,19 @@ mod lp_swap_tests { let amount = (1001 * 777, 90000000).into(); let min_tx_amount = "0.00001".into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - assert_eq!( - DexFee::WithBurn { - fee_amount: "0.00001".into(), - burn_amount: "0.00000001".into() - }, - actual_fee - ); + assert_eq!(DexFee::Zero, actual_fee); let base = "BTC"; let rel = "KMD"; let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - let expected_fee = DexFee::Standard(amount * (9, 7770).into()); - assert_eq!(expected_fee, actual_fee); + assert_eq!(DexFee::Zero, actual_fee); let base = "BTC"; let rel = "KMD"; let amount: MmNumber = "0.001".parse::().unwrap().into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); - assert_eq!(DexFee::Standard(min_tx_amount), actual_fee); + assert_eq!(DexFee::Zero, actual_fee); } #[test] @@ -2413,27 +2384,25 @@ mod lp_swap_tests { std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); let kmd = coins::TestCoin::new("KMD"); - let (kmd_taker_fee, kmd_burn_amount) = match dex_fee_amount_from_taker_coin(&kmd, "", &MmNumber::from(6150)) { - DexFee::Standard(_) => panic!("Wrong variant returned for KMD from `dex_fee_amount_from_taker_coin`."), - DexFee::WithBurn { - fee_amount, - burn_amount, - } => (fee_amount, burn_amount), + let kmd_burn_amount = match dex_fee_amount_from_taker_coin(&kmd, "MYCOIN", &MmNumber::from(6150)) { + DexFee::Standard(_) | DexFee::WithBurn { .. } => { + panic!("Wrong variant returned for KMD from `dex_fee_amount_from_taker_coin`.") + }, + DexFee::Zero => MmNumber::default(), }; let mycoin = coins::TestCoin::new("MYCOIN"); - let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { - DexFee::Standard(t) => t, - DexFee::WithBurn { .. } => { + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "KMD", &MmNumber::from(6150)) { + DexFee::Zero => MmNumber::default(), + DexFee::WithBurn { .. } | DexFee::Standard(_) => { panic!("Wrong variant returned for MYCOIN from `dex_fee_amount_from_taker_coin`.") }, }; - let expected_mycoin_taker_fee = &kmd_taker_fee / &MmNumber::from("0.75"); - let expected_kmd_burn_amount = &mycoin_taker_fee - &kmd_taker_fee; + let zero_amount = MmNumber::default(); - assert_eq!(expected_mycoin_taker_fee, mycoin_taker_fee); - assert_eq!(expected_kmd_burn_amount, kmd_burn_amount); + assert_eq!(zero_amount, mycoin_taker_fee); + assert_eq!(zero_amount, kmd_burn_amount); } #[test] @@ -2441,17 +2410,17 @@ mod lp_swap_tests { std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); let mycoin = coins::TestCoin::new("MYCOIN"); - let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "MYCOIN", &MmNumber::from(6150)) { DexFee::Standard(t) => t, - DexFee::WithBurn { .. } => { + DexFee::WithBurn { .. } | DexFee::Zero => { panic!("Wrong variant returned for MYCOIN from `dex_fee_amount_from_taker_coin`.") }, }; let testcoin = coins::TestCoin::default(); - let testcoin_taker_fee = match dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)) { + q let testcoin_taker_fee = match dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)) { DexFee::Standard(t) => t, - DexFee::WithBurn { .. } => { + DexFee::WithBurn { .. } | DexFee::Zero => { panic!("Wrong variant returned for TEST coin from `dex_fee_amount_from_taker_coin`.") }, }; 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 d0e667a752..4a174dc720 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -616,7 +616,10 @@ impl MmNumber::default() { + let is_kmd = "KMD" == recreate_ctx.maker_coin.ticker(); + let dex_fee = if is_kmd { + DexFee::Zero + } else if repr.dex_fee_burn > MmNumber::default() { DexFee::with_burn(repr.dex_fee_amount, repr.dex_fee_burn) } else { DexFee::Standard(repr.dex_fee_amount) From 450ced608c5810cbb334b357520b78926946a61b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Tue, 14 Jan 2025 16:59:28 +0100 Subject: [PATCH 2/9] fix swap unit test --- mm2src/mm2_main/src/lp_swap.rs | 4 ++-- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 27 ++++++++++++++++------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 21dd172929..b449e633a3 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -800,6 +800,7 @@ fn dex_fee_rate(base: &str, rel: &str) -> MmNumber { } else { &["KMD"] }; + if fee_discount_tickers.contains(&base) || fee_discount_tickers.contains(&rel) { // 1/777 - 10% BigRational::new(9.into(), 7770.into()).into() @@ -816,7 +817,6 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo let rate = dex_fee_rate(base, rel); let fee = trade_amount * &rate; - if &fee <= min_tx_amount { return DexFee::Standard(min_tx_amount.clone()); } @@ -2418,7 +2418,7 @@ mod lp_swap_tests { }; let testcoin = coins::TestCoin::default(); - q let testcoin_taker_fee = match dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)) { + let testcoin_taker_fee = match dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)) { DexFee::Standard(t) => t, DexFee::WithBurn { .. } | DexFee::Zero => { panic!("Wrong variant returned for TEST coin from `dex_fee_amount_from_taker_coin`.") diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 90c2f3655d..795a34de0e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -2735,12 +2735,16 @@ pub fn max_taker_vol_from_available( rel: &str, min_tx_amount: &MmNumber, ) -> Result> { - let dex_fee_rate = dex_fee_rate(base, rel); - let threshold_coef = &(&MmNumber::from(1) + &dex_fee_rate) / &dex_fee_rate; - let max_vol = if available > min_tx_amount * &threshold_coef { - available / (MmNumber::from(1) + dex_fee_rate) - } else { + let max_vol = if base == "KMD" || rel == "KMD" { &available - min_tx_amount + } else { + let dex_fee_rate = dex_fee_rate(base, rel); + let threshold_coef = &(&MmNumber::from(1) + &dex_fee_rate) / &dex_fee_rate; + if available > min_tx_amount * &threshold_coef { + available / (MmNumber::from(1) + dex_fee_rate) + } else { + &available - min_tx_amount + } }; if &max_vol <= min_tx_amount { @@ -3162,6 +3166,7 @@ mod taker_swap_tests { let min_tx_amount = MmNumber::from("0.00001"); // For these `availables` the dex_fee must be greater than min_tx_amount + // For these `availables` the dex_fee must be lesser than min_tx_amount let source = vec![ ("0.00779", false), ("0.01", false), @@ -3186,7 +3191,8 @@ mod taker_swap_tests { assert_eq!(max_taker_vol + dex_fee, available); } - // for these `availables` the dex_fee must be the same as min_tx_amount + // KMD pairs: for these `availables` the dex_fee must be 0 + // Non KMD pairs: for these `availables` the dex_fee must be the same as min_tx_amount let source = vec![ ("0.00863333333333333333333333333333333333333333333333332", true), ("0.00863333333333333333333333333333333333333333333333331", true), @@ -3207,9 +3213,14 @@ mod taker_swap_tests { max_taker_vol.to_decimal(), dex_fee.to_decimal() ); - assert_eq!(min_tx_amount, dex_fee); assert!(min_tx_amount <= max_taker_vol); - assert_eq!(max_taker_vol + dex_fee, available); + if is_kmd { + assert_eq!(MmNumber::default(), dex_fee); + assert_eq!(&max_taker_vol + &min_tx_amount, available); + } else { + assert!(min_tx_amount <= max_taker_vol); + assert_eq!(max_taker_vol + dex_fee, available); + } } // these `availables` must return an error From 0cc613adc26e5e0f4892e8acb366468b9350c63a Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Tue, 14 Jan 2025 19:22:27 +0100 Subject: [PATCH 3/9] fix docker test and dissallow sending dex fee --- mm2src/coins/utxo/utxo_common.rs | 8 ++++++++ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 15 +++++++++++++-- .../tests/docker_tests/docker_tests_inner.rs | 6 +++--- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 0c1a2ea636..d41ffe9b7b 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -3991,6 +3991,14 @@ pub async fn get_fee_to_send_taker_fee( where T: MarketCoinOps + UtxoCommonOps, { + if DexFee::Zero == dex_fee { + return Ok(TradeFee { + coin: coin.ticker().to_owned(), + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + let decimals = coin.as_ref().decimals; let outputs = generate_taker_fee_tx_outputs(decimals, &AddressHashEnum::default_address_hash(), &dex_fee)?; diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index e5e8c23c7a..715497e16a 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -739,6 +739,19 @@ impl MakerSwap { }; swap_events.push(MakerSwapEvent::MakerPaymentInstructionsReceived(instructions)); + let taker_amount = MmNumber::from(self.taker_amount.clone()); + let dex_fee = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); + + if coins::DexFee::Zero == dex_fee { + info!("Zero Dex Fee: Skipping taker fee"); + let fee_ident = TransactionIdentifier { + tx_hex: BytesJson::from(vec![]), + tx_hash: BytesJson::from(vec![]), + }; + swap_events.push(MakerSwapEvent::TakerFeeValidated(fee_ident)); + return Ok((Some(MakerSwapCommand::SendPayment), swap_events)); + } + let taker_fee = match self.taker_coin.tx_enum_from_bytes(payload.data()) { Ok(tx) => tx, Err(e) => { @@ -751,8 +764,6 @@ impl MakerSwap { let hash = taker_fee.tx_hash_as_bytes(); info!("Taker fee tx {:02x}", hash); - let taker_amount = MmNumber::from(self.taker_amount.clone()); - let dex_fee = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); let other_taker_coin_htlc_pub = self.r().other_taker_coin_htlc_pub; let taker_coin_start_block = self.r().data.taker_coin_start_block; 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..1bc9255da7 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -2167,9 +2167,9 @@ fn test_get_max_taker_vol_with_kmd() { .unwrap(); assert!(rc.0.is_success(), "!max_taker_vol: {}", rc.1); let json: Json = serde_json::from_str(&rc.1).unwrap(); - // the result of equation x + x * 9 / 7770 + 0.0002 = 1 - assert_eq!(json["result"]["numer"], Json::from("1294741")); - assert_eq!(json["result"]["denom"], Json::from("1296500")); + // the result of equation x + 0.0002 = 1 (no dex fee included) + assert_eq!(json["result"]["numer"], Json::from("99989")); + assert_eq!(json["result"]["denom"], Json::from("100000")); let rc = block_on(mm_alice.rpc(&json!({ "userpass": mm_alice.userpass, From 49f08ac8f0eef6cb4556a510d1cac3f77921aadf Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 15 Jan 2025 02:09:56 +0100 Subject: [PATCH 4/9] fix gen_taker_payment_spend_preimage --- mm2src/coins/utxo/utxo_common.rs | 54 +++++++++++++++----------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index d41ffe9b7b..526bbe0411 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1243,50 +1243,48 @@ async fn gen_taker_payment_spend_preimage( args: &GenTakerPaymentSpendArgs<'_, T>, n_time: NTimeSetting, ) -> GenPreimageResInner { - let mut outputs: Vec = Vec::with_capacity(3); - match args.dex_fee { - // KMD Zero DexFee: Don't include dex fee output for KMD ticker. - DexFee::Zero => (), - DexFee::WithBurn { .. } => { - if let DexFee::WithBurn { .. } = args.dex_fee { + let outputs = match args.dex_fee { + DexFee::Zero => vec![], + dex_fee => { + let dex_fee_address = address_from_raw_pubkey( + args.dex_fee_pub, + coin.as_ref().conf.address_prefixes.clone(), + coin.as_ref().conf.checksum_type, + coin.as_ref().conf.bech32_hrp.clone(), + coin.addr_format().clone(), + ) + .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive dex_fee_address: {}", e)))?; + + let mut fee_outputs = + generate_taker_fee_tx_outputs(coin.as_ref().decimals, dex_fee_address.hash(), dex_fee)?; + + if let DexFee::WithBurn { .. } = dex_fee { let script = output_script(args.maker_address).map_to_mm(|e| { TxGenError::Other(format!( "Couldn't generate output script for maker address {}, error {}", args.maker_address, e )) })?; + let tx_fee = coin .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) .await?; - let maker_value = args + + let total_fee_value: u64 = fee_outputs.iter().map(|out| out.value).sum(); + let first_output_value = args .taker_tx .first_output() .map_to_mm(|e| TxGenError::PrevTxIsNotValid(e.to_string()))? - .value - - outputs[0].value - - outputs[1].value - - tx_fee; + .value; - outputs.push(TransactionOutput { + let maker_value = first_output_value - total_fee_value - tx_fee; + fee_outputs.push(TransactionOutput { value: maker_value, script_pubkey: script.to_bytes(), - }) + }); } - }, - DexFee::Standard(_) => { - let dex_fee_address = address_from_raw_pubkey( - args.dex_fee_pub, - coin.as_ref().conf.address_prefixes.clone(), - coin.as_ref().conf.checksum_type, - coin.as_ref().conf.bech32_hrp.clone(), - coin.addr_format().clone(), - ) - .map_to_mm(|e| TxGenError::AddressDerivation(format!("Failed to derive dex_fee_address: {}", e)))?; - outputs.extend_from_slice(&generate_taker_fee_tx_outputs( - coin.as_ref().decimals, - dex_fee_address.hash(), - args.dex_fee, - )?); + + fee_outputs }, }; From 1a8539adda32e8f334f9d7672e455f67f7c64884 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 15 Jan 2025 02:43:34 +0100 Subject: [PATCH 5/9] check for zero dex fee in other protocol --- mm2src/coins/qrc20.rs | 8 ++++++++ mm2src/coins/utxo/slp.rs | 9 +++++++++ mm2src/coins/z_coin.rs | 10 +++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index df14fea09a..ebe365feb5 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1418,6 +1418,14 @@ impl MmCoin for Qrc20Coin { dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { + if DexFee::Zero == dex_fee_amount { + return Ok(TradeFee { + coin: self.platform.clone(), + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + let amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.utxo.decimals)?; // pass the dummy params diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index cbc7780a34..104c41a4fe 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1844,6 +1844,14 @@ impl MmCoin for SlpToken { dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { + if DexFee::Zero == dex_fee_amount { + return Ok(TradeFee { + coin: self.platform_coin.ticker().into(), + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + let slp_amount = sat_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.decimals())?; // can use dummy P2PKH script_pubkey here let script_pubkey = ScriptBuilder::build_p2pkh(&H160::default().into()).into(); @@ -1861,6 +1869,7 @@ impl MmCoin for SlpToken { &stage, ) .await?; + Ok(TradeFee { coin: self.platform_coin.ticker().into(), amount: fee.into(), diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index b8c7c8c944..8bb8687874 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1750,9 +1750,17 @@ impl MmCoin for ZCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: DexFee, + dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { + if DexFee::Zero == dex_fee_amount { + return Ok(TradeFee { + coin: self.ticker().to_owned(), + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + Ok(TradeFee { coin: self.ticker().to_owned(), amount: self.get_one_kbyte_tx_fee().await?.into(), From dc45a358e63164b1e840f3eb24fc76601358875b Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 15 Jan 2025 03:08:46 +0100 Subject: [PATCH 6/9] eth and tendermint --- mm2src/coins/eth.rs | 17 ++++++++++++++--- mm2src/coins/tendermint/tendermint_coin.rs | 8 ++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b8db8c7c12..a3d461772c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5891,11 +5891,24 @@ impl MmCoin for EthCoin { dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let dex_fee_amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.decimals)?; + let fee_coin = match &self.coin_type { + EthCoinType::Eth => self.ticker.to_owned(), + EthCoinType::Erc20 { platform, .. } => platform.to_owned(), + EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), + }; + if DexFee::Zero == dex_fee_amount { + return Ok(TradeFee { + coin: fee_coin, + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + let dex_fee_amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.decimals)?; // pass the dummy params let to_addr = addr_from_raw_pubkey(&DEX_FEE_ADDR_RAW_PUBKEY) .expect("addr_from_raw_pubkey should never fail with DEX_FEE_ADDR_RAW_PUBKEY"); + let my_address = self.derivation_method.single_addr_or_err().await?; let (eth_value, data, call_addr, fee_coin) = match &self.coin_type { EthCoinType::Eth => (dex_fee_amount, Vec::new(), &to_addr, &self.ticker), EthCoinType::Erc20 { platform, token_addr } => { @@ -5905,8 +5918,6 @@ impl MmCoin for EthCoin { }, EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - - let my_address = self.derivation_method.single_addr_or_err().await?; let fee_policy_for_estimate = get_swap_fee_policy_for_estimate(self.get_swap_transaction_fee_policy()); let pay_for_gas_option = self.get_swap_pay_for_gas_option(fee_policy_for_estimate).await?; let pay_for_gas_option = increase_gas_price_by_stage(pay_for_gas_option, &stage); diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 9573e6de4b..231747f765 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2478,6 +2478,14 @@ impl MmCoin for TendermintCoin { dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { + if DexFee::Zero == dex_fee_amount { + return Ok(TradeFee { + coin: self.ticker.clone(), + amount: MmNumber::default(), + paid_from_trading_vol: false, + }); + } + self.get_fee_to_send_taker_fee_for_denom(self.ticker.clone(), self.denom.clone(), self.decimals, dex_fee_amount) .await } From f62a0de73d83b340fcda2e1a46d429b5297d569d Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Thu, 16 Jan 2025 15:54:31 +0100 Subject: [PATCH 7/9] check for KMD ticker in swap_v2 --- mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs | 2 +- mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) 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 4a174dc720..61aba78799 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -616,7 +616,7 @@ impl MmNumber::default() { 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 29f3d07277..f75a39c891 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -732,7 +732,10 @@ impl MmNumber::default() { + let is_kmd = "KMD" == recreate_ctx.maker_coin.ticker() || "KMD" == recreate_ctx.taker_coin.ticker(); + let dex_fee = if is_kmd { + DexFee::Zero + } else if repr.dex_fee_burn > MmNumber::default() { DexFee::with_burn(repr.dex_fee_amount, repr.dex_fee_burn) } else { DexFee::Standard(repr.dex_fee_amount) From 2d6ae32ac4b6996c2212002c02148d6daa87c2c4 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 20 Jan 2025 15:55:38 +0100 Subject: [PATCH 8/9] save dev state --- mm2src/coins/eth.rs | 2 +- mm2src/coins/lp_coins.rs | 3 +++ mm2src/coins/qrc20.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 2 +- mm2src/coins/utxo/slp.rs | 2 +- mm2src/coins/utxo/utxo_common.rs | 4 ++-- mm2src/coins/z_coin.rs | 2 +- mm2src/mm2_main/src/lp_swap.rs | 2 +- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 8 ++------ mm2src/mm2_main/src/lp_swap/taker_swap.rs | 7 +++++++ 10 files changed, 20 insertions(+), 14 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a3d461772c..ea00074814 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5896,7 +5896,7 @@ impl MmCoin for EthCoin { EthCoinType::Erc20 { platform, .. } => platform.to_owned(), EthCoinType::Nft { .. } => return MmError::err(TradePreimageError::NftProtocolNotSupported), }; - if DexFee::Zero == dex_fee_amount { + if dex_fee_amount.no_fee() { return Ok(TradeFee { coin: fee_coin, amount: MmNumber::default(), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index cff077a416..2b3fd9e621 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3680,6 +3680,9 @@ impl DexFee { Ok(None) } } + + /// Check and return true if DexFee is not required to trade otherwise return false. + pub fn no_fee(&self) -> bool { matches!(self, Self::Zero) } } pub struct CoinsContext { diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index ebe365feb5..e25574741f 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1418,7 +1418,7 @@ impl MmCoin for Qrc20Coin { dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - if DexFee::Zero == dex_fee_amount { + if dex_fee_amount.no_fee() { return Ok(TradeFee { coin: self.platform.clone(), amount: MmNumber::default(), diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 231747f765..469aae4ed1 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -2478,7 +2478,7 @@ impl MmCoin for TendermintCoin { dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { - if DexFee::Zero == dex_fee_amount { + if dex_fee_amount.no_fee() { return Ok(TradeFee { coin: self.ticker.clone(), amount: MmNumber::default(), diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 104c41a4fe..60d55a1c2c 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1844,7 +1844,7 @@ impl MmCoin for SlpToken { dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - if DexFee::Zero == dex_fee_amount { + if dex_fee_amount.no_fee() { return Ok(TradeFee { coin: self.platform_coin.ticker().into(), amount: MmNumber::default(), diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 526bbe0411..f7c4b1783f 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1498,7 +1498,7 @@ fn generate_taker_fee_tx_outputs( dex_fee: &DexFee, ) -> Result, MmError> { // Don't add outputs for Zero DexFee. - if let DexFee::Zero = dex_fee { + if dex_fee.no_fee() { return Ok(vec![]); } @@ -3989,7 +3989,7 @@ pub async fn get_fee_to_send_taker_fee( where T: MarketCoinOps + UtxoCommonOps, { - if DexFee::Zero == dex_fee { + if dex_fee.no_fee() { return Ok(TradeFee { coin: coin.ticker().to_owned(), amount: MmNumber::default(), diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 8bb8687874..39c6ace6e2 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1753,7 +1753,7 @@ impl MmCoin for ZCoin { dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { - if DexFee::Zero == dex_fee_amount { + if dex_fee_amount.no_fee() { return Ok(TradeFee { coin: self.ticker().to_owned(), amount: MmNumber::default(), diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index b449e633a3..1bdff48d5e 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -969,7 +969,7 @@ impl SwapTxDataMsg { } } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct TransactionIdentifier { /// Raw bytes of signed transaction in hexadecimal string, this should be sent as is to send_raw_transaction RPC to broadcast the transaction. /// Some payments like lightning payments don't have a tx_hex, for such payments tx_hex will be equal to tx_hash. diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 715497e16a..2da58e84af 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -742,13 +742,9 @@ impl MakerSwap { let taker_amount = MmNumber::from(self.taker_amount.clone()); let dex_fee = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); - if coins::DexFee::Zero == dex_fee { + if dex_fee.no_fee() { info!("Zero Dex Fee: Skipping taker fee"); - let fee_ident = TransactionIdentifier { - tx_hex: BytesJson::from(vec![]), - tx_hash: BytesJson::from(vec![]), - }; - swap_events.push(MakerSwapEvent::TakerFeeValidated(fee_ident)); + swap_events.push(MakerSwapEvent::TakerFeeValidated(TransactionIdentifier::default())); return Ok((Some(MakerSwapCommand::SendPayment), swap_events)); } diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 795a34de0e..4c2821b92f 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1304,6 +1304,13 @@ impl TakerSwap { let fee_amount = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &self.taker_amount); + if fee_amount.no_fee() { + debug!("zero-dex-fee: skipping send taker fee"); + return Ok((Some(TakerSwapCommand::WaitForMakerPayment), vec![ + TakerSwapEvent::TakerFeeSent(TransactionIdentifier::default()), + ])); + } + let fee_tx = self .taker_coin .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, self.uuid.as_bytes(), expire_at) From a6cb780192b71d068e4879b9b386170e09d12c39 Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Mon, 20 Jan 2025 16:05:49 +0100 Subject: [PATCH 9/9] update taker fee validated log info --- mm2src/mm2_main/src/lp_swap/maker_swap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 2da58e84af..bb51003089 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -743,7 +743,7 @@ impl MakerSwap { let dex_fee = dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); if dex_fee.no_fee() { - info!("Zero Dex Fee: Skipping taker fee"); + info!("zero-dex-fee: skipping taker fee validation"); swap_events.push(MakerSwapEvent::TakerFeeValidated(TransactionIdentifier::default())); return Ok((Some(MakerSwapCommand::SendPayment), swap_events)); }