Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions mm2src/coins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ tonic = { version = "0.10", features = ["tls", "tls-webpki-roots", "gzip"] }
webpki-roots = { version = "0.25" }
zcash_client_sqlite = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.2" }
zcash_proofs = { git = "https://github.com/KomodoPlatform/librustzcash.git", tag = "k-1.4.2", default-features = false, features = ["local-prover", "multicore"] }
tempfile = "3.0"
ff = "0.8"
jubjub = "0.5.1"
Comment thread
shamardy marked this conversation as resolved.
Outdated

[target.'cfg(windows)'.dependencies]
winapi = "0.3"
Expand Down
284 changes: 283 additions & 1 deletion mm2src/coins/z_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ impl ZCoin {
else {
return Ok(false);
};
if &address == expected_address {
if &address != expected_address {
Comment thread
shamardy marked this conversation as resolved.
return Ok(false);
}
if note.value != amount_sat {
Expand Down Expand Up @@ -783,6 +783,8 @@ pub enum ZcoinRpcMode {
/// Will use `sync_params` if no last synced block found.
skip_sync_params: Option<bool>,
},
#[cfg(all(test, not(target_arch = "wasm32")))]
UnitTests,

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.

Can't we do the tests for wasm as well using cross_test

@dimxy dimxy May 27, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I tried to make it a cross_test and was getting a failure on wasm with an error in zcash_proofs/bellman::multicore mod:

Error { kind: Unsupported, message: "operation not supported on this platform" }

I managed to fix this error to by disabling the multicore feature in zcash_proofs for wasm dependencies (like it had been before the workspace dependencies PR #2449. See zcash_proofs in the main branch Cargo.toml ).
@borngraced you may want to disable multicore for zcash_proofs for wasm, as we now have zcash_proofs only with multicore in workspace deps.

PS However the wasm test still would not work as looks like mocktopus is not working there
(I even tried to make mocktopus as a release dependency but still no luck)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

weird, even tho mocktopus supports WASM

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@dimxy please could you be more specific on the issue you had using mocktopus in wasm?

}

#[derive(Clone, Deserialize)]
Expand Down Expand Up @@ -964,6 +966,8 @@ impl<'a> UtxoCoinBuilder for ZCoinBuilder<'a> {
)
.await?
},
#[cfg(all(test, not(target_arch = "wasm32")))]
ZcoinRpcMode::UnitTests => tests::create_test_sync_connector(&self).await,
};

let z_fields = Arc::new(ZCoinFields {
Expand Down Expand Up @@ -1008,6 +1012,12 @@ impl<'a> ZCoinBuilder<'a> {
min_connected: *min_connected,
max_connected: *max_connected,
},
#[cfg(test)]
ZcoinRpcMode::UnitTests => UtxoRpcMode::Electrum {
servers: vec![],
min_connected: None,
max_connected: Some(1),
},
};
let utxo_params = UtxoActivationParams {
mode: utxo_mode,
Expand Down Expand Up @@ -2064,3 +2074,275 @@ fn test_interpret_memo_string() {
let expected = MemoBytes::from_bytes(&hex::decode("68656c6c6f207a63617368").unwrap()).unwrap();
assert_eq!(actual, expected);
}

#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
use crate::utxo::rpc_clients::ElectrumClient;
use crate::utxo::rpc_clients::UtxoRpcClientOps;
use crate::z_coin::storage::WalletDbShared;
use crate::CoinProtocol;
use crate::DexFeeBurnDestination;
use common::executor::spawn_abortable;
use ff::{Field, PrimeField};
Comment thread
shamardy marked this conversation as resolved.
Outdated
use futures::channel::mpsc::channel;
use futures::lock::Mutex as AsyncMutex;
use lazy_static::lazy_static;
use mm2_core::mm_ctx::{MmArc, MmCtxBuilder};
use mm2_test_helpers::for_tests::zombie_conf;
use mocktopus::mocking::*;
use rand::rngs::OsRng;
use rand::RngCore;
use tempfile::TempDir;
use tokio::sync::Mutex as TokioMutex;
use zcash_primitives::merkle_tree::CommitmentTree;
use zcash_primitives::merkle_tree::IncrementalWitness;
use zcash_primitives::sapling::Node;
use zcash_primitives::sapling::Rseed;
use zcash_primitives::transaction::components::amount::DEFAULT_FEE;

lazy_static! {
static ref TEMP_DIR: TokioMutex<TempDir> = TokioMutex::new(TempDir::new().unwrap());
}
Comment thread
shamardy marked this conversation as resolved.
Outdated

pub(crate) async fn create_test_sync_connector<'a>(
builder: &ZCoinBuilder<'a>,
) -> (AsyncMutex<SaplingSyncConnector>, WalletDbShared) {
let wallet_db = WalletDbShared::new(builder, None, builder.z_spending_key.as_ref().unwrap(), true)
.await
.unwrap(); // Note: assuming we have a spending key in the builder
let (_, sync_watcher) = channel(1);
let (on_tx_gen_notifier, _) = channel(1);
let abort_handle = spawn_abortable(futures::future::ready(()));
let first_sync_block = FirstSyncBlock {
requested: 0,
is_pre_sapling: false,
actual: 0,
};
let sync_state_connector =
SaplingSyncConnector::new_mutex_wrapped(sync_watcher, on_tx_gen_notifier, abort_handle, first_sync_block);
(sync_state_connector, wallet_db)
}

#[allow(clippy::too_many_arguments)]
async fn z_coin_from_conf_and_params_for_tests(
ctx: &MmArc,
ticker: &str,
conf: &Json,
params: &ZcoinActivationParams,
priv_key_policy: PrivKeyBuildPolicy,
db_dir_path: PathBuf,
protocol_info: ZcoinProtocolInfo,
spending_key: &str,
) -> Result<ZCoin, MmError<ZCoinBuildError>> {
use zcash_client_backend::encoding::decode_extended_spending_key;
let z_spending_key =
decode_extended_spending_key(z_mainnet_constants::HRP_SAPLING_EXTENDED_SPENDING_KEY, spending_key)
.unwrap()
.unwrap();

let builder = ZCoinBuilder::new(
ctx,
ticker,
conf,
params,
priv_key_policy,
db_dir_path,
Some(z_spending_key),
protocol_info,
);

builder.build().await
}
/// Build asset `ZCoin` for unit tests.
async fn z_coin_from_spending_key_for_unit_test(spending_key: &str, path: &str) -> (MmArc, ZCoin) {
let ctx = MmCtxBuilder::new().into_mm_arc();
let mut conf = zombie_conf();
let params = ZcoinActivationParams {
mode: ZcoinRpcMode::UnitTests,
..Default::default()
};
let pk_data = [1; 32];
let tmp = TEMP_DIR.lock().await;
let db_folder = tmp.path().join(format!("ZOMBIE_DB_{path}"));
std::fs::create_dir_all(&db_folder).unwrap();
let protocol_info = match serde_json::from_value::<CoinProtocol>(conf["protocol"].take()).unwrap() {
CoinProtocol::ZHTLC(protocol_info) => protocol_info,
other_protocol => panic!("Failed to get protocol from config: {:?}", other_protocol),
};

let coin = z_coin_from_conf_and_params_for_tests(
&ctx,
"ZOMBIE",
&conf,
&params,
PrivKeyBuildPolicy::IguanaPrivKey(pk_data.into()),
db_folder,
protocol_info,
spending_key,
)
.await
.unwrap();
(ctx, coin)
}

fn add_test_spend<P: Parameters, R: RngCore>(coin: &ZCoin, tx_builder: &mut ZTxBuilder<P, R>, amount: u64) {
let extsk = coin.z_fields.z_spending_key.clone();
let extfvk = coin.z_fields.evk.clone();
let to = extfvk.default_address().unwrap().1;
let mut rng = OsRng;
let note1 = to
.create_note(amount, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cmu1 = Node::new(note1.cmu().to_repr());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();
let witness1 = IncrementalWitness::from_tree(&tree);

tx_builder
.add_sapling_spend(extsk, *to.diversifier(), note1, witness1.path().unwrap())
.unwrap();
}

async fn validate_fee_caller(
coin: &ZCoin,
dex_params: (PaymentAddress, u64),
burn_params: Option<(PaymentAddress, u64)>,
dex_fee: &DexFee,
) -> ValidatePaymentResult<()> {
let uuid = &[1; 16];
let mut z_outputs = vec![];
let mut tx_builder = ZTxBuilder::new(coin.consensus_params(), BlockHeight::from_u32(1));

add_test_spend(
coin,
&mut tx_builder,
dex_params.1
+ if let Some(ref burn_params) = burn_params {
burn_params.1
} else {
0
}
+ u64::from(DEFAULT_FEE),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
add_test_spend(
coin,
&mut tx_builder,
dex_params.1
+ if let Some(ref burn_params) = burn_params {
burn_params.1
} else {
0
}
+ u64::from(DEFAULT_FEE),
);
let amount = dex_params.1 + burn_params.map(|(_, v)| v).unwrap_or_default() + u64::from(DEFAULT_FEE);
add_test_spend(coin, &mut tx_builder, amount);


let dex_fee_out = ZOutput {
to_addr: dex_params.0,
amount: Amount::from_u64(dex_params.1).unwrap(),
viewing_key: Some(DEX_FEE_OVK),
memo: Some(MemoBytes::from_bytes(uuid).expect("uuid length < 512")),
};
z_outputs.push(dex_fee_out);

// add output to the dex burn address:
if let Some(burn_params) = burn_params {
let dex_burn_out = ZOutput {
to_addr: burn_params.0,
amount: Amount::from_u64(burn_params.1).unwrap(),
viewing_key: Some(DEX_FEE_OVK),
memo: Some(MemoBytes::from_bytes(uuid).expect("uuid length < 512")),
};
z_outputs.push(dex_burn_out);
}
for z_out in z_outputs {
tx_builder
.add_sapling_output(z_out.viewing_key, z_out.to_addr, z_out.amount, z_out.memo)
.unwrap();
}
let (tx, _) = async_blocking({
let prover = coin.z_fields.z_tx_prover.clone();
move || tx_builder.build(BranchId::Sapling, prover.as_ref())
})
.await
.unwrap();

let tx: TransactionEnum = tx.into();
let tx_ret = tx.clone();
ElectrumClient::get_verbose_transaction.mock_safe(move |_, txid| {
let bytes: BytesJson = tx_ret.tx_hex().into();
MockResult::Return(Box::new(futures01::future::ok(RpcTransaction {
txid: *txid,
hash: None,
blockhash: H256Json::default(),
confirmations: 0,
time: 0,
blocktime: 0,
hex: bytes,
vout: vec![],
vin: vec![],
size: None,
version: 4,
locktime: 0,
vsize: None,
rawconfirmations: None,
height: None,
})))
});
let validate_fee_args = ValidateFeeArgs {
fee_tx: &tx,
expected_sender: &[],
dex_fee,
min_block_number: 1,
uuid: &[1; 16],
};
coin.validate_fee(validate_fee_args).await
}

#[tokio::test]
async fn test_validate_zcoin_dex_fee() {
Comment thread
shamardy marked this conversation as resolved.
Outdated
let (_ctx, coin) = z_coin_from_spending_key_for_unit_test("secret-extended-key-main1qvqstxphqyqqpqqnh3hstqpdjzkpadeed6u7fz230jmm2mxl0aacrtu9vt7a7rmr2w5az5u79d24t0rudak3newknrz5l0m3dsd8m4dffqh5xwyldc5qwz8pnalrnhlxdzf900x83jazc52y25e9hvyd4kepaze6nlcvk8sd8a4qjh3e9j5d6730t7ctzhhrhp0zljjtwuptadnksxf8a8y5axwdhass5pjaxg0hzhg7z25rx0rll7a6txywl32s6cda0s5kexr03uqdtelwe", "we").await;

let std_fee = DexFee::Standard("0.001".into());
let with_burn = DexFee::WithBurn {
fee_amount: "0.0075".into(),
burn_amount: "0.0025".into(),
burn_destination: DexFeeBurnDestination::PreBurnAccount,
};
assert!(
validate_fee_caller(&coin, (coin.z_fields.dex_fee_addr.clone(), 100000), None, &std_fee)
.await
.is_ok()
);
assert!(validate_fee_caller(
&coin,
(coin.z_fields.dex_fee_addr.clone(), 750000),
Some((coin.z_fields.dex_burn_addr.clone(), 250000)),
&with_burn
)
.await
.is_ok());
// try reverted addresses
assert!(validate_fee_caller(
&coin,
(coin.z_fields.dex_burn_addr.clone(), 750000),
Some((coin.z_fields.dex_fee_addr.clone(), 250000)),
&with_burn
)
.await
.is_err());
let other_addr = decode_payment_address(
coin.z_fields.consensus_params.hrp_sapling_payment_address(),
"zs182ht30wnnnr8jjhj2j9v5dkx3qsknnr5r00jfwk2nczdtqy7w0v836kyy840kv2r8xle5gcl549",
)
.expect("valid z address format")
.expect("valid z address");
// try invalid dex address
assert!(validate_fee_caller(
&coin,
(other_addr.clone(), 750000),
Some((coin.z_fields.dex_burn_addr.clone(), 250000)),
&with_burn
)
.await
.is_err());
// try invalid burn address
assert!(validate_fee_caller(
&coin,
(coin.z_fields.dex_fee_addr.clone(), 750000),
Some((other_addr.clone(), 250000)),
&with_burn
)
.await
.is_err());
}
}
Comment thread
shamardy marked this conversation as resolved.
Outdated
Loading