fake_wallet.rs 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549
  1. //! Fake Wallet Integration Tests
  2. //!
  3. //! This file contains tests for the fake wallet backend functionality.
  4. //! The fake wallet simulates Lightning Network behavior for testing purposes,
  5. //! allowing verification of mint behavior in various payment scenarios without
  6. //! requiring a real Lightning node.
  7. //!
  8. //! Test Scenarios:
  9. //! - Pending payment states and proof handling
  10. //! - Payment failure cases and proof state management
  11. //! - Change output verification in melt operations
  12. //! - Witness signature validation
  13. //! - Cross-unit transaction validation
  14. //! - Overflow and balance validation
  15. //! - Duplicate proof detection
  16. use std::sync::Arc;
  17. use bip39::Mnemonic;
  18. use cashu::Amount;
  19. use cdk::amount::SplitTarget;
  20. use cdk::nuts::nut00::ProofsMethods;
  21. use cdk::nuts::{
  22. CurrencyUnit, MeltQuoteState, MeltRequest, MintRequest, PreMintSecrets, Proofs, SecretKey,
  23. State, SwapRequest,
  24. };
  25. use cdk::wallet::types::TransactionDirection;
  26. use cdk::wallet::{HttpClient, MintConnector, Wallet};
  27. use cdk::StreamExt;
  28. use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
  29. use cdk_sqlite::wallet::memory;
  30. const MINT_URL: &str = "http://127.0.0.1:8086";
  31. /// Tests that when both pay and check return pending status, input proofs should remain pending
  32. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  33. async fn test_fake_tokens_pending() {
  34. let wallet = Wallet::new(
  35. MINT_URL,
  36. CurrencyUnit::Sat,
  37. Arc::new(memory::empty().await.unwrap()),
  38. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  39. None,
  40. )
  41. .expect("failed to create new wallet");
  42. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  43. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  44. let _proofs = proof_streams
  45. .next()
  46. .await
  47. .expect("payment")
  48. .expect("no error");
  49. let fake_description = FakeInvoiceDescription {
  50. pay_invoice_state: MeltQuoteState::Pending,
  51. check_payment_state: MeltQuoteState::Pending,
  52. pay_err: false,
  53. check_err: false,
  54. };
  55. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  56. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  57. let melt = wallet.melt(&melt_quote.id).await;
  58. assert!(melt.is_err());
  59. // melt failed, but there is new code to reclaim unspent proofs
  60. assert!(!wallet
  61. .localstore
  62. .get_proofs(None, None, Some(vec![State::Pending]), None)
  63. .await
  64. .unwrap()
  65. .is_empty());
  66. }
  67. /// Tests that if the pay error fails and the check returns unknown or failed,
  68. /// the input proofs should be unset as spending (returned to unspent state)
  69. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  70. async fn test_fake_melt_payment_fail() {
  71. let wallet = Wallet::new(
  72. MINT_URL,
  73. CurrencyUnit::Sat,
  74. Arc::new(memory::empty().await.unwrap()),
  75. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  76. None,
  77. )
  78. .expect("Failed to create new wallet");
  79. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  80. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  81. let _proofs = proof_streams
  82. .next()
  83. .await
  84. .expect("payment")
  85. .expect("no error");
  86. let fake_description = FakeInvoiceDescription {
  87. pay_invoice_state: MeltQuoteState::Unknown,
  88. check_payment_state: MeltQuoteState::Unknown,
  89. pay_err: true,
  90. check_err: false,
  91. };
  92. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  93. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  94. // The melt should error at the payment invoice command
  95. let melt = wallet.melt(&melt_quote.id).await;
  96. assert!(melt.is_err());
  97. let fake_description = FakeInvoiceDescription {
  98. pay_invoice_state: MeltQuoteState::Failed,
  99. check_payment_state: MeltQuoteState::Failed,
  100. pay_err: true,
  101. check_err: false,
  102. };
  103. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  104. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  105. // The melt should error at the payment invoice command
  106. let melt = wallet.melt(&melt_quote.id).await;
  107. assert!(melt.is_err());
  108. let wallet_bal = wallet.total_balance().await.unwrap();
  109. assert_eq!(wallet_bal, 98.into());
  110. }
  111. /// Tests that when both the pay_invoice and check_invoice both fail,
  112. /// the proofs should remain in pending state
  113. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  114. async fn test_fake_melt_payment_fail_and_check() {
  115. let wallet = Wallet::new(
  116. MINT_URL,
  117. CurrencyUnit::Sat,
  118. Arc::new(memory::empty().await.unwrap()),
  119. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  120. None,
  121. )
  122. .expect("Failed to create new wallet");
  123. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  124. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  125. let _proofs = proof_streams
  126. .next()
  127. .await
  128. .expect("payment")
  129. .expect("no error");
  130. let fake_description = FakeInvoiceDescription {
  131. pay_invoice_state: MeltQuoteState::Unknown,
  132. check_payment_state: MeltQuoteState::Unknown,
  133. pay_err: true,
  134. check_err: true,
  135. };
  136. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  137. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  138. // The melt should error at the payment invoice command
  139. let melt = wallet.melt(&melt_quote.id).await;
  140. assert!(melt.is_err());
  141. assert!(!wallet
  142. .localstore
  143. .get_proofs(None, None, Some(vec![State::Pending]), None)
  144. .await
  145. .unwrap()
  146. .is_empty());
  147. }
  148. /// Tests that when the ln backend returns a failed status but does not error,
  149. /// the mint should do a second check, then remove proofs from pending state
  150. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  151. async fn test_fake_melt_payment_return_fail_status() {
  152. let wallet = Wallet::new(
  153. MINT_URL,
  154. CurrencyUnit::Sat,
  155. Arc::new(memory::empty().await.unwrap()),
  156. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  157. None,
  158. )
  159. .expect("Failed to create new wallet");
  160. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  161. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  162. let _proofs = proof_streams
  163. .next()
  164. .await
  165. .expect("payment")
  166. .expect("no error");
  167. let fake_description = FakeInvoiceDescription {
  168. pay_invoice_state: MeltQuoteState::Failed,
  169. check_payment_state: MeltQuoteState::Failed,
  170. pay_err: false,
  171. check_err: false,
  172. };
  173. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  174. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  175. // The melt should error at the payment invoice command
  176. let melt = wallet.melt(&melt_quote.id).await;
  177. assert!(melt.is_err());
  178. wallet.check_all_pending_proofs().await.unwrap();
  179. let pending = wallet
  180. .localstore
  181. .get_proofs(None, None, Some(vec![State::Pending]), None)
  182. .await
  183. .unwrap();
  184. assert!(pending.is_empty());
  185. let fake_description = FakeInvoiceDescription {
  186. pay_invoice_state: MeltQuoteState::Unknown,
  187. check_payment_state: MeltQuoteState::Unknown,
  188. pay_err: false,
  189. check_err: false,
  190. };
  191. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  192. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  193. // The melt should error at the payment invoice command
  194. let melt = wallet.melt(&melt_quote.id).await;
  195. assert!(melt.is_err());
  196. wallet.check_all_pending_proofs().await.unwrap();
  197. assert!(!wallet
  198. .localstore
  199. .get_proofs(None, None, Some(vec![State::Pending]), None)
  200. .await
  201. .unwrap()
  202. .is_empty());
  203. }
  204. /// Tests that when the ln backend returns an error with unknown status,
  205. /// the mint should do a second check, then remove proofs from pending state
  206. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  207. async fn test_fake_melt_payment_error_unknown() {
  208. let wallet = Wallet::new(
  209. MINT_URL,
  210. CurrencyUnit::Sat,
  211. Arc::new(memory::empty().await.unwrap()),
  212. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  213. None,
  214. )
  215. .unwrap();
  216. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  217. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  218. let _proofs = proof_streams
  219. .next()
  220. .await
  221. .expect("payment")
  222. .expect("no error");
  223. let fake_description = FakeInvoiceDescription {
  224. pay_invoice_state: MeltQuoteState::Failed,
  225. check_payment_state: MeltQuoteState::Unknown,
  226. pay_err: true,
  227. check_err: false,
  228. };
  229. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  230. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  231. // The melt should error at the payment invoice command
  232. let melt = wallet.melt(&melt_quote.id).await;
  233. assert!(melt.is_err());
  234. let fake_description = FakeInvoiceDescription {
  235. pay_invoice_state: MeltQuoteState::Unknown,
  236. check_payment_state: MeltQuoteState::Unknown,
  237. pay_err: true,
  238. check_err: false,
  239. };
  240. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  241. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  242. // The melt should error at the payment invoice command
  243. let melt = wallet.melt(&melt_quote.id).await;
  244. assert!(melt.is_err());
  245. assert!(!wallet
  246. .localstore
  247. .get_proofs(None, None, Some(vec![State::Pending]), None)
  248. .await
  249. .unwrap()
  250. .is_empty());
  251. }
  252. /// Tests that when the ln backend returns an error but the second check returns paid,
  253. /// proofs should remain in pending state
  254. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  255. async fn test_fake_melt_payment_err_paid() {
  256. let wallet = Wallet::new(
  257. MINT_URL,
  258. CurrencyUnit::Sat,
  259. Arc::new(memory::empty().await.unwrap()),
  260. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  261. None,
  262. )
  263. .expect("Failed to create new wallet");
  264. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  265. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  266. let _proofs = proof_streams
  267. .next()
  268. .await
  269. .expect("payment")
  270. .expect("no error");
  271. let old_balance = wallet.total_balance().await.expect("balance");
  272. let fake_description = FakeInvoiceDescription {
  273. pay_invoice_state: MeltQuoteState::Failed,
  274. check_payment_state: MeltQuoteState::Paid,
  275. pay_err: true,
  276. check_err: false,
  277. };
  278. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  279. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  280. // The melt should error at the payment invoice command
  281. let melt = wallet.melt(&melt_quote.id).await.unwrap();
  282. assert!(melt.fee_paid == Amount::ZERO);
  283. assert!(melt.amount == Amount::from(7));
  284. // melt failed, but there is new code to reclaim unspent proofs
  285. assert_eq!(
  286. old_balance - melt.amount,
  287. wallet.total_balance().await.expect("new balance")
  288. );
  289. assert!(wallet
  290. .localstore
  291. .get_proofs(None, None, Some(vec![State::Pending]), None)
  292. .await
  293. .unwrap()
  294. .is_empty());
  295. }
  296. /// Tests that change outputs in a melt quote are correctly handled
  297. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  298. async fn test_fake_melt_change_in_quote() {
  299. let wallet = Wallet::new(
  300. MINT_URL,
  301. CurrencyUnit::Sat,
  302. Arc::new(memory::empty().await.unwrap()),
  303. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  304. None,
  305. )
  306. .expect("Failed to create new wallet");
  307. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  308. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  309. let _proofs = proof_streams
  310. .next()
  311. .await
  312. .expect("payment")
  313. .expect("no error");
  314. let transaction = wallet
  315. .list_transactions(Some(TransactionDirection::Incoming))
  316. .await
  317. .unwrap()
  318. .pop()
  319. .expect("No transaction found");
  320. assert_eq!(wallet.mint_url, transaction.mint_url);
  321. assert_eq!(TransactionDirection::Incoming, transaction.direction);
  322. assert_eq!(Amount::from(100), transaction.amount);
  323. assert_eq!(Amount::from(0), transaction.fee);
  324. assert_eq!(CurrencyUnit::Sat, transaction.unit);
  325. let fake_description = FakeInvoiceDescription::default();
  326. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  327. let proofs = wallet.get_unspent_proofs().await.unwrap();
  328. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  329. let keyset = wallet.fetch_active_keyset().await.unwrap();
  330. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  331. let premint_secrets = PreMintSecrets::random(
  332. keyset.id,
  333. 100.into(),
  334. &SplitTarget::default(),
  335. &fee_and_amounts,
  336. )
  337. .unwrap();
  338. let client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  339. let melt_request = MeltRequest::new(
  340. melt_quote.id.clone(),
  341. proofs.clone(),
  342. Some(premint_secrets.blinded_messages()),
  343. );
  344. let melt_response = client.post_melt(melt_request).await.unwrap();
  345. assert!(melt_response.change.is_some());
  346. let check = wallet.melt_quote_status(&melt_quote.id).await.unwrap();
  347. let mut melt_change = melt_response.change.unwrap();
  348. melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
  349. let mut check = check.change.unwrap();
  350. check.sort_by(|a, b| a.amount.cmp(&b.amount));
  351. assert_eq!(melt_change, check);
  352. }
  353. /// Tests minting tokens with a valid witness signature
  354. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  355. async fn test_fake_mint_with_witness() {
  356. let wallet = Wallet::new(
  357. MINT_URL,
  358. CurrencyUnit::Sat,
  359. Arc::new(memory::empty().await.unwrap()),
  360. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  361. None,
  362. )
  363. .expect("failed to create new wallet");
  364. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  365. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  366. let proofs = proof_streams
  367. .next()
  368. .await
  369. .expect("payment")
  370. .expect("no error");
  371. let mint_amount = proofs.total_amount().unwrap();
  372. assert!(mint_amount == 100.into());
  373. }
  374. /// Tests that minting without a witness signature fails with the correct error
  375. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  376. async fn test_fake_mint_without_witness() {
  377. let wallet = Wallet::new(
  378. MINT_URL,
  379. CurrencyUnit::Sat,
  380. Arc::new(memory::empty().await.unwrap()),
  381. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  382. None,
  383. )
  384. .expect("failed to create new wallet");
  385. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  386. let mut payment_streams = wallet.payment_stream(&mint_quote);
  387. payment_streams
  388. .next()
  389. .await
  390. .expect("payment")
  391. .expect("no error");
  392. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  393. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  394. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  395. let premint_secrets = PreMintSecrets::random(
  396. active_keyset_id,
  397. 100.into(),
  398. &SplitTarget::default(),
  399. &fee_and_amounts,
  400. )
  401. .unwrap();
  402. let request = MintRequest {
  403. quote: mint_quote.id,
  404. outputs: premint_secrets.blinded_messages(),
  405. signature: None,
  406. };
  407. let response = http_client.post_mint(request.clone()).await;
  408. match response {
  409. Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
  410. Err(err) => panic!("Wrong mint response for minting without witness: {}", err),
  411. Ok(_) => panic!("Minting should not have succeed without a witness"),
  412. }
  413. }
  414. /// Tests that minting with an incorrect witness signature fails with the correct error
  415. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  416. async fn test_fake_mint_with_wrong_witness() {
  417. let wallet = Wallet::new(
  418. MINT_URL,
  419. CurrencyUnit::Sat,
  420. Arc::new(memory::empty().await.unwrap()),
  421. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  422. None,
  423. )
  424. .expect("failed to create new wallet");
  425. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  426. let mut payment_streams = wallet.payment_stream(&mint_quote);
  427. payment_streams
  428. .next()
  429. .await
  430. .expect("payment")
  431. .expect("no error");
  432. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  433. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  434. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  435. let premint_secrets = PreMintSecrets::random(
  436. active_keyset_id,
  437. 100.into(),
  438. &SplitTarget::default(),
  439. &fee_and_amounts,
  440. )
  441. .unwrap();
  442. let mut request = MintRequest {
  443. quote: mint_quote.id,
  444. outputs: premint_secrets.blinded_messages(),
  445. signature: None,
  446. };
  447. let secret_key = SecretKey::generate();
  448. request
  449. .sign(secret_key)
  450. .expect("failed to sign the mint request");
  451. let response = http_client.post_mint(request.clone()).await;
  452. match response {
  453. Err(cdk::error::Error::SignatureMissingOrInvalid) => {} //pass
  454. Err(err) => panic!("Wrong mint response for minting without witness: {}", err),
  455. Ok(_) => panic!("Minting should not have succeed without a witness"),
  456. }
  457. }
  458. /// Tests that attempting to mint more tokens than allowed by the quote fails
  459. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  460. async fn test_fake_mint_inflated() {
  461. let wallet = Wallet::new(
  462. MINT_URL,
  463. CurrencyUnit::Sat,
  464. Arc::new(memory::empty().await.unwrap()),
  465. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  466. None,
  467. )
  468. .expect("failed to create new wallet");
  469. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  470. let mut payment_streams = wallet.payment_stream(&mint_quote);
  471. payment_streams
  472. .next()
  473. .await
  474. .expect("payment")
  475. .expect("no error");
  476. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  477. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  478. let pre_mint = PreMintSecrets::random(
  479. active_keyset_id,
  480. 500.into(),
  481. &SplitTarget::None,
  482. &fee_and_amounts,
  483. )
  484. .unwrap();
  485. let quote_info = wallet
  486. .localstore
  487. .get_mint_quote(&mint_quote.id)
  488. .await
  489. .unwrap()
  490. .expect("there is a quote");
  491. let mut mint_request = MintRequest {
  492. quote: mint_quote.id,
  493. outputs: pre_mint.blinded_messages(),
  494. signature: None,
  495. };
  496. if let Some(secret_key) = quote_info.secret_key {
  497. mint_request
  498. .sign(secret_key)
  499. .expect("failed to sign the mint request");
  500. }
  501. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  502. let response = http_client.post_mint(mint_request.clone()).await;
  503. match response {
  504. Err(err) => match err {
  505. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  506. err => {
  507. panic!("Wrong mint error returned: {}", err);
  508. }
  509. },
  510. Ok(_) => {
  511. panic!("Should not have allowed second payment");
  512. }
  513. }
  514. }
  515. /// Tests that attempting to mint with multiple currency units in the same request fails
  516. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  517. async fn test_fake_mint_multiple_units() {
  518. let wallet = Wallet::new(
  519. MINT_URL,
  520. CurrencyUnit::Sat,
  521. Arc::new(memory::empty().await.unwrap()),
  522. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  523. None,
  524. )
  525. .expect("failed to create new wallet");
  526. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  527. let mut payment_streams = wallet.payment_stream(&mint_quote);
  528. payment_streams
  529. .next()
  530. .await
  531. .expect("payment")
  532. .expect("no error");
  533. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  534. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  535. let pre_mint = PreMintSecrets::random(
  536. active_keyset_id,
  537. 50.into(),
  538. &SplitTarget::None,
  539. &fee_and_amounts,
  540. )
  541. .unwrap();
  542. let wallet_usd = Wallet::new(
  543. MINT_URL,
  544. CurrencyUnit::Usd,
  545. Arc::new(memory::empty().await.unwrap()),
  546. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  547. None,
  548. )
  549. .expect("failed to create new wallet");
  550. let active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  551. let usd_pre_mint = PreMintSecrets::random(
  552. active_keyset_id,
  553. 50.into(),
  554. &SplitTarget::None,
  555. &fee_and_amounts,
  556. )
  557. .unwrap();
  558. let quote_info = wallet
  559. .localstore
  560. .get_mint_quote(&mint_quote.id)
  561. .await
  562. .unwrap()
  563. .expect("there is a quote");
  564. let mut sat_outputs = pre_mint.blinded_messages();
  565. let mut usd_outputs = usd_pre_mint.blinded_messages();
  566. sat_outputs.append(&mut usd_outputs);
  567. let mut mint_request = MintRequest {
  568. quote: mint_quote.id,
  569. outputs: sat_outputs,
  570. signature: None,
  571. };
  572. if let Some(secret_key) = quote_info.secret_key {
  573. mint_request
  574. .sign(secret_key)
  575. .expect("failed to sign the mint request");
  576. }
  577. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  578. let response = http_client.post_mint(mint_request.clone()).await;
  579. match response {
  580. Err(err) => match err {
  581. cdk::Error::MultipleUnits => (),
  582. err => {
  583. panic!("Wrong mint error returned: {}", err);
  584. }
  585. },
  586. Ok(_) => {
  587. panic!("Should not have allowed to mint with multiple units");
  588. }
  589. }
  590. }
  591. /// Tests that attempting to swap tokens with multiple currency units fails
  592. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  593. async fn test_fake_mint_multiple_unit_swap() {
  594. let wallet = Wallet::new(
  595. MINT_URL,
  596. CurrencyUnit::Sat,
  597. Arc::new(memory::empty().await.unwrap()),
  598. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  599. None,
  600. )
  601. .expect("failed to create new wallet");
  602. wallet.refresh_keysets().await.unwrap();
  603. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  604. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  605. let proofs = proof_streams
  606. .next()
  607. .await
  608. .expect("payment")
  609. .expect("no error");
  610. let wallet_usd = Wallet::new(
  611. MINT_URL,
  612. CurrencyUnit::Usd,
  613. Arc::new(memory::empty().await.unwrap()),
  614. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  615. None,
  616. )
  617. .expect("failed to create usd wallet");
  618. wallet_usd.refresh_keysets().await.unwrap();
  619. let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
  620. let mut proof_streams =
  621. wallet_usd.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  622. let usd_proofs = proof_streams
  623. .next()
  624. .await
  625. .expect("payment")
  626. .expect("no error");
  627. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  628. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  629. {
  630. let inputs: Proofs = vec![
  631. proofs.first().expect("There is a proof").clone(),
  632. usd_proofs.first().expect("There is a proof").clone(),
  633. ];
  634. let pre_mint = PreMintSecrets::random(
  635. active_keyset_id,
  636. inputs.total_amount().unwrap(),
  637. &SplitTarget::None,
  638. &fee_and_amounts,
  639. )
  640. .unwrap();
  641. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  642. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  643. let response = http_client.post_swap(swap_request.clone()).await;
  644. match response {
  645. Err(err) => match err {
  646. cdk::Error::MultipleUnits => (),
  647. err => {
  648. panic!("Wrong mint error returned: {}", err);
  649. }
  650. },
  651. Ok(_) => {
  652. panic!("Should not have allowed to mint with multiple units");
  653. }
  654. }
  655. }
  656. {
  657. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  658. let inputs: Proofs = proofs.into_iter().take(2).collect();
  659. let total_inputs = inputs.total_amount().unwrap();
  660. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  661. let half = total_inputs / 2.into();
  662. let usd_pre_mint = PreMintSecrets::random(
  663. usd_active_keyset_id,
  664. half,
  665. &SplitTarget::None,
  666. &fee_and_amounts,
  667. )
  668. .unwrap();
  669. let pre_mint = PreMintSecrets::random(
  670. active_keyset_id,
  671. total_inputs - half,
  672. &SplitTarget::None,
  673. &fee_and_amounts,
  674. )
  675. .unwrap();
  676. let mut usd_outputs = usd_pre_mint.blinded_messages();
  677. let mut sat_outputs = pre_mint.blinded_messages();
  678. usd_outputs.append(&mut sat_outputs);
  679. let swap_request = SwapRequest::new(inputs, usd_outputs);
  680. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  681. let response = http_client.post_swap(swap_request.clone()).await;
  682. match response {
  683. Err(err) => match err {
  684. cdk::Error::MultipleUnits => (),
  685. err => {
  686. panic!("Wrong mint error returned: {}", err);
  687. }
  688. },
  689. Ok(_) => {
  690. panic!("Should not have allowed to mint with multiple units");
  691. }
  692. }
  693. }
  694. }
  695. /// Tests that attempting to melt tokens with multiple currency units fails
  696. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  697. async fn test_fake_mint_multiple_unit_melt() {
  698. let wallet = Wallet::new(
  699. MINT_URL,
  700. CurrencyUnit::Sat,
  701. Arc::new(memory::empty().await.unwrap()),
  702. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  703. None,
  704. )
  705. .expect("failed to create new wallet");
  706. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  707. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  708. let proofs = proof_streams
  709. .next()
  710. .await
  711. .expect("payment")
  712. .expect("no error");
  713. println!("Minted sat");
  714. let wallet_usd = Wallet::new(
  715. MINT_URL,
  716. CurrencyUnit::Usd,
  717. Arc::new(memory::empty().await.unwrap()),
  718. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  719. None,
  720. )
  721. .expect("failed to create new wallet");
  722. let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
  723. println!("Minted quote usd");
  724. let mut proof_streams =
  725. wallet_usd.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  726. let usd_proofs = proof_streams
  727. .next()
  728. .await
  729. .expect("payment")
  730. .expect("no error");
  731. {
  732. let inputs: Proofs = vec![
  733. proofs.first().expect("There is a proof").clone(),
  734. usd_proofs.first().expect("There is a proof").clone(),
  735. ];
  736. let input_amount: u64 = inputs.total_amount().unwrap().into();
  737. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  738. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  739. let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
  740. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  741. let response = http_client.post_melt(melt_request.clone()).await;
  742. match response {
  743. Err(err) => match err {
  744. cdk::Error::MultipleUnits => (),
  745. err => {
  746. panic!("Wrong mint error returned: {}", err);
  747. }
  748. },
  749. Ok(_) => {
  750. panic!("Should not have allowed to melt with multiple units");
  751. }
  752. }
  753. }
  754. {
  755. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  756. let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
  757. let input_amount: u64 = inputs.total_amount().unwrap().into();
  758. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  759. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  760. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  761. let usd_pre_mint = PreMintSecrets::random(
  762. usd_active_keyset_id,
  763. inputs.total_amount().unwrap() + 100.into(),
  764. &SplitTarget::None,
  765. &fee_and_amounts,
  766. )
  767. .unwrap();
  768. let pre_mint = PreMintSecrets::random(
  769. active_keyset_id,
  770. 100.into(),
  771. &SplitTarget::None,
  772. &fee_and_amounts,
  773. )
  774. .unwrap();
  775. let mut usd_outputs = usd_pre_mint.blinded_messages();
  776. let mut sat_outputs = pre_mint.blinded_messages();
  777. usd_outputs.append(&mut sat_outputs);
  778. let quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  779. let melt_request = MeltRequest::new(quote.id, inputs, Some(usd_outputs));
  780. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  781. let response = http_client.post_melt(melt_request.clone()).await;
  782. match response {
  783. Err(err) => match err {
  784. cdk::Error::MultipleUnits => (),
  785. err => {
  786. panic!("Wrong mint error returned: {}", err);
  787. }
  788. },
  789. Ok(_) => {
  790. panic!("Should not have allowed to melt with multiple units");
  791. }
  792. }
  793. }
  794. }
  795. /// Tests that swapping tokens where input unit doesn't match output unit fails
  796. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  797. async fn test_fake_mint_input_output_mismatch() {
  798. let wallet = Wallet::new(
  799. MINT_URL,
  800. CurrencyUnit::Sat,
  801. Arc::new(memory::empty().await.unwrap()),
  802. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  803. None,
  804. )
  805. .expect("failed to create new wallet");
  806. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  807. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  808. let proofs = proof_streams
  809. .next()
  810. .await
  811. .expect("payment")
  812. .expect("no error");
  813. let wallet_usd = Wallet::new(
  814. MINT_URL,
  815. CurrencyUnit::Usd,
  816. Arc::new(memory::empty().await.unwrap()),
  817. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  818. None,
  819. )
  820. .expect("failed to create new usd wallet");
  821. let usd_active_keyset_id = wallet_usd.fetch_active_keyset().await.unwrap().id;
  822. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  823. let inputs = proofs;
  824. let pre_mint = PreMintSecrets::random(
  825. usd_active_keyset_id,
  826. inputs.total_amount().unwrap(),
  827. &SplitTarget::None,
  828. &fee_and_amounts,
  829. )
  830. .unwrap();
  831. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  832. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  833. let response = http_client.post_swap(swap_request.clone()).await;
  834. match response {
  835. Err(err) => match err {
  836. cdk::Error::UnitMismatch => (),
  837. err => panic!("Wrong error returned: {}", err),
  838. },
  839. Ok(_) => {
  840. panic!("Should not have allowed to mint with multiple units");
  841. }
  842. }
  843. }
  844. /// Tests that swapping tokens where output amount is greater than input amount fails
  845. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  846. async fn test_fake_mint_swap_inflated() {
  847. let wallet = Wallet::new(
  848. MINT_URL,
  849. CurrencyUnit::Sat,
  850. Arc::new(memory::empty().await.unwrap()),
  851. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  852. None,
  853. )
  854. .expect("failed to create new wallet");
  855. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  856. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  857. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  858. let proofs = proof_streams
  859. .next()
  860. .await
  861. .expect("payment")
  862. .expect("no error");
  863. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  864. let pre_mint = PreMintSecrets::random(
  865. active_keyset_id,
  866. 101.into(),
  867. &SplitTarget::None,
  868. &fee_and_amounts,
  869. )
  870. .unwrap();
  871. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  872. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  873. let response = http_client.post_swap(swap_request.clone()).await;
  874. match response {
  875. Err(err) => match err {
  876. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  877. err => {
  878. panic!("Wrong mint error returned: {}", err);
  879. }
  880. },
  881. Ok(_) => {
  882. panic!("Should not have allowed to mint with multiple units");
  883. }
  884. }
  885. }
  886. /// Tests that tokens cannot be spent again after a failed swap attempt
  887. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  888. async fn test_fake_mint_swap_spend_after_fail() {
  889. let wallet = Wallet::new(
  890. MINT_URL,
  891. CurrencyUnit::Sat,
  892. Arc::new(memory::empty().await.unwrap()),
  893. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  894. None,
  895. )
  896. .expect("failed to create new wallet");
  897. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  898. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  899. let proofs = proof_streams
  900. .next()
  901. .await
  902. .expect("payment")
  903. .expect("no error");
  904. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  905. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  906. let pre_mint = PreMintSecrets::random(
  907. active_keyset_id,
  908. 100.into(),
  909. &SplitTarget::None,
  910. &fee_and_amounts,
  911. )
  912. .unwrap();
  913. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  914. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  915. let response = http_client.post_swap(swap_request.clone()).await;
  916. assert!(response.is_ok());
  917. let pre_mint = PreMintSecrets::random(
  918. active_keyset_id,
  919. 101.into(),
  920. &SplitTarget::None,
  921. &fee_and_amounts,
  922. )
  923. .unwrap();
  924. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  925. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  926. let response = http_client.post_swap(swap_request.clone()).await;
  927. match response {
  928. Err(err) => match err {
  929. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  930. err => panic!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  931. },
  932. Ok(_) => panic!("Should not have allowed swap with unbalanced"),
  933. }
  934. let pre_mint = PreMintSecrets::random(
  935. active_keyset_id,
  936. 100.into(),
  937. &SplitTarget::None,
  938. &fee_and_amounts,
  939. )
  940. .unwrap();
  941. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  942. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  943. let response = http_client.post_swap(swap_request.clone()).await;
  944. match response {
  945. Err(err) => match err {
  946. cdk::Error::TokenAlreadySpent => (),
  947. err => {
  948. panic!("Wrong mint error returned: {}", err);
  949. }
  950. },
  951. Ok(_) => {
  952. panic!("Should not have allowed to mint with multiple units");
  953. }
  954. }
  955. }
  956. /// Tests that tokens cannot be melted after a failed swap attempt
  957. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  958. async fn test_fake_mint_melt_spend_after_fail() {
  959. let wallet = Wallet::new(
  960. MINT_URL,
  961. CurrencyUnit::Sat,
  962. Arc::new(memory::empty().await.unwrap()),
  963. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  964. None,
  965. )
  966. .expect("failed to create new wallet");
  967. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  968. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  969. let proofs = proof_streams
  970. .next()
  971. .await
  972. .expect("payment")
  973. .expect("no error");
  974. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  975. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  976. let pre_mint = PreMintSecrets::random(
  977. active_keyset_id,
  978. 100.into(),
  979. &SplitTarget::None,
  980. &fee_and_amounts,
  981. )
  982. .unwrap();
  983. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  984. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  985. let response = http_client.post_swap(swap_request.clone()).await;
  986. assert!(response.is_ok());
  987. let pre_mint = PreMintSecrets::random(
  988. active_keyset_id,
  989. 101.into(),
  990. &SplitTarget::None,
  991. &fee_and_amounts,
  992. )
  993. .unwrap();
  994. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  995. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  996. let response = http_client.post_swap(swap_request.clone()).await;
  997. match response {
  998. Err(err) => match err {
  999. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  1000. err => panic!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  1001. },
  1002. Ok(_) => panic!("Should not have allowed swap with unbalanced"),
  1003. }
  1004. let input_amount: u64 = proofs.total_amount().unwrap().into();
  1005. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  1006. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  1007. let melt_request = MeltRequest::new(melt_quote.id, proofs, None);
  1008. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1009. let response = http_client.post_melt(melt_request.clone()).await;
  1010. match response {
  1011. Err(err) => match err {
  1012. cdk::Error::TokenAlreadySpent => (),
  1013. err => {
  1014. panic!("Wrong mint error returned: {}", err);
  1015. }
  1016. },
  1017. Ok(_) => {
  1018. panic!("Should not have allowed to melt with multiple units");
  1019. }
  1020. }
  1021. }
  1022. /// Tests that attempting to swap with duplicate proofs fails
  1023. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1024. async fn test_fake_mint_duplicate_proofs_swap() {
  1025. let wallet = Wallet::new(
  1026. MINT_URL,
  1027. CurrencyUnit::Sat,
  1028. Arc::new(memory::empty().await.unwrap()),
  1029. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1030. None,
  1031. )
  1032. .expect("failed to create new wallet");
  1033. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  1034. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1035. let proofs = proof_streams
  1036. .next()
  1037. .await
  1038. .expect("payment")
  1039. .expect("no error");
  1040. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1041. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1042. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  1043. let pre_mint = PreMintSecrets::random(
  1044. active_keyset_id,
  1045. inputs.total_amount().unwrap(),
  1046. &SplitTarget::None,
  1047. &fee_and_amounts,
  1048. )
  1049. .unwrap();
  1050. let swap_request = SwapRequest::new(inputs.clone(), pre_mint.blinded_messages());
  1051. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1052. let response = http_client.post_swap(swap_request.clone()).await;
  1053. match response {
  1054. Err(err) => match err {
  1055. cdk::Error::DuplicateInputs => (),
  1056. err => {
  1057. panic!(
  1058. "Wrong mint error returned, expected duplicate inputs: {}",
  1059. err
  1060. );
  1061. }
  1062. },
  1063. Ok(_) => {
  1064. panic!("Should not have allowed duplicate inputs");
  1065. }
  1066. }
  1067. let blinded_message = pre_mint.blinded_messages();
  1068. let inputs = vec![proofs[0].clone()];
  1069. let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
  1070. let swap_request = SwapRequest::new(inputs, outputs);
  1071. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1072. let response = http_client.post_swap(swap_request.clone()).await;
  1073. match response {
  1074. Err(err) => match err {
  1075. cdk::Error::DuplicateOutputs => (),
  1076. err => {
  1077. panic!(
  1078. "Wrong mint error returned, expected duplicate outputs: {}",
  1079. err
  1080. );
  1081. }
  1082. },
  1083. Ok(_) => {
  1084. panic!("Should not have allow duplicate inputs");
  1085. }
  1086. }
  1087. }
  1088. /// Tests that attempting to melt with duplicate proofs fails
  1089. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1090. async fn test_fake_mint_duplicate_proofs_melt() {
  1091. let wallet = Wallet::new(
  1092. MINT_URL,
  1093. CurrencyUnit::Sat,
  1094. Arc::new(memory::empty().await.unwrap()),
  1095. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1096. None,
  1097. )
  1098. .expect("failed to create new wallet");
  1099. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  1100. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1101. let proofs = proof_streams
  1102. .next()
  1103. .await
  1104. .expect("payment")
  1105. .expect("no error");
  1106. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  1107. let invoice = create_fake_invoice(7000, "".to_string());
  1108. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  1109. let melt_request = MeltRequest::new(melt_quote.id, inputs, None);
  1110. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1111. let response = http_client.post_melt(melt_request.clone()).await;
  1112. match response {
  1113. Err(err) => match err {
  1114. cdk::Error::DuplicateInputs => (),
  1115. err => {
  1116. panic!("Wrong mint error returned: {}", err);
  1117. }
  1118. },
  1119. Ok(_) => {
  1120. panic!("Should not have allow duplicate inputs");
  1121. }
  1122. }
  1123. }
  1124. /// Tests that wallet automatically recovers proofs after a failed melt operation
  1125. /// by swapping them to new proofs, preventing loss of funds
  1126. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1127. async fn test_wallet_proof_recovery_after_failed_melt() {
  1128. let wallet = Wallet::new(
  1129. MINT_URL,
  1130. CurrencyUnit::Sat,
  1131. Arc::new(memory::empty().await.unwrap()),
  1132. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1133. None,
  1134. )
  1135. .expect("failed to create new wallet");
  1136. // Mint 100 sats
  1137. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  1138. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1139. let initial_proofs = proof_streams
  1140. .next()
  1141. .await
  1142. .expect("payment")
  1143. .expect("no error");
  1144. let initial_ys: Vec<_> = initial_proofs.iter().map(|p| p.y().unwrap()).collect();
  1145. assert_eq!(wallet.total_balance().await.unwrap(), Amount::from(100));
  1146. // Create a melt quote that will fail
  1147. let fake_description = FakeInvoiceDescription {
  1148. pay_invoice_state: MeltQuoteState::Unknown,
  1149. check_payment_state: MeltQuoteState::Unpaid,
  1150. pay_err: true,
  1151. check_err: false,
  1152. };
  1153. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  1154. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await.unwrap();
  1155. // Attempt to melt - this should fail but trigger proof recovery
  1156. let melt_result = wallet.melt(&melt_quote.id).await;
  1157. assert!(melt_result.is_err(), "Melt should have failed");
  1158. // Verify wallet still has balance (proofs recovered)
  1159. assert_eq!(
  1160. wallet.total_balance().await.unwrap(),
  1161. Amount::from(100),
  1162. "Balance should be recovered"
  1163. );
  1164. // Verify the proofs were swapped (different Ys)
  1165. let recovered_proofs = wallet.get_unspent_proofs().await.unwrap();
  1166. let recovered_ys: Vec<_> = recovered_proofs.iter().map(|p| p.y().unwrap()).collect();
  1167. // The Ys should be different (swapped to new proofs)
  1168. assert!(
  1169. initial_ys.iter().any(|y| !recovered_ys.contains(y)),
  1170. "Proofs should have been swapped to new ones"
  1171. );
  1172. // Verify we can still spend the recovered proofs
  1173. let valid_invoice = create_fake_invoice(7000, "".to_string());
  1174. let valid_melt_quote = wallet
  1175. .melt_quote(valid_invoice.to_string(), None)
  1176. .await
  1177. .unwrap();
  1178. let successful_melt = wallet.melt(&valid_melt_quote.id).await;
  1179. assert!(
  1180. successful_melt.is_ok(),
  1181. "Should be able to spend recovered proofs"
  1182. );
  1183. }
  1184. /// Tests that wallet automatically recovers proofs after a failed swap operation
  1185. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  1186. async fn test_wallet_proof_recovery_after_failed_swap() {
  1187. let wallet = Wallet::new(
  1188. MINT_URL,
  1189. CurrencyUnit::Sat,
  1190. Arc::new(memory::empty().await.unwrap()),
  1191. Mnemonic::generate(12).unwrap().to_seed_normalized(""),
  1192. None,
  1193. )
  1194. .expect("failed to create new wallet");
  1195. // Mint 100 sats
  1196. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  1197. let mut proof_streams = wallet.proof_stream(mint_quote.clone(), SplitTarget::default(), None);
  1198. let initial_proofs = proof_streams
  1199. .next()
  1200. .await
  1201. .expect("payment")
  1202. .expect("no error");
  1203. let initial_ys: Vec<_> = initial_proofs.iter().map(|p| p.y().unwrap()).collect();
  1204. assert_eq!(wallet.total_balance().await.unwrap(), Amount::from(100));
  1205. let unspent_proofs = wallet.get_unspent_proofs().await.unwrap();
  1206. // Create an invalid swap by manually constructing a request that will fail
  1207. // We'll use the wallet's swap with invalid parameters to trigger a failure
  1208. let active_keyset_id = wallet.fetch_active_keyset().await.unwrap().id;
  1209. let fee_and_amounts = (0, ((0..32).map(|x| 2u64.pow(x)).collect::<Vec<_>>())).into();
  1210. // Create invalid swap request (requesting more than we have)
  1211. let preswap = PreMintSecrets::random(
  1212. active_keyset_id,
  1213. 1000.into(), // More than the 100 we have
  1214. &SplitTarget::default(),
  1215. &fee_and_amounts,
  1216. )
  1217. .unwrap();
  1218. let swap_request = SwapRequest::new(unspent_proofs.clone(), preswap.blinded_messages());
  1219. // Use HTTP client directly to bypass wallet's validation and trigger recovery
  1220. let http_client = HttpClient::new(MINT_URL.parse().unwrap(), None);
  1221. let response = http_client.post_swap(swap_request).await;
  1222. assert!(response.is_err(), "Swap should have failed");
  1223. // Note: The HTTP client doesn't trigger the wallet's try_proof_operation wrapper
  1224. // So we need to test through the wallet's own methods
  1225. // After the failed HTTP request, the proofs are still in the wallet's database
  1226. // Verify balance is still available after the failed operation
  1227. assert_eq!(
  1228. wallet.total_balance().await.unwrap(),
  1229. Amount::from(100),
  1230. "Balance should still be available"
  1231. );
  1232. // Verify we can perform a successful swap operation
  1233. let successful_swap = wallet
  1234. .swap(None, SplitTarget::None, unspent_proofs, None, false)
  1235. .await;
  1236. assert!(
  1237. successful_swap.is_ok(),
  1238. "Should be able to swap after failed operation"
  1239. );
  1240. // Verify the proofs were swapped to new ones
  1241. let final_proofs = wallet.get_unspent_proofs().await.unwrap();
  1242. let final_ys: Vec<_> = final_proofs.iter().map(|p| p.y().unwrap()).collect();
  1243. // The Ys should be different after the successful swap
  1244. assert!(
  1245. initial_ys.iter().any(|y| !final_ys.contains(y)),
  1246. "Proofs should have been swapped to new ones"
  1247. );
  1248. }