diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6d7ecc942d..d5a6dd0095 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -73,23 +73,22 @@ cfg_wasm32! { } use super::{coin_conf, AsyncMutex, BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, - CoinProtocol, CoinTransportMetrics, CoinsContext, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, - IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, - NumConversError, NumConversResult, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, RawTransactionRequest, RawTransactionRes, - RawTransactionResult, RefundError, RefundResult, RpcClientType, RpcTransportEventHandler, - RpcTransportEventHandlerShared, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, - TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, - UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, - ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, - VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, - EARLY_CONFIRMATION_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_RECEIVER_ERR_LOG, - INVALID_SENDER_ERR_LOG}; + CoinProtocol, CoinTransportMetrics, CoinsContext, EthValidateFeeArgs, FeeApproxStage, FoundSwapTxSpend, + HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, + NegotiateSwapContractAddrErr, NumConversError, NumConversResult, PaymentInstructions, + PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, + RawTransactionFut, RawTransactionRequest, RawTransactionRes, RawTransactionResult, RefundError, + RefundPaymentArgs, RefundResult, RpcClientType, RpcTransportEventHandler, RpcTransportEventHandlerShared, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionDetails, + TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult, EARLY_CONFIRMATION_ERR_LOG, + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, INVALID_PAYMENT_STATE_ERR_LOG, + INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG}; pub use rlp; #[cfg(test)] mod eth_tests; @@ -420,6 +419,7 @@ pub struct EthCoinImpl { sign_message_prefix: Option, swap_contract_address: Address, fallback_swap_contract: Option
, + contract_supports_watchers: bool, web3: Web3, /// The separate web3 instances kept to get nonce, will replace the web3 completely soon web3_instances: Vec, @@ -894,224 +894,71 @@ impl SwapOps for EthCoin { ) } - fn send_maker_payment(&self, maker_payment: SendMakerPaymentArgs) -> TransactionFut { - let taker_addr = try_tx_fus!(addr_from_raw_pubkey(maker_payment.other_pubkey)); - let swap_contract_address = try_tx_fus!(maker_payment.swap_contract_address.try_to_address()); - + fn send_maker_payment(&self, maker_payment: SendPaymentArgs) -> TransactionFut { Box::new( - self.send_hash_time_locked_payment( - self.etomic_swap_id(maker_payment.time_lock, maker_payment.secret_hash), - try_tx_fus!(wei_from_big_decimal(&maker_payment.amount, self.decimals)), - maker_payment.time_lock, - maker_payment.secret_hash, - taker_addr, - swap_contract_address, - ) - .map(TransactionEnum::from), + self.send_hash_time_locked_payment(maker_payment) + .map(TransactionEnum::from), ) } - fn send_taker_payment(&self, taker_payment: SendTakerPaymentArgs) -> TransactionFut { - let maker_addr = try_tx_fus!(addr_from_raw_pubkey(taker_payment.other_pubkey)); - let swap_contract_address = try_tx_fus!(taker_payment.swap_contract_address.try_to_address()); - + fn send_taker_payment(&self, taker_payment: SendPaymentArgs) -> TransactionFut { Box::new( - self.send_hash_time_locked_payment( - self.etomic_swap_id(taker_payment.time_lock, taker_payment.secret_hash), - try_tx_fus!(wei_from_big_decimal(&taker_payment.amount, self.decimals)), - taker_payment.time_lock, - taker_payment.secret_hash, - maker_addr, - swap_contract_address, - ) - .map(TransactionEnum::from), + self.send_hash_time_locked_payment(taker_payment) + .map(TransactionEnum::from), ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(maker_spends_payment_args.other_payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = - try_tx_fus!(maker_spends_payment_args.swap_contract_address.try_to_address(), signed); - + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { Box::new( - self.spend_hash_time_locked_payment( - signed, - maker_spends_payment_args.secret_hash, - swap_contract_address, - maker_spends_payment_args.secret, - ) - .map(TransactionEnum::from), + self.spend_hash_time_locked_payment(maker_spends_payment_args) + .map(TransactionEnum::from), ) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(taker_spends_payment_args.other_payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(taker_spends_payment_args.swap_contract_address.try_to_address()); + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { Box::new( - self.spend_hash_time_locked_payment( - signed, - taker_spends_payment_args.secret_hash, - swap_contract_address, - taker_spends_payment_args.secret, - ) - .map(TransactionEnum::from), + self.spend_hash_time_locked_payment(taker_spends_payment_args) + .map(TransactionEnum::from), ) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(taker_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(taker_refunds_payment_args.swap_contract_address.try_to_address()); - + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.refund_hash_time_locked_payment(swap_contract_address, signed, taker_refunds_payment_args.secret_hash) + self.refund_hash_time_locked_payment(taker_refunds_payment_args) .map(TransactionEnum::from), ) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(maker_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - let swap_contract_address = try_tx_fus!(maker_refunds_payment_args.swap_contract_address.try_to_address()); - + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.refund_hash_time_locked_payment(swap_contract_address, signed, maker_refunds_payment_args.secret_hash) + self.refund_hash_time_locked_payment(maker_refunds_payment_args) .map(TransactionEnum::from), ) } - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { - let selfi = self.clone(); + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::SignedEthTx(t) => t.clone(), _ => panic!(), }; - let sender_addr = try_fus!(addr_from_raw_pubkey(validate_fee_args.expected_sender)); - let fee_addr = try_fus!(addr_from_raw_pubkey(validate_fee_args.fee_addr)); - let amount = validate_fee_args.amount.clone(); - let min_block_number = validate_fee_args.min_block_number; - - let fut = async move { - let expected_value = try_s!(wei_from_big_decimal(&amount, selfi.decimals)); - let tx_from_rpc = try_s!(selfi.web3.eth().transaction(TransactionId::Hash(tx.hash)).await); - let tx_from_rpc = match tx_from_rpc { - Some(t) => t, - None => return ERR!("Didn't find provided tx {:?} on ETH node", tx), - }; - - if tx_from_rpc.from != Some(sender_addr) { - return ERR!( - "Fee tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, - sender_addr - ); - } - - if let Some(block_number) = tx_from_rpc.block_number { - if block_number <= min_block_number.into() { - return ERR!( - "Fee tx {:?} confirmed before min_block {}", - tx_from_rpc, - min_block_number, - ); - } - } - match &selfi.coin_type { - EthCoinType::Eth => { - if tx_from_rpc.to != Some(fee_addr) { - return ERR!( - "Fee tx {:?} was sent to wrong address, expected {:?}", - tx_from_rpc, - fee_addr - ); - } - - if tx_from_rpc.value < expected_value { - return ERR!( - "Fee tx {:?} value is less than expected {:?}", - tx_from_rpc, - expected_value - ); - } - }, - EthCoinType::Erc20 { - platform: _, - token_addr, - } => { - if tx_from_rpc.to != Some(*token_addr) { - return ERR!( - "ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", - tx_from_rpc, - token_addr - ); - } - - let function = try_s!(ERC20_CONTRACT.function("transfer")); - let decoded_input = try_s!(decode_contract_call(function, &tx_from_rpc.input.0)); - - if decoded_input[0] != Token::Address(fee_addr) { - return ERR!( - "ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", - decoded_input[0], - fee_addr - ); - } - - match decoded_input[1] { - Token::Uint(value) => { - if value < expected_value { - return ERR!("ERC20 Fee tx value {} is less than expected {}", value, expected_value); - } - }, - _ => return ERR!("Should have got uint token but got {:?}", decoded_input[1]), - } - }, - } - - Ok(()) - }; - Box::new(fut.boxed().compat()) + validate_fee_impl(self.clone(), EthValidateFeeArgs { + fee_tx_hash: &tx.hash, + expected_sender: validate_fee_args.expected_sender, + fee_addr: validate_fee_args.fee_addr, + amount: validate_fee_args.amount, + min_block_number: validate_fee_args.min_block_number, + uuid: validate_fee_args.uuid, + }) } + #[inline] fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { - let swap_contract_address = try_f!(input - .swap_contract_address - .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidParameter)); - self.validate_payment( - &input.payment_tx, - input.time_lock, - &input.other_pub, - &input.secret_hash, - input.amount, - swap_contract_address, - ) + self.validate_payment(input) } + #[inline] fn validate_taker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { - let swap_contract_address = try_f!(input - .swap_contract_address - .try_to_address() - .map_to_mm(ValidatePaymentError::InvalidParameter)); - self.validate_payment( - &input.payment_tx, - input.time_lock, - &input.other_pub, - &input.secret_hash, - input.amount, - swap_contract_address, - ) + self.validate_payment(input) } fn check_if_my_payment_sent( @@ -1189,6 +1036,7 @@ impl SwapOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -1203,6 +1051,7 @@ impl SwapOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -1211,9 +1060,15 @@ impl SwapOps for EthCoin { unimplemented!(); } - async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + _secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); - let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); + let function_name = get_function_name("receiverSpend", watcher_reward); + let function = try_s!(SWAP_CONTRACT.function(&function_name)); // Validate contract call; expected to be receiverSpend. // https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. @@ -1343,7 +1198,10 @@ impl SwapOps for EthCoin { MmError::err(ValidateInstructionsErr::UnsupportedCoin(self.ticker().to_string())) } - fn is_supported_by_watchers(&self) -> bool { true } + fn is_supported_by_watchers(&self) -> bool { + false + //self.contract_supports_watchers + } } #[async_trait] @@ -1363,11 +1221,8 @@ impl MakerSwapTakerCoin for EthCoin { #[async_trait] impl WatcherOps for EthCoin { fn send_maker_payment_spend_preimage(&self, input: SendMakerPaymentSpendPreimageInput) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(input.preimage)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - Box::new( - self.watcher_spend_hash_time_locked_payment(signed, input.secret_hash, input.secret, input.taker_pub) + self.watcher_spends_hash_time_locked_payment(input) .map(TransactionEnum::from), ) } @@ -1403,107 +1258,25 @@ impl WatcherOps for EthCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { - let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(watcher_refunds_payment_args.payment_tx)); - let signed = try_tx_fus!(SignedEthTx::new(tx)); - + fn send_taker_payment_refund_preimage(&self, args: RefundPaymentArgs) -> TransactionFut { Box::new( - self.watcher_refunds_hash_time_locked_payment( - signed, - watcher_refunds_payment_args.secret_hash, - watcher_refunds_payment_args.other_pubkey, - ) - .map(TransactionEnum::from), + self.watcher_refunds_hash_time_locked_payment(args) + .map(TransactionEnum::from), ) } fn watcher_validate_taker_fee(&self, validate_fee_args: WatcherValidateTakerFeeInput) -> ValidatePaymentFut<()> { - let selfi = self.clone(); - let sender_addr = - try_f!(addr_from_raw_pubkey(&validate_fee_args.sender_pubkey) - .map_to_mm(ValidatePaymentError::InvalidParameter)); - let fee_addr = - try_f!(addr_from_raw_pubkey(&validate_fee_args.fee_addr).map_to_mm(ValidatePaymentError::InvalidParameter)); - let min_block_number = validate_fee_args.min_block_number; - let taker_fee_hash = validate_fee_args.taker_fee_hash; - - let fut = async move { - let tx_from_rpc = selfi - .web3 - .eth() - .transaction(TransactionId::Hash(H256::from_slice(taker_fee_hash.as_slice()))) - .await - .map_to_mm(|e| ValidatePaymentError::InvalidRpcResponse(e.to_string()))?; - - let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { - ValidatePaymentError::TxDoesNotExist(format!( - "Didn't find provided tx {:?} on ETH node", - H256::from_slice(taker_fee_hash.as_slice()) - )) - })?; - - if tx_from_rpc.from != Some(sender_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} was sent from wrong address, expected {:?}", - INVALID_SENDER_ERR_LOG, tx_from_rpc, sender_addr - ))); - } - - if let Some(block_number) = tx_from_rpc.block_number { - if block_number <= min_block_number.into() { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} confirmed before min_block {}", - EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number - ))); - } - } - - //TODO: Validate if taker fee is old - - match &selfi.coin_type { - EthCoinType::Eth => { - if tx_from_rpc.to != Some(fee_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: Fee tx {:?} was sent to wrong address, expected {:?}", - INVALID_RECEIVER_ERR_LOG, tx_from_rpc, fee_addr - ))); - } - }, - EthCoinType::Erc20 { - platform: _, - token_addr, - } => { - if tx_from_rpc.to != Some(*token_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", - INVALID_CONTRACT_ADDRESS_ERR_LOG, tx_from_rpc, token_addr - ))); - } - - let function = ERC20_CONTRACT - .function("transfer") - .map_to_mm(|e| ValidatePaymentError::InternalError(e.to_string()))?; - let decoded_input = function - .decode_input(&tx_from_rpc.input.0) - .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; - let address_input = get_function_input_data(&decoded_input, function, 0) - .map_to_mm(ValidatePaymentError::TxDeserializationError)?; - if address_input != Token::Address(fee_addr) { - return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "{}: ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", - INVALID_RECEIVER_ERR_LOG, address_input, fee_addr - ))); - } - }, - } - - Ok(()) - }; + validate_fee_impl(self.clone(), EthValidateFeeArgs { + fee_tx_hash: &H256::from_slice(validate_fee_args.taker_fee_hash.as_slice()), + expected_sender: &validate_fee_args.sender_pubkey, + fee_addr: &validate_fee_args.fee_addr, + amount: &BigDecimal::from(0), + min_block_number: validate_fee_args.min_block_number, + uuid: &[], + }) - Box::new(fut.boxed().compat()) + // TODO: Add validations specific for watchers + // 1.Validate if taker fee is old } fn watcher_validate_taker_payment(&self, input: WatcherValidatePaymentInput) -> ValidatePaymentFut<()> { @@ -1532,8 +1305,7 @@ impl WatcherOps for EthCoin { if tx_from_rpc.from != Some(sender) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx {:?} was sent from wrong address, expected {:?}", - tx_from_rpc, sender + "{INVALID_SENDER_ERR_LOG}: Payment tx {tx_from_rpc:?} was sent from wrong address, expected {sender:?}" ))); } @@ -1547,7 +1319,7 @@ impl WatcherOps for EthCoin { && Some(swap_contract_address) != fallback_swap_contract { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx {tx_from_rpc:?} was sent to wrong address, expected either {expected_swap_contract_address:?} or the fallback {fallback_swap_contract:?}" + "{INVALID_CONTRACT_ADDRESS_ERR_LOG}: Payment tx {tx_from_rpc:?} was sent to wrong address, expected either {expected_swap_contract_address:?} or the fallback {fallback_swap_contract:?}" ))); } @@ -1558,26 +1330,28 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::Transport)?; if status != PAYMENT_STATE_SENT.into() { return MmError::err(ValidatePaymentError::UnexpectedPaymentState(format!( - "Payment state is not PAYMENT_STATE_SENT, got {}", - status + "{INVALID_PAYMENT_STATE_ERR_LOG}: Payment state is not PAYMENT_STATE_SENT, got {status}" ))); } + let min_watcher_reward = input.min_watcher_reward.ok_or_else(|| { + ValidatePaymentError::InvalidParameter("Minimum watcher reward argument is not provided".to_string()) + })?; + match &selfi.coin_type { EthCoinType::Eth => { + let function_name = get_function_name("ethPayment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("ethPayment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = function - .decode_input(&tx_from_rpc.input.0) + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; let swap_id_input = get_function_input_data(&decoded, function, 0) .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if swap_id_input != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Invalid 'swap_id' {:?}, expected {:?}", - decoded, swap_id + "{INVALID_SWAP_ID_ERR_LOG}: Invalid 'swap_id' {decoded:?}, expected {swap_id:?}" ))); } @@ -1585,9 +1359,7 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if receiver_input != Token::Address(receiver) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx receiver arg {:?} is invalid, expected {:?}", - receiver_input, - Token::Address(receiver) + "{INVALID_RECEIVER_ERR_LOG}: Payment tx receiver arg {receiver_input:?} is invalid, expected {:?}", Token::Address(receiver) ))); } @@ -1610,24 +1382,46 @@ impl WatcherOps for EthCoin { Token::Uint(U256::from(input.time_lock)), ))); } + + let watcher_reward = get_function_input_data(&decoded, function, 4) + .map_to_mm(ValidatePaymentError::TxDeserializationError)? + .into_uint() + .ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for watcher reward argument".to_string()) + })? + .as_u64(); + + if watcher_reward < min_watcher_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INSUFFICIENT_WATCHER_REWARD_ERR_LOG}: Provided watcher reward {} is less than the minimum required amount {}", + watcher_reward, min_watcher_reward, + ))); + } }, EthCoinType::Erc20 { platform: _, token_addr, } => { + let function_name = get_function_name("erc20Payment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("erc20Payment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = function - .decode_input(&tx_from_rpc.input.0) + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if tx_from_rpc.value.as_u64() < min_watcher_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INSUFFICIENT_WATCHER_REWARD_ERR_LOG}: Provided watcher reward {} is less than the minimum required amount {}", + tx_from_rpc.value.as_u64(), + min_watcher_reward, + ))); + } + let swap_id_input = get_function_input_data(&decoded, function, 0) .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if swap_id_input != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Invalid 'swap_id' {:?}, expected {:?}", - decoded, swap_id + "{INVALID_SWAP_ID_ERR_LOG}: Invalid 'swap_id' {decoded:?}, expected {swap_id:?}" ))); } @@ -1645,9 +1439,7 @@ impl WatcherOps for EthCoin { .map_to_mm(ValidatePaymentError::TxDeserializationError)?; if receiver_addr_input != Token::Address(receiver) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx receiver arg {:?} is invalid, expected {:?}", - receiver_addr_input, - Token::Address(receiver), + "{INVALID_RECEIVER_ERR_LOG}: Payment tx receiver arg {receiver_addr_input:?} is invalid, expected {:?}", Token::Address(receiver), ))); } @@ -1694,6 +1486,7 @@ impl WatcherOps for EthCoin { swap_contract_address, input.secret_hash, input.search_from_block, + input.watcher_reward, ) .await } @@ -1895,7 +1688,18 @@ impl MarketCoinOps for EthCoin { ) -> TransactionFut { let unverified: UnverifiedTransaction = try_tx_fus!(rlp::decode(tx_bytes)); let tx = try_tx_fus!(SignedEthTx::new(unverified)); - let swap_contract_address = try_tx_fus!(swap_contract_address.try_to_address()); + + let swap_contract_address = match swap_contract_address { + Some(addr) => try_tx_fus!(addr.try_to_address()), + None => match tx.action { + Call(address) => address, + Create => { + return Box::new(futures01::future::err(TransactionErr::Plain(ERRL!( + "Invalid payment action: the payment action cannot be create" + )))) + }, + }, + }; let func_name = match self.coin_type { EthCoinType::Eth => "ethPayment", @@ -2890,30 +2694,44 @@ impl EthCoin { } } - fn send_hash_time_locked_payment( - &self, - id: Vec, - value: U256, - time_lock: u32, - secret_hash: &[u8], - receiver_addr: Address, - swap_contract_address: Address, - ) -> EthTxFut { - let secret_hash = if secret_hash.len() == 32 { - ripemd160(secret_hash).to_vec() + fn send_hash_time_locked_payment(&self, args: SendPaymentArgs<'_>) -> EthTxFut { + let receiver_addr = try_tx_fus!(addr_from_raw_pubkey(args.other_pubkey)); + let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); + let id = self.etomic_swap_id(args.time_lock, args.secret_hash); + let trade_amount = try_tx_fus!(wei_from_big_decimal(&args.amount, self.decimals)); + + let secret_hash = if args.secret_hash.len() == 32 { + ripemd160(args.secret_hash).to_vec() } else { - secret_hash.to_vec() + args.secret_hash.to_vec() }; match &self.coin_type { EthCoinType::Eth => { - let function = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let data = try_tx_fus!(function.encode_input(&[ - Token::FixedBytes(id), - Token::Address(receiver_addr), - Token::FixedBytes(secret_hash), - Token::Uint(U256::from(time_lock)) - ])); + let function_name = get_function_name("ethPayment", args.watcher_reward.is_some()); + let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let mut value = trade_amount; + + let data = match args.watcher_reward { + Some(reward) => { + value += U256::from(reward); + + try_tx_fus!(function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(receiver_addr), + Token::FixedBytes(secret_hash), + Token::Uint(U256::from(args.time_lock)), + Token::Uint(U256::from(reward)), + ])) + }, + None => try_tx_fus!(function.encode_input(&[ + Token::FixedBytes(id), + Token::Address(receiver_addr), + Token::FixedBytes(secret_hash), + Token::Uint(U256::from(args.time_lock)), + ])), + }; + self.sign_and_send_transaction(value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS)) }, EthCoinType::Erc20 { @@ -2924,16 +2742,18 @@ impl EthCoin { .allowance(swap_contract_address) .map_err(|e| TransactionErr::Plain(ERRL!("{}", e))); - let function = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", args.watcher_reward.is_some()); + let function = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let data = try_tx_fus!(function.encode_input(&[ Token::FixedBytes(id), - Token::Uint(value), + Token::Uint(trade_amount), Token::Address(*token_addr), Token::Address(receiver_addr), Token::FixedBytes(secret_hash), - Token::Uint(U256::from(time_lock)) + Token::Uint(U256::from(args.time_lock)) ])); + let value = U256::from(args.watcher_reward.unwrap_or(0)); let arc = self.clone(); Box::new(allowance_fut.and_then(move |allowed| -> EthTxFut { @@ -2942,7 +2762,7 @@ impl EthCoin { arc.approve(swap_contract_address, U256::max_value()) .and_then(move |_approved| { arc.sign_and_send_transaction( - 0.into(), + value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS), @@ -2951,7 +2771,7 @@ impl EthCoin { ) } else { Box::new(arc.sign_and_send_transaction( - 0.into(), + value, Action::Call(swap_contract_address), data, U256::from(ETH_GAS), @@ -2962,17 +2782,15 @@ impl EthCoin { } } - fn watcher_spend_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - secret: &[u8], - taker_pub: &[u8], - ) -> EthTxFut { - let spend_func = try_tx_fus!(SWAP_CONTRACT.function("watcherSpend")); + fn watcher_spends_hash_time_locked_payment(&self, input: SendMakerPaymentSpendPreimageInput) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(input.preimage)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + + let function_name = get_function_name("receiverSpend", input.watcher_reward); + let spend_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let clone = self.clone(); - let secret_vec = secret.to_vec(); - let taker_addr = addr_from_raw_pubkey(taker_pub).unwrap(); + let secret_vec = input.secret.to_vec(); + let taker_addr = addr_from_raw_pubkey(input.taker_pub).unwrap(); let swap_contract_address = match payment.action { Call(address) => address, Create => { @@ -2982,10 +2800,12 @@ impl EthCoin { }, }; + let watcher_reward = input.watcher_reward; match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3002,13 +2822,15 @@ impl EthCoin { } let value = payment.value; + let watcher_reward_amount = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let data = try_tx_fus!(spend_func.encode_input(&[ swap_id_input, Token::Uint(value), Token::FixedBytes(secret_vec.clone()), Token::Address(Address::default()), Token::Address(payment.sender()), - Token::Address(taker_addr) + Token::Address(taker_addr), + watcher_reward_amount, ])); clone.sign_and_send_transaction( @@ -3024,9 +2846,10 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3048,9 +2871,9 @@ impl EthCoin { Token::FixedBytes(secret_vec.clone()), Token::Address(token_addr), Token::Address(payment.sender()), - Token::Address(taker_addr) + Token::Address(taker_addr), + Token::Uint(payment.value) ])); - clone.sign_and_send_transaction( 0.into(), Action::Call(swap_contract_address), @@ -3063,15 +2886,16 @@ impl EthCoin { } } - fn watcher_refunds_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - taker_pub: &[u8], - ) -> EthTxFut { - let refund_func = try_tx_fus!(SWAP_CONTRACT.function("watcherRefund")); + fn watcher_refunds_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let watcher_reward = args.watcher_reward; + + let function_name = get_function_name("senderRefund", watcher_reward); + let refund_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); - let taker_addr = addr_from_raw_pubkey(taker_pub).unwrap(); + let taker_addr = addr_from_raw_pubkey(args.other_pubkey).unwrap(); let swap_contract_address = match payment.action { Call(address) => address, Create => { @@ -3083,10 +2907,11 @@ impl EthCoin { match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); - let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); + let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); let hash_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 2)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); @@ -3103,13 +2928,16 @@ impl EthCoin { } let value = payment.value; + let watcher_reward_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); + let data = try_tx_fus!(refund_func.encode_input(&[ swap_id_input.clone(), Token::Uint(value), hash_input.clone(), Token::Address(Address::default()), Token::Address(taker_addr), - amount_input.clone(), + receiver_input.clone(), + watcher_reward_input, ])); clone.sign_and_send_transaction( @@ -3125,12 +2953,14 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); - let decoded = try_tx_fus!(payment_func.decode_input(&payment.data)); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let swap_id_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 0)); let amount_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 1)); - let token_addr_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 3)); - let sender_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); + let receiver_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 3)); + let hash_input = try_tx_fus!(get_function_input_data(&decoded, payment_func, 4)); let state_f = self.payment_status(swap_contract_address, swap_id_input.clone()); Box::new( state_f @@ -3147,10 +2977,11 @@ impl EthCoin { let data = try_tx_fus!(refund_func.encode_input(&[ swap_id_input.clone(), amount_input.clone(), - sender_input.clone(), + hash_input.clone(), Token::Address(token_addr), Token::Address(taker_addr), - token_addr_input.clone(), + receiver_input.clone(), + Token::Uint(payment.value), ])); clone.sign_and_send_transaction( @@ -3165,20 +2996,22 @@ impl EthCoin { } } - fn spend_hash_time_locked_payment( - &self, - payment: SignedEthTx, - _secret_hash: &[u8], - swap_contract_address: Address, - secret: &[u8], - ) -> EthTxFut { - let spend_func = try_tx_fus!(SWAP_CONTRACT.function("receiverSpend")); + fn spend_hash_time_locked_payment(&self, args: SpendPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.other_payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); + let watcher_reward = args.watcher_reward; + + let function_name = get_function_name("receiverSpend", watcher_reward); + let spend_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); - let secret_vec = secret.to_vec(); + let secret_vec = args.secret.to_vec(); match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3194,14 +3027,25 @@ impl EthCoin { )))); } - let value = payment.value; - let data = try_tx_fus!(spend_func.encode_input(&[ - decoded[0].clone(), - Token::Uint(value), - Token::FixedBytes(secret_vec), - Token::Address(Address::default()), - Token::Address(payment.sender()), - ])); + let data = if watcher_reward { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(payment.value), + Token::FixedBytes(secret_vec), + Token::Address(Address::default()), + Token::Address(payment.sender()), + Token::Address(clone.my_address), + decoded[4].clone(), + ])) + } else { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(payment.value), + Token::FixedBytes(secret_vec), + Token::Address(Address::default()), + Token::Address(payment.sender()), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3216,7 +3060,8 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3232,13 +3077,25 @@ impl EthCoin { state )))); } - let data = try_tx_fus!(spend_func.encode_input(&[ - decoded[0].clone(), - decoded[1].clone(), - Token::FixedBytes(secret_vec), - Token::Address(token_addr), - Token::Address(payment.sender()), - ])); + let data = if watcher_reward { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + Token::FixedBytes(secret_vec), + Token::Address(token_addr), + Token::Address(payment.sender()), + Token::Address(clone.my_address), + Token::Uint(payment.value), + ])) + } else { + try_tx_fus!(spend_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + Token::FixedBytes(secret_vec), + Token::Address(token_addr), + Token::Address(payment.sender()), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3252,18 +3109,22 @@ impl EthCoin { } } - fn refund_hash_time_locked_payment( - &self, - swap_contract_address: Address, - payment: SignedEthTx, - _secret_hash: &[u8], - ) -> EthTxFut { - let refund_func = try_tx_fus!(SWAP_CONTRACT.function("senderRefund")); + fn refund_hash_time_locked_payment(&self, args: RefundPaymentArgs) -> EthTxFut { + let tx: UnverifiedTransaction = try_tx_fus!(rlp::decode(args.payment_tx)); + let payment = try_tx_fus!(SignedEthTx::new(tx)); + let swap_contract_address = try_tx_fus!(args.swap_contract_address.try_to_address()); + + let watcher_reward = args.watcher_reward; + let function_name = get_function_name("senderRefund", watcher_reward); + let refund_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let clone = self.clone(); match self.coin_type { EthCoinType::Eth => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("ethPayment")); + let function_name = get_function_name("ethPayment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); @@ -3280,13 +3141,25 @@ impl EthCoin { } let value = payment.value; - let data = try_tx_fus!(refund_func.encode_input(&[ - decoded[0].clone(), - Token::Uint(value), - decoded[2].clone(), - Token::Address(Address::default()), - decoded[1].clone(), - ])); + let data = if watcher_reward { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(value), + decoded[2].clone(), + Token::Address(Address::default()), + Token::Address(clone.my_address), + decoded[1].clone(), + decoded[4].clone(), + ])) + } else { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + Token::Uint(value), + decoded[2].clone(), + Token::Address(Address::default()), + decoded[1].clone(), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3301,7 +3174,9 @@ impl EthCoin { platform: _, token_addr, } => { - let payment_func = try_tx_fus!(SWAP_CONTRACT.function("erc20Payment")); + let function_name = get_function_name("erc20Payment", watcher_reward); + let payment_func = try_tx_fus!(SWAP_CONTRACT.function(&function_name)); + let decoded = try_tx_fus!(decode_contract_call(payment_func, &payment.data)); let state_f = self.payment_status(swap_contract_address, decoded[0].clone()); Box::new( @@ -3316,13 +3191,25 @@ impl EthCoin { )))); } - let data = try_tx_fus!(refund_func.encode_input(&[ - decoded[0].clone(), - decoded[1].clone(), - decoded[4].clone(), - Token::Address(token_addr), - decoded[3].clone(), - ])); + let data = if watcher_reward { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + decoded[4].clone(), + Token::Address(token_addr), + Token::Address(clone.my_address), + decoded[3].clone(), + Token::Uint(payment.value), + ])) + } else { + try_tx_fus!(refund_func.encode_input(&[ + decoded[0].clone(), + decoded[1].clone(), + decoded[4].clone(), + Token::Address(token_addr), + decoded[3].clone(), + ])) + }; clone.sign_and_send_transaction( 0.into(), @@ -3543,28 +3430,26 @@ impl EthCoin { Box::new(self.web3.eth().logs(filter).compat().map_err(|e| ERRL!("{}", e))) } - fn validate_payment( - &self, - payment_tx: &[u8], - time_lock: u32, - sender_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - expected_swap_contract_address: Address, - ) -> ValidatePaymentFut<()> { - let unsigned: UnverifiedTransaction = try_f!(rlp::decode(payment_tx)); + fn validate_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { + let expected_swap_contract_address = try_f!(input + .swap_contract_address + .try_to_address() + .map_to_mm(ValidatePaymentError::InvalidParameter)); + + let unsigned: UnverifiedTransaction = try_f!(rlp::decode(&input.payment_tx)); let tx = try_f!(SignedEthTx::new(unsigned) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))); - let sender = try_f!(addr_from_raw_pubkey(sender_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); - let expected_value = try_f!(wei_from_big_decimal(&amount, self.decimals)); + let sender = try_f!(addr_from_raw_pubkey(&input.other_pub).map_to_mm(ValidatePaymentError::InvalidParameter)); + let selfi = self.clone(); - let swap_id = selfi.etomic_swap_id(time_lock, secret_hash); - let secret_hash = if secret_hash.len() == 32 { - ripemd160(secret_hash).to_vec() + let swap_id = selfi.etomic_swap_id(input.time_lock, &input.secret_hash); + let secret_hash = if input.secret_hash.len() == 32 { + ripemd160(&input.secret_hash).to_vec() } else { - secret_hash.to_vec() + input.secret_hash.to_vec() }; + let mut expected_value = try_f!(wei_from_big_decimal(&input.amount, self.decimals)); let fut = async move { let status = selfi .payment_status(expected_swap_contract_address, Token::FixedBytes(swap_id.clone())) @@ -3599,17 +3484,36 @@ impl EthCoin { ))); } + let function_name = get_function_name("ethPayment", input.min_watcher_reward.is_some()); + let function = SWAP_CONTRACT + .function(&function_name) + .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; + + let decoded = decode_contract_call(function, &tx_from_rpc.input.0) + .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + + if let Some(min_reward) = input.min_watcher_reward { + let reward = decoded[4].clone().into_uint().ok_or_else(|| { + ValidatePaymentError::WrongPaymentTx("Invalid type for watcher reward argument".to_string()) + })?; + if reward.as_u64() < min_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Insufficient watcher reward {}, must be at least {}", + reward, min_reward + ))); + } + + expected_value += reward; + } + drop_mutability!(expected_value); + if tx_from_rpc.value != expected_value { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx value arg {:?} is invalid, expected {:?}", tx_from_rpc, expected_value ))); } - let function = SWAP_CONTRACT - .function("ethPayment") - .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; - let decoded = decode_contract_call(function, &tx_from_rpc.input.0) - .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if decoded[0] != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid 'swap_id' {:?}, expected {:?}", @@ -3633,11 +3537,11 @@ impl EthCoin { ))); } - if decoded[3] != Token::Uint(U256::from(time_lock)) { + if decoded[3] != Token::Uint(U256::from(input.time_lock)) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[3], - Token::Uint(U256::from(time_lock)), + Token::Uint(U256::from(input.time_lock)), ))); } }, @@ -3651,11 +3555,21 @@ impl EthCoin { tx_from_rpc, expected_swap_contract_address, ))); } + if let Some(min_reward) = input.min_watcher_reward { + if tx_from_rpc.value.as_u64() < min_reward { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Payment tx value arg {} is less than the minimum expected watcher reward {}", + tx_from_rpc.value, min_reward + ))); + } + } + let function_name = get_function_name("erc20Payment", input.min_watcher_reward.is_some()); let function = SWAP_CONTRACT - .function("erc20Payment") + .function(&function_name) .map_to_mm(|err| ValidatePaymentError::InternalError(err.to_string()))?; let decoded = decode_contract_call(function, &tx_from_rpc.input.0) .map_to_mm(|err| ValidatePaymentError::TxDeserializationError(err.to_string()))?; + if decoded[0] != Token::FixedBytes(swap_id.clone()) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid 'swap_id' {:?}, expected {:?}", @@ -3695,11 +3609,11 @@ impl EthCoin { ))); } - if decoded[5] != Token::Uint(U256::from(time_lock)) { + if decoded[5] != Token::Uint(U256::from(input.time_lock)) { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Payment tx time_lock arg {:?} is invalid, expected {:?}", decoded[5], - Token::Uint(U256::from(time_lock)), + Token::Uint(U256::from(input.time_lock)), ))); } }, @@ -3740,16 +3654,17 @@ impl EthCoin { swap_contract_address: Address, _secret_hash: &[u8], search_from_block: u64, + watcher_reward: bool, ) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(tx)); let tx = try_s!(SignedEthTx::new(unverified)); let func_name = match self.coin_type { - EthCoinType::Eth => "ethPayment", - EthCoinType::Erc20 { .. } => "erc20Payment", + EthCoinType::Eth => get_function_name("ethPayment", watcher_reward), + EthCoinType::Erc20 { .. } => get_function_name("erc20Payment", watcher_reward), }; - let payment_func = try_s!(SWAP_CONTRACT.function(func_name)); + let payment_func = try_s!(SWAP_CONTRACT.function(&func_name)); let decoded = try_s!(decode_contract_call(payment_func, &tx.data)); let id = match decoded.first() { Some(Token::FixedBytes(bytes)) => bytes.clone(), @@ -3828,7 +3743,7 @@ impl EthCoin { } /// Get gas price - fn get_gas_price(&self) -> Web3RpcFut { + pub fn get_gas_price(&self) -> Web3RpcFut { let coin = self.clone(); let fut = async move { // TODO refactor to error_log_passthrough once simple maker bot is merged @@ -4284,6 +4199,108 @@ impl GuiAuthMessages for EthCoin { } } +fn validate_fee_impl(coin: EthCoin, validate_fee_args: EthValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { + let fee_tx_hash = validate_fee_args.fee_tx_hash.to_owned(); + let sender_addr = try_f!( + addr_from_raw_pubkey(validate_fee_args.expected_sender).map_to_mm(ValidatePaymentError::InvalidParameter) + ); + let fee_addr = + try_f!(addr_from_raw_pubkey(validate_fee_args.fee_addr).map_to_mm(ValidatePaymentError::InvalidParameter)); + let amount = validate_fee_args.amount.clone(); + let min_block_number = validate_fee_args.min_block_number; + + let fut = async move { + let expected_value = wei_from_big_decimal(&amount, coin.decimals)?; + let tx_from_rpc = coin.web3.eth().transaction(TransactionId::Hash(fee_tx_hash)).await?; + + let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| { + ValidatePaymentError::TxDoesNotExist(format!("Didn't find provided tx {:?} on ETH node", fee_tx_hash)) + })?; + + if tx_from_rpc.from != Some(sender_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} was sent from wrong address, expected {:?}", + INVALID_SENDER_ERR_LOG, tx_from_rpc, sender_addr + ))); + } + + if let Some(block_number) = tx_from_rpc.block_number { + if block_number <= min_block_number.into() { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} confirmed before min_block {}", + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number + ))); + } + } + match &coin.coin_type { + EthCoinType::Eth => { + if tx_from_rpc.to != Some(fee_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} was sent to wrong address, expected {:?}", + INVALID_RECEIVER_ERR_LOG, tx_from_rpc, fee_addr + ))); + } + + if tx_from_rpc.value < expected_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Fee tx {:?} value is less than expected {:?}", + tx_from_rpc, expected_value + ))); + } + }, + EthCoinType::Erc20 { + platform: _, + token_addr, + } => { + if tx_from_rpc.to != Some(*token_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: ERC20 Fee tx {:?} called wrong smart contract, expected {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, tx_from_rpc, token_addr + ))); + } + + let function = ERC20_CONTRACT + .function("transfer") + .map_to_mm(|e| ValidatePaymentError::InternalError(e.to_string()))?; + let decoded_input = decode_contract_call(function, &tx_from_rpc.input.0) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; + let address_input = get_function_input_data(&decoded_input, function, 0) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + + if address_input != Token::Address(fee_addr) { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: ERC20 Fee tx was sent to wrong address {:?}, expected {:?}", + INVALID_RECEIVER_ERR_LOG, address_input, fee_addr + ))); + } + + let value_input = get_function_input_data(&decoded_input, function, 1) + .map_to_mm(ValidatePaymentError::TxDeserializationError)?; + + match value_input { + Token::Uint(value) => { + if value < expected_value { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "ERC20 Fee tx value {} is less than expected {}", + value, expected_value + ))); + } + }, + _ => { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Should have got uint token but got {:?}", + value_input + ))) + }, + } + }, + } + + Ok(()) + }; + Box::new(fut.boxed().compat()) +} + fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> Result { decoded.get(index).cloned().ok_or(format!( "Missing input in function {}: No input found at index {}", @@ -4292,6 +4309,14 @@ fn get_function_input_data(decoded: &[Token], func: &Function, index: usize) -> )) } +fn get_function_name(name: &str, watcher_reward: bool) -> String { + if watcher_reward { + format!("{}{}", name, "Reward") + } else { + name.to_owned() + } +} + pub fn addr_from_raw_pubkey(pubkey: &[u8]) -> Result { let pubkey = try_s!(PublicKey::from_slice(pubkey).map_err(|e| ERRL!("{:?}", e))); let eth_public = Public::from_slice(&pubkey.serialize_uncompressed()[1..65]); @@ -4532,13 +4557,13 @@ pub async fn eth_coin_from_conf_and_request( if swap_contract_address == Address::default() { return ERR!("swap_contract_address can't be zero address"); } - let fallback_swap_contract: Option
= try_s!(json::from_value(req["fallback_swap_contract"].clone())); if let Some(fallback) = fallback_swap_contract { if fallback == Address::default() { return ERR!("fallback_swap_contract can't be zero address"); } } + let contract_supports_watchers = req["contract_supports_watchers"].as_bool().unwrap_or_default(); let (my_address, key_pair) = try_s!(build_address_and_priv_key_policy(conf, priv_key_policy).await); @@ -4629,6 +4654,7 @@ pub async fn eth_coin_from_conf_and_request( sign_message_prefix, swap_contract_address, fallback_swap_contract, + contract_supports_watchers, decimals, ticker: ticker.into(), gas_station_url: try_s!(json::from_value(req["gas_station_url"].clone())), diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 954080fdf3..f4890d6a77 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -66,6 +66,7 @@ fn eth_coin_for_test( priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract, + contract_supports_watchers: false, ticker, web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -232,6 +233,7 @@ fn send_and_refund_erc20_payment() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -250,7 +252,7 @@ fn send_and_refund_erc20_payment() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -259,19 +261,21 @@ fn send_and_refund_erc20_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); log!("{:?}", payment); block_on(Timer::sleep(60.)); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment.tx_hex(), time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, secret_hash: &[1; 20], swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -299,6 +303,7 @@ fn send_and_refund_eth_payment() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -317,7 +322,7 @@ fn send_and_refund_eth_payment() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let send_maker_payment_args = SendMakerPaymentArgs { + let send_maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -326,19 +331,21 @@ fn send_and_refund_eth_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(send_maker_payment_args).wait().unwrap(); log!("{:?}", payment); block_on(Timer::sleep(60.)); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment.tx_hex(), time_lock: (now_ms() / 1000) as u32 - 200, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, secret_hash: &[1; 20], swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -374,6 +381,7 @@ fn test_nonce_several_urls() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![ Web3Instance { web3: web3_infura.clone(), @@ -439,6 +447,7 @@ fn test_wait_for_payment_spend_timeout() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -509,6 +518,7 @@ fn test_search_for_swap_tx_spend_was_spent() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -553,9 +563,10 @@ fn test_search_for_swap_tx_spend_was_spent() { ]; let spend_tx = FoundSwapTxSpend::Spent(signed_eth_tx_from_bytes(&spend_tx).unwrap().into()); - let found_tx = block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 15643279)) - .unwrap() - .unwrap(); + let found_tx = + block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 15643279, false)) + .unwrap() + .unwrap(); assert_eq!(spend_tx, found_tx); } @@ -620,6 +631,7 @@ fn test_search_for_swap_tx_spend_was_refunded() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "BAT".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -667,9 +679,10 @@ fn test_search_for_swap_tx_spend_was_refunded() { ]; let refund_tx = FoundSwapTxSpend::Refunded(signed_eth_tx_from_bytes(&refund_tx).unwrap().into()); - let found_tx = block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 13638713)) - .unwrap() - .unwrap(); + let found_tx = + block_on(coin.search_for_swap_tx_spend(&payment_tx, swap_contract_address, &[0; 20], 13638713, false)) + .unwrap() + .unwrap(); assert_eq!(refund_tx, found_tx); } @@ -1036,8 +1049,11 @@ fn validate_dex_fee_invalid_sender_eth() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } #[test] @@ -1067,8 +1083,11 @@ fn validate_dex_fee_invalid_sender_erc() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] { @@ -1102,8 +1121,11 @@ fn validate_dex_fee_eth_confirmed_before_min_block() { min_block_number: 11784793, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] @@ -1136,8 +1158,11 @@ fn validate_dex_fee_erc_confirmed_before_min_block() { min_block_number: 11823975, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] @@ -1276,6 +1301,7 @@ fn test_message_hash() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -1320,6 +1346,7 @@ fn test_sign_verify_message() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -1375,6 +1402,7 @@ fn test_eth_extract_secret() { priv_key_policy: key_pair.into(), swap_contract_address, fallback_swap_contract: None, + contract_supports_watchers: false, ticker: "ETH".into(), web3_instances: vec![Web3Instance { web3: web3.clone(), @@ -1404,7 +1432,7 @@ fn test_eth_extract_secret() { 100, 189, 72, 74, 221, 144, 66, 170, 68, 121, 29, 105, 19, 194, 35, 245, 196, 131, 236, 29, 105, 101, 30, ]; - let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())); + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice(), false)); assert!(secret.is_ok()); let expect_secret = &[ 168, 151, 11, 232, 224, 253, 63, 180, 26, 114, 23, 184, 27, 10, 161, 80, 178, 251, 73, 204, 80, 174, 97, 118, @@ -1427,7 +1455,7 @@ fn test_eth_extract_secret() { 6, 108, 165, 181, 188, 40, 56, 47, 211, 229, 221, 73, 5, 15, 89, 81, 117, 225, 216, 108, 98, 226, 119, 232, 94, 184, 42, 106, ]; - let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())) + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice(), false)) .err() .unwrap(); assert!(secret.contains("Expected 'receiverSpend' contract call signature")); diff --git a/mm2src/coins/eth/eth_wasm_tests.rs b/mm2src/coins/eth/eth_wasm_tests.rs index f8e22335df..d5ed0fc61c 100644 --- a/mm2src/coins/eth/eth_wasm_tests.rs +++ b/mm2src/coins/eth/eth_wasm_tests.rs @@ -31,6 +31,7 @@ async fn test_send() { priv_key_policy: key_pair.into(), swap_contract_address: Address::from_str("0x7Bc1bBDD6A0a722fC9bffC49c921B685ECB84b94").unwrap(), fallback_swap_contract: None, + contract_supports_watchers: false, web3_instances: vec![Web3Instance { web3: web3.clone(), is_parity: true, @@ -49,7 +50,7 @@ async fn test_send() { erc20_tokens_infos: Default::default(), abortable_system: AbortableQueue::default(), })); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: 1000, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -58,6 +59,7 @@ async fn test_send() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).compat().await; console::log_1(&format!("{:?}", tx).into()); diff --git a/mm2src/coins/eth/swap_contract_abi.json b/mm2src/coins/eth/swap_contract_abi.json index 7003d9a181..1ff37734b9 100644 --- a/mm2src/coins/eth/swap_contract_abi.json +++ b/mm2src/coins/eth/swap_contract_abi.json @@ -1,8 +1,29 @@ [ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "Log", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "number", + "type": "uint256" + } + ], + "name": "LogNumber", + "type": "event" }, { "anonymous": false, @@ -84,7 +105,7 @@ ], "name": "erc20Payment", "outputs": [], - "stateMutability": "payable", + "stateMutability": "nonpayable", "type": "function" }, { @@ -110,9 +131,9 @@ "type": "address" }, { - "internalType": "bytes32", + "internalType": "bytes20", "name": "_secretHash", - "type": "bytes32" + "type": "bytes20" }, { "internalType": "uint64", @@ -120,7 +141,7 @@ "type": "uint64" } ], - "name": "erc20PaymentSha256", + "name": "erc20PaymentReward", "outputs": [], "stateMutability": "payable", "type": "function" @@ -165,49 +186,25 @@ "name": "_receiver", "type": "address" }, - { - "internalType": "bytes32", - "name": "_secretHash", - "type": "bytes32" - }, - { - "internalType": "uint64", - "name": "_lockTime", - "type": "uint64" - } - ], - "name": "ethPaymentSha256", - "outputs": [], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "payments", - "outputs": [ { "internalType": "bytes20", - "name": "paymentHash", + "name": "_secretHash", "type": "bytes20" }, { "internalType": "uint64", - "name": "lockTime", + "name": "_lockTime", "type": "uint64" }, { - "internalType": "enum EtomicSwap.PaymentState", - "name": "state", - "type": "uint8" + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "stateMutability": "view", + "name": "ethPaymentReward", + "outputs": [], + "stateMutability": "payable", "type": "function" }, { @@ -243,25 +240,6 @@ "stateMutability": "nonpayable", "type": "function" }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "secret_hash_algos", - "outputs": [ - { - "internalType": "enum EtomicSwap.SecretHashAlgo", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -275,22 +253,32 @@ "type": "uint256" }, { - "internalType": "bytes20", - "name": "_secretHash", - "type": "bytes20" + "internalType": "bytes32", + "name": "_secret", + "type": "bytes32" }, { "internalType": "address", "name": "_tokenAddress", "type": "address" }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, { "internalType": "address", "name": "_receiver", "type": "address" + }, + { + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "name": "senderRefund", + "name": "receiverSpendReward", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -308,9 +296,9 @@ "type": "uint256" }, { - "internalType": "bytes32", - "name": "_secretHash", - "type": "bytes32" + "internalType": "bytes20", + "name": "_paymentHash", + "type": "bytes20" }, { "internalType": "address", @@ -323,7 +311,7 @@ "type": "address" } ], - "name": "senderRefundSha256", + "name": "senderRefund", "outputs": [], "stateMutability": "nonpayable", "type": "function" @@ -342,7 +330,7 @@ }, { "internalType": "bytes20", - "name": "_secretHash", + "name": "_paymentHash", "type": "bytes20" }, { @@ -359,49 +347,50 @@ "internalType": "address", "name": "_receiver", "type": "address" + }, + { + "internalType": "uint256", + "name": "_watcherReward", + "type": "uint256" } ], - "name": "watcherRefund", + "name": "senderRefundReward", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "inputs": [ { "internalType": "bytes32", - "name": "_id", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_secret", + "name": "", "type": "bytes32" - }, + } + ], + "name": "payments", + "outputs": [ { - "internalType": "address", - "name": "_tokenAddress", - "type": "address" + "internalType": "bytes20", + "name": "paymentHash", + "type": "bytes20" }, { - "internalType": "address", - "name": "_sender", - "type": "address" + "internalType": "uint64", + "name": "lockTime", + "type": "uint64" }, { - "internalType": "address", - "name": "_receiver", - "type": "address" + "internalType": "enum EtomicSwap.PaymentState", + "name": "state", + "type": "uint8" } ], - "name": "watcherSpend", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" } ] \ No newline at end of file diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 3395cbed70..17e89cd2ae 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -90,6 +90,8 @@ pub struct EthActivationV2Request { pub rpc_mode: EthRpcMode, pub swap_contract_address: Address, pub fallback_swap_contract: Option
, + #[serde(default)] + pub contract_supports_watchers: bool, pub gas_station_url: Option, pub gas_station_decimals: Option, #[serde(default)] @@ -197,6 +199,7 @@ impl EthCoin { sign_message_prefix: self.sign_message_prefix.clone(), swap_contract_address: self.swap_contract_address, fallback_swap_contract: self.fallback_swap_contract, + contract_supports_watchers: self.contract_supports_watchers, decimals, ticker, gas_station_url: self.gas_station_url.clone(), @@ -292,6 +295,7 @@ pub async fn eth_coin_from_conf_and_request_v2( sign_message_prefix, swap_contract_address: req.swap_contract_address, fallback_swap_contract: req.fallback_swap_contract, + contract_supports_watchers: req.contract_supports_watchers, decimals: ETH_DECIMALS, ticker, gas_station_url: req.gas_station_url, diff --git a/mm2src/coins/lightning.rs b/mm2src/coins/lightning.rs index 8a69514384..98d787257f 100644 --- a/mm2src/coins/lightning.rs +++ b/mm2src/coins/lightning.rs @@ -18,15 +18,13 @@ use crate::utxo::{sat_from_big_decimal, utxo_common, BlockchainNetwork}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendSpendPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, Transaction, TransactionEnum, TransactionErr, - TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, UtxoStandardCoin, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + Transaction, TransactionEnum, TransactionErr, TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, + UtxoStandardCoin, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, + WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcoin::bech32::ToBase32; @@ -523,7 +521,7 @@ impl LightningCoin { Ok(PaymentInstructions::Lightning(invoice)) } - fn spend_swap_payment(&self, spend_payment_args: SendSpendPaymentArgs<'_>) -> TransactionFut { + fn spend_swap_payment(&self, spend_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let payment_hash = try_tx_fus!(payment_hash_from_slice(spend_payment_args.other_payment_tx)); let mut preimage = [b' '; 32]; preimage.copy_from_slice(spend_payment_args.secret); @@ -601,7 +599,7 @@ impl SwapOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let PaymentInstructions::Lightning(invoice) = try_tx_fus!(maker_payment_args .payment_instructions .clone() @@ -615,7 +613,7 @@ impl SwapOps for LightningCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let PaymentInstructions::Lightning(invoice) = try_tx_fus!(taker_payment_args .payment_instructions .clone() @@ -634,44 +632,29 @@ impl SwapOps for LightningCoin { } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { self.spend_swap_payment(maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { self.spend_swap_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment( - &self, - _taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), ))) } - fn send_maker_refunds_payment( - &self, - _maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund lightning HTLC".into(), ))) } // Todo: This validates the dummy fee 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 validate_fee( - &self, - _validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { Box::new(futures01::future::ok(())) } @@ -782,7 +765,12 @@ impl SwapOps for LightningCoin { unimplemented!(); } - async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + _secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { let payment_hash = payment_hash_from_slice(spend_tx).map_err(|e| e.to_string())?; let payment_hex = hex::encode(payment_hash.0); @@ -967,10 +955,7 @@ impl WatcherOps for LightningCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 265b3e69ea..acf59a756b 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -45,6 +45,7 @@ use crypto::{Bip32Error, CryptoCtx, CryptoCtxError, DerivationPath, GlobalHDAcco Secp256k1Secret, WithHwRpcError}; use derive_more::Display; use enum_from::{EnumFromStringify, EnumFromTrait}; +use ethereum_types::H256; use futures::compat::Future01CompatExt; use futures::lock::Mutex as AsyncMutex; use futures::{FutureExt, TryFutureExt}; @@ -278,13 +279,6 @@ pub type RawTransactionResult = Result = Box> + Send + 'a>; pub type RefundResult = Result>; -pub type SendMakerPaymentArgs<'a> = SendSwapPaymentArgs<'a>; -pub type SendTakerPaymentArgs<'a> = SendSwapPaymentArgs<'a>; -pub type SendMakerSpendsTakerPaymentArgs<'a> = SendSpendPaymentArgs<'a>; -pub type SendTakerSpendsMakerPaymentArgs<'a> = SendSpendPaymentArgs<'a>; -pub type SendTakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; -pub type SendMakerRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; -pub type SendWatcherRefundsPaymentArgs<'a> = SendRefundPaymentArgs<'a>; pub type IguanaPrivKey = Secp256k1Secret; @@ -294,6 +288,11 @@ pub const EARLY_CONFIRMATION_ERR_LOG: &str = "Early confirmation"; pub const OLD_TRANSACTION_ERR_LOG: &str = "Old transaction"; pub const INVALID_RECEIVER_ERR_LOG: &str = "Invalid receiver"; pub const INVALID_CONTRACT_ADDRESS_ERR_LOG: &str = "Invalid contract address"; +pub const INVALID_PAYMENT_STATE_ERR_LOG: &str = "Invalid payment state"; +pub const INVALID_SWAP_ID_ERR_LOG: &str = "Invalid swap id"; +pub const INVALID_SCRIPT_ERR_LOG: &str = "Invalid script"; +pub const INVALID_REFUND_TX_ERR_LOG: &str = "Invalid refund transaction"; +pub const INSUFFICIENT_WATCHER_REWARD_ERR_LOG: &str = "Insufficient watcher reward"; #[derive(Debug, Deserialize, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] @@ -518,6 +517,7 @@ pub struct WatcherValidatePaymentInput { pub secret_hash: Vec, pub try_spv_proof_until: u64, pub confirmations: u64, + pub min_watcher_reward: Option, } #[derive(Clone, Debug)] @@ -532,6 +532,7 @@ pub struct ValidatePaymentInput { pub try_spv_proof_until: u64, pub confirmations: u64, pub unique_swap_data: Vec, + pub min_watcher_reward: Option, } #[derive(Clone, Debug)] @@ -542,6 +543,7 @@ pub struct WatcherSearchForSwapTxSpendInput<'a> { pub secret_hash: &'a [u8], pub tx: &'a [u8], pub search_from_block: u64, + pub watcher_reward: bool, } #[derive(Clone, Debug)] @@ -550,6 +552,7 @@ pub struct SendMakerPaymentSpendPreimageInput<'a> { pub secret_hash: &'a [u8], pub secret: &'a [u8], pub taker_pub: &'a [u8], + pub watcher_reward: bool, } pub struct SearchForSwapTxSpendInput<'a> { @@ -560,10 +563,11 @@ pub struct SearchForSwapTxSpendInput<'a> { pub search_from_block: u64, pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] -pub struct SendSwapPaymentArgs<'a> { +pub struct SendPaymentArgs<'a> { pub time_lock_duration: u64, pub time_lock: u32, /// This is either: @@ -575,10 +579,11 @@ pub struct SendSwapPaymentArgs<'a> { pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], pub payment_instructions: &'a Option, + pub watcher_reward: Option, } #[derive(Clone, Debug)] -pub struct SendSpendPaymentArgs<'a> { +pub struct SpendPaymentArgs<'a> { /// This is either: /// * Taker's payment tx if this structure is used in [`SwapOps::send_maker_spends_taker_payment`]. /// * Maker's payment tx if this structure is used in [`SwapOps::send_taker_spends_maker_payment`]. @@ -592,10 +597,11 @@ pub struct SendSpendPaymentArgs<'a> { pub secret_hash: &'a [u8], pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] -pub struct SendRefundPaymentArgs<'a> { +pub struct RefundPaymentArgs<'a> { pub payment_tx: &'a [u8], pub time_lock: u32, /// This is either: @@ -605,6 +611,7 @@ pub struct SendRefundPaymentArgs<'a> { pub secret_hash: &'a [u8], pub swap_contract_address: &'a Option, pub swap_unique_data: &'a [u8], + pub watcher_reward: bool, } #[derive(Clone, Debug)] @@ -629,6 +636,15 @@ pub struct ValidateFeeArgs<'a> { pub uuid: &'a [u8], } +pub struct EthValidateFeeArgs<'a> { + pub fee_tx_hash: &'a H256, + pub expected_sender: &'a [u8], + pub fee_addr: &'a [u8], + pub amount: &'a BigDecimal, + pub min_block_number: u64, + pub uuid: &'a [u8], +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub enum PaymentInstructions { #[cfg(not(target_arch = "wasm32"))] @@ -669,28 +685,19 @@ pub enum RefundError { pub trait SwapOps { fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut; - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut; + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut; + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut; - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut; + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut; + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut; - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>) - -> TransactionFut; + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>) - -> TransactionFut; + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut; - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) - -> Box + Send>; + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()>; fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()>; @@ -711,7 +718,12 @@ pub trait SwapOps { input: SearchForSwapTxSpendInput<'_>, ) -> Result, String>; - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String>; + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String>; fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result>; @@ -782,6 +794,9 @@ pub trait SwapOps { fn is_supported_by_watchers(&self) -> bool { false } + // Do we also need a method for the fallback contract? + fn contract_supports_watchers(&self) -> bool { true } + fn maker_locktime_multiplier(&self) -> f64 { 2.0 } } @@ -807,10 +822,7 @@ pub trait MakerSwapTakerCoin { pub trait WatcherOps { fn send_maker_payment_spend_preimage(&self, input: SendMakerPaymentSpendPreimageInput) -> TransactionFut; - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut; + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut; fn create_taker_payment_refund_preimage( &self, @@ -2215,6 +2227,8 @@ impl MmCoinEnum { _ => false, } } + + pub fn is_eth(&self) -> bool { matches!(self, MmCoinEnum::EthCoin(_)) } } #[async_trait] diff --git a/mm2src/coins/qrc20.rs b/mm2src/coins/qrc20.rs index cddbfaf6d5..2bb6fe9a76 100644 --- a/mm2src/coins/qrc20.rs +++ b/mm2src/coins/qrc20.rs @@ -18,17 +18,15 @@ use crate::utxo::{qtum, ActualTxFee, AdditionalTxData, AddrFromStrError, Broadca use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, IguanaPrivKey, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, - PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, - TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, - TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest, WithdrawResult}; + PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bitcrypto::{dhash160, sha256}; use chain::TransactionOutput; @@ -765,7 +763,7 @@ impl SwapOps for Qrc20Coin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { let time_lock = maker_payment_args.time_lock; let taker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(maker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, maker_payment_args.secret_hash); @@ -783,7 +781,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { let time_lock = taker_payment_args.time_lock; let maker_addr = try_tx_fus!(self.contract_address_from_raw_pubkey(taker_payment_args.other_pubkey)); let id = qrc20_swap_id(time_lock, taker_payment_args.secret_hash); @@ -801,10 +799,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_spends_payment_args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(maker_spends_payment_args.swap_contract_address.try_to_address()); @@ -820,10 +815,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_spends_payment_args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); let secret = taker_spends_payment_args.secret.to_vec(); @@ -839,7 +831,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(taker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(taker_refunds_payment_args.swap_contract_address.try_to_address()); @@ -854,7 +846,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let payment_tx: UtxoTx = try_tx_fus!(deserialize(maker_refunds_payment_args.payment_tx).map_err(|e| ERRL!("{:?}", e))); let swap_contract_address = try_tx_fus!(maker_refunds_payment_args.swap_contract_address.try_to_address()); @@ -869,10 +861,7 @@ impl SwapOps for Qrc20Coin { } #[inline] - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let fee_tx = validate_fee_args.fee_tx; let min_block_number = validate_fee_args.min_block_number; let fee_tx = match fee_tx { @@ -880,20 +869,26 @@ impl SwapOps for Qrc20Coin { _ => panic!("Unexpected TransactionEnum"), }; let fee_tx_hash = fee_tx.hash().reversed().into(); - if !try_fus!(check_all_utxo_inputs_signed_by_pub( + let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub( fee_tx, validate_fee_args.expected_sender - )) { - return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); + )); + if !inputs_signed_by_pub { + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx("The dex fee was sent from wrong address".to_string()).into(), + )); } - let fee_addr = try_fus!(self.contract_address_from_raw_pubkey(validate_fee_args.fee_addr)); - let expected_value = try_fus!(wei_from_big_decimal(validate_fee_args.amount, self.utxo.decimals)); + 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 selfi = self.clone(); let fut = async move { selfi .validate_fee_impl(fee_tx_hash, fee_addr, expected_value, min_block_number) .await + .map_to_mm(ValidatePaymentError::WrongPaymentTx) }; Box::new(fut.boxed().compat()) } @@ -997,7 +992,12 @@ impl SwapOps for Qrc20Coin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { self.extract_secret_impl(secret_hash, spend_tx) } @@ -1130,10 +1130,7 @@ impl WatcherOps for Qrc20Coin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/qrc20/qrc20_tests.rs b/mm2src/coins/qrc20/qrc20_tests.rs index 12f606cb26..7354b5a103 100644 --- a/mm2src/coins/qrc20/qrc20_tests.rs +++ b/mm2src/coins/qrc20/qrc20_tests.rs @@ -170,6 +170,7 @@ fn test_validate_maker_payment() { try_spv_proof_until: now_ms() / 1000 + 30, confirmations: 1, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; coin.validate_maker_payment(input.clone()).wait().unwrap(); @@ -314,7 +315,7 @@ fn test_send_taker_fee() { uuid: &[], }) .wait(); - assert_eq!(result, Ok(())); + assert!(result.is_ok()); } #[test] @@ -342,7 +343,7 @@ fn test_validate_fee() { uuid: &[], }) .wait(); - assert_eq!(result, Ok(())); + assert!(result.is_ok()); let fee_addr_dif = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap(); let err = coin @@ -356,9 +357,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("QRC20 Fee tx was sent to wrong address")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("QRC20 Fee tx was sent to wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong receiver address, found {:?}", err), + } let err = coin .validate_fee(ValidateFeeArgs { @@ -371,9 +376,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("was sent from wrong address")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("was sent from wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", err), + } let err = coin .validate_fee(ValidateFeeArgs { @@ -386,9 +395,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("confirmed before min_block")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", err), + } let amount_dif = BigDecimal::from_str("0.02").unwrap(); let err = coin @@ -402,9 +415,15 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("QRC20 Fee tx value 1000000 is less than expected 2000000")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains("QRC20 Fee tx value 1000000 is less than expected 2000000")) + }, + _ => panic!("Expected `WrongPaymentTx` invalid fee value, found {:?}", err), + } // QTUM tx "8a51f0ffd45f34974de50f07c5bf2f0949da4e88433f8f75191953a442cf9310" let tx = TransactionEnum::UtxoTx("020000000113640281c9332caeddd02a8dd0d784809e1ad87bda3c972d89d5ae41f5494b85010000006a47304402207c5c904a93310b8672f4ecdbab356b65dd869a426e92f1064a567be7ccfc61ff02203e4173b9467127f7de4682513a21efb5980e66dbed4da91dff46534b8e77c7ef012102baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1afeffffff020001b2c4000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acbc4dd20c2f0000001976a9144208fa7be80dcf972f767194ad365950495064a488ac76e70800".into()); @@ -420,9 +439,13 @@ fn test_validate_fee() { }) .wait() .err() - .expect("Expected an error"); + .expect("Expected an error") + .into_inner(); log!("error: {:?}", err); - assert!(err.contains("Expected 'transfer' contract call")); + match err { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Expected 'transfer' contract call")), + _ => panic!("Expected `WrongPaymentTx` invalid contract call, found {:?}", err), + } } #[test] @@ -478,7 +501,7 @@ fn test_extract_secret() { // taker spent maker payment - d3f5dab4d54c14b3d7ed8c7f5c8cc7f47ccf45ce589fdc7cd5140a3c1c3df6e1 let tx_hex = hex::decode("01000000033f56ecafafc8602fde083ba868d1192d6649b8433e42e1a2d79ba007ea4f7abb010000006b48304502210093404e90e40d22730013035d31c404c875646dcf2fad9aa298348558b6d65ba60220297d045eac5617c1a3eddb71d4bca9772841afa3c4c9d6c68d8d2d42ee6de3950121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff9cac7fe90d597922a1d92e05306c2215628e7ea6d5b855bfb4289c2944f4c73a030000006b483045022100b987da58c2c0c40ce5b6ef2a59e8124ed4ef7a8b3e60c7fb631139280019bc93022069649bcde6fe4dd5df9462a1fcae40598488d6af8c324cd083f5c08afd9568be0121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff70b9870f2b0c65d220a839acecebf80f5b44c3ca4c982fa2fdc5552c037f5610010000006a473044022071b34dd3ebb72d29ca24f3fa0fc96571c815668d3b185dd45cc46a7222b6843f02206c39c030e618d411d4124f7b3e7ca1dd5436775bd8083a85712d123d933a51300121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff020000000000000000c35403a0860101284ca402ed292b806a1835a1b514ad643f2acdb5c8db6b6a9714accff3275ea0d79a3f23be8fd00000000000000000000000000000000000000000000000000000000001312d000101010101010101010101010101010101010101010101010101010101010101000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac2c02288d4010000001976a914783cf0be521101942da509846ea476e683aad83288ac0f047f5f").unwrap(); - let secret = block_on(coin.extract_secret(secret_hash, &tx_hex)).unwrap(); + let secret = block_on(coin.extract_secret(secret_hash, &tx_hex, false)).unwrap(); assert_eq!(secret, expected_secret); } @@ -499,7 +522,7 @@ fn test_extract_secret_malicious() { let spend_tx = hex::decode("01000000022bc8299981ec0cea664cdf9df4f8306396a02e2067d6ac2d3770b34646d2bc2a010000006b483045022100eb13ef2d99ac1cd9984045c2365654b115dd8a7815b7fbf8e2a257f0b93d1592022060d648e73118c843e97f75fafc94e5ff6da70ec8ba36ae255f8c96e2626af6260121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffffd92a0a10ac6d144b36033916f67ae79889f40f35096629a5cd87be1a08f40ee7010000006b48304502210080cdad5c4770dfbeb760e215494c63cc30da843b8505e75e7bf9e8dad18568000220234c0b11c41bfbcdd50046c69059976aedabe17657fe43d809af71e9635678e20121022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1affffffff030000000000000000c35403a0860101284ca402ed292b8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d000202020202020202020202020202020202020202020202020202020202020202000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac20000000000000000c35403a0860101284ca402ed292b8620ad3b72361a5aeba5dffd333fb64750089d935a1ec974d6a91ef4f24ff6ba0000000000000000000000000000000000000000000000000000000001312d000101010101010101010101010101010101010101010101010101010101010101000000000000000000000000d362e096e873eb7907e205fadc6175c6fec7bc440000000000000000000000009e032d4b0090a11dc40fe6c47601499a35d55fbb14ba8b71f3544b93e2f681f996da519a98ace0107ac2b8ea82d3010000001976a914783cf0be521101942da509846ea476e683aad83288ac735d855f").unwrap(); let expected_secret = &[1; 32]; let secret_hash = &*dhash160(expected_secret); - let actual = block_on(coin.extract_secret(secret_hash, &spend_tx)); + let actual = block_on(coin.extract_secret(secret_hash, &spend_tx, false)); assert_eq!(actual, Ok(expected_secret.to_vec())); } @@ -1043,6 +1066,7 @@ fn test_validate_maker_payment_malicious() { confirmations: 1, other_pub: maker_pub, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let error = coin .validate_maker_payment(input) diff --git a/mm2src/coins/solana.rs b/mm2src/coins/solana.rs index cd92a1c30f..3f8dc1e6c9 100644 --- a/mm2src/coins/solana.rs +++ b/mm2src/coins/solana.rs @@ -5,15 +5,13 @@ use crate::solana::spl::SplTokenInfo; use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, RawTransactionRequest, RefundError, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, - TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, - WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, + TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, + ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use base58::ToBase58; use bincode::{deserialize, serialize}; @@ -489,35 +487,27 @@ impl MarketCoinOps for SolanaCoin { impl SwapOps for SolanaCoin { fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -548,7 +538,14 @@ impl SwapOps for SolanaCoin { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -657,10 +654,7 @@ impl WatcherOps for SolanaCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/solana/spl.rs b/mm2src/coins/solana/spl.rs index 05b4e76566..c005a09de9 100644 --- a/mm2src/coins/solana/spl.rs +++ b/mm2src/coins/solana/spl.rs @@ -4,16 +4,14 @@ use crate::solana::solana_common::{ui_amount_to_amount, PrepareTransferData, Suf use crate::solana::{solana_common, AccountError, SolanaCommonOps, SolanaFeeDetails}; use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, - RawTransactionFut, RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, SignatureResult, SolanaCoin, - TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, - TransactionFut, TransactionType, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, - WithdrawResult}; + RawTransactionFut, RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SolanaCoin, SpendPaymentArgs, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionFut, TransactionType, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest, WithdrawResult}; use async_trait::async_trait; use bincode::serialize; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError}; @@ -309,35 +307,25 @@ impl MarketCoinOps for SplToken { impl SwapOps for SplToken { fn send_taker_fee(&self, _fee_addr: &[u8], amount: BigDecimal, _uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - todo!() - } + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { todo!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -368,7 +356,14 @@ impl SwapOps for SplToken { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -477,10 +472,7 @@ impl WatcherOps for SplToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/tendermint/tendermint_coin.rs b/mm2src/coins/tendermint/tendermint_coin.rs index cb70b0b8fb..1d09ee3b18 100644 --- a/mm2src/coins/tendermint/tendermint_coin.rs +++ b/mm2src/coins/tendermint/tendermint_coin.rs @@ -9,11 +9,9 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionError, RawTransactionFut, - RawTransactionRequest, RawTransactionRes, RefundError, RefundResult, RpcCommonOps, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, + RawTransactionRequest, RawTransactionRes, RefundError, RefundPaymentArgs, RefundResult, RpcCommonOps, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, @@ -970,30 +968,33 @@ impl TendermintCoin { decimals: u8, uuid: &[u8], denom: String, - ) -> Box + Send> { + ) -> ValidatePaymentFut<()> { let tx = match fee_tx { TransactionEnum::CosmosTransaction(tx) => tx.clone(), invalid_variant => { - return Box::new(futures01::future::err(ERRL!( - "Unexpected tx variant {:?}", - invalid_variant - ))) + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx(format!("Unexpected tx variant {:?}", invalid_variant)).into(), + )) }, }; - let uuid = try_fus!(Uuid::from_slice(uuid)).to_string(); + let uuid = try_f!(Uuid::from_slice(uuid).map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) + .to_string(); + let sender_pubkey_hash = dhash160(expected_sender); - let expected_sender_address = - try_fus!(AccountId::new(&self.account_prefix, sender_pubkey_hash.as_slice())).to_string(); + let expected_sender_address = try_f!(AccountId::new(&self.account_prefix, sender_pubkey_hash.as_slice()) + .map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) + .to_string(); let dex_fee_addr_pubkey_hash = dhash160(fee_addr); - let expected_dex_fee_address = try_fus!(AccountId::new( + let expected_dex_fee_address = try_f!(AccountId::new( &self.account_prefix, dex_fee_addr_pubkey_hash.as_slice() - )) + ) + .map_to_mm(|r| ValidatePaymentError::InvalidParameter(r.to_string()))) .to_string(); - let expected_amount = try_fus!(sat_from_big_decimal(amount, decimals)); + let expected_amount = try_f!(sat_from_big_decimal(amount, decimals)); let expected_amount = CoinProto { denom, amount: expected_amount.to_string(), @@ -1001,45 +1002,61 @@ impl TendermintCoin { let coin = self.clone(); let fut = async move { - let tx_body = try_s!(TxBody::decode(tx.data.body_bytes.as_slice())); + let tx_body = TxBody::decode(tx.data.body_bytes.as_slice()) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; if tx_body.messages.len() != 1 { - return ERR!("Tx body must have exactly one message"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Tx body must have exactly one message".to_string(), + )); } - let msg = try_s!(MsgSendProto::decode(tx_body.messages[0].value.as_slice())); + let msg = MsgSendProto::decode(tx_body.messages[0].value.as_slice()) + .map_to_mm(|e| ValidatePaymentError::TxDeserializationError(e.to_string()))?; if msg.to_address != expected_dex_fee_address { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Dex fee is sent to wrong address: {}, expected {}", - msg.to_address, - expected_dex_fee_address - ); + msg.to_address, expected_dex_fee_address + ))); } if msg.amount.len() != 1 { - return ERR!("Msg must have exactly one Coin"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Msg must have exactly one Coin".to_string(), + )); } if msg.amount[0] != expected_amount { - return ERR!("Invalid amount {:?}, expected {:?}", msg.amount[0], expected_amount); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Invalid amount {:?}, expected {:?}", + msg.amount[0], expected_amount + ))); } if msg.from_address != expected_sender_address { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Invalid sender: {}, expected {}", - msg.from_address, - expected_sender_address - ); + msg.from_address, expected_sender_address + ))); } if tx_body.memo != uuid { - return ERR!("Invalid memo: {}, expected {}", msg.from_address, uuid); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Invalid memo: {}, expected {}", + msg.from_address, uuid + ))); } let encoded_tx = tx.data.encode_to_vec(); let hash = hex::encode_upper(sha256(&encoded_tx).as_slice()); - let encoded_from_rpc = try_s!(coin.request_tx(hash).await).encode_to_vec(); + let encoded_from_rpc = coin + .request_tx(hash) + .await + .map_err(|e| MmError::new(ValidatePaymentError::TxDeserializationError(e.into_inner().to_string())))? + .encode_to_vec(); if encoded_tx != encoded_from_rpc { - return ERR!("Transaction from RPC doesn't match the input"); + return MmError::err(ValidatePaymentError::WrongPaymentTx( + "Transaction from RPC doesn't match the input".to_string(), + )); } Ok(()) }; @@ -1888,7 +1905,7 @@ impl SwapOps for TendermintCoin { self.send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { self.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -1899,7 +1916,7 @@ impl SwapOps for TendermintCoin { ) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { self.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -1910,10 +1927,7 @@ impl SwapOps for TendermintCoin { ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = try_tx_fus!(cosmrs::Tx::from_bytes(maker_spends_payment_args.other_payment_tx)); let msg = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: CreateHtlcProtoRep = try_tx_fus!(prost::Message::decode(msg.value.as_slice())); @@ -1968,10 +1982,7 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = try_tx_fus!(cosmrs::Tx::from_bytes(taker_spends_payment_args.other_payment_tx)); let msg = try_tx_fus!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: CreateHtlcProtoRep = try_tx_fus!(prost::Message::decode(msg.value.as_slice())); @@ -2026,19 +2037,19 @@ impl SwapOps for TendermintCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), ))) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to refund IRIS HTLC".into(), ))) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { self.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -2085,7 +2096,12 @@ impl SwapOps for TendermintCoin { self.search_for_swap_tx_spend(input).await.map_err(|e| e.to_string()) } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { let tx = try_s!(cosmrs::Tx::from_bytes(spend_tx)); let msg = try_s!(tx.body.messages.first().ok_or("Tx body couldn't be read.")); let htlc_proto: super::iris::htlc_proto::ClaimHtlcProtoRep = @@ -2214,10 +2230,7 @@ impl WatcherOps for TendermintCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } @@ -2646,7 +2659,7 @@ pub mod tendermint_coin_tests { }); let invalid_amount = 1.into(); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &create_htlc_tx, expected_sender: &[], @@ -2656,9 +2669,18 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("failed to decode Protobuf message: MsgSend.amount")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::TxDeserializationError(err) => { + assert!(err.contains("failed to decode Protobuf message: MsgSend.amount")) + }, + _ => panic!( + "Expected `WrongPaymentTx` MsgSend.amount decode failure, found {:?}", + error + ), + } // just a random transfer tx not related to AtomicDEX, should fail on recipient address check // https://nyancat.iobscan.io/#/tx?txHash=65815814E7D74832D87956144C1E84801DC94FE9A509D207A0ABC3F17775E5DF @@ -2671,7 +2693,7 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(random_transfer_tx_bytes.as_slice()).unwrap(), }); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &random_transfer_tx, expected_sender: &[], @@ -2681,9 +2703,13 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("sent to wrong address")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("sent to wrong address")), + _ => panic!("Expected `WrongPaymentTx` wrong address, found {:?}", error), + } // dex fee tx sent during real swap // https://nyancat.iobscan.io/#/tx?txHash=8AA6B9591FE1EE93C8B89DE4F2C59B2F5D3473BD9FB5F3CFF6A5442BEDC881D7 @@ -2700,7 +2726,7 @@ pub mod tendermint_coin_tests { data: TxRaw::decode(dex_fee_tx.encode_to_vec().as_slice()).unwrap(), }); - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &[], @@ -2710,13 +2736,17 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid amount")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid amount")), + _ => panic!("Expected `WrongPaymentTx` invalid amount, found {:?}", error), + } let valid_amount: BigDecimal = "0.0001".parse().unwrap(); // valid amount but invalid sender - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -2726,12 +2756,16 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid sender")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid sender")), + _ => panic!("Expected `WrongPaymentTx` invalid sender, found {:?}", error), + } // invalid memo - let validate_err = coin + let error = coin .validate_fee(ValidateFeeArgs { fee_tx: &dex_fee_tx, expected_sender: &pubkey, @@ -2741,9 +2775,13 @@ pub mod tendermint_coin_tests { uuid: &[1; 16], }) .wait() - .unwrap_err(); - println!("{}", validate_err); - assert!(validate_err.contains("Invalid memo")); + .unwrap_err() + .into_inner(); + println!("{}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("Invalid memo")), + _ => panic!("Expected `WrongPaymentTx` invalid memo, found {:?}", error), + } // https://nyancat.iobscan.io/#/tx?txHash=5939A9D1AF57BB828714E0C4C4D7F2AEE349BB719B0A1F25F8FBCC3BB227C5F9 let fee_with_memo_hash = "5939A9D1AF57BB828714E0C4C4D7F2AEE349BB719B0A1F25F8FBCC3BB227C5F9"; @@ -2822,6 +2860,7 @@ pub mod tendermint_coin_tests { try_spv_proof_until: 0, confirmations: 0, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validate_err = coin.validate_taker_payment(input).wait().unwrap_err(); match validate_err.into_inner() { @@ -2847,6 +2886,7 @@ pub mod tendermint_coin_tests { try_spv_proof_until: 0, confirmations: 0, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validate_err = block_on( coin.validate_payment_for_denom(input, "nim".parse().unwrap(), 6) @@ -2919,6 +2959,7 @@ pub mod tendermint_coin_tests { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = match block_on(coin.search_for_swap_tx_spend_my(input)).unwrap().unwrap() { @@ -2992,6 +3033,7 @@ pub mod tendermint_coin_tests { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; match block_on(coin.search_for_swap_tx_spend_my(input)).unwrap().unwrap() { diff --git a/mm2src/coins/tendermint/tendermint_token.rs b/mm2src/coins/tendermint/tendermint_token.rs index 3a0b6af9f1..777df3ac67 100644 --- a/mm2src/coins/tendermint/tendermint_token.rs +++ b/mm2src/coins/tendermint/tendermint_token.rs @@ -6,15 +6,14 @@ use crate::{big_decimal_from_sat_unsigned, utxo::sat_from_big_decimal, BalanceFu CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, MyAddressError, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, RawTransactionFut, RawTransactionRequest, RefundError, - RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TransactionType, - TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; + RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, + SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradeFee, + TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, + TransactionErr, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::sha256; use common::executor::abortable_queue::AbortableQueue; @@ -107,7 +106,7 @@ impl SwapOps for TendermintToken { .send_taker_fee_for_denom(fee_addr, amount, self.denom.clone(), self.decimals, uuid) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { self.platform_coin.send_htlc_for_denom( maker_payment_args.time_lock_duration, maker_payment_args.other_pubkey, @@ -118,7 +117,7 @@ impl SwapOps for TendermintToken { ) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { self.platform_coin.send_htlc_for_denom( taker_payment_args.time_lock_duration, taker_payment_args.other_pubkey, @@ -129,35 +128,29 @@ impl SwapOps for TendermintToken { ) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { self.platform_coin .send_maker_spends_taker_payment(maker_spends_payment_args) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { self.platform_coin .send_taker_spends_maker_payment(taker_spends_payment_args) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), ))) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { Box::new(futures01::future::err(TransactionErr::Plain( "Doesn't need transaction broadcast to be refunded".into(), ))) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { self.platform_coin.validate_fee_for_denom( validate_fee_args.fee_tx, validate_fee_args.expected_sender, @@ -206,8 +199,15 @@ impl SwapOps for TendermintToken { self.platform_coin.search_for_swap_tx_spend_other(input).await } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { - self.platform_coin.extract_secret(secret_hash, spend_tx).await + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + self.platform_coin + .extract_secret(secret_hash, spend_tx, watcher_reward) + .await } fn check_tx_signed_by_pub(&self, tx: &[u8], expected_pub: &[u8]) -> Result> { @@ -327,10 +327,7 @@ impl WatcherOps for TendermintToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/test_coin.rs b/mm2src/coins/test_coin.rs index 8b59b92cae..5383922dd2 100644 --- a/mm2src/coins/test_coin.rs +++ b/mm2src/coins/test_coin.rs @@ -4,14 +4,13 @@ use super::{CoinBalance, HistorySyncState, MarketCoinOps, MmCoin, RawTransaction TradeFee, TransactionEnum, TransactionFut}; use crate::{coin_errors::MyAddressError, BalanceFut, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, - TradePreimageValue, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, - ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, - ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; + PaymentInstructionsErr, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, + TakerSwapMakerCoin, TradePreimageFut, TradePreimageResult, TradePreimageValue, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use common::executor::AbortedError; use futures01::Future; @@ -108,35 +107,27 @@ impl MarketCoinOps for TestCoin { impl SwapOps for TestCoin { fn send_taker_fee(&self, fee_addr: &[u8], amount: BigDecimal, uuid: &[u8]) -> TransactionFut { unimplemented!() } - fn send_maker_payment(&self, _maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_maker_payment(&self, _maker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_payment(&self, _taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { unimplemented!() } + fn send_taker_payment(&self, _taker_payment_args: SendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_spends_taker_payment( - &self, - _maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, _maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_spends_maker_payment( - &self, - _taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, _taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, _taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, _maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!() } - fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> Box + Send> { - unimplemented!() - } + fn validate_fee(&self, _validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { unimplemented!() } fn validate_maker_payment(&self, _input: ValidatePaymentInput) -> ValidatePaymentFut<()> { unimplemented!() } @@ -167,7 +158,14 @@ impl SwapOps for TestCoin { unimplemented!(); } - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { unimplemented!() } + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + watcher_reward: bool, + ) -> Result, String> { + unimplemented!() + } fn is_auto_refundable(&self) -> bool { false } @@ -273,10 +271,7 @@ impl WatcherOps for TestCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/coins/utxo/bch.rs b/mm2src/coins/utxo/bch.rs index e04d345877..02eefb15c9 100644 --- a/mm2src/coins/utxo/bch.rs +++ b/mm2src/coins/utxo/bch.rs @@ -12,14 +12,13 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails use crate::{BlockHeightAndTime, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinProtocol, CoinWithDerivationMethod, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RawTransactionFut, RawTransactionRequest, - RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, TakerSwapMakerCoin, TradePreimageValue, - TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut}; + RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TransactionType, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut}; use common::executor::{AbortableSystem, AbortedError}; use common::log::warn; use derive_more::Display; @@ -835,86 +834,36 @@ impl SwapOps for BchCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.secret_hash, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -976,7 +925,12 @@ impl SwapOps for BchCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1125,10 +1079,7 @@ impl WatcherOps for BchCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) } diff --git a/mm2src/coins/utxo/qtum.rs b/mm2src/coins/utxo/qtum.rs index ab3f336ceb..d5ad345f92 100644 --- a/mm2src/coins/utxo/qtum.rs +++ b/mm2src/coins/utxo/qtum.rs @@ -26,12 +26,10 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails UtxoTxHistoryOps}; use crate::{eth, CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, DelegationError, DelegationFut, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, - PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundResult, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, - SignatureResult, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, - TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + PaymentInstructions, PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, + RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, + SignatureResult, SpendPaymentArgs, StakingInfosFut, SwapOps, TakerSwapMakerCoin, TradePreimageValue, + TransactionFut, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; @@ -532,86 +530,36 @@ impl SwapOps for QtumCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.swap_unique_data, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -673,7 +621,12 @@ impl SwapOps for QtumCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -818,10 +771,7 @@ impl WatcherOps for QtumCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) } diff --git a/mm2src/coins/utxo/slp.rs b/mm2src/coins/utxo/slp.rs index 1f7cf01839..67d85faf12 100644 --- a/mm2src/coins/utxo/slp.rs +++ b/mm2src/coins/utxo/slp.rs @@ -16,16 +16,14 @@ use crate::utxo::{generate_and_send_tx, sat_from_big_decimal, ActualTxFee, Addit use crate::{BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, CoinFutSpawner, FeeApproxStage, FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageError, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionErr, TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, - ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, - ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, - WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, - WithdrawRequest}; + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, SpendPaymentArgs, SwapOps, + TakerSwapMakerCoin, TradeFee, TradePreimageError, TradePreimageFut, TradePreimageResult, + TradePreimageValue, TransactionDetails, TransactionEnum, TransactionErr, TransactionFut, TxFeeDetails, + TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, + ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentInput, VerificationError, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest}; use async_trait::async_trait; use bitcrypto::dhash160; use chain::constants::SEQUENCE_FINAL; @@ -752,7 +750,7 @@ impl SlpToken { validate_fut .compat() .await - .map_to_mm(ValidateDexFeeError::ValidatePaymentError)?; + .map_err(|e| MmError::new(ValidateDexFeeError::ValidatePaymentError(e.into_inner().to_string())))?; Ok(()) } @@ -1224,7 +1222,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat().map(|tx| tx.into())) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); let amount = try_tx_fus!(sat_from_big_decimal(&maker_payment_args.amount, self.decimals())); let secret_hash = maker_payment_args.secret_hash.to_owned(); @@ -1242,7 +1240,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); let amount = try_tx_fus!(sat_from_big_decimal(&taker_payment_args.amount, self.decimals())); let secret_hash = taker_payment_args.secret_hash.to_owned(); @@ -1261,10 +1259,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = maker_spends_payment_args.other_payment_tx.to_owned(); let taker_pub = try_tx_fus!(Public::from_slice(maker_spends_payment_args.other_pubkey)); let secret = maker_spends_payment_args.secret.to_owned(); @@ -1283,10 +1278,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { let tx = taker_spends_payment_args.other_payment_tx.to_owned(); let maker_pub = try_tx_fus!(Public::from_slice(taker_spends_payment_args.other_pubkey)); let secret = taker_spends_payment_args.secret.to_owned(); @@ -1305,7 +1297,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let tx = taker_refunds_payment_args.payment_tx.to_owned(); let maker_pub = try_tx_fus!(Public::from_slice(taker_refunds_payment_args.other_pubkey)); let secret_hash = taker_refunds_payment_args.secret_hash.to_owned(); @@ -1323,7 +1315,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat().map_err(TransactionErr::Plain)) } - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { let tx = maker_refunds_payment_args.payment_tx.to_owned(); let taker_pub = try_tx_fus!(Public::from_slice(maker_refunds_payment_args.other_pubkey)); let secret_hash = maker_refunds_payment_args.secret_hash.to_owned(); @@ -1341,7 +1333,7 @@ impl SwapOps for SlpToken { Box::new(fut.boxed().compat()) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -1353,10 +1345,9 @@ impl SwapOps for SlpToken { let min_block_number = validate_fee_args.min_block_number; let fut = async move { - try_s!( - coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) - .await - ); + coin.validate_dex_fee(tx, &expected_sender, &fee_addr, amount, min_block_number) + .await + .map_err(|e| MmError::new(ValidatePaymentError::WrongPaymentTx(e.into_inner().to_string())))?; Ok(()) }; Box::new(fut.boxed().compat()) @@ -1415,7 +1406,12 @@ impl SwapOps for SlpToken { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1530,10 +1526,7 @@ impl WatcherOps for SlpToken { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } @@ -2119,6 +2112,7 @@ mod slp_tests { try_spv_proof_until: now_ms() / 1000 + 60, unique_swap_data: Vec::new(), swap_contract_address: None, + min_watcher_reward: None, }; block_on(fusd.validate_htlc(input)).unwrap(); } @@ -2253,6 +2247,7 @@ mod slp_tests { try_spv_proof_until: now_ms() / 1000 + 60, confirmations: 1, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; let validity_err = block_on(fusd.validate_htlc(input)).unwrap_err(); match validity_err.into_inner() { diff --git a/mm2src/coins/utxo/utxo_common.rs b/mm2src/coins/utxo/utxo_common.rs index 19db7c9f32..1323a2a349 100644 --- a/mm2src/coins/utxo/utxo_common.rs +++ b/mm2src/coins/utxo/utxo_common.rs @@ -13,12 +13,13 @@ use crate::utxo::spv::SimplePaymentVerification; use crate::utxo::tx_cache::TxCacheResult; use crate::utxo::utxo_withdraw::{InitUtxoWithdraw, StandardUtxoWithdraw, UtxoWithdraw}; use crate::{CanRefundHtlc, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, HDAccountAddressId, - RawTransactionError, RawTransactionRequest, RawTransactionRes, SearchForSwapTxSpendInput, - SendMakerPaymentSpendPreimageInput, SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, - SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, TxMarshalingErr, ValidateAddressResult, - ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFrom, - WithdrawResult, WithdrawSenderAddress, EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, + RawTransactionError, RawTransactionRequest, RawTransactionRes, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, + SignatureResult, SpendPaymentArgs, SwapOps, TradePreimageValue, TransactionFut, TxFeeDetails, + TxMarshalingErr, ValidateAddressResult, ValidateOtherPubKeyErr, ValidatePaymentFut, ValidatePaymentInput, + VerificationError, VerificationResult, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFrom, 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}; pub use bitcrypto::{dhash160, sha256, ChecksumType}; use bitcrypto::{dhash256, ripemd160}; @@ -1189,28 +1190,21 @@ where send_outputs_from_my_address(coin, vec![output]) } -pub fn send_maker_payment( - coin: T, - time_lock: u32, - taker_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - swap_unique_data: &[u8], -) -> TransactionFut +pub fn send_maker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - let maker_htlc_key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let SwapPaymentOutputsResult { payment_address, outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - time_lock, + args.time_lock, maker_htlc_key_pair.public_slice(), - taker_pub, - secret_hash, - amount + args.other_pubkey, + args.secret_hash, + args.amount )); let send_fut = match &coin.as_ref().rpc_client { UtxoRpcClientEnum::Electrum(_) => Either::A(send_outputs_from_my_address(coin, outputs)), @@ -1227,28 +1221,21 @@ where Box::new(send_fut) } -pub fn send_taker_payment( - coin: T, - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - amount: BigDecimal, - swap_unique_data: &[u8], -) -> TransactionFut +pub fn send_taker_payment(coin: T, args: SendPaymentArgs) -> TransactionFut where T: UtxoCommonOps + GetUtxoListOps + SwapOps, { - let taker_htlc_key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let taker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let SwapPaymentOutputsResult { payment_address, outputs, } = try_tx_fus!(generate_swap_payment_outputs( &coin, - time_lock, + args.time_lock, taker_htlc_key_pair.public_slice(), - maker_pub, - secret_hash, - amount + args.other_pubkey, + args.secret_hash, + args.amount )); let send_fut = match &coin.as_ref().rpc_client { @@ -1266,36 +1253,29 @@ where Box::new(send_fut) } -pub fn send_maker_spends_taker_payment( - coin: T, - taker_payment_tx: &[u8], - time_lock: u32, - taker_pub: &[u8], - secret: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_maker_spends_taker_payment(coin: T, args: SpendPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); if prev_transaction.outputs.is_empty() { return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() - .push_data(secret) + .push_data(args.secret) .push_opcode(Opcode::OP_0) .into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, - &try_tx_fus!(Public::from_slice(taker_pub)), + args.time_lock, + args.secret_hash, + &try_tx_fus!(Public::from_slice(args.other_pubkey)), key_pair.public(), ) .into(); + let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1499,36 +1479,30 @@ pub fn create_taker_payment_refund_preimage( Box::new(fut.boxed().compat()) } -pub fn send_taker_spends_maker_payment( - coin: T, - maker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_taker_spends_maker_payment(coin: T, args: SpendPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.other_payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); if prev_transaction.outputs.is_empty() { return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default() - .push_data(secret) + .push_data(args.secret) .push_opcode(Opcode::OP_0) .into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, - &try_tx_fus!(Public::from_slice(maker_pub)), + args.time_lock, + args.secret_hash, + &try_tx_fus!(Public::from_slice(args.other_pubkey)), key_pair.public(), ) .into(); + + let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1566,32 +1540,26 @@ pub fn send_taker_spends_maker_payment( Box::new(fut.boxed().compat()) } -pub fn send_taker_refunds_payment( - coin: T, - taker_payment_tx: &[u8], - time_lock: u32, - maker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_taker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); let mut prev_transaction: UtxoTx = - try_tx_fus!(deserialize(taker_payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); + try_tx_fus!(deserialize(args.payment_tx).map_err(|e| TransactionErr::Plain(format!("{:?}", e)))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); if prev_transaction.outputs.is_empty() { return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, + args.time_lock, + args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(maker_pub)), + &try_tx_fus!(Public::from_slice(args.other_pubkey)), ) .into(); + let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1631,7 +1599,7 @@ pub fn send_taker_refunds_payment( pub fn send_taker_payment_refund_preimage( coin: &T, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, + watcher_refunds_payment_args: RefundPaymentArgs, ) -> TransactionFut { let coin = coin.clone(); let transaction: UtxoTx = try_tx_fus!( @@ -1648,31 +1616,24 @@ pub fn send_taker_payment_refund_preimage( Box::new(fut.boxed().compat()) } -pub fn send_maker_refunds_payment( - coin: T, - maker_payment_tx: &[u8], - time_lock: u32, - taker_pub: &[u8], - secret_hash: &[u8], - swap_unique_data: &[u8], -) -> TransactionFut { +pub fn send_maker_refunds_payment(coin: T, args: RefundPaymentArgs) -> TransactionFut { let my_address = try_tx_fus!(coin.as_ref().derivation_method.single_addr_or_err()).clone(); - let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(maker_payment_tx).map_err(|e| ERRL!("{:?}", e))); + let mut prev_transaction: UtxoTx = try_tx_fus!(deserialize(args.payment_tx).map_err(|e| ERRL!("{:?}", e))); prev_transaction.tx_hash_algo = coin.as_ref().tx_hash_algo; drop_mutability!(prev_transaction); if prev_transaction.outputs.is_empty() { return try_tx_fus!(TX_PLAIN_ERR!("Transaction doesn't have any output")); } - - let key_pair = coin.derive_htlc_key_pair(swap_unique_data); + let key_pair = coin.derive_htlc_key_pair(args.swap_unique_data); let script_data = Builder::default().push_opcode(Opcode::OP_1).into_script(); let redeem_script = payment_script( - time_lock, - secret_hash, + args.time_lock, + args.secret_hash, key_pair.public(), - &try_tx_fus!(Public::from_slice(taker_pub)), + &try_tx_fus!(Public::from_slice(args.other_pubkey)), ) .into(); + let time_lock = args.time_lock; let fut = async move { let fee = try_tx_s!( coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox) @@ -1904,67 +1865,77 @@ pub fn validate_fee( amount: &BigDecimal, min_block_number: u64, fee_addr: &[u8], -) -> Box + Send> { +) -> ValidatePaymentFut<()> { let amount = amount.clone(); - let address = try_fus!(address_from_raw_pubkey( + let address = try_f!(address_from_raw_pubkey( fee_addr, coin.as_ref().conf.pub_addr_prefix, coin.as_ref().conf.pub_t_addr_prefix, coin.as_ref().conf.checksum_type, coin.as_ref().conf.bech32_hrp.clone(), coin.addr_format().clone(), - )); + ) + .map_to_mm(ValidatePaymentError::TxDeserializationError)); - if !try_fus!(check_all_utxo_inputs_signed_by_pub(&tx, sender_pubkey)) { - return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address"))); + let inputs_signed_by_pub = try_f!(check_all_utxo_inputs_signed_by_pub(&tx, sender_pubkey)); + if !inputs_signed_by_pub { + return Box::new(futures01::future::err( + ValidatePaymentError::WrongPaymentTx(format!( + "{INVALID_SENDER_ERR_LOG}: Taker payment does not belong to the verified public key" + )) + .into(), + )); } + let fut = async move { - let amount = try_s!(sat_from_big_decimal(&amount, coin.as_ref().decimals)); - let tx_from_rpc = try_s!( - coin.as_ref() - .rpc_client - .get_verbose_transaction(&tx.hash().reversed().into()) - .compat() - .await - ); + let amount = sat_from_big_decimal(&amount, coin.as_ref().decimals)?; + let tx_from_rpc = coin + .as_ref() + .rpc_client + .get_verbose_transaction(&tx.hash().reversed().into()) + .compat() + .await?; - if try_s!(is_tx_confirmed_before_block(&coin, &tx_from_rpc, min_block_number).await) { - return ERR!( - "Fee tx {:?} confirmed before min_block {}", - tx_from_rpc, - min_block_number, - ); + let tx_confirmed_before_block = is_tx_confirmed_before_block(&coin, &tx_from_rpc, min_block_number) + .await + .map_to_mm(ValidatePaymentError::InternalError)?; + + if tx_confirmed_before_block { + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Fee tx {:?} confirmed before min_block {}", + EARLY_CONFIRMATION_ERR_LOG, tx_from_rpc, min_block_number + ))); } if tx_from_rpc.hex.0 != serialize(&tx).take() && tx_from_rpc.hex.0 != serialize_with_flags(&tx, SERIALIZE_TRANSACTION_WITNESS).take() { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx {:?} doesn't match tx data from rpc {:?}", - tx, - tx_from_rpc - ); + tx, tx_from_rpc + ))); } match tx.outputs.get(output_index) { Some(out) => { let expected_script_pubkey = Builder::build_p2pkh(&address.hash).to_bytes(); if out.script_pubkey != expected_script_pubkey { - return ERR!( - "Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", - out.script_pubkey, - expected_script_pubkey - ); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{}: Provided dex fee tx output script_pubkey doesn't match expected {:?} {:?}", + INVALID_RECEIVER_ERR_LOG, out.script_pubkey, expected_script_pubkey + ))); } if out.value < amount { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Provided dex fee tx output value is less than expected {:?} {:?}", - out.value, - amount - ); + out.value, amount + ))); } }, None => { - return ERR!("Provided dex fee tx {:?} does not have output {}", tx, output_index); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Provided dex fee tx {:?} does not have output {}", + tx, output_index + ))) }, } Ok(()) @@ -2015,9 +1986,9 @@ pub fn watcher_validate_taker_payment( let fut = async move { let inputs_signed_by_pub = check_all_utxo_inputs_signed_by_pub(&taker_payment_tx, &input.taker_pub)?; if !inputs_signed_by_pub { - return MmError::err(ValidatePaymentError::WrongPaymentTx( - "Taker payment does not belong to the verified public key".to_string(), - )); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "{INVALID_SENDER_ERR_LOG}: Taker payment does not belong to the verified public key" + ))); } let taker_payment_locking_script = match taker_payment_tx.outputs.get(DEFAULT_SWAP_VOUT) { @@ -2031,8 +2002,7 @@ pub fn watcher_validate_taker_payment( if taker_payment_locking_script != Builder::build_p2sh(&dhash160(&expected_redeem).into()).to_bytes() { return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( - "Payment tx locking script {:?} doesn't match expected", - taker_payment_locking_script + "{INVALID_SCRIPT_ERR_LOG}: Payment tx locking script {taker_payment_locking_script:?} doesn't match expected" ))); } @@ -2057,7 +2027,7 @@ pub fn watcher_validate_taker_payment( if expected_redeem.as_slice() != redeem_script { return MmError::err(ValidatePaymentError::WrongPaymentTx( - "Taker payment tx locking script doesn't match with taker payment refund redeem script".to_string(), + format!("{INVALID_REFUND_TX_ERR_LOG}: Taker payment tx locking script doesn't match with taker payment refund redeem script") )); } diff --git a/mm2src/coins/utxo/utxo_standard.rs b/mm2src/coins/utxo/utxo_standard.rs index 34746570f7..360fb2d566 100644 --- a/mm2src/coins/utxo/utxo_standard.rs +++ b/mm2src/coins/utxo/utxo_standard.rs @@ -24,13 +24,11 @@ use crate::utxo::utxo_tx_history_v2::{UtxoMyAddressesHistoryError, UtxoTxDetails UtxoTxHistoryOps}; use crate::{CanRefundHtlc, CheckIfMyPaymentSentArgs, CoinBalance, CoinWithDerivationMethod, GetWithdrawSenderAddress, IguanaPrivKey, MakerSwapTakerCoin, NegotiateSwapContractAddrErr, PaymentInstructions, - PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundResult, SearchForSwapTxSpendInput, - SendMakerPaymentArgs, SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SendWatcherRefundsPaymentArgs, SignatureResult, SwapOps, - TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, + PaymentInstructionsErr, PrivKeyBuildPolicy, RefundError, RefundPaymentArgs, RefundResult, + SearchForSwapTxSpendInput, SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureResult, + SpendPaymentArgs, SwapOps, TakerSwapMakerCoin, TradePreimageValue, TransactionFut, TxMarshalingErr, + ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, + ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, WithdrawSenderAddress}; use common::executor::{AbortableSystem, AbortedError}; @@ -296,86 +294,36 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs) -> TransactionFut { - utxo_common::send_maker_payment( - self.clone(), - maker_payment_args.time_lock, - maker_payment_args.other_pubkey, - maker_payment_args.secret_hash, - maker_payment_args.amount, - maker_payment_args.swap_unique_data, - ) + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_payment(self.clone(), maker_payment_args) } #[inline] - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs) -> TransactionFut { - utxo_common::send_taker_payment( - self.clone(), - taker_payment_args.time_lock, - taker_payment_args.other_pubkey, - taker_payment_args.secret_hash, - taker_payment_args.amount, - taker_payment_args.swap_unique_data, - ) + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment(self.clone(), taker_payment_args) } #[inline] - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_maker_spends_taker_payment( - self.clone(), - maker_spends_payment_args.other_payment_tx, - maker_spends_payment_args.time_lock, - maker_spends_payment_args.other_pubkey, - maker_spends_payment_args.secret, - maker_spends_payment_args.secret_hash, - maker_spends_payment_args.swap_unique_data, - ) + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_maker_spends_taker_payment(self.clone(), maker_spends_payment_args) } #[inline] - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_spends_maker_payment( - self.clone(), - taker_spends_payment_args.other_payment_tx, - taker_spends_payment_args.time_lock, - taker_spends_payment_args.other_pubkey, - taker_spends_payment_args.secret, - taker_spends_payment_args.secret_hash, - taker_spends_payment_args.swap_unique_data, - ) + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs) -> TransactionFut { + utxo_common::send_taker_spends_maker_payment(self.clone(), taker_spends_payment_args) } #[inline] - fn send_taker_refunds_payment(&self, taker_refunds_payment_args: SendTakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_taker_refunds_payment( - self.clone(), - taker_refunds_payment_args.payment_tx, - taker_refunds_payment_args.time_lock, - taker_refunds_payment_args.other_pubkey, - taker_refunds_payment_args.secret_hash, - taker_refunds_payment_args.swap_unique_data, - ) + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_refunds_payment(self.clone(), taker_refunds_payment_args) } #[inline] - fn send_maker_refunds_payment(&self, maker_refunds_payment_args: SendMakerRefundsPaymentArgs) -> TransactionFut { - utxo_common::send_maker_refunds_payment( - self.clone(), - maker_refunds_payment_args.payment_tx, - maker_refunds_payment_args.time_lock, - maker_refunds_payment_args.other_pubkey, - maker_refunds_payment_args.secret_hash, - maker_refunds_payment_args.swap_unique_data, - ) + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_maker_refunds_payment(self.clone(), maker_refunds_payment_args) } - fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs) -> ValidatePaymentFut<()> { let tx = match validate_fee_args.fee_tx { TransactionEnum::UtxoTx(tx) => tx.clone(), _ => panic!(), @@ -437,7 +385,12 @@ impl SwapOps for UtxoStandardCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -577,11 +530,8 @@ impl WatcherOps for UtxoStandardCoin { } #[inline] - fn send_taker_payment_refund_preimage( - &self, - watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { - utxo_common::send_taker_payment_refund_preimage(self, watcher_refunds_payment_args) + fn send_taker_payment_refund_preimage(&self, refund_payment_args: RefundPaymentArgs) -> TransactionFut { + utxo_common::send_taker_payment_refund_preimage(self, refund_payment_args) } #[inline] diff --git a/mm2src/coins/utxo/utxo_tests.rs b/mm2src/coins/utxo/utxo_tests.rs index cec80eb40c..ae54d5fd57 100644 --- a/mm2src/coins/utxo/utxo_tests.rs +++ b/mm2src/coins/utxo/utxo_tests.rs @@ -1,5 +1,6 @@ use super::*; use crate::coin_balance::HDAddressBalance; +use crate::coin_errors::ValidatePaymentError; use crate::hd_confirm_address::for_tests::MockableConfirmAddress; use crate::hd_confirm_address::{HDConfirmAddress, HDConfirmAddressError}; use crate::hd_wallet::HDAccountsMap; @@ -27,9 +28,10 @@ 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::INVALID_SENDER_ERR_LOG; use crate::{BlockHeightAndTime, CoinBalance, IguanaPrivKey, PrivKeyBuildPolicy, SearchForSwapTxSpendInput, - SendMakerSpendsTakerPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, - TxMarshalingErr, ValidateFeeArgs}; + SpendPaymentArgs, StakingInfosDetails, SwapOps, TradePreimageValue, TxFeeDetails, TxMarshalingErr, + ValidateFeeArgs}; use chain::{BlockHeader, BlockHeaderBits, OutPoint}; use common::executor::Timer; use common::{block_on, now_ms, OrdRange, PagingOptionsEnum, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -149,7 +151,7 @@ fn test_extract_secret() { let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let expected_secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); let secret_hash = &*dhash160(&expected_secret); - let secret = block_on(coin.extract_secret(secret_hash, &tx_hex)).unwrap(); + let secret = block_on(coin.extract_secret(secret_hash, &tx_hex, false)).unwrap(); assert_eq!(secret, expected_secret); } @@ -159,7 +161,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { let coin = utxo_coin_for_test(client.into(), None, false); let tx_hex = hex::decode("0100000001de7aa8d29524906b2b54ee2e0281f3607f75662cbc9080df81d1047b78e21dbc00000000d7473044022079b6c50820040b1fbbe9251ced32ab334d33830f6f8d0bf0a40c7f1336b67d5b0220142ccf723ddabb34e542ed65c395abc1fbf5b6c3e730396f15d25c49b668a1a401209da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365004c6b6304f62b0e5cb175210270e75970bb20029b3879ec76c4acd320a8d0589e003636264d01a7d566504bfbac6782012088a9142fb610d856c19fd57f2d0cffe8dff689074b3d8a882103f368228456c940ac113e53dad5c104cf209f2f102a409207269383b6ab9b03deac68ffffffff01d0dc9800000000001976a9146d9d2b554d768232320587df75c4338ecc8bf37d88ac40280e5c").unwrap(); let secret = hex::decode("9da937e5609680cb30bff4a7661364ca1d1851c2506fa80c443f00a3d3bf7365").unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx_hex, time_lock: 777, other_pubkey: &coin.my_public_key().unwrap().to_vec(), @@ -167,6 +169,7 @@ fn test_send_maker_spends_taker_payment_recoverable_tx() { secret_hash: &*dhash160(&secret), swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let tx_err = coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -498,6 +501,7 @@ fn test_search_for_swap_tx_spend_electrum_was_spent() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -532,6 +536,7 @@ fn test_search_for_swap_tx_spend_electrum_was_refunded() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -2535,8 +2540,12 @@ fn test_validate_fee_wrong_sender() { min_block_number: 0, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("was sent from wrong address")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains(INVALID_SENDER_ERR_LOG)), + _ => panic!("Expected `WrongPaymentTx` wrong sender address, found {:?}", error), + } } #[test] @@ -2560,8 +2569,11 @@ fn test_validate_fee_min_block() { min_block_number: 810329, uuid: &[], }; - let validate_err = coin.validate_fee(validate_fee_args).wait().unwrap_err(); - assert!(validate_err.contains("confirmed before min_block")); + let error = coin.validate_fee(validate_fee_args).wait().unwrap_err().into_inner(); + match error { + ValidatePaymentError::WrongPaymentTx(err) => assert!(err.contains("confirmed before min_block")), + _ => panic!("Expected `WrongPaymentTx` early confirmation, found {:?}", error), + } } #[test] diff --git a/mm2src/coins/z_coin.rs b/mm2src/coins/z_coin.rs index 72c86f38a1..7a145473f3 100644 --- a/mm2src/coins/z_coin.rs +++ b/mm2src/coins/z_coin.rs @@ -17,16 +17,14 @@ use crate::{BalanceError, BalanceFut, CheckIfMyPaymentSentArgs, CoinBalance, Coi FoundSwapTxSpend, HistorySyncState, MakerSwapTakerCoin, MarketCoinOps, MmCoin, NegotiateSwapContractAddrErr, NumConversError, PaymentInstructions, PaymentInstructionsErr, PrivKeyActivationPolicy, PrivKeyBuildPolicy, PrivKeyPolicyNotAllowed, RawTransactionFut, - RawTransactionRequest, RefundError, RefundResult, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerPaymentSpendPreimageInput, SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SignatureError, SignatureResult, SwapOps, TakerSwapMakerCoin, TradeFee, - TradePreimageFut, TradePreimageResult, TradePreimageValue, TransactionDetails, TransactionEnum, - TransactionFut, TxFeeDetails, TxMarshalingErr, UnexpectedDerivationMethod, ValidateAddressResult, - ValidateFeeArgs, ValidateInstructionsErr, ValidateOtherPubKeyErr, ValidatePaymentError, - ValidatePaymentFut, ValidatePaymentInput, VerificationError, VerificationResult, WatcherOps, - WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, WithdrawFut, - WithdrawRequest}; + RawTransactionRequest, RefundError, RefundPaymentArgs, RefundResult, SearchForSwapTxSpendInput, + SendMakerPaymentSpendPreimageInput, SendPaymentArgs, SignatureError, SignatureResult, SpendPaymentArgs, + SwapOps, TakerSwapMakerCoin, TradeFee, TradePreimageFut, TradePreimageResult, TradePreimageValue, + TransactionDetails, TransactionEnum, TransactionFut, TxFeeDetails, TxMarshalingErr, + UnexpectedDerivationMethod, ValidateAddressResult, ValidateFeeArgs, ValidateInstructionsErr, + ValidateOtherPubKeyErr, ValidatePaymentError, ValidatePaymentFut, ValidatePaymentInput, VerificationError, + VerificationResult, WatcherOps, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, + WatcherValidateTakerFeeInput, WithdrawFut, WithdrawRequest}; use crate::{Transaction, WithdrawError}; use async_trait::async_trait; use bitcrypto::dhash256; @@ -1115,7 +1113,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_payment(&self, maker_payment_args: SendMakerPaymentArgs<'_>) -> TransactionFut { + fn send_maker_payment(&self, maker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let selfi = self.clone(); let maker_key_pair = self.derive_htlc_key_pair(maker_payment_args.swap_unique_data); let taker_pub = try_tx_fus!(Public::from_slice(maker_payment_args.other_pubkey)); @@ -1139,7 +1137,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_payment(&self, taker_payment_args: SendTakerPaymentArgs<'_>) -> TransactionFut { + fn send_taker_payment(&self, taker_payment_args: SendPaymentArgs<'_>) -> TransactionFut { let selfi = self.clone(); let taker_keypair = self.derive_htlc_key_pair(taker_payment_args.swap_unique_data); let maker_pub = try_tx_fus!(Public::from_slice(taker_payment_args.other_pubkey)); @@ -1163,10 +1161,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_spends_taker_payment( - &self, - maker_spends_payment_args: SendMakerSpendsTakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_spends_taker_payment(&self, maker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(maker_spends_payment_args.other_payment_tx)); let key_pair = self.derive_htlc_key_pair(maker_spends_payment_args.swap_unique_data); let time_lock = maker_spends_payment_args.time_lock; @@ -1197,10 +1192,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_spends_maker_payment( - &self, - taker_spends_payment_args: SendTakerSpendsMakerPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_spends_maker_payment(&self, taker_spends_payment_args: SpendPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(taker_spends_payment_args.other_payment_tx)); let key_pair = self.derive_htlc_key_pair(taker_spends_payment_args.swap_unique_data); let time_lock = taker_spends_payment_args.time_lock; @@ -1231,10 +1223,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_taker_refunds_payment( - &self, - taker_refunds_payment_args: SendTakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_taker_refunds_payment(&self, taker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(taker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(taker_refunds_payment_args.swap_unique_data); let time_lock = taker_refunds_payment_args.time_lock; @@ -1262,10 +1251,7 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn send_maker_refunds_payment( - &self, - maker_refunds_payment_args: SendMakerRefundsPaymentArgs<'_>, - ) -> TransactionFut { + fn send_maker_refunds_payment(&self, maker_refunds_payment_args: RefundPaymentArgs<'_>) -> TransactionFut { let tx = try_tx_fus!(ZTransaction::read(maker_refunds_payment_args.payment_tx)); let key_pair = self.derive_htlc_key_pair(maker_refunds_payment_args.swap_unique_data); let time_lock = maker_refunds_payment_args.time_lock; @@ -1293,41 +1279,41 @@ impl SwapOps for ZCoin { Box::new(fut.boxed().compat()) } - fn validate_fee( - &self, - validate_fee_args: ValidateFeeArgs<'_>, - ) -> Box + Send> { + fn validate_fee(&self, validate_fee_args: ValidateFeeArgs<'_>) -> ValidatePaymentFut<()> { let z_tx = match validate_fee_args.fee_tx { TransactionEnum::ZTransaction(t) => t.clone(), _ => panic!("Unexpected tx {:?}", validate_fee_args.fee_tx), }; - let amount_sat = try_fus!(sat_from_big_decimal(validate_fee_args.amount, self.utxo_arc.decimals)); + let amount_sat = try_f!(sat_from_big_decimal(validate_fee_args.amount, 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; let coin = self.clone(); let fut = async move { let tx_hash = H256::from(z_tx.txid().0).reversed(); - let tx_from_rpc = try_s!( - coin.utxo_rpc_client() - .get_verbose_transaction(&tx_hash.into()) - .compat() - .await - ); + let tx_from_rpc = coin + .utxo_rpc_client() + .get_verbose_transaction(&tx_hash.into()) + .compat() + .await + .map_err(|e| MmError::new(ValidatePaymentError::InvalidRpcResponse(e.into_inner().to_string())))?; + let mut encoded = Vec::with_capacity(1024); z_tx.write(&mut encoded).expect("Writing should not fail"); if encoded != tx_from_rpc.hex.0 { - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Encoded transaction {:?} does not match the tx {:?} from RPC", - encoded, - tx_from_rpc - ); + encoded, tx_from_rpc + ))); } let block_height = match tx_from_rpc.height { Some(h) => { if h < min_block_number { - return ERR!("Dex fee tx {:?} confirmed before min block {}", z_tx, min_block_number); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee tx {:?} confirmed before min block {}", + z_tx, min_block_number + ))); } else { BlockHeight::from_u32(h as u32) } @@ -1346,29 +1332,34 @@ impl SwapOps for ZCoin { z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &coin.z_fields.dex_fee_addr, ); - return ERR!( + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "Dex fee was sent to the invalid address {}, expected {}", - encoded, - expected - ); + encoded, expected + ))); } if note.value != amount_sat { - return ERR!("Dex fee has invalid amount {}, expected {}", note.value, amount_sat); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid amount {}, expected {}", + note.value, amount_sat + ))); } if memo != expected_memo { - return ERR!("Dex fee has invalid memo {:?}, expected {:?}", memo, expected_memo); + return MmError::err(ValidatePaymentError::WrongPaymentTx(format!( + "Dex fee has invalid memo {:?}, expected {:?}", + memo, expected_memo + ))); } return Ok(()); } } - ERR!( + MmError::err(ValidatePaymentError::WrongPaymentTx(format!( "The dex fee tx {:?} has no shielded outputs or outputs decryption failed", z_tx - ) + ))) }; Box::new(fut.boxed().compat()) @@ -1419,7 +1410,12 @@ impl SwapOps for ZCoin { } #[inline] - async fn extract_secret(&self, secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { + async fn extract_secret( + &self, + secret_hash: &[u8], + spend_tx: &[u8], + _watcher_reward: bool, + ) -> Result, String> { utxo_common::extract_secret(secret_hash, spend_tx) } @@ -1539,10 +1535,7 @@ impl WatcherOps for ZCoin { unimplemented!(); } - fn send_taker_payment_refund_preimage( - &self, - _watcher_refunds_payment_args: SendWatcherRefundsPaymentArgs, - ) -> TransactionFut { + fn send_taker_payment_refund_preimage(&self, _watcher_refunds_payment_args: RefundPaymentArgs) -> TransactionFut { unimplemented!(); } diff --git a/mm2src/mm2_main/src/lp_swap.rs b/mm2src/mm2_main/src/lp_swap.rs index 711dbdb064..c84402b902 100644 --- a/mm2src/mm2_main/src/lp_swap.rs +++ b/mm2src/mm2_main/src/lp_swap.rs @@ -59,6 +59,7 @@ use crate::mm2::lp_network::{broadcast_p2p_msg, Libp2pPeerId}; use bitcrypto::{dhash160, sha256}; +use coins::eth::Web3RpcError; use coins::{lp_coinfind, lp_coinfind_or_err, CoinFindError, MmCoinEnum, TradeFee, TransactionEnum}; use common::log::{debug, warn}; use common::time_cache::DuplicateCache; @@ -67,6 +68,7 @@ use common::{bits256, calc_total_pages, log::{error, info}, now_ms, var, HttpStatusCode, PagingOptions, StatusCode}; use derive_more::Display; +use futures::compat::Future01CompatExt; use http::Response; use mm2_core::mm_ctx::{from_ctx, MmArc}; use mm2_err_handle::prelude::*; @@ -682,6 +684,72 @@ pub fn dex_fee_amount_from_taker_coin(taker_coin: &MmCoinEnum, maker_coin: &str, dex_fee_amount(taker_coin.ticker(), maker_coin, trade_amount, &dex_fee_threshold) } +#[derive(Debug, Display)] +pub enum WatcherRewardError { + RPCError(Web3RpcError), + InvalidCoinType(String), +} + +// This needs discussion. We need a way to determine the watcher reward amount, and a way to validate it at watcher side so +// that watchers won't accept it if it's less than the expected amount. This has to be done for all coin types, because watcher rewards +// will be required by both parties even if only one side is ETH coin. Artem's suggestion was first calculating the reward for ETH and +// converting the value to other coins, which is what I'm planning to do. I based the values to be higher than the gas amounts used +// when the watcher spends the maker payment or refunds the taker payment. For the validation, I check if the reward is higher +// than a minimum amount using the min_watcher_reward method. I can't make an exact comparison because the gas price and relative +// price of the coins will be different when the taker/maker sends their payment and when the watcher receives the message. This should +// work fine if we pick the WATCHER_REWARD_GAS and MIN_WATCHER_REWARD_GAS constants good. The advantage of this is that the reward will +// be directly based on the amount of gas burned when the watcher will call the contract functions (Artem's idea was to make it slightly +// profitable for the watchers). The disadvantage is the comparison during the validations using a separate minimum value. If we want +// validations with exact values, there are two other ways I could think of: +// 1. Precalculating fixed rewards for all coins. The disadvantage is that the gas price and the relative price of coins will change over +// time and the reward will deviate from the actual gas used by the watchers, and we can't keep updating the values. +// 2. Picking the reward to be a percentage of the trade amount like the taker fee. The disadvantage is it will be extremely hard to +// pick the right ratio such that it will be slightly profitable for watchers. +pub async fn watcher_reward_amount( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, +) -> Result> { + const WATCHER_REWARD_GAS: u64 = 100_000; + watcher_reward_from_gas(coin, other_coin, WATCHER_REWARD_GAS).await +} + +pub async fn min_watcher_reward( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, +) -> Result> { + const MIN_WATCHER_REWARD_GAS: u64 = 70_000; + watcher_reward_from_gas(coin, other_coin, MIN_WATCHER_REWARD_GAS).await +} + +pub async fn watcher_reward_from_gas( + coin: &MmCoinEnum, + other_coin: &MmCoinEnum, + gas: u64, +) -> Result> { + match (coin, other_coin) { + (MmCoinEnum::EthCoin(coin), _) | (_, MmCoinEnum::EthCoin(coin)) => { + let mut attempts = 0; + loop { + match coin.get_gas_price().compat().await { + Ok(gas_price) => return Ok(gas * gas_price.as_u64()), + Err(err) => { + if attempts >= 3 { + return MmError::err(WatcherRewardError::RPCError(err.into_inner())); + } else { + attempts += 1; + Timer::sleep(10.).await; + } + }, + }; + } + }, + _ => Err(WatcherRewardError::InvalidCoinType( + "At least one coin must be ETH to use watcher reward".to_string(), + ) + .into()), + } +} + #[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)] pub struct NegotiationDataV1 { started_at: u64, diff --git a/mm2src/mm2_main/src/lp_swap/maker_swap.rs b/mm2src/mm2_main/src/lp_swap/maker_swap.rs index 7f12d7ec3d..00d068dbf5 100644 --- a/mm2src/mm2_main/src/lp_swap/maker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/maker_swap.rs @@ -14,11 +14,12 @@ use crate::mm2::lp_dispatcher::{DispatcherContext, LpEvents}; use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MakerOrderBuilder, OrderConfirmationsSettings}; use crate::mm2::lp_price::fetch_swap_coins_price; -use crate::mm2::lp_swap::{broadcast_swap_message, taker_payment_spend_duration}; +use crate::mm2::lp_swap::{broadcast_swap_message, min_watcher_reward, taker_payment_spend_duration, + watcher_reward_amount}; use coins::{CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, TradeFee, TradePreimageValue, - TransactionEnum, ValidateFeeArgs, ValidatePaymentInput}; + PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, TransactionEnum, ValidateFeeArgs, + ValidatePaymentInput}; use common::log::{debug, error, info, warn}; use common::{bits256, executor::Timer, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::SerializableSecp256k1Keypair; @@ -184,6 +185,7 @@ pub struct MakerSwapMut { taker_payment_spend_confirmed: bool, maker_payment_refund: Option, payment_instructions: Option, + watcher_reward: bool, } #[cfg(test)] @@ -387,6 +389,7 @@ impl MakerSwap { maker_payment_refund: None, taker_payment_spend_confirmed: false, payment_instructions: None, + watcher_reward: false, }), ctx, secret, @@ -562,6 +565,11 @@ impl MakerSwap { p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), }; + // This value will be true if both sides support & want to use watchers and either the taker or the maker coin is ETH. + // This requires a communication between the parties before the swap starts, which will be done during the ordermatch phase + // or via negotiation messages in the next sprint. + self.w().watcher_reward = false; + Ok((Some(MakerSwapCommand::Negotiate), vec![MakerSwapEvent::Started(data)])) } @@ -802,11 +810,25 @@ impl MakerSwap { }) .compat(); + let reward_amount = if self.r().watcher_reward { + let reward = match watcher_reward_amount(&self.maker_coin, &self.taker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(MakerSwapCommand::Finish), vec![ + MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let transaction = match transaction_f.await { Ok(res) => match res { Some(tx) => tx, None => { - let payment_fut = self.maker_coin.send_maker_payment(SendMakerPaymentArgs { + let payment_fut = self.maker_coin.send_maker_payment(SendPaymentArgs { time_lock_duration: self.r().data.lock_duration, time_lock: self.r().data.maker_payment_lock as u32, other_pubkey: &*self.r().other_maker_coin_htlc_pub, @@ -815,6 +837,7 @@ impl MakerSwap { swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, + watcher_reward: reward_amount, }); match payment_fut.compat().await { @@ -959,6 +982,20 @@ impl MakerSwap { ])); } + let min_watcher_reward = if self.r().watcher_reward { + let reward = match min_watcher_reward(&self.taker_coin, &self.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(MakerSwapCommand::Finish), vec![ + MakerSwapEvent::MakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let validate_input = ValidatePaymentInput { payment_tx: self.r().taker_payment.clone().unwrap().tx_hex.0, time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, @@ -970,7 +1007,9 @@ impl MakerSwap { swap_contract_address: self.r().data.taker_coin_swap_contract_address.clone(), try_spv_proof_until: wait_taker_payment, confirmations, + min_watcher_reward, }; + let validated_f = self.taker_coin.validate_taker_payment(validate_input).compat(); if let Err(e) = validated_f.await { @@ -1006,17 +1045,16 @@ impl MakerSwap { ])); } - let spend_fut = self - .taker_coin - .send_maker_spends_taker_payment(SendMakerSpendsTakerPaymentArgs { - other_payment_tx: &self.r().taker_payment.clone().unwrap().tx_hex, - time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: &*self.r().other_taker_coin_htlc_pub, - secret: &self.r().data.secret.0, - secret_hash: &self.secret_hash(), - swap_contract_address: &self.r().data.taker_coin_swap_contract_address, - swap_unique_data: &self.unique_swap_data(), - }); + let spend_fut = self.taker_coin.send_maker_spends_taker_payment(SpendPaymentArgs { + other_payment_tx: &self.r().taker_payment.clone().unwrap().tx_hex, + time_lock: self.taker_payment_lock.load(Ordering::Relaxed) as u32, + other_pubkey: &*self.r().other_taker_coin_htlc_pub, + secret: &self.r().data.secret.0, + secret_hash: &self.secret_hash(), + swap_contract_address: &self.r().data.taker_coin_swap_contract_address, + swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, + }); let transaction = match spend_fut.compat().await { Ok(t) => t, @@ -1141,13 +1179,14 @@ impl MakerSwap { } } - let spend_fut = self.maker_coin.send_maker_refunds_payment(SendMakerRefundsPaymentArgs { + let spend_fut = self.maker_coin.send_maker_refunds_payment(RefundPaymentArgs { payment_tx: &maker_payment, time_lock: locktime as u32, other_pubkey: &*self.r().other_maker_coin_htlc_pub, secret_hash: self.secret_hash().as_slice(), swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match spend_fut.compat().await { @@ -1304,6 +1343,7 @@ impl MakerSwap { let secret = selfi.r().data.secret.0; let unique_data = selfi.unique_swap_data(); + let watcher_reward = selfi.r().watcher_reward; let search_input = SearchForSwapTxSpendInput { time_lock: timelock, @@ -1313,6 +1353,7 @@ impl MakerSwap { search_from_block: taker_coin_start_block, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; // check if the taker payment is not spent yet match selfi.taker_coin.search_for_swap_tx_spend_other(search_input).await { @@ -1336,7 +1377,7 @@ impl MakerSwap { selfi .taker_coin - .send_maker_spends_taker_payment(SendMakerSpendsTakerPaymentArgs { + .send_maker_spends_taker_payment(SpendPaymentArgs { other_payment_tx: taker_payment_hex, time_lock: timelock, other_pubkey: other_taker_coin_htlc_pub.as_slice(), @@ -1344,6 +1385,7 @@ impl MakerSwap { secret_hash: &selfi.secret_hash(), swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &selfi.unique_swap_data(), + watcher_reward, }) .compat() .await @@ -1374,6 +1416,7 @@ impl MakerSwap { let payment_instructions = self.r().payment_instructions.clone(); let maybe_maker_payment = self.r().maker_payment.clone(); + let watcher_reward = self.r().watcher_reward; let maker_payment = match maybe_maker_payment { Some(tx) => tx.tx_hex.0, None => { @@ -1407,6 +1450,7 @@ impl MakerSwap { search_from_block: maker_coin_start_block, swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; // validate that maker payment is not spent match self.maker_coin.search_for_swap_tx_spend_my(search_input).await { @@ -1440,13 +1484,14 @@ impl MakerSwap { if let CanRefundHtlc::HaveToWait(seconds_to_wait) = can_refund_htlc { return ERR!("Too early to refund, wait until {}", now_ms() / 1000 + seconds_to_wait); } - let fut = self.maker_coin.send_maker_refunds_payment(SendMakerRefundsPaymentArgs { + let fut = self.maker_coin.send_maker_refunds_payment(RefundPaymentArgs { payment_tx: &maker_payment, time_lock: maker_payment_lock, other_pubkey: other_maker_coin_htlc_pub.as_slice(), secret_hash: secret_hash.as_slice(), swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }); let transaction = match fut.compat().await { @@ -2387,7 +2432,6 @@ mod maker_swap_tests { let taker_coin = MmCoinEnum::Test(TestCoin::default()); let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); - println!("{}", err); assert!(err.contains("Taker payment was already refunded")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_OTHER_CALLED }); @@ -2493,7 +2537,6 @@ mod maker_swap_tests { let taker_coin = MmCoinEnum::Test(TestCoin::default()); let (maker_swap, _) = MakerSwap::load_from_saved(ctx, maker_coin, taker_coin, maker_saved_swap).unwrap(); let err = block_on(maker_swap.recover_funds()).expect_err("Expected an error"); - println!("{}", err); assert!(err.contains("Taker payment was already spent")); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_MY_CALLED }); assert!(unsafe { SEARCH_FOR_SWAP_TX_SPEND_OTHER_CALLED }); diff --git a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs index 7135c13306..a816695d84 100644 --- a/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs +++ b/mm2src/mm2_main/src/lp_swap/recreate_swap_data.rs @@ -448,7 +448,8 @@ async fn convert_maker_to_taker_events( return events; }, MakerSwapEvent::TakerPaymentSpent(tx_ident) => { - let secret = match maker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex).await { + //Is the watcher_reward argument important here? + let secret = match maker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex, false).await { Ok(secret) => H256Json::from(secret.as_slice()), Err(e) => { push_event!(TakerSwapEvent::TakerPaymentWaitForSpendFailed(ERRL!("{}", e).into())); @@ -528,7 +529,7 @@ mod tests { #[test] fn test_recreate_taker_swap() { - TestCoin::extract_secret.mock_safe(|_coin, _secret_hash, _spend_tx| { + TestCoin::extract_secret.mock_safe(|_coin, _secret_hash, _spend_tx, _watcher_reward| { let secret = hex::decode("23a6bb64bc0ab2cc14cb84277d8d25134b814e5f999c66e578c9bba3c5e2d3a4").unwrap(); MockResult::Return(Box::pin(async move { Ok(secret) })) }); diff --git a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs index 26067ae625..09b42f2863 100644 --- a/mm2src/mm2_main/src/lp_swap/swap_watcher.rs +++ b/mm2src/mm2_main/src/lp_swap/swap_watcher.rs @@ -1,10 +1,9 @@ -use super::{broadcast_p2p_tx_msg, lp_coinfind, taker_payment_spend_deadline, tx_helper_topic, H256Json, SwapsContext, - WAIT_CONFIRM_INTERVAL}; +use super::{broadcast_p2p_tx_msg, get_payment_locktime, lp_coinfind, min_watcher_reward, taker_payment_spend_deadline, + tx_helper_topic, H256Json, SwapsContext, WAIT_CONFIRM_INTERVAL}; use crate::mm2::MmError; use async_trait::async_trait; -use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, SendMakerPaymentSpendPreimageInput, - SendWatcherRefundsPaymentArgs, WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, - WatcherValidateTakerFeeInput}; +use coins::{CanRefundHtlc, FoundSwapTxSpend, MmCoinEnum, RefundPaymentArgs, SendMakerPaymentSpendPreimageInput, + WatcherSearchForSwapTxSpendInput, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput}; use common::executor::{AbortSettings, SpawnAbortable, Timer}; use common::log::{debug, error, info}; use common::state_machine::prelude::*; @@ -33,6 +32,7 @@ struct WatcherContext { verified_pub: Vec, data: TakerSwapWatcherData, conf: WatcherConf, + watcher_reward: bool, } impl WatcherContext { @@ -141,6 +141,7 @@ enum WatcherError { MakerPaymentSpendFailed(String), MakerPaymentCouldNotBeFound(String), TakerPaymentRefundFailed(String), + InternalError(String), } impl Stopped { @@ -232,6 +233,20 @@ impl State for ValidateTakerPayment { ))); } + let min_watcher_reward = if watcher_ctx.watcher_reward { + let reward = match min_watcher_reward(&watcher_ctx.taker_coin, &watcher_ctx.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Self::change_state(Stopped::from_reason(StopReason::Error( + WatcherError::InternalError(err.into_inner().to_string()).into(), + ))) + }, + }; + Some(reward) + } else { + None + }; + let validate_input = WatcherValidatePaymentInput { payment_tx: taker_payment_hex.clone(), taker_payment_refund_preimage: watcher_ctx.data.taker_payment_refund_preimage.clone(), @@ -244,6 +259,7 @@ impl State for ValidateTakerPayment { secret_hash: watcher_ctx.data.secret_hash.clone(), try_spv_proof_until: taker_payment_spend_deadline, confirmations, + min_watcher_reward, }; let validated_f = watcher_ctx @@ -276,6 +292,7 @@ impl State for WaitForTakerPaymentSpend { secret_hash: &watcher_ctx.data.secret_hash, tx: &self.taker_payment_hex, search_from_block: watcher_ctx.data.taker_coin_start_block, + watcher_reward: watcher_ctx.watcher_reward, }; loop { @@ -349,7 +366,7 @@ impl State for WaitForTakerPaymentSpend { let tx_hex = tx.tx_hex(); let secret = match watcher_ctx .taker_coin - .extract_secret(&watcher_ctx.data.secret_hash, &tx_hex) + .extract_secret(&watcher_ctx.data.secret_hash, &tx_hex, watcher_ctx.watcher_reward) .await { Ok(bytes) => H256Json::from(bytes.as_slice()), @@ -377,6 +394,7 @@ impl State for SpendMakerPayment { secret: &self.secret.0, secret_hash: &watcher_ctx.data.secret_hash, taker_pub: &watcher_ctx.verified_pub, + watcher_reward: watcher_ctx.watcher_reward, }); let transaction = match spend_fut.compat().await { @@ -441,13 +459,14 @@ impl State for RefundTakerPayment { let refund_fut = watcher_ctx .taker_coin - .send_taker_payment_refund_preimage(SendWatcherRefundsPaymentArgs { + .send_taker_payment_refund_preimage(RefundPaymentArgs { payment_tx: &watcher_ctx.data.taker_payment_refund_preimage, swap_contract_address: &None, secret_hash: &watcher_ctx.data.secret_hash, other_pubkey: &watcher_ctx.verified_pub, time_lock: watcher_ctx.taker_locktime() as u32, swap_unique_data: &[], + watcher_reward: watcher_ctx.watcher_reward, }); let transaction = match refund_fut.compat().await { Ok(t) => t, @@ -557,6 +576,15 @@ impl Drop for SwapWatcherLock { } fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, verified_pub: Vec) { + // TODO: See if more data validations can be added here + if watcher_data.lock_duration != get_payment_locktime() + && watcher_data.lock_duration != get_payment_locktime() * 4 + && watcher_data.lock_duration != get_payment_locktime() * 10 + { + error!("Invalid lock duration"); + return; + } + let swap_ctx = SwapsContext::from_ctx(&ctx).unwrap(); if swap_ctx.swap_msgs.lock().unwrap().contains_key(&watcher_data.uuid) { return; @@ -595,6 +623,11 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri }, }; + if !taker_coin.is_supported_by_watchers() || !maker_coin.is_supported_by_watchers() { + log!("One of the coins or their contracts does not support watchers"); + return; + } + log_tag!( ctx, ""; @@ -605,6 +638,8 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri ); let conf = json::from_value::(ctx.conf["watcher_conf"].clone()).unwrap_or_default(); + //let watcher_reward = taker_coin.is_eth() || maker_coin.is_eth(); + let watcher_reward = false; let watcher_ctx = WatcherContext { ctx, maker_coin, @@ -612,6 +647,7 @@ fn spawn_taker_swap_watcher(ctx: MmArc, watcher_data: TakerSwapWatcherData, veri verified_pub, data: watcher_data, conf, + watcher_reward, }; let state_machine: StateMachine<_, ()> = StateMachine::from_ctx(watcher_ctx); state_machine.run(ValidateTakerFee {}).await; diff --git a/mm2src/mm2_main/src/lp_swap/taker_swap.rs b/mm2src/mm2_main/src/lp_swap/taker_swap.rs index f87cfb1b5e..1a1e583282 100644 --- a/mm2src/mm2_main/src/lp_swap/taker_swap.rs +++ b/mm2src/mm2_main/src/lp_swap/taker_swap.rs @@ -13,12 +13,11 @@ use super::{broadcast_my_swap_status, broadcast_swap_message, broadcast_swap_mes use crate::mm2::lp_network::subscribe_to_topic; use crate::mm2::lp_ordermatch::{MatchBy, OrderConfirmationsSettings, TakerAction, TakerOrderBuilder}; use crate::mm2::lp_price::fetch_swap_coins_price; -use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, tx_helper_topic, wait_for_maker_payment_conf_duration, - TakerSwapWatcherData}; +use crate::mm2::lp_swap::{broadcast_p2p_tx_msg, min_watcher_reward, tx_helper_topic, + wait_for_maker_payment_conf_duration, watcher_reward_amount, TakerSwapWatcherData}; use coins::{lp_coinfind, CanRefundHtlc, CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MmCoinEnum, - PaymentInstructions, PaymentInstructionsErr, SearchForSwapTxSpendInput, SendSpendPaymentArgs, - SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, SendTakerSpendsMakerPaymentArgs, TradeFee, - TradePreimageValue, ValidatePaymentInput}; + PaymentInstructions, PaymentInstructionsErr, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SpendPaymentArgs, TradeFee, TradePreimageValue, ValidatePaymentInput}; use common::executor::Timer; use common::log::{debug, error, info, warn}; use common::{bits256, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; @@ -511,6 +510,7 @@ pub struct TakerSwapMut { taker_payment_refund: Option, secret_hash: BytesJson, secret: H256Json, + watcher_reward: bool, payment_instructions: Option, } @@ -850,6 +850,7 @@ impl TakerSwap { taker_payment_refund: None, secret_hash: BytesJson::default(), secret: H256Json::default(), + watcher_reward: false, payment_instructions: None, }), ctx, @@ -1030,6 +1031,11 @@ impl TakerSwap { p2p_privkey: self.p2p_privkey.map(SerializableSecp256k1Keypair::from), }; + // This value will be true if both sides support & want to use watchers and either the taker or the maker coin is ETH. + // This requires a communication between the parties before the swap starts, which will be done during the ordermatch phase + // or via negotiation messages in the next sprint. + self.w().watcher_reward = false; + Ok((Some(TakerSwapCommand::Negotiate), vec![TakerSwapEvent::Started(data)])) } @@ -1131,6 +1137,7 @@ impl TakerSwap { let taker_coin_swap_contract_bytes = taker_coin_swap_contract_addr .clone() .map_or_else(Vec::new, |bytes| bytes.0); + let my_negotiation_data = self.get_my_negotiation_data( maker_data.secret_hash().to_vec(), maker_coin_swap_contract_bytes, @@ -1320,6 +1327,20 @@ impl TakerSwap { } info!("After wait confirm"); + let min_watcher_reward = if self.r().watcher_reward { + let reward = match min_watcher_reward(&self.maker_coin, &self.taker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::TakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let validate_input = ValidatePaymentInput { payment_tx: self.r().maker_payment.clone().unwrap().tx_hex.0, time_lock_duration: self.r().data.lock_duration, @@ -1331,6 +1352,7 @@ impl TakerSwap { try_spv_proof_until: self.r().data.maker_payment_wait, confirmations, unique_swap_data: self.unique_swap_data(), + min_watcher_reward, }; let validated = self.maker_coin.validate_maker_payment(validate_input).compat().await; @@ -1398,6 +1420,21 @@ impl TakerSwap { amount: &self.taker_amount.to_decimal(), payment_instructions: &self.r().payment_instructions, }); + + let reward_amount = if self.r().watcher_reward { + let reward = match watcher_reward_amount(&self.taker_coin, &self.maker_coin).await { + Ok(reward) => reward, + Err(err) => { + return Ok((Some(TakerSwapCommand::Finish), vec![ + TakerSwapEvent::TakerPaymentTransactionFailed(err.into_inner().to_string().into()), + ])) + }, + }; + Some(reward) + } else { + None + }; + let transaction = match f.compat().await { Ok(res) => match res { Some(tx) => tx, @@ -1406,7 +1443,7 @@ impl TakerSwap { Ok(_) => self.r().data.started_at as u32, Err(_) => self.r().data.taker_payment_lock as u32, }; - let payment_fut = self.taker_coin.send_taker_payment(SendTakerPaymentArgs { + let payment_fut = self.taker_coin.send_taker_payment(SendPaymentArgs { time_lock_duration: self.r().data.lock_duration, time_lock, other_pubkey: &*self.r().other_taker_coin_htlc_pub, @@ -1415,6 +1452,7 @@ impl TakerSwap { swap_contract_address: &self.r().data.taker_coin_swap_contract_address, swap_unique_data: &unique_data, payment_instructions: &self.r().payment_instructions, + watcher_reward: reward_amount, }); match payment_fut.compat().await { @@ -1598,7 +1636,12 @@ impl TakerSwap { }; let secret_hash = self.r().secret_hash.clone(); - let secret = match self.taker_coin.extract_secret(&secret_hash.0, &tx_ident.tx_hex).await { + let watcher_reward = self.r().watcher_reward; + let secret = match self + .taker_coin + .extract_secret(&secret_hash.0, &tx_ident.tx_hex, watcher_reward) + .await + { Ok(bytes) => H256Json::from(bytes.as_slice()), Err(e) => { return Ok((Some(TakerSwapCommand::Finish), vec![ @@ -1622,7 +1665,7 @@ impl TakerSwap { TakerSwapEvent::MakerPaymentSpendFailed("Explicit test failure".into()), ])); } - let spend_fut = self.maker_coin.send_taker_spends_maker_payment(SendSpendPaymentArgs { + let spend_fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { other_payment_tx: &self.r().maker_payment.clone().unwrap().tx_hex, time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, other_pubkey: &*self.r().other_maker_coin_htlc_pub, @@ -1630,6 +1673,7 @@ impl TakerSwap { secret_hash: &self.r().secret_hash.0, swap_contract_address: &self.r().data.maker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match spend_fut.compat().await { Ok(t) => t, @@ -1715,13 +1759,14 @@ impl TakerSwap { } } - let refund_fut = self.taker_coin.send_taker_refunds_payment(SendTakerRefundsPaymentArgs { + let refund_fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, time_lock: locktime as u32, other_pubkey: &*self.r().other_taker_coin_htlc_pub, secret_hash: &self.r().secret_hash.0, swap_contract_address: &self.r().data.taker_coin_swap_contract_address, swap_unique_data: &self.unique_swap_data(), + watcher_reward: self.r().watcher_reward, }); let transaction = match refund_fut.compat().await { @@ -1879,6 +1924,7 @@ impl TakerSwap { let taker_payment_lock = self.r().data.taker_payment_lock; let taker_coin_start_block = self.r().data.taker_coin_start_block; let taker_coin_swap_contract_address = self.r().data.taker_coin_swap_contract_address.clone(); + let watcher_reward = self.r().watcher_reward; let unique_data = self.unique_swap_data(); macro_rules! check_maker_payment_is_not_spent { @@ -1892,6 +1938,7 @@ impl TakerSwap { search_from_block: maker_coin_start_block, swap_contract_address: &maker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; match self.maker_coin.search_for_swap_tx_spend_other(search_input).await { @@ -1951,17 +1998,16 @@ impl TakerSwap { let secret = self.r().secret.0; let maker_coin_swap_contract_address = self.r().data.maker_coin_swap_contract_address.clone(); - let fut = self - .maker_coin - .send_taker_spends_maker_payment(SendTakerSpendsMakerPaymentArgs { - other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: other_maker_coin_htlc_pub.as_slice(), - secret: &secret, - secret_hash: &secret_hash, - swap_contract_address: &maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - }); + let fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { + other_payment_tx: &maker_payment, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + other_pubkey: other_maker_coin_htlc_pub.as_slice(), + secret: &secret, + secret_hash: &secret_hash, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward: self.r().watcher_reward, + }); let transaction = match fut.compat().await { Ok(t) => t, @@ -1994,6 +2040,7 @@ impl TakerSwap { search_from_block: taker_coin_start_block, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }; let taker_payment_spend = try_s!(self.taker_coin.search_for_swap_tx_spend_my(search_input).await); @@ -2003,19 +2050,22 @@ impl TakerSwap { check_maker_payment_is_not_spent!(); let secret_hash = self.r().secret_hash.clone(); let tx_hex = tx.tx_hex(); - let secret = try_s!(self.taker_coin.extract_secret(&secret_hash.0, &tx_hex).await); - - let fut = self - .maker_coin - .send_taker_spends_maker_payment(SendTakerSpendsMakerPaymentArgs { - other_payment_tx: &maker_payment, - time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, - other_pubkey: other_maker_coin_htlc_pub.as_slice(), - secret: &secret, - secret_hash: &secret_hash, - swap_contract_address: &maker_coin_swap_contract_address, - swap_unique_data: &unique_data, - }); + let secret = try_s!( + self.taker_coin + .extract_secret(&secret_hash.0, &tx_hex, watcher_reward) + .await + ); + + let fut = self.maker_coin.send_taker_spends_maker_payment(SpendPaymentArgs { + other_payment_tx: &maker_payment, + time_lock: self.maker_payment_lock.load(Ordering::Relaxed) as u32, + other_pubkey: other_maker_coin_htlc_pub.as_slice(), + secret: &secret, + secret_hash: &secret_hash, + swap_contract_address: &maker_coin_swap_contract_address, + swap_unique_data: &unique_data, + watcher_reward: self.r().watcher_reward, + }); let transaction = match fut.compat().await { Ok(t) => t, @@ -2054,13 +2104,14 @@ impl TakerSwap { return ERR!("Too early to refund, wait until {}", now_ms() / 1000 + seconds_to_wait); } - let fut = self.taker_coin.send_taker_refunds_payment(SendTakerRefundsPaymentArgs { + let fut = self.taker_coin.send_taker_refunds_payment(RefundPaymentArgs { payment_tx: &taker_payment, time_lock: taker_payment_lock as u32, other_pubkey: other_taker_coin_htlc_pub.as_slice(), secret_hash: &secret_hash, swap_contract_address: &taker_coin_swap_contract_address, swap_unique_data: &unique_data, + watcher_reward, }); let transaction = match fut.compat().await { @@ -2574,7 +2625,7 @@ mod taker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); - TestCoin::extract_secret.mock_safe(|_, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); + TestCoin::extract_secret.mock_safe(|_, _, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); static mut MY_PAYMENT_SENT_CALLED: bool = false; TestCoin::check_if_my_payment_sent.mock_safe(|_, _| { @@ -2683,7 +2734,7 @@ mod taker_swap_tests { TestCoin::ticker.mock_safe(|_| MockResult::Return("ticker")); TestCoin::swap_contract_address.mock_safe(|_| MockResult::Return(None)); - TestCoin::extract_secret.mock_safe(|_, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); + TestCoin::extract_secret.mock_safe(|_, _, _, _| MockResult::Return(Box::pin(async move { Ok(vec![]) }))); static mut SEARCH_TX_SPEND_CALLED: bool = false; TestCoin::search_for_swap_tx_spend_my.mock_safe(|_, _| { diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs index 9e81d2c3b3..fe4145e002 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_common.rs @@ -1,5 +1,7 @@ pub use common::{block_on, now_ms}; pub use mm2_number::MmNumber; +use mm2_test_helpers::for_tests::ETH_SEPOLIA_NODE; +use mm2_test_helpers::for_tests::ETH_SEPOLIA_SWAP_CONTRACT; pub use mm2_test_helpers::for_tests::{check_my_swap_status, check_recent_swaps, check_stats_swap_status, enable_native_bch, mm_dump, MarketMakerIt, MAKER_ERROR_EVENTS, MAKER_SUCCESS_EVENTS, TAKER_ERROR_EVENTS, TAKER_SUCCESS_EVENTS}; @@ -163,15 +165,16 @@ pub fn fill_eth(to_addr: &str) { .unwrap(); } -pub fn generate_eth_coin_with_random_privkey() -> EthCoin { +// Generates an ethereum coin in the sepolia network with the given seed +pub fn generate_eth_coin_with_seed(seed: &str) -> EthCoin { let req = json!({ "method": "enable", "coin": "ETH", - "urls": ETH_DEV_NODES, - "swap_contract_address": ETH_DEV_SWAP_CONTRACT, + "urls": ETH_SEPOLIA_NODE, + "swap_contract_address": ETH_SEPOLIA_SWAP_CONTRACT, }); - let priv_key = random_secp256k1_secret(); - let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(priv_key); + let keypair = key_pair_from_seed(seed).unwrap(); + let priv_key_policy = PrivKeyBuildPolicy::IguanaPrivKey(keypair.private().secret); block_on(eth_coin_from_conf_and_request( &MM_CTX, "ETH", @@ -187,8 +190,8 @@ pub fn generate_jst_with_seed(seed: &str) -> EthCoin { let req = json!({ "method": "enable", "coin": "JST", - "urls": ETH_DEV_NODES, - "swap_contract_address": ETH_DEV_SWAP_CONTRACT, + "urls": ETH_SEPOLIA_NODE, + "swap_contract_address": ETH_SEPOLIA_SWAP_CONTRACT, }); let keypair = key_pair_from_seed(seed).unwrap(); @@ -200,7 +203,7 @@ pub fn generate_jst_with_seed(seed: &str) -> EthCoin { &req, CoinProtocol::ERC20 { platform: "ETH".into(), - contract_address: String::from("0x2b294F029Fde858b2c62184e8390591755521d8E"), + contract_address: String::from("0x948BF5172383F1Bc0Fdf3aBe0630b855694A5D2c"), }, priv_key_policy, )) diff --git a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs index 891a817503..d638f285ce 100644 --- a/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs +++ b/mm2src/mm2_main/tests/docker_tests/docker_tests_inner.rs @@ -5,9 +5,8 @@ use bitcrypto::dhash160; use chain::OutPoint; use coins::utxo::rpc_clients::UnspentInfo; use coins::utxo::{GetUtxoListOps, UtxoCommonOps}; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, SearchForSwapTxSpendInput, SendMakerPaymentArgs, - SendMakerRefundsPaymentArgs, SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; +use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, SearchForSwapTxSpendInput, SendPaymentArgs, + SpendPaymentArgs, SwapOps, TransactionEnum, WithdrawRequest}; use common::{block_on, now_ms}; use futures01::Future; use mm2_number::{BigDecimal, MmNumber}; @@ -28,7 +27,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -37,19 +36,21 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -68,6 +69,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -99,7 +101,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -108,19 +110,21 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -139,6 +143,7 @@ fn test_search_for_swap_tx_spend_native_was_refunded_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -155,7 +160,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { let secret_hash = dhash160(&secret); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -164,13 +169,14 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, @@ -178,6 +184,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { secret_hash: secret_hash.as_slice(), swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -196,6 +203,7 @@ fn test_search_for_taker_swap_tx_spend_native_was_spent_by_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -212,7 +220,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { let time_lock = (now_ms() / 1000) as u32 - 3600; let secret_hash = dhash160(&secret); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_pubkey, @@ -221,13 +229,14 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_pubkey, @@ -235,6 +244,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { secret_hash: secret_hash.as_slice(), swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let spend_tx = coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -253,6 +263,7 @@ fn test_search_for_maker_swap_tx_spend_native_was_spent_by_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -272,7 +283,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { let mut unspents = vec![]; let mut sent_tx = vec![]; for i in 0..100 { - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: time_lock + i, other_pubkey: my_pubkey, @@ -281,6 +292,7 @@ fn test_one_hundred_maker_payments_in_a_row_native() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment_args).wait().unwrap(); if let TransactionEnum::UtxoTx(tx) = tx { diff --git a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs index 838a148095..9233477c00 100644 --- a/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/qrc20_tests.rs @@ -6,10 +6,9 @@ use coins::utxo::qtum::{qtum_coin_with_priv_key, QtumCoin}; use coins::utxo::rpc_clients::UtxoRpcClientEnum; use coins::utxo::utxo_common::big_decimal_from_sat; use coins::utxo::{UtxoActivationParams, UtxoCommonOps}; -use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, - SearchForSwapTxSpendInput, SendMakerPaymentArgs, SendMakerRefundsPaymentArgs, - SendMakerSpendsTakerPaymentArgs, SendTakerPaymentArgs, SendTakerRefundsPaymentArgs, - SendTakerSpendsMakerPaymentArgs, SwapOps, TradePreimageValue, TransactionEnum, ValidatePaymentInput}; +use coins::{CheckIfMyPaymentSentArgs, FeeApproxStage, FoundSwapTxSpend, MarketCoinOps, MmCoin, RefundPaymentArgs, + SearchForSwapTxSpendInput, SendPaymentArgs, SpendPaymentArgs, SwapOps, TradePreimageValue, + TransactionEnum, ValidatePaymentInput}; use common::log::debug; use common::{temp_dir, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::Secp256k1Secret; @@ -182,7 +181,7 @@ fn test_taker_spends_maker_payment() { let secret = &[1; 32]; let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -191,6 +190,7 @@ fn test_taker_spends_maker_payment() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -217,9 +217,10 @@ fn test_taker_spends_maker_payment() { try_spv_proof_until: wait_until + 30, confirmations, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; taker_coin.validate_maker_payment(input).wait().unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &maker_pub, @@ -227,6 +228,7 @@ fn test_taker_spends_maker_payment() { secret_hash: &secret_hash, swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -275,7 +277,7 @@ fn test_maker_spends_taker_payment() { let secret = &[1; 32]; let secret_hash = dhash160(secret).to_vec(); let amount = BigDecimal::try_from(0.2).unwrap(); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -284,6 +286,7 @@ fn test_maker_spends_taker_payment() { swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = taker_coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -310,9 +313,10 @@ fn test_maker_spends_taker_payment() { try_spv_proof_until: wait_until + 30, confirmations, unique_swap_data: Vec::new(), + min_watcher_reward: None, }; maker_coin.validate_taker_payment(input).wait().unwrap(); - let maker_spends_payment_args = SendMakerSpendsTakerPaymentArgs { + let maker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, @@ -320,6 +324,7 @@ fn test_maker_spends_taker_payment() { secret_hash: &secret_hash, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = maker_coin .send_maker_spends_taker_payment(maker_spends_payment_args) @@ -357,7 +362,7 @@ fn test_maker_refunds_payment() { let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let maker_payment = SendMakerPaymentArgs { + let maker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -366,6 +371,7 @@ fn test_maker_refunds_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -382,13 +388,14 @@ fn test_maker_refunds_payment() { let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, secret_hash, swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -417,7 +424,7 @@ fn test_taker_refunds_payment() { let maker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &maker_pub, @@ -426,6 +433,7 @@ fn test_taker_refunds_payment() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_taker_payment(taker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -442,13 +450,14 @@ fn test_taker_refunds_payment() { let balance_after_payment = coin.my_spendable_balance().wait().unwrap(); assert_eq!(expected_balance.clone() - amount, balance_after_payment); - let taker_refunds_payment_args = SendTakerRefundsPaymentArgs { + let taker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &maker_pub, secret_hash, swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = coin .send_taker_refunds_payment(taker_refunds_payment_args) @@ -474,7 +483,7 @@ fn test_check_if_my_payment_sent() { let taker_pub = hex::decode("022b00078841f37b5d30a6a1defb82b3af4d4e2d24dd4204d41f0c9ce1e875de1a").unwrap(); let secret_hash = &[1; 20]; let amount = BigDecimal::from_str("0.2").unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -483,6 +492,7 @@ fn test_check_if_my_payment_sent() { swap_contract_address: &coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -524,7 +534,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { let secret = &[1; 32]; let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -533,6 +543,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -547,7 +558,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) .wait() .unwrap(); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: maker_pub, @@ -555,6 +566,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { secret_hash: secret_hash.as_slice(), swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -578,6 +590,7 @@ fn test_search_for_swap_tx_spend_taker_spent() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); let expected = Ok(Some(FoundSwapTxSpend::Spent(spend))); @@ -594,7 +607,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { let secret = &[1; 32]; let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -603,6 +616,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -617,13 +631,14 @@ fn test_search_for_swap_tx_spend_maker_refunded() { .wait_for_confirmations(&payment_tx_hex, confirmations, requires_nota, wait_until, check_every) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &payment_tx_hex, time_lock: timelock, other_pubkey: &taker_pub, secret_hash, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let refund = maker_coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -647,6 +662,7 @@ fn test_search_for_swap_tx_spend_maker_refunded() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); let expected = Ok(Some(FoundSwapTxSpend::Refunded(refund))); @@ -663,7 +679,7 @@ fn test_search_for_swap_tx_spend_not_spent() { let secret = &[1; 32]; let secret_hash = &*dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &taker_pub, @@ -672,6 +688,7 @@ fn test_search_for_swap_tx_spend_not_spent() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -695,6 +712,7 @@ fn test_search_for_swap_tx_spend_not_spent() { search_from_block, swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let actual = block_on(maker_coin.search_for_swap_tx_spend_my(search_input)); // maker payment hasn't been spent or refunded yet @@ -713,7 +731,7 @@ fn test_wait_for_tx_spend() { let secret = &[1; 32]; let secret_hash = dhash160(secret); let amount = BigDecimal::try_from(0.2).unwrap(); - let maker_payment_args = SendMakerPaymentArgs { + let maker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: taker_pub, @@ -722,6 +740,7 @@ fn test_wait_for_tx_spend() { swap_contract_address: &maker_coin.swap_contract_address(), swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let payment = maker_coin.send_maker_payment(maker_payment_args).wait().unwrap(); let payment_tx_hash = payment.tx_hash(); @@ -762,7 +781,7 @@ fn test_wait_for_tx_spend() { let payment_hex = payment_tx_hex.clone(); thread::spawn(move || { thread::sleep(Duration::from_secs(5)); - let taker_spends_payment_args = SendTakerSpendsMakerPaymentArgs { + let taker_spends_payment_args = SpendPaymentArgs { other_payment_tx: &payment_hex, time_lock: timelock, other_pubkey: &maker_pub_c, @@ -770,6 +789,7 @@ fn test_wait_for_tx_spend() { secret_hash: secret_hash.as_slice(), swap_contract_address: &taker_coin.swap_contract_address(), swap_unique_data: &[], + watcher_reward: false, }; let spend = taker_coin .send_taker_spends_maker_payment(taker_spends_payment_args) @@ -1028,7 +1048,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ .send_taker_fee(&DEX_FEE_ADDR_RAW_PUBKEY, dex_fee_amount.to_decimal(), &[]) .wait() .expect("!send_taker_fee"); - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock: timelock, other_pubkey: &DEX_FEE_ADDR_RAW_PUBKEY, @@ -1037,6 +1057,7 @@ fn test_get_max_taker_vol_and_trade_with_dynamic_trade_fee(coin: QtumCoin, priv_ swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let _taker_payment_tx = coin @@ -1426,7 +1447,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let maker_payment = SendMakerPaymentArgs { + let maker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -1435,19 +1456,21 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_maker_payment(maker_payment).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -1466,6 +1489,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_maker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() @@ -1481,7 +1505,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment = SendTakerPaymentArgs { + let taker_payment = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -1490,19 +1514,21 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment).wait().unwrap(); coin.wait_for_confirmations(&tx.tx_hex(), 1, false, timeout, 1) .wait() .unwrap(); - let maker_refunds_payment_args = SendMakerRefundsPaymentArgs { + let maker_refunds_payment_args = RefundPaymentArgs { payment_tx: &tx.tx_hex(), time_lock, other_pubkey: my_public_key, secret_hash: &[0; 20], swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let refund_tx = coin .send_maker_refunds_payment(maker_refunds_payment_args) @@ -1521,6 +1547,7 @@ fn test_search_for_segwit_swap_tx_spend_native_was_refunded_taker() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() 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 98ea02ce44..6bd474ff32 100644 --- a/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs +++ b/mm2src/mm2_main/tests/docker_tests/swap_watcher_tests.rs @@ -1,18 +1,20 @@ -use crate::docker_tests::docker_tests_common::{eth_distributor, generate_eth_coin_with_random_privkey, - generate_jst_with_seed}; +use crate::docker_tests::docker_tests_common::{eth_distributor, generate_eth_coin_with_seed, generate_jst_with_seed}; use crate::integration_tests_common::*; use crate::{generate_utxo_coin_with_privkey, generate_utxo_coin_with_random_privkey, random_secp256k1_secret, SecretKey}; use coins::coin_errors::ValidatePaymentError; -use coins::utxo::UtxoCommonOps; -use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoinEnum, SearchForSwapTxSpendInput, SendTakerPaymentArgs, - SendWatcherRefundsPaymentArgs, SwapOps, WatcherOps, WatcherValidateTakerFeeInput, - EARLY_CONFIRMATION_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_SENDER_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; +use coins::utxo::{dhash160, UtxoCommonOps}; +use coins::{FoundSwapTxSpend, MarketCoinOps, MmCoin, MmCoinEnum, RefundPaymentArgs, SearchForSwapTxSpendInput, + SendPaymentArgs, SwapOps, WatcherOps, WatcherValidatePaymentInput, WatcherValidateTakerFeeInput, + EARLY_CONFIRMATION_ERR_LOG, INSUFFICIENT_WATCHER_REWARD_ERR_LOG, INVALID_CONTRACT_ADDRESS_ERR_LOG, + INVALID_PAYMENT_STATE_ERR_LOG, INVALID_RECEIVER_ERR_LOG, INVALID_REFUND_TX_ERR_LOG, + INVALID_SCRIPT_ERR_LOG, INVALID_SENDER_ERR_LOG, INVALID_SWAP_ID_ERR_LOG, OLD_TRANSACTION_ERR_LOG}; use common::{block_on, now_ms, DEX_FEE_ADDR_RAW_PUBKEY}; +use crypto::privkey::key_pair_from_secret; use futures01::Future; -use mm2_main::mm2::lp_swap::{dex_fee_amount_from_taker_coin, get_payment_locktime, MAKER_PAYMENT_SENT_LOG, - MAKER_PAYMENT_SPEND_FOUND_LOG, MAKER_PAYMENT_SPEND_SENT_LOG, - TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; +use mm2_main::mm2::lp_swap::{dex_fee_amount_from_taker_coin, get_payment_locktime, min_watcher_reward, + watcher_reward_amount, MakerSwap, MAKER_PAYMENT_SENT_LOG, MAKER_PAYMENT_SPEND_FOUND_LOG, + MAKER_PAYMENT_SPEND_SENT_LOG, TAKER_PAYMENT_REFUND_SENT_LOG, WATCHER_MESSAGE_SENT_LOG}; use mm2_number::BigDecimal; use mm2_number::MmNumber; use mm2_test_helpers::for_tests::{enable_eth_coin, eth_jst_conf, eth_testnet_conf, mm_dump, my_balance, mycoin1_conf, @@ -30,7 +32,8 @@ fn enable_eth_and_jst(mm_node: &MarketMakerIt) { "ETH", ETH_SEPOLIA_NODE, ETH_SEPOLIA_SWAP_CONTRACT, - Some(ETH_SEPOLIA_SWAP_CONTRACT) + Some(ETH_SEPOLIA_SWAP_CONTRACT), + true ))); dbg!(block_on(enable_eth_coin( @@ -38,7 +41,8 @@ fn enable_eth_and_jst(mm_node: &MarketMakerIt) { "JST", ETH_SEPOLIA_NODE, ETH_SEPOLIA_SWAP_CONTRACT, - Some(ETH_SEPOLIA_SWAP_CONTRACT) + Some(ETH_SEPOLIA_SWAP_CONTRACT), + true ))); } @@ -55,7 +59,7 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -81,6 +85,7 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); @@ -96,12 +101,101 @@ fn test_watcher_spends_maker_payment_spend_eth_erc20() { let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; let volume = BigDecimal::from_str("0.01").unwrap(); assert_eq!(alice_jst_balance_before - volume.clone(), alice_jst_balance_after); assert_eq!(bob_jst_balance_before + volume.clone(), bob_jst_balance_after); assert_eq!(alice_eth_balance_before + volume.clone(), alice_eth_balance_after); assert_eq!(bob_eth_balance_before - volume.clone(), bob_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); +} + +#[test] +#[ignore] +fn test_two_watchers_spend_maker_payment_eth_erc20() { + let coins = json!([eth_testnet_conf(), eth_jst_conf(ETH_SEPOLIA_TOKEN_CONTRACT)]); + + let alice_passphrase = + String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let alice_conf = Mm2TestConf::seednode_using_watchers(&alice_passphrase, &coins); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), alice_conf.rpc_password.clone(), None).unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let watcher1_passphrase = + String::from("also shoot benefit prefer juice shell thank unfair canal monkey style afraid"); + let watcher1_conf = + Mm2TestConf::watcher_light_node(&watcher1_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }) + .conf; + let mut mm_watcher1 = MarketMakerIt::start(watcher1_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher1.log_path); + + let watcher2_passphrase = + String::from("also shoot benefit shell thank prefer juice unfair canal monkey style afraid"); + let watcher2_conf = + Mm2TestConf::watcher_light_node(&watcher2_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 0., + refund_start_factor: 1.5, + search_interval: 1.0, + }) + .conf; + let mut mm_watcher2 = MarketMakerIt::start(watcher2_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher1.log_path); + + enable_eth_and_jst(&mm_alice); + enable_eth_and_jst(&mm_bob); + enable_eth_and_jst(&mm_watcher1); + enable_eth_and_jst(&mm_watcher2); + + let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); + let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); + let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher1_eth_balance_before = block_on(my_balance(&mm_watcher1, "ETH")).balance; + let watcher2_eth_balance_before = block_on(my_balance(&mm_watcher2, "ETH")).balance; + + block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); + + block_on(mm_alice.wait_for_log(180., |log| log.contains(WATCHER_MESSAGE_SENT_LOG))).unwrap(); + block_on(mm_alice.stop()).unwrap(); + block_on(mm_watcher1.wait_for_log(180., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + block_on(mm_watcher2.wait_for_log(180., |log| log.contains(MAKER_PAYMENT_SPEND_SENT_LOG))).unwrap(); + thread::sleep(Duration::from_secs(25)); + + let mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), alice_conf.rpc_password.clone(), None).unwrap(); + enable_eth_and_jst(&mm_alice); + + let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); + let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); + let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher1_eth_balance_after = block_on(my_balance(&mm_watcher1, "ETH")).balance; + let watcher2_eth_balance_after = block_on(my_balance(&mm_watcher2, "ETH")).balance; + + let volume = BigDecimal::from_str("0.01").unwrap(); + assert_eq!(alice_jst_balance_before - volume.clone(), alice_jst_balance_after); + assert_eq!(bob_jst_balance_before + volume.clone(), bob_jst_balance_after); + assert_eq!(alice_eth_balance_before + volume.clone(), alice_eth_balance_after); + assert_eq!(bob_eth_balance_before - volume.clone(), bob_eth_balance_after); + if watcher1_eth_balance_after > watcher1_eth_balance_before { + assert_eq!(watcher2_eth_balance_after, watcher2_eth_balance_after); + } + if watcher2_eth_balance_after > watcher2_eth_balance_before { + assert_eq!(watcher1_eth_balance_after, watcher1_eth_balance_after); + } } #[test] @@ -117,7 +211,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -144,6 +238,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_before = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_before = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("JST", "ETH")], 1., 1., 0.01)); @@ -159,6 +254,7 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); let bob_eth_balance_after = block_on(my_balance(&mm_bob, "ETH")).balance.with_scale(2); let bob_jst_balance_after = block_on(my_balance(&mm_bob, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; let volume = BigDecimal::from_str("0.01").unwrap(); @@ -166,6 +262,48 @@ fn test_watcher_spends_maker_payment_spend_erc20_eth() { assert_eq!(bob_jst_balance_before - volume.clone(), bob_jst_balance_after); assert_eq!(alice_eth_balance_before - volume.clone(), alice_eth_balance_after); assert_eq!(bob_eth_balance_before + volume.clone(), bob_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); +} + +#[test] +#[ignore] +fn test_watcher_waits_for_taker_eth() { + let coins = json!([eth_testnet_conf(), eth_jst_conf(ETH_SEPOLIA_TOKEN_CONTRACT)]); + + let alice_passphrase = + String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let alice_conf = Mm2TestConf::seednode_using_watchers(&alice_passphrase, &coins); + let mut mm_alice = MarketMakerIt::start(alice_conf.conf.clone(), alice_conf.rpc_password.clone(), None).unwrap(); + let (_alice_dump_log, _alice_dump_dashboard) = mm_alice.mm_dump(); + log!("Alice log path: {}", mm_alice.log_path.display()); + + let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); + let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); + log!("Bob log path: {}", mm_bob.log_path.display()); + + let watcher_passphrase = + String::from("also shoot benefit prefer juice shell thank unfair canal monkey style afraid"); + let watcher_conf = + Mm2TestConf::watcher_light_node(&watcher_passphrase, &coins, &[&mm_alice.ip.to_string()], WatcherConf { + wait_taker_payment: 0., + wait_maker_payment_spend_factor: 1., + refund_start_factor: 1.5, + search_interval: 1., + }) + .conf; + + let mut mm_watcher = MarketMakerIt::start(watcher_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); + let (_watcher_dump_log, _watcher_dump_dashboard) = mm_dump(&mm_watcher.log_path); + + enable_eth_and_jst(&mm_alice); + enable_eth_and_jst(&mm_bob); + enable_eth_and_jst(&mm_watcher); + + block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); + + block_on(mm_watcher.wait_for_log(160., |log| log.contains(MAKER_PAYMENT_SPEND_FOUND_LOG))).unwrap(); } #[test] @@ -187,7 +325,7 @@ fn test_watcher_refunds_taker_payment_erc20() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -217,6 +355,7 @@ fn test_watcher_refunds_taker_payment_erc20() { let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("ETH", "JST")], 1., 1., 0.01)); @@ -232,9 +371,11 @@ fn test_watcher_refunds_taker_payment_erc20() { let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; assert_eq!(alice_jst_balance_before, alice_jst_balance_after); assert_eq!(alice_eth_balance_before, alice_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); } #[test] @@ -256,7 +397,7 @@ fn test_watcher_refunds_taker_payment_eth() { log!("Alice log path: {}", mm_alice.log_path.display()); let bob_passphrase = String::from("also shoot benefit prefer juice shell elder veteran woman mimic image kidney"); - let bob_conf = Mm2TestConf::light_node(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); + let bob_conf = Mm2TestConf::light_node_using_watchers(&bob_passphrase, &coins, &[&mm_alice.ip.to_string()]); let mut mm_bob = MarketMakerIt::start(bob_conf.conf, bob_conf.rpc_password, None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_bob.mm_dump(); log!("Bob log path: {}", mm_bob.log_path.display()); @@ -286,6 +427,7 @@ fn test_watcher_refunds_taker_payment_eth() { let alice_eth_balance_before = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_before = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_before = block_on(my_balance(&mm_watcher, "ETH")).balance; block_on(start_swaps(&mut mm_bob, &mut mm_alice, &[("JST", "ETH")], 1., 1., 0.01)); @@ -301,9 +443,11 @@ fn test_watcher_refunds_taker_payment_eth() { let alice_eth_balance_after = block_on(my_balance(&mm_alice, "ETH")).balance.with_scale(2); let alice_jst_balance_after = block_on(my_balance(&mm_alice, "JST")).balance.with_scale(2); + let watcher_eth_balance_after = block_on(my_balance(&mm_watcher, "ETH")).balance; assert_eq!(alice_jst_balance_before, alice_jst_balance_after); assert_eq!(alice_eth_balance_before, alice_eth_balance_after); + assert!(watcher_eth_balance_after > watcher_eth_balance_before); } #[test] @@ -315,16 +459,8 @@ fn test_watcher_validate_taker_fee_eth() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pubkey = taker_keypair.public(); - let maker_coin = generate_eth_coin_with_random_privkey(); - let maker_keypair = maker_coin.derive_htlc_key_pair(&[]); - let maker_pubkey = maker_keypair.public(); - let taker_amount = MmNumber::from((10, 1)); - let fee_amount = dex_fee_amount_from_taker_coin( - &MmCoinEnum::EthCoin(taker_coin.clone()), - maker_coin.ticker(), - &taker_amount, - ); + let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee( &DEX_FEE_ADDR_RAW_PUBKEY, @@ -350,10 +486,11 @@ fn test_watcher_validate_taker_fee_eth() { .wait(); assert!(validate_taker_fee_res.is_ok()); + let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); let error = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { taker_fee_hash: taker_fee.tx_hash().into_vec(), - sender_pubkey: maker_pubkey.to_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), min_block_number: 0, fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), lock_duration, @@ -416,6 +553,7 @@ fn test_watcher_validate_taker_fee_eth() { } #[test] +#[ignore] fn test_watcher_validate_taker_fee_erc20() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run let lock_duration = get_payment_locktime(); @@ -425,16 +563,8 @@ fn test_watcher_validate_taker_fee_erc20() { let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); let taker_pubkey = taker_keypair.public(); - let maker_coin = generate_eth_coin_with_random_privkey(); - let maker_keypair = maker_coin.derive_htlc_key_pair(&[]); - let maker_pubkey = maker_keypair.public(); - let taker_amount = MmNumber::from((10, 1)); - let fee_amount = dex_fee_amount_from_taker_coin( - &MmCoinEnum::EthCoin(taker_coin.clone()), - maker_coin.ticker(), - &taker_amount, - ); + let fee_amount = dex_fee_amount_from_taker_coin(&MmCoinEnum::EthCoin(taker_coin.clone()), "ETH", &taker_amount); let taker_fee = taker_coin .send_taker_fee( &DEX_FEE_ADDR_RAW_PUBKEY, @@ -460,10 +590,11 @@ fn test_watcher_validate_taker_fee_erc20() { .wait(); assert!(validate_taker_fee_res.is_ok()); + let wrong_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); let error = taker_coin .watcher_validate_taker_fee(WatcherValidateTakerFeeInput { taker_fee_hash: taker_fee.tx_hash().into_vec(), - sender_pubkey: maker_pubkey.to_vec(), + sender_pubkey: wrong_keypair.public().to_vec(), min_block_number: 0, fee_addr: DEX_FEE_ADDR_RAW_PUBKEY.to_vec(), lock_duration, @@ -525,6 +656,529 @@ fn test_watcher_validate_taker_fee_erc20() { } } +#[test] +#[ignore] +fn test_watcher_validate_taker_payment_eth() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + + let seed = String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let taker_coin = generate_eth_coin_with_seed(&seed); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + let amount = BigDecimal::from_str("0.01").unwrap(); + let secret_hash = dhash160(&MakerSwap::generate_secret()); + let watcher_reward = Some( + block_on(watcher_reward_amount( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + + let min_watcher_reward = Some( + block_on(min_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: amount.clone(), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SENDER_ERR_LOG, error + ), + } + + let taker_payment_wrong_contract = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: amount.clone(), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_CONTRACT_ADDRESS_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, error + ), + } + + // Used to get wrong swap id + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(coins::WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains(INVALID_PAYMENT_STATE_ERR_LOG)) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + INVALID_PAYMENT_STATE_ERR_LOG, error + ), + } + + let taker_payment_wrong_secret = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount, + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SWAP_ID_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SWAP_ID_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_RECEIVER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_RECEIVER_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: min_watcher_reward.map(|min_reward| min_reward * 3), + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INSUFFICIENT_WATCHER_REWARD_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, error + ), + } +} + +#[test] +#[ignore] +fn test_watcher_validate_taker_payment_erc20() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + + let seed = String::from("spice describe gravity federal blast come thank unfair canal monkey style afraid"); + let taker_coin = generate_jst_with_seed(&seed); + let taker_keypair = taker_coin.derive_htlc_key_pair(&[]); + let taker_pub = taker_keypair.public(); + + let maker_keypair = key_pair_from_secret(random_secp256k1_secret().as_slice()).unwrap(); + let maker_pub = maker_keypair.public(); + + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + + let secret_hash = dhash160(&MakerSwap::generate_secret()); + let watcher_reward = Some( + block_on(watcher_reward_amount( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + let min_watcher_reward = Some( + block_on(min_watcher_reward( + &MmCoinEnum::from(taker_coin.clone()), + &MmCoinEnum::from(taker_coin.clone()), + )) + .unwrap(), + ); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: maker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SENDER_ERR_LOG, error + ), + } + + let taker_payment_wrong_contract = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &Some("9130b257d37a52e52f21054c4da3450c72f595ce".into()), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment_wrong_contract.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_CONTRACT_ADDRESS_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_CONTRACT_ADDRESS_ERR_LOG, error + ), + } + + // Used to get wrong swap id + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::UnexpectedPaymentState(err) => { + assert!(err.contains(INVALID_PAYMENT_STATE_ERR_LOG)) + }, + _ => panic!( + "Expected `UnexpectedPaymentState` {}, found {:?}", + INVALID_PAYMENT_STATE_ERR_LOG, error + ), + } + + let taker_payment_wrong_secret = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pub, + secret_hash: wrong_secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &taker_coin.swap_contract_address(), + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment_wrong_secret.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SWAP_ID_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SWAP_ID_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: taker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward, + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_RECEIVER_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_RECEIVER_ERR_LOG, error + ), + } + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: Vec::new(), + time_lock, + taker_pub: taker_pub.to_vec(), + maker_pub: maker_pub.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: min_watcher_reward.map(|min_reward| min_reward * 3), + }) + .wait() + .unwrap_err() + .into_inner(); + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INSUFFICIENT_WATCHER_REWARD_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INSUFFICIENT_WATCHER_REWARD_ERR_LOG, error + ), + } +} + #[test] fn test_watcher_spends_maker_payment_spend_utxo() { let (_ctx, _, bob_priv_key) = generate_utxo_coin_with_random_privkey("MYCOIN", 100.into()); @@ -540,10 +1194,11 @@ fn test_watcher_spends_maker_payment_spend_utxo() { let mut mm_alice = MarketMakerIt::start(alice_conf.clone(), DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -620,10 +1275,11 @@ fn test_watcher_waits_for_taker_utxo() { let mut mm_alice = MarketMakerIt::start(alice_conf.clone(), DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -680,10 +1336,11 @@ fn test_watcher_refunds_taker_payment_utxo() { .unwrap(); let (_alice_dump_log, _alice_dump_dashboard) = mm_dump(&mm_alice.log_path); - let bob_conf = Mm2TestConf::light_node(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice - .ip - .to_string()]) - .conf; + let bob_conf = + Mm2TestConf::light_node_using_watchers(&format!("0x{}", hex::encode(bob_priv_key)), &coins, &[&mm_alice + .ip + .to_string()]) + .conf; let mut mm_bob = MarketMakerIt::start(bob_conf, DEFAULT_RPC_PASSWORD.to_string(), None).unwrap(); let (_bob_dump_log, _bob_dump_dashboard) = mm_dump(&mm_bob.log_path); @@ -869,6 +1526,186 @@ fn test_watcher_validate_taker_fee_utxo() { } } +#[test] +fn test_watcher_validate_taker_payment_utxo() { + let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run + let time_lock_duration = get_payment_locktime(); + let time_lock = (now_ms() / 1000 + time_lock_duration) as u32; + + let (_ctx, taker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let taker_pubkey = taker_coin.my_public_key().unwrap(); + + let (_ctx, maker_coin, _) = generate_utxo_coin_with_random_privkey("MYCOIN", 1000u64.into()); + let maker_pubkey = maker_coin.my_public_key().unwrap(); + + let secret_hash = dhash160(&MakerSwap::generate_secret()); + + let taker_payment = taker_coin + .send_taker_payment(SendPaymentArgs { + time_lock_duration, + time_lock, + other_pubkey: maker_pubkey, + secret_hash: secret_hash.as_slice(), + amount: BigDecimal::from(10), + swap_contract_address: &None, + swap_unique_data: &[], + payment_instructions: &None, + watcher_reward: None, + }) + .wait() + .unwrap(); + + taker_coin + .wait_for_confirmations(&taker_payment.tx_hex(), 1, false, timeout, 1) + .wait() + .unwrap(); + + let taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + secret_hash.as_slice(), + &None, + &[], + ) + .wait() + .unwrap(); + let validate_taker_payment_res = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait(); + assert!(validate_taker_payment_res.is_ok()); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: maker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SENDER_ERR_LOG)) + }, + _ => panic!("Expected `WrongPaymentTx` {INVALID_SENDER_ERR_LOG}, found {:?}", error), + } + + let wrong_secret_hash = dhash160(&MakerSwap::generate_secret()); + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SCRIPT_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SCRIPT_ERR_LOG, error + ), + } + + // Wrong time lock + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: taker_payment_refund_preimage.tx_hex(), + time_lock: 500, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: wrong_secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_SCRIPT_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_SCRIPT_ERR_LOG, error + ), + } + + let wrong_taker_payment_refund_preimage = taker_coin + .create_taker_payment_refund_preimage( + &taker_payment.tx_hex(), + time_lock, + maker_pubkey, + wrong_secret_hash.as_slice(), + &None, + &[], + ) + .wait() + .unwrap(); + + let error = taker_coin + .watcher_validate_taker_payment(WatcherValidatePaymentInput { + payment_tx: taker_payment.tx_hex(), + taker_payment_refund_preimage: wrong_taker_payment_refund_preimage.tx_hex(), + time_lock, + taker_pub: taker_pubkey.to_vec(), + maker_pub: maker_pubkey.to_vec(), + secret_hash: secret_hash.to_vec(), + try_spv_proof_until: timeout, + confirmations: 1, + min_watcher_reward: None, + }) + .wait() + .unwrap_err() + .into_inner(); + + log!("error: {:?}", error); + match error { + ValidatePaymentError::WrongPaymentTx(err) => { + assert!(err.contains(INVALID_REFUND_TX_ERR_LOG)) + }, + _ => panic!( + "Expected `WrongPaymentTx` {}, found {:?}", + INVALID_REFUND_TX_ERR_LOG, error + ), + } +} + #[test] fn test_send_taker_payment_refund_preimage_utxo() { let timeout = (now_ms() / 1000) + 120; // timeout if test takes more than 120 seconds to run @@ -876,7 +1713,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { let my_public_key = coin.my_public_key().unwrap(); let time_lock = (now_ms() / 1000) as u32 - 3600; - let taker_payment_args = SendTakerPaymentArgs { + let taker_payment_args = SendPaymentArgs { time_lock_duration: 0, time_lock, other_pubkey: my_public_key, @@ -885,6 +1722,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { swap_contract_address: &None, swap_unique_data: &[], payment_instructions: &None, + watcher_reward: None, }; let tx = coin.send_taker_payment(taker_payment_args).wait().unwrap(); @@ -898,13 +1736,14 @@ fn test_send_taker_payment_refund_preimage_utxo() { .unwrap(); let refund_tx = coin - .send_taker_payment_refund_preimage(SendWatcherRefundsPaymentArgs { + .send_taker_payment_refund_preimage(RefundPaymentArgs { payment_tx: &refund_tx.tx_hex(), swap_contract_address: &None, secret_hash: &[0; 20], other_pubkey: my_public_key, time_lock, swap_unique_data: &[], + watcher_reward: false, }) .wait() .unwrap(); @@ -921,6 +1760,7 @@ fn test_send_taker_payment_refund_preimage_utxo() { search_from_block: 0, swap_contract_address: &None, swap_unique_data: &[], + watcher_reward: false, }; let found = block_on(coin.search_for_swap_tx_spend_my(search_input)) .unwrap() diff --git a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs index c6dd0265b6..832bdca84c 100644 --- a/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs +++ b/mm2src/mm2_main/tests/mm2_tests/iris_swap.rs @@ -110,8 +110,8 @@ pub async fn trade_base_rel_iris( .await ); dbg!(enable_electrum(&mm_alice, "RICK", false, RICK_ELECTRUM_ADDRS).await); - dbg!(enable_eth_coin(&mm_bob, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None).await); - dbg!(enable_eth_coin(&mm_alice, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None).await); + dbg!(enable_eth_coin(&mm_bob, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); + dbg!(enable_eth_coin(&mm_alice, "tBNB", TBNB_URLS, TBNB_SWAP_CONTRACT, None, false).await); for (base, rel) in pairs.iter() { log!("Issue bob {}/{} sell request", base, rel); diff --git a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs index d6d4042ace..98425fda32 100644 --- a/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs +++ b/mm2src/mm2_main/tests/mm2_tests/mm2_tests_inner.rs @@ -7450,7 +7450,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7459,7 +7460,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7468,7 +7470,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7477,7 +7480,8 @@ fn test_eth_swap_contract_addr_negotiation_same_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); let uuids = block_on(start_swaps( @@ -7538,6 +7542,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", None, + false ))); dbg!(block_on(enable_eth_coin( @@ -7547,6 +7552,7 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { // using arbitrary address "0x2b294F029Fde858b2c62184e8390591755521d8E", None, + false ))); dbg!(block_on(enable_eth_coin( @@ -7555,7 +7561,8 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); dbg!(block_on(enable_eth_coin( @@ -7564,7 +7571,8 @@ fn test_eth_swap_negotiation_fails_maker_no_fallback() { ETH_DEV_NODES, // using arbitrary address ETH_MAINNET_SWAP_CONTRACT, - Some(ETH_DEV_SWAP_CONTRACT) + Some(ETH_DEV_SWAP_CONTRACT), + false ))); let uuids = block_on(start_swaps( diff --git a/mm2src/mm2_test_helpers/src/for_tests.rs b/mm2src/mm2_test_helpers/src/for_tests.rs index 2680ced419..74d0bd1fb8 100644 --- a/mm2src/mm2_test_helpers/src/for_tests.rs +++ b/mm2src/mm2_test_helpers/src/for_tests.rs @@ -147,7 +147,7 @@ pub const ETH_DEV_NODES: &[&str] = &["http://195.201.0.6:8565"]; pub const ETH_DEV_SWAP_CONTRACT: &str = "0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd"; pub const ETH_SEPOLIA_NODE: &[&str] = &["https://rpc-sepolia.rockx.com/"]; -pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0xA25E0e06fB139CDc2f9f11675877DaD9EdD1C352"; +pub const ETH_SEPOLIA_SWAP_CONTRACT: &str = "0x5BCC05dD32a87fABEDBcbbfeb77476eaD1F7051C"; pub const ETH_SEPOLIA_TOKEN_CONTRACT: &str = "0x948BF5172383F1Bc0Fdf3aBe0630b855694A5D2c"; pub const BCHD_TESTNET_URLS: &[&str] = &["https://bchd-testnet.greyh.at:18335"]; @@ -216,6 +216,21 @@ impl Mm2TestConf { } } + pub fn light_node_using_watchers(passphrase: &str, coins: &Json, seednodes: &[&str]) -> Self { + Mm2TestConf { + conf: json!({ + "gui": "nogui", + "netid": 9998, + "passphrase": passphrase, + "coins": coins, + "rpc_password": DEFAULT_RPC_PASSWORD, + "seednodes": seednodes, + "use_watchers": true + }), + rpc_password: DEFAULT_RPC_PASSWORD.into(), + } + } + pub fn watcher_light_node(passphrase: &str, coins: &Json, seednodes: &[&str], conf: WatcherConf) -> Self { Mm2TestConf { conf: json!({ @@ -1576,6 +1591,7 @@ pub async fn enable_eth_coin( urls: &[&str], swap_contract_address: &str, fallback_swap_contract: Option<&str>, + contract_supports_watcher: bool, ) -> Json { let enable = mm .rpc(&json!({ @@ -1586,6 +1602,7 @@ pub async fn enable_eth_coin( "swap_contract_address": swap_contract_address, "fallback_swap_contract": fallback_swap_contract, "mm2": 1, + "contract_supports_watchers": contract_supports_watcher })) .await .unwrap();