integration_tests_pure.rs 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054
  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(
  495. CurrencyUnit::Sat,
  496. cdk_integration_tests::standard_keyset_amounts(32),
  497. 1,
  498. )
  499. .await
  500. .unwrap();
  501. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  502. .await
  503. .expect("Failed to create test wallet");
  504. // Alice gets 100 sats
  505. fund_wallet(wallet_alice.clone(), 1000, None)
  506. .await
  507. .expect("Failed to fund wallet");
  508. let proofs = wallet_alice
  509. .get_unspent_proofs()
  510. .await
  511. .expect("Could not get proofs");
  512. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone().keys;
  513. let keyset_id = Id::v1_from_keys(&keys);
  514. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  515. let preswap = PreMintSecrets::random(
  516. keyset_id,
  517. 9998.into(),
  518. &SplitTarget::default(),
  519. &fee_and_amounts,
  520. )
  521. .unwrap();
  522. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  523. // Attempt to swap overpaying fee
  524. match mint_bob.process_swap_request(swap_request).await {
  525. Ok(_) => panic!("Swap was allowed unbalanced"),
  526. Err(err) => match err {
  527. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  528. _ => {
  529. println!("{:?}", err);
  530. panic!("Wrong error returned")
  531. }
  532. },
  533. }
  534. let preswap = PreMintSecrets::random(
  535. keyset_id,
  536. 1000.into(),
  537. &SplitTarget::default(),
  538. &fee_and_amounts,
  539. )
  540. .unwrap();
  541. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  542. // Attempt to swap underpaying fee
  543. match mint_bob.process_swap_request(swap_request).await {
  544. Ok(_) => panic!("Swap was allowed unbalanced"),
  545. Err(err) => match err {
  546. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  547. _ => {
  548. println!("{:?}", err);
  549. panic!("Wrong error returned")
  550. }
  551. },
  552. }
  553. }
  554. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  555. async fn test_mint_enforce_fee() {
  556. setup_tracing();
  557. let mint_bob = create_and_start_test_mint()
  558. .await
  559. .expect("Failed to create test mint");
  560. mint_bob
  561. .rotate_keyset(
  562. CurrencyUnit::Sat,
  563. cdk_integration_tests::standard_keyset_amounts(32),
  564. 1,
  565. )
  566. .await
  567. .unwrap();
  568. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  569. .await
  570. .expect("Failed to create test wallet");
  571. // Alice gets 100 sats
  572. fund_wallet(
  573. wallet_alice.clone(),
  574. 1010,
  575. Some(SplitTarget::Value(Amount::ONE)),
  576. )
  577. .await
  578. .expect("Failed to fund wallet");
  579. let mut proofs = wallet_alice
  580. .get_unspent_proofs()
  581. .await
  582. .expect("Could not get proofs");
  583. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  584. let keyset_id = keys.id;
  585. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  586. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  587. let preswap = PreMintSecrets::random(
  588. keyset_id,
  589. 5.into(),
  590. &SplitTarget::default(),
  591. &fee_and_amounts,
  592. )
  593. .unwrap();
  594. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  595. // Attempt to swap underpaying fee
  596. match mint_bob.process_swap_request(swap_request).await {
  597. Ok(_) => panic!("Swap was allowed unbalanced"),
  598. Err(err) => match err {
  599. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  600. _ => {
  601. println!("{:?}", err);
  602. panic!("Wrong error returned")
  603. }
  604. },
  605. }
  606. let preswap = PreMintSecrets::random(
  607. keyset_id,
  608. 4.into(),
  609. &SplitTarget::default(),
  610. &fee_and_amounts,
  611. )
  612. .unwrap();
  613. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  614. let res = mint_bob.process_swap_request(swap_request).await;
  615. assert!(res.is_ok());
  616. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  617. let preswap = PreMintSecrets::random(
  618. keyset_id,
  619. 1000.into(),
  620. &SplitTarget::default(),
  621. &fee_and_amounts,
  622. )
  623. .unwrap();
  624. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  625. // Attempt to swap underpaying fee
  626. match mint_bob.process_swap_request(swap_request).await {
  627. Ok(_) => panic!("Swap was allowed unbalanced"),
  628. Err(err) => match err {
  629. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  630. _ => {
  631. println!("{:?}", err);
  632. panic!("Wrong error returned")
  633. }
  634. },
  635. }
  636. let preswap = PreMintSecrets::random(
  637. keyset_id,
  638. 999.into(),
  639. &SplitTarget::default(),
  640. &fee_and_amounts,
  641. )
  642. .unwrap();
  643. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  644. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  645. }
  646. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  647. async fn test_mint_change_with_fee_melt() {
  648. setup_tracing();
  649. let mint_bob = create_and_start_test_mint()
  650. .await
  651. .expect("Failed to create test mint");
  652. mint_bob
  653. .rotate_keyset(
  654. CurrencyUnit::Sat,
  655. cdk_integration_tests::standard_keyset_amounts(32),
  656. 1,
  657. )
  658. .await
  659. .unwrap();
  660. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  661. .await
  662. .expect("Failed to create test wallet");
  663. // Alice gets 100 sats
  664. fund_wallet(
  665. wallet_alice.clone(),
  666. 100,
  667. Some(SplitTarget::Value(Amount::ONE)),
  668. )
  669. .await
  670. .expect("Failed to fund wallet");
  671. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  672. // Check amounts after minting
  673. let total_issued = mint_bob.total_issued().await.unwrap();
  674. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  675. let initial_issued = total_issued.get(&keyset_id).copied().unwrap_or_default();
  676. let initial_redeemed = total_redeemed
  677. .get(&keyset_id)
  678. .copied()
  679. .unwrap_or(Amount::ZERO);
  680. assert_eq!(
  681. initial_issued,
  682. Amount::from(100),
  683. "Should have issued 100 sats, got {:?}",
  684. total_issued
  685. );
  686. assert_eq!(
  687. initial_redeemed,
  688. Amount::ZERO,
  689. "Should have redeemed 0 sats initially, "
  690. );
  691. let proofs = wallet_alice
  692. .get_unspent_proofs()
  693. .await
  694. .expect("Could not get proofs");
  695. let fake_invoice = create_fake_invoice(1000, "".to_string());
  696. let melt_quote = wallet_alice
  697. .melt_quote(fake_invoice.to_string(), None)
  698. .await
  699. .unwrap();
  700. let w = wallet_alice
  701. .melt_proofs(&melt_quote.id, proofs)
  702. .await
  703. .unwrap();
  704. assert_eq!(w.change.unwrap().total_amount().unwrap(), 97.into());
  705. // Check amounts after melting
  706. // Melting redeems 100 sats and issues 97 sats as change
  707. let total_issued = mint_bob.total_issued().await.unwrap();
  708. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  709. let after_issued = total_issued
  710. .get(&keyset_id)
  711. .copied()
  712. .unwrap_or(Amount::ZERO);
  713. let after_redeemed = total_redeemed
  714. .get(&keyset_id)
  715. .copied()
  716. .unwrap_or(Amount::ZERO);
  717. assert_eq!(
  718. after_issued,
  719. Amount::from(197),
  720. "Should have issued 197 sats total (100 initial + 97 change)"
  721. );
  722. assert_eq!(
  723. after_redeemed,
  724. Amount::from(100),
  725. "Should have redeemed 100 sats from the melt"
  726. );
  727. }
  728. /// Tests concurrent double-spending attempts by trying to use the same proofs
  729. /// in 3 swap transactions simultaneously using tokio tasks
  730. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  731. async fn test_concurrent_double_spend_swap() {
  732. setup_tracing();
  733. let mint_bob = create_and_start_test_mint()
  734. .await
  735. .expect("Failed to create test mint");
  736. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  737. .await
  738. .expect("Failed to create test wallet");
  739. // Alice gets 100 sats
  740. fund_wallet(wallet_alice.clone(), 100, None)
  741. .await
  742. .expect("Failed to fund wallet");
  743. let proofs = wallet_alice
  744. .get_unspent_proofs()
  745. .await
  746. .expect("Could not get proofs");
  747. let keyset_id = get_keyset_id(&mint_bob).await;
  748. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  749. // Create 3 identical swap requests with the same proofs
  750. let preswap1 = PreMintSecrets::random(
  751. keyset_id,
  752. 100.into(),
  753. &SplitTarget::default(),
  754. &fee_and_amounts,
  755. )
  756. .expect("Failed to create preswap");
  757. let swap_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  758. let preswap2 = PreMintSecrets::random(
  759. keyset_id,
  760. 100.into(),
  761. &SplitTarget::default(),
  762. &fee_and_amounts,
  763. )
  764. .expect("Failed to create preswap");
  765. let swap_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  766. let preswap3 = PreMintSecrets::random(
  767. keyset_id,
  768. 100.into(),
  769. &SplitTarget::default(),
  770. &fee_and_amounts,
  771. )
  772. .expect("Failed to create preswap");
  773. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  774. // Spawn 3 concurrent tasks to process the swap requests
  775. let mint_clone1 = mint_bob.clone();
  776. let mint_clone2 = mint_bob.clone();
  777. let mint_clone3 = mint_bob.clone();
  778. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  779. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  780. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  781. // Wait for all tasks to complete
  782. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  783. // Count successes and failures
  784. let mut success_count = 0;
  785. let mut token_already_spent_count = 0;
  786. for result in [results.0, results.1, results.2] {
  787. match result {
  788. Ok(_) => success_count += 1,
  789. Err(err) => match err {
  790. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  791. token_already_spent_count += 1
  792. }
  793. other_err => panic!("Unexpected error: {:?}", other_err),
  794. },
  795. }
  796. }
  797. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  798. assert_eq!(1, success_count, "Expected exactly one successful swap");
  799. assert_eq!(
  800. 2, token_already_spent_count,
  801. "Expected exactly two TokenAlreadySpent errors"
  802. );
  803. // Verify that all proofs are marked as spent in the mint
  804. let states = mint_bob
  805. .localstore()
  806. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  807. .await
  808. .expect("Failed to get proof state");
  809. for state in states {
  810. assert_eq!(
  811. State::Spent,
  812. state.expect("Known state"),
  813. "Expected proof to be marked as spent, but got {:?}",
  814. state
  815. );
  816. }
  817. }
  818. /// Tests concurrent double-spending attempts by trying to use the same proofs
  819. /// in 3 melt transactions simultaneously using tokio tasks
  820. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  821. async fn test_concurrent_double_spend_melt() {
  822. setup_tracing();
  823. let mint_bob = create_and_start_test_mint()
  824. .await
  825. .expect("Failed to create test mint");
  826. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  827. .await
  828. .expect("Failed to create test wallet");
  829. // Alice gets 100 sats
  830. fund_wallet(wallet_alice.clone(), 100, None)
  831. .await
  832. .expect("Failed to fund wallet");
  833. let proofs = wallet_alice
  834. .get_unspent_proofs()
  835. .await
  836. .expect("Could not get proofs");
  837. // Create a Lightning invoice for the melt
  838. let invoice = create_fake_invoice(1000, "".to_string());
  839. // Create a melt quote
  840. let melt_quote = wallet_alice
  841. .melt_quote(invoice.to_string(), None)
  842. .await
  843. .expect("Failed to create melt quote");
  844. // Get the quote ID and payment request
  845. let quote_id = melt_quote.id.clone();
  846. // Create 3 identical melt requests with the same proofs
  847. let mint_clone1 = mint_bob.clone();
  848. let mint_clone2 = mint_bob.clone();
  849. let mint_clone3 = mint_bob.clone();
  850. let melt_request = MeltRequest::new(quote_id.parse().unwrap(), proofs.clone(), None);
  851. let melt_request2 = melt_request.clone();
  852. let melt_request3 = melt_request.clone();
  853. // Spawn 3 concurrent tasks to process the melt requests
  854. let task1 = tokio::spawn(async move { mint_clone1.melt(&melt_request).await });
  855. let task2 = tokio::spawn(async move { mint_clone2.melt(&melt_request2).await });
  856. let task3 = tokio::spawn(async move { mint_clone3.melt(&melt_request3).await });
  857. // Wait for all tasks to complete
  858. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  859. // Count successes and failures
  860. let mut success_count = 0;
  861. let mut token_already_spent_count = 0;
  862. for result in [results.0, results.1, results.2] {
  863. match result {
  864. Ok(_) => success_count += 1,
  865. Err(err) => match err {
  866. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  867. token_already_spent_count += 1;
  868. println!("Got expected error: {:?}", err);
  869. }
  870. other_err => {
  871. println!("Got unexpected error: {:?}", other_err);
  872. token_already_spent_count += 1;
  873. }
  874. },
  875. }
  876. }
  877. // Only one melt should succeed, the other two should fail
  878. assert_eq!(1, success_count, "Expected exactly one successful melt");
  879. assert_eq!(
  880. 2, token_already_spent_count,
  881. "Expected exactly two TokenAlreadySpent errors"
  882. );
  883. // Verify that all proofs are marked as spent in the mint
  884. let states = mint_bob
  885. .localstore()
  886. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  887. .await
  888. .expect("Failed to get proof state");
  889. for state in states {
  890. assert_eq!(
  891. State::Spent,
  892. state.expect("Known state"),
  893. "Expected proof to be marked as spent, but got {:?}",
  894. state
  895. );
  896. }
  897. }
  898. async fn get_keyset_id(mint: &Mint) -> Id {
  899. let keys = mint.pubkeys().keysets.first().unwrap().clone();
  900. keys.verify_id()
  901. .expect("Keyset ID generation is successful");
  902. keys.id
  903. }