Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
128c3eb
delete payment state check from eth/nft TPU validation impls
laruh Jan 28, 2025
c321e95
eth-taker: delete PaymentSent state check in taker_payment_approve
laruh Jan 28, 2025
466a633
nft-maker: delete payment state check in spend_nft_maker_payment_v2_impl
laruh Jan 28, 2025
6191d51
call and validate token funcs with and without retry call functionality
laruh Jan 29, 2025
616ff35
eth-taker: remove payment state validation in sign_and_broadcast_take…
laruh Jan 30, 2025
2c0e8ad
WIP: return Result<bool, Web3RpcError> from Fn closure
laruh Jan 30, 2025
0136ea9
delete validate_payment_state fnc
laruh Jan 30, 2025
39b8471
integrate call_validate_token_with_retry with validator closure to se…
laruh Jan 31, 2025
70a1c53
delete PaymentStatusErr and reuse PrepareTxDataError instead
laruh Jan 31, 2025
f676b9c
use constants
laruh Jan 31, 2025
415c24a
return String err from validator, instead of Web3RpcError
laruh Feb 2, 2025
dea6b74
review: use Function in params
laruh Feb 3, 2025
4245767
review: make consts local
laruh Feb 5, 2025
b406a0b
review: update Latest block comment
laruh Feb 5, 2025
1e87e34
review: make call_contract_function private
laruh Feb 5, 2025
358b3c1
review: return payment_status_v2
laruh Feb 17, 2025
77ebb20
Retry Call was removed
laruh Feb 17, 2025
7c29065
make payment_status_v2 private fnc
laruh Feb 17, 2025
dfa01f0
review: update comment and avoid unnecessary variable creation
laruh Feb 19, 2025
7c4fb52
Merge remote-tracking branch 'origin/dev' into fix-tpu-v2-wait-for-pa…
shamardy Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions mm2src/coins/coin_errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::eth::eth_swap_v2::{PaymentStatusErr, PrepareTxDataError, ValidatePaymentV2Err};
use crate::eth::eth_swap_v2::{PrepareTxDataError, ValidatePaymentV2Err};
use crate::eth::nft_swap_v2::errors::{Erc721FunctionError, HtlcParamsError};
use crate::eth::{EthAssocTypesError, EthNftAssocTypesError, Web3RpcError};
use crate::{utxo::rpc_clients::UtxoRpcError, NumConversError, UnexpectedDerivationMethod};
Expand Down Expand Up @@ -85,16 +85,6 @@ impl From<Web3RpcError> for ValidatePaymentError {
}
}

impl From<PaymentStatusErr> for ValidatePaymentError {
fn from(err: PaymentStatusErr) -> Self {
match err {
PaymentStatusErr::Transport(e) => Self::Transport(e),
PaymentStatusErr::ABIError(e) | PaymentStatusErr::Internal(e) => Self::InternalError(e),
PaymentStatusErr::InvalidData(e) => Self::InvalidData(e),
}
}
}

