Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/mm2.1' into mm2.1-getblock-hash-…
Browse files Browse the repository at this point in the history
…only

# Conflicts:
#	mm2src/coins/utxo/utxo_tests.rs
  • Loading branch information
artemii235 committed Feb 23, 2021
2 parents 73dc3fc + d5bb73f commit 84b93fe
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 21 deletions.
4 changes: 3 additions & 1 deletion mm2src/coins/qrc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,9 @@ impl Qrc20Coin {
/// or should be sum of gas fee of all contract calls.
pub async fn get_qrc20_tx_fee(&self, gas_fee: u64) -> Result<u64, String> {
match try_s!(self.get_tx_fee().await) {
ActualTxFee::Fixed(amount) | ActualTxFee::Dynamic(amount) => Ok(amount + gas_fee),
ActualTxFee::Fixed(amount) | ActualTxFee::Dynamic(amount) | ActualTxFee::FixedPerKb(amount) => {
Ok(amount + gas_fee)
},
}
}

Expand Down
10 changes: 10 additions & 0 deletions mm2src/coins/utxo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub enum TxFee {
Fixed(u64),
/// Tell the coin that it should request the fee from daemon RPC and calculate it relying on tx size
Dynamic(EstimateFeeMethod),
FixedPerKb(u64),
}

/// The actual "runtime" fee that is received from RPC in case of dynamic calculation
Expand All @@ -149,6 +150,9 @@ pub enum ActualTxFee {
Fixed(u64),
/// fee amount per Kbyte received from coin RPC
Dynamic(u64),
/// Use specified amount per each 1 kb of transaction and also per each output less than amount.
/// Used by DOGE, but more coins might support it too.
FixedPerKb(u64),
}

/// Fee policy applied on transaction creation
Expand Down Expand Up @@ -1025,6 +1029,12 @@ pub trait UtxoCoinBuilder {
}

async fn tx_fee(&self, rpc_client: &UtxoRpcClientEnum) -> Result<TxFee, String> {
const ONE_DOGE: u64 = 100000000;

if self.ticker() == "DOGE" {
return Ok(TxFee::FixedPerKb(ONE_DOGE));
}

let tx_fee = match self.conf()["txfee"].as_u64() {
None => TxFee::Fixed(1000),
Some(0) => {
Expand Down
83 changes: 63 additions & 20 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ pub async fn get_tx_fee(coin: &UtxoCoinFields) -> Result<ActualTxFee, JsonRpcErr
.await?;
Ok(ActualTxFee::Dynamic(fee))
},
TxFee::FixedPerKb(satoshis) => Ok(ActualTxFee::FixedPerKb(*satoshis)),
}
}

Expand All @@ -164,6 +165,8 @@ where
ActualTxFee::Fixed(fee) => fee,
// atomic swap payment spend transaction is slightly more than 300 bytes in average as of now
ActualTxFee::Dynamic(fee_per_kb) => (fee_per_kb * SWAP_TX_SPEND_SIZE) / KILO_BYTE,
// return satoshis here as swap spend transaction size is always less than 1 kb
ActualTxFee::FixedPerKb(satoshis) => satoshis,
};
if coin.as_ref().conf.force_min_relay_fee {
let relay_fee = try_s!(coin.as_ref().rpc_client.get_relay_fee().compat().await);
Expand Down Expand Up @@ -373,6 +376,20 @@ where
let tx_size = transaction_bytes.len() + transaction.inputs().len() * additional_len;
(f * tx_size as u64) / KILO_BYTE
},
ActualTxFee::FixedPerKb(f) => {
let transaction = UtxoTx::from(tx.clone());
let transaction_bytes = serialize(&transaction);
// 2 bytes are used to indicate the length of signature and pubkey
// total is 107
let additional_len = 2 + MAX_DER_SIGNATURE_LEN + COMPRESSED_PUBKEY_LEN;
let tx_size_bytes = (transaction_bytes.len() + transaction.inputs().len() * additional_len) as u64;
let tx_size_kb = if tx_size_bytes % KILO_BYTE == 0 {
tx_size_bytes / KILO_BYTE
} else {
tx_size_bytes / KILO_BYTE + 1
};
f * tx_size_kb
},
};

