diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 47722ccab2..78d7c52341 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -7418,9 +7418,9 @@ impl TakerCoinSwapOpsV2 for EthCoin { } /// Eth doesnt have preimages - async fn gen_taker_funding_spend_preimage( + async fn gen_taker_payment_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, _swap_unique_data: &[u8], ) -> GenPreimageResult { Ok(TxPreimageWithSig { @@ -7430,19 +7430,19 @@ impl TakerCoinSwapOpsV2 for EthCoin { } /// Eth doesnt have preimages - async fn validate_taker_funding_spend_preimage( + async fn validate_taker_payment_preimage( &self, - _gen_args: &GenTakerFundingSpendArgs<'_, Self>, + _gen_args: &GenTakerPaymentPreimageArgs<'_, Self>, _preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult { Ok(()) } /// Wrapper for [EthCoin::taker_payment_approve] - async fn sign_and_send_taker_funding_spend( + async fn sign_and_send_taker_payment( &self, _preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, _swap_unique_data: &[u8], ) -> Result { self.taker_payment_approve(args).await @@ -7505,6 +7505,12 @@ impl TakerCoinSwapOpsV2 for EthCoin { async fn extract_secret_v2(&self, _secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { self.extract_secret_v2_impl(spend_tx).await } + + async fn get_funding_fee(&self, _value: TradePreimageValue) -> TradePreimageResult { todo!() } + + async fn get_taker_payment_fee(&self) -> TradePreimageResult { todo!() } + + async fn get_taker_payment_spend_fee(&self) -> TradePreimageResult { todo!() } } impl CommonSwapOpsV2 for EthCoin { @@ -7612,4 +7618,8 @@ impl MakerCoinSwapOpsV2 for EthCoin { async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result { self.spend_maker_payment_v2_impl(args).await } + + async fn get_maker_payment_fee(&self, _value: TradePreimageValue) -> TradePreimageResult { todo!() } + + async fn get_maker_payment_spend_fee(&self) -> TradePreimageResult { todo!() } } diff --git a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs index a89ba16f57..fea3c49085 100644 --- a/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs +++ b/mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs @@ -4,7 +4,7 @@ use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_dec ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, SignedEthTx, SwapTxTypeWithSecretHash, TakerPaymentStateV2, TransactionErr, ValidateSwapV2TxError, ValidateSwapV2TxResult, ValidateTakerFundingArgs, TAKER_SWAP_V2}; -use crate::{FindPaymentSpendError, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, +use crate::{FindPaymentSpendError, FundingTxSpend, GenTakerPaymentPreimageArgs, GenTakerPaymentSpendArgs, SearchForFundingSpendErr}; use derive_more::Display; use enum_derives::EnumFromStringify; @@ -208,7 +208,7 @@ impl EthCoin { /// Function accepts taker payment transaction, returns taker approve payment transaction. pub(crate) async fn taker_payment_approve( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, ) -> Result { let gas_limit = match self.coin_type { EthCoinType::Eth | EthCoinType::Erc20 { .. } => U256::from(self.gas_limit_v2.taker.approve_payment), @@ -531,7 +531,7 @@ impl EthCoin { /// The `decoded` parameter should contain the transaction input data from the `ethTakerPayment` or `erc20TakerPayment` function of the EtomicSwapTakerV2 contract. async fn prepare_taker_payment_approve_data( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, decoded: Vec, token_address: Address, ) -> Result, PrepareTxDataError> { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 1626e9d8af..e0f2d6907a 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1370,6 +1370,8 @@ pub struct SendTakerFundingArgs<'a> { pub maker_pub: &'a [u8], /// DEX fee pub dex_fee: &'a DexFee, + /// The extra fee added to the trading volume to cover the taker payment transaction fees + pub taker_payment_fee: BigDecimal, /// Additional reward for maker (premium) pub premium_amount: BigDecimal, /// Actual volume of taker's payment @@ -1396,8 +1398,8 @@ pub struct RefundFundingSecretArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub watcher_reward: bool, } -/// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::gen_taker_funding_spend_preimage] -pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { +/// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::gen_taker_payment_preimage] +pub struct GenTakerPaymentPreimageArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { /// Taker payment transaction serialized to raw bytes pub funding_tx: &'a Coin::Tx, /// Maker's pubkey @@ -1412,6 +1414,8 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub taker_payment_time_lock: u64, /// The hash of the secret generated by maker pub maker_secret_hash: &'a [u8], + /// The extra fee added to the trading volume to cover the taker payment transaction fees + pub taker_payment_fee: BigDecimal, } /// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::validate_taker_funding] @@ -1432,6 +1436,8 @@ pub struct ValidateTakerFundingArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> { pub taker_pub: &'a Coin::Pubkey, /// DEX fee amount pub dex_fee: &'a DexFee, + /// The extra fee added to the trading volume to cover the taker payment transaction fees + pub taker_payment_fee: BigDecimal, /// Additional reward for maker (premium) pub premium_amount: BigDecimal, /// Actual volume of taker's payment @@ -1568,6 +1574,8 @@ impl From for ValidateSwapV2TxError { /// Enum covering error cases that can happen during taker funding spend preimage validation. #[derive(Debug, Display, EnumFromStringify)] pub enum ValidateTakerFundingSpendPreimageError { + /// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.). + NumConversion(String), /// Funding tx has no outputs FundingTxNoOutputs, /// Actual preimage fee is either too high or too small @@ -1588,6 +1596,10 @@ pub enum ValidateTakerFundingSpendPreimageError { Rpc(String), } +impl From for ValidateTakerFundingSpendPreimageError { + fn from(err: NumConversError) -> Self { ValidateTakerFundingSpendPreimageError::NumConversion(err.to_string()) } +} + impl From for ValidateTakerFundingSpendPreimageError { fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) } } @@ -1834,6 +1846,12 @@ pub trait MakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Syn /// Spend maker payment transaction async fn spend_maker_payment_v2(&self, args: SpendMakerPaymentArgs<'_, Self>) -> Result; + + /// Get the fee to be paid for the processing of the maker payment transaction. + async fn get_maker_payment_fee(&self, value: TradePreimageValue) -> TradePreimageResult; + + /// Get the fee to be paid for the processing of the maker payment spend transaction aka the claiming transaction. + async fn get_maker_payment_spend_fee(&self) -> TradePreimageResult; } #[async_trait] @@ -1993,24 +2011,24 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Syn ) -> Result>, SearchForFundingSpendErr>; /// Generates and signs a preimage spending funding tx to the combined taker payment - async fn gen_taker_funding_spend_preimage( + async fn gen_taker_payment_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> GenPreimageResult; /// Validates taker funding spend preimage generated and signed by maker - async fn validate_taker_funding_spend_preimage( + async fn validate_taker_payment_preimage( &self, - gen_args: &GenTakerFundingSpendArgs<'_, Self>, + gen_args: &GenTakerPaymentPreimageArgs<'_, Self>, preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult; /// Sign and send a spending funding tx to the combined taker payment - async fn sign_and_send_taker_funding_spend( + async fn sign_and_send_taker_payment( &self, preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> Result; @@ -2056,6 +2074,18 @@ pub trait TakerCoinSwapOpsV2: ParseCoinAssocTypes + CommonSwapOpsV2 + Send + Syn ) -> MmResult; async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String>; + + /// Get the fee to be paid for the processing of the funding transaction. + /// Txs in success case (no refund) go as follows: + /// Chain A: funding -> taker payment -> taker payment spend (aka preimage) (to the maker & dex) + /// Chain B: maker payment -> maker payment spend (aka claiming) (to the taker) + async fn get_funding_fee(&self, value: TradePreimageValue) -> TradePreimageResult; + + /// Get the fee to be paid for the processing of the taker payment transaction. + async fn get_taker_payment_fee(&self) -> TradePreimageResult; + + /// Get the fee to be paid for the processing of the taker payment spend transaction aka the preimage transaction. + async fn get_taker_payment_spend_fee(&self) -> TradePreimageResult; } #[async_trait] diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 13c8beb7a5..30844fb797 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -6,18 +6,19 @@ use super::{CoinBalance, CommonSwapOpsV2, FindPaymentSpendError, FundingTxSpend, use crate::coin_errors::{AddressFromPubkeyError, ValidatePaymentResult}; use crate::hd_wallet::{AddrToString, HDAddressSelector}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, ConfirmPaymentInput, - FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, - MmCoinEnum, NegotiateSwapContractAddrErr, ParseCoinAssocTypes, PaymentInstructionArgs, - PaymentInstructions, PaymentInstructionsErr, RawTransactionResult, RefundFundingSecretArgs, - RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, - SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, SignatureResult, SpendPaymentArgs, - TakerCoinSwapOpsV2, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, - TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, ValidateTakerFundingArgs, - ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageResult, VerificationResult, - WaitForHTLCTxSpendArgs, WatcherOps, WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WeakSpawner, WithdrawFut, WithdrawRequest}; + FeeApproxStage, FoundSwapTxSpend, GenPreimageResult, GenTakerPaymentPreimageArgs, + GenTakerPaymentSpendArgs, MmCoinEnum, NegotiateSwapContractAddrErr, ParseCoinAssocTypes, + PaymentInstructionArgs, PaymentInstructions, PaymentInstructionsErr, RawTransactionResult, + RefundFundingSecretArgs, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionRequest, + SignatureResult, SpendPaymentArgs, TakerCoinSwapOpsV2, TradePreimageFut, TradePreimageResult, + TradePreimageValue, Transaction, TransactionErr, TransactionResult, TxMarshalingErr, TxPreimageWithSig, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxResult, + ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageResult, VerificationResult, WaitForHTLCTxSpendArgs, WatcherOps, + WatcherReward, WatcherRewardError, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WeakSpawner, WithdrawFut, WithdrawRequest}; use crate::{DexFee, ToBytes, ValidateWatcherSpendInput}; use async_trait::async_trait; use common::executor::AbortedError; @@ -508,26 +509,26 @@ impl TakerCoinSwapOpsV2 for TestCoin { todo!() } - async fn gen_taker_funding_spend_preimage( + async fn gen_taker_payment_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> GenPreimageResult { todo!() } - async fn validate_taker_funding_spend_preimage( + async fn validate_taker_payment_preimage( &self, - gen_args: &GenTakerFundingSpendArgs<'_, Self>, + gen_args: &GenTakerPaymentPreimageArgs<'_, Self>, preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult { todo!() } - async fn sign_and_send_taker_funding_spend( + async fn sign_and_send_taker_payment( &self, preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> Result { todo!() @@ -578,10 +579,16 @@ impl TakerCoinSwapOpsV2 for TestCoin { async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { unimplemented!() } + + async fn get_funding_fee(&self, value: TradePreimageValue) -> TradePreimageResult { todo!() } + + async fn get_taker_payment_fee(&self) -> TradePreimageResult { todo!() } + + async fn get_taker_payment_spend_fee(&self) -> TradePreimageResult { todo!() } } impl CommonSwapOpsV2 for TestCoin { - fn derive_htlc_pubkey_v2(&self, _swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } + fn derive_htlc_pubkey_v2(&self, swap_unique_data: &[u8]) -> Self::Pubkey { todo!() } fn derive_htlc_pubkey_v2_bytes(&self, _swap_unique_data: &[u8]) -> Vec { todo!() } diff --git a/mm2src/coins/utxo.rs b/mm2src/coins/utxo.rs index b03e27f6c1..60727ee3b0 100644 --- a/mm2src/coins/utxo.rs +++ b/mm2src/coins/utxo.rs @@ -571,7 +571,7 @@ pub struct UtxoCoinConf { /// Address and privkey checksum type pub checksum_type: ChecksumType, /// Fork id used in sighash - pub fork_id: u32, + pub fork_id: u8, /// Signature version pub signature_version: SignatureVersion, pub required_confirmations: AtomicU64, diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index 21191f4e85..9e577fb9bc 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -984,7 +984,7 @@ impl UtxoSignerOps for QtumCoin { }) } - fn fork_id(&self) -> u32 { self.utxo_arc.conf.fork_id } + fn fork_id(&self) -> u8 { self.utxo_arc.conf.fork_id } fn branch_id(&self) -> u32 { self.utxo_arc.conf.consensus_branch_id } diff --git a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs index 90077f1edd..16393993b0 100644 --- a/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs +++ b/mm2src/coins/utxo/utxo_builder/utxo_conf_builder.rs @@ -33,6 +33,8 @@ pub enum UtxoConfError { InvalidVersionGroupId(String), InvalidAddressFormat(String), InvalidDecimals(String), + #[display(fmt = "Invalid fork id: {}, valid fork ids are values between 0x00 - 0xff", _0)] + InvalidForkId(String), } impl From for UtxoConfError { @@ -90,8 +92,8 @@ impl<'a> UtxoConfBuilder<'a> { let tx_fee_volatility_percent = self.tx_fee_volatility_percent(); let version_group_id = self.version_group_id(tx_version, overwintered)?; let consensus_branch_id = self.consensus_branch_id(tx_version)?; - let signature_version = self.signature_version(); - let fork_id = self.fork_id(); + let signature_version = self.signature_version()?; + let fork_id = self.fork_id()?; // should be sufficient to detect zcash by overwintered flag let zcash = overwintered; @@ -242,23 +244,23 @@ impl<'a> UtxoConfBuilder<'a> { Ok(consensus_branch_id) } - fn signature_version(&self) -> SignatureVersion { - let default_signature_version = if self.ticker == "BCH" || self.fork_id() != 0 { + fn signature_version(&self) -> UtxoConfResult { + let default_signature_version = if self.ticker == "BCH" || self.fork_id()? != 0 { SignatureVersion::ForkId } else { SignatureVersion::Base }; - json::from_value(self.conf["signature_version"].clone()).unwrap_or(default_signature_version) + Ok(json::from_value(self.conf["signature_version"].clone()).unwrap_or(default_signature_version)) } - fn fork_id(&self) -> u32 { + fn fork_id(&self) -> UtxoConfResult { let default_fork_id = match self.ticker { "BCH" => "0x40", _ => "0x0", }; let hex_string = self.conf["fork_id"].as_str().unwrap_or(default_fork_id); - let fork_id = u32::from_str_radix(hex_string.trim_start_matches("0x"), 16).unwrap(); - fork_id + u8::from_str_radix(hex_string.trim_start_matches("0x"), 16) + .map_to_mm(|e| UtxoConfError::InvalidForkId(e.to_string())) } fn required_confirmations(&self) -> u64 { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 3641ce8d8a..4a61987ea2 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -13,21 +13,22 @@ use crate::utxo::utxo_hd_wallet::UtxoHDAddress; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::watcher_common::validate_watcher_reward; use crate::{scan_for_new_addresses_impl, CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, ConfirmPaymentInput, - DexFee, DexFeeBurnDestination, GenPreimageResult, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs, + DexFee, DexFeeBurnDestination, GenPreimageResult, GenTakerPaymentPreimageArgs, GenTakerPaymentSpendArgs, GetWithdrawSenderAddress, RawTransactionError, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundFundingSecretArgs, RefundMakerPaymentSecretArgs, RefundPaymentArgs, RewardTarget, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SendTakerFundingArgs, SignRawTransactionEnum, SignRawTransactionRequest, SignUtxoTransactionParams, SignatureError, SignatureResult, SpendMakerPaymentArgs, SpendPaymentArgs, - SwapOps, SwapTxTypeWithSecretHash, TradePreimageValue, TransactionData, TransactionFut, TransactionResult, - TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, ValidateAddressResult, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, ValidateSwapV2TxError, - ValidateSwapV2TxResult, ValidateTakerFundingArgs, ValidateTakerFundingSpendPreimageError, - ValidateTakerFundingSpendPreimageResult, ValidateTakerPaymentSpendPreimageError, - ValidateTakerPaymentSpendPreimageResult, ValidateWatcherSpendInput, VerificationError, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, - INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; + SwapOps, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, TradePreimageValue, TransactionData, + TransactionFut, TransactionResult, TxFeeDetails, TxGenError, TxMarshalingErr, TxPreimageWithSig, + ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + ValidateSwapV2TxError, ValidateSwapV2TxResult, ValidateTakerFundingArgs, + ValidateTakerFundingSpendPreimageError, ValidateTakerFundingSpendPreimageResult, + ValidateTakerPaymentSpendPreimageError, ValidateTakerPaymentSpendPreimageResult, + ValidateWatcherSpendInput, VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, + WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawResult, WithdrawSenderAddress, + EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, INVALID_SCRIPT_ERR_LOG, + INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use crate::{MmCoinEnum, WatcherReward, WatcherRewardError}; use base64::engine::general_purpose::STANDARD; use base64::Engine; @@ -915,6 +916,191 @@ enum FundingSpendFeeSetting { UseExact(u64), } +pub mod tx_sizes { + use super::*; + + /// Returns the taker payment transaction size in vbytes. + pub fn get_taker_payment_tx_size(coin: &impl UtxoCommonOps) -> usize { + let preimage = TransactionInputSigner { + lock_time: 0, + version: coin.as_ref().conf.tx_version, + n_time: None, + overwintered: coin.as_ref().conf.overwintered, + inputs: vec![UnsignedTransactionInput { + sequence: SEQUENCE_FINAL, + previous_output: OutPoint { + hash: H256::default(), + index: DEFAULT_SWAP_VOUT as u32, + }, + prev_script: Vec::new().into(), + amount: 0, + }], + outputs: vec![ + // This output is used in taker payment spent tx. + TransactionOutput { + value: 0, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::default_address_hash()).to_bytes(), + }, + ], + expiry_height: 0, + join_splits: vec![], + shielded_spends: vec![], + shielded_outputs: vec![], + value_balance: 0, + version_group_id: coin.as_ref().conf.version_group_id, + consensus_branch_id: coin.as_ref().conf.consensus_branch_id, + zcash: coin.as_ref().conf.zcash, + posv: coin.as_ref().conf.is_posv, + str_d_zeel: None, + hash_algo: coin.as_ref().tx_hash_algo.into(), + v_extra_payload: None, + }; + + let redeem_script = swap_proto_v2_scripts::taker_funding_script( + 0, + H160::default().as_slice(), + &Public::default(), + &Public::default(), + ); + + let mut final_tx: UtxoTx = preimage.into(); + final_tx.inputs[0].script_sig = Builder::default() + // Maximum of 72 byte maker signature + a sighash byte. + .push_data(&[0; 72 + 1]) + // Maximum of 72 byte taker signature + a sighash byte. + .push_data(&[0; 72 + 1]) + .push_opcode(Opcode::OP_1) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + + // We aren't spending from the segwit address, we are spending from a P2SH address. + tx_size_in_v_bytes(&UtxoAddressFormat::Standard, &final_tx) + } + + /// Returns the taker payment spend transaction size in vbytes. + pub fn get_taker_payment_spend_tx_size(coin: &impl UtxoCommonOps) -> usize { + let preimage = TransactionInputSigner { + lock_time: 0, + version: coin.as_ref().conf.tx_version, + n_time: None, + overwintered: coin.as_ref().conf.overwintered, + inputs: vec![UnsignedTransactionInput { + sequence: SEQUENCE_FINAL, + previous_output: OutPoint { + hash: H256::default(), + index: DEFAULT_SWAP_VOUT as u32, + }, + prev_script: Vec::new().into(), + amount: 0, + }], + outputs: vec![ + // An output for the dex. + TransactionOutput { + value: 0, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::default_address_hash()).to_bytes(), + }; + // And another for the maker. + 2 + ], + expiry_height: 0, + join_splits: vec![], + shielded_spends: vec![], + shielded_outputs: vec![], + value_balance: 0, + version_group_id: coin.as_ref().conf.version_group_id, + consensus_branch_id: coin.as_ref().conf.consensus_branch_id, + zcash: coin.as_ref().conf.zcash, + posv: coin.as_ref().conf.is_posv, + str_d_zeel: None, + hash_algo: coin.as_ref().tx_hash_algo.into(), + v_extra_payload: None, + }; + + let redeem_script = swap_proto_v2_scripts::taker_payment_script( + 0, + H160::default().as_slice(), + &Public::default(), + &Public::default(), + ); + + let mut final_tx: UtxoTx = preimage.into(); + final_tx.inputs[0].script_sig = Builder::default() + // Maximum of 72 byte maker signature + a sighash byte. + .push_data(&[0; 72 + 1]) + // Maximum of 72 byte taker signature + a sighash byte. + .push_data(&[0; 72 + 1]) + // Maker's secret hash. + .push_data(&[0; 32]) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + + // We aren't spending from the segwit address, we are spending from a P2SH address. + tx_size_in_v_bytes(&UtxoAddressFormat::Standard, &final_tx) + } + + /// Returns the maker payment spend transaction size in vbytes. + pub fn get_maker_payment_spend_tx_size(coin: &impl UtxoCommonOps) -> usize { + let preimage = TransactionInputSigner { + lock_time: 0, + version: coin.as_ref().conf.tx_version, + n_time: None, + overwintered: coin.as_ref().conf.overwintered, + inputs: vec![UnsignedTransactionInput { + sequence: SEQUENCE_FINAL, + previous_output: OutPoint { + hash: H256::default(), + index: DEFAULT_SWAP_VOUT as u32, + }, + prev_script: Vec::new().into(), + amount: 0, + }], + outputs: vec![ + // An output for the taker + TransactionOutput { + value: 0, + script_pubkey: Builder::build_p2sh(&AddressHashEnum::default_address_hash()).to_bytes(), + }, + ], + expiry_height: 0, + join_splits: vec![], + shielded_spends: vec![], + shielded_outputs: vec![], + value_balance: 0, + version_group_id: coin.as_ref().conf.version_group_id, + consensus_branch_id: coin.as_ref().conf.consensus_branch_id, + zcash: coin.as_ref().conf.zcash, + posv: coin.as_ref().conf.is_posv, + str_d_zeel: None, + hash_algo: coin.as_ref().tx_hash_algo.into(), + v_extra_payload: None, + }; + + let redeem_script = swap_proto_v2_scripts::maker_payment_script( + 0, + H160::default().as_slice(), + H160::default().as_slice(), + &Public::default(), + &Public::default(), + ); + + let mut final_tx: UtxoTx = preimage.into(); + final_tx.inputs[0].script_sig = Builder::default() + // Maximum of 72 byte taker signature + a sighash byte. + .push_data(&[0; 72 + 1]) + // Maker's secret hash. + .push_data(&[0; 32]) + .push_opcode(Opcode::OP_1) + .push_opcode(Opcode::OP_0) + .push_data(&redeem_script) + .into_bytes(); + + // We aren't spending from the segwit address, we are spending from a P2SH address. + tx_size_in_v_bytes(&UtxoAddressFormat::Standard, &final_tx) + } +} + async fn p2sh_spending_tx_preimage( coin: &T, prev_tx: &UtxoTx, @@ -1019,12 +1205,16 @@ pub async fn p2sh_spending_tx(coin: &T, input: P2SHSpendingTxI type GenPreimageResInner = MmResult; -async fn gen_taker_funding_spend_preimage( - coin: &T, - args: &GenTakerFundingSpendArgs<'_, T>, +async fn gen_taker_funding_spend_preimage( + coin: &Coin, + args: &GenTakerPaymentPreimageArgs<'_, T>, n_time: NTimeSetting, fee: FundingSpendFeeSetting, -) -> GenPreimageResInner { +) -> GenPreimageResInner +where + T: UtxoCommonOps, + Coin: UtxoCommonOps + TakerCoinSwapOpsV2, +{ let payment_time_lock = args .taker_payment_time_lock .try_into() @@ -1044,10 +1234,15 @@ async fn gen_taker_funding_spend_preimage( .value; let fee = match fee { - FundingSpendFeeSetting::GetFromCoin => coin - .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await - .map_mm_err()?, + FundingSpendFeeSetting::GetFromCoin => { + let calculated_fee = coin.get_taker_payment_fee().await.unwrap().amount.to_decimal(); + let calculated_fee = sat_from_big_decimal(&calculated_fee, coin.as_ref().decimals).mm_err(|e| e.into())?; + let taker_instructed_fee = + sat_from_big_decimal(&args.taker_payment_fee, coin.as_ref().decimals).mm_err(|e| e.into())?; + // Cap the paid fee to the taker instructed fee. + // Since any fee above that will be paid from our (maker) trading volume. + calculated_fee.min(taker_instructed_fee) + }, FundingSpendFeeSetting::UseExact(f) => f, }; @@ -1076,11 +1271,15 @@ async fn gen_taker_funding_spend_preimage( .map_to_mm(TxGenError::Legacy) } -pub async fn gen_and_sign_taker_funding_spend_preimage( - coin: &T, - args: &GenTakerFundingSpendArgs<'_, T>, +pub async fn gen_and_sign_taker_payment_preimage( + coin: &Coin, + args: &GenTakerPaymentPreimageArgs<'_, T>, htlc_keypair: &KeyPair, -) -> GenPreimageResult { +) -> GenPreimageResult +where + T: UtxoCommonOps, + Coin: UtxoCommonOps + TakerCoinSwapOpsV2, +{ let funding_time_lock = args .funding_time_lock .try_into() @@ -1113,11 +1312,15 @@ pub async fn gen_and_sign_taker_funding_spend_preimage( /// Common implementation of taker funding spend preimage validation for UTXO coins. /// Checks maker's signature and compares received preimage with the expected tx. -pub async fn validate_taker_funding_spend_preimage( - coin: &T, - gen_args: &GenTakerFundingSpendArgs<'_, T>, +pub async fn validate_taker_payment_preimage( + coin: &Coin, + gen_args: &GenTakerPaymentPreimageArgs<'_, T>, preimage: &TxPreimageWithSig, -) -> ValidateTakerFundingSpendPreimageResult { +) -> ValidateTakerFundingSpendPreimageResult +where + T: UtxoCommonOps, + Coin: UtxoCommonOps + TakerCoinSwapOpsV2, +{ let funding_amount = gen_args .funding_tx .first_output() @@ -1137,19 +1340,16 @@ pub async fn validate_taker_funding_spend_preimage( ))); } - let expected_fee = coin - .get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) - .await - .map_mm_err()?; - let actual_fee = funding_amount - payment_amount; + let instructed_fee = + sat_from_big_decimal(&gen_args.taker_payment_fee, coin.as_ref().decimals).mm_err(|e| e.into())?; - let fee_div = expected_fee as f64 / actual_fee as f64; - - if !(0.9..=1.1).contains(&fee_div) { + if actual_fee > instructed_fee { + // Reject a high fee. Such fee will typically be paid from the maker's trading volume, + // but the maker might fail the swap later and this would make us incur this fee instead. return MmError::err(ValidateTakerFundingSpendPreimageError::UnexpectedPreimageFee(format!( - "Too large difference between expected {} and actual {} fees", - expected_fee, actual_fee + "A fee of {} was used, more than the agreed upon fee of {}.", + actual_fee, instructed_fee ))); } @@ -1199,10 +1399,10 @@ pub async fn validate_taker_funding_spend_preimage( } /// Common implementation of taker funding spend finalization and broadcast for UTXO coins. -pub async fn sign_and_send_taker_funding_spend( +pub async fn sign_and_send_taker_payment( coin: &T, preimage: &TxPreimageWithSig, - gen_args: &GenTakerFundingSpendArgs<'_, T>, + gen_args: &GenTakerPaymentPreimageArgs<'_, T>, htlc_keypair: &KeyPair, ) -> Result { let redeem_script = swap_proto_v2_scripts::taker_funding_script( @@ -1227,13 +1427,13 @@ pub async fn sign_and_send_taker_funding_spend( SIGHASH_ALL, coin.as_ref().conf.fork_id )); - let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; + let sig_hash_all_fork_id = SIGHASH_ALL | coin.as_ref().conf.fork_id; let mut maker_signature_with_sighash = preimage.signature.to_vec(); maker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(maker_signature_with_sighash); - let mut taker_signature_with_sighash: Vec = taker_signature.take(); + let mut taker_signature_with_sighash = taker_signature.take(); taker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(taker_signature_with_sighash); @@ -1513,14 +1713,14 @@ pub async fn sign_and_broadcast_taker_payment_spend( )); let mut taker_signature_with_sighash = preimage.signature.to_vec(); let taker_sig_hash = match gen_args.dex_fee { - DexFee::Standard(_) => (SIGHASH_SINGLE | coin.as_ref().conf.fork_id) as u8, - DexFee::WithBurn { .. } | DexFee::NoFee => (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8, + DexFee::Standard(_) => SIGHASH_SINGLE | coin.as_ref().conf.fork_id, + DexFee::WithBurn { .. } | DexFee::NoFee => SIGHASH_ALL | coin.as_ref().conf.fork_id, }; taker_signature_with_sighash.push(taker_sig_hash); drop_mutability!(taker_signature_with_sighash); - let sig_hash_all_fork_id = (SIGHASH_ALL | coin.as_ref().conf.fork_id) as u8; + let sig_hash_all_fork_id = SIGHASH_ALL | coin.as_ref().conf.fork_id; let mut maker_signature_with_sighash: Vec = maker_signature.take(); maker_signature_with_sighash.push(sig_hash_all_fork_id); drop_mutability!(maker_signature_with_sighash); @@ -2100,7 +2300,7 @@ fn verify_p2pk_input_pubkey( unsigned_tx: &TransactionInputSigner, index: usize, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> Result { // Extract the signature from the scriptSig. let signature = script.extract_signature()?; @@ -4148,6 +4348,9 @@ pub fn get_trade_fee(coin: T) -> Box) -> Result { utxo_common::spend_maker_payment_v2(self, args).await } + + async fn get_maker_payment_fee(&self, value: TradePreimageValue) -> TradePreimageResult { + let mut fee = utxo_common::get_sender_trade_fee(self, value, FeeApproxStage::StartSwap).await?; + fee.paid_from_trading_vol = true; + Ok(fee) + } + + async fn get_maker_payment_spend_fee(&self) -> TradePreimageResult { + let maker_payment_spend_tx_size = utxo_common::tx_sizes::get_maker_payment_spend_tx_size(self); + let fee_sat = self + .get_htlc_spend_fee(maker_payment_spend_tx_size as u64, &FeeApproxStage::TradePreimage) + .await + .map_mm_err()?; + let amount = big_decimal_from_sat_unsigned(fee_sat, self.as_ref().decimals).into(); + Ok(TradeFee { + coin: self.as_ref().conf.ticker.clone(), + amount, + paid_from_trading_vol: true, + }) + } } #[async_trait] @@ -727,31 +747,31 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { } } - async fn gen_taker_funding_spend_preimage( + async fn gen_taker_payment_preimage( &self, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> GenPreimageResult { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::gen_and_sign_taker_funding_spend_preimage(self, args, &htlc_keypair).await + utxo_common::gen_and_sign_taker_payment_preimage(self, args, &htlc_keypair).await } - async fn validate_taker_funding_spend_preimage( + async fn validate_taker_payment_preimage( &self, - gen_args: &GenTakerFundingSpendArgs<'_, Self>, + gen_args: &GenTakerPaymentPreimageArgs<'_, Self>, preimage: &TxPreimageWithSig, ) -> ValidateTakerFundingSpendPreimageResult { - utxo_common::validate_taker_funding_spend_preimage(self, gen_args, preimage).await + utxo_common::validate_taker_payment_preimage(self, gen_args, preimage).await } - async fn sign_and_send_taker_funding_spend( + async fn sign_and_send_taker_payment( &self, preimage: &TxPreimageWithSig, - args: &GenTakerFundingSpendArgs<'_, Self>, + args: &GenTakerPaymentPreimageArgs<'_, Self>, swap_unique_data: &[u8], ) -> Result { let htlc_keypair = self.derive_htlc_key_pair(swap_unique_data); - utxo_common::sign_and_send_taker_funding_spend(self, preimage, args, &htlc_keypair).await + utxo_common::sign_and_send_taker_payment(self, preimage, args, &htlc_keypair).await } async fn refund_combined_taker_payment( @@ -822,6 +842,40 @@ impl TakerCoinSwapOpsV2 for UtxoStandardCoin { async fn extract_secret_v2(&self, secret_hash: &[u8], spend_tx: &Self::Tx) -> Result<[u8; 32], String> { utxo_common::extract_secret_v2(secret_hash, spend_tx) } + + async fn get_funding_fee(&self, value: TradePreimageValue) -> TradePreimageResult { + let mut fee = utxo_common::get_sender_trade_fee(self, value, FeeApproxStage::StartSwap).await?; + fee.paid_from_trading_vol = true; + Ok(fee) + } + + async fn get_taker_payment_fee(&self) -> TradePreimageResult { + let taker_payment_tx_size = utxo_common::tx_sizes::get_taker_payment_tx_size(self); + let fee_sat = self + .get_htlc_spend_fee(taker_payment_tx_size as u64, &FeeApproxStage::TradePreimage) + .await + .map_mm_err()?; + let amount = big_decimal_from_sat_unsigned(fee_sat, self.as_ref().decimals).into(); + Ok(TradeFee { + coin: self.as_ref().conf.ticker.clone(), + amount, + paid_from_trading_vol: true, + }) + } + + async fn get_taker_payment_spend_fee(&self) -> TradePreimageResult { + let taker_payment_spend_tx_size = utxo_common::tx_sizes::get_taker_payment_spend_tx_size(self); + let fee_sat = self + .get_htlc_spend_fee(taker_payment_spend_tx_size as u64, &FeeApproxStage::TradePreimage) + .await + .map_mm_err()?; + let amount = big_decimal_from_sat_unsigned(fee_sat, self.as_ref().decimals).into(); + Ok(TradeFee { + coin: self.as_ref().conf.ticker.clone(), + amount, + paid_from_trading_vol: true, + }) + } } impl CommonSwapOpsV2 for UtxoStandardCoin { @@ -1067,7 +1121,7 @@ impl UtxoSignerOps for UtxoStandardCoin { }) } - fn fork_id(&self) -> u32 { self.utxo_arc.conf.fork_id } + fn fork_id(&self) -> u8 { self.utxo_arc.conf.fork_id } fn branch_id(&self) -> u32 { self.utxo_arc.conf.consensus_branch_id } diff --git a/mm2src/coins/utxo_signer/src/lib.rs b/mm2src/coins/utxo_signer/src/lib.rs index 3f9ee23095..87a4e39a72 100644 --- a/mm2src/coins/utxo_signer/src/lib.rs +++ b/mm2src/coins/utxo_signer/src/lib.rs @@ -126,7 +126,7 @@ pub trait UtxoSignerOps { fn trezor_coin(&self) -> UtxoSignTxResult; - fn fork_id(&self) -> u32; + fn fork_id(&self) -> u8; fn branch_id(&self) -> u32; diff --git a/mm2src/coins/utxo_signer/src/sign_common.rs b/mm2src/coins/utxo_signer/src/sign_common.rs index aa2ab355ef..c57ba3feae 100644 --- a/mm2src/coins/utxo_signer/src/sign_common.rs +++ b/mm2src/coins/utxo_signer/src/sign_common.rs @@ -1,4 +1,4 @@ -use crate::Signature; +use crate::{with_key_pair::SIGHASH_ALL, Signature}; use chain::{Transaction as UtxoTx, TransactionInput}; use keys::bytes::Bytes; use keys::Public as PublicKey; @@ -33,7 +33,7 @@ pub(crate) fn complete_tx(unsigned: TransactionInputSigner, signed_inputs: Vec TransactionInput { let script_sig = script_sig(signature, fork_id); @@ -50,7 +50,7 @@ pub(crate) fn p2pk_spend_with_signature( pub(crate) fn p2pkh_spend_with_signature( unsigned_input: &UnsignedTransactionInput, public_key: &PublicKey, - fork_id: u32, + fork_id: u8, signature: Signature, ) -> TransactionInput { let script_sig = script_sig_with_pub(public_key, fork_id, signature); @@ -68,7 +68,7 @@ pub(crate) fn p2sh_spend_with_signature( unsigned_input: &UnsignedTransactionInput, redeem_script: Script, script_data: Script, - fork_id: u32, + fork_id: u8, signature: Signature, ) -> TransactionInput { let script_sig = script_sig(signature, fork_id); @@ -93,7 +93,7 @@ pub(crate) fn p2sh_spend_with_signature( pub(crate) fn p2wpkh_spend_with_signature( unsigned_input: &UnsignedTransactionInput, public_key: &PublicKey, - fork_id: u32, + fork_id: u8, signature: Signature, ) -> TransactionInput { let script_sig = script_sig(signature, fork_id); @@ -106,7 +106,7 @@ pub(crate) fn p2wpkh_spend_with_signature( } } -pub(crate) fn script_sig_with_pub(public_key: &PublicKey, fork_id: u32, signature: Signature) -> Bytes { +pub(crate) fn script_sig_with_pub(public_key: &PublicKey, fork_id: u8, signature: Signature) -> Bytes { let script_sig = script_sig(signature, fork_id); let builder = Builder::default(); builder @@ -115,12 +115,12 @@ pub(crate) fn script_sig_with_pub(public_key: &PublicKey, fork_id: u32, signatur .into_bytes() } -pub(crate) fn script_sig(mut signature: Signature, fork_id: u32) -> Bytes { +pub(crate) fn script_sig(mut signature: Signature, fork_id: u8) -> Bytes { let mut sig_script = Bytes::default(); sig_script.append(&mut signature); // Using SIGHASH_ALL only for now - sig_script.append(&mut Bytes::from(vec![1 | fork_id as u8])); + sig_script.append(&mut Bytes::from(vec![SIGHASH_ALL | fork_id])); sig_script } diff --git a/mm2src/coins/utxo_signer/src/with_key_pair.rs b/mm2src/coins/utxo_signer/src/with_key_pair.rs index b62fa695f2..f1782adf90 100644 --- a/mm2src/coins/utxo_signer/src/with_key_pair.rs +++ b/mm2src/coins/utxo_signer/src/with_key_pair.rs @@ -9,9 +9,9 @@ use mm2_err_handle::prelude::*; use primitives::hash::H256; use script::{Builder, Script, ScriptType, SignatureVersion, TransactionInputSigner, UnsignedTransactionInput}; -pub const SIGHASH_ALL: u32 = 1; -pub const _SIGHASH_NONE: u32 = 2; -pub const SIGHASH_SINGLE: u32 = 3; +pub const SIGHASH_ALL: u8 = 1; +pub const _SIGHASH_NONE: u8 = 2; +pub const SIGHASH_SINGLE: u8 = 3; pub type UtxoSignWithKeyPairResult = Result>; @@ -47,7 +47,7 @@ pub fn sign_tx( unsigned: TransactionInputSigner, key_pair: &KeyPair, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let signed_inputs = unsigned .inputs @@ -74,7 +74,7 @@ pub fn p2pk_spend( input_index: usize, key_pair: &KeyPair, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let unsigned_input = get_input(signer, input_index)?; @@ -105,7 +105,7 @@ pub fn p2pkh_spend( input_index: usize, key_pair: &KeyPair, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let unsigned_input = get_input(signer, input_index)?; @@ -143,7 +143,7 @@ pub fn p2sh_spend( script_data: Script, redeem_script: Script, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let unsigned_input = get_input(signer, input_index)?; @@ -171,7 +171,7 @@ pub fn p2wpkh_spend( input_index: usize, key_pair: &KeyPair, signature_version: SignatureVersion, - fork_id: u32, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let unsigned_input = get_input(signer, input_index)?; @@ -209,8 +209,8 @@ pub fn calc_and_sign_sighash( output_script: &Script, key_pair: &KeyPair, signature_version: SignatureVersion, - sighash_type: u32, - fork_id: u32, + sighash_type: u8, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let sighash = signature_hash_to_sign( signer, @@ -228,8 +228,8 @@ pub fn signature_hash_to_sign( input_index: usize, output_script: &Script, signature_version: SignatureVersion, - sighash_type: u32, - fork_id: u32, + sighash_type: u8, + fork_id: u8, ) -> UtxoSignWithKeyPairResult { let input_amount = get_input(signer, input_index)?.amount; diff --git a/mm2src/coins/utxo_signer/src/with_trezor.rs b/mm2src/coins/utxo_signer/src/with_trezor.rs index 3909175a1b..dae22986d4 100644 --- a/mm2src/coins/utxo_signer/src/with_trezor.rs +++ b/mm2src/coins/utxo_signer/src/with_trezor.rs @@ -17,7 +17,7 @@ pub struct TrezorTxSigner<'a, TxP> { pub tx_provider: TxP, pub trezor_coin: String, pub params: UtxoSignTxParams, - pub fork_id: u32, + pub fork_id: u8, pub branch_id: u32, } diff --git a/mm2src/mm2_bitcoin/script/src/sign.rs b/mm2src/mm2_bitcoin/script/src/sign.rs index a0115172d1..e7388649a8 100644 --- a/mm2src/mm2_bitcoin/script/src/sign.rs +++ b/mm2src/mm2_bitcoin/script/src/sign.rs @@ -38,8 +38,8 @@ pub enum SighashBase { Single = 3, } -impl From for u32 { - fn from(s: SighashBase) -> Self { s as u32 } +impl From for u8 { + fn from(s: SighashBase) -> Self { s as u8 } } /// Signature hash type. [Documentation](https://en.bitcoin.it/wiki/OP_CHECKSIG#Procedure_for_Hashtype_SIGHASH_SINGLE) @@ -50,16 +50,13 @@ pub struct Sighash { pub fork_id: bool, } -impl From for u32 { +impl From for u8 { fn from(s: Sighash) -> Self { - let base = s.base as u32; - let base = if s.anyone_can_pay { base | 0x80 } else { base }; + let mut base = s.base as u8; + base = if s.anyone_can_pay { base | 0x80 } else { base }; + base = if s.fork_id { base | 0x40 } else { base }; - if s.fork_id { - base | 0x40 - } else { - base - } + base } } @@ -73,7 +70,7 @@ impl Sighash { } /// Used by SCRIPT_VERIFY_STRICTENC - pub fn is_defined(version: SignatureVersion, u: u32) -> bool { + pub fn is_defined(version: SignatureVersion, u: u8) -> bool { // reset anyone_can_pay && fork_id (if applicable) bits let u = match version { SignatureVersion::ForkId => u & !(0x40 | 0x80), @@ -85,7 +82,7 @@ impl Sighash { } /// Creates Sighash from any u, even if is_defined() == false - pub fn from_u32(version: SignatureVersion, u: u32) -> Self { + pub fn from_u8(version: SignatureVersion, u: u8) -> Self { let anyone_can_pay = (u & 0x80) == 0x80; let fork_id = version == SignatureVersion::ForkId && (u & 0x40) == 0x40; let base = match u & 0x1f { @@ -236,9 +233,9 @@ impl TransactionInputSigner { input_amount: u64, script_pubkey: &Script, sigversion: SignatureVersion, - sighashtype: u32, + sighashtype: u8, ) -> H256 { - let sighash = Sighash::from_u32(sigversion, sighashtype); + let sighash = Sighash::from_u8(sigversion, sighashtype); match sigversion { SignatureVersion::ForkId if sighash.fork_id => { self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash) @@ -261,12 +258,12 @@ impl TransactionInputSigner { input_amount: u64, script_pubkey: &Script, sigversion: SignatureVersion, - sighash: u32, + sighash: u8, ) -> TransactionInput { let hash = self.signature_hash(input_index, input_amount, script_pubkey, sigversion, sighash); let mut signature: Vec = keypair.private().sign_low_r(&hash).unwrap().into(); - signature.push(sighash as u8); + signature.push(sighash); let script_sig = Builder::default() .push_data(&signature) //.push_data(keypair.public()) @@ -285,7 +282,7 @@ impl TransactionInputSigner { &self, input_index: usize, script_pubkey: &Script, - sighashtype: u32, + sighashtype: u8, sighash: Sighash, ) -> H256 { if input_index >= self.inputs.len() { @@ -378,7 +375,7 @@ impl TransactionInputSigner { let mut stream = Stream::default(); stream.append(&tx); - stream.append(&sighashtype); + stream.append(&(sighashtype as u32)); let out = stream.out(); match self.hash_algo { SignerHashAlgo::DSHA256 => dhash256(&out), @@ -391,7 +388,7 @@ impl TransactionInputSigner { input_index: usize, input_amount: u64, script_pubkey: &Script, - sighashtype: u32, + sighashtype: u8, sighash: Sighash, ) -> H256 { let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); @@ -408,7 +405,8 @@ impl TransactionInputSigner { stream.append(&self.inputs[input_index].sequence); stream.append(&hash_outputs); stream.append(&self.lock_time); - stream.append(&sighashtype); // this also includes 24-bit fork id. which is 0 for BitcoinCash + // TODO: This is missing the fork id. + stream.append(&(sighashtype as u32)); // this also includes 24-bit fork id. which is 0 for BitcoinCash let out = stream.out(); dhash256(&out) } @@ -418,7 +416,7 @@ impl TransactionInputSigner { input_index: usize, input_amount: u64, script_pubkey: &Script, - sighashtype: u32, + sighashtype: u8, sighash: Sighash, ) -> H256 { if input_index >= self.inputs.len() { @@ -440,7 +438,7 @@ impl TransactionInputSigner { &self, input_index: usize, script_pubkey: &Script, - sighashtype: u32, + sighashtype: u8, sighash: Sighash, ) -> Result { let mut sig_hash_stream = Stream::new(); @@ -557,7 +555,7 @@ impl TransactionInputSigner { sig_hash_stream.append(&self.lock_time); sig_hash_stream.append(&self.expiry_height); sig_hash_stream.append(&self.value_balance); - sig_hash_stream.append(&sighashtype); + sig_hash_stream.append(&(sighashtype as u32)); sig_hash_stream.append(&self.inputs[input_index].previous_output); sig_hash_stream.append(&script_pubkey.to_bytes()); @@ -759,7 +757,7 @@ mod tests { tx: &'static str, script: &'static str, input_index: usize, - hash_type: i32, + hash_type: u8, result: &'static str, ) { let tx: Transaction = tx.into(); @@ -767,22 +765,20 @@ mod tests { let script: Script = script.into(); let expected = H256::from_reversed_str(result); - let sighash = Sighash::from_u32(SignatureVersion::Base, hash_type as u32); - let hash = signer.signature_hash_original(input_index, &script, hash_type as u32, sighash); + let sighash = Sighash::from_u8(SignatureVersion::Base, hash_type); + let hash = signer.signature_hash_original(input_index, &script, hash_type, sighash); assert_eq!(expected, hash); } #[test] fn test_sighash_forkid_from_u32() { - assert!(!Sighash::is_defined(SignatureVersion::Base, 0xFFFFFF82)); - assert!(!Sighash::is_defined(SignatureVersion::Base, 0x00000182)); - assert!(!Sighash::is_defined(SignatureVersion::Base, 0x00000080)); - assert!(Sighash::is_defined(SignatureVersion::Base, 0x00000001)); - assert!(Sighash::is_defined(SignatureVersion::Base, 0x00000082)); - assert!(Sighash::is_defined(SignatureVersion::Base, 0x00000003)); - - assert!(!Sighash::is_defined(SignatureVersion::ForkId, 0xFFFFFFC2)); - assert!(!Sighash::is_defined(SignatureVersion::ForkId, 0x000001C2)); + assert!(!Sighash::is_defined(SignatureVersion::Base, 0x80)); + assert!(!Sighash::is_defined(SignatureVersion::Base, 0x84)); + assert!(!Sighash::is_defined(SignatureVersion::Base, 0x85)); + assert!(Sighash::is_defined(SignatureVersion::Base, 0x01)); + assert!(Sighash::is_defined(SignatureVersion::Base, 0x82)); + assert!(Sighash::is_defined(SignatureVersion::Base, 0x03)); + assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x00000081)); assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x000000C2)); assert!(Sighash::is_defined(SignatureVersion::ForkId, 0x00000043)); @@ -807,7 +803,7 @@ mod tests { signer.inputs[0].amount = 50000000; signer.consensus_branch_id = 0x76b809bb; - let sig_hash = Sighash::from_u32(SignatureVersion::Base, 1); + let sig_hash = Sighash::from_u8(SignatureVersion::Base, 1); let hash = signer.signature_hash_overwintered( 0, &Script::from("1976a914507173527b4c3318a2aecd793bf1cfed705950cf88ac"), @@ -841,7 +837,7 @@ mod tests { signer.inputs[0].amount = 9924260; signer.consensus_branch_id = 0x76b809bb; - let sig_hash = Sighash::from_u32(SignatureVersion::Base, 1); + let sig_hash = Sighash::from_u8(SignatureVersion::Base, 1); let hash = signer.signature_hash_overwintered( 0, &Script::from("76a91405aab5342166f8594baf17a7d9bef5d56744332788ac"), @@ -875,7 +871,7 @@ mod tests { signer.inputs[0].amount = 100000000; signer.consensus_branch_id = 0x76b809bb; - let sig_hash = Sighash::from_u32(SignatureVersion::Base, 1); + let sig_hash = Sighash::from_u8(SignatureVersion::Base, 1); let hash = signer.signature_hash_overwintered( 0, &Script::from("6304e5928060b17521031c632dad67a611de77d9666cbc61e65957c7d7544c25e384f4e76de729e6a1bfac6782012088a914b78f0b837e2c710f8b28e59d06473d489e5315c88821037310a8fb9fd8f198a1a21db830252ad681fccda580ed4101f3f6bfb98b34fab5ac68"), diff --git a/mm2src/mm2_bitcoin/script/src/verify.rs b/mm2src/mm2_bitcoin/script/src/verify.rs index 804a856d34..2ba19a3ace 100644 --- a/mm2src/mm2_bitcoin/script/src/verify.rs +++ b/mm2src/mm2_bitcoin/script/src/verify.rs @@ -11,7 +11,7 @@ pub trait SignatureChecker { signature: &Signature, public: &Public, script_code: &Script, - sighashtype: u32, + sighashtype: u8, version: SignatureVersion, ) -> bool; @@ -23,7 +23,7 @@ pub trait SignatureChecker { pub struct NoopSignatureChecker; impl SignatureChecker for NoopSignatureChecker { - fn check_signature(&self, _: &Signature, _: &Public, _: &Script, _: u32, _: SignatureVersion) -> bool { false } + fn check_signature(&self, _: &Signature, _: &Public, _: &Script, _: u8, _: SignatureVersion) -> bool { false } fn check_lock_time(&self, _: Num) -> bool { false } @@ -43,7 +43,7 @@ impl SignatureChecker for TransactionSignatureChecker { signature: &Signature, public: &Public, script_code: &Script, - sighashtype: u32, + sighashtype: u8, version: SignatureVersion, ) -> bool { let hash = self diff --git a/mm2src/mm2_main/src/lp_ordermatch.rs b/mm2src/mm2_main/src/lp_ordermatch.rs index f874eac21b..4ee3aef147 100644 --- a/mm2src/mm2_main/src/lp_ordermatch.rs +++ b/mm2src/mm2_main/src/lp_ordermatch.rs @@ -3559,7 +3559,7 @@ async fn start_taker_swap_state_machine< p2p_keypair: taker_order.p2p_privkey.map(SerializableSecp256k1Keypair::into_inner), taker_secret: *taker_secret, maker_p2p_pubkey: *maker_p2p_pubkey, - require_maker_payment_confirm_before_funding_spend: true, + require_maker_payment_confirm_before_taker_payment: true, require_maker_payment_spend_confirm: true, swap_version: taker_order.request.swap_version.version, }; diff --git a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs index 38c6e07106..92afc129d9 100644 --- a/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs +++ b/mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs @@ -54,6 +54,8 @@ pub struct TakerNegotiationData { pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(bytes = "vec", optional, tag = "8")] pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec>, + #[prost(uint64, tag = "9")] + pub taker_payment_fee: u64, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -105,9 +107,9 @@ pub struct MakerPaymentInfo { #[prost(bytes = "vec", optional, tag = "2")] pub next_step_instructions: ::core::option::Option<::prost::alloc::vec::Vec>, #[prost(bytes = "vec", tag = "3")] - pub funding_preimage_sig: ::prost::alloc::vec::Vec, + pub taker_payment_preimage_sig: ::prost::alloc::vec::Vec, #[prost(bytes = "vec", tag = "4")] - pub funding_preimage_tx: ::prost::alloc::vec::Vec, + pub taker_payment_preimage_tx: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 69cba10ac4..db30b62341 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -508,7 +508,7 @@ impl MakerSwap { let params = MakerSwapPreparedParams { maker_payment_trade_fee: maker_payment_trade_fee.clone(), - taker_payment_spend_trade_fee: taker_payment_spend_trade_fee.clone(), + taker_payment_fee: taker_payment_spend_trade_fee.clone(), }; match check_balance_for_maker_swap( &self.ctx, @@ -2248,7 +2248,7 @@ pub async fn run_maker_swap(swap: RunMakerSwapInput, ctx: MmArc) { pub struct MakerSwapPreparedParams { pub(super) maker_payment_trade_fee: TradeFee, - pub(super) taker_payment_spend_trade_fee: TradeFee, + pub(super) taker_payment_fee: TradeFee, } pub async fn check_balance_for_maker_swap( @@ -2263,7 +2263,7 @@ pub async fn check_balance_for_maker_swap( let (maker_payment_trade_fee, taker_payment_spend_trade_fee) = match prepared_params { Some(MakerSwapPreparedParams { maker_payment_trade_fee, - taker_payment_spend_trade_fee, + taker_payment_fee: taker_payment_spend_trade_fee, }) => (maker_payment_trade_fee, taker_payment_spend_trade_fee), None => { let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); @@ -2341,7 +2341,7 @@ pub async fn maker_swap_trade_preimage( } else { let prepared_params = MakerSwapPreparedParams { maker_payment_trade_fee: base_coin_fee.clone(), - taker_payment_spend_trade_fee: rel_coin_fee.clone(), + taker_payment_fee: rel_coin_fee.clone(), }; check_balance_for_maker_swap( ctx, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs index 53dbc1d6e7..2f6b9b1b5a 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs @@ -4,13 +4,15 @@ use super::{swap_v2_topic, LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsC NEGOTIATION_TIMEOUT_SEC}; use crate::lp_swap::maker_swap::MakerSwapPreparedParams; use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::swap_v2_pb::{swap_message, taker_negotiation, MakerNegotiated, MakerNegotiation, MakerPaymentInfo, + SwapMessage}; use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_maker_swap, recv_swap_v2_msg, SwapConfirmationsSettings, TransactionIdentifier, MAKER_SWAP_V2_TYPE, MAX_STARTED_AT_DIFF}; -use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::hd_wallet::AddrToString; -use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerFundingSpendArgs, +use coins::utxo::utxo_common::big_decimal_from_sat_unsigned; +use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, FundingTxSpend, GenTakerPaymentPreimageArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, SearchForFundingSpendErr, SendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradePreimageValue, Transaction, TxPreimageWithSig, ValidateTakerFundingArgs}; @@ -26,9 +28,10 @@ use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_event_stream::DeriveStreamerId; use mm2_libp2p::Secp256k1PubkeySerialize; -use mm2_number::MmNumber; +use mm2_number::{BigDecimal, MmNumber}; use mm2_state_machine::prelude::*; use mm2_state_machine::storable_state_machine::*; +use num_traits::Zero; use primitives::hash::H256; use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; use secp256k1::PublicKey; @@ -61,6 +64,7 @@ pub struct StoredNegotiationData { maker_coin_swap_contract: Option, taker_coin_swap_contract: Option, taker_secret_hash: BytesJson, + taker_payment_fee: BigDecimal, } /// Represents events produced by maker swap states. @@ -71,15 +75,15 @@ pub enum MakerSwapEvent { Initialized { maker_coin_start_block: u64, taker_coin_start_block: u64, - maker_payment_trade_fee: SavedTradeFee, - taker_payment_spend_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, + taker_payment_fee: SavedTradeFee, }, /// Started waiting for taker funding tx. WaitingForTakerFunding { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, - maker_payment_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, }, /// Received taker funding info. TakerFundingReceived { @@ -87,16 +91,16 @@ pub enum MakerSwapEvent { taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, taker_funding: TransactionIdentifier, - maker_payment_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, }, - /// Sent maker payment and generated funding spend preimage. - MakerPaymentSentFundingSpendGenerated { + /// Sent maker payment and generated taker payment preimage. + MakerPaymentSentTakerPaymentPreimageGenerated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, maker_payment: TransactionIdentifier, taker_funding: TransactionIdentifier, - funding_spend_preimage: StoredTxPreimage, + taker_payment_preimage: StoredTxPreimage, }, /// Something went wrong, so maker payment refund is required. MakerPaymentRefundRequired { @@ -513,21 +517,21 @@ impl Box::new(Initialized { maker_coin: Default::default(), taker_coin: Default::default(), maker_coin_start_block, taker_coin_start_block, - maker_payment_trade_fee, - taker_payment_spend_trade_fee, + maker_payment_fee, + taker_payment_fee, }), MakerSwapEvent::WaitingForTakerFunding { maker_coin_start_block, taker_coin_start_block, negotiation_data, - maker_payment_trade_fee, + maker_payment_fee, } => Box::new(WaitingForTakerFunding { maker_coin_start_block, taker_coin_start_block, @@ -536,14 +540,14 @@ impl Box::new(TakerFundingReceived { maker_coin_start_block, taker_coin_start_block, @@ -556,16 +560,16 @@ impl Box::new(MakerPaymentSentFundingSpendGenerated { + taker_payment_preimage, + } => Box::new(MakerPaymentSentTakerPaymentPreimageGenerated { maker_coin_start_block, taker_coin_start_block, negotiation_data: NegotiationData::from_stored_data( @@ -577,14 +581,14 @@ impl { + MakerSwapEvent::Initialized { maker_payment_fee, .. } => { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); let maker_coin_ticker: String = self.maker_coin.ticker().into(); let new_locked = LockedAmountInfo { @@ -763,7 +763,7 @@ impl { + MakerSwapEvent::MakerPaymentSentTakerPaymentPreimageGenerated { .. } => { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); let ticker = self.maker_coin.ticker(); if let Some(maker_coin_locked) = swaps_ctx.locked_amounts.lock().unwrap().get_mut(ticker) { @@ -805,18 +805,9 @@ impl { + MakerSwapEvent::Initialized { maker_payment_fee, .. } + | MakerSwapEvent::WaitingForTakerFunding { maker_payment_fee, .. } + | MakerSwapEvent::TakerFundingReceived { maker_payment_fee, .. } => { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); let maker_coin_ticker: String = self.maker_coin.ticker().into(); let new_locked = LockedAmountInfo { @@ -824,7 +815,7 @@ impl fee, Err(e) => { let reason = AbortReason::FailedToGetMakerPaymentFee(e.to_string()); @@ -905,18 +891,17 @@ impl fee, Err(e) => { - let reason = AbortReason::FailedToGetTakerPaymentSpendFee(e.to_string()); + let reason = AbortReason::FailedToGetTakerPaymentFee(e.to_string()); return Self::change_state(Aborted::new(reason), state_machine).await; }, }; let prepared_params = MakerSwapPreparedParams { - maker_payment_trade_fee: maker_payment_trade_fee.clone(), - taker_payment_spend_trade_fee: taker_payment_spend_trade_fee.clone(), + maker_payment_trade_fee: maker_payment_fee.clone(), + taker_payment_fee: taker_payment_fee.clone(), }; if let Err(e) = check_balance_for_maker_swap( @@ -926,7 +911,7 @@ impl { taker_coin: PhantomData, maker_coin_start_block: u64, taker_coin_start_block: u64, - maker_payment_trade_fee: SavedTradeFee, - taker_payment_spend_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, + taker_payment_fee: SavedTradeFee, } impl TransitionFrom> for Initialized {} @@ -967,8 +952,8 @@ impl p, @@ -1081,8 +1085,9 @@ impl>, taker_coin_swap_contract: Option>, taker_secret_hash: Vec, + taker_payment_fee: BigDecimal, } impl NegotiationData { @@ -1108,6 +1114,7 @@ impl Negotiation maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()), taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()), taker_secret_hash: self.taker_secret_hash.clone().into(), + taker_payment_fee: self.taker_payment_fee.clone(), } } @@ -1128,6 +1135,7 @@ impl Negotiation maker_coin_swap_contract: None, taker_coin_swap_contract: None, taker_secret_hash: stored.taker_secret_hash.into(), + taker_payment_fee: stored.taker_payment_fee, }) } } @@ -1136,7 +1144,7 @@ struct WaitingForTakerFunding, - maker_payment_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, } impl TransitionFrom> @@ -1196,7 +1204,7 @@ impl, taker_funding: TakerCoin::Tx, - maker_payment_trade_fee: SavedTradeFee, + maker_payment_fee: SavedTradeFee, } impl @@ -1246,6 +1254,7 @@ impl p, Err(e) => { - let reason = AbortReason::FailedToGenerateFundingSpend(e.to_string()); + let reason = AbortReason::FailedToGenerateTakerPaymentPreimage(e.to_string()); return Self::change_state(Aborted::new(reason), state_machine).await; }, }; @@ -1298,12 +1308,12 @@ impl { +struct MakerPaymentSentTakerPaymentPreimageGenerated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, taker_funding: TakerCoin::Tx, - funding_spend_preimage: TxPreimageWithSig, + taker_payment_preimage: TxPreimageWithSig, maker_payment: MakerCoin::Tx, } impl TransitionFrom> - for MakerPaymentSentFundingSpendGenerated + for MakerPaymentSentTakerPaymentPreimageGenerated { } #[async_trait] impl State - for MakerPaymentSentFundingSpendGenerated + for MakerPaymentSentTakerPaymentPreimageGenerated { type StateMachine = MakerSwapStateMachine; @@ -1355,8 +1365,8 @@ impl StorableState - for MakerPaymentSentFundingSpendGenerated + for MakerPaymentSentTakerPaymentPreimageGenerated { type StateMachine = MakerSwapStateMachine; fn get_event(&self) -> MakerSwapEvent { - MakerSwapEvent::MakerPaymentSentFundingSpendGenerated { + MakerSwapEvent::MakerPaymentSentTakerPaymentPreimageGenerated { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data.to_stored_data(), @@ -1488,9 +1498,9 @@ impl - TransitionFrom> + TransitionFrom> for MakerPaymentRefundRequired { } @@ -1665,7 +1675,7 @@ struct TakerPaymentReceived - TransitionFrom> + TransitionFrom> for TakerPaymentReceived { } @@ -1845,7 +1855,7 @@ struct TakerPaymentReceivedAndPreimageValidationSkipped - TransitionFrom> + TransitionFrom> for TakerPaymentReceivedAndPreimageValidationSkipped { } @@ -2039,15 +2049,16 @@ pub enum AbortReason { DidNotReceiveTakerFundingInfo(String), FailedToParseTakerFunding(String), TakerFundingValidationFailed(String), - FailedToGenerateFundingSpend(String), + FailedToGenerateTakerPaymentPreimage(String), FailedToSendMakerPayment(String), TooLargeStartedAtDiff(u64), TakerProvidedInvalidFundingLocktime(u64), TakerProvidedInvalidPaymentLocktime(u64), FailedToParsePubkey(String), + TakerPaymentSpentFeeTooLow(BigDecimal), MakerPaymentRefundFailed(String), FailedToGetMakerPaymentFee(String), - FailedToGetTakerPaymentSpendFee(String), + FailedToGetTakerPaymentFee(String), } struct Aborted { diff --git a/mm2src/mm2_main/src/lp_swap/swap_v2.proto b/mm2src/mm2_main/src/lp_swap/swap_v2.proto index 9d8d92d28f..ce86744520 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_v2.proto +++ b/mm2src/mm2_main/src/lp_swap/swap_v2.proto @@ -32,6 +32,7 @@ message TakerNegotiationData { bytes taker_coin_htlc_pub = 6; optional bytes maker_coin_swap_contract = 7; optional bytes taker_coin_swap_contract = 8; + uint64 taker_payment_fee = 9; } message TakerNegotiation { @@ -60,8 +61,8 @@ message TakerPaymentInfo { message MakerPaymentInfo { bytes tx_bytes = 1; optional bytes next_step_instructions = 2; - bytes funding_preimage_sig = 3; - bytes funding_preimage_tx = 4; + bytes taker_payment_preimage_sig = 3; + bytes taker_payment_preimage_tx = 4; } message TakerPaymentSpendPreimage { diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index ecc8490b28..0e3e022f51 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -1100,8 +1100,8 @@ impl TakerSwap { let params = TakerSwapPreparedParams { 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(), + funding_fee: taker_payment_trade_fee.clone(), + maker_payment_spend_fee: maker_payment_spend_trade_fee.clone(), }; let check_balance_f = check_balance_for_taker_swap( &self.ctx, @@ -2505,8 +2505,8 @@ impl AtomicSwap for TakerSwap { pub struct TakerSwapPreparedParams { pub(super) dex_fee: MmNumber, pub(super) fee_to_send_dex_fee: TradeFee, - pub(super) taker_payment_trade_fee: TradeFee, - pub(super) maker_payment_spend_trade_fee: TradeFee, + pub(super) funding_fee: TradeFee, + pub(super) maker_payment_spend_fee: TradeFee, } pub async fn check_balance_for_taker_swap( @@ -2527,11 +2527,11 @@ pub async fn check_balance_for_taker_swap( .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; let preimage_value = TradePreimageValue::Exact(volume.to_decimal()); - let taker_payment_trade_fee = my_coin + let funding_fee = my_coin .get_sender_trade_fee(preimage_value, stage, INCLUDE_REFUND_FEE) .await .mm_err(|e| CheckBalanceError::from_trade_preimage_error(e, my_coin.ticker()))?; - let maker_payment_spend_trade_fee = other_coin + let maker_payment_spend_fee = other_coin .get_receiver_trade_fee(stage) .compat() .await @@ -2539,8 +2539,8 @@ pub async fn check_balance_for_taker_swap( TakerSwapPreparedParams { dex_fee: dex_fee.total_spend_amount(), fee_to_send_dex_fee, - taker_payment_trade_fee, - maker_payment_spend_trade_fee, + funding_fee, + maker_payment_spend_fee, } }, }; @@ -2550,17 +2550,9 @@ pub async fn check_balance_for_taker_swap( fee_to_send_dex_fee: params.fee_to_send_dex_fee, }; - check_my_coin_balance_for_swap( - ctx, - my_coin, - swap_uuid, - volume, - params.taker_payment_trade_fee, - Some(taker_fee), - ) - .await?; - if !params.maker_payment_spend_trade_fee.paid_from_trading_vol { - check_other_coin_balance_for_swap(ctx, other_coin, swap_uuid, params.maker_payment_spend_trade_fee).await?; + check_my_coin_balance_for_swap(ctx, my_coin, swap_uuid, volume, params.funding_fee, Some(taker_fee)).await?; + if !params.maker_payment_spend_fee.paid_from_trading_vol { + check_other_coin_balance_for_swap(ctx, other_coin, swap_uuid, params.maker_payment_spend_fee).await?; } Ok(()) } @@ -2640,8 +2632,8 @@ pub async fn taker_swap_trade_preimage( let prepared_params = TakerSwapPreparedParams { dex_fee: dex_fee.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(), + funding_fee: my_coin_trade_fee.clone(), + maker_payment_spend_fee: other_coin_trade_fee.clone(), }; check_balance_for_taker_swap( ctx, diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs index b3ee43a6a1..462539e74e 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap_v2.rs @@ -3,13 +3,14 @@ use super::swap_v2_common::*; use super::{LockedAmount, LockedAmountInfo, SavedTradeFee, SwapsContext, TakerSwapPreparedParams, NEGOTIATE_SEND_INTERVAL, NEGOTIATION_TIMEOUT_SEC}; use crate::lp_swap::swap_lock::SwapLock; +use crate::lp_swap::swap_v2_pb::*; use crate::lp_swap::{broadcast_swap_v2_msg_every, check_balance_for_taker_swap, recv_swap_v2_msg, swap_v2_topic, SwapConfirmationsSettings, TransactionIdentifier, MAX_STARTED_AT_DIFF, TAKER_SWAP_V2_TYPE}; -use crate::lp_swap::{swap_v2_pb::*, NO_REFUND_FEE}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use coins::hd_wallet::AddrToString; -use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerFundingSpendArgs, +use coins::utxo::sat_from_big_decimal; +use coins::{CanRefundHtlc, ConfirmPaymentInput, DexFee, FeeApproxStage, GenTakerPaymentPreimageArgs, GenTakerPaymentSpendArgs, MakerCoinSwapOpsV2, MmCoin, ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs, SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TakerCoinSwapOpsV2, ToBytes, TradeFee, TradePreimageValue, Transaction, TxPreimageWithSig, @@ -71,16 +72,16 @@ pub enum TakerSwapEvent { Initialized { maker_coin_start_block: u64, taker_coin_start_block: u64, + funding_fee: SavedTradeFee, taker_payment_fee: SavedTradeFee, - maker_payment_spend_fee: SavedTradeFee, }, /// Negotiated swap data with maker. Negotiated { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, + funding_fee: SavedTradeFee, taker_payment_fee: SavedTradeFee, - maker_payment_spend_fee: SavedTradeFee, }, /// Sent taker funding tx. TakerFundingSent { @@ -88,6 +89,7 @@ pub enum TakerSwapEvent { taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, taker_funding: TransactionIdentifier, + taker_payment_fee: SavedTradeFee, }, /// Taker funding tx refund is required. TakerFundingRefundRequired { @@ -98,12 +100,13 @@ pub enum TakerSwapEvent { reason: TakerFundingRefundReason, }, /// Received maker payment and taker funding spend preimage - MakerPaymentAndFundingSpendPreimgReceived { + MakerPaymentAndTakerPaymentPreimageReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: StoredNegotiationData, + taker_payment_fee: SavedTradeFee, taker_funding: TransactionIdentifier, - funding_spend_preimage: StoredTxPreimage, + taker_payment_preimage: StoredTxPreimage, maker_payment: TransactionIdentifier, }, /// Sent taker payment. @@ -134,7 +137,7 @@ pub enum TakerSwapEvent { taker_coin_start_block: u64, maker_payment: TransactionIdentifier, taker_funding: TransactionIdentifier, - funding_spend_preimage: StoredTxPreimage, + taker_payment_preimage: StoredTxPreimage, negotiation_data: StoredNegotiationData, }, /// Maker spent taker's payment and taker discovered the tx on-chain. @@ -437,7 +440,7 @@ pub struct TakerSwapStateMachine Box::new(Initialized { maker_coin: Default::default(), taker_coin: Default::default(), maker_coin_start_block, taker_coin_start_block, + funding_fee, taker_payment_fee, - maker_payment_spend_fee, }), TakerSwapEvent::Negotiated { maker_coin_start_block, taker_coin_start_block, negotiation_data, + funding_fee, taker_payment_fee, - maker_payment_spend_fee, } => Box::new(Negotiated { maker_coin_start_block, taker_coin_start_block, @@ -564,14 +567,15 @@ impl Box::new(TakerFundingSent { maker_coin_start_block, taker_coin_start_block, @@ -584,6 +588,7 @@ impl Box::new(MakerPaymentAndFundingSpendPreimgReceived { + taker_payment_preimage, + } => Box::new(MakerPaymentAndTakerPaymentPreimageReceived { maker_coin_start_block, taker_coin_start_block, negotiation_data: NegotiationData::from_stored_data( @@ -620,18 +626,19 @@ impl Box::new(MakerPaymentConfirmed { maker_coin_start_block, @@ -719,14 +726,14 @@ impl { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); @@ -875,8 +882,9 @@ impl { + TakerSwapEvent::Initialized { + funding_fee, + taker_payment_fee, + .. + } + | TakerSwapEvent::Negotiated { + funding_fee, + taker_payment_fee, + .. + } => { let swaps_ctx = SwapsContext::from_ctx(&self.ctx).expect("from_ctx should not fail at this point"); let taker_coin_ticker: String = self.taker_coin.ticker().into(); let new_locked = LockedAmountInfo { swap_uuid: self.uuid, locked_amount: LockedAmount { coin: taker_coin_ticker.clone(), - amount: &(&self.taker_volume + &self.dex_fee().total_spend_amount()) + &self.taker_premium, - trade_fee: Some(taker_payment_fee.into()), + amount: &(&self.taker_volume + &self.dex_fee().total_spend_amount()) + + &(&self.taker_premium + &taker_payment_fee.amount.into()), + trade_fee: Some(funding_fee.into()), }, }; swaps_ctx @@ -944,7 +961,7 @@ impl fee, + Err(e) => { + let reason = AbortReason::FailedToGetTakerPaymentSpendFee(e.to_string()); + return Self::change_state(Aborted::new(reason), state_machine).await; + }, + }; + + // TODO: Add the price of maker_payment fee (in taker coin) + taker_payment_spend fee to the total payment value. let total_payment_value = &(&state_machine.taker_volume + &state_machine.dex_fee().total_spend_amount()) - + &state_machine.taker_premium; + + &(&state_machine.taker_premium + &taker_payment_fee.amount); let preimage_value = TradePreimageValue::Exact(total_payment_value.to_decimal()); - let stage = FeeApproxStage::StartSwap; - let taker_payment_fee = match state_machine - .taker_coin - .get_sender_trade_fee(preimage_value, stage, NO_REFUND_FEE) - .await - { + let funding_fee = match state_machine.taker_coin.get_funding_fee(preimage_value).await { Ok(fee) => fee, Err(e) => { let reason = AbortReason::FailedToGetTakerPaymentFee(e.to_string()); @@ -1020,7 +1043,7 @@ impl fee, Err(e) => { let reason = AbortReason::FailedToGetMakerPaymentSpendFee(e.to_string()); @@ -1029,14 +1052,18 @@ impl { taker_coin: PhantomData, maker_coin_start_block: u64, taker_coin_start_block: u64, + funding_fee: SavedTradeFee, taker_payment_fee: SavedTradeFee, - maker_payment_spend_fee: SavedTradeFee, } impl TransitionFrom> for Initialized {} @@ -1087,8 +1114,8 @@ impl, + funding_fee: SavedTradeFee, taker_payment_fee: SavedTradeFee, - maker_payment_spend_fee: SavedTradeFee, } impl TransitionFrom> @@ -1307,6 +1339,7 @@ impl, + taker_payment_fee: SavedTradeFee, } #[async_trait] @@ -1380,7 +1415,7 @@ impl p, Err(e) => { @@ -1433,7 +1468,7 @@ impl p, Err(e) => { @@ -1450,18 +1485,19 @@ impl { +struct MakerPaymentAndTakerPaymentPreimageReceived { maker_coin_start_block: u64, taker_coin_start_block: u64, negotiation_data: NegotiationData, + taker_payment_fee: SavedTradeFee, taker_funding: TakerCoin::Tx, - funding_spend_preimage: TxPreimageWithSig, + taker_payment_preimage: TxPreimageWithSig, maker_payment: MakerCoin::Tx, } impl TransitionFrom> - for MakerPaymentAndFundingSpendPreimgReceived + for MakerPaymentAndTakerPaymentPreimageReceived { } impl StorableState - for MakerPaymentAndFundingSpendPreimgReceived + for MakerPaymentAndTakerPaymentPreimageReceived { type StateMachine = TakerSwapStateMachine; fn get_event(&self) -> TakerSwapEvent { - TakerSwapEvent::MakerPaymentAndFundingSpendPreimgReceived { + TakerSwapEvent::MakerPaymentAndTakerPaymentPreimageReceived { maker_coin_start_block: self.maker_coin_start_block, taker_coin_start_block: self.taker_coin_start_block, negotiation_data: self.negotiation_data.to_stored_data(), + taker_payment_fee: self.taker_payment_fee.clone(), taker_funding: TransactionIdentifier { tx_hex: self.taker_funding.tx_hex().into(), tx_hash: self.taker_funding.tx_hash_as_bytes(), }, - funding_spend_preimage: StoredTxPreimage { - preimage: self.funding_spend_preimage.preimage.to_bytes().into(), - signature: self.funding_spend_preimage.signature.to_bytes().into(), + taker_payment_preimage: StoredTxPreimage { + preimage: self.taker_payment_preimage.preimage.to_bytes().into(), + signature: self.taker_payment_preimage.signature.to_bytes().into(), }, maker_payment: TransactionIdentifier { tx_hex: self.maker_payment.tx_hex().into(), @@ -1537,7 +1576,7 @@ impl State - for MakerPaymentAndFundingSpendPreimgReceived + for MakerPaymentAndTakerPaymentPreimageReceived { type StateMachine = TakerSwapStateMachine; @@ -1565,7 +1604,7 @@ impl tx, @@ -1692,8 +1733,9 @@ impl TransitionFrom> for TakerPaymentSent { } + impl - TransitionFrom> + TransitionFrom> for TakerPaymentSent { } @@ -1705,23 +1747,21 @@ impl; async fn on_changed(self: Box, state_machine: &mut Self::StateMachine) -> StateResult { - if !state_machine.require_maker_payment_confirm_before_funding_spend { - let input = ConfirmPaymentInput { - payment_tx: self.maker_payment.tx_hex(), - confirmations: state_machine.conf_settings.maker_coin_confs, - requires_nota: state_machine.conf_settings.maker_coin_nota, - wait_until: state_machine.maker_payment_conf_timeout(), - check_every: 10, - }; + let input = ConfirmPaymentInput { + payment_tx: self.maker_payment.tx_hex(), + confirmations: state_machine.conf_settings.maker_coin_confs, + requires_nota: state_machine.conf_settings.maker_coin_nota, + wait_until: state_machine.maker_payment_conf_timeout(), + check_every: 10, + }; - if let Err(e) = state_machine.maker_coin.wait_for_confirmations(input).compat().await { - let next_state = TakerPaymentRefundRequired { - taker_payment: self.taker_payment, - negotiation_data: self.negotiation_data, - reason: TakerPaymentRefundReason::MakerPaymentNotConfirmedInTime(e), - }; - return Self::change_state(next_state, state_machine).await; - } + if let Err(e) = state_machine.maker_coin.wait_for_confirmations(input).compat().await { + let next_state = TakerPaymentRefundRequired { + taker_payment: self.taker_payment, + negotiation_data: self.negotiation_data, + reason: TakerPaymentRefundReason::MakerPaymentNotConfirmedInTime(e), + }; + return Self::change_state(next_state, state_machine).await; } let unique_data = state_machine.unique_data(); @@ -1845,7 +1885,7 @@ impl { } impl - TransitionFrom> + TransitionFrom> for TakerPaymentSentAndPreimageSendingSkipped { } @@ -1859,7 +1899,7 @@ impl, state_machine: &mut Self::StateMachine) -> StateResult { info!("Skipped the generation of the taker payment spend preimage and its p2p message broadcast because the taker's coin does not require this preimage for the process."); - if !state_machine.require_maker_payment_confirm_before_funding_spend { + if !state_machine.require_maker_payment_confirm_before_taker_payment { let input = ConfirmPaymentInput { payment_tx: self.maker_payment.tx_hex(), confirmations: state_machine.conf_settings.maker_coin_confs, @@ -1942,11 +1982,11 @@ impl { } impl - TransitionFrom> + TransitionFrom> for TakerFundingRefundRequired { } @@ -2161,12 +2201,12 @@ struct MakerPaymentConfirmed, + taker_payment_preimage: TxPreimageWithSig, negotiation_data: NegotiationData, } impl - TransitionFrom> + TransitionFrom> for MakerPaymentConfirmed { } @@ -2180,7 +2220,7 @@ impl, state_machine: &mut Self::StateMachine) -> StateResult { let unique_data = state_machine.unique_data(); - let args = GenTakerFundingSpendArgs { + let args = GenTakerPaymentPreimageArgs { funding_tx: &self.taker_funding, maker_pub: &self.negotiation_data.taker_coin_htlc_pub_from_maker, taker_pub: &state_machine.taker_coin.derive_htlc_pubkey_v2(&unique_data), @@ -2188,11 +2228,12 @@ impl tx, @@ -2255,9 +2296,9 @@ impl