integration_tests_pure.rs 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050
  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 pre_mint_amount = PreMintSecrets::from_secrets(
  289. keyset_id,
  290. vec![amount.into()],
  291. vec![cashu::secret::Secret::generate()],
  292. )
  293. .unwrap();
  294. let pre_mint_amount_two = PreMintSecrets::from_secrets(
  295. keyset_id,
  296. vec![amount.into()],
  297. vec![cashu::secret::Secret::generate()],
  298. )
  299. .unwrap();
  300. let mut pre_mint = PreMintSecrets::from_secrets(
  301. keyset_id,
  302. vec![1.into()],
  303. vec![cashu::secret::Secret::generate()],
  304. )
  305. .unwrap();
  306. pre_mint.combine(pre_mint_amount);
  307. pre_mint.combine(pre_mint_amount_two);
  308. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  309. match mint_bob.process_swap_request(swap_request).await {
  310. Ok(_) => panic!("Swap occurred with overflow"),
  311. Err(err) => match err {
  312. cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
  313. cdk::Error::AmountOverflow => (),
  314. cdk::Error::AmountError(_) => (),
  315. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  316. _ => {
  317. panic!("Wrong error returned in swap overflow {:?}", err);
  318. }
  319. },
  320. }
  321. }
  322. /// Tests that the mint correctly rejects unbalanced swap requests:
  323. /// 1. Attempts to swap for less than the input amount (95 < 100)
  324. /// 2. Attempts to swap for more than the input amount (101 > 100)
  325. /// 3. Both should fail with TransactionUnbalanced error
  326. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  327. async fn test_swap_unbalanced() {
  328. setup_tracing();
  329. let mint_bob = create_and_start_test_mint()
  330. .await
  331. .expect("Failed to create test mint");
  332. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  333. .await
  334. .expect("Failed to create test wallet");
  335. // Alice gets 100 sats
  336. fund_wallet(wallet_alice.clone(), 100, None)
  337. .await
  338. .expect("Failed to fund wallet");
  339. let proofs = wallet_alice
  340. .get_unspent_proofs()
  341. .await
  342. .expect("Could not get proofs");
  343. let keyset_id = get_keyset_id(&mint_bob).await;
  344. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  345. // Try to swap for less than the input amount (95 < 100)
  346. let preswap = PreMintSecrets::random(
  347. keyset_id,
  348. 95.into(),
  349. &SplitTarget::default(),
  350. &fee_and_amounts,
  351. )
  352. .expect("Failed to create preswap");
  353. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  354. match mint_bob.process_swap_request(swap_request).await {
  355. Ok(_) => panic!("Swap was allowed unbalanced"),
  356. Err(err) => match err {
  357. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  358. _ => panic!("Wrong error returned"),
  359. },
  360. }
  361. // Try to swap for more than the input amount (101 > 100)
  362. let preswap = PreMintSecrets::random(
  363. keyset_id,
  364. 101.into(),
  365. &SplitTarget::default(),
  366. &fee_and_amounts,
  367. )
  368. .expect("Failed to create preswap");
  369. let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
  370. match mint_bob.process_swap_request(swap_request).await {
  371. Ok(_) => panic!("Swap was allowed unbalanced"),
  372. Err(err) => match err {
  373. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  374. _ => panic!("Wrong error returned"),
  375. },
  376. }
  377. }
  378. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  379. pub async fn test_p2pk_swap() {
  380. setup_tracing();
  381. let mint_bob = create_and_start_test_mint()
  382. .await
  383. .expect("Failed to create test mint");
  384. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  385. .await
  386. .expect("Failed to create test wallet");
  387. // Alice gets 100 sats
  388. fund_wallet(wallet_alice.clone(), 100, None)
  389. .await
  390. .expect("Failed to fund wallet");
  391. let proofs = wallet_alice
  392. .get_unspent_proofs()
  393. .await
  394. .expect("Could not get proofs");
  395. let keyset_id = get_keyset_id(&mint_bob).await;
  396. let secret = SecretKey::generate();
  397. let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
  398. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  399. let pre_swap = PreMintSecrets::with_conditions(
  400. keyset_id,
  401. 100.into(),
  402. &SplitTarget::default(),
  403. &spending_conditions,
  404. &fee_and_amounts,
  405. )
  406. .unwrap();
  407. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  408. let keys = mint_bob.pubkeys().keysets.first().cloned().unwrap().keys;
  409. let post_swap = mint_bob.process_swap_request(swap_request).await.unwrap();
  410. let mut proofs = construct_proofs(
  411. post_swap.signatures,
  412. pre_swap.rs(),
  413. pre_swap.secrets(),
  414. &keys,
  415. )
  416. .unwrap();
  417. let pre_swap = PreMintSecrets::random(
  418. keyset_id,
  419. 100.into(),
  420. &SplitTarget::default(),
  421. &fee_and_amounts,
  422. )
  423. .unwrap();
  424. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  425. // Listen for status updates on all input proof pks
  426. let public_keys_to_listen: Vec<_> = swap_request
  427. .inputs()
  428. .ys()
  429. .unwrap()
  430. .iter()
  431. .map(|pk| pk.to_string())
  432. .collect();
  433. let mut listener = mint_bob
  434. .pubsub_manager()
  435. .subscribe(Params {
  436. kind: cdk::nuts::nut17::Kind::ProofState,
  437. filters: public_keys_to_listen.clone(),
  438. id: Arc::new("test".into()),
  439. })
  440. .expect("valid subscription");
  441. match mint_bob.process_swap_request(swap_request).await {
  442. Ok(_) => panic!("Proofs spent without sig"),
  443. Err(err) => match err {
  444. cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
  445. _ => {
  446. println!("{:?}", err);
  447. panic!("Wrong error returned")
  448. }
  449. },
  450. }
  451. for proof in &mut proofs {
  452. proof.sign_p2pk(secret.clone()).unwrap();
  453. }
  454. let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
  455. let attempt_swap = mint_bob.process_swap_request(swap_request).await;
  456. assert!(attempt_swap.is_ok());
  457. sleep(Duration::from_secs(1)).await;
  458. let mut msgs = HashMap::new();
  459. while let Some(msg) = listener.try_recv() {
  460. match msg.into_inner() {
  461. NotificationPayload::ProofState(ProofState { y, state, .. }) => {
  462. msgs.entry(y.to_string())
  463. .or_insert_with(Vec::new)
  464. .push(state);
  465. }
  466. _ => panic!("Wrong message received"),
  467. }
  468. }
  469. for (i, key) in public_keys_to_listen.into_iter().enumerate() {
  470. let statuses = msgs.remove(&key).expect("some events");
  471. // Every input pk receives two state updates, as there are only two state transitions
  472. assert_eq!(
  473. statuses,
  474. vec![State::Pending, State::Spent],
  475. "failed to test key {:?} (pos {})",
  476. key,
  477. i,
  478. );
  479. }
  480. assert!(listener.try_recv().is_none(), "no other event is happening");
  481. assert!(msgs.is_empty(), "Only expected key events are received");
  482. }
  483. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  484. async fn test_swap_overpay_underpay_fee() {
  485. setup_tracing();
  486. let mint_bob = create_and_start_test_mint()
  487. .await
  488. .expect("Failed to create test mint");
  489. mint_bob
  490. .rotate_keyset(
  491. CurrencyUnit::Sat,
  492. cdk_integration_tests::standard_keyset_amounts(32),
  493. 1,
  494. )
  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(
  558. CurrencyUnit::Sat,
  559. cdk_integration_tests::standard_keyset_amounts(32),
  560. 1,
  561. )
  562. .await
  563. .unwrap();
  564. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  565. .await
  566. .expect("Failed to create test wallet");
  567. // Alice gets 100 sats
  568. fund_wallet(
  569. wallet_alice.clone(),
  570. 1010,
  571. Some(SplitTarget::Value(Amount::ONE)),
  572. )
  573. .await
  574. .expect("Failed to fund wallet");
  575. let mut proofs = wallet_alice
  576. .get_unspent_proofs()
  577. .await
  578. .expect("Could not get proofs");
  579. let keys = mint_bob.pubkeys().keysets.first().unwrap().clone();
  580. let keyset_id = keys.id;
  581. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  582. let five_proofs: Vec<_> = proofs.drain(..5).collect();
  583. let preswap = PreMintSecrets::random(
  584. keyset_id,
  585. 5.into(),
  586. &SplitTarget::default(),
  587. &fee_and_amounts,
  588. )
  589. .unwrap();
  590. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  591. // Attempt to swap underpaying fee
  592. match mint_bob.process_swap_request(swap_request).await {
  593. Ok(_) => panic!("Swap was allowed unbalanced"),
  594. Err(err) => match err {
  595. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  596. _ => {
  597. println!("{:?}", err);
  598. panic!("Wrong error returned")
  599. }
  600. },
  601. }
  602. let preswap = PreMintSecrets::random(
  603. keyset_id,
  604. 4.into(),
  605. &SplitTarget::default(),
  606. &fee_and_amounts,
  607. )
  608. .unwrap();
  609. let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
  610. let res = mint_bob.process_swap_request(swap_request).await;
  611. assert!(res.is_ok());
  612. let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
  613. let preswap = PreMintSecrets::random(
  614. keyset_id,
  615. 1000.into(),
  616. &SplitTarget::default(),
  617. &fee_and_amounts,
  618. )
  619. .unwrap();
  620. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  621. // Attempt to swap underpaying fee
  622. match mint_bob.process_swap_request(swap_request).await {
  623. Ok(_) => panic!("Swap was allowed unbalanced"),
  624. Err(err) => match err {
  625. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  626. _ => {
  627. println!("{:?}", err);
  628. panic!("Wrong error returned")
  629. }
  630. },
  631. }
  632. let preswap = PreMintSecrets::random(
  633. keyset_id,
  634. 999.into(),
  635. &SplitTarget::default(),
  636. &fee_and_amounts,
  637. )
  638. .unwrap();
  639. let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
  640. let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
  641. }
  642. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  643. async fn test_mint_change_with_fee_melt() {
  644. setup_tracing();
  645. let mint_bob = create_and_start_test_mint()
  646. .await
  647. .expect("Failed to create test mint");
  648. mint_bob
  649. .rotate_keyset(
  650. CurrencyUnit::Sat,
  651. cdk_integration_tests::standard_keyset_amounts(32),
  652. 1,
  653. )
  654. .await
  655. .unwrap();
  656. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  657. .await
  658. .expect("Failed to create test wallet");
  659. // Alice gets 100 sats
  660. fund_wallet(
  661. wallet_alice.clone(),
  662. 100,
  663. Some(SplitTarget::Value(Amount::ONE)),
  664. )
  665. .await
  666. .expect("Failed to fund wallet");
  667. let keyset_id = mint_bob.pubkeys().keysets.first().unwrap().id;
  668. // Check amounts after minting
  669. let total_issued = mint_bob.total_issued().await.unwrap();
  670. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  671. let initial_issued = total_issued.get(&keyset_id).copied().unwrap_or_default();
  672. let initial_redeemed = total_redeemed
  673. .get(&keyset_id)
  674. .copied()
  675. .unwrap_or(Amount::ZERO);
  676. assert_eq!(
  677. initial_issued,
  678. Amount::from(100),
  679. "Should have issued 100 sats, got {:?}",
  680. total_issued
  681. );
  682. assert_eq!(
  683. initial_redeemed,
  684. Amount::ZERO,
  685. "Should have redeemed 0 sats initially, "
  686. );
  687. let proofs = wallet_alice
  688. .get_unspent_proofs()
  689. .await
  690. .expect("Could not get proofs");
  691. let fake_invoice = create_fake_invoice(1000, "".to_string());
  692. let melt_quote = wallet_alice
  693. .melt_quote(fake_invoice.to_string(), None)
  694. .await
  695. .unwrap();
  696. let w = wallet_alice
  697. .melt_proofs(&melt_quote.id, proofs)
  698. .await
  699. .unwrap();
  700. assert_eq!(w.change.unwrap().total_amount().unwrap(), 97.into());
  701. // Check amounts after melting
  702. // Melting redeems 100 sats and issues 97 sats as change
  703. let total_issued = mint_bob.total_issued().await.unwrap();
  704. let total_redeemed = mint_bob.total_redeemed().await.unwrap();
  705. let after_issued = total_issued
  706. .get(&keyset_id)
  707. .copied()
  708. .unwrap_or(Amount::ZERO);
  709. let after_redeemed = total_redeemed
  710. .get(&keyset_id)
  711. .copied()
  712. .unwrap_or(Amount::ZERO);
  713. assert_eq!(
  714. after_issued,
  715. Amount::from(197),
  716. "Should have issued 197 sats total (100 initial + 97 change)"
  717. );
  718. assert_eq!(
  719. after_redeemed,
  720. Amount::from(100),
  721. "Should have redeemed 100 sats from the melt"
  722. );
  723. }
  724. /// Tests concurrent double-spending attempts by trying to use the same proofs
  725. /// in 3 swap transactions simultaneously using tokio tasks
  726. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  727. async fn test_concurrent_double_spend_swap() {
  728. setup_tracing();
  729. let mint_bob = create_and_start_test_mint()
  730. .await
  731. .expect("Failed to create test mint");
  732. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  733. .await
  734. .expect("Failed to create test wallet");
  735. // Alice gets 100 sats
  736. fund_wallet(wallet_alice.clone(), 100, None)
  737. .await
  738. .expect("Failed to fund wallet");
  739. let proofs = wallet_alice
  740. .get_unspent_proofs()
  741. .await
  742. .expect("Could not get proofs");
  743. let keyset_id = get_keyset_id(&mint_bob).await;
  744. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  745. // Create 3 identical swap requests with the same proofs
  746. let preswap1 = 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_request1 = SwapRequest::new(proofs.clone(), preswap1.blinded_messages());
  754. let preswap2 = 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_request2 = SwapRequest::new(proofs.clone(), preswap2.blinded_messages());
  762. let preswap3 = PreMintSecrets::random(
  763. keyset_id,
  764. 100.into(),
  765. &SplitTarget::default(),
  766. &fee_and_amounts,
  767. )
  768. .expect("Failed to create preswap");
  769. let swap_request3 = SwapRequest::new(proofs.clone(), preswap3.blinded_messages());
  770. // Spawn 3 concurrent tasks to process the swap requests
  771. let mint_clone1 = mint_bob.clone();
  772. let mint_clone2 = mint_bob.clone();
  773. let mint_clone3 = mint_bob.clone();
  774. let task1 = tokio::spawn(async move { mint_clone1.process_swap_request(swap_request1).await });
  775. let task2 = tokio::spawn(async move { mint_clone2.process_swap_request(swap_request2).await });
  776. let task3 = tokio::spawn(async move { mint_clone3.process_swap_request(swap_request3).await });
  777. // Wait for all tasks to complete
  778. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  779. // Count successes and failures
  780. let mut success_count = 0;
  781. let mut token_already_spent_count = 0;
  782. for result in [results.0, results.1, results.2] {
  783. match result {
  784. Ok(_) => success_count += 1,
  785. Err(err) => match err {
  786. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  787. token_already_spent_count += 1
  788. }
  789. other_err => panic!("Unexpected error: {:?}", other_err),
  790. },
  791. }
  792. }
  793. // Only one swap should succeed, the other two should fail with TokenAlreadySpent
  794. assert_eq!(1, success_count, "Expected exactly one successful swap");
  795. assert_eq!(
  796. 2, token_already_spent_count,
  797. "Expected exactly two TokenAlreadySpent errors"
  798. );
  799. // Verify that all proofs are marked as spent in the mint
  800. let states = mint_bob
  801. .localstore()
  802. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  803. .await
  804. .expect("Failed to get proof state");
  805. for state in states {
  806. assert_eq!(
  807. State::Spent,
  808. state.expect("Known state"),
  809. "Expected proof to be marked as spent, but got {:?}",
  810. state
  811. );
  812. }
  813. }
  814. /// Tests concurrent double-spending attempts by trying to use the same proofs
  815. /// in 3 melt transactions simultaneously using tokio tasks
  816. #[tokio::test(flavor = "multi_thread", worker_threads = 3)]
  817. async fn test_concurrent_double_spend_melt() {
  818. setup_tracing();
  819. let mint_bob = create_and_start_test_mint()
  820. .await
  821. .expect("Failed to create test mint");
  822. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
  823. .await
  824. .expect("Failed to create test wallet");
  825. // Alice gets 100 sats
  826. fund_wallet(wallet_alice.clone(), 100, None)
  827. .await
  828. .expect("Failed to fund wallet");
  829. let proofs = wallet_alice
  830. .get_unspent_proofs()
  831. .await
  832. .expect("Could not get proofs");
  833. // Create a Lightning invoice for the melt
  834. let invoice = create_fake_invoice(1000, "".to_string());
  835. // Create a melt quote
  836. let melt_quote = wallet_alice
  837. .melt_quote(invoice.to_string(), None)
  838. .await
  839. .expect("Failed to create melt quote");
  840. // Get the quote ID and payment request
  841. let quote_id = melt_quote.id.clone();
  842. // Create 3 identical melt requests with the same proofs
  843. let mint_clone1 = mint_bob.clone();
  844. let mint_clone2 = mint_bob.clone();
  845. let mint_clone3 = mint_bob.clone();
  846. let melt_request = MeltRequest::new(quote_id.parse().unwrap(), proofs.clone(), None);
  847. let melt_request2 = melt_request.clone();
  848. let melt_request3 = melt_request.clone();
  849. // Spawn 3 concurrent tasks to process the melt requests
  850. let task1 = tokio::spawn(async move { mint_clone1.melt(&melt_request).await });
  851. let task2 = tokio::spawn(async move { mint_clone2.melt(&melt_request2).await });
  852. let task3 = tokio::spawn(async move { mint_clone3.melt(&melt_request3).await });
  853. // Wait for all tasks to complete
  854. let results = tokio::try_join!(task1, task2, task3).expect("Tasks failed to complete");
  855. // Count successes and failures
  856. let mut success_count = 0;
  857. let mut token_already_spent_count = 0;
  858. for result in [results.0, results.1, results.2] {
  859. match result {
  860. Ok(_) => success_count += 1,
  861. Err(err) => match err {
  862. cdk::Error::TokenAlreadySpent | cdk::Error::TokenPending => {
  863. token_already_spent_count += 1;
  864. println!("Got expected error: {:?}", err);
  865. }
  866. other_err => {
  867. println!("Got unexpected error: {:?}", other_err);
  868. token_already_spent_count += 1;
  869. }
  870. },
  871. }
  872. }
  873. // Only one melt should succeed, the other two should fail
  874. assert_eq!(1, success_count, "Expected exactly one successful melt");
  875. assert_eq!(
  876. 2, token_already_spent_count,
  877. "Expected exactly two TokenAlreadySpent errors"
  878. );
  879. // Verify that all proofs are marked as spent in the mint
  880. let states = mint_bob
  881. .localstore()
  882. .get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
  883. .await
  884. .expect("Failed to get proof state");
  885. for state in states {
  886. assert_eq!(
  887. State::Spent,
  888. state.expect("Known state"),
  889. "Expected proof to be marked as spent, but got {:?}",
  890. state
  891. );
  892. }
  893. }
  894. async fn get_keyset_id(mint: &Mint) -> Id {
  895. let keys = mint.pubkeys().keysets.first().unwrap().clone();
  896. keys.verify_id()
  897. .expect("Keyset ID generation is successful");
  898. keys.id
  899. }