Skip to content

Commit

Permalink
Few additions to dex fee validation (#827)
Browse files Browse the repository at this point in the history
* WIP.

* WIP.

* Ready for PR.

* Remove use is_tx_confirmed_before_block.
  • Loading branch information
artemii235 committed Feb 19, 2021
1 parent a96e698 commit 0457a74
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 15 deletions.
20 changes: 20 additions & 0 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,14 +641,17 @@ impl SwapOps for EthCoin {
fn validate_fee(
&self,
fee_tx: &TransactionEnum,
expected_sender: &[u8],
fee_addr: &[u8],
amount: &BigDecimal,
min_block_number: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
let selfi = self.clone();
let tx = match fee_tx {
TransactionEnum::SignedEthTx(t) => t.clone(),
_ => panic!(),
};
let sender_addr = try_fus!(addr_from_raw_pubkey(expected_sender));
let fee_addr = try_fus!(addr_from_raw_pubkey(fee_addr));
let amount = amount.clone();

Expand All @@ -667,6 +670,23 @@ impl SwapOps for EthCoin {
None => return ERR!("Didn't find provided tx {:?} on ETH node", tx),
};

if tx_from_rpc.from != 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) {
Expand Down
109 changes: 109 additions & 0 deletions mm2src/coins/eth/eth_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,3 +934,112 @@ fn test_get_fee_to_send_taker_fee_insufficient_balance() {
"Expected TradePreimageError::NotSufficientBalance"
);
}

#[test]
fn validate_dex_fee_invalid_sender_eth() {
let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, vec!["http://eth1.cipig.net:8555".into()]);
// the real dex fee sent on mainnet
// https://etherscan.io/tx/0x7e9ca16c85efd04ee5e31f2c1914b48f5606d6f9ce96ecce8c96d47d6857278f
let tx = coin
.web3
.eth()
.transaction(TransactionId::Hash(
"0x7e9ca16c85efd04ee5e31f2c1914b48f5606d6f9ce96ecce8c96d47d6857278f".into(),
))
.wait()
.unwrap()
.unwrap();
let tx = signed_tx_from_web3_tx(tx).unwrap().into();
let amount: BigDecimal = "0.000526435076465".parse().unwrap();
let validate_err = coin
.validate_fee(&tx, &*DEX_FEE_ADDR_RAW_PUBKEY, &*DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait()
.unwrap_err();
assert!(validate_err.contains("was sent from wrong address"));
}

#[test]
fn validate_dex_fee_invalid_sender_erc() {
let (_ctx, coin) = eth_coin_for_test(
EthCoinType::Erc20("0xa1d6df714f91debf4e0802a542e13067f31b8262".into()),
vec!["http://eth1.cipig.net:8555".into()],
);
// the real dex fee sent on mainnet
// https://etherscan.io/tx/0xd6403b41c79f9c9e9c83c03d920ee1735e7854d85d94cef48d95dfeca95cd600
let tx = coin
.web3
.eth()
.transaction(TransactionId::Hash(
"0xd6403b41c79f9c9e9c83c03d920ee1735e7854d85d94cef48d95dfeca95cd600".into(),
))
.wait()
.unwrap()
.unwrap();
let tx = signed_tx_from_web3_tx(tx).unwrap().into();
let amount: BigDecimal = "5.548262548262548262".parse().unwrap();
let validate_err = coin
.validate_fee(&tx, &*DEX_FEE_ADDR_RAW_PUBKEY, &*DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait()
.unwrap_err();
assert!(validate_err.contains("was sent from wrong address"));
}

fn sender_compressed_pub(tx: &SignedEthTx) -> [u8; 33] {
let raw_pubkey = tx.public.unwrap();
let secp_public = PublicKey::parse_slice(&raw_pubkey, None).unwrap();
secp_public.serialize_compressed()
}

#[test]
fn validate_dex_fee_eth_confirmed_before_min_block() {
let (_ctx, coin) = eth_coin_for_test(EthCoinType::Eth, vec!["http://eth1.cipig.net:8555".into()]);
// the real dex fee sent on mainnet
// https://etherscan.io/tx/0x7e9ca16c85efd04ee5e31f2c1914b48f5606d6f9ce96ecce8c96d47d6857278f
let tx = coin
.web3
.eth()
.transaction(TransactionId::Hash(
"0x7e9ca16c85efd04ee5e31f2c1914b48f5606d6f9ce96ecce8c96d47d6857278f".into(),
))
.wait()
.unwrap()
.unwrap();
let tx = signed_tx_from_web3_tx(tx).unwrap();
let compressed_public = sender_compressed_pub(&tx);
let tx = tx.into();
let amount: BigDecimal = "0.000526435076465".parse().unwrap();
let validate_err = coin
.validate_fee(&tx, &compressed_public, &*DEX_FEE_ADDR_RAW_PUBKEY, &amount, 11784793)
.wait()
.unwrap_err();
assert!(validate_err.contains("confirmed before min_block"));
}

#[test]
fn validate_dex_fee_erc_confirmed_before_min_block() {
let (_ctx, coin) = eth_coin_for_test(
EthCoinType::Erc20("0xa1d6df714f91debf4e0802a542e13067f31b8262".into()),
vec!["http://eth1.cipig.net:8555".into()],
);
// the real dex fee sent on mainnet
// https://etherscan.io/tx/0xd6403b41c79f9c9e9c83c03d920ee1735e7854d85d94cef48d95dfeca95cd600
let tx = coin
.web3
.eth()
.transaction(TransactionId::Hash(
"0xd6403b41c79f9c9e9c83c03d920ee1735e7854d85d94cef48d95dfeca95cd600".into(),
))
.wait()
.unwrap()
.unwrap();

let tx = signed_tx_from_web3_tx(tx).unwrap();
let compressed_public = sender_compressed_pub(&tx);
let tx = tx.into();
let amount: BigDecimal = "5.548262548262548262".parse().unwrap();
let validate_err = coin
.validate_fee(&tx, &compressed_public, &*DEX_FEE_ADDR_RAW_PUBKEY, &amount, 11823975)
.wait()
.unwrap_err();
assert!(validate_err.contains("confirmed before min_block"));
}
2 changes: 2 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ pub trait SwapOps {
fn validate_fee(
&self,
fee_tx: &TransactionEnum,
expected_sender: &[u8],
fee_addr: &[u8],
amount: &BigDecimal,
min_block_number: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send>;

fn validate_maker_payment(
Expand Down
18 changes: 14 additions & 4 deletions mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::qrc20::rpc_clients::{LogEntry, Qrc20ElectrumOps, Qrc20NativeOps, Qrc2
ViewContractCallType};
use crate::utxo::qtum::QtumBasedCoin;
use crate::utxo::rpc_clients::{ElectrumClient, NativeClient, UnspentInfo, UtxoRpcClientEnum, UtxoRpcClientOps};
use crate::utxo::utxo_common::{self, big_decimal_from_sat};
use crate::utxo::utxo_common::{self, big_decimal_from_sat, check_all_inputs_signed_by_pub};
use crate::utxo::{coin_daemon_data_dir, qtum, sign_tx, ActualTxFee, AdditionalTxData, FeePolicy,
GenerateTransactionError, RecentlySpentOutPoints, UtxoCoinBuilder, UtxoCoinFields, UtxoCommonOps,
UtxoTx, VerboseTransactionFrom, UTXO_LOCK};
Expand Down Expand Up @@ -684,18 +684,28 @@ impl SwapOps for Qrc20Coin {
fn validate_fee(
&self,
fee_tx: &TransactionEnum,
expected_sender: &[u8],
fee_addr: &[u8],
amount: &BigDecimal,
min_block_number: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
let fee_tx_hash: H256Json = match fee_tx {
TransactionEnum::UtxoTx(tx) => tx.hash().reversed().into(),
let fee_tx = match fee_tx {
TransactionEnum::UtxoTx(tx) => tx,
_ => panic!("Unexpected TransactionEnum"),
};
let fee_tx_hash = fee_tx.hash().reversed().into();
if !try_fus!(check_all_inputs_signed_by_pub(&fee_tx, expected_sender)) {
return Box::new(futures01::future::err(ERRL!("The dex fee was sent from wrong address")));
}
let fee_addr = try_fus!(self.contract_address_from_raw_pubkey(fee_addr));
let expected_value = try_fus!(wei_from_big_decimal(amount, self.utxo.decimals));

let selfi = self.clone();
let fut = async move { selfi.validate_fee_impl(fee_tx_hash, fee_addr, expected_value).await };
let fut = async move {
selfi
.validate_fee_impl(fee_tx_hash, fee_addr, expected_value, min_block_number)
.await
};
Box::new(fut.boxed().compat())
}

Expand Down
32 changes: 27 additions & 5 deletions mm2src/coins/qrc20/qrc20_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ fn test_send_taker_fee() {
};
log!("Fee tx "[tx_hash]);

let result = coin.validate_fee(&tx, &DEX_FEE_ADDR_RAW_PUBKEY, &amount).wait();
let result = coin
.validate_fee(&tx, &*coin.utxo.key_pair.public(), &DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait();
assert_eq!(result, Ok(()));
}

Expand All @@ -262,24 +264,43 @@ fn test_validate_fee() {

// QRC20 transfer tx "f97d3a43dbea0993f1b7a6a299377d4ee164c84935a1eb7d835f70c9429e6a1d"
let tx = TransactionEnum::UtxoTx("010000000160fd74b5714172f285db2b36f0b391cd6883e7291441631c8b18f165b0a4635d020000006a47304402205d409e141111adbc4f185ae856997730de935ac30a0d2b1ccb5a6c4903db8171022024fc59bbcfdbba283556d7eeee4832167301dc8e8ad9739b7865f67b9676b226012103693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9ffffffff020000000000000000625403a08601012844a9059cbb000000000000000000000000ca1e04745e8ca0c60d8c5881531d51bec470743f00000000000000000000000000000000000000000000000000000000000f424014d362e096e873eb7907e205fadc6175c6fec7bc44c200ada205000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acfe967d5f".into());
let sender_pub = hex::decode("03693bff1b39e8b5a306810023c29b95397eb395530b106b1820ea235fd81d9ce9").unwrap();

let amount = BigDecimal::from_str("0.01").unwrap();

let result = coin.validate_fee(&tx, &DEX_FEE_ADDR_RAW_PUBKEY, &amount).wait();
let result = coin
.validate_fee(&tx, &sender_pub, &DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait();
assert_eq!(result, Ok(()));

let fee_addr_dif = hex::decode("03bc2c7ba671bae4a6fc835244c9762b41647b9827d4780a89a949b984a8ddcc05").unwrap();
let err = coin
.validate_fee(&tx, &fee_addr_dif, &amount)
.validate_fee(&tx, &sender_pub, &fee_addr_dif, &amount, 0)
.wait()
.err()
.expect("Expected an error");
log!("error: "[err]);
assert!(err.contains("QRC20 Fee tx was sent to wrong address"));

let err = coin
.validate_fee(&tx, &DEX_FEE_ADDR_RAW_PUBKEY, &DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait()
.err()
.expect("Expected an error");
log!("error: "[err]);
assert!(err.contains("was sent from wrong address"));

let err = coin
.validate_fee(&tx, &sender_pub, &DEX_FEE_ADDR_RAW_PUBKEY, &amount, 2000000)
.wait()
.err()
.expect("Expected an error");
log!("error: "[err]);
assert!(err.contains("confirmed before min_block"));

let amount_dif = BigDecimal::from_str("0.02").unwrap();
let err = coin
.validate_fee(&tx, &DEX_FEE_ADDR_RAW_PUBKEY, &amount_dif)
.validate_fee(&tx, &sender_pub, &DEX_FEE_ADDR_RAW_PUBKEY, &amount_dif, 0)
.wait()
.err()
.expect("Expected an error");
Expand All @@ -288,8 +309,9 @@ fn test_validate_fee() {

// QTUM tx "8a51f0ffd45f34974de50f07c5bf2f0949da4e88433f8f75191953a442cf9310"
let tx = TransactionEnum::UtxoTx("020000000113640281c9332caeddd02a8dd0d784809e1ad87bda3c972d89d5ae41f5494b85010000006a47304402207c5c904a93310b8672f4ecdbab356b65dd869a426e92f1064a567be7ccfc61ff02203e4173b9467127f7de4682513a21efb5980e66dbed4da91dff46534b8e77c7ef012102baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1afeffffff020001b2c4000000001976a9149e032d4b0090a11dc40fe6c47601499a35d55fbb88acbc4dd20c2f0000001976a9144208fa7be80dcf972f767194ad365950495064a488ac76e70800".into());
let sender_pub = hex::decode("02baefe72b3591de2070c0da3853226b00f082d72daa417688b61cb18c1d543d1a").unwrap();
let err = coin
.validate_fee(&tx, &DEX_FEE_ADDR_RAW_PUBKEY, &amount)
.validate_fee(&tx, &sender_pub, &DEX_FEE_ADDR_RAW_PUBKEY, &amount, 0)
.wait()
.err()
.expect("Expected an error");
Expand Down
9 changes: 9 additions & 0 deletions mm2src/coins/qrc20/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,17 @@ impl Qrc20Coin {
fee_tx_hash: H256Json,
fee_addr: H160,
expected_value: U256,
min_block_number: u64,
) -> Result<(), String> {
let verbose_tx = try_s!(self.utxo.rpc_client.get_verbose_transaction(fee_tx_hash).compat().await);
let conf_before_block = utxo_common::is_tx_confirmed_before_block(self, &verbose_tx, min_block_number);
if try_s!(conf_before_block.await) {
return ERR!(
"Fee tx {:?} confirmed before min_block {}",
verbose_tx,
min_block_number,
);
}
let qtum_tx: UtxoTx = try_s!(deserialize(verbose_tx.hex.as_slice()).map_err(|e| ERRL!("{:?}", e)));

// The transaction could not being mined, just check the transfer tokens.
Expand Down
2 changes: 2 additions & 0 deletions mm2src/coins/test_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,10 @@ impl SwapOps for TestCoin {
fn validate_fee(
&self,
fee_tx: &TransactionEnum,
expected_sender: &[u8],
fee_addr: &[u8],
amount: &BigDecimal,
min_block_number: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
unimplemented!()
}
Expand Down
11 changes: 10 additions & 1 deletion mm2src/coins/utxo/qtum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,19 @@ impl SwapOps for QtumCoin {
fn validate_fee(
&self,
fee_tx: &TransactionEnum,
expected_sender: &[u8],
fee_addr: &[u8],
amount: &BigDecimal,
min_block_number: u64,
) -> Box<dyn Future<Item = (), Error = String> + Send> {
utxo_common::validate_fee(self.clone(), fee_tx, fee_addr, amount)
utxo_common::validate_fee(
self.clone(),
fee_tx,
fee_addr,
expected_sender,
amount,
min_block_number,
)
}

fn validate_maker_payment(
Expand Down
Loading

0 comments on commit 0457a74

Please sign in to comment.