Skip to content

Commit

Permalink
Negotiate the taker payment spend fee
Browse files Browse the repository at this point in the history
Actually should be called funding spend fee or taker payment fee, but we are just following the current code naming at the moment.

The negotiation goes as follows:
1- Taker and maker each decide how much this fee should be at the beginning of the swap.

2- The taker will send the maker their proposed fee during negotiation,
   if the maker deems the fee as low enough (less than 90% of the maker's own calculated fee),
   they will refuse to trade before the trade starts. Otherwise, they
   will continue and use this fee for validation later.

3- The maker will validate that the taker has accounted for this fee in
   the funding transaction.

4- The taker will validate that the taker payment preimage (generated by the maker)
   uses the negotiated fee or greater (greater number will deduct from the maker's trading volume anyway).
   If not, the taker will stop the trade since the maker is clearly stealing funds that isn't his
   which might affect the completion of the swap if the fee is low.
  • Loading branch information
mariocynicys committed May 3, 2024
1 parent 39b9a44 commit 3c888aa
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 29 deletions.
12 changes: 12 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,8 @@ pub struct SendTakerFundingArgs<'a> {
pub maker_pub: &'a [u8],
/// DEX fee
pub dex_fee: &'a DexFee,
/// The extra fee added to the trading volume to cover the taker payment spend transaction fees
pub taker_payment_spend_fee: BigDecimal,
/// Additional reward for maker (premium)
pub premium_amount: BigDecimal,
/// Actual volume of taker's payment
Expand Down Expand Up @@ -1266,6 +1268,8 @@ pub struct GenTakerFundingSpendArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> {
pub taker_payment_time_lock: u64,
/// The hash of the secret generated by maker
pub maker_secret_hash: &'a [u8],
/// The extra fee added to the trading volume to cover the taker payment spend transaction fees
pub taker_payment_spend_fee: BigDecimal,
}

/// Helper struct wrapping arguments for [TakerCoinSwapOpsV2::validate_taker_funding]
Expand All @@ -1280,6 +1284,8 @@ pub struct ValidateTakerFundingArgs<'a, Coin: ParseCoinAssocTypes + ?Sized> {
pub other_pub: &'a Coin::Pubkey,
/// DEX fee amount
pub dex_fee: &'a DexFee,
/// The extra fee added to the trading volume to cover the taker payment spend transaction fees
pub taker_payment_spend_fee: BigDecimal,
/// Additional reward for maker (premium)
pub premium_amount: BigDecimal,
/// Actual volume of taker's payment
Expand Down Expand Up @@ -1388,6 +1394,8 @@ impl From<UtxoRpcError> for ValidateSwapV2TxError {
/// Enum covering error cases that can happen during taker funding spend preimage validation.
#[derive(Debug, Display, EnumFromStringify)]
pub enum ValidateTakerFundingSpendPreimageError {
/// Error during conversion of BigDecimal amount to coin's specific monetary units (satoshis, wei, etc.).
NumConversion(String),
/// Funding tx has no outputs
FundingTxNoOutputs,
/// Actual preimage fee is either too high or too small
Expand All @@ -1408,6 +1416,10 @@ pub enum ValidateTakerFundingSpendPreimageError {
Rpc(String),
}

impl From<NumConversError> for ValidateTakerFundingSpendPreimageError {
fn from(err: NumConversError) -> Self { ValidateTakerFundingSpendPreimageError::NumConversion(err.to_string()) }
}

impl From<TxGenError> for ValidateTakerFundingSpendPreimageError {
fn from(err: TxGenError) -> Self { ValidateTakerFundingSpendPreimageError::TxGenError(format!("{:?}", err)) }
}
Expand Down
39 changes: 24 additions & 15 deletions mm2src/coins/utxo/utxo_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1328,8 +1328,17 @@ async fn gen_taker_funding_spend_preimage<T: UtxoCommonOps>(

let fee = match fee {
FundingSpendFeeSetting::GetFromCoin => {
coin.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox)
.await?
let calculated_fee = coin
.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox)
.await?;
let taker_instructed_fee =
sat_from_big_decimal(&args.taker_payment_spend_fee, coin.as_ref().decimals).mm_err(|e| e.into())?;
// If calculated fee is less than instructed fee, use instructed fee because it was never intended
// to be deposited to us (maker). If the calculated fee is larger, we will incur the fee difference
// just to make sure the transaction is confirmed quickly.
// FIXME: Possible abuse vector is that the taker can always send the fee to be just above 90% of the
// expected fee knowing that the maker will always accept incurring the difference.
calculated_fee.max(taker_instructed_fee)
},
FundingSpendFeeSetting::UseExact(f) => f,
};
Expand Down Expand Up @@ -1419,19 +1428,14 @@ pub async fn validate_taker_funding_spend_preimage<T: UtxoCommonOps + SwapOps>(
)));
}