match fee_policy {
Expand Down Expand Up @@ -1823,6 +1840,7 @@ where
let amount = match fee {
ActualTxFee::Fixed(f) => f,
ActualTxFee::Dynamic(f) => f,
ActualTxFee::FixedPerKb(f) => f,
};
Ok(TradeFee {
coin: ticker,
Expand Down Expand Up @@ -1856,36 +1874,61 @@ where
{
let decimals = coin.as_ref().decimals;
let tx_fee = try_map!(coin.get_tx_fee().await, TradePreimageError::Other);
let dynamic_fee = match tx_fee {
match tx_fee {
ActualTxFee::Fixed(fee_amount) => {
let amount = big_decimal_from_sat(fee_amount as i64, decimals);
return Ok(amount);
},
// if it's a dynamic fee, we should generate a swap transaction to get an actual trade fee
ActualTxFee::Dynamic(fee) => fee,
};
ActualTxFee::Dynamic(fee) => {
// take into account that the dynamic tx fee may increase during the swap
let dynamic_fee = coin.increase_dynamic_fee_by_stage(fee, stage);

let outputs_count = outputs.len();
let (unspents, _recently_sent_txs) = try_map!(
coin.list_unspent_ordered(&coin.as_ref().my_address).await,
TradePreimageError::Other
);

// take into account that the dynamic tx fee may increase during the swap
let dynamic_fee = coin.increase_dynamic_fee_by_stage(dynamic_fee, stage);
let actual_tx_fee = Some(ActualTxFee::Dynamic(dynamic_fee));
let (tx, data) = generate_transaction(coin, unspents, outputs, fee_policy, actual_tx_fee, gas_fee).await?;

let outputs_count = outputs.len();
let (unspents, _recently_sent_txs) = try_map!(
coin.list_unspent_ordered(&coin.as_ref().my_address).await,
TradePreimageError::Other
);
let total_fee = if tx.outputs.len() == outputs_count {
// take into account the change output
data.fee_amount + (dynamic_fee * P2PKH_OUTPUT_LEN) / KILO_BYTE
} else {
// the change output is included already
data.fee_amount
};

let actual_tx_fee = Some(ActualTxFee::Dynamic(dynamic_fee));
let (tx, data) = generate_transaction(coin, unspents, outputs, fee_policy, actual_tx_fee, gas_fee).await?;
Ok(big_decimal_from_sat(total_fee as i64, decimals))
},
ActualTxFee::FixedPerKb(fee) => {
let outputs_count = outputs.len();
let (unspents, _recently_sent_txs) = try_map!(
coin.list_unspent_ordered(&coin.as_ref().my_address).await,
TradePreimageError::Other
);

let total_fee = if tx.outputs.len() == outputs_count {
// take into account the change output
data.fee_amount + (dynamic_fee * P2PKH_OUTPUT_LEN) / KILO_BYTE
} else {
// the change outputs is included already
data.fee_amount
};
let (tx, data) = generate_transaction(coin, unspents, outputs, fee_policy, Some(tx_fee), gas_fee).await?;

Ok(big_decimal_from_sat(total_fee as i64, decimals))
let total_fee = if tx.outputs.len() == outputs_count {
// take into account the change output if tx_size_kb(tx with change) > tx_size_kb(tx without change)
let tx = UtxoTx::from(tx);
let tx_bytes = serialize(&tx);
if tx_bytes.len() as u64 % KILO_BYTE + P2PKH_OUTPUT_LEN > KILO_BYTE {
data.fee_amount + fee
} else {
data.fee_amount
}
} else {
// the change output is included already
data.fee_amount
};

Ok(big_decimal_from_sat(total_fee as i64, decimals))
},
}
}

/// Maker or Taker should pay fee only for sending his payment.
Expand Down
84 changes: 84 additions & 0 deletions mm2src/coins/utxo/utxo_tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::rpc_clients::{ElectrumProtocol, ListSinceBlockRes, NetworkInfo};
use super::*;
use crate::utxo::rpc_clients::{GetAddressInfoRes, UtxoRpcClientOps, ValidateAddressRes, VerboseBlock};
use crate::utxo::utxo_common::{generate_transaction, UtxoArcBuilder};
use crate::utxo::utxo_standard::{utxo_standard_coin_from_conf_and_request, UtxoStandardCoin};
use crate::{SwapOps, TradePreimageValue, WithdrawFee};
use bigdecimal::BigDecimal;
Expand Down Expand Up @@ -2214,3 +2215,86 @@ fn firo_verbose_block_deserialize() {
});
let _block: VerboseBlock = json::from_value(json).unwrap();
}

#[test]
fn test_generate_tx_doge_fee() {
// A tx below 1kb is always 1 doge fee yes.
// But keep in mind that every output below 1 doge will incur and extra 1 doge dust fee
let config = json!({
"coin": "DOGE",
"name": "dogecoin",
"fname": "Dogecoin",
"rpcport": 22555,
"pubtype": 30,
"p2shtype": 22,
"wiftype": 158,
"txfee": 0,
"force_min_relay_fee": true,
"dust": 100000000,
"mm2": 1,
"required_confirmations": 2,
"avg_blocktime": 1,
"protocol": {
"type": "UTXO"
}
});
let request = json!({
"method": "electrum",
"coin": "DOGE",
"servers": [{"url": "electrum1.cipig.net:10060"},{"url": "electrum2.cipig.net:10060"},{"url": "electrum3.cipig.net:10060"}],
});
let ctx = MmCtxBuilder::default().into_mm_arc();
let doge: UtxoStandardCoin = block_on(UtxoArcBuilder::new(&ctx, "DOGE", &config, &request, &[1; 32]).build())
.unwrap()
.into();

let unspents = vec![UnspentInfo {
outpoint: Default::default(),
value: 1000000000000,
height: None,
}];
let outputs = vec![TransactionOutput {
value: 100000000,
script_pubkey: vec![0; 26].into(),
}];
let policy = FeePolicy::SendExact;
let (_, data) = block_on(generate_transaction(&doge, unspents, outputs, policy, None, None)).unwrap();
let expected_fee = 100000000;
assert_eq!(expected_fee, data.fee_amount);

let unspents = vec![UnspentInfo {
outpoint: Default::default(),
value: 1000000000000,
height: None,
}];
let outputs = vec![
TransactionOutput {
value: 100000000,
script_pubkey: vec![0; 26].into(),
}
.clone();
40
];
let policy = FeePolicy::SendExact;
let (_, data) = block_on(generate_transaction(&doge, unspents, outputs, policy, None, None)).unwrap();
let expected_fee = 200000000;
assert_eq!(expected_fee, data.fee_amount);

let unspents = vec![UnspentInfo {
outpoint: Default::default(),
value: 1000000000000,
height: None,
}];
let outputs = vec![
TransactionOutput {
value: 100000000,
script_pubkey: vec![0; 26].into(),
}
.clone();
60
];
let policy = FeePolicy::SendExact;
let (_, data) = block_on(generate_transaction(&doge, unspents, outputs, policy, None, None)).unwrap();
let expected_fee = 300000000;
assert_eq!(expected_fee, data.fee_amount);
}

0 comments on commit 84b93fe

Please sign in to comment.