Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utxo-swap): add utxo burn output for non-kmd taker fee #2112

Open
wants to merge 109 commits into
base: dev
Choose a base branch
from

Conversation

dimxy
Copy link
Collaborator

@dimxy dimxy commented May 6, 2024

Adds a burn output sending 25% of the taker utxo DEX fee to a dedicated pre-burn address. Funds collected on the pre-burn address will be traded for KMD to burn them (thus additionally burning KMD supply).
This PR partially closes #2010

The requirements for this PR: #2269

In this PR:

  • split dex fee for non-kmd utxo coins (apart from zcoin) and added an output to the pre-burn account
  • old-style non-split dex fee non-kmd txns are also allowed in validation code (until all taker nodes upgraded) - this feature is removed in favour of version-based burning activation
  • refactored dex fee pubkey in params: instead of passing dex fee and burn pubkeys in function params new methods dex_pubkey() and burn_pubkey() were added to the SwapOps trait
  • add version to maker/taker negotiation message and activate burning for the new version
  • mocktopus was made optional dependency and activated only for development builds (as its doc suggests).
  • for the burn account no burn part is added (DexFee::Standard used)
  • sending burn part to the burn account for zcoin
  • sending burn part to the burn account for tendermint
  • fix burn pubkey
  • do not pay dex fee if taker is the dex pubkey (for non-privacy utxo)

NOTE: As mocktopus now is marked 'optional = true' in coins Cargo.toml and activated from the mm2_main crate by adding features = ["mocktopus"] in [dev-dependencies] section, you also need to mark your mockable code, called from other crates, this way: #[cfg_attr(feature = "mocktopus", mockable)], otherwise mocks won't work (see samples in code)

TODO:

  • fix burn zaddr (enable burn for zcoin)
  • disable non-split non-kmd dex fee (no burn output) validation when all taker nodes upgrade to new dex fee splitting

