integration_tests_pure.rs 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042
  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, PreMintSecrets, ProofState, SecretKey,
  22. 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. )
  127. .await
  128. .expect("Failed to receive proofs");
  129. assert_eq!(Amount::from(40), received_amount);
  130. assert_eq!(
  131. Amount::from(40),
  132. wallet_carol
  133. .total_balance()
  134. .await
  135. .expect("Failed to get Carol's balance")
  136. );
  137. let transaction = wallet_carol
  138. .get_transaction(transaction_id)
  139. .await
  140. .expect("Failed to get transaction")
  141. .expect("Transaction not found");
  142. assert_eq!(wallet_carol.mint_url, transaction.mint_url);
  143. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  144. assert_eq!(Amount::from(40), transaction.amount);
  145. assert_eq!(Amount::from(0), transaction.fee);
  146. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  147. assert_eq!(token_proofs.ys().unwrap(), transaction.ys);
  148. assert_eq!(token.memo().clone(), transaction.memo);
  149. }
  150. /// Tests the NUT-06 functionality (mint discovery):
  151. /// 1. Alice gets funded with 64 sats
  152. /// 2. Verifies the initial mint URL is in the mint info
  153. /// 3. Updates the mint URL to a new value
  154. /// 4. Verifies the wallet balance is maintained after changing the mint URL
  155. #[tokio::test]
  156. async fn test_mint_nut06() {
  157. setup_tracing();
  158. let mint_bob = create_and_start_test_mint()
  159. .await
  160. .expect("Failed to create test mint");
  161. let mut wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  162. .await
  163. .expect("Failed to create test wallet");
  164. // Alice gets 64 sats
  165. fund_wallet(wallet_alice.clone(), 64, None)
  166. .await
  167. .expect("Failed to fund wallet");
  168. let balance_alice = wallet_alice
  169. .total_balance()
  170. .await
  171. .expect("Failed to get balance");
  172. assert_eq!(Amount::from(64), balance_alice);
  173. // Verify keyset amounts after minting
  174. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  175. let total_issued = mint_bob.total_issued().await.unwrap();
  176. let issued_amount = total_issued
  177. .get(&keyset_id)
  178. .copied()
  179. .unwrap_or(Amount::ZERO);
  180. assert_eq!(
  181. issued_amount,
  182. Amount::from(64),
  183. "Should have issued 64 sats"
  184. );
  185. let transaction = wallet_alice
  186. .list_transactions(None)
  187. .await
  188. .expect("Failed to list transactions")
  189. .pop()
  190. .expect("No transactions found");
  191. assert_eq!(wallet_alice.mint_url, transaction.mint_url);
  192. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  193. assert_eq!(Amount::from(64), transaction.amount);
  194. assert_eq!(Amount::from(0), transaction.fee);
  195. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  196. let initial_mint_url = wallet_alice.mint_url.clone();
  197. let mint_info_before = wallet_alice
  198. .fetch_mint_info()
  199. .await
  200. .expect("Failed to get mint info")
  201. .unwrap();
  202. assert!(mint_info_before
  203. .urls
  204. .unwrap()
  205. .contains(&initial_mint_url.to_string()));
  206. // Wallet updates mint URL
  207. let new_mint_url = MintUrl::from_str("https://new-mint-url").expect("Failed to parse mint URL");
  208. wallet_alice
  209. .update_mint_url(new_mint_url.clone())
  210. .await
  211. .expect("Failed to update mint URL");
  212. // Check balance after mint URL was updated
  213. let balance_alice_after = wallet_alice
  214. .total_balance()
  215. .await
  216. .expect("Failed to get balance after URL update");
  217. assert_eq!(Amount::from(64), balance_alice_after);
  218. }
  219. /// Attempt to double spend proofs on swap
  220. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  221. async fn test_mint_double_spend() {
  222. setup_tracing();
  223. let mint_bob = create_and_start_test_mint()
  224. .await
  225. .expect("Failed to create test mint");
  226. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  227. .await
  228. .expect("Failed to create test wallet");
  229. // Alice gets 64 sats
  230. fund_wallet(wallet_alice.clone(), 64, None)
  231. .await
  232. .expect("Failed to fund wallet");
  233. let proofs = wallet_alice
  234. .get_unspent_proofs()
  235. .await
  236. .expect("Could not get proofs");
  237. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  238. let keyset_id = keys.id;
  239. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  240. let preswap = PreMintSecrets::random(
  241. keyset_id,
  242. proofs.total_amount().unwrap(),
  243. &SplitTarget::default(),
  244. &fee_and_amounts,
  245. )
  246. .unwrap();
  247. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  248. let swap = mint_bob.process_swap_request(swap_request).await;
  249. assert!(swap.is_ok());
  250. let preswap_two = PreMintSecrets::random(
  251. keyset_id,
  252. proofs.total_amount().unwrap(),
  253. &SplitTarget::default(),
  254. &fee_and_amounts,
  255. )
  256. .unwrap();
  257. let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
  258. match mint_bob.process_swap_request(swap_two_request).await {
  259. Ok(_) => panic!("Proofs double spent"),
  260. Err(err) => match err {
  261. cdk::Error::TokenAlreadySpent => (),
  262. _ => panic!("Wrong error returned"),
  263. },
  264. }
  265. }
  266. /// This attempts to swap for more outputs then inputs.
  267. /// This will work if the mint does not check for outputs amounts overflowing
  268. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  269. async fn test_attempt_to_swap_by_overflowing() {
  270. setup_tracing();
  271. let mint_bob = create_and_start_test_mint()
  272. .await
  273. .expect("Failed to create test mint");
  274. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  275. .await
  276. .expect("Failed to create test wallet");
  277. // Alice gets 64 sats
  278. fund_wallet(wallet_alice.clone(), 64, None)
  279. .await
  280. .expect("Failed to fund wallet");
  281. let proofs = wallet_alice
  282. .get_unspent_proofs()
  283. .await
  284. .expect("Could not get proofs");
  285. let amount = 2_u64.pow(63);
  286. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  287. let keyset_id = keys.id;
  288. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  289. let pre_mint_amount = PreMintSecrets::random(
  290. keyset_id,
  291. amount.into(),
  292. &SplitTarget::default(),
  293. &fee_and_amounts,
  294. )
  295. .unwrap();
  296. let pre_mint_amount_two = PreMintSecrets::random(
  297. keyset_id,
  298. amount.into(),
  299. &SplitTarget::default(),
  300. &fee_and_amounts,
  301. )
  302. .unwrap();
  303. let mut pre_mint = PreMintSecrets::random(
  304. keyset_id,
  305. 1.into(),
  306. &SplitTarget::default(),
  307. &fee_and_amounts,
  308. )
  309. .unwrap();
  310. pre_mint.combine(pre_mint_amount);
  311. pre_mint.combine(pre_mint_amount_two);
  312. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  313. match mint_bob.process_swap_request(swap_request).await {
  314. Ok(_) => panic!("Swap occurred with overflow"),
  315. Err(err) => match err {
  316. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  317. cdk::Error::AmountOverflow => (),
  318. cdk::Error::AmountError(_) => (),
  319. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  320. _ => {
  321. panic!("Wrong error returned in swap overflow {:?}", err);
  322. }
  323. },
  324. }
  325. }
  326. /// Tests that the mint correctly rejects unbalanced swap requests:
  327. /// 1. Attempts to swap for less than the input amount (95 < 100)
  328. /// 2. Attempts to swap for more than the input amount (101 > 100)
  329. /// 3. Both should fail with TransactionUnbalanced error
  330. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  331. async fn test_swap_unbalanced() {
  332. setup_tracing();
  333. let mint_bob = create_and_start_test_mint()
  334. .await
  335. .expect("Failed to create test mint");
  336. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  337. .await
  338. .expect("Failed to create test wallet");
  339. // Alice gets 100 sats
  340. fund_wallet(wallet_alice.clone(), 100, None)
  341. .await
  342. .expect("Failed to fund wallet");
  343. let proofs = wallet_alice
  344. .get_unspent_proofs()
  345. .await
  346. .expect("Could not get proofs");
  347. let keyset_id = get_keyset_id(&mint_bob).await;
  348. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  349. // Try to swap for less than the input amount (95 < 100)
  350. let preswap = PreMintSecrets::random(
  351. keyset_id,
  352. 95.into(),
  353. &SplitTarget::default(),
  354. &fee_and_amounts,
  355. )
  356. .expect("Failed to create preswap");
  357. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  358. match mint_bob.process_swap_request(swap_request).await {
  359. Ok(_) => panic!("Swap was allowed unbalanced"),
  360. Err(err) => match err {
  361. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  362. _ => panic!("Wrong error returned"),
  363. },
  364. }
  365. // Try to swap for more than the input amount (101 > 100)
  366. let preswap = PreMintSecrets::random(
  367. keyset_id,
  368. 101.into(),
  369. &SplitTarget::default(),
  370. &fee_and_amounts,
  371. )
  372. .expect("Failed to create preswap");
  373. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  374. match mint_bob.process_swap_request(swap_request).await {
  375. Ok(_) => panic!("Swap was allowed unbalanced"),
  376. Err(err) => match err {
  377. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  378. _ => panic!("Wrong error returned"),
  379. },
  380. }
  381. }
  382. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  383. pub async fn test_p2pk_swap() {
  384. setup_tracing();
  385. let mint_bob = create_and_start_test_mint()
  386. .await
  387. .expect("Failed to create test mint");
  388. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  389. .await
  390. .expect("Failed to create test wallet");
  391. // Alice gets 100 sats
  392. fund_wallet(wallet_alice.clone(), 100, None)
  393. .await
  394. .expect("Failed to fund wallet");
  395. let proofs = wallet_alice
  396. .get_unspent_proofs()
  397. .await
  398. .expect("Could not get proofs");
  399. let keyset_id = get_keyset_id(&mint_bob).await;
  400. let secret = SecretKey::generate();
  401. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  402. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  403. let pre_swap = PreMintSecrets::with_conditions(
  404. keyset_id,
  405. 100.into(),
  406. &SplitTarget::default(),
  407. &spending_conditions,
  408. &fee_and_amounts,
  409. )
  410. .unwrap();
  411. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  412. let keys = mint_bob.pubkeys().keysets.first().cloned().unwrap().keys;
  413. let post_swap = mint_bob.process_swap_request(swap_request).await.unwrap();
  414. let mut proofs = construct_proofs(
  415. post_swap.signatures,
  416. pre_swap.rs(),
  417. pre_swap.secrets(),
  418. &keys,
  419. )
  420. .unwrap();
  421. let pre_swap = PreMintSecrets::random(
  422. keyset_id,
  423. 100.into(),
  424. &SplitTarget::default(),
  425. &fee_and_amounts,
  426. )
  427. .unwrap();
  428. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  429. // Listen for status updates on all input proof pks
  430. let public_keys_to_listen: Vec<_> = swap_request
  431. .inputs()
  432. .ys()
  433. .unwrap()
  434. .iter()
  435. .map(|pk| pk.to_string())
  436. .collect();
  437. let mut listener = mint_bob
  438. .pubsub_manager()
  439. .subscribe(Params {
  440. kind: cdk::nuts::nut17::Kind::ProofState,
  441. filters: public_keys_to_listen.clone(),
  442. id: Arc::new("test".into()),
  443. })
  444. .expect("valid subscription");
  445. match mint_bob.process_swap_request(swap_request).await {
  446. Ok(_) => panic!("Proofs spent without sig"),
  447. Err(err) => match err {
  448. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  449. _ => {
  450. println!("{:?}", err);
  451. panic!("Wrong error returned")
  452. }
  453. },
  454. }
  455. for proof in &mut proofs {
  456. proof.sign_p2pk(secret.clone()).unwrap();
  457. }
  458. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  459. let attempt_swap = mint_bob.process_swap_request(swap_request).await;
  460. assert!(attempt_swap.is_ok());
  461. sleep(Duration::from_secs(1)).await;
  462. let mut msgs = HashMap::new();
  463. while let Some(msg) = listener.try_recv() {
  464. match msg.into_inner() {
  465. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  466. msgs.entry(y.to_string())
  467. .or_insert_with(Vec::new)
  468. .push(state);
  469. }
  470. _ => panic!("Wrong message received"),
  471. }
  472. }
  473. for (i, key) in public_keys_to_listen.into_iter().enumerate() {
  474. let statuses = msgs.remove(&key).expect("some events");
  475. // Every input pk receives two state updates, as there are only two state transitions
  476. assert_eq!(
  477. statuses,
  478. vec![State::Pending, State::Spent],
  479. "failed to test key {:?} (pos {})",
  480. key,
  481. i,
  482. );
  483. }
  484. assert!(listener.try_recv().is_none(), "no other event is happening");
  485. assert!(msgs.is_empty(), "Only expected key events are received");
  486. }
  487. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  488. async fn test_swap_overpay_underpay_fee() {
  489. setup_tracing();
  490. let mint_bob = create_and_start_test_mint()
  491. .await
  492. .expect("Failed to create test mint");
  493. mint_bob
  494. .rotate_keyset(CurrencyUnit::Sat, 32, 1)
  495. .await
  496. .unwrap();
  497. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  498. .await
  499. .expect("Failed to create test wallet");
  500. // Alice gets 100 sats
  501. fund_wallet(wallet_alice.clone(), 1000, None)
  502. .await
  503. .expect("Failed to fund wallet");
  504. let proofs = wallet_alice
  505. .get_unspent_proofs()
  506. .await
  507. .expect("Could not get proofs");
  508. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
  509. let keyset_id = Id::v1_from_keys(&keys);
  510. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  511. let preswap = PreMintSecrets::random(
  512. keyset_id,
  513. 9998.into(),
  514. &SplitTarget::default(),
  515. &fee_and_amounts,
  516. )
  517. .unwrap();
  518. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  519. // Attempt to swap overpaying fee
  520. match mint_bob.process_swap_request(swap_request).await {
  521. Ok(_) => panic!("Swap was allowed unbalanced"),
  522. Err(err) => match err {
  523. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  524. _ => {
  525. println!("{:?}", err);
  526. panic!("Wrong error returned")
  527. }
  528. },
  529. }
  530. let preswap = PreMintSecrets::random(
  531. keyset_id,
  532. 1000.into(),
  533. &SplitTarget::default(),
  534. &fee_and_amounts,
  535. )
  536. .unwrap();
  537. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  538. // Attempt to swap underpaying fee
  539. match mint_bob.process_swap_request(swap_request).await {
  540. Ok(_) => panic!("Swap was allowed unbalanced"),
  541. Err(err) => match err {
  542. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  543. _ => {
  544. println!("{:?}", err);
  545. panic!("Wrong error returned")
  546. }
  547. },
  548. }
  549. }
  550. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  551. async fn test_mint_enforce_fee() {
  552. setup_tracing();
  553. let mint_bob = create_and_start_test_mint()
  554. .await
  555. .expect("Failed to create test mint");
  556. mint_bob
  557. .rotate_keyset(CurrencyUnit::Sat, 32, 1)
  558. .await
  559. .unwrap();
  560. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  561. .await
  562. .expect("Failed to create test wallet");
  563. // Alice gets 100 sats
  564. fund_wallet(
  565. wallet_alice.clone(),
  566. 1010,
  567. Some(SplitTarget::Value(Amount::ONE)),
  568. )
  569. .await
  570. .expect("Failed to fund wallet");
  571. let mut proofs = wallet_alice
  572. .get_unspent_proofs()
  573. .await
  574. .expect("Could not get proofs");
  575. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  576. let keyset_id = keys.id;
  577. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  578. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  579. let preswap = PreMintSecrets::random(
  580. keyset_id,
  581. 5.into(),
  582. &SplitTarget::default(),
  583. &fee_and_amounts,
  584. )
  585. .unwrap();
  586. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  587. // Attempt to swap underpaying fee
  588. match mint_bob.process_swap_request(swap_request).await {
  589. Ok(_) => panic!("Swap was allowed unbalanced"),
  590. Err(err) => match err {
  591. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  592. _ => {
  593. println!("{:?}", err);
  594. panic!("Wrong error returned")
  595. }
  596. },
  597. }
  598. let preswap = PreMintSecrets::random(
  599. keyset_id,
  600. 4.into(),
  601. &SplitTarget::default(),
  602. &fee_and_amounts,
  603. )
  604. .unwrap();
  605. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  606. let res = mint_bob.process_swap_request(swap_request).await;
  607. assert!(res.is_ok());
  608. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  609. let preswap = PreMintSecrets::random(
  610. keyset_id,
  611. 1000.into(),
  612. &SplitTarget::default(),
  613. &fee_and_amounts,
  614. )
  615. .unwrap();
  616. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  617. // Attempt to swap underpaying fee
  618. match mint_bob.process_swap_request(swap_request).await {
  619. Ok(_) => panic!("Swap was allowed unbalanced"),
  620. Err(err) => match err {
  621. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  622. _ => {
  623. println!("{:?}", err);
  624. panic!("Wrong error returned")
  625. }
  626. },
  627. }
  628. let preswap = PreMintSecrets::random(
  629. keyset_id,
  630. 999.into(),
  631. &SplitTarget::default(),
  632. &fee_and_amounts,
  633. )
  634. .unwrap();
  635. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  636. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  637. }
  638. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  639. async fn test_mint_change_with_fee_melt() {
  640. setup_tracing();
  641. let mint_bob = create_and_start_test_mint()
  642. .await
  643. .expect("Failed to create test mint");
  644. mint_bob
  645. .rotate_keyset(CurrencyUnit::Sat, 32, 1)
  646. .await
  647. .unwrap();
  648. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  649. .await
  650. .expect("Failed to create test wallet");
  651. // Alice gets 100 sats
  652. fund_wallet(
  653. wallet_alice.clone(),
  654. 100,
  655. Some(SplitTarget::Value(Amount::ONE)),
  656. )
  657. .await
  658. .expect("Failed to fund wallet");
  659. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  660. // Check amounts after minting
  661. let total_issued = mint_bob.total_issued().await.unwrap();
  662. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  663. let initial_issued = total_issued.get(&keyset_id).copied().unwrap_or_default();
  664. let initial_redeemed = total_redeemed
  665. .get(&keyset_id)
  666. .copied()
  667. .unwrap_or(Amount::ZERO);
  668. assert_eq!(
  669. initial_issued,
  670. Amount::from(100),
  671. "Should have issued 100 sats, got {:?}",
  672. total_issued
  673. );
  674. assert_eq!(
  675. initial_redeemed,
  676. Amount::ZERO,
  677. "Should have redeemed 0 sats initially, "
  678. );
  679. let proofs = wallet_alice
  680. .get_unspent_proofs()
  681. .await
  682. .expect("Could not get proofs");
  683. let fake_invoice = create_fake_invoice(1000, "".to_string());
  684. let melt_quote = wallet_alice
  685. .melt_quote(fake_invoice.to_string(), None)
  686. .await
  687. .unwrap();
  688. let w = wallet_alice
  689. .melt_proofs(&melt_quote.id, proofs)
  690. .await
  691. .unwrap();
  692. assert_eq!(w.change.unwrap().total_amount().unwrap(), 97.into());
  693. // Check amounts after melting
  694. // Melting redeems 100 sats and issues 97 sats as change
  695. let total_issued = mint_bob.total_issued().await.unwrap();
  696. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  697. let after_issued = total_issued
  698. .get(&keyset_id)
  699. .copied()
  700. .unwrap_or(Amount::ZERO);
  701. let after_redeemed = total_redeemed
  702. .get(&keyset_id)
  703. .copied()
  704. .unwrap_or(Amount::ZERO);
  705. assert_eq!(
  706. after_issued,
  707. Amount::from(197),
  708. "Should have issued 197 sats total (100 initial + 97 change)"
  709. );
  710. assert_eq!(
  711. after_redeemed,
  712. Amount::from(100),
  713. "Should have redeemed 100 sats from the melt"
  714. );
  715. }
  716. /// Tests concurrent double-spending attempts by trying to use the same proofs
  717. /// in 3 swap transactions simultaneously using tokio tasks
  718. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  719. async fn test_concurrent_double_spend_swap() {
  720. setup_tracing();
  721. let mint_bob = create_and_start_test_mint()
  722. .await
  723. .expect("Failed to create test mint");
  724. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  725. .await
  726. .expect("Failed to create test wallet");
  727. // Alice gets 100 sats
  728. fund_wallet(wallet_alice.clone(), 100, None)
  729. .await
  730. .expect("Failed to fund wallet");
  731. let proofs = wallet_alice
  732. .get_unspent_proofs()
  733. .await
  734. .expect("Could not get proofs");
  735. let keyset_id = get_keyset_id(&mint_bob).await;
  736. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  737. // Create 3 identical swap requests with the same proofs
  738. let preswap1 = PreMintSecrets::random(
  739. keyset_id,
  740. 100.into(),
  741. &SplitTarget::default(),
  742. &fee_and_amounts,
  743. )
  744. .expect("Failed to create preswap");
  745. let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  746. let preswap2 = PreMintSecrets::random(
  747. keyset_id,
  748. 100.into(),
  749. &SplitTarget::default(),
  750. &fee_and_amounts,
  751. )
  752. .expect("Failed to create preswap");
  753. let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  754. let preswap3 = PreMintSecrets::random(
  755. keyset_id,
  756. 100.into(),
  757. &SplitTarget::default(),
  758. &fee_and_amounts,
  759. )
  760. .expect("Failed to create preswap");
  761. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  762. // Spawn 3 concurrent tasks to process the swap requests
  763. let mint_clone1 = mint_bob.clone();
  764. let mint_clone2 = mint_bob.clone();
  765. let mint_clone3 = mint_bob.clone();
  766. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  767. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  768. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  769. // Wait for all tasks to complete
  770. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  771. // Count successes and failures
  772. let mut success_count = 0;
  773. let mut token_already_spent_count = 0;
  774. for result in [results.0, results.1, results.2] {
  775. match result {
  776. Ok(_) => success_count += 1,
  777. Err(err) => match err {
  778. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  779. token_already_spent_count += 1
  780. }
  781. other_err => panic!("Unexpected error: {:?}", other_err),
  782. },
  783. }
  784. }
  785. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  786. assert_eq!(1, success_count, "Expected exactly one successful swap");
  787. assert_eq!(
  788. 2, token_already_spent_count,
  789. "Expected exactly two TokenAlreadySpent errors"
  790. );
  791. // Verify that all proofs are marked as spent in the mint
  792. let states = mint_bob
  793. .localstore()
  794. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  795. .await
  796. .expect("Failed to get proof state");
  797. for state in states {
  798. assert_eq!(
  799. State::Spent,
  800. state.expect("Known state"),
  801. "Expected proof to be marked as spent, but got {:?}",
  802. state
  803. );
  804. }
  805. }
  806. /// Tests concurrent double-spending attempts by trying to use the same proofs
  807. /// in 3 melt transactions simultaneously using tokio tasks
  808. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  809. async fn test_concurrent_double_spend_melt() {
  810. setup_tracing();
  811. let mint_bob = create_and_start_test_mint()
  812. .await
  813. .expect("Failed to create test mint");
  814. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  815. .await
  816. .expect("Failed to create test wallet");
  817. // Alice gets 100 sats
  818. fund_wallet(wallet_alice.clone(), 100, None)
  819. .await
  820. .expect("Failed to fund wallet");
  821. let proofs = wallet_alice
  822. .get_unspent_proofs()
  823. .await
  824. .expect("Could not get proofs");
  825. // Create a Lightning invoice for the melt
  826. let invoice = create_fake_invoice(1000, "".to_string());
  827. // Create a melt quote
  828. let melt_quote = wallet_alice
  829. .melt_quote(invoice.to_string(), None)
  830. .await
  831. .expect("Failed to create melt quote");
  832. // Get the quote ID and payment request
  833. let quote_id = melt_quote.id.clone();
  834. // Create 3 identical melt requests with the same proofs
  835. let mint_clone1 = mint_bob.clone();
  836. let mint_clone2 = mint_bob.clone();
  837. let mint_clone3 = mint_bob.clone();
  838. let melt_request = MeltRequest::new(quote_id.parse().unwrap(), proofs.clone(), None);
  839. let melt_request2 = melt_request.clone();
  840. let melt_request3 = melt_request.clone();
  841. // Spawn 3 concurrent tasks to process the melt requests
  842. let task1 = tokio::spawn(async move { mint_clone1.melt(&melt_request).await });
  843. let task2 = tokio::spawn(async move { mint_clone2.melt(&melt_request2).await });
  844. let task3 = tokio::spawn(async move { mint_clone3.melt(&melt_request3).await });
  845. // Wait for all tasks to complete
  846. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  847. // Count successes and failures
  848. let mut success_count = 0;
  849. let mut token_already_spent_count = 0;
  850. for result in [results.0, results.1, results.2] {
  851. match result {
  852. Ok(_) => success_count += 1,
  853. Err(err) => match err {
  854. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  855. token_already_spent_count += 1;
  856. println!("Got expected error: {:?}", err);
  857. }
  858. other_err => {
  859. println!("Got unexpected error: {:?}", other_err);
  860. token_already_spent_count += 1;
  861. }
  862. },
  863. }
  864. }
  865. // Only one melt should succeed, the other two should fail
  866. assert_eq!(1, success_count, "Expected exactly one successful melt");
  867. assert_eq!(
  868. 2, token_already_spent_count,
  869. "Expected exactly two TokenAlreadySpent errors"
  870. );
  871. // Verify that all proofs are marked as spent in the mint
  872. let states = mint_bob
  873. .localstore()
  874. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  875. .await
  876. .expect("Failed to get proof state");
  877. for state in states {
  878. assert_eq!(
  879. State::Spent,
  880. state.expect("Known state"),
  881. "Expected proof to be marked as spent, but got {:?}",
  882. state
  883. );
  884. }
  885. }
  886. async fn get_keyset_id(mint: &Mint) -> Id {
  887. let keys = mint.pubkeys().keysets.first().unwrap().clone();
  888. keys.verify_id()
  889. .expect("Keyset ID generation is successful");
  890. keys.id
  891. }