integration_tests_pure.rs 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504
  1. //! This file contains integration tests for the Cashu Development Kit (CDK)
  2. //!
  3. //! These tests verify the interaction between mint and wallet components, simulating real-world usage scenarios.
  4. //! They test the complete flow of operations including wallet funding, token swapping, sending tokens between wallets,
  5. //! and other operations that require client-mint interaction.
  6. //!
  7. //! Test Environment:
  8. //! - Uses pure in-memory mint instances for fast execution
  9. //! - Tests run concurrently with multi-threaded tokio runtime
  10. //! - No external dependencies (Lightning nodes, databases) required
  11. use std::assert_eq;
  12. use std::collections::{HashMap, HashSet};
  13. use std::hash::RandomState;
  14. use std::str::FromStr;
  15. use std::sync::Arc;
  16. use std::time::Duration;
  17. use cashu::amount::SplitTarget;
  18. use cashu::dhke::construct_proofs;
  19. use cashu::mint_url::MintUrl;
  20. use cashu::{
  21. CurrencyUnit, Id, MeltRequest, NotificationPayload, PaymentMethod, PreMintSecrets, ProofState,
  22. SecretKey, SpendingConditions, State, SwapRequest,
  23. };
  24. use cdk::mint::Mint;
  25. use cdk::nuts::nut00::ProofsMethods;
  26. use cdk::subscription::Params;
  27. use cdk::wallet::types::{TransactionDirection, TransactionId};
  28. use cdk::wallet::{ReceiveOptions, SendMemo, SendOptions};
  29. use cdk::Amount;
  30. use cdk_fake_wallet::create_fake_invoice;
  31. use cdk_integration_tests::init_pure_tests::*;
  32. use tokio::time::sleep;
  33. /// Tests the token swap and send functionality:
  34. /// 1. Alice gets funded with 64 sats
  35. /// 2. Alice prepares to send 40 sats (which requires internal swapping)
  36. /// 3. Alice sends the token
  37. /// 4. Carol receives the token and has the correct balance
  38. #[tokio::test]
  39. async fn test_swap_to_send() {
  40. setup_tracing();
  41. let mint_bob = create_and_start_test_mint()
  42. .await
  43. .expect("Failed to create test mint");
  44. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  45. .await
  46. .expect("Failed to create test wallet");
  47. // Alice gets 64 sats
  48. fund_wallet(wallet_alice.clone(), 64, None)
  49. .await
  50. .expect("Failed to fund wallet");
  51. let balance_alice = wallet_alice
  52. .total_balance()
  53. .await
  54. .expect("Failed to get balance");
  55. assert_eq!(Amount::from(64), balance_alice);
  56. // Alice wants to send 40 sats, which internally swaps
  57. let prepared_send = wallet_alice
  58. .prepare_send(Amount::from(40), SendOptions::default())
  59. .await
  60. .expect("Failed to prepare send");
  61. assert_eq!(
  62. HashSet::<_, RandomState>::from_iter(
  63. prepared_send.proofs().ys().expect("Failed to get ys")
  64. ),
  65. HashSet::from_iter(
  66. wallet_alice
  67. .get_reserved_proofs()
  68. .await
  69. .expect("Failed to get reserved proofs")
  70. .ys()
  71. .expect("Failed to get ys")
  72. )
  73. );
  74. let token = prepared_send
  75. .confirm(Some(SendMemo::for_token("test_swapt_to_send")))
  76. .await
  77. .expect("Failed to send token");
  78. let keysets_info = wallet_alice.get_mint_keysets().await.unwrap();
  79. let token_proofs = token.proofs(&keysets_info).unwrap();
  80. assert_eq!(
  81. Amount::from(40),
  82. token_proofs
  83. .total_amount()
  84. .expect("Failed to get total amount")
  85. );
  86. assert_eq!(
  87. Amount::from(24),
  88. wallet_alice
  89. .total_balance()
  90. .await
  91. .expect("Failed to get balance")
  92. );
  93. assert_eq!(
  94. HashSet::<_, RandomState>::from_iter(token_proofs.ys().expect("Failed to get ys")),
  95. HashSet::from_iter(
  96. wallet_alice
  97. .get_pending_spent_proofs()
  98. .await
  99. .expect("Failed to get pending spent proofs")
  100. .ys()
  101. .expect("Failed to get ys")
  102. )
  103. );
  104. let transaction_id =
  105. TransactionId::from_proofs(token_proofs.clone()).expect("Failed to get tx id");
  106. let transaction = wallet_alice
  107. .get_transaction(transaction_id)
  108. .await
  109. .expect("Failed to get transaction")
  110. .expect("Transaction not found");
  111. assert_eq!(wallet_alice.mint_url, transaction.mint_url);
  112. assert_eq!(TransactionDirection::Outgoing, transaction.direction);
  113. assert_eq!(Amount::from(40), transaction.amount);
  114. assert_eq!(Amount::from(0), transaction.fee);
  115. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  116. assert_eq!(token_proofs.ys().unwrap(), transaction.ys);
  117. // Alice sends cashu, Carol receives
  118. let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())
  119. .await
  120. .expect("Failed to create Carol's wallet");
  121. let received_amount = wallet_carol
  122. .receive_proofs(
  123. token_proofs.clone(),
  124. ReceiveOptions::default(),
  125. token.memo().clone(),
  126. Some(token.to_string()),
  127. )
  128. .await
  129. .expect("Failed to receive proofs");
  130. assert_eq!(Amount::from(40), received_amount);
  131. assert_eq!(
  132. Amount::from(40),
  133. wallet_carol
  134. .total_balance()
  135. .await
  136. .expect("Failed to get Carol's balance")
  137. );
  138. let transaction = wallet_carol
  139. .get_transaction(transaction_id)
  140. .await
  141. .expect("Failed to get transaction")
  142. .expect("Transaction not found");
  143. assert_eq!(wallet_carol.mint_url, transaction.mint_url);
  144. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  145. assert_eq!(Amount::from(40), transaction.amount);
  146. assert_eq!(Amount::from(0), transaction.fee);
  147. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  148. assert_eq!(token_proofs.ys().unwrap(), transaction.ys);
  149. assert_eq!(token.memo().clone(), transaction.memo);
  150. }
  151. /// Tests the NUT-06 functionality (mint discovery):
  152. /// 1. Alice gets funded with 64 sats
  153. /// 2. Verifies the initial mint URL is in the mint info
  154. /// 3. Updates the mint URL to a new value
  155. /// 4. Verifies the wallet balance is maintained after changing the mint URL
  156. #[tokio::test]
  157. async fn test_mint_nut06() {
  158. setup_tracing();
  159. let mint_bob = create_and_start_test_mint()
  160. .await
  161. .expect("Failed to create test mint");
  162. let mut wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  163. .await
  164. .expect("Failed to create test wallet");
  165. // Alice gets 64 sats
  166. fund_wallet(wallet_alice.clone(), 64, None)
  167. .await
  168. .expect("Failed to fund wallet");
  169. let balance_alice = wallet_alice
  170. .total_balance()
  171. .await
  172. .expect("Failed to get balance");
  173. assert_eq!(Amount::from(64), balance_alice);
  174. // Verify keyset amounts after minting
  175. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  176. let total_issued = mint_bob.total_issued().await.unwrap();
  177. let issued_amount = total_issued
  178. .get(&keyset_id)
  179. .copied()
  180. .unwrap_or(Amount::ZERO);
  181. assert_eq!(
  182. issued_amount,
  183. Amount::from(64),
  184. "Should have issued 64 sats"
  185. );
  186. let transaction = wallet_alice
  187. .list_transactions(None)
  188. .await
  189. .expect("Failed to list transactions")
  190. .pop()
  191. .expect("No transactions found");
  192. assert_eq!(wallet_alice.mint_url, transaction.mint_url);
  193. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  194. assert_eq!(Amount::from(64), transaction.amount);
  195. assert_eq!(Amount::from(0), transaction.fee);
  196. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  197. let initial_mint_url = wallet_alice.mint_url.clone();
  198. let mint_info_before = wallet_alice
  199. .fetch_mint_info()
  200. .await
  201. .expect("Failed to get mint info")
  202. .unwrap();
  203. assert!(mint_info_before
  204. .urls
  205. .unwrap()
  206. .contains(&initial_mint_url.to_string()));
  207. // Wallet updates mint URL
  208. let new_mint_url = MintUrl::from_str("https://new-mint-url").expect("Failed to parse mint URL");
  209. wallet_alice
  210. .update_mint_url(new_mint_url.clone())
  211. .await
  212. .expect("Failed to update mint URL");
  213. // Check balance after mint URL was updated
  214. let balance_alice_after = wallet_alice
  215. .total_balance()
  216. .await
  217. .expect("Failed to get balance after URL update");
  218. assert_eq!(Amount::from(64), balance_alice_after);
  219. }
  220. /// Attempt to double spend proofs on swap
  221. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  222. async fn test_mint_double_spend() {
  223. setup_tracing();
  224. let mint_bob = create_and_start_test_mint()
  225. .await
  226. .expect("Failed to create test mint");
  227. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  228. .await
  229. .expect("Failed to create test wallet");
  230. // Alice gets 64 sats
  231. fund_wallet(wallet_alice.clone(), 64, None)
  232. .await
  233. .expect("Failed to fund wallet");
  234. let proofs = wallet_alice
  235. .get_unspent_proofs()
  236. .await
  237. .expect("Could not get proofs");
  238. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  239. let keyset_id = keys.id;
  240. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  241. let preswap = PreMintSecrets::random(
  242. keyset_id,
  243. proofs.total_amount().unwrap(),
  244. &SplitTarget::default(),
  245. &fee_and_amounts,
  246. )
  247. .unwrap();
  248. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  249. let swap = mint_bob.process_swap_request(swap_request).await;
  250. assert!(swap.is_ok());
  251. let preswap_two = PreMintSecrets::random(
  252. keyset_id,
  253. proofs.total_amount().unwrap(),
  254. &SplitTarget::default(),
  255. &fee_and_amounts,
  256. )
  257. .unwrap();
  258. let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
  259. match mint_bob.process_swap_request(swap_two_request).await {
  260. Ok(_) => panic!("Proofs double spent"),
  261. Err(err) => match err {
  262. cdk::Error::TokenAlreadySpent => (),
  263. _ => panic!("Wrong error returned"),
  264. },
  265. }
  266. }
  267. /// This attempts to swap for more outputs then inputs.
  268. /// This will work if the mint does not check for outputs amounts overflowing
  269. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  270. async fn test_attempt_to_swap_by_overflowing() {
  271. setup_tracing();
  272. let mint_bob = create_and_start_test_mint()
  273. .await
  274. .expect("Failed to create test mint");
  275. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  276. .await
  277. .expect("Failed to create test wallet");
  278. // Alice gets 64 sats
  279. fund_wallet(wallet_alice.clone(), 64, None)
  280. .await
  281. .expect("Failed to fund wallet");
  282. let proofs = wallet_alice
  283. .get_unspent_proofs()
  284. .await
  285. .expect("Could not get proofs");
  286. let amount = 2_u64.pow(63);
  287. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  288. let keyset_id = keys.id;
  289. let pre_mint_amount = PreMintSecrets::from_secrets(
  290. keyset_id,
  291. vec![amount.into()],
  292. vec![cashu::secret::Secret::generate()],
  293. )
  294. .unwrap();
  295. let pre_mint_amount_two = PreMintSecrets::from_secrets(
  296. keyset_id,
  297. vec![amount.into()],
  298. vec![cashu::secret::Secret::generate()],
  299. )
  300. .unwrap();
  301. let mut pre_mint = PreMintSecrets::from_secrets(
  302. keyset_id,
  303. vec![1.into()],
  304. vec![cashu::secret::Secret::generate()],
  305. )
  306. .unwrap();
  307. pre_mint.combine(pre_mint_amount);
  308. pre_mint.combine(pre_mint_amount_two);
  309. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  310. match mint_bob.process_swap_request(swap_request).await {
  311. Ok(_) => panic!("Swap occurred with overflow"),
  312. Err(err) => match err {
  313. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  314. cdk::Error::AmountOverflow => (),
  315. cdk::Error::AmountError(_) => (),
  316. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  317. _ => {
  318. panic!("Wrong error returned in swap overflow {:?}", err);
  319. }
  320. },
  321. }
  322. }
  323. /// Tests that the mint correctly rejects unbalanced swap requests:
  324. /// 1. Attempts to swap for less than the input amount (95 < 100)
  325. /// 2. Attempts to swap for more than the input amount (101 > 100)
  326. /// 3. Both should fail with TransactionUnbalanced error
  327. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  328. async fn test_swap_unbalanced() {
  329. setup_tracing();
  330. let mint_bob = create_and_start_test_mint()
  331. .await
  332. .expect("Failed to create test mint");
  333. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  334. .await
  335. .expect("Failed to create test wallet");
  336. // Alice gets 100 sats
  337. fund_wallet(wallet_alice.clone(), 100, None)
  338. .await
  339. .expect("Failed to fund wallet");
  340. let proofs = wallet_alice
  341. .get_unspent_proofs()
  342. .await
  343. .expect("Could not get proofs");
  344. let keyset_id = get_keyset_id(&mint_bob).await;
  345. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  346. // Try to swap for less than the input amount (95 < 100)
  347. let preswap = PreMintSecrets::random(
  348. keyset_id,
  349. 95.into(),
  350. &SplitTarget::default(),
  351. &fee_and_amounts,
  352. )
  353. .expect("Failed to create preswap");
  354. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  355. match mint_bob.process_swap_request(swap_request).await {
  356. Ok(_) => panic!("Swap was allowed unbalanced"),
  357. Err(err) => match err {
  358. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  359. _ => panic!("Wrong error returned"),
  360. },
  361. }
  362. // Try to swap for more than the input amount (101 > 100)
  363. let preswap = PreMintSecrets::random(
  364. keyset_id,
  365. 101.into(),
  366. &SplitTarget::default(),
  367. &fee_and_amounts,
  368. )
  369. .expect("Failed to create preswap");
  370. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  371. match mint_bob.process_swap_request(swap_request).await {
  372. Ok(_) => panic!("Swap was allowed unbalanced"),
  373. Err(err) => match err {
  374. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  375. _ => panic!("Wrong error returned"),
  376. },
  377. }
  378. }
  379. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  380. pub async fn test_p2pk_swap() {
  381. setup_tracing();
  382. let mint_bob = create_and_start_test_mint()
  383. .await
  384. .expect("Failed to create test mint");
  385. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  386. .await
  387. .expect("Failed to create test wallet");
  388. // Alice gets 100 sats
  389. fund_wallet(wallet_alice.clone(), 100, None)
  390. .await
  391. .expect("Failed to fund wallet");
  392. let proofs = wallet_alice
  393. .get_unspent_proofs()
  394. .await
  395. .expect("Could not get proofs");
  396. let keyset_id = get_keyset_id(&mint_bob).await;
  397. let secret = SecretKey::generate();
  398. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  399. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  400. let pre_swap = PreMintSecrets::with_conditions(
  401. keyset_id,
  402. 100.into(),
  403. &SplitTarget::default(),
  404. &spending_conditions,
  405. &fee_and_amounts,
  406. )
  407. .unwrap();
  408. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  409. let keys = mint_bob.pubkeys().keysets.first().cloned().unwrap().keys;
  410. let post_swap = mint_bob.process_swap_request(swap_request).await.unwrap();
  411. let mut proofs = construct_proofs(
  412. post_swap.signatures,
  413. pre_swap.rs(),
  414. pre_swap.secrets(),
  415. &keys,
  416. )
  417. .unwrap();
  418. let pre_swap = PreMintSecrets::random(
  419. keyset_id,
  420. 100.into(),
  421. &SplitTarget::default(),
  422. &fee_and_amounts,
  423. )
  424. .unwrap();
  425. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  426. // Listen for status updates on all input proof pks
  427. let public_keys_to_listen: Vec<_> = swap_request
  428. .inputs()
  429. .ys()
  430. .unwrap()
  431. .iter()
  432. .map(|pk| pk.to_string())
  433. .collect();
  434. let mut listener = mint_bob
  435. .pubsub_manager()
  436. .subscribe(Params {
  437. kind: cdk::nuts::nut17::Kind::ProofState,
  438. filters: public_keys_to_listen.clone(),
  439. id: Arc::new("test".into()),
  440. })
  441. .expect("valid subscription");
  442. match mint_bob.process_swap_request(swap_request).await {
  443. Ok(_) => panic!("Proofs spent without sig"),
  444. Err(err) => match err {
  445. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  446. _ => {
  447. println!("{:?}", err);
  448. panic!("Wrong error returned")
  449. }
  450. },
  451. }
  452. for proof in &mut proofs {
  453. proof.sign_p2pk(secret.clone()).unwrap();
  454. }
  455. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  456. let attempt_swap = mint_bob.process_swap_request(swap_request).await;
  457. assert!(attempt_swap.is_ok());
  458. sleep(Duration::from_secs(1)).await;
  459. let mut msgs = HashMap::new();
  460. while let Some(msg) = listener.try_recv() {
  461. match msg.into_inner() {
  462. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  463. msgs.entry(y.to_string())
  464. .or_insert_with(Vec::new)
  465. .push(state);
  466. }
  467. _ => panic!("Wrong message received"),
  468. }
  469. }
  470. for (i, key) in public_keys_to_listen.into_iter().enumerate() {
  471. let statuses = msgs.remove(&key).expect("some events");
  472. // Every input pk receives two state updates, as there are only two state transitions
  473. assert_eq!(
  474. statuses,
  475. vec![State::Pending, State::Spent],
  476. "failed to test key {:?} (pos {})",
  477. key,
  478. i,
  479. );
  480. }
  481. assert!(listener.try_recv().is_none(), "no other event is happening");
  482. assert!(msgs.is_empty(), "Only expected key events are received");
  483. }
  484. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  485. async fn test_swap_overpay_underpay_fee() {
  486. setup_tracing();
  487. let mint_bob = create_and_start_test_mint()
  488. .await
  489. .expect("Failed to create test mint");
  490. mint_bob
  491. .rotate_keyset(
  492. CurrencyUnit::Sat,
  493. cdk_integration_tests::standard_keyset_amounts(32),
  494. 1,
  495. true,
  496. )
  497. .await
  498. .unwrap();
  499. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  500. .await
  501. .expect("Failed to create test wallet");
  502. // Alice gets 100 sats
  503. fund_wallet(wallet_alice.clone(), 1000, None)
  504. .await
  505. .expect("Failed to fund wallet");
  506. let proofs = wallet_alice
  507. .get_unspent_proofs()
  508. .await
  509. .expect("Could not get proofs");
  510. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  511. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  512. let preswap = PreMintSecrets::random(
  513. keyset_id,
  514. 9998.into(),
  515. &SplitTarget::default(),
  516. &fee_and_amounts,
  517. )
  518. .unwrap();
  519. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  520. // Attempt to swap overpaying fee
  521. match mint_bob.process_swap_request(swap_request).await {
  522. Ok(_) => panic!("Swap was allowed unbalanced"),
  523. Err(err) => match err {
  524. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  525. _ => {
  526. println!("{:?}", err);
  527. panic!("Wrong error returned")
  528. }
  529. },
  530. }
  531. let preswap = PreMintSecrets::random(
  532. keyset_id,
  533. 1000.into(),
  534. &SplitTarget::default(),
  535. &fee_and_amounts,
  536. )
  537. .unwrap();
  538. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  539. // Attempt to swap underpaying fee
  540. match mint_bob.process_swap_request(swap_request).await {
  541. Ok(_) => panic!("Swap was allowed unbalanced"),
  542. Err(err) => match err {
  543. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  544. _ => {
  545. println!("{:?}", err);
  546. panic!("Wrong error returned")
  547. }
  548. },
  549. }
  550. }
  551. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  552. async fn test_mint_enforce_fee() {
  553. setup_tracing();
  554. let mint_bob = create_and_start_test_mint()
  555. .await
  556. .expect("Failed to create test mint");
  557. mint_bob
  558. .rotate_keyset(
  559. CurrencyUnit::Sat,
  560. cdk_integration_tests::standard_keyset_amounts(32),
  561. 1,
  562. true,
  563. )
  564. .await
  565. .unwrap();
  566. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  567. .await
  568. .expect("Failed to create test wallet");
  569. // Alice gets 100 sats
  570. fund_wallet(
  571. wallet_alice.clone(),
  572. 1010,
  573. Some(SplitTarget::Value(Amount::ONE)),
  574. )
  575. .await
  576. .expect("Failed to fund wallet");
  577. let mut proofs = wallet_alice
  578. .get_unspent_proofs()
  579. .await
  580. .expect("Could not get proofs");
  581. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  582. let keyset_id = keys.id;
  583. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  584. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  585. let preswap = PreMintSecrets::random(
  586. keyset_id,
  587. 5.into(),
  588. &SplitTarget::default(),
  589. &fee_and_amounts,
  590. )
  591. .unwrap();
  592. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  593. // Attempt to swap underpaying fee
  594. match mint_bob.process_swap_request(swap_request).await {
  595. Ok(_) => panic!("Swap was allowed unbalanced"),
  596. Err(err) => match err {
  597. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  598. _ => {
  599. println!("{:?}", err);
  600. panic!("Wrong error returned")
  601. }
  602. },
  603. }
  604. let preswap = PreMintSecrets::random(
  605. keyset_id,
  606. 4.into(),
  607. &SplitTarget::default(),
  608. &fee_and_amounts,
  609. )
  610. .unwrap();
  611. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  612. let res = mint_bob.process_swap_request(swap_request).await;
  613. assert!(res.is_ok());
  614. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  615. let preswap = PreMintSecrets::random(
  616. keyset_id,
  617. 1000.into(),
  618. &SplitTarget::default(),
  619. &fee_and_amounts,
  620. )
  621. .unwrap();
  622. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  623. // Attempt to swap underpaying fee
  624. match mint_bob.process_swap_request(swap_request).await {
  625. Ok(_) => panic!("Swap was allowed unbalanced"),
  626. Err(err) => match err {
  627. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  628. _ => {
  629. println!("{:?}", err);
  630. panic!("Wrong error returned")
  631. }
  632. },
  633. }
  634. let preswap = PreMintSecrets::random(
  635. keyset_id,
  636. 999.into(),
  637. &SplitTarget::default(),
  638. &fee_and_amounts,
  639. )
  640. .unwrap();
  641. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  642. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  643. }
  644. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  645. async fn test_mint_max_outputs_exceeded_mint() {
  646. setup_tracing();
  647. // Set max outputs to 5
  648. let mint_bob = create_mint_with_limits(Some((100, 5)))
  649. .await
  650. .expect("Failed to create test mint");
  651. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  652. .await
  653. .expect("Failed to create test wallet");
  654. // Alice tries to mint 10 sats with split target 1 (requesting 10 outputs)
  655. // This should fail because max_outputs is 5
  656. let result = fund_wallet(
  657. wallet_alice.clone(),
  658. 10,
  659. Some(SplitTarget::Value(Amount::ONE)),
  660. )
  661. .await;
  662. match result {
  663. Ok(_) => panic!("Mint allowed exceeding max outputs"),
  664. Err(err) => {
  665. if let Some(cdk::Error::MaxOutputsExceeded { actual, max }) =
  666. err.downcast_ref::<cdk::Error>()
  667. {
  668. // actual might be more than 10 depending on internal splitting logic, but certainly > 5
  669. assert!(*actual >= 10);
  670. assert_eq!(*max, 5);
  671. } else {
  672. panic!("Wrong error returned: {:?}", err);
  673. }
  674. }
  675. }
  676. }
  677. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  678. async fn test_mint_max_inputs_exceeded_melt() {
  679. setup_tracing();
  680. // Set max inputs to 5
  681. let mint_bob = create_mint_with_limits(Some((5, 100)))
  682. .await
  683. .expect("Failed to create test mint");
  684. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  685. .await
  686. .expect("Failed to create test wallet");
  687. // Alice gets 10 sats with small outputs to have enough proofs
  688. fund_wallet(
  689. wallet_alice.clone(),
  690. 10,
  691. Some(SplitTarget::Value(Amount::ONE)),
  692. )
  693. .await
  694. .expect("Failed to fund wallet");
  695. let proofs = wallet_alice
  696. .get_unspent_proofs()
  697. .await
  698. .expect("Could not get proofs");
  699. // Use 6 proofs (limit is 5)
  700. let six_proofs: Vec<_> = proofs.iter().take(6).cloned().collect();
  701. assert_eq!(six_proofs.len(), 6);
  702. let fake_invoice = create_fake_invoice(1000, "".to_string());
  703. let melt_quote = wallet_alice
  704. .melt_quote(PaymentMethod::BOLT11, fake_invoice.to_string(), None, None)
  705. .await
  706. .expect("Failed to create melt quote");
  707. let melt_request = MeltRequest::new(melt_quote.id.parse().unwrap(), six_proofs, None);
  708. match mint_bob.melt(&melt_request).await {
  709. Ok(_) => panic!("Melt allowed exceeding max inputs"),
  710. Err(err) => match err {
  711. cdk::Error::MaxInputsExceeded { actual, max } => {
  712. assert_eq!(actual, 6);
  713. assert_eq!(max, 5);
  714. }
  715. _ => panic!("Wrong error returned: {:?}", err),
  716. },
  717. }
  718. }
  719. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  720. async fn test_mint_max_outputs_exceeded_melt() {
  721. setup_tracing();
  722. // Set max outputs to 20
  723. let mint_bob = create_mint_with_limits(Some((100, 20)))
  724. .await
  725. .expect("Failed to create test mint");
  726. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  727. .await
  728. .expect("Failed to create test wallet");
  729. // Alice gets 100 sats
  730. fund_wallet(wallet_alice.clone(), 100, None)
  731. .await
  732. .expect("Failed to fund wallet");
  733. let proofs = wallet_alice
  734. .get_unspent_proofs()
  735. .await
  736. .expect("Could not get proofs");
  737. let fake_invoice = create_fake_invoice(1000, "".to_string()); // 1000 msat = 1 sat
  738. let melt_quote = wallet_alice
  739. .melt_quote(PaymentMethod::BOLT11, fake_invoice.to_string(), None, None)
  740. .await
  741. .expect("Failed to create melt quote");
  742. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  743. let keyset_id = keys.id;
  744. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  745. // Create 21 blinded messages for change (limit is 20)
  746. let preswap = PreMintSecrets::random(
  747. keyset_id,
  748. 21.into(),
  749. &SplitTarget::Value(Amount::ONE),
  750. &fee_and_amounts,
  751. )
  752. .unwrap();
  753. let change_messages = preswap.blinded_messages();
  754. assert!(change_messages.len() >= 21);
  755. let excessive_change: Vec<_> = change_messages.into_iter().take(21).collect();
  756. let melt_request = MeltRequest::new(
  757. melt_quote.id.parse().unwrap(),
  758. proofs,
  759. Some(excessive_change),
  760. );
  761. match mint_bob.melt(&melt_request).await {
  762. Ok(_) => panic!("Melt allowed exceeding max outputs"),
  763. Err(err) => match err {
  764. cdk::Error::MaxOutputsExceeded { actual, max } => {
  765. assert_eq!(actual, 21);
  766. assert_eq!(max, 20);
  767. }
  768. _ => panic!("Wrong error returned: {:?}", err),
  769. },
  770. }
  771. }
  772. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  773. async fn test_mint_max_inputs_exceeded() {
  774. setup_tracing();
  775. let mint_bob = create_mint_with_limits(Some((5, 100)))
  776. .await
  777. .expect("Failed to create test mint");
  778. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  779. .await
  780. .expect("Failed to create test wallet");
  781. // Alice gets 100 sats with small outputs to have enough proofs
  782. fund_wallet(
  783. wallet_alice.clone(),
  784. 100,
  785. Some(SplitTarget::Value(Amount::ONE)),
  786. )
  787. .await
  788. .expect("Failed to fund wallet");
  789. let proofs = wallet_alice
  790. .get_unspent_proofs()
  791. .await
  792. .expect("Could not get proofs");
  793. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  794. let keyset_id = keys.id;
  795. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  796. // Use 6 proofs (limit is 5)
  797. let six_proofs: Vec<_> = proofs.iter().take(6).cloned().collect();
  798. assert_eq!(six_proofs.len(), 6);
  799. let preswap = PreMintSecrets::random(
  800. keyset_id,
  801. 6.into(),
  802. &SplitTarget::default(),
  803. &fee_and_amounts,
  804. )
  805. .unwrap();
  806. let swap_request = SwapRequest::new(six_proofs, preswap.blinded_messages());
  807. match mint_bob.process_swap_request(swap_request).await {
  808. Ok(_) => panic!("Swap allowed exceeding max inputs"),
  809. Err(err) => match err {
  810. cdk::Error::MaxInputsExceeded { actual, max } => {
  811. assert_eq!(actual, 6);
  812. assert_eq!(max, 5);
  813. }
  814. _ => panic!("Wrong error returned: {:?}", err),
  815. },
  816. }
  817. }
  818. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  819. async fn test_mint_max_outputs_exceeded() {
  820. setup_tracing();
  821. // Set max outputs to 20
  822. let mint_bob = create_mint_with_limits(Some((100, 20)))
  823. .await
  824. .expect("Failed to create test mint");
  825. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  826. .await
  827. .expect("Failed to create test wallet");
  828. // Alice gets 50 sats
  829. fund_wallet(wallet_alice.clone(), 50, None)
  830. .await
  831. .expect("Failed to fund wallet");
  832. let proofs = wallet_alice
  833. .get_unspent_proofs()
  834. .await
  835. .expect("Could not get proofs");
  836. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  837. let keyset_id = keys.id;
  838. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  839. // Try to split into 21 outputs (limit is 20)
  840. let preswap = PreMintSecrets::random(
  841. keyset_id,
  842. 21.into(),
  843. &SplitTarget::Value(Amount::ONE),
  844. &fee_and_amounts,
  845. )
  846. .unwrap();
  847. // Verify we generated enough messages
  848. let messages = preswap.blinded_messages();
  849. // We expect 21 messages because we asked for split target of 1 sat for 21 sats total.
  850. assert!(messages.len() >= 21);
  851. // Just take 21 messages to trigger the limit
  852. let excessive_messages: Vec<_> = messages.into_iter().take(21).collect();
  853. let swap_request = SwapRequest::new(proofs, excessive_messages);
  854. match mint_bob.process_swap_request(swap_request).await {
  855. Ok(_) => panic!("Swap allowed exceeding max outputs"),
  856. Err(err) => match err {
  857. cdk::Error::MaxOutputsExceeded { actual, max } => {
  858. assert_eq!(actual, 21);
  859. assert_eq!(max, 20);
  860. }
  861. _ => panic!("Wrong error returned: {:?}", err),
  862. },
  863. }
  864. }
  865. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  866. async fn test_mint_change_with_fee_melt() {
  867. setup_tracing();
  868. let mint_bob = create_and_start_test_mint()
  869. .await
  870. .expect("Failed to create test mint");
  871. mint_bob
  872. .rotate_keyset(
  873. CurrencyUnit::Sat,
  874. cdk_integration_tests::standard_keyset_amounts(32),
  875. 1,
  876. true,
  877. )
  878. .await
  879. .unwrap();
  880. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  881. .await
  882. .expect("Failed to create test wallet");
  883. // Alice gets 100 sats
  884. fund_wallet(
  885. wallet_alice.clone(),
  886. 100,
  887. Some(SplitTarget::Value(Amount::ONE)),
  888. )
  889. .await
  890. .expect("Failed to fund wallet");
  891. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  892. // Check amounts after minting
  893. let total_issued = mint_bob.total_issued().await.unwrap();
  894. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  895. let initial_issued = total_issued.get(&keyset_id).copied().unwrap_or_default();
  896. let initial_redeemed = total_redeemed
  897. .get(&keyset_id)
  898. .copied()
  899. .unwrap_or(Amount::ZERO);
  900. assert_eq!(
  901. initial_issued,
  902. Amount::from(100),
  903. "Should have issued 100 sats, got {:?}",
  904. total_issued
  905. );
  906. assert_eq!(
  907. initial_redeemed,
  908. Amount::ZERO,
  909. "Should have redeemed 0 sats initially, "
  910. );
  911. let proofs = wallet_alice
  912. .get_unspent_proofs()
  913. .await
  914. .expect("Could not get proofs");
  915. let fake_invoice = create_fake_invoice(1000, "".to_string());
  916. let melt_quote = wallet_alice
  917. .melt_quote(PaymentMethod::BOLT11, fake_invoice.to_string(), None, None)
  918. .await
  919. .unwrap();
  920. let prepared = wallet_alice
  921. .prepare_melt_proofs(&melt_quote.id, proofs, std::collections::HashMap::new())
  922. .await
  923. .unwrap();
  924. let w = prepared.confirm().await.unwrap();
  925. assert_eq!(w.change().unwrap().total_amount().unwrap(), 97.into());
  926. // Check amounts after melting
  927. // Melting redeems 100 sats and issues 97 sats as change
  928. let total_issued = mint_bob.total_issued().await.unwrap();
  929. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  930. let after_issued = total_issued
  931. .get(&keyset_id)
  932. .copied()
  933. .unwrap_or(Amount::ZERO);
  934. let after_redeemed = total_redeemed
  935. .get(&keyset_id)
  936. .copied()
  937. .unwrap_or(Amount::ZERO);
  938. assert_eq!(
  939. after_issued,
  940. Amount::from(197),
  941. "Should have issued 197 sats total (100 initial + 97 change)"
  942. );
  943. assert_eq!(
  944. after_redeemed,
  945. Amount::from(100),
  946. "Should have redeemed 100 sats from the melt"
  947. );
  948. }
  949. /// Tests concurrent double-spending attempts by trying to use the same proofs
  950. /// in 3 swap transactions simultaneously using tokio tasks
  951. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  952. async fn test_concurrent_double_spend_swap() {
  953. setup_tracing();
  954. let mint_bob = create_and_start_test_mint()
  955. .await
  956. .expect("Failed to create test mint");
  957. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  958. .await
  959. .expect("Failed to create test wallet");
  960. // Alice gets 100 sats
  961. fund_wallet(wallet_alice.clone(), 100, None)
  962. .await
  963. .expect("Failed to fund wallet");
  964. let proofs = wallet_alice
  965. .get_unspent_proofs()
  966. .await
  967. .expect("Could not get proofs");
  968. let keyset_id = get_keyset_id(&mint_bob).await;
  969. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  970. // Create 3 identical swap requests with the same proofs
  971. let preswap1 = PreMintSecrets::random(
  972. keyset_id,
  973. 100.into(),
  974. &SplitTarget::default(),
  975. &fee_and_amounts,
  976. )
  977. .expect("Failed to create preswap");
  978. let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  979. let preswap2 = PreMintSecrets::random(
  980. keyset_id,
  981. 100.into(),
  982. &SplitTarget::default(),
  983. &fee_and_amounts,
  984. )
  985. .expect("Failed to create preswap");
  986. let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  987. let preswap3 = PreMintSecrets::random(
  988. keyset_id,
  989. 100.into(),
  990. &SplitTarget::default(),
  991. &fee_and_amounts,
  992. )
  993. .expect("Failed to create preswap");
  994. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  995. // Spawn 3 concurrent tasks to process the swap requests
  996. let mint_clone1 = mint_bob.clone();
  997. let mint_clone2 = mint_bob.clone();
  998. let mint_clone3 = mint_bob.clone();
  999. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  1000. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  1001. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  1002. // Wait for all tasks to complete
  1003. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  1004. // Count successes and failures
  1005. let mut success_count = 0;
  1006. let mut token_already_spent_count = 0;
  1007. for result in [results.0, results.1, results.2] {
  1008. match result {
  1009. Ok(_) => success_count += 1,
  1010. Err(err) => match err {
  1011. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  1012. token_already_spent_count += 1
  1013. }
  1014. other_err => panic!("Unexpected error: {:?}", other_err),
  1015. },
  1016. }
  1017. }
  1018. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  1019. assert_eq!(1, success_count, "Expected exactly one successful swap");
  1020. assert_eq!(
  1021. 2, token_already_spent_count,
  1022. "Expected exactly two TokenAlreadySpent errors"
  1023. );
  1024. // Verify that all proofs are marked as spent in the mint
  1025. let states = mint_bob
  1026. .localstore()
  1027. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  1028. .await
  1029. .expect("Failed to get proof state");
  1030. for state in states {
  1031. assert_eq!(
  1032. State::Spent,
  1033. state.expect("Known state"),
  1034. "Expected proof to be marked as spent, but got {:?}",
  1035. state
  1036. );
  1037. }
  1038. }
  1039. /// Tests concurrent double-spending attempts by trying to use the same proofs
  1040. /// in 3 melt transactions simultaneously using tokio tasks
  1041. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  1042. async fn test_concurrent_double_spend_melt() {
  1043. setup_tracing();
  1044. let mint_bob = create_and_start_test_mint()
  1045. .await
  1046. .expect("Failed to create test mint");
  1047. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  1048. .await
  1049. .expect("Failed to create test wallet");
  1050. // Alice gets 100 sats
  1051. fund_wallet(wallet_alice.clone(), 100, None)
  1052. .await
  1053. .expect("Failed to fund wallet");
  1054. let proofs = wallet_alice
  1055. .get_unspent_proofs()
  1056. .await
  1057. .expect("Could not get proofs");
  1058. // Create a Lightning invoice for the melt
  1059. let invoice = create_fake_invoice(1000, "".to_string());
  1060. // Create a melt quote
  1061. let melt_quote = wallet_alice
  1062. .melt_quote(PaymentMethod::BOLT11, invoice.to_string(), None, None)
  1063. .await
  1064. .expect("Failed to create melt quote");
  1065. // Get the quote ID and payment request
  1066. let quote_id = melt_quote.id.clone();
  1067. // Create 3 identical melt requests with the same proofs
  1068. let mint_clone1 = mint_bob.clone();
  1069. let mint_clone2 = mint_bob.clone();
  1070. let mint_clone3 = mint_bob.clone();
  1071. let melt_request = MeltRequest::new(quote_id.parse().unwrap(), proofs.clone(), None);
  1072. let melt_request2 = melt_request.clone();
  1073. let melt_request3 = melt_request.clone();
  1074. // Spawn 3 concurrent tasks to process the melt requests
  1075. let task1 = tokio::spawn(async move { mint_clone1.melt(&melt_request).await });
  1076. let task2 = tokio::spawn(async move { mint_clone2.melt(&melt_request2).await });
  1077. let task3 = tokio::spawn(async move { mint_clone3.melt(&melt_request3).await });
  1078. // Wait for all tasks to complete
  1079. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  1080. // Count successes and failures
  1081. let mut success_count = 0;
  1082. let mut token_already_spent_count = 0;
  1083. for result in [results.0, results.1, results.2] {
  1084. match result {
  1085. Ok(_) => success_count += 1,
  1086. Err(err) => match err {
  1087. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  1088. token_already_spent_count += 1;
  1089. println!("Got expected error: {:?}", err);
  1090. }
  1091. other_err => {
  1092. println!("Got unexpected error: {:?}", other_err);
  1093. token_already_spent_count += 1;
  1094. }
  1095. },
  1096. }
  1097. }
  1098. // Only one melt should succeed, the other two should fail
  1099. assert_eq!(1, success_count, "Expected exactly one successful melt");
  1100. assert_eq!(
  1101. 2, token_already_spent_count,
  1102. "Expected exactly two TokenAlreadySpent errors"
  1103. );
  1104. // Verify that all proofs are marked as spent in the mint
  1105. let states = mint_bob
  1106. .localstore()
  1107. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  1108. .await
  1109. .expect("Failed to get proof state");
  1110. for state in states {
  1111. assert_eq!(
  1112. State::Spent,
  1113. state.expect("Known state"),
  1114. "Expected proof to be marked as spent, but got {:?}",
  1115. state
  1116. );
  1117. }
  1118. }
  1119. /// Tests that P2PK send with force_swap works when the mint charges fees.
  1120. ///
  1121. /// When a wallet has no proofs matching the P2PK spending conditions,
  1122. /// `prepare_send` sets `force_swap=true` and re-selects from all proofs.
  1123. /// All selected proofs are routed through a swap (which costs a fee).
  1124. ///
  1125. /// Bug: `select_proofs` was called with `include_fees=opts.include_fee`
  1126. /// instead of `include_fees=opts.include_fee || force_swap`, so with the
  1127. /// default `include_fee=false`, proofs were selected without accounting
  1128. /// for the swap fee. The swap then couldn't produce enough output,
  1129. /// causing `InsufficientFunds` during confirm.
  1130. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1131. async fn test_p2pk_send_force_swap_with_fees() {
  1132. setup_tracing();
  1133. // Create a mint with fee_ppk=1000 (1 sat per input proof)
  1134. let mint = create_mint_with_fee(1000)
  1135. .await
  1136. .expect("Failed to create test mint with fees");
  1137. let wallet = create_test_wallet_for_mint(mint.clone())
  1138. .await
  1139. .expect("Failed to create test wallet");
  1140. // Fund wallet with 64 sats (normal proofs, no P2PK conditions)
  1141. fund_wallet(wallet.clone(), 64, None)
  1142. .await
  1143. .expect("Failed to fund wallet");
  1144. assert_eq!(
  1145. Amount::from(64),
  1146. wallet.total_balance().await.expect("Failed to get balance")
  1147. );
  1148. // Generate P2PK spending conditions
  1149. let secret = SecretKey::generate();
  1150. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  1151. let send_amount = Amount::from(10);
  1152. // Attempt to send with P2PK conditions (triggers force_swap since no proofs match)
  1153. let prepared = wallet
  1154. .prepare_send(
  1155. send_amount,
  1156. SendOptions {
  1157. conditions: Some(spending_conditions),
  1158. ..Default::default() // include_fee: false
  1159. },
  1160. )
  1161. .await
  1162. .expect("prepare_send should select enough proofs to cover amount + swap fee");
  1163. let swap_fee = prepared.swap_fee();
  1164. assert!(
  1165. swap_fee > Amount::ZERO,
  1166. "Expected non-zero swap fee for force_swap with fee_ppk=1000"
  1167. );
  1168. // All proofs should be routed through swap (force_swap=true)
  1169. assert!(
  1170. !prepared.proofs_to_swap().is_empty(),
  1171. "Expected proofs_to_swap to be non-empty for force_swap"
  1172. );
  1173. assert!(
  1174. prepared.proofs_to_send().is_empty(),
  1175. "Expected proofs_to_send to be empty for force_swap"
  1176. );
  1177. // Confirm the send — this is where the bug manifests: the swap can't
  1178. // produce enough output because the selected proofs don't cover the fee
  1179. let token = prepared
  1180. .confirm(None)
  1181. .await
  1182. .expect("confirm should succeed — swap should produce enough output");
  1183. // Verify token contains exactly the requested amount
  1184. let keysets_info = wallet.get_mint_keysets().await.unwrap();
  1185. let token_proofs = token.proofs(&keysets_info).unwrap();
  1186. assert_eq!(
  1187. send_amount,
  1188. token_proofs.total_amount().unwrap(),
  1189. "Token should contain exactly the send amount"
  1190. );
  1191. // Verify wallet balance decreased by amount + swap_fee
  1192. let expected_balance = Amount::from(64) - send_amount - swap_fee;
  1193. assert_eq!(
  1194. expected_balance,
  1195. wallet.total_balance().await.unwrap(),
  1196. "Wallet balance should be reduced by send amount + swap fee"
  1197. );
  1198. }
  1199. /// Tests that P2PK send with force_swap and include_fee=true works when the
  1200. /// mint charges fees.
  1201. ///
  1202. /// Same scenario as above, but with `include_fee: true` so the token includes
  1203. /// enough value for the recipient to pay the redemption fee and receive the
  1204. /// full requested amount.
  1205. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1206. async fn test_p2pk_send_force_swap_with_fees_include_fee() {
  1207. setup_tracing();
  1208. // Create a mint with fee_ppk=1000 (1 sat per input proof)
  1209. let mint = create_mint_with_fee(1000)
  1210. .await
  1211. .expect("Failed to create test mint with fees");
  1212. let wallet_sender = create_test_wallet_for_mint(mint.clone())
  1213. .await
  1214. .expect("Failed to create sender wallet");
  1215. let wallet_receiver = create_test_wallet_for_mint(mint.clone())
  1216. .await
  1217. .expect("Failed to create receiver wallet");
  1218. // Fund sender with 64 sats
  1219. fund_wallet(wallet_sender.clone(), 64, None)
  1220. .await
  1221. .expect("Failed to fund wallet");
  1222. // Generate P2PK spending conditions
  1223. let secret = SecretKey::generate();
  1224. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  1225. let send_amount = Amount::from(10);
  1226. // Send with include_fee=true so token covers the redemption fee
  1227. let prepared = wallet_sender
  1228. .prepare_send(
  1229. send_amount,
  1230. SendOptions {
  1231. conditions: Some(spending_conditions),
  1232. include_fee: true,
  1233. ..Default::default()
  1234. },
  1235. )
  1236. .await
  1237. .expect("prepare_send should succeed with include_fee and force_swap");
  1238. let swap_fee = prepared.swap_fee();
  1239. let send_fee = prepared.send_fee();
  1240. assert!(
  1241. swap_fee > Amount::ZERO,
  1242. "Expected non-zero swap fee for force_swap with fee_ppk=1000"
  1243. );
  1244. assert!(
  1245. send_fee > Amount::ZERO,
  1246. "Expected non-zero send fee with include_fee=true and fee_ppk=1000"
  1247. );
  1248. let token = prepared
  1249. .confirm(None)
  1250. .await
  1251. .expect("confirm should succeed");
  1252. // Token should include amount + send_fee (so recipient can pay the redemption fee)
  1253. let keysets_info = wallet_sender.get_mint_keysets().await.unwrap();
  1254. let token_proofs = token.proofs(&keysets_info).unwrap();
  1255. assert_eq!(
  1256. send_amount + send_fee,
  1257. token_proofs.total_amount().unwrap(),
  1258. "Token should contain send amount + redemption fee"
  1259. );
  1260. // Receiver redeems the token using the P2PK signing key
  1261. let received_amount = wallet_receiver
  1262. .receive(
  1263. &token.to_string(),
  1264. ReceiveOptions {
  1265. p2pk_signing_keys: vec![secret],
  1266. ..Default::default()
  1267. },
  1268. )
  1269. .await
  1270. .expect("Receiver should be able to redeem P2PK token");
  1271. // Receiver should get exactly the send_amount after the redemption fee is deducted
  1272. assert_eq!(
  1273. send_amount, received_amount,
  1274. "Receiver should get exactly the requested amount after fees"
  1275. );
  1276. // Verify sender balance
  1277. let expected_sender_balance = Amount::from(64) - send_amount - swap_fee - send_fee;
  1278. assert_eq!(
  1279. expected_sender_balance,
  1280. wallet_sender.total_balance().await.unwrap(),
  1281. "Sender balance should be reduced by amount + swap_fee + send_fee"
  1282. );
  1283. }
  1284. async fn get_keyset_id(mint: &Mint) -> Id {
  1285. let keys = mint.pubkeys().keysets.first().unwrap().clone();
  1286. keys.verify_id()
  1287. .expect("Keyset ID generation is successful");
  1288. keys.id
  1289. }