let expected_fee = coin
.get_htlc_spend_fee(DEFAULT_SWAP_TX_SPEND_SIZE, &FeeApproxStage::WithoutApprox)
.await?;

let actual_fee = funding_amount - payment_amount;
let instructed_fee =
sat_from_big_decimal(&gen_args.taker_payment_spend_fee, coin.as_ref().decimals).mm_err(|e| e.into())?;

let fee_div = expected_fee as f64 / actual_fee as f64;

// FIXME: Should negotiate the fee beforehand, accept fee >= negotiated_fee
if !(0.9..=1.1).contains(&fee_div) {
if actual_fee < instructed_fee {
return MmError::err(ValidateTakerFundingSpendPreimageError::UnexpectedPreimageFee(format!(
"Too large difference between expected {} and actual {} fees",
expected_fee, actual_fee
"A fee of {} was used, less than the agreed upon fee of {}",
actual_fee, instructed_fee
)));
}

Expand Down Expand Up @@ -5196,7 +5200,10 @@ where
// FIXME: Add taker payment fee + preimage fee
// Qs:
// 1- Who pays the maker payment fee? Take in considration that a nicer UX would be to hand the taker the full amount they requested. (so account for maker payment fee and taker claimation fee)
let total_amount = &args.dex_fee.total_spend_amount().to_decimal() + &args.premium_amount + &args.trading_amount;
let total_amount = &args.dex_fee.total_spend_amount().to_decimal()
+ &args.premium_amount
+ &args.trading_amount
+ &args.taker_payment_spend_fee;

let SwapPaymentOutputsResult {
payment_address,
Expand Down Expand Up @@ -5288,8 +5295,10 @@ where
T: UtxoCommonOps + SwapOps,
{
let maker_htlc_key_pair = coin.derive_htlc_key_pair(args.swap_unique_data);
let total_expected_amount =
&args.dex_fee.total_spend_amount().to_decimal() + &args.premium_amount + &args.trading_amount;
let total_expected_amount = &args.dex_fee.total_spend_amount().to_decimal()
+ &args.premium_amount
+ &args.trading_amount
+ &args.taker_payment_spend_fee;

let expected_amount_sat = sat_from_big_decimal(&total_expected_amount, coin.as_ref().decimals)?;

Expand Down
2 changes: 2 additions & 0 deletions mm2src/mm2_main/src/lp_swap/komodefi.swap_v2.pb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub struct TakerNegotiationData {
pub maker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
#[prost(bytes = "vec", optional, tag = "8")]
pub taker_coin_swap_contract: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
#[prost(uint64, tag = "9")]
pub taker_payment_spend_fee: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
Expand Down
21 changes: 20 additions & 1 deletion mm2src/mm2_main/src/lp_swap/maker_swap_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ use keys::KeyPair;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
use mm2_libp2p::Secp256k1PubkeySerialize;
use mm2_number::MmNumber;
use mm2_number::{BigDecimal, MmNumber};
use mm2_state_machine::prelude::*;
use mm2_state_machine::storable_state_machine::*;
use num_traits::ToPrimitive;
use primitives::hash::H256;
use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json};
use secp256k1::PublicKey;
Expand Down Expand Up @@ -55,6 +56,7 @@ pub struct StoredNegotiationData {
maker_coin_swap_contract: Option<BytesJson>,
taker_coin_swap_contract: Option<BytesJson>,
taker_secret_hash: BytesJson,
taker_payment_spend_fee: BigDecimal,
}

/// Represents events produced by maker swap states.
Expand Down Expand Up @@ -822,6 +824,8 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
},
};

// FIXME: I am lost here, is this the fee for funding spend or taker payment spend?
// if it's the former, where is the fee for the the taker payment spend?
let taker_payment_spend_trade_fee = match state_machine.taker_coin.get_receiver_trade_fee(stage).compat().await
{
Ok(fee) => fee,
Expand Down Expand Up @@ -968,6 +972,14 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
return Self::change_state(Aborted::new(reason), state_machine).await;
}

let expected_taker_payment_spend_fee_u64 = self.taker_payment_spend_trade_fee.amount.to_u64().unwrap_or(1);
let ratio = taker_data.taker_payment_spend_fee as f64 / expected_taker_payment_spend_fee_u64 as f64;
if ratio < 0.9 {
let diff = expected_taker_payment_spend_fee_u64 - taker_data.taker_payment_spend_fee;
let reason = AbortReason::TakerPaymentSpentFeeTooLow(diff);
return Self::change_state(Aborted::new(reason), state_machine).await;
}

let taker_coin_htlc_pub_from_taker =
match state_machine.taker_coin.parse_pubkey(&taker_data.taker_coin_htlc_pub) {
Ok(p) => p,
Expand Down Expand Up @@ -997,6 +1009,7 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
maker_coin_swap_contract: taker_data.maker_coin_swap_contract,
taker_coin_swap_contract: taker_data.taker_coin_swap_contract,
taker_secret_hash: taker_data.taker_secret_hash,
taker_payment_spend_fee: taker_data.taker_payment_spend_fee.into(),
},
maker_payment_trade_fee: self.maker_payment_trade_fee,
};
Expand All @@ -1012,6 +1025,7 @@ struct NegotiationData<MakerCoin: ParseCoinAssocTypes, TakerCoin: ParseCoinAssoc
maker_coin_swap_contract: Option<Vec<u8>>,
taker_coin_swap_contract: Option<Vec<u8>>,
taker_secret_hash: Vec<u8>,
taker_payment_spend_fee: BigDecimal,
}

impl<MakerCoin: ParseCoinAssocTypes, TakerCoin: ParseCoinAssocTypes> NegotiationData<MakerCoin, TakerCoin> {
Expand All @@ -1024,6 +1038,7 @@ impl<MakerCoin: ParseCoinAssocTypes, TakerCoin: ParseCoinAssocTypes> Negotiation
maker_coin_swap_contract: self.maker_coin_swap_contract.clone().map(|b| b.into()),
taker_coin_swap_contract: self.taker_coin_swap_contract.clone().map(|b| b.into()),
taker_secret_hash: self.taker_secret_hash.clone().into(),
taker_payment_spend_fee: self.taker_payment_spend_fee.clone(),
}
}

Expand All @@ -1044,6 +1059,7 @@ impl<MakerCoin: ParseCoinAssocTypes, TakerCoin: ParseCoinAssocTypes> Negotiation
maker_coin_swap_contract: None,
taker_coin_swap_contract: None,
taker_secret_hash: stored.taker_secret_hash.into(),
taker_payment_spend_fee: stored.taker_payment_spend_fee,
})
}
}
Expand Down Expand Up @@ -1161,6 +1177,7 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
taker_secret_hash: &self.negotiation_data.taker_secret_hash,
other_pub: &self.negotiation_data.taker_coin_htlc_pub_from_taker,
dex_fee: &state_machine.dex_fee,
taker_payment_spend_fee: self.negotiation_data.taker_payment_spend_fee.clone(),
premium_amount: state_machine.taker_premium.to_decimal(),
trading_amount: state_machine.taker_volume.to_decimal(),
swap_unique_data: &unique_data,
Expand All @@ -1179,6 +1196,7 @@ impl<MakerCoin: MmCoin + MakerCoinSwapOpsV2, TakerCoin: MmCoin + TakerCoinSwapOp
taker_secret_hash: &self.negotiation_data.taker_secret_hash,
taker_payment_time_lock: self.negotiation_data.taker_payment_locktime,
maker_secret_hash: &state_machine.secret_hash(),
taker_payment_spend_fee: self.negotiation_data.taker_payment_spend_fee.clone(),
};
let funding_spend_preimage = match state_machine
.taker_coin
Expand Down Expand Up @@ -1796,6 +1814,7 @@ pub enum AbortReason {
TakerProvidedInvalidFundingLocktime(u64),
TakerProvidedInvalidPaymentLocktime(u64),
FailedToParsePubkey(String),
TakerPaymentSpentFeeTooLow(u64),
MakerPaymentRefundFailed(String),
FailedToGetMakerPaymentFee(String),
FailedToGetTakerPaymentSpendFee(String),
Expand Down
1 change: 1 addition & 0 deletions mm2src/mm2_main/src/lp_swap/swap_v2.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ message TakerNegotiationData {
bytes taker_coin_htlc_pub = 6;
optional bytes maker_coin_swap_contract = 7;
optional bytes taker_coin_swap_contract = 8;
uint64 taker_payment_spend_fee = 9;
}

message TakerNegotiation {
Expand Down
Loading

0 comments on commit 3c888aa

Please sign in to comment.