dimxy added 9 commits April 27, 2024 14:33
* dev:
  docs(README): remove outdated information from the README (#2097)
  fix(sia): fix sia compilation after hd wallet PR merge (#2103)
  feat(hd_wallet): utxo and evm hd wallet and trezor (#1962)
  feat(sia): initial Sia integration (#2086)
  fix(BCH): deserialize BCH header that uses KAWPOW version correctly (#2099)
  fix(eth_tests): remove ETH_DEV_NODE from tests (#2101)
@dimxy dimxy changed the title Add utxo burn output for non-kmd taker fee feat(utxo-swap): add utxo burn output for non-kmd taker fee May 6, 2024
* dev:
  feat(tendermint): pubkey-only activation and unsigned tx (#2088)
  fix(tests): set txfee for some tbtc tests (#2116)
  fix(eth): remove my_address from sign_and_send_transaction_with_keypair (#2115)
  fix(utxo-swap): apply events occurred while taker down (#2114)
  refactor(memory): memory usage improvements (#2098)
  feat(app-dir): implement root application dir `.kdf` (#2102)
  fix tendermint fee calculation (#2106)
  update dockerfile (#2104)
* dev:
  feat(ETH): eip1559 gas fee estimator and rpcs (#2051)
  fix(p2pk-tests): fix p2pk tests post merge (#2119)
  fix(p2pk): show and spend P2PK balance (#2053)
  fix(swap): use tmp file for swap and order files (#2118)
dimxy added 9 commits June 14, 2024 20:54
fix zhtlc send and spend tests
add should_burn_dex_fee method to indicate which coin has burn output
* dev:
  fix(indexeddb): window usage in worker env (#2131)
  feat(tx-history): handle encoded transaction values (#2133)
  fix(core): tendermint withdraws on hd accounts (#2130)
  fix(core): improve validation rules for table names (#2123)
  fix(test): improve log wait condition to fix taker restart test (#2125)
…er test

add timeout in wait for fee in qrc20 docker test
… docker test failed due to different dex fee values if trait default impl was used)
Copy link
Member

@laruh laruh left a comment

Choose a reason for hiding this comment

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

Great progress! Here is the first review iteration

mm2src/coins/lp_coins.rs Outdated Show resolved Hide resolved
mm2src/coins/lp_coins.rs Outdated Show resolved Hide resolved
mm2src/coins/lp_coins.rs Outdated Show resolved Hide resolved
mm2src/coins/lp_coins.rs Outdated Show resolved Hide resolved
mm2src/coins/lp_coins.rs Outdated Show resolved Hide resolved
dimxy added 2 commits June 19, 2024 13:05
use same ovk for dex fee and burn outputs
add sanity check for dex_fee in calc_burn_amount_for_op_return
Copy link
Member

@borngraced borngraced left a comment

Choose a reason for hiding this comment

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

Great work! I have one more review after this

Comment on lines +95 to +97
#[cfg(feature = "for-tests")]
pub static mut TEST_BURN_ADDR_RAW_PUBKEY: Option<Vec<u8>> = None;

Copy link
Member

@borngraced borngraced Jan 20, 2025

Choose a reason for hiding this comment

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

please can you move this down to where all const are declared? to avoid having declaration before import statements.

Q: TEST_BURN_ADDR_RAW_PUBKEY is read with run-docker-tests and for-tests features

why not initialize TEST_BURN_ADDR_RAW_PUBKEY with lazy_static once with docker-test and for-tests feature directives to avoid doing something like mutating/writing unsafe

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I need this var initialised only for couple of tests (testing cases when taker is also a burn pubkey), that's why I have to init it with a env var.

Comment on lines 3594 to 3596
MmCoinEnum::SlpToken(ref c) => c.as_ref().rpc_client.is_native(),
#[cfg(all(not(target_arch = "wasm32"), feature = "zhtlc"))]
// TODO: we do not have such feature = "zhtlc" in toml. Remove this cfg part?
Copy link
Member

Choose a reason for hiding this comment

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

Yes, I believe it can be removed.
#[cfg(not(target_arch = "wasm32"))] should be enough

Comment on lines +956 to +958
let activated_priv_key = if let Ok(activated_priv_key) = self.activation_policy.activated_key_or_err() {
activated_priv_key
} else {
Copy link
Member

Choose a reason for hiding this comment

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

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I had to change this because this code did not build when mockable attr was added.

Comment on lines +1043 to +1045
let priv_key = if let Some(priv_key) = priv_key {
priv_key
} else {
Copy link
Member

Choose a reason for hiding this comment

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

same as above

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same, did not compile with mockable attr

Comment on lines +1117 to +1120
let account_prefix = self.account_prefix.clone();
let base_account = match BaseAccount::decode(account.value.as_slice()) {
Ok(account) => account,
Err(err) if &self.account_prefix == "iaa" => {
Err(err) if account_prefix.as_str() == "iaa" => {
Copy link
Member

Choose a reason for hiding this comment

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

why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

bcz did not compile with mocktopus added (seems some issues with code processing in their macro)


pub const PROXY_REQUEST_EXPIRATION_SEC: i64 = 15;

lazy_static! {
pub static ref DEX_FEE_ADDR_RAW_PUBKEY: Vec<u8> =
hex::decode(DEX_FEE_ADDR_PUBKEY).expect("DEX_FEE_ADDR_PUBKEY is expected to be a hexadecimal string");
pub static ref DEX_BURN_ADDR_RAW_PUBKEY: Vec<u8> =
hex::decode(DEX_BURN_ADDR_PUBKEY).expect("DEX_BURN_ADDR_PUBKEY is expected to be a hexadecimal string");
}
Copy link
Member

Choose a reason for hiding this comment

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

I believe TEST_DEX_BURN_ADDR_RAW_PUBKEY belongs here...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TEST_DEX_BURN_ADDR_RAW_PUBKEY was fixed to actual DEX_BURN_ADDR_PUBKEY

use common::executor::Timer;
use common::log::{debug, error, info, warn};
use common::{bits256, now_ms, now_sec, wait_until_sec, DEX_FEE_ADDR_RAW_PUBKEY};
use common::{bits256, env_var_as_bool, now_ms, now_sec, wait_until_sec};
Copy link
Member

Choose a reason for hiding this comment

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

env_var_as_bool unused?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed

Comment on lines 913 to 918
/// NegotiationDataMsg with version
#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)]
pub struct NegotiationDataMsgVersion {
version: u16,
msg: NegotiationDataMsg,
}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// NegotiationDataMsg with version
#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)]
pub struct NegotiationDataMsgVersion {
version: u16,
msg: NegotiationDataMsg,
}
/// NegotiationDataMsg with version
#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)]
pub struct NegotiationDataMsgWithVersion {
version: u16,
msg: NegotiationDataMsg,
}

NegotiationDataMsgWithVersion is a better name

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed version support for now

Comment on lines 920 to 936
impl NegotiationDataMsgVersion {
pub fn version(&self) -> u16 { self.version }

pub fn started_at(&self) -> u64 { self.msg.started_at() }

pub fn payment_locktime(&self) -> u64 { self.msg.payment_locktime() }

pub fn secret_hash(&self) -> &[u8] { self.msg.secret_hash() }

pub fn maker_coin_htlc_pub(&self) -> &[u8] { self.msg.maker_coin_htlc_pub() }

pub fn taker_coin_htlc_pub(&self) -> &[u8] { self.msg.taker_coin_htlc_pub() }

pub fn maker_coin_swap_contract(&self) -> Option<&[u8]> { self.msg.maker_coin_swap_contract() }

pub fn taker_coin_swap_contract(&self) -> Option<&[u8]> { self.msg.taker_coin_swap_contract() }
}
Copy link
Member

Choose a reason for hiding this comment

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

these are redundancy, we should remove or is there a reason why we can't make msg and version pub?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed this version related code

Comment on lines 459 to 467
#[cfg(feature = "run-docker-tests")]
let version = if !env_var_as_bool("USE_OLD_VERSION_MAKER") {
LEGACY_SWAP_MSG_VERSION
} else {
0 // use old version for tests
};

#[cfg(not(feature = "run-docker-tests"))]
let version = LEGACY_SWAP_MSG_VERSION;
Copy link
Member

Choose a reason for hiding this comment

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

can this be extracted to a separate inline function and reuse in taker_swap too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed this code (with version support removal in favour of versions in order matching)

/// NegotiationDataMsg with version
#[derive(Clone, Debug, Eq, Deserialize, PartialEq, Serialize)]
pub struct NegotiationDataMsgVersion {
version: u16,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As our discussion about versioning is inclining to put maker and taker version into orders, maybe we don't need this version field.

Copy link
Member

@laruh laruh Jan 21, 2025

Choose a reason for hiding this comment

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

As our discussion about versioning is inclining to put maker and taker version into orders, maybe we don't need this version field.

I believe if we currently don’t require such feature, it’s better to leave it out for now. We can always introduce it later when we are sure that negotiation version support is necessary. This helps keep things simpler and avoids adding unused fields/types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed version from negotiation e4e8de0

Copy link
Member

@laruh laruh left a comment

Choose a reason for hiding this comment

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

Thanks for the updates, here is quick note I want to cover

Comment on lines 129 to 130
/// Swap message exchange version supported by remote taker peer. Optional because it is found at negotiation
pub taker_version: Option<u16>,
Copy link
Member

Choose a reason for hiding this comment

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

Why you didnt use skip_serializing_if annotation? Not sure its safe to not to use it, please re check it.
Also Its better to rename field to taker_negotiation_msg_v. It is clearer.

Same for LEGACY_SWAP_MSG_VERSION. Its about negotiation msg version, not all swap p2p messages. Should be LEGACY_SWAP_NEGOTIATION_MSG_V

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Its about negotiation msg version, not all swap p2p messages.

Not quite: it's about the swap message version supported by a party. I meant it for the whole message set used in the swap, not only negotiation

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why you didnt use skip_serializing_if annotation?

skip_serializing_if does not matter for rmp (checked with a test sample).
There is also the same var in the MakerSwapData struct. Not sure it's worth adding skip_serializing_if there: IMO it's easier to analyse swap jsons if "taker_version": null present, don't you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Also Its better to rename field to taker_negotiation_msg_v. It is clearer.

I renamed it to taker_msg_version and maker_msg_version 897aa4d.
It's worth renaming indeed but I did not like losing the key work 'version'. Also did not add 'negotiation' considering the above

Copy link
Member

@laruh laruh Jan 22, 2025

Choose a reason for hiding this comment

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

Its about negotiation msg version, not all swap p2p messages.

Not quite: it's about the swap message version supported by a party. I meant it for the whole message set used in the swap, not only negotiation

I think then it doesnt make sense, p2p message versions (I mean all topic related messages) are handled in process_p2p_message. If you change p2p msg version it means user should be subscribed to another topic version

let mut split = message.topic.as_str().split(TOPIC_SEPARATOR);
match split.next() {
Some(lp_ordermatch::ORDERBOOK_PREFIX) => {
if let Err(e) = lp_ordermatch::handle_orderbook_msg(
ctx.clone(),
&message.topic,
peer_id.to_string(),
&message.data,
i_am_relay,
)
.await
{
if e.get_inner().is_warning() {
log::warn!("{}", e);
} else {
log::error!("{}", e);
}
return;
}
to_propagate = true;
},
Some(lp_swap::SWAP_PREFIX) => {
if let Err(e) =
lp_swap::process_swap_msg(ctx.clone(), split.next().unwrap_or_default(), &message.data).await

You added version field in Negotiation msg of legacy swap topic, which belongs to SWAP_PREFIX.
But when we change all p2p messages version, it means we need to create a new topic, what we actually did for SWAP_V2_PREFIX
image

I dont get why we would need version inside the "swap" msg topic. to be able to version this specific topic?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

At first place, I needed anything like 'remote peer version' to activate features like 'eip1559 tx' or 'burn non-kmd output' (we need to know if the remote swap party has sufficient version to support such features)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

removed version from negotiation e4e8de0

mm2src/coins/solana.rs Outdated Show resolved Hide resolved
mm2src/coins/solana/spl.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@mariocynicys mariocynicys left a comment

Choose a reason for hiding this comment

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

Thanks! Huge PR!
had to cut the review short to push out my pending comments and Qs. more iterations to come.

mm2src/coins/tendermint/tendermint_coin.rs Outdated Show resolved Hide resolved
mm2src/coins/tendermint/tendermint_coin.rs Outdated Show resolved Hide resolved
mm2src/coins/tendermint/tendermint_coin.rs Outdated Show resolved Hide resolved
mm2src/coins/utxo/qtum.rs Outdated Show resolved Hide resolved
Comment on lines 1273 to 1276
- tx_fee;
// taker also adds maker output as we can't use SIGHASH_SINGLE with two outputs, dex fee and burn,
// and both the maker and taker sign all outputs:
outputs.push(TransactionOutput {
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think then we should do the same for the standard non-burn case for simplicity.

im opening a discussion here but this could be done later in another PR ofc.

P.S.: and we don't have to use SIGHASH_ALL for NoFee case. We can actually do SIGHASH_NONE.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I would agree that it's better to do all cases identically (standard will be eliminated eventually though).
Not sure about SIGHASH_NONE: this actually contradict the first idea - do all cases same way

Comment on lines 3653 to 3671
/// Calculates DEX fee with a threshold based on min tx amount of the taker coin.
/// preburn_account_active param indicates that swap nodes version supports burning dex fee for non kmd coins.
/// It could be None if dex fee is needed only to get total dex fee amount
/// taker_pubkey also may be optional if it is not known yet but we need total dex fee amount
pub fn dex_fee_from_taker_coin(
taker_coin: &dyn MmCoin,
maker_coin: &str,
trade_amount: &MmNumber,
taker_pubkey: Option<&[u8]>,
preburn_account_active: Option<bool>,
) -> DexFee {
DexFee::new_from_taker_coin(
taker_coin.deref(),
maker_coin,
trade_amount,
taker_pubkey,
preburn_account_active,
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

i see in eth we have fn should_burn_dex_fee(&self) -> bool { false } which is false i think because we don't maintain an eth burn account, no?

if so, the param preburn_account_active is confusing as it describes the same thing. i think it should be swap_supports_burning or something, as this is how it's used.

another thing: Option<bool> almost certainly could always be reduced to bool with defaulting the None case to either true or false. If the None case is needed here we can turn this Option<bool> into an enum instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

preburn_account_active is removed (it was meant to denote that version is high enough to activate the burn feature)

Comment on lines +1134 to +1135
println!("ZOMBIE_wallet.db will be synch'ed with the chain, this may take a while for the first time.");
println!("You may also run prepare_zombie_sapling_cache test to update ZOMBIE_wallet.db before running tests.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's use our logging system here

Copy link
Collaborator Author

@dimxy dimxy Jan 27, 2025

Choose a reason for hiding this comment

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

well it's a hint for the user that this test may take some time (logs may be captured and not being watched atm)
It's meant to be read from console.

Comment on lines +1466 to +1504
let mut fee_output_valid = false;
let mut burn_output_valid = false;
for shielded_out in z_tx.shielded_outputs.iter() {
if let Some((note, address, memo)) =
try_sapling_output_recovery(self.consensus_params_ref(), block_height, &DEX_FEE_OVK, shielded_out)
if self
.validate_dex_fee_output(
shielded_out,
&DEX_FEE_OVK,
&self.z_fields.dex_fee_addr,
block_height,
fee_amount_sat,
&expected_memo,
)
.map_err(|err| {
MmError::new(ValidatePaymentError::WrongPaymentTx(format!(
"Bad dex fee output: {}",
err
)))
})?
{
if address != self.z_fields.dex_fee_addr {
let encoded = encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address);
let expected = encode_payment_address(
z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS,
&self.z_fields.dex_fee_addr,
);
return MmError::err(ValidatePaymentError::WrongPaymentTx(format!(
"Dex fee was sent to the invalid address {}, expected {}",
encoded, expected
)));
}

if 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 MmError::err(ValidatePaymentError::WrongPaymentTx(format!(
"Dex fee has invalid memo {:?}, expected {:?}",
memo, expected_memo
)));
fee_output_valid = true;
}
if let Some(burn_amount_sat) = burn_amount_sat {
if self
.validate_dex_fee_output(
shielded_out,
&DEX_FEE_OVK,
&self.z_fields.dex_burn_addr,
block_height,
burn_amount_sat,
&expected_memo,
)
.map_err(|err| {
MmError::new(ValidatePaymentError::WrongPaymentTx(format!(
"Bad burn output: {}",
err
)))
})?
{
burn_output_valid = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

are we doing this loop and true/false flags because we don't know which order the outputs are? like which is the dex fee and which is the burn?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, I believe such code looks more universal than simply rely on the output order

@@ -39,7 +39,8 @@ use mm2_metrics::{mm_label, mm_timing};
use serde::de;
use std::net::ToSocketAddrs;

use crate::{lp_healthcheck, lp_ordermatch, lp_stats, lp_swap};
use crate::{lp_healthcheck, lp_ordermatch, lp_stats,
lp_swap::{self}};
Copy link
Collaborator

Choose a reason for hiding this comment

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

no need for self, lp_swap is enough

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed in latest commits

Comment on lines +166 to +170
/// Add refund fee to calculate maximum available balance for a swap (including possible refund)
pub(crate) const INCLUDE_REFUND_FEE: bool = true;

/// Do not add refund fee to calculate fee needed only to make a successful swap
pub(crate) const NO_REFUND_FEE: bool = false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Q, when are each used and why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Both are part of same entity, meaning to include or not the refund fee into the return value.
Does it look confusing?
Idea was to use a named const instead of just true or false.

Copy link
Collaborator

Choose a reason for hiding this comment

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

nah, that's not what I meant.

I just ran out of time and decided to ask when each case is used instead of digging in the code 😂

I'm asking here when do we use the fee calcs with refund in mind and when do we use it without considering a possible refund. and why would we use each in such cases.

Copy link
Collaborator

Choose a reason for hiding this comment

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

nah, that's not what I meant.

I just ran out of time and decided to ask when each case is used instead of digging in the code 😂

I'm asking here when do we use the fee calcs with refund in mind and when do we use it without considering a possible refund. and why would we use each in such cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah I see,
true is when we calc max amount avail for swap (with fees required for the worst case of refund)
false - when we show to user required fees (considering swap will be successful and we won't need refund)

Copy link
Member

@borngraced borngraced left a comment

Choose a reason for hiding this comment

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

last notes

mm2src/coins/utxo/utxo_common.rs Show resolved Hide resolved
mm2src/coins/utxo/utxo_common.rs Outdated Show resolved Hide resolved
mm2src/coins/utxo/utxo_common.rs Outdated Show resolved Hide resolved
mm2src/coins/utxo/utxo_common.rs Outdated Show resolved Hide resolved
Comment on lines +1469 to +1504
if self
.validate_dex_fee_output(
shielded_out,
&DEX_FEE_OVK,
&self.z_fields.dex_fee_addr,
block_height,
fee_amount_sat,
&expected_memo,
)
.map_err(|err| {
MmError::new(ValidatePaymentError::WrongPaymentTx(format!(
"Bad dex fee output: {}",
err
)))
})?
{
if address != self.z_fields.dex_fee_addr {
let encoded = encode_payment_address(z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS, &address);
let expected = encode_payment_address(
z_mainnet_constants::HRP_SAPLING_PAYMENT_ADDRESS,
&self.z_fields.dex_fee_addr,
);
return MmError::err(ValidatePaymentError::WrongPaymentTx(format!(
"Dex fee was sent to the invalid address {}, expected {}",
encoded, expected
)));
}

if 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 MmError::err(ValidatePaymentError::WrongPaymentTx(format!(
"Dex fee has invalid memo {:?}, expected {:?}",
memo, expected_memo
)));
fee_output_valid = true;
}
if let Some(burn_amount_sat) = burn_amount_sat {
if self
.validate_dex_fee_output(
shielded_out,
&DEX_FEE_OVK,
&self.z_fields.dex_burn_addr,
block_height,
burn_amount_sat,
&expected_memo,
)
.map_err(|err| {
MmError::new(ValidatePaymentError::WrongPaymentTx(format!(
"Bad burn output: {}",
err
)))
})?
{
burn_output_valid = true;
Copy link
Member

Choose a reason for hiding this comment

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

I think if statement is not need just assign directly e.g

                fee_output_valid = self
                .validate_dex_fee_output(
                    shielded_out,
                    &DEX_FEE_OVK,
                    &self.z_fields.dex_fee_addr,
                    block_height,
                    fee_amount_sat,
                    &expected_memo,
                )
                .map_err(|err| {
                    MmError::new(ValidatePaymentError::WrongPaymentTx(format!(
                        "Bad dex fee output: {}",
                        err
                    )))
                })?;

same with burn_output_valid

Copy link
Collaborator Author

@dimxy dimxy Feb 3, 2025

Choose a reason for hiding this comment

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

I re-worked validate_fee for z_coin: eliminated loop and used fixed 0 and 1 positions for dexfee and burn outputs (like in utxo)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry returned the loop back :-)
Forgot we can't assume zcoin outputs ordering - they are shuffled
@borngraced

Comment on lines +1431 to 1433
let fee_amount_sat = validate_fee_args.dex_fee.fee_amount_as_u64(self.utxo_arc.decimals)?;
let burn_amount_sat = validate_fee_args.dex_fee.burn_amount_as_u64(self.utxo_arc.decimals)?;
let expected_memo = MemoBytes::from_bytes(validate_fee_args.uuid).expect("Uuid length < 512");
Copy link
Member

Choose a reason for hiding this comment

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

Can we check if z_tx.shielded_outputs.is_empty() and return early error before performing these operations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do we really need this?
If there is no shielded_outputs or less than expected, we would have one of fee_output_valid or burn_output_valid false. Looks like an extra check for me

@laruh
Copy link
Member

laruh commented Feb 4, 2025

@dimxy pr started to have conflicts. pinging you just to be sure

dimxy added 5 commits February 4, 2025 15:40
* dev:
  fix(hash-types): remove panic, enforce fixed-size arrays (#2279)
  fix(ARRR): store unconfirmed change output (#2276)
  feat(tendermint): staking/delegation (#2322)
  chore(deps): `timed-map` migration (#2247)
  fix(mem-leak): `running_swap` never shrinks (#2301)
  chore(dep-bump): libp2p (#2326)
  refactor(build script): rewrite the main build script (#2319)
@dimxy
Copy link
Collaborator Author

dimxy commented Feb 4, 2025

Plz note: some zcoin tests (zombie_coin_send_dex_fee) fail until #2331 merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.4.0-beta priority: high Important tasks that need attention soon. status: pending review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants