From d4c9b9f71e21a27758ab653781f666a958a99de6 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 14 Nov 2023 23:19:47 +0300 Subject: [PATCH 01/12] burn out the 25% of the KMD taker fees Signed-off-by: onur-ozkan --- mm2src/coins/utxo/utxo_common.rs | 34 ++++++++++++++++++++---- mm2src/mm2_bitcoin/script/src/builder.rs | 2 +- mm2src/mm2_main/src/lp_swap.rs | 9 ++++++- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 82e33e4b36..a0624c7cda 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1680,12 +1680,36 @@ where coin.as_ref().conf.bech32_hrp.clone(), coin.addr_format().clone(), )); - let amount = try_tx_fus!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); - let output = TransactionOutput { - value: amount, - script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), + + let fee_amount = try_tx_fus!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); + + let outputs = if coin.as_ref().conf.ticker != "KMD" { + vec![TransactionOutput { + value: fee_amount, + script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), + }] + } else { + // For KMD, the dex fee is calculated with a -25% adjustment because this percentage + // will be burned; and that is what we are doing here. + let burn_amount_dec = (&amount / BigDecimal::from(MmNumber::from("0.75"))) - &amount; + let burn_amount = try_tx_fus!(sat_from_big_decimal(&burn_amount_dec, coin.as_ref().decimals)); + + vec![ + TransactionOutput { + value: fee_amount, + script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), + }, + TransactionOutput { + value: burn_amount, + script_pubkey: Builder::default() + .push_opcode(Opcode::OP_RETURN) + .into_script() + .to_bytes(), + }, + ] }; - send_outputs_from_my_address(coin, vec![output]) + + send_outputs_from_my_address(coin, outputs) } pub fn send_maker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut diff --git a/mm2src/mm2_bitcoin/script/src/builder.rs b/mm2src/mm2_bitcoin/script/src/builder.rs index 4dfc3e4d07..281cc0f185 100644 --- a/mm2src/mm2_bitcoin/script/src/builder.rs +++ b/mm2src/mm2_bitcoin/script/src/builder.rs @@ -78,7 +78,7 @@ impl Builder { pub fn push_bytes(mut self, bytes: &[u8]) -> Self { let len = bytes.len(); if !(1..=75).contains(&len) { - panic!("Canot push {} bytes", len); + panic!("Can not push {} bytes", len); } let opcode: Opcode = Opcode::from_u8(((Opcode::OP_PUSHBYTES_1 as usize) + len - 1) as u8) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 4f0a704f16..b1e55f45f0 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -755,7 +755,14 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo /// Calculates DEX fee with a threshold based on min tx amount of the taker coin. pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); - dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount) + let fee = dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount); + + // Drop the fee by 25%, which will be burned during the taker fee payment. + if taker_coin.ticker() == "KMD" { + fee * MmNumber::from("0.75") + } else { + fee + } } #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] From 8d3a2270d8e807aa385328800544b8b4a9d45dfa Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 15 Nov 2023 00:13:14 +0300 Subject: [PATCH 02/12] test kmd taker fee calculation Signed-off-by: onur-ozkan --- mm2src/coins/test_coin.rs | 2 +- mm2src/coins/utxo/utxo_common.rs | 57 ++++++++++++++++++++++++++++---- mm2src/mm2_main/src/lp_swap.rs | 26 +++++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index d21b438f58..99ced18e69 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -97,7 +97,7 @@ impl MarketCoinOps for TestCoin { fn display_priv_key(&self) -> Result { unimplemented!() } - fn min_tx_amount(&self) -> BigDecimal { unimplemented!() } + fn min_tx_amount(&self) -> BigDecimal { Default::default() } fn min_trading_vol(&self) -> MmNumber { MmNumber::from("0.00777") } } diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index a0624c7cda..ddf7a0e050 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1681,23 +1681,39 @@ where coin.addr_format().clone(), )); - let fee_amount = try_tx_fus!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); + let outputs = try_tx_fus!(generate_taker_fee_tx_outputs( + &coin.as_ref().conf.ticker, + coin.as_ref().decimals, + &address.hash, + amount + )); + + send_outputs_from_my_address(coin, outputs) +} - let outputs = if coin.as_ref().conf.ticker != "KMD" { +fn generate_taker_fee_tx_outputs( + ticker: &str, + decimals: u8, + address_hash: &AddressHashEnum, + amount: BigDecimal, +) -> Result, MmError> { + let fee_amount = sat_from_big_decimal(&amount, decimals)?; + + let outputs = if ticker != "KMD" { vec![TransactionOutput { value: fee_amount, - script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), + script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), }] } else { // For KMD, the dex fee is calculated with a -25% adjustment because this percentage // will be burned; and that is what we are doing here. let burn_amount_dec = (&amount / BigDecimal::from(MmNumber::from("0.75"))) - &amount; - let burn_amount = try_tx_fus!(sat_from_big_decimal(&burn_amount_dec, coin.as_ref().decimals)); + let burn_amount = sat_from_big_decimal(&burn_amount_dec, decimals)?; vec![ TransactionOutput { value: fee_amount, - script_pubkey: Builder::build_p2pkh(&address.hash).to_bytes(), + script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), }, TransactionOutput { value: burn_amount, @@ -1709,7 +1725,7 @@ where ] }; - send_outputs_from_my_address(coin, outputs) + Ok(outputs) } pub fn send_maker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut @@ -5081,3 +5097,32 @@ fn test_tx_v_size() { let v_size = tx_size_in_v_bytes(&UtxoAddressFormat::Segwit, &tx); assert_eq!(v_size, 209) } + +#[test] +fn test_generate_taker_fee_tx_outputs() { + let amount = BigDecimal::from(6150); + let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); + + let outputs = generate_taker_fee_tx_outputs("TEST", 8, &AddressHashEnum::default_address_hash(), amount).unwrap(); + + assert_eq!(outputs.len(), 1); + + assert_eq!(outputs[0].value, fee_amount); +} + +#[test] +fn test_generate_taker_fee_tx_outputs_for_kmd() { + let amount = BigDecimal::from(6150); + let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); + + let outputs = + generate_taker_fee_tx_outputs("KMD", 8, &AddressHashEnum::default_address_hash(), amount.clone()).unwrap(); + + assert_eq!(outputs.len(), 2); + + assert_eq!(outputs[0].value, fee_amount); + + let burn_amount = + sat_from_big_decimal(&((&amount / &BigDecimal::from(MmNumber::from("0.75"))) - &amount), 8).unwrap(); + assert_eq!(outputs[1].value, burn_amount); +} diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index b1e55f45f0..f423b79fe4 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -2189,4 +2189,30 @@ mod lp_swap_tests { let _: SavedSwap = json::from_str(include_str!("for_tests/iris_nimda_rick_taker_swap.json")).unwrap(); let _: SavedSwap = json::from_str(include_str!("for_tests/iris_nimda_rick_maker_swap.json")).unwrap(); } + + #[test] + fn test_kmd_taker_dex_fee_calculation() { + std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); + + let kmd = coins::TestCoin::new("KMD"); + let kmd_taker_fee = dex_fee_amount_from_taker_coin(&kmd, "", &MmNumber::from(6150)); + + let mycoin = coins::TestCoin::new("MYCOIN"); + let mycoin_taker_fee = dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)); + + assert_eq!(kmd_taker_fee / MmNumber::from("0.75"), mycoin_taker_fee); + } + + #[test] + fn test_dex_fee_amount_from_taker_coin_discount() { + std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); + + let mycoin = coins::TestCoin::new("MYCOIN"); + let mycoin_taker_fee = dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)); + + let testcoin = coins::TestCoin::default(); + let testcoin_taker_fee = dex_fee_amount_from_taker_coin(&testcoin, "", &MmNumber::from(6150)); + + assert_eq!(testcoin_taker_fee * MmNumber::from("0.90"), mycoin_taker_fee); + } } From 920e9ebd467801b66e6de2928bca47b5996fe7ad Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 15 Nov 2023 18:00:09 +0300 Subject: [PATCH 03/12] validate burn output from maker side Signed-off-by: onur-ozkan --- mm2src/coins/utxo/utxo_common.rs | 104 ++++++++++++++++++++++--------- mm2src/mm2_main/src/lp_swap.rs | 21 ++++--- 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index ddf7a0e050..70a72f31bf 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1668,6 +1668,13 @@ pub async fn sign_and_broadcast_taker_payment_spend( Ok(final_tx.into()) } +fn should_taker_burn_kmd(ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { + ticker == "DOC" && + // Burn percentage is only calculated for the dex fee if it's still + // higher than minimum transaction amount after the cut. + fee_amount > &(min_tx_amount / &BigDecimal::from_str("0.75").unwrap()) +} + pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], amount: BigDecimal) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps, @@ -1685,7 +1692,8 @@ where &coin.as_ref().conf.ticker, coin.as_ref().decimals, &address.hash, - amount + amount, + min_tx_amount(coin.as_ref()) )); send_outputs_from_my_address(coin, outputs) @@ -1696,34 +1704,24 @@ fn generate_taker_fee_tx_outputs( decimals: u8, address_hash: &AddressHashEnum, amount: BigDecimal, + min_tx_amount: BigDecimal, ) -> Result, MmError> { let fee_amount = sat_from_big_decimal(&amount, decimals)?; - let outputs = if ticker != "KMD" { - vec![TransactionOutput { - value: fee_amount, - script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), - }] - } else { - // For KMD, the dex fee is calculated with a -25% adjustment because this percentage - // will be burned; and that is what we are doing here. - let burn_amount_dec = (&amount / BigDecimal::from(MmNumber::from("0.75"))) - &amount; + let mut outputs = vec![TransactionOutput { + value: fee_amount, + script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), + }]; + + if should_taker_burn_kmd(ticker, &amount, &min_tx_amount) { + let burn_amount_dec = (&amount / BigDecimal::from_str("0.75").unwrap()) - &amount; let burn_amount = sat_from_big_decimal(&burn_amount_dec, decimals)?; - vec![ - TransactionOutput { - value: fee_amount, - script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), - }, - TransactionOutput { - value: burn_amount, - script_pubkey: Builder::default() - .push_opcode(Opcode::OP_RETURN) - .into_script() - .to_bytes(), - }, - ] - }; + outputs.push(TransactionOutput { + value: burn_amount, + script_pubkey: Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(), + }); + } Ok(outputs) } @@ -2395,7 +2393,7 @@ pub fn validate_fee( } let fut = async move { - let amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; + let fee_amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; let tx_from_rpc = coin .as_ref() .rpc_client @@ -2431,7 +2429,7 @@ pub fn validate_fee( INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey ))); } - if out.value < amount { + if out.value < fee_amount { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx output value is less than expected {:?} {:?}", out.value, amount @@ -2445,6 +2443,38 @@ pub fn validate_fee( ))) }, } + + if should_taker_burn_kmd(&coin.as_ref().conf.ticker, &amount, &min_tx_amount(coin.as_ref())) { + match tx.outputs.get(output_index + 1) { + Some(out) => { + let expected_script_pubkey = Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(); + + if out.script_pubkey != expected_script_pubkey { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Provided burn tx output script_pubkey doesn't match expected {:?} {:?}", + INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey + ))); + } + + let burn_amount_dec = (&amount / BigDecimal::from_str("0.75").unwrap()) - &amount; + let burn_amount = sat_from_big_decimal(&burn_amount_dec, coin.as_ref().decimals)?; + + if out.value < burn_amount { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided burn tx output value is less than expected {:?} {:?}", + out.value, amount + ))); + } + }, + None => { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided burn tx output {:?} does not have output {}", + tx, output_index + ))) + }, + } + } + Ok(()) }; Box::new(fut.boxed().compat()) @@ -5103,7 +5133,14 @@ fn test_generate_taker_fee_tx_outputs() { let amount = BigDecimal::from(6150); let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); - let outputs = generate_taker_fee_tx_outputs("TEST", 8, &AddressHashEnum::default_address_hash(), amount).unwrap(); + let outputs = generate_taker_fee_tx_outputs( + "TEST", + 8, + &AddressHashEnum::default_address_hash(), + amount, + Default::default(), + ) + .unwrap(); assert_eq!(outputs.len(), 1); @@ -5115,14 +5152,19 @@ fn test_generate_taker_fee_tx_outputs_for_kmd() { let amount = BigDecimal::from(6150); let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); - let outputs = - generate_taker_fee_tx_outputs("KMD", 8, &AddressHashEnum::default_address_hash(), amount.clone()).unwrap(); + let outputs = generate_taker_fee_tx_outputs( + "KMD", + 8, + &AddressHashEnum::default_address_hash(), + amount.clone(), + Default::default(), + ) + .unwrap(); assert_eq!(outputs.len(), 2); assert_eq!(outputs[0].value, fee_amount); - let burn_amount = - sat_from_big_decimal(&((&amount / &BigDecimal::from(MmNumber::from("0.75"))) - &amount), 8).unwrap(); + let burn_amount = sat_from_big_decimal(&((&amount / &BigDecimal::from_str("0.75").unwrap()) - &amount), 8).unwrap(); assert_eq!(outputs[1].value, burn_amount); } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index f423b79fe4..f2b069ea41 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -744,7 +744,17 @@ 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) -> MmNumber { let rate = dex_fee_rate(base, rel); - let fee_amount = trade_amount * &rate; + + let fee_amount = if base == "DOC" { + // 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. + (trade_amount * &rate) * MmNumber::from("0.75") + } else { + trade_amount * &rate + }; + if &fee_amount < min_tx_amount { min_tx_amount.clone() } else { @@ -755,14 +765,7 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo /// Calculates DEX fee with a threshold based on min tx amount of the taker coin. pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); - let fee = dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount); - - // Drop the fee by 25%, which will be burned during the taker fee payment. - if taker_coin.ticker() == "KMD" { - fee * MmNumber::from("0.75") - } else { - fee - } + dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount) } #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] From c1bb28e44f131b74a5b90322fdc35b196acb1d44 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 15 Nov 2023 18:08:17 +0300 Subject: [PATCH 04/12] leave a TODO note Signed-off-by: onur-ozkan --- mm2src/coins/utxo/utxo_common.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 70a72f31bf..1383ac7e22 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1668,6 +1668,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( Ok(final_tx.into()) } +// TODO: add unit tests fn should_taker_burn_kmd(ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { ticker == "DOC" && // Burn percentage is only calculated for the dex fee if it's still From e665bec68e4122388b59440c2470dd25cb582a69 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 15 Nov 2023 19:11:14 +0300 Subject: [PATCH 05/12] replace "DOC" tickers with "KMD" for burn impl Signed-off-by: onur-ozkan --- mm2src/coins/utxo/utxo_common.rs | 4 ++-- mm2src/mm2_main/src/lp_swap.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 1383ac7e22..3297cf543a 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1669,8 +1669,8 @@ pub async fn sign_and_broadcast_taker_payment_spend( } // TODO: add unit tests -fn should_taker_burn_kmd(ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { - ticker == "DOC" && +fn should_taker_burn_kmd(taker_ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { + taker_ticker == "KMD" && // Burn percentage is only calculated for the dex fee if it's still // higher than minimum transaction amount after the cut. fee_amount > &(min_tx_amount / &BigDecimal::from_str("0.75").unwrap()) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index f2b069ea41..381df9df53 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -745,7 +745,7 @@ 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) -> MmNumber { let rate = dex_fee_rate(base, rel); - let fee_amount = if base == "DOC" { + let fee_amount = 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 From 38ae93c6143e19fa58a28963eb025678aa9b3580 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Wed, 15 Nov 2023 23:44:54 +0300 Subject: [PATCH 06/12] add unit test for `should_taker_burn_kmd` Signed-off-by: onur-ozkan --- mm2src/coins/utxo/utxo_common.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 3297cf543a..ec050a35f9 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -1668,7 +1668,6 @@ pub async fn sign_and_broadcast_taker_payment_spend( Ok(final_tx.into()) } -// TODO: add unit tests fn should_taker_burn_kmd(taker_ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { taker_ticker == "KMD" && // Burn percentage is only calculated for the dex fee if it's still @@ -5169,3 +5168,27 @@ fn test_generate_taker_fee_tx_outputs_for_kmd() { let burn_amount = sat_from_big_decimal(&((&amount / &BigDecimal::from_str("0.75").unwrap()) - &amount), 8).unwrap(); assert_eq!(outputs[1].value, burn_amount); } + +#[test] +fn test_should_taker_burn_kmd() { + // When taker_ticker is "KMD" and fee_amount is enough to allow burning. + assert!(should_taker_burn_kmd( + "KMD", + &BigDecimal::from_str("0.02").unwrap(), + &BigDecimal::from_str("0.00777").unwrap() + )); + + // When taker_ticker is not "KMD". + assert!(!should_taker_burn_kmd( + "BTC", + &BigDecimal::from_str("0.02").unwrap(), + &BigDecimal::from_str("0.00777").unwrap() + )); + + // When taker_ticker is "KMD" but fee_amount is not enough to allow burning. + assert!(!should_taker_burn_kmd( + "KMD", + &BigDecimal::from_str("0.01").unwrap(), + &BigDecimal::from_str("0.00777").unwrap() + )); +} From 7fedd40898cc159c2eb6e71f539525b5d7b98283 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Thu, 16 Nov 2023 20:51:47 +0300 Subject: [PATCH 07/12] fix dex fee calculation bug Signed-off-by: onur-ozkan --- mm2src/mm2_main/src/lp_swap.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 381df9df53..21147f3e3d 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -750,7 +750,13 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo // // This cut will be dropped before return if the final amount is less than // the minimum transaction amount. - (trade_amount * &rate) * MmNumber::from("0.75") + let new_dex_fee = (trade_amount * &rate) * MmNumber::from("0.75"); + + if &new_dex_fee >= min_tx_amount { + new_dex_fee + } else { + min_tx_amount.clone() + } } else { trade_amount * &rate }; @@ -1667,7 +1673,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 * (9, 7770).into(); + let expected_fee = amount * (9, 7770).into() * MmNumber::from("0.75"); assert_eq!(expected_fee, actual_fee); let base = "BTC"; From 8bdfb5a8fd378c1b557d9311df1a5872ee0da8cc Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Sat, 18 Nov 2023 12:49:54 +0300 Subject: [PATCH 08/12] improve dex fee handling in whole mm2 Signed-off-by: onur-ozkan --- mm2src/coins/eth.rs | 13 +-- mm2src/coins/eth/eth_tests.rs | 10 +-- mm2src/coins/lightning.rs | 6 +- mm2src/coins/lp_coins.rs | 70 ++++++++++++++- mm2src/coins/qrc20.rs | 34 +++---- mm2src/coins/qrc20/qrc20_tests.rs | 18 ++-- mm2src/coins/solana.rs | 26 +++--- mm2src/coins/solana/spl.rs | 4 +- mm2src/coins/tendermint/tendermint_coin.rs | 28 +++--- mm2src/coins/tendermint/tendermint_token.rs | 15 ++-- mm2src/coins/test_coin.rs | 4 +- mm2src/coins/utxo/bch.rs | 8 +- mm2src/coins/utxo/qtum.rs | 24 ++--- mm2src/coins/utxo/slp.rs | 14 +-- mm2src/coins/utxo/utxo_common.rs | 88 +++++-------------- mm2src/coins/utxo/utxo_standard.rs | 8 +- mm2src/coins/utxo/utxo_tests.rs | 8 +- mm2src/coins/z_coin.rs | 40 ++++----- mm2src/coins/z_coin/z_coin_native_tests.rs | 10 ++- mm2src/mm2_main/src/lp_ordermatch.rs | 10 ++- mm2src/mm2_main/src/lp_swap.rs | 76 ++++++++++------ mm2src/mm2_main/src/lp_swap/maker_swap.rs | 5 +- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 23 ++--- .../tests/docker_tests/qrc20_tests.rs | 9 +- .../tests/docker_tests/swap_watcher_tests.rs | 10 ++- 25 files changed, 320 insertions(+), 241 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index c69ce6981f..7afb9c5472 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -23,7 +23,7 @@ use super::eth::Action::{Call, Create}; use crate::lp_price::get_base_price_in_rel; use crate::nft::nft_structs::{ContractType, ConvertChain, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; -use crate::{ValidateWatcherSpendInput, WatcherSpendType}; +use crate::{DexFee, ValidateWatcherSpendInput, WatcherSpendType}; use async_trait::async_trait; use bitcrypto::{dhash160, keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry, RetryOnError}; @@ -1058,12 +1058,15 @@ impl Deref for EthCoin { #[async_trait] impl SwapOps for EthCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let address = try_tx_fus!(addr_from_raw_pubkey(fee_addr)); Box::new( - self.send_to_address(address, try_tx_fus!(wei_from_big_decimal(&amount, self.decimals))) - .map(TransactionEnum::from), + self.send_to_address( + address, + try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.decimals)), + ) + .map(TransactionEnum::from), ) } @@ -1118,7 +1121,7 @@ impl SwapOps for EthCoin { fee_tx_hash: &tx.hash, expected_sender: validate_fee_args.expected_sender, fee_addr: validate_fee_args.fee_addr, - amount: validate_fee_args.amount, + amount: &validate_fee_args.dex_fee.fee_amount().into(), min_block_number: validate_fee_args.min_block_number, uuid: validate_fee_args.uuid, }) diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index dfeff254f9..5314321fb2 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1,5 +1,5 @@ use super::*; -use crate::IguanaPrivKey; +use crate::{DexFee, IguanaPrivKey}; use common::{block_on, now_sec, wait_until_sec}; use crypto::privkey::key_pair_from_seed; use ethkey::{Generator, Random}; @@ -1167,7 +1167,7 @@ fn validate_dex_fee_invalid_sender_eth() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -1201,7 +1201,7 @@ fn validate_dex_fee_invalid_sender_erc() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -1239,7 +1239,7 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { fee_tx: &tx, expected_sender: &compressed_public, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 11784793, uuid: &[], }; @@ -1276,7 +1276,7 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { fee_tx: &tx, expected_sender: &compressed_public, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 11823975, uuid: &[], }; diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 74d5619c5d..812634f107 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -15,8 +15,8 @@ use crate::lightning::ln_utils::{filter_channels, pay_invoice_with_max_total_clt use crate::utxo::rpc_clients::UtxoRpcClientEnum; use crate::utxo::utxo_common::{big_decimal_from_sat, big_decimal_from_sat_unsigned}; use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -612,7 +612,7 @@ impl LightningCoin { #[async_trait] impl SwapOps for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning - fn send_taker_fee(&self, _fee_addr: &[u8], _amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, _fee_addr: &[u8], _dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let fut = async move { Ok(TransactionEnum::LightningPayment(PaymentHash([1; 32]))) }; Box::new(fut.boxed().compat()) } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 74303ba226..ad874ebbfc 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -820,7 +820,7 @@ pub struct ValidateFeeArgs<'a> { pub fee_tx: &'a TransactionEnum, pub expected_sender: &'a [u8], pub fee_addr: &'a [u8], - pub amount: &'a BigDecimal, + pub dex_fee: &'a DexFee, pub min_block_number: u64, pub uuid: &'a [u8], } @@ -903,7 +903,7 @@ pub enum WatcherRewardError { /// Swap operations (mostly based on the Hash/Time locked transactions implemented by coin wallets). #[async_trait] pub trait SwapOps { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut; + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut; fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; @@ -2951,6 +2951,72 @@ impl MmCoinStruct { } } +/// Represents the different types of DEX fees. +#[derive(Debug, PartialEq)] +pub enum DexFee { + /// Standard dex fee which will be sent to the dex fee address + Standard(MmNumber), + /// Dex fee with the burn amount. + /// - `fee_amount` goes to the dex fee address. + /// - `burn_amount` will be added as `OP_RETURN` output in the dex fee transaction. + WithBurn { + fee_amount: MmNumber, + burn_amount: MmNumber, + }, +} + +impl DexFee { + /// Creates a new `DexFee` with burn amounts. + pub fn with_burn(fee_amount: MmNumber, burn_amount: MmNumber) -> DexFee { + DexFee::WithBurn { + fee_amount, + burn_amount, + } + } + + /// Gets the fee amount associated with the dex fee. + pub fn fee_amount(&self) -> MmNumber { + match self { + DexFee::Standard(t) => t.clone(), + DexFee::WithBurn { fee_amount, .. } => fee_amount.clone(), + } + } + + /// Gets the burn amount associated with the dex fee, if applicable. + pub fn burn_amount(&self) -> Option { + match self { + DexFee::Standard(_) => None, + DexFee::WithBurn { burn_amount, .. } => Some(burn_amount.clone()), + } + } + + /// Calculates the total spend amount, considering both the fee and burn amounts. + pub fn total_spend_amount(&self) -> MmNumber { + match self { + DexFee::Standard(t) => t.clone(), + DexFee::WithBurn { + fee_amount, + burn_amount, + } => fee_amount + burn_amount, + } + } + + /// Converts the fee amount to micro-units based on the specified decimal places. + pub fn fee_uamount(&self, decimals: u8) -> NumConversResult { + let fee_amount = self.fee_amount(); + utxo::sat_from_big_decimal(&fee_amount.into(), decimals) + } + + /// Converts the burn amount to micro-units, if applicable, based on the specified decimal places. + pub fn burn_uamount(&self, decimals: u8) -> NumConversResult> { + if let Some(burn_amount) = self.burn_amount() { + Ok(Some(utxo::sat_from_big_decimal(&burn_amount.into(), decimals)?)) + } else { + Ok(None) + } + } +} + pub struct CoinsContext { /// A map from a currency ticker symbol to the corresponding coin. /// Similar to `LP_coins`. diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index f556ba5032..9873618e6b 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -16,18 +16,19 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca UtxoActivationParams, UtxoAddressFormat, UtxoCoinFields, UtxoCommonOps, UtxoFromLegacyReqErr, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom, UTXO_LOCK}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, - FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, - MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, - TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, - TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, - WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; + DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, + MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, + TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, + WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -753,9 +754,9 @@ impl UtxoCommonOps for Qrc20Coin { #[async_trait] impl SwapOps for Qrc20Coin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let to_address = try_tx_fus!(self.contract_address_from_raw_pubkey(fee_addr)); - let amount = try_tx_fus!(wei_from_big_decimal(&amount, self.utxo.decimals)); + let amount = try_tx_fus!(wei_from_big_decimal(&dex_fee.fee_amount().into(), self.utxo.decimals)); let transfer_output = try_tx_fus!(self.transfer_output(to_address, amount, QRC20_GAS_LIMIT_DEFAULT, QRC20_GAS_PRICE_DEFAULT)); let outputs = vec![transfer_output]; @@ -874,7 +875,10 @@ impl SwapOps for Qrc20Coin { let fee_addr = try_f!(self .contract_address_from_raw_pubkey(validate_fee_args.fee_addr) .map_to_mm(ValidatePaymentError::WrongPaymentTx)); - let expected_value = try_f!(wei_from_big_decimal(validate_fee_args.amount, self.utxo.decimals)); + let expected_value = try_f!(wei_from_big_decimal( + &validate_fee_args.dex_fee.fee_amount().into(), + self.utxo.decimals + )); let selfi = self.clone(); let fut = async move { diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index f5aa39ba52..5675e9612c 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -1,6 +1,6 @@ use super::*; use crate::utxo::rpc_clients::UnspentInfo; -use crate::{TxFeeDetails, WaitForHTLCTxSpendArgs}; +use crate::{DexFee, TxFeeDetails, WaitForHTLCTxSpendArgs}; use chain::OutPoint; use common::{block_on, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -309,7 +309,7 @@ fn test_send_taker_fee() { let amount = BigDecimal::from_str("0.01").unwrap(); let tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, amount.clone(), &[]) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, DexFee::Standard(amount.clone().into()), &[]) .wait() .unwrap(); let tx_hash: H256Json = match tx { @@ -323,7 +323,7 @@ fn test_send_taker_fee() { fee_tx: &tx, expected_sender: coin.my_public_key().unwrap(), fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }) @@ -351,7 +351,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -364,7 +364,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &fee_addr_dif, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -382,7 +382,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 0, uuid: &[], }) @@ -400,7 +400,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.clone().into()), min_block_number: 2000000, uuid: &[], }) @@ -419,7 +419,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount_dif, + dex_fee: &DexFee::Standard(amount_dif.into()), min_block_number: 0, uuid: &[], }) @@ -442,7 +442,7 @@ fn test_validate_fee() { fee_tx: &tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }) diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 56178efa7a..1d8a6165d7 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -2,18 +2,18 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, Trade use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{lamports_to_sol, PrepareTransferData, SufficientBalanceError}; use crate::solana::spl::SplTokenInfo; -use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, - RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, - SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionResult, TransactionType, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherReward, WatcherRewardError, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; +use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionResult, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -468,7 +468,7 @@ impl MarketCoinOps for SolanaCoin { #[async_trait] impl SwapOps for SolanaCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 9270302b0b..4463621566 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -2,7 +2,7 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, SwapOps, Trade use crate::coin_errors::MyAddressError; use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, SufficientBalanceError}; use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -289,7 +289,7 @@ impl MarketCoinOps for SplToken { #[async_trait] impl SwapOps for SplToken { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 6d0432d703..764ab0d9be 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -15,8 +15,8 @@ use crate::tendermint::ibc::IBC_OUT_SOURCE_PORT; use crate::utxo::sat_from_big_decimal; use crate::utxo::utxo_common::big_decimal_from_sat; use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CheckIfMyPaymentSentArgs, - CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, - MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, + CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, FeeApproxStage, FoundSwapTxSpend, + HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, SearchForSwapTxSpendInput, @@ -2421,8 +2421,14 @@ impl MarketCoinOps for TendermintCoin { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { - self.send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { + self.send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + ) } fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { @@ -2570,7 +2576,7 @@ impl SwapOps for TendermintCoin { validate_fee_args.fee_tx, validate_fee_args.expected_sender, validate_fee_args.fee_addr, - validate_fee_args.amount, + &validate_fee_args.dex_fee.fee_amount().into(), self.decimals, validate_fee_args.uuid, self.denom.to_string(), @@ -3185,13 +3191,13 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(create_htlc_tx_bytes.as_slice()).unwrap(), }); - let invalid_amount = 1.into(); + let invalid_amount: MmNumber = 1.into(); let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &create_htlc_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount.clone()), min_block_number: 0, uuid: &[1; 16], }) @@ -3225,7 +3231,7 @@ pub mod tendermint_coin_tests { fee_tx: &random_transfer_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount.clone()), min_block_number: 0, uuid: &[1; 16], }) @@ -3258,7 +3264,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &[], fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &invalid_amount, + dex_fee: &DexFee::Standard(invalid_amount), min_block_number: 0, uuid: &[1; 16], }) @@ -3278,7 +3284,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &valid_amount, + dex_fee: &DexFee::Standard(valid_amount.clone().into()), min_block_number: 0, uuid: &[1; 16], }) @@ -3297,7 +3303,7 @@ pub mod tendermint_coin_tests { fee_tx: &dex_fee_tx, expected_sender: &pubkey, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &valid_amount, + dex_fee: &DexFee::Standard(valid_amount.into()), min_block_number: 0, uuid: &[1; 16], }) diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 061478aa09..d2a76edbf9 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -20,7 +20,7 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu ValidatePaymentInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFrom, WithdrawFut, WithdrawRequest}; -use crate::{MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; +use crate::{DexFee, MmCoinEnum, PaymentInstructionArgs, ValidateWatcherSpendInput, WatcherReward, WatcherRewardError}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -268,9 +268,14 @@ impl TendermintToken { #[async_trait] #[allow(unused_variables)] impl SwapOps for TendermintToken { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { - self.platform_coin - .send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { + self.platform_coin.send_taker_fee_for_denom( + fee_addr, + dex_fee.fee_amount().into(), + self.denom.clone(), + self.decimals, + uuid, + ) } fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { @@ -322,7 +327,7 @@ impl SwapOps for TendermintToken { validate_fee_args.fee_tx, validate_fee_args.expected_sender, validate_fee_args.fee_addr, - validate_fee_args.amount, + &validate_fee_args.dex_fee.fee_amount().into(), self.decimals, validate_fee_args.uuid, self.denom.to_string(), diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 99ced18e69..af664bddf6 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -16,7 +16,7 @@ use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPay ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; -use crate::{ToBytes, ValidateWatcherSpendInput}; +use crate::{DexFee, ToBytes, ValidateWatcherSpendInput}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -105,7 +105,7 @@ impl MarketCoinOps for TestCoin { #[async_trait] #[mockable] impl SwapOps for TestCoin { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { unimplemented!() } + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { unimplemented!() } fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index f584ccb6bf..7ae0e53b7c 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -10,7 +10,7 @@ use crate::utxo::utxo_common::big_decimal_from_sat_unsigned; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, - CoinWithDerivationMethod, ConfirmPaymentInput, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, + CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -840,8 +840,8 @@ impl UtxoCommonOps for BchCoin { #[async_trait] impl SwapOps for BchCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -884,7 +884,7 @@ impl SwapOps for BchCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index e901a53e94..8b13c46a80 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -25,15 +25,15 @@ use crate::utxo::utxo_builder::{MergeUtxoArcOps, UtxoCoinBuildError, UtxoCoinBui use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - DelegationError, DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, StakingInfosFut, - SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionResult, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, - ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + DelegationError, DelegationFut, DexFee, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, + MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, + TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; use crypto::Bip44Chain; @@ -526,8 +526,8 @@ impl UtxoStandardOps for QtumCoin { #[async_trait] impl SwapOps for QtumCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -570,7 +570,7 @@ impl SwapOps for QtumCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index e400b3e128..aba336f599 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -13,8 +13,8 @@ use crate::utxo::utxo_common::{self, big_decimal_from_sat_unsigned, payment_scri use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, AdditionalTxData, BroadcastTxErr, FeePolicy, GenerateTxError, RecentlySpentOutPointsGuard, UtxoCoinConf, UtxoCoinFields, UtxoCommonOps, UtxoTx, UtxoTxBroadcastOps, UtxoTxGenerationOps}; -use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, FeeApproxStage, - FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, +use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, DexFee, + FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -750,7 +750,7 @@ impl SlpToken { tx, SLP_FEE_VOUT, expected_sender, - &self.platform_dust_dec(), + &DexFee::Standard(self.platform_dust_dec().into()), min_block_number, fee_addr, ); @@ -1193,11 +1193,11 @@ impl MarketCoinOps for SlpToken { #[async_trait] impl SwapOps for SlpToken { - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { let coin = self.clone(); let fee_pubkey = try_tx_fus!(Public::from_slice(fee_addr)); let script_pubkey = ScriptBuilder::build_p2pkh(&fee_pubkey.address_hash().into()).into(); - let amount = try_tx_fus!(sat_from_big_decimal(&amount, self.decimals())); + let amount = try_tx_fus!(dex_fee.fee_uamount(self.decimals())); let fut = async move { let slp_out = SlpOutput { amount, script_pubkey }; @@ -1326,11 +1326,11 @@ impl SwapOps for SlpToken { let coin = self.clone(); let expected_sender = validate_fee_args.expected_sender.to_owned(); let fee_addr = validate_fee_args.fee_addr.to_owned(); - let amount = validate_fee_args.amount.to_owned(); + let amount = validate_fee_args.dex_fee.fee_amount(); let min_block_number = validate_fee_args.min_block_number; let fut = async move { - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) + coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount.into(), min_block_number) .await .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; Ok(()) diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index ec050a35f9..b567e9aff6 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -15,7 +15,7 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; -use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, GenPreimageResult, +use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, HDAccountAddressId, RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundFundingSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, @@ -1668,14 +1668,7 @@ pub async fn sign_and_broadcast_taker_payment_spend( Ok(final_tx.into()) } -fn should_taker_burn_kmd(taker_ticker: &str, fee_amount: &BigDecimal, min_tx_amount: &BigDecimal) -> bool { - taker_ticker == "KMD" && - // Burn percentage is only calculated for the dex fee if it's still - // higher than minimum transaction amount after the cut. - fee_amount > &(min_tx_amount / &BigDecimal::from_str("0.75").unwrap()) -} - -pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], amount: BigDecimal) -> TransactionFut +pub fn send_taker_fee(coin: T, fee_pub_key: &[u8], dex_fee: DexFee) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps, { @@ -1689,34 +1682,27 @@ where )); let outputs = try_tx_fus!(generate_taker_fee_tx_outputs( - &coin.as_ref().conf.ticker, coin.as_ref().decimals, &address.hash, - amount, - min_tx_amount(coin.as_ref()) + dex_fee, )); send_outputs_from_my_address(coin, outputs) } fn generate_taker_fee_tx_outputs( - ticker: &str, decimals: u8, address_hash: &AddressHashEnum, - amount: BigDecimal, - min_tx_amount: BigDecimal, + dex_fee: DexFee, ) -> Result, MmError> { - let fee_amount = sat_from_big_decimal(&amount, decimals)?; + let fee_amount = dex_fee.fee_uamount(decimals)?; let mut outputs = vec![TransactionOutput { value: fee_amount, script_pubkey: Builder::build_p2pkh(address_hash).to_bytes(), }]; - if should_taker_burn_kmd(ticker, &amount, &min_tx_amount) { - let burn_amount_dec = (&amount / BigDecimal::from_str("0.75").unwrap()) - &amount; - let burn_amount = sat_from_big_decimal(&burn_amount_dec, decimals)?; - + if let Some(burn_amount) = dex_fee.burn_uamount(decimals)? { outputs.push(TransactionOutput { value: burn_amount, script_pubkey: Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(), @@ -2367,11 +2353,10 @@ pub fn validate_fee( tx: UtxoTx, output_index: usize, sender_pubkey: &[u8], - amount: &BigDecimal, + dex_amount: &DexFee, min_block_number: u64, fee_addr: &[u8], ) -> ValidatePaymentFut<()> { - let amount = amount.clone(); let address = try_f!(address_from_raw_pubkey( fee_addr, coin.as_ref().conf.pub_addr_prefix, @@ -2392,8 +2377,10 @@ pub fn validate_fee( )); } + let fee_amount = try_f!(dex_amount.fee_uamount(coin.as_ref().decimals)); + let burn_amount = try_f!(dex_amount.burn_uamount(coin.as_ref().decimals)); + let fut = async move { - let fee_amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; let tx_from_rpc = coin .as_ref() .rpc_client @@ -2432,7 +2419,7 @@ pub fn validate_fee( if out.value < fee_amount { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx output value is less than expected {:?} {:?}", - out.value, amount + out.value, fee_amount ))); } }, @@ -2444,7 +2431,7 @@ pub fn validate_fee( }, } - if should_taker_burn_kmd(&coin.as_ref().conf.ticker, &amount, &min_tx_amount(coin.as_ref())) { + if let Some(burn_amount) = burn_amount { match tx.outputs.get(output_index + 1) { Some(out) => { let expected_script_pubkey = Builder::default().push_opcode(Opcode::OP_RETURN).into_bytes(); @@ -2456,13 +2443,10 @@ pub fn validate_fee( ))); } - let burn_amount_dec = (&amount / BigDecimal::from_str("0.75").unwrap()) - &amount; - let burn_amount = sat_from_big_decimal(&burn_amount_dec, coin.as_ref().decimals)?; - if out.value < burn_amount { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided burn tx output value is less than expected {:?} {:?}", - out.value, amount + out.value, burn_amount ))); } }, @@ -5134,11 +5118,9 @@ fn test_generate_taker_fee_tx_outputs() { let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); let outputs = generate_taker_fee_tx_outputs( - "TEST", 8, &AddressHashEnum::default_address_hash(), - amount, - Default::default(), + DexFee::Standard(amount.into()), ) .unwrap(); @@ -5148,47 +5130,23 @@ fn test_generate_taker_fee_tx_outputs() { } #[test] -fn test_generate_taker_fee_tx_outputs_for_kmd() { - let amount = BigDecimal::from(6150); - let fee_amount = sat_from_big_decimal(&amount, 8).unwrap(); +fn test_generate_taker_fee_tx_outputs_with_burn() { + let fee_amount = BigDecimal::from(6150); + let burn_amount = &(&fee_amount / &BigDecimal::from_str("0.75").unwrap()) - &fee_amount; + + let fee_uamount = sat_from_big_decimal(&fee_amount, 8).unwrap(); + let burn_uamount = sat_from_big_decimal(&burn_amount, 8).unwrap(); let outputs = generate_taker_fee_tx_outputs( - "KMD", 8, &AddressHashEnum::default_address_hash(), - amount.clone(), - Default::default(), + DexFee::with_burn(fee_amount.into(), burn_amount.into()), ) .unwrap(); assert_eq!(outputs.len(), 2); - assert_eq!(outputs[0].value, fee_amount); - - let burn_amount = sat_from_big_decimal(&((&amount / &BigDecimal::from_str("0.75").unwrap()) - &amount), 8).unwrap(); - assert_eq!(outputs[1].value, burn_amount); -} - -#[test] -fn test_should_taker_burn_kmd() { - // When taker_ticker is "KMD" and fee_amount is enough to allow burning. - assert!(should_taker_burn_kmd( - "KMD", - &BigDecimal::from_str("0.02").unwrap(), - &BigDecimal::from_str("0.00777").unwrap() - )); + assert_eq!(outputs[0].value, fee_uamount); - // When taker_ticker is not "KMD". - assert!(!should_taker_burn_kmd( - "BTC", - &BigDecimal::from_str("0.02").unwrap(), - &BigDecimal::from_str("0.00777").unwrap() - )); - - // When taker_ticker is "KMD" but fee_amount is not enough to allow burning. - assert!(!should_taker_burn_kmd( - "KMD", - &BigDecimal::from_str("0.01").unwrap(), - &BigDecimal::from_str("0.00777").unwrap() - )); + assert_eq!(outputs[1].value, burn_uamount); } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 26bc6bdc88..41519d4d8e 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -23,7 +23,7 @@ use crate::utxo::utxo_builder::{UtxoArcBuilder, UtxoCoinBuilder}; use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetailsError, UtxoTxDetailsParams, UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, + DexFee, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, @@ -294,8 +294,8 @@ impl UtxoStandardOps for UtxoStandardCoin { #[async_trait] impl SwapOps for UtxoStandardCoin { #[inline] - fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { - utxo_common::send_taker_fee(self.clone(), fee_addr, amount) + fn send_taker_fee(&self, fee_addr: &[u8], dex_fee: DexFee, _uuid: &[u8]) -> TransactionFut { + utxo_common::send_taker_fee(self.clone(), fee_addr, dex_fee) } #[inline] @@ -338,7 +338,7 @@ impl SwapOps for UtxoStandardCoin { tx, utxo_common::DEFAULT_FEE_VOUT, validate_fee_args.expected_sender, - validate_fee_args.amount, + validate_fee_args.dex_fee, validate_fee_args.min_block_number, validate_fee_args.fee_addr, ) diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index 265b1d94d2..88d60142bd 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -28,7 +28,7 @@ use crate::utxo::utxo_common_tests::{self, utxo_coin_fields_for_test, utxo_coin_ use crate::utxo::utxo_standard::{utxo_standard_coin_with_priv_key, UtxoStandardCoin}; use crate::utxo::utxo_tx_history_v2::{UtxoTxDetailsParams, UtxoTxHistoryOps}; #[cfg(not(target_arch = "wasm32"))] use crate::WithdrawFee; -use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, IguanaPrivKey, PrivKeyBuildPolicy, +use crate::{BlockHeightAndTime, CoinBalance, ConfirmPaymentInput, DexFee, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, ValidateFeeArgs, WaitForHTLCTxSpendArgs, INVALID_SENDER_ERR_LOG}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; @@ -2520,7 +2520,7 @@ fn test_validate_fee_wrong_sender() { fee_tx: &taker_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; @@ -2545,7 +2545,7 @@ fn test_validate_fee_min_block() { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 278455, uuid: &[], }; @@ -2574,7 +2574,7 @@ fn test_validate_fee_bch_70_bytes_signature() { fee_tx: &taker_fee_tx, expected_sender: &sender_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &amount, + dex_fee: &DexFee::Standard(amount.into()), min_block_number: 0, uuid: &[], }; diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index af7d816435..2b6cea5d96 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -9,23 +9,23 @@ use crate::utxo::utxo_builder::UtxoCoinBuildError; use crate::utxo::utxo_builder::{UtxoCoinBuilder, UtxoCoinBuilderCommonOps, UtxoFieldsWithGlobalHDBuilder, UtxoFieldsWithHardwareWalletBuilder, UtxoFieldsWithIguanaSecretBuilder}; use crate::utxo::utxo_common::{big_decimal_from_sat_unsigned, payment_script}; -use crate::utxo::{sat_from_big_decimal, utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, - BroadcastTxErr, FeePolicy, GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, - RecentlySpentOutPointsGuard, UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, - UtxoCommonOps, UtxoRpcMode, UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; +use crate::utxo::{utxo_common, ActualTxFee, AdditionalTxData, AddrFromStrError, Address, BroadcastTxErr, FeePolicy, + GetUtxoListOps, HistoryUtxoTx, HistoryUtxoTxMap, MatureUnspentList, RecentlySpentOutPointsGuard, + UtxoActivationParams, UtxoAddressFormat, UtxoArc, UtxoCoinFields, UtxoCommonOps, UtxoRpcMode, + UtxoTxBroadcastOps, UtxoTxGenerationOps, VerboseTransactionFrom}; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, ConfirmPaymentInput, - FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MmCoinEnum, - NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, - PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, - SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionEnum, TransactionFut, TransactionResult, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, ValidateWatcherSpendInput, - VerificationError, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, - WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + DexFee, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, + MmCoinEnum, NegotiateSwapContractAddrErr, PaymentInstructionArgs, PaymentInstructions, + PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionEnum, TransactionFut, TransactionResult, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, ValidateWatcherSpendInput, VerificationError, VerificationResult, + WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -82,7 +82,7 @@ pub use z_rpc::{FirstSyncBlock, SyncStatus}; cfg_native!( use crate::{NumConversError, TransactionDetails, TxFeeDetails}; - use crate::utxo::UtxoFeeDetails; + use crate::utxo::{UtxoFeeDetails, sat_from_big_decimal}; use crate::utxo::utxo_common::{addresses_from_script, big_decimal_from_sat}; use common::{async_blocking, calc_total_pages, PagingOptionsEnum}; @@ -1180,11 +1180,11 @@ impl MarketCoinOps for ZCoin { #[async_trait] impl SwapOps for ZCoin { - fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { + fn send_taker_fee(&self, _fee_addr: &[u8], dex_fee: DexFee, uuid: &[u8]) -> TransactionFut { let selfi = self.clone(); let uuid = uuid.to_owned(); let fut = async move { - let tx = try_tx_s!(z_send_dex_fee(&selfi, amount, &uuid).await); + let tx = try_tx_s!(z_send_dex_fee(&selfi, dex_fee.fee_amount().into(), &uuid).await); Ok(tx.into()) }; Box::new(fut.boxed().compat()) @@ -1354,7 +1354,7 @@ impl SwapOps for ZCoin { TransactionEnum::ZTransaction(t) => t.clone(), _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), }; - let amount_sat = try_f!(sat_from_big_decimal(validate_fee_args.amount, self.utxo_arc.decimals)); + let amount_sat = try_f!(validate_fee_args.dex_fee.fee_uamount(self.utxo_arc.decimals)); let expected_memo = MemoBytes::from_bytes(validate_fee_args.uuid).expect("Uuid length < 512"); let min_block_number = validate_fee_args.min_block_number; diff --git a/mm2src/coins/z_coin/z_coin_native_tests.rs b/mm2src/coins/z_coin/z_coin_native_tests.rs index 12789b2090..c2a0b66201 100644 --- a/mm2src/coins/z_coin/z_coin_native_tests.rs +++ b/mm2src/coins/z_coin/z_coin_native_tests.rs @@ -11,6 +11,8 @@ use super::{z_coin_from_conf_and_params_with_z_key, z_mainnet_constants, Future, ZTransaction}; use crate::z_coin::{z_htlc::z_send_dex_fee, ZcoinActivationParams, ZcoinRpcMode}; use crate::CoinProtocol; +use crate::DexFee; +use mm2_number::MmNumber; #[test] fn zombie_coin_send_and_refund_maker_payment() { @@ -228,7 +230,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.001".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.001")), min_block_number: 12000, uuid: &[1; 16], }; @@ -244,7 +246,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 12000, uuid: &[2; 16], }; @@ -259,7 +261,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 14000, uuid: &[1; 16], }; @@ -274,7 +276,7 @@ fn zombie_coin_validate_dex_fee() { fee_tx: &tx, expected_sender: &[], fee_addr: &[], - amount: &"0.01".parse().unwrap(), + dex_fee: &DexFee::Standard(MmNumber::from("0.01")), min_block_number: 12000, uuid: &[1; 16], }; diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index 8dfe4b7e30..92055cf43d 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -2974,7 +2974,10 @@ fn lp_connect_start_bob(ctx: MmArc, maker_match: MakerMatch, maker_order: MakerO maker_volume: maker_amount, secret, taker_coin: t.clone(), - dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount), + // TODO: + // Support KMD burning for v2 + dex_fee_amount: dex_fee_amount_from_taker_coin(&t, m.ticker(), &taker_amount) + .total_spend_amount(), taker_volume: taker_amount, taker_premium: Default::default(), conf_settings: my_conf_settings, @@ -3121,7 +3124,10 @@ fn lp_connected_alice(ctx: MmArc, taker_order: TakerOrder, taker_match: TakerMat maker_coin: m.clone(), maker_volume: maker_amount, taker_coin: t.clone(), - dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount), + // TODO: + // Support KMD burning for v2 + dex_fee: dex_fee_amount_from_taker_coin(&t, maker_coin_ticker, &taker_amount) + .total_spend_amount(), taker_volume: taker_amount, taker_premium: Default::default(), secret_hash_algo, diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 21147f3e3d..79cc3464f0 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -60,7 +60,7 @@ use super::lp_network::P2PRequestResult; use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId, P2PProcessError, P2PProcessResult, P2PRequestError}; use bitcrypto::{dhash160, sha256}; -use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; +use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, DexFee, MmCoin, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::now_sec; use common::time_cache::DuplicateCache; @@ -742,34 +742,34 @@ 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) -> MmNumber { +pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amount: &MmNumber) -> DexFee { let rate = dex_fee_rate(base, rel); + let fee = trade_amount * &rate; - let fee_amount = if base == "KMD" { + if &fee < min_tx_amount { + 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. - let new_dex_fee = (trade_amount * &rate) * MmNumber::from("0.75"); + let new_fee = &fee * &MmNumber::from("0.75"); + let burn_amount = &fee - &new_fee; - if &new_dex_fee >= min_tx_amount { - new_dex_fee + if &new_fee >= min_tx_amount { + return DexFee::with_burn(new_fee, burn_amount); } else { - min_tx_amount.clone() + return DexFee::Standard(min_tx_amount.clone()); } - } else { - trade_amount * &rate - }; - - if &fee_amount < min_tx_amount { - min_tx_amount.clone() - } else { - fee_amount } + + DexFee::Standard(fee) } /// Calculates DEX fee with a threshold based on min tx amount of the taker coin. -pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> MmNumber { +pub fn dex_fee_amount_from_taker_coin(taker_coin: &dyn MmCoin, maker_coin: &str, trade_amount: &MmNumber) -> DexFee { let min_tx_amount = MmNumber::from(taker_coin.min_tx_amount()); dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &min_tx_amount) } @@ -1666,7 +1666,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 / 777u64.into(); + let expected_fee = DexFee::Standard(amount / 777u64.into()); assert_eq!(expected_fee, actual_fee); let base = "KMD"; @@ -1674,20 +1674,21 @@ mod lp_swap_tests { let amount = 1.into(); let actual_fee = dex_fee_amount(base, rel, &amount, &min_tx_amount); let expected_fee = amount * (9, 7770).into() * MmNumber::from("0.75"); - assert_eq!(expected_fee, actual_fee); + let expected_burn_amount = &(&expected_fee / &MmNumber::from("0.75")) - &expected_fee; + assert_eq!(DexFee::with_burn(expected_fee, expected_burn_amount), 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 = amount * (9, 7770).into(); + let expected_fee = DexFee::Standard(amount * (9, 7770).into()); assert_eq!(expected_fee, 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!(min_tx_amount, actual_fee); + assert_eq!(DexFee::Standard(min_tx_amount), actual_fee); } #[test] @@ -2204,12 +2205,27 @@ mod lp_swap_tests { std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); let kmd = coins::TestCoin::new("KMD"); - let kmd_taker_fee = dex_fee_amount_from_taker_coin(&kmd, "", &MmNumber::from(6150)); + 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 mycoin = coins::TestCoin::new("MYCOIN"); - let mycoin_taker_fee = dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)); + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { + DexFee::Standard(t) => t, + DexFee::WithBurn { .. } => { + 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; - assert_eq!(kmd_taker_fee / MmNumber::from("0.75"), mycoin_taker_fee); + assert_eq!(expected_mycoin_taker_fee, mycoin_taker_fee); + assert_eq!(expected_kmd_burn_amount, kmd_burn_amount); } #[test] @@ -2217,10 +2233,20 @@ mod lp_swap_tests { std::env::set_var("MYCOIN_FEE_DISCOUNT", ""); let mycoin = coins::TestCoin::new("MYCOIN"); - let mycoin_taker_fee = dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)); + let mycoin_taker_fee = match dex_fee_amount_from_taker_coin(&mycoin, "", &MmNumber::from(6150)) { + DexFee::Standard(t) => t, + DexFee::WithBurn { .. } => { + panic!("Wrong variant returned for MYCOIN from `dex_fee_amount_from_taker_coin`.") + }, + }; let testcoin = coins::TestCoin::default(); - let testcoin_taker_fee = 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 { .. } => { + panic!("Wrong variant returned for TEST coin from `dex_fee_amount_from_taker_coin`.") + }, + }; assert_eq!(testcoin_taker_fee * MmNumber::from("0.90"), mycoin_taker_fee); } diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 67f1b6285a..9608213eaa 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -748,8 +748,7 @@ impl MakerSwap { info!("Taker fee tx {:02x}", hash); let taker_amount = MmNumber::from(self.taker_amount.clone()); - let fee_amount = - dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &taker_amount); + 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; @@ -761,7 +760,7 @@ impl MakerSwap { fee_tx: &taker_fee, expected_sender: &*other_taker_coin_htlc_pub, fee_addr: &DEX_FEE_ADDR_RAW_PUBKEY, - amount: &fee_amount.clone().into(), + dex_fee: &dex_fee, min_block_number: taker_coin_start_block, uuid: self.uuid.as_bytes(), }) diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index f16bdd129d..6293d1f82f 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -993,7 +993,8 @@ impl TakerSwap { // do not use self.r().data here as it is not initialized at this step yet let stage = FeeApproxStage::StartSwap; let dex_fee = - dex_fee_amount_from_taker_coin(self.taker_coin.deref(), self.maker_coin.ticker(), &self.taker_amount); + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), self.maker_coin.ticker(), &self.taker_amount) + .fee_amount(); let preimage_value = TradePreimageValue::Exact(self.taker_amount.to_decimal()); let fee_to_send_dex_fee_fut = self @@ -1272,7 +1273,7 @@ impl TakerSwap { dex_fee_amount_from_taker_coin(self.taker_coin.deref(), &self.r().data.maker_coin, &self.taker_amount); let fee_tx = self .taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), self.uuid.as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, self.uuid.as_bytes()) .compat() .await; let transaction = match fee_tx { @@ -2291,7 +2292,7 @@ impl AtomicSwap for TakerSwap { if self.r().taker_fee.is_none() { result.push(LockedAmount { coin: self.taker_coin.ticker().to_owned(), - amount: taker_fee_amount, + amount: taker_fee_amount.fee_amount(), trade_fee, }); } @@ -2354,7 +2355,7 @@ pub async fn check_balance_for_taker_swap( let params = match prepared_params { Some(params) => params, None => { - let dex_fee = dex_fee_amount_from_taker_coin(my_coin, other_coin.ticker(), &volume); + let dex_fee = dex_fee_amount_from_taker_coin(my_coin, other_coin.ticker(), &volume).fee_amount(); let fee_to_send_dex_fee = my_coin .get_fee_to_send_taker_fee(dex_fee.to_decimal(), stage.clone()) .await @@ -2445,12 +2446,12 @@ pub async fn taker_swap_trade_preimage( let dex_amount = dex_fee_amount_from_taker_coin(my_coin.deref(), other_coin_ticker, &my_coin_volume); let taker_fee = TradeFee { coin: my_coin_ticker.to_owned(), - amount: dex_amount.clone(), + amount: dex_amount.fee_amount(), paid_from_trading_vol: false, }; let fee_to_send_taker_fee = my_coin - .get_fee_to_send_taker_fee(dex_amount.to_decimal(), stage.clone()) + .get_fee_to_send_taker_fee(dex_amount.fee_amount().to_decimal(), stage.clone()) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, my_coin_ticker))?; @@ -2466,7 +2467,7 @@ pub async fn taker_swap_trade_preimage( .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, other_coin_ticker))?; let prepared_params = TakerSwapPreparedParams { - dex_fee: dex_amount, + dex_fee: dex_amount.fee_amount(), fee_to_send_dex_fee: fee_to_send_taker_fee.clone(), taker_payment_trade_fee: my_coin_trade_fee.clone(), maker_payment_spend_trade_fee: other_coin_trade_fee.clone(), @@ -2590,7 +2591,7 @@ pub async fn calc_max_taker_vol( let max_possible_2 = &max_possible - &max_trade_fee.amount; let max_dex_fee = dex_fee_amount_from_taker_coin(coin.deref(), other_coin, &max_possible_2); let max_fee_to_send_taker_fee = coin - .get_fee_to_send_taker_fee(max_dex_fee.to_decimal(), stage) + .get_fee_to_send_taker_fee(max_dex_fee.fee_amount().to_decimal(), stage) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin))?; let min_max_possible = &max_possible_2 - &max_fee_to_send_taker_fee.amount; @@ -2601,7 +2602,7 @@ pub async fn calc_max_taker_vol( balance.to_fraction(), locked.to_fraction(), max_trade_fee.amount.to_fraction(), - max_dex_fee.to_fraction(), + max_dex_fee.fee_amount().to_fraction(), max_fee_to_send_taker_fee.amount.to_fraction() ); max_taker_vol_from_available(min_max_possible, my_coin, other_coin, &min_tx_amount) @@ -3078,7 +3079,7 @@ mod taker_swap_tests { let max_taker_vol = max_taker_vol_from_available(available.clone(), "RICK", "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount).fee_amount(); assert!(min_tx_amount < dex_fee); assert!(min_tx_amount <= max_taker_vol); assert_eq!(max_taker_vol + dex_fee, available); @@ -3098,7 +3099,7 @@ mod taker_swap_tests { let base = if is_kmd { "KMD" } else { "RICK" }; let max_taker_vol = max_taker_vol_from_available(available.clone(), base, "MORTY", &min_tx_amount) .expect("!max_taker_vol_from_available"); - let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount); + let dex_fee = dex_fee_amount(base, "MORTY", &max_taker_vol, &min_tx_amount).fee_amount(); println!( "available={:?} max_taker_vol={:?} dex_fee={:?}", available.to_decimal(), diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 03c484275d..877ab1e914 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -1053,7 +1053,8 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ // - `max_possible_2 = balance - locked_amount - max_trade_fee`, where `locked_amount = 0` let max_possible_2 = &qtum_balance - &max_trade_fee; // - `max_dex_fee = dex_fee(max_possible_2)` - let max_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount); + let max_dex_fee = + dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount).fee_amount(); debug!("max_dex_fee: {:?}", max_dex_fee.to_fraction()); // - `max_fee_to_send_taker_fee = fee_to_send_taker_fee(max_dex_fee)` @@ -1072,7 +1073,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let expected_max_taker_vol = max_taker_vol_from_available(MmNumber::from(available), "QTUM", "MYCOIN", &min_tx_amount) .expect("max_taker_vol_from_available"); - let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); + let real_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount).fee_amount(); debug!("real_max_dex_fee: {:?}", real_dex_fee.to_fraction()); // check if the actual max_taker_vol equals to the expected @@ -1105,9 +1106,9 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ let timelock = now_sec() - 200; let secret_hash = &[0; 20]; - let dex_fee_amount = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); + let dex_fee = dex_fee_amount("QTUM", "MYCOIN", &expected_max_taker_vol, &qtum_min_tx_amount); let _taker_fee_tx = coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount.to_decimal(), &[]) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee, &[]) .wait() .expect("!send_taker_fee"); let taker_payment_args = SendPaymentArgs { 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 4f3a10593f..7a5f340fbd 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -738,8 +738,9 @@ fn test_watcher_spends_maker_payment_eth_utxo() { let mycoin_volume = BigDecimal::from_str("1").unwrap(); let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); - let dex_fee: BigDecimal = - dex_fee_amount("MYCOIN", "ETH", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); + let dex_fee: BigDecimal = dex_fee_amount("MYCOIN", "ETH", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount) + .fee_amount() + .into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() @@ -878,8 +879,9 @@ fn test_watcher_spends_maker_payment_erc20_utxo() { let jst_volume = BigDecimal::from_str("1").unwrap(); let min_tx_amount = BigDecimal::from_str("0.00001").unwrap().into(); - let dex_fee: BigDecimal = - dex_fee_amount("MYCOIN", "JST", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount).into(); + let dex_fee: BigDecimal = dex_fee_amount("MYCOIN", "JST", &MmNumber::from(mycoin_volume.clone()), &min_tx_amount) + .fee_amount() + .into(); let alice_mycoin_reward_sent = balances.alice_acoin_balance_before - balances.alice_acoin_balance_after.clone() - mycoin_volume.clone() From bbd4c80ef6db6d5e1a7a17f089381ff6f1ebe08c Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 20 Nov 2023 09:17:31 +0300 Subject: [PATCH 09/12] fix clippy for docker tests Signed-off-by: onur-ozkan --- mm2src/mm2_main/src/lp_swap.rs | 4 ++-- mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 79cc3464f0..433719e827 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -1673,8 +1673,8 @@ 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 * (9, 7770).into() * MmNumber::from("0.75"); - let expected_burn_amount = &(&expected_fee / &MmNumber::from("0.75")) - &expected_fee; + 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); let base = "BTC"; 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 7a5f340fbd..00348f66c0 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -1132,7 +1132,7 @@ fn test_watcher_validate_taker_fee_utxo() { let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, maker_coin.ticker(), &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); @@ -1253,7 +1253,7 @@ fn test_watcher_validate_taker_fee_eth() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); @@ -1356,7 +1356,7 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_amount = MmNumber::from((1, 1)); let fee_amount = dex_fee_amount_from_taker_coin(&taker_coin, "ETH", &taker_amount); let taker_fee = taker_coin - .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount.into(), Uuid::new_v4().as_bytes()) + .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, fee_amount, Uuid::new_v4().as_bytes()) .wait() .unwrap(); From 7e05366dd2f75fbe31ce3cd1be9dd41172960a1b Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 20 Nov 2023 12:24:30 +0300 Subject: [PATCH 10/12] improve min_tx_amount check in `dex_fee_amount` Signed-off-by: onur-ozkan --- mm2src/mm2_main/src/lp_swap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 433719e827..e0a9a85435 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -746,7 +746,7 @@ 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 { + if &fee <= min_tx_amount { return DexFee::Standard(min_tx_amount.clone()); } @@ -761,7 +761,7 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo if &new_fee >= min_tx_amount { return DexFee::with_burn(new_fee, burn_amount); } else { - return DexFee::Standard(min_tx_amount.clone()); + return DexFee::Standard(fee); } } From c005c11b792ea18b3b78e025fe3557df93a17fb7 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 20 Nov 2023 15:38:19 +0300 Subject: [PATCH 11/12] update burn algorithm Signed-off-by: onur-ozkan --- mm2src/mm2_main/src/lp_swap.rs | 35 +++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index e0a9a85435..1d78c7e230 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -755,14 +755,25 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo // // 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 burn_amount = &fee - &new_fee; - if &new_fee >= min_tx_amount { - return DexFee::with_burn(new_fee, burn_amount); + 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 { - return DexFee::Standard(fee); - } + // Burn only the exceed amount because fee after 25% cut is less + // than `min_tx_amount`. + let burn_amount = &fee - min_tx_amount; + let new_fee = &fee - &burn_amount; + + (new_fee, burn_amount) + }; + + return DexFee::with_burn(fee, burn); } DexFee::Standard(fee) @@ -1677,6 +1688,20 @@ mod lp_swap_tests { let expected_burn_amount = amount * (9, 7770).into() * MmNumber::from("0.25"); assert_eq!(DexFee::with_burn(expected_fee, expected_burn_amount), actual_fee); + // check the case when KMD taker fee is close to dust + let base = "KMD"; + let rel = "BTC"; + 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 + ); + let base = "BTC"; let rel = "KMD"; let amount = 1.into(); From 2f553d1d2283f30d5fcbde1c506e89c643ca2938 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Tue, 21 Nov 2023 12:55:26 +0300 Subject: [PATCH 12/12] fix review notes Signed-off-by: onur-ozkan --- mm2src/coins/eth.rs | 4 +-- mm2src/coins/eth/eth_tests.rs | 20 +++++++++++---- mm2src/coins/lightning.rs | 2 +- mm2src/coins/lp_coins.rs | 4 +-- mm2src/coins/qrc20.rs | 4 +-- mm2src/coins/qrc20/qrc20_tests.rs | 7 ++++-- mm2src/coins/solana.rs | 2 +- mm2src/coins/solana/spl.rs | 2 +- mm2src/coins/tendermint/tendermint_coin.rs | 6 ++--- mm2src/coins/tendermint/tendermint_token.rs | 2 +- mm2src/coins/test_coin.rs | 2 +- mm2src/coins/utxo/bch.rs | 2 +- mm2src/coins/utxo/qtum.rs | 2 +- mm2src/coins/utxo/slp.rs | 4 +-- mm2src/coins/utxo/utxo_common.rs | 12 ++++----- mm2src/coins/utxo/utxo_standard.rs | 2 +- mm2src/coins/z_coin.rs | 2 +- mm2src/mm2_main/src/lp_swap.rs | 3 +-- mm2src/mm2_main/src/lp_swap/taker_swap.rs | 25 +++++++++---------- .../tests/docker_tests/qrc20_tests.rs | 7 +++--- 20 files changed, 61 insertions(+), 53 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 7afb9c5472..53567b87eb 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4904,10 +4904,10 @@ impl MmCoin for EthCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let dex_fee_amount = wei_from_big_decimal(&dex_fee_amount, self.decimals)?; + 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) diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 5314321fb2..152f68436f 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1104,8 +1104,11 @@ fn test_get_fee_to_send_taker_fee() { let dex_fee_amount = u256_to_big_decimal(DEX_FEE_AMOUNT.into(), 18).expect("!u256_to_big_decimal"); let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, &["http://dummy.dummy"], None); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount.clone(), FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount.clone())), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); assert_eq!(actual, expected_fee); let (_ctx, coin) = eth_coin_for_test( @@ -1116,8 +1119,11 @@ fn test_get_fee_to_send_taker_fee() { &["http://dummy.dummy"], None, ); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); assert_eq!(actual, expected_fee); } @@ -1143,7 +1149,11 @@ fn test_get_fee_to_send_taker_fee_insufficient_balance() { ); let dex_fee_amount = u256_to_big_decimal(DEX_FEE_AMOUNT.into(), 18).expect("!u256_to_big_decimal"); - let error = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)).unwrap_err(); + let error = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .unwrap_err(); log!("{}", error); assert!( matches!(error.get_inner(), TradePreimageError::NotSufficientBalance { .. }), diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 812634f107..447990e2b1 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -1342,7 +1342,7 @@ impl MmCoin for LightningCoin { // Todo: This uses dummy data for now for the sake of swap P.O.C., this should be implemented probably after agreeing on how fees will work for lightning async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { Ok(TradeFee { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index ad874ebbfc..ad33246977 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -2673,7 +2673,7 @@ pub trait MmCoin: /// Get transaction fee the Taker has to pay to send a `TakerFee` transaction and check if the wallet has sufficient balance to pay the fee. async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult; @@ -2952,7 +2952,7 @@ impl MmCoinStruct { } /// Represents the different types of DEX fees. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum DexFee { /// Standard dex fee which will be sent to the dex fee address Standard(MmNumber), diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index 9873618e6b..4043d299bb 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -1436,10 +1436,10 @@ impl MmCoin for Qrc20Coin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let amount = wei_from_big_decimal(&dex_fee_amount, self.utxo.decimals)?; + let amount = wei_from_big_decimal(&dex_fee_amount.fee_amount().into(), self.utxo.decimals)?; // pass the dummy params let to_addr = H160::default(); diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 5675e9612c..a837e18364 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -949,8 +949,11 @@ fn test_taker_fee_tx_fee() { assert_eq!(coin.my_balance().wait().expect("!my_balance"), expected_balance); let dex_fee_amount = BigDecimal::from(5u32); - let actual = block_on(coin.get_fee_to_send_taker_fee(dex_fee_amount, FeeApproxStage::WithoutApprox)) - .expect("!get_fee_to_send_taker_fee"); + let actual = block_on(coin.get_fee_to_send_taker_fee( + DexFee::Standard(MmNumber::from(dex_fee_amount)), + FeeApproxStage::WithoutApprox, + )) + .expect("!get_fee_to_send_taker_fee"); // only one contract call should be included into the expected trade fee let expected_receiver_fee = big_decimal_from_sat(CONTRACT_CALL_GAS_FEE + EXPECTED_TX_FEE, coin.utxo.decimals); let expected = TradeFee { diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index 1d8a6165d7..025f4c6c7d 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -745,7 +745,7 @@ impl MmCoin for SolanaCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 4463621566..0777e278e5 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -539,7 +539,7 @@ impl MmCoin for SplToken { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index 764ab0d9be..89b79573c3 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -1612,11 +1612,11 @@ impl TendermintCoin { ticker: String, denom: Denom, decimals: u8, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, ) -> TradePreimageResult { let to_address = account_id_from_pubkey_hex(&self.account_prefix, DEX_FEE_ADDR_PUBKEY) .map_err(|e| MmError::new(TradePreimageError::InternalError(e.into_inner().to_string())))?; - let amount = sat_from_big_decimal(&dex_fee_amount, decimals)?; + let amount = sat_from_big_decimal(&dex_fee_amount.fee_amount().into(), decimals)?; let current_block = self.current_block().compat().await.map_err(|e| { MmError::new(TradePreimageError::InternalError(format!( @@ -2174,7 +2174,7 @@ impl MmCoin for TendermintCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { self.get_fee_to_send_taker_fee_for_denom(self.ticker.clone(), self.denom.clone(), self.decimals, dex_fee_amount) diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index d2a76edbf9..93c5eb56f7 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -840,7 +840,7 @@ impl MmCoin for TendermintToken { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { self.platform_coin diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index af664bddf6..d4da2109bd 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -351,7 +351,7 @@ impl MmCoin for TestCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { unimplemented!() diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index 7ae0e53b7c..072719eeee 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -1268,7 +1268,7 @@ impl MmCoin for BchCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 8b13c46a80..a66ae04331 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -939,7 +939,7 @@ impl MmCoin for QtumCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index aba336f599..da2a51224b 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -1818,10 +1818,10 @@ impl MmCoin for SlpToken { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { - let slp_amount = sat_from_big_decimal(&dex_fee_amount, self.decimals())?; + 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(); let slp_out = SlpOutput { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index b567e9aff6..2354d8ba44 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -4053,21 +4053,19 @@ pub fn get_receiver_trade_fee(coin: T) -> TradePreimageFut( coin: &T, - dex_fee_amount: BigDecimal, + dex_fee: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult where T: MarketCoinOps + UtxoCommonOps, { let decimals = coin.as_ref().decimals; - let value = sat_from_big_decimal(&dex_fee_amount, decimals)?; - let output = TransactionOutput { - value, - script_pubkey: Builder::build_p2pkh(&AddressHashEnum::default_address_hash()).to_bytes(), - }; + + let outputs = generate_taker_fee_tx_outputs(decimals, &AddressHashEnum::default_address_hash(), dex_fee)?; + let gas_fee = None; let fee_amount = coin - .preimage_trade_fee_required_to_send_outputs(vec![output], FeePolicy::SendExact, gas_fee, &stage) + .preimage_trade_fee_required_to_send_outputs(outputs, FeePolicy::SendExact, gas_fee, &stage) .await?; Ok(TradeFee { coin: coin.ticker().to_owned(), diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 41519d4d8e..ab809ccfc5 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -804,7 +804,7 @@ impl MmCoin for UtxoStandardCoin { async fn get_fee_to_send_taker_fee( &self, - dex_fee_amount: BigDecimal, + dex_fee_amount: DexFee, stage: FeeApproxStage, ) -> TradePreimageResult { utxo_common::get_fee_to_send_taker_fee(self, dex_fee_amount, stage).await diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 2b6cea5d96..65e2729361 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -1717,7 +1717,7 @@ impl MmCoin for ZCoin { async fn get_fee_to_send_taker_fee( &self, - _dex_fee_amount: BigDecimal, + _dex_fee_amount: DexFee, _stage: FeeApproxStage, ) -> TradePreimageResult { Ok(TradeFee { diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 1d78c7e230..8d207542fc 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -768,9 +768,8 @@ pub fn dex_fee_amount(base: &str, rel: &str, trade_amount: &MmNumber, min_tx_amo // Burn only the exceed amount because fee after 25% cut is less // than `min_tx_amount`. let burn_amount = &fee - min_tx_amount; - let new_fee = &fee - &burn_amount; - (new_fee, burn_amount) + (min_tx_amount.clone(), burn_amount) }; return DexFee::with_burn(fee, burn); diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index 6293d1f82f..a60ff2e149 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -993,13 +993,12 @@ impl TakerSwap { // do not use self.r().data here as it is not initialized at this step yet let stage = FeeApproxStage::StartSwap; let dex_fee = - dex_fee_amount_from_taker_coin(self.taker_coin.deref(), self.maker_coin.ticker(), &self.taker_amount) - .fee_amount(); + dex_fee_amount_from_taker_coin(self.taker_coin.deref(), self.maker_coin.ticker(), &self.taker_amount); let preimage_value = TradePreimageValue::Exact(self.taker_amount.to_decimal()); let fee_to_send_dex_fee_fut = self .taker_coin - .get_fee_to_send_taker_fee(dex_fee.to_decimal(), stage.clone()); + .get_fee_to_send_taker_fee(dex_fee.clone(), stage.clone()); let fee_to_send_dex_fee = match fee_to_send_dex_fee_fut.await { Ok(fee) => fee, Err(e) => { @@ -1028,7 +1027,7 @@ impl TakerSwap { }; let params = TakerSwapPreparedParams { - dex_fee: dex_fee.clone(), + dex_fee: dex_fee.total_spend_amount(), fee_to_send_dex_fee: fee_to_send_dex_fee.clone(), taker_payment_trade_fee: taker_payment_trade_fee.clone(), maker_payment_spend_trade_fee: maker_payment_spend_trade_fee.clone(), @@ -2292,7 +2291,7 @@ impl AtomicSwap for TakerSwap { if self.r().taker_fee.is_none() { result.push(LockedAmount { coin: self.taker_coin.ticker().to_owned(), - amount: taker_fee_amount.fee_amount(), + amount: taker_fee_amount.total_spend_amount(), trade_fee, }); } @@ -2355,9 +2354,9 @@ pub async fn check_balance_for_taker_swap( let params = match prepared_params { Some(params) => params, None => { - let dex_fee = dex_fee_amount_from_taker_coin(my_coin, other_coin.ticker(), &volume).fee_amount(); + let dex_fee = dex_fee_amount_from_taker_coin(my_coin, other_coin.ticker(), &volume); let fee_to_send_dex_fee = my_coin - .get_fee_to_send_taker_fee(dex_fee.to_decimal(), stage.clone()) + .get_fee_to_send_taker_fee(dex_fee.clone(), stage.clone()) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); @@ -2371,7 +2370,7 @@ pub async fn check_balance_for_taker_swap( .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, other_coin.ticker()))?; TakerSwapPreparedParams { - dex_fee, + dex_fee: dex_fee.total_spend_amount(), fee_to_send_dex_fee, taker_payment_trade_fee, maker_payment_spend_trade_fee, @@ -2446,12 +2445,12 @@ pub async fn taker_swap_trade_preimage( let dex_amount = dex_fee_amount_from_taker_coin(my_coin.deref(), other_coin_ticker, &my_coin_volume); let taker_fee = TradeFee { coin: my_coin_ticker.to_owned(), - amount: dex_amount.fee_amount(), + amount: dex_amount.total_spend_amount(), paid_from_trading_vol: false, }; let fee_to_send_taker_fee = my_coin - .get_fee_to_send_taker_fee(dex_amount.fee_amount().to_decimal(), stage.clone()) + .get_fee_to_send_taker_fee(dex_amount.clone(), stage.clone()) .await .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, my_coin_ticker))?; @@ -2467,7 +2466,7 @@ pub async fn taker_swap_trade_preimage( .mm_err(|e| TradePreimageRpcError::from_trade_preimage_error(e, other_coin_ticker))?; let prepared_params = TakerSwapPreparedParams { - dex_fee: dex_amount.fee_amount(), + dex_fee: dex_amount.total_spend_amount(), fee_to_send_dex_fee: fee_to_send_taker_fee.clone(), taker_payment_trade_fee: my_coin_trade_fee.clone(), maker_payment_spend_trade_fee: other_coin_trade_fee.clone(), @@ -2591,7 +2590,7 @@ pub async fn calc_max_taker_vol( let max_possible_2 = &max_possible - &max_trade_fee.amount; let max_dex_fee = dex_fee_amount_from_taker_coin(coin.deref(), other_coin, &max_possible_2); let max_fee_to_send_taker_fee = coin - .get_fee_to_send_taker_fee(max_dex_fee.fee_amount().to_decimal(), stage) + .get_fee_to_send_taker_fee(max_dex_fee.clone(), stage) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin))?; let min_max_possible = &max_possible_2 - &max_fee_to_send_taker_fee.amount; @@ -2602,7 +2601,7 @@ pub async fn calc_max_taker_vol( balance.to_fraction(), locked.to_fraction(), max_trade_fee.amount.to_fraction(), - max_dex_fee.fee_amount().to_fraction(), + max_dex_fee.total_spend_amount().to_fraction(), max_fee_to_send_taker_fee.amount.to_fraction() ); max_taker_vol_from_available(min_max_possible, my_coin, other_coin, &min_tx_amount) diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 877ab1e914..109e31b6cc 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -1053,14 +1053,13 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ // - `max_possible_2 = balance - locked_amount - max_trade_fee`, where `locked_amount = 0` let max_possible_2 = &qtum_balance - &max_trade_fee; // - `max_dex_fee = dex_fee(max_possible_2)` - let max_dex_fee = - dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount).fee_amount(); - debug!("max_dex_fee: {:?}", max_dex_fee.to_fraction()); + let max_dex_fee = dex_fee_amount("QTUM", "MYCOIN", &MmNumber::from(max_possible_2), &qtum_min_tx_amount); + debug!("max_dex_fee: {:?}", max_dex_fee.fee_amount().to_fraction()); // - `max_fee_to_send_taker_fee = fee_to_send_taker_fee(max_dex_fee)` // `taker_fee` is sent using general withdraw, and the fee get be obtained from withdraw result let max_fee_to_send_taker_fee = - block_on(coin.get_fee_to_send_taker_fee(max_dex_fee.to_decimal(), FeeApproxStage::TradePreimage)) + block_on(coin.get_fee_to_send_taker_fee(max_dex_fee, FeeApproxStage::TradePreimage)) .expect("!get_fee_to_send_taker_fee"); let max_fee_to_send_taker_fee = max_fee_to_send_taker_fee.amount.to_decimal(); debug!("max_fee_to_send_taker_fee: {}", max_fee_to_send_taker_fee);