impl From<HtlcParamsError> for ValidatePaymentError {
fn from(err: HtlcParamsError) -> Self {
match err {
Expand All @@ -107,7 +97,6 @@ impl From<HtlcParamsError> for ValidatePaymentError {
impl From<ValidatePaymentV2Err> for ValidatePaymentError {
fn from(err: ValidatePaymentV2Err) -> Self {
match err {
ValidatePaymentV2Err::UnexpectedPaymentState(e) => ValidatePaymentError::UnexpectedPaymentState(e),
ValidatePaymentV2Err::WrongPaymentTx(e) => ValidatePaymentError::WrongPaymentTx(e),
}
}
Expand Down
34 changes: 4 additions & 30 deletions mm2src/coins/eth/eth_swap_v2/eth_maker_swap_v2.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use super::{validate_amount, validate_from_to_and_status, EthPaymentType, PaymentMethod, PrepareTxDataError,
ZERO_VALUE};
use super::{validate_amount, validate_from_to_addresses, EthPaymentType, PaymentMethod, PrepareTxDataError, ZERO_VALUE};
use crate::coin_errors::{ValidatePaymentError, ValidatePaymentResult};
use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType,
MakerPaymentStateV2, SignedEthTx, MAKER_SWAP_V2};
SignedEthTx, MAKER_SWAP_V2};
use crate::{ParseCoinAssocTypes, RefundMakerPaymentSecretArgs, RefundMakerPaymentTimelockArgs, SendMakerPaymentArgs,
SpendMakerPaymentArgs, SwapTxTypeWithSecretHash, TransactionErr, ValidateMakerPaymentArgs};
use ethabi::{Function, Token};
Expand All @@ -13,20 +12,11 @@ use futures::compat::Future01CompatExt;
use mm2_err_handle::mm_error::MmError;
use mm2_err_handle::prelude::MapToMmResult;
use std::convert::TryInto;
use web3::types::{BlockNumber, TransactionId};
use web3::types::TransactionId;

const ETH_MAKER_PAYMENT: &str = "ethMakerPayment";
const ERC20_MAKER_PAYMENT: &str = "erc20MakerPayment";

/// state index for `MakerPayment` structure from `EtomicSwapMakerV2.sol`
///
/// struct MakerPayment {
/// bytes20 paymentHash;
/// uint32 paymentLockTime;
/// MakerPaymentState state;
/// }
const MAKER_PAYMENT_STATE_INDEX: usize = 2;

struct MakerPaymentArgs<'a> {
taker_address: Address,
taker_secret_hash: &'a [u8; 32],
Expand Down Expand Up @@ -136,16 +126,6 @@ impl EthCoin {
let maker_secret_hash = args.maker_secret_hash.try_into()?;
validate_amount(&args.amount).map_to_mm(ValidatePaymentError::InternalError)?;
let swap_id = self.etomic_swap_id_v2(args.time_lock, args.maker_secret_hash);
let maker_status = self
.payment_status_v2(
maker_swap_v2_contract,
Token::FixedBytes(swap_id.clone()),
&MAKER_SWAP_V2,
EthPaymentType::MakerPayments,
MAKER_PAYMENT_STATE_INDEX,
BlockNumber::Latest,
)
.await?;

let tx_from_rpc = self
.transaction(TransactionId::Hash(args.maker_payment_tx.tx_hash()))
Expand All @@ -157,13 +137,7 @@ impl EthCoin {
))
})?;
let maker_address = public_to_address(args.maker_pub);
validate_from_to_and_status(
tx_from_rpc,
maker_address,
maker_swap_v2_contract,
maker_status,
MakerPaymentStateV2::PaymentSent as u8,
)?;
validate_from_to_addresses(tx_from_rpc, maker_address, maker_swap_v2_contract)?;

let validation_args = {
let amount = wei_from_big_decimal(&args.amount, self.decimals)?;
Expand Down
110 changes: 60 additions & 50 deletions mm2src/coins/eth/eth_swap_v2/eth_taker_swap_v2.rs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but I noticed that the functions here are related to taker payments not taker swap, e.g. sign_and_broadcast_taker_payment_spend_impl is called by maker. Maybe we should call this file eth_taker_payments_v2 and rename eth_maker_swap_v2 to eth_maker_payment_v2 as well. You should do it in a new PR.

Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use super::{check_decoded_length, extract_id_from_tx_data, validate_amount, validate_from_to_and_status,
validate_payment_state, EthPaymentType, PaymentMethod, PrepareTxDataError, SpendTxSearchParams, ZERO_VALUE};
use super::{check_decoded_length, extract_id_from_tx_data, validate_amount, validate_from_to_addresses,
EthPaymentType, PaymentMethod, PrepareTxDataError, SpendTxSearchParams, ZERO_VALUE};
use crate::eth::{decode_contract_call, get_function_input_data, wei_from_big_decimal, EthCoin, EthCoinType,
ParseCoinAssocTypes, RefundFundingSecretArgs, RefundTakerPaymentArgs, SendTakerFundingArgs,
SignedEthTx, SwapTxTypeWithSecretHash, TakerPaymentStateV2, TransactionErr, ValidateSwapV2TxError,
ValidateSwapV2TxResult, ValidateTakerFundingArgs, TAKER_SWAP_V2};
use crate::{FindPaymentSpendError, FundingTxSpend, GenTakerFundingSpendArgs, GenTakerPaymentSpendArgs,
SearchForFundingSpendErr};
use ethabi::{Function, Token};
use enum_derives::EnumFromStringify;
use ethabi::{Contract, Function, Token};
use ethcore_transaction::Action;
use ethereum_types::{Address, Public, U256};
use ethkey::public_to_address;
Expand Down Expand Up @@ -156,16 +157,6 @@ impl EthCoin {
let maker_secret_hash = args.maker_secret_hash.try_into()?;
validate_amount(&args.trading_amount).map_err(ValidateSwapV2TxError::Internal)?;
let swap_id = self.etomic_swap_id_v2(args.payment_time_lock, args.maker_secret_hash);
let taker_status = self
.payment_status_v2(
taker_swap_v2_contract,
Token::FixedBytes(swap_id.clone()),
&TAKER_SWAP_V2,
EthPaymentType::TakerPayments,
TAKER_PAYMENT_STATE_INDEX,
BlockNumber::Latest,
)
.await?;

let tx_from_rpc = self.transaction(TransactionId::Hash(args.funding_tx.tx_hash())).await?;
let tx_from_rpc = tx_from_rpc.as_ref().ok_or_else(|| {
Expand All @@ -175,13 +166,7 @@ impl EthCoin {
))
})?;
let taker_address = public_to_address(args.taker_pub);
validate_from_to_and_status(
tx_from_rpc,
taker_address,
taker_swap_v2_contract,
taker_status,
TakerPaymentStateV2::PaymentSent as u8,
)?;
validate_from_to_addresses(tx_from_rpc, taker_address, taker_swap_v2_contract)?;

let validation_args = {
let dex_fee = wei_from_big_decimal(&args.dex_fee.fee_amount().into(), self.decimals)?;
Expand Down Expand Up @@ -235,19 +220,6 @@ impl EthCoin {
.taker_swap_v2_details(ETH_TAKER_PAYMENT, ERC20_TAKER_PAYMENT)
.await?;
let decoded = try_tx_s!(decode_contract_call(send_func, args.funding_tx.unsigned().data()));
let taker_status = try_tx_s!(
self.payment_status_v2(
taker_swap_v2_contract,
decoded[0].clone(),
&TAKER_SWAP_V2,
EthPaymentType::TakerPayments,
TAKER_PAYMENT_STATE_INDEX,
BlockNumber::Latest,
)
.await
);
validate_payment_state(args.funding_tx, taker_status, TakerPaymentStateV2::PaymentSent as u8)
.map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?;
let data = try_tx_s!(
self.prepare_taker_payment_approve_data(args, decoded, token_address)
.await
Expand Down Expand Up @@ -399,6 +371,9 @@ impl EthCoin {
&TAKER_SWAP_V2,
EthPaymentType::TakerPayments,
TAKER_PAYMENT_STATE_INDEX,
// Use the latest confirmed block to ensure smart contract has the correct taker payment state (`TakerPaymentStateV2::TakerApproved`)
// before the maker sends the spend transaction, which reveals the maker's secret.
// TPU state machine waits confirmations only for send payment tx, not approve tx.
BlockNumber::Latest,
)
.await
Expand All @@ -425,23 +400,6 @@ impl EthCoin {
.taker_swap_v2_details(ETH_TAKER_PAYMENT, ERC20_TAKER_PAYMENT)
.await?;
let decoded = try_tx_s!(decode_contract_call(taker_payment, gen_args.taker_tx.unsigned().data()));
let taker_status = try_tx_s!(
self.payment_status_v2(
taker_swap_v2_contract,
decoded[0].clone(),
&TAKER_SWAP_V2,
EthPaymentType::TakerPayments,
TAKER_PAYMENT_STATE_INDEX,
BlockNumber::Latest
)
.await
);
validate_payment_state(
gen_args.taker_tx,
taker_status,
TakerPaymentStateV2::TakerApproved as u8,
)
.map_err(|e| TransactionErr::Plain(ERRL!("{}", e)))?;
let data = try_tx_s!(
self.prepare_spend_taker_payment_data(gen_args, secret, decoded, token_address)
.await
Expand Down Expand Up @@ -743,6 +701,58 @@ impl EthCoin {
),
}
}

/// Retrieves the payment status from a given smart contract address based on the swap ID and state type.
async fn payment_status_v2(
&self,
swap_address: Address,
swap_id: Token,
contract_abi: &Contract,
payment_type: EthPaymentType,
state_index: usize,
block_number: BlockNumber,
) -> Result<U256, PaymentStatusErr> {
let function = contract_abi.function(payment_type.as_str())?;
let data = function.encode_input(&[swap_id])?;
let bytes = self
.call_request(
self.my_addr().await,
swap_address,
None,
Some(data.into()),
block_number,
)
.await?;
let decoded_tokens = function.decode_output(&bytes.0)?;

let state = decoded_tokens.get(state_index).ok_or_else(|| {
PaymentStatusErr::Internal(format!(
"Payment status must contain 'state' as the {} token",
state_index
))
})?;
match state {
Token::Uint(state) => Ok(*state),
_ => Err(PaymentStatusErr::InvalidData(format!(
"Payment status must be Uint, got {:?}",
state
))),
}
}
}

#[derive(Debug, Display, EnumFromStringify)]
enum PaymentStatusErr {
#[from_stringify("ethabi::Error")]
#[display(fmt = "ABI error: {}", _0)]
ABIError(String),
#[from_stringify("web3::Error")]
#[display(fmt = "Transport error: {}", _0)]
Transport(String),
#[display(fmt = "Internal error: {}", _0)]
Internal(String),
#[display(fmt = "Invalid data error: {}", _0)]
InvalidData(String),
}

/// Validation function for ETH taker payment data
Expand Down
83 changes: 4 additions & 79 deletions mm2src/coins/eth/eth_swap_v2/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::eth::{decode_contract_call, signed_tx_from_web3_tx, EthCoin, EthCoinType, ParseCoinAssocTypes, Transaction,
TransactionErr};
use crate::eth::{decode_contract_call, signed_tx_from_web3_tx, EthCoin, EthCoinType, Transaction, TransactionErr};
use crate::{FindPaymentSpendError, MarketCoinOps};
use common::executor::Timer;
use common::log::{error, info};
Expand All @@ -12,7 +11,7 @@ use futures::compat::Future01CompatExt;
use mm2_err_handle::prelude::{MmError, MmResult};
use mm2_number::BigDecimal;
use num_traits::Signed;
use web3::types::{BlockNumber, Transaction as Web3Tx, TransactionId};
use web3::types::{Transaction as Web3Tx, TransactionId};

pub(crate) mod eth_maker_swap_v2;
pub(crate) mod eth_taker_swap_v2;
Expand Down Expand Up @@ -46,33 +45,20 @@ pub enum PaymentMethod {

#[derive(Debug, Display)]
pub(crate) enum ValidatePaymentV2Err {
UnexpectedPaymentState(String),
WrongPaymentTx(String),
}

#[derive(Debug, Display, EnumFromStringify)]
pub(crate) enum PaymentStatusErr {
pub(crate) enum PrepareTxDataError {
#[from_stringify("ethabi::Error")]
#[display(fmt = "ABI error: {}", _0)]
ABIError(String),
#[from_stringify("web3::Error")]
#[display(fmt = "Transport error: {}", _0)]
Transport(String),
#[display(fmt = "Internal error: {}", _0)]
Internal(String),
#[display(fmt = "Invalid data error: {}", _0)]
InvalidData(String),
}

#[derive(Debug, Display, EnumFromStringify)]
pub(crate) enum PrepareTxDataError {
#[from_stringify("ethabi::Error")]
#[display(fmt = "ABI error: {}", _0)]
ABIError(String),
#[display(fmt = "Internal error: {}", _0)]
Internal(String),
}

pub(crate) struct SpendTxSearchParams<'a> {
pub(crate) swap_contract_address: Address,
pub(crate) event_name: &'a str,
Expand All @@ -84,45 +70,6 @@ pub(crate) struct SpendTxSearchParams<'a> {
}

impl EthCoin {
/// Retrieves the payment status from a given smart contract address based on the swap ID and state type.
pub(crate) async fn payment_status_v2(
&self,
swap_address: Address,
swap_id: Token,
contract_abi: &Contract,
payment_type: EthPaymentType,
state_index: usize,
block_number: BlockNumber,
) -> Result<U256, PaymentStatusErr> {
let function_name = payment_type.as_str();
let function = contract_abi.function(function_name)?;
let data = function.encode_input(&[swap_id])?;
let bytes = self
.call_request(
self.my_addr().await,
swap_address,
None,
Some(data.into()),
block_number,
)
.await?;
let decoded_tokens = function.decode_output(&bytes.0)?;

let state = decoded_tokens.get(state_index).ok_or_else(|| {
PaymentStatusErr::Internal(format!(
"Payment status must contain 'state' as the {} token",
state_index
))
})?;
match state {
Token::Uint(state) => Ok(*state),
_ => Err(PaymentStatusErr::InvalidData(format!(
"Payment status must be Uint, got {:?}",
state
))),
}
}

pub(super) fn get_token_address(&self) -> Result<Address, String> {
match &self.coin_type {
EthCoinType::Eth => Ok(Address::default()),
Expand Down Expand Up @@ -226,33 +173,11 @@ impl EthCoin {
}
}

pub(crate) fn validate_payment_state(
tx: &SignedEthTx,
state: U256,
expected_state: u8,
) -> Result<(), PrepareTxDataError> {
if state != U256::from(expected_state) {
return Err(PrepareTxDataError::Internal(format!(
"Payment {:?} state is not `{}`, got `{}`",
tx, expected_state, state
)));
}
Ok(())
}

pub(crate) fn validate_from_to_and_status(
pub(crate) fn validate_from_to_addresses(
tx_from_rpc: &Web3Tx,
expected_from: Address,
expected_to: Address,
status: U256,
expected_status: u8,
) -> Result<(), MmError<ValidatePaymentV2Err>> {
if status != U256::from(expected_status) {
return MmError::err(ValidatePaymentV2Err::UnexpectedPaymentState(format!(
"tx {:?} Payment state is not `PaymentSent`, got {}",
tx_from_rpc.hash, status
)));
}
if tx_from_rpc.from != Some(expected_from) {
return MmError::err(ValidatePaymentV2Err::WrongPaymentTx(format!(
"Payment tx {:?} was sent from wrong address, expected {:?}",
Expand Down
Loading