fake_wallet.rs 50 KB

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