fake_wallet.rs 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. use std::sync::Arc;
  2. use anyhow::{bail, Result};
  3. use bip39::Mnemonic;
  4. use cdk::amount::SplitTarget;
  5. use cdk::nuts::nut00::ProofsMethods;
  6. use cdk::nuts::{
  7. CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintBolt11Request, PreMintSecrets, Proofs,
  8. SecretKey, State, SwapRequest,
  9. };
  10. use cdk::wallet::{HttpClient, MintConnector, Wallet};
  11. use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
  12. use cdk_integration_tests::{attempt_to_swap_pending, wait_for_mint_to_be_paid};
  13. use cdk_sqlite::wallet::memory;
  14. const MINT_URL: &str = "http://127.0.0.1:8086";
  15. // If both pay and check return pending input proofs should remain pending
  16. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  17. async fn test_fake_tokens_pending() -> Result<()> {
  18. let wallet = Wallet::new(
  19. MINT_URL,
  20. CurrencyUnit::Sat,
  21. Arc::new(memory::empty().await?),
  22. &Mnemonic::generate(12)?.to_seed_normalized(""),
  23. None,
  24. )?;
  25. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  26. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  27. let _mint_amount = wallet
  28. .mint(&mint_quote.id, SplitTarget::default(), None)
  29. .await?;
  30. let fake_description = FakeInvoiceDescription {
  31. pay_invoice_state: MeltQuoteState::Pending,
  32. check_payment_state: MeltQuoteState::Pending,
  33. pay_err: false,
  34. check_err: false,
  35. };
  36. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  37. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  38. let melt = wallet.melt(&melt_quote.id).await;
  39. assert!(melt.is_err());
  40. attempt_to_swap_pending(&wallet).await?;
  41. Ok(())
  42. }
  43. // If the pay error fails and the check returns unknown or failed
  44. // The inputs proofs should be unset as spending
  45. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  46. async fn test_fake_melt_payment_fail() -> Result<()> {
  47. let wallet = Wallet::new(
  48. MINT_URL,
  49. CurrencyUnit::Sat,
  50. Arc::new(memory::empty().await?),
  51. &Mnemonic::generate(12)?.to_seed_normalized(""),
  52. None,
  53. )?;
  54. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  55. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  56. let _mint_amount = wallet
  57. .mint(&mint_quote.id, SplitTarget::default(), None)
  58. .await?;
  59. let fake_description = FakeInvoiceDescription {
  60. pay_invoice_state: MeltQuoteState::Unknown,
  61. check_payment_state: MeltQuoteState::Unknown,
  62. pay_err: true,
  63. check_err: false,
  64. };
  65. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  66. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  67. // The melt should error at the payment invoice command
  68. let melt = wallet.melt(&melt_quote.id).await;
  69. assert!(melt.is_err());
  70. let fake_description = FakeInvoiceDescription {
  71. pay_invoice_state: MeltQuoteState::Failed,
  72. check_payment_state: MeltQuoteState::Failed,
  73. pay_err: true,
  74. check_err: false,
  75. };
  76. let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
  77. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  78. // The melt should error at the payment invoice command
  79. let melt = wallet.melt(&melt_quote.id).await;
  80. assert!(melt.is_err());
  81. // The mint should have unset proofs from pending since payment failed
  82. let all_proof = wallet.get_unspent_proofs().await?;
  83. let states = wallet.check_proofs_spent(all_proof).await?;
  84. for state in states {
  85. assert!(state.state == State::Unspent);
  86. }
  87. let wallet_bal = wallet.total_balance().await?;
  88. assert_eq!(wallet_bal, 100.into());
  89. Ok(())
  90. }
  91. // When both the pay_invoice and check_invoice both fail
  92. // the proofs should remain as pending
  93. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  94. async fn test_fake_melt_payment_fail_and_check() -> Result<()> {
  95. let wallet = Wallet::new(
  96. MINT_URL,
  97. CurrencyUnit::Sat,
  98. Arc::new(memory::empty().await?),
  99. &Mnemonic::generate(12)?.to_seed_normalized(""),
  100. None,
  101. )?;
  102. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  103. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  104. let _mint_amount = wallet
  105. .mint(&mint_quote.id, SplitTarget::default(), None)
  106. .await?;
  107. let fake_description = FakeInvoiceDescription {
  108. pay_invoice_state: MeltQuoteState::Unknown,
  109. check_payment_state: MeltQuoteState::Unknown,
  110. pay_err: true,
  111. check_err: true,
  112. };
  113. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  114. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  115. // The melt should error at the payment invoice command
  116. let melt = wallet.melt(&melt_quote.id).await;
  117. assert!(melt.is_err());
  118. let pending = wallet
  119. .localstore
  120. .get_proofs(None, None, Some(vec![State::Pending]), None)
  121. .await?;
  122. assert!(!pending.is_empty());
  123. Ok(())
  124. }
  125. // In the case that the ln backend returns a failed status but does not error
  126. // The mint should do a second check, then remove proofs from pending
  127. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  128. async fn test_fake_melt_payment_return_fail_status() -> Result<()> {
  129. let wallet = Wallet::new(
  130. MINT_URL,
  131. CurrencyUnit::Sat,
  132. Arc::new(memory::empty().await?),
  133. &Mnemonic::generate(12)?.to_seed_normalized(""),
  134. None,
  135. )?;
  136. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  137. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  138. let _mint_amount = wallet
  139. .mint(&mint_quote.id, SplitTarget::default(), None)
  140. .await?;
  141. let fake_description = FakeInvoiceDescription {
  142. pay_invoice_state: MeltQuoteState::Failed,
  143. check_payment_state: MeltQuoteState::Failed,
  144. pay_err: false,
  145. check_err: false,
  146. };
  147. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  148. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  149. // The melt should error at the payment invoice command
  150. let melt = wallet.melt(&melt_quote.id).await;
  151. assert!(melt.is_err());
  152. let fake_description = FakeInvoiceDescription {
  153. pay_invoice_state: MeltQuoteState::Unknown,
  154. check_payment_state: MeltQuoteState::Unknown,
  155. pay_err: false,
  156. check_err: false,
  157. };
  158. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  159. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  160. // The melt should error at the payment invoice command
  161. let melt = wallet.melt(&melt_quote.id).await;
  162. assert!(melt.is_err());
  163. let pending = wallet
  164. .localstore
  165. .get_proofs(None, None, Some(vec![State::Pending]), None)
  166. .await?;
  167. assert!(pending.is_empty());
  168. Ok(())
  169. }
  170. // In the case that the ln backend returns a failed status but does not error
  171. // The mint should do a second check, then remove proofs from pending
  172. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  173. async fn test_fake_melt_payment_error_unknown() -> Result<()> {
  174. let wallet = Wallet::new(
  175. MINT_URL,
  176. CurrencyUnit::Sat,
  177. Arc::new(memory::empty().await?),
  178. &Mnemonic::generate(12)?.to_seed_normalized(""),
  179. None,
  180. )?;
  181. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  182. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  183. let _mint_amount = wallet
  184. .mint(&mint_quote.id, SplitTarget::default(), None)
  185. .await?;
  186. let fake_description = FakeInvoiceDescription {
  187. pay_invoice_state: MeltQuoteState::Failed,
  188. check_payment_state: MeltQuoteState::Unknown,
  189. pay_err: true,
  190. check_err: false,
  191. };
  192. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  193. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  194. // The melt should error at the payment invoice command
  195. let melt = wallet.melt(&melt_quote.id).await;
  196. assert_eq!(melt.unwrap_err().to_string(), "Payment failed");
  197. let fake_description = FakeInvoiceDescription {
  198. pay_invoice_state: MeltQuoteState::Unknown,
  199. check_payment_state: MeltQuoteState::Unknown,
  200. pay_err: true,
  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?;
  205. // The melt should error at the payment invoice command
  206. let melt = wallet.melt(&melt_quote.id).await;
  207. assert_eq!(melt.unwrap_err().to_string(), "Payment failed");
  208. let pending = wallet
  209. .localstore
  210. .get_proofs(None, None, Some(vec![State::Pending]), None)
  211. .await?;
  212. assert!(pending.is_empty());
  213. Ok(())
  214. }
  215. // In the case that the ln backend returns an err
  216. // The mint should do a second check, that returns paid
  217. // Proofs should remain pending
  218. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  219. async fn test_fake_melt_payment_err_paid() -> Result<()> {
  220. let wallet = Wallet::new(
  221. MINT_URL,
  222. CurrencyUnit::Sat,
  223. Arc::new(memory::empty().await?),
  224. &Mnemonic::generate(12)?.to_seed_normalized(""),
  225. None,
  226. )?;
  227. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  228. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  229. let _mint_amount = wallet
  230. .mint(&mint_quote.id, SplitTarget::default(), None)
  231. .await?;
  232. let fake_description = FakeInvoiceDescription {
  233. pay_invoice_state: MeltQuoteState::Failed,
  234. check_payment_state: MeltQuoteState::Paid,
  235. pay_err: true,
  236. check_err: false,
  237. };
  238. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  239. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  240. // The melt should error at the payment invoice command
  241. let melt = wallet.melt(&melt_quote.id).await;
  242. assert!(melt.is_err());
  243. attempt_to_swap_pending(&wallet).await?;
  244. Ok(())
  245. }
  246. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  247. async fn test_fake_melt_change_in_quote() -> Result<()> {
  248. let wallet = Wallet::new(
  249. MINT_URL,
  250. CurrencyUnit::Sat,
  251. Arc::new(memory::empty().await?),
  252. &Mnemonic::generate(12)?.to_seed_normalized(""),
  253. None,
  254. )?;
  255. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  256. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  257. let _mint_amount = wallet
  258. .mint(&mint_quote.id, SplitTarget::default(), None)
  259. .await?;
  260. let fake_description = FakeInvoiceDescription::default();
  261. let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
  262. let proofs = wallet.get_unspent_proofs().await?;
  263. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  264. let keyset = wallet.get_active_mint_keyset().await?;
  265. let premint_secrets = PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default())?;
  266. let client = HttpClient::new(MINT_URL.parse()?, None);
  267. let melt_request = MeltBolt11Request::new(
  268. melt_quote.id.clone(),
  269. proofs.clone(),
  270. Some(premint_secrets.blinded_messages()),
  271. );
  272. let melt_response = client.post_melt(melt_request).await?;
  273. assert!(melt_response.change.is_some());
  274. let check = wallet.melt_quote_status(&melt_quote.id).await?;
  275. let mut melt_change = melt_response.change.unwrap();
  276. melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
  277. let mut check = check.change.unwrap();
  278. check.sort_by(|a, b| a.amount.cmp(&b.amount));
  279. assert_eq!(melt_change, check);
  280. Ok(())
  281. }
  282. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  283. async fn test_database_type() -> Result<()> {
  284. // Get the database type and work dir from environment
  285. let db_type = std::env::var("MINT_DATABASE").expect("MINT_DATABASE env var should be set");
  286. let work_dir =
  287. std::env::var("CDK_MINTD_WORK_DIR").expect("CDK_MINTD_WORK_DIR env var should be set");
  288. // Check that the correct database file exists
  289. match db_type.as_str() {
  290. "REDB" => {
  291. let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.redb");
  292. assert!(
  293. db_path.exists(),
  294. "Expected redb database file to exist at {:?}",
  295. db_path
  296. );
  297. }
  298. "SQLITE" => {
  299. let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.sqlite");
  300. assert!(
  301. db_path.exists(),
  302. "Expected sqlite database file to exist at {:?}",
  303. db_path
  304. );
  305. }
  306. "MEMORY" => {
  307. // Memory database has no file to check
  308. println!("Memory database in use - no file to check");
  309. }
  310. _ => bail!("Unknown database type: {}", db_type),
  311. }
  312. Ok(())
  313. }
  314. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  315. async fn test_fake_mint_with_witness() -> Result<()> {
  316. let wallet = Wallet::new(
  317. MINT_URL,
  318. CurrencyUnit::Sat,
  319. Arc::new(memory::empty().await?),
  320. &Mnemonic::generate(12)?.to_seed_normalized(""),
  321. None,
  322. )?;
  323. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  324. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  325. let proofs = wallet
  326. .mint(&mint_quote.id, SplitTarget::default(), None)
  327. .await?;
  328. let mint_amount = proofs.total_amount()?;
  329. assert!(mint_amount == 100.into());
  330. Ok(())
  331. }
  332. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  333. async fn test_fake_mint_without_witness() -> Result<()> {
  334. let wallet = Wallet::new(
  335. MINT_URL,
  336. CurrencyUnit::Sat,
  337. Arc::new(memory::empty().await?),
  338. &Mnemonic::generate(12)?.to_seed_normalized(""),
  339. None,
  340. )?;
  341. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  342. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  343. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  344. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  345. let premint_secrets =
  346. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  347. let request = MintBolt11Request {
  348. quote: mint_quote.id,
  349. outputs: premint_secrets.blinded_messages(),
  350. signature: None,
  351. };
  352. let response = http_client.post_mint(request.clone()).await;
  353. match response {
  354. Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()),
  355. Err(err) => bail!("Wrong mint response for minting without witness: {}", err),
  356. Ok(_) => bail!("Minting should not have succeed without a witness"),
  357. }
  358. }
  359. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  360. async fn test_fake_mint_with_wrong_witness() -> Result<()> {
  361. let wallet = Wallet::new(
  362. MINT_URL,
  363. CurrencyUnit::Sat,
  364. Arc::new(memory::empty().await?),
  365. &Mnemonic::generate(12)?.to_seed_normalized(""),
  366. None,
  367. )?;
  368. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  369. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  370. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  371. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  372. let premint_secrets =
  373. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  374. let mut request = MintBolt11Request {
  375. quote: mint_quote.id,
  376. outputs: premint_secrets.blinded_messages(),
  377. signature: None,
  378. };
  379. let secret_key = SecretKey::generate();
  380. request.sign(secret_key)?;
  381. let response = http_client.post_mint(request.clone()).await;
  382. match response {
  383. Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()),
  384. Err(err) => bail!("Wrong mint response for minting without witness: {}", err),
  385. Ok(_) => bail!("Minting should not have succeed without a witness"),
  386. }
  387. }
  388. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  389. async fn test_fake_mint_inflated() -> Result<()> {
  390. let wallet = Wallet::new(
  391. MINT_URL,
  392. CurrencyUnit::Sat,
  393. Arc::new(memory::empty().await?),
  394. &Mnemonic::generate(12)?.to_seed_normalized(""),
  395. None,
  396. )?;
  397. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  398. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  399. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  400. let pre_mint = PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None)?;
  401. let quote_info = wallet
  402. .localstore
  403. .get_mint_quote(&mint_quote.id)
  404. .await?
  405. .expect("there is a quote");
  406. let mut mint_request = MintBolt11Request {
  407. quote: mint_quote.id,
  408. outputs: pre_mint.blinded_messages(),
  409. signature: None,
  410. };
  411. if let Some(secret_key) = quote_info.secret_key {
  412. mint_request.sign(secret_key)?;
  413. }
  414. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  415. let response = http_client.post_mint(mint_request.clone()).await;
  416. match response {
  417. Err(err) => match err {
  418. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  419. err => {
  420. bail!("Wrong mint error returned: {}", err.to_string());
  421. }
  422. },
  423. Ok(_) => {
  424. bail!("Should not have allowed second payment");
  425. }
  426. }
  427. Ok(())
  428. }
  429. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  430. async fn test_fake_mint_multiple_units() -> Result<()> {
  431. let wallet = Wallet::new(
  432. MINT_URL,
  433. CurrencyUnit::Sat,
  434. Arc::new(memory::empty().await?),
  435. &Mnemonic::generate(12)?.to_seed_normalized(""),
  436. None,
  437. )?;
  438. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  439. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  440. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  441. let pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?;
  442. let wallet_usd = Wallet::new(
  443. MINT_URL,
  444. CurrencyUnit::Usd,
  445. Arc::new(memory::empty().await?),
  446. &Mnemonic::generate(12)?.to_seed_normalized(""),
  447. None,
  448. )?;
  449. let active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  450. let usd_pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?;
  451. let quote_info = wallet
  452. .localstore
  453. .get_mint_quote(&mint_quote.id)
  454. .await?
  455. .expect("there is a quote");
  456. let mut sat_outputs = pre_mint.blinded_messages();
  457. let mut usd_outputs = usd_pre_mint.blinded_messages();
  458. sat_outputs.append(&mut usd_outputs);
  459. let mut mint_request = MintBolt11Request {
  460. quote: mint_quote.id,
  461. outputs: sat_outputs,
  462. signature: None,
  463. };
  464. if let Some(secret_key) = quote_info.secret_key {
  465. mint_request.sign(secret_key)?;
  466. }
  467. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  468. let response = http_client.post_mint(mint_request.clone()).await;
  469. match response {
  470. Err(err) => match err {
  471. cdk::Error::MultipleUnits => (),
  472. err => {
  473. bail!("Wrong mint error returned: {}", err.to_string());
  474. }
  475. },
  476. Ok(_) => {
  477. bail!("Should not have allowed to mint with multiple units");
  478. }
  479. }
  480. Ok(())
  481. }
  482. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  483. async fn test_fake_mint_multiple_unit_swap() -> Result<()> {
  484. let wallet = Wallet::new(
  485. MINT_URL,
  486. CurrencyUnit::Sat,
  487. Arc::new(memory::empty().await?),
  488. &Mnemonic::generate(12)?.to_seed_normalized(""),
  489. None,
  490. )?;
  491. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  492. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  493. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  494. let wallet_usd = Wallet::new(
  495. MINT_URL,
  496. CurrencyUnit::Usd,
  497. Arc::new(memory::empty().await?),
  498. &Mnemonic::generate(12)?.to_seed_normalized(""),
  499. None,
  500. )?;
  501. let mint_quote = wallet_usd.mint_quote(100.into(), None).await?;
  502. wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?;
  503. let usd_proofs = wallet_usd
  504. .mint(&mint_quote.id, SplitTarget::None, None)
  505. .await?;
  506. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  507. {
  508. let inputs: Proofs = vec![
  509. proofs.first().expect("There is a proof").clone(),
  510. usd_proofs.first().expect("There is a proof").clone(),
  511. ];
  512. let pre_mint =
  513. PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;
  514. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  515. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  516. let response = http_client.post_swap(swap_request.clone()).await;
  517. match response {
  518. Err(err) => match err {
  519. cdk::Error::MultipleUnits => (),
  520. err => {
  521. bail!("Wrong mint error returned: {}", err.to_string());
  522. }
  523. },
  524. Ok(_) => {
  525. bail!("Should not have allowed to mint with multiple units");
  526. }
  527. }
  528. }
  529. {
  530. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  531. let inputs: Proofs = proofs.into_iter().take(2).collect();
  532. let total_inputs = inputs.total_amount()?;
  533. let half = total_inputs / 2.into();
  534. let usd_pre_mint = PreMintSecrets::random(usd_active_keyset_id, half, &SplitTarget::None)?;
  535. let pre_mint =
  536. PreMintSecrets::random(active_keyset_id, total_inputs - half, &SplitTarget::None)?;
  537. let mut usd_outputs = usd_pre_mint.blinded_messages();
  538. let mut sat_outputs = pre_mint.blinded_messages();
  539. usd_outputs.append(&mut sat_outputs);
  540. let swap_request = SwapRequest::new(inputs, usd_outputs);
  541. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  542. let response = http_client.post_swap(swap_request.clone()).await;
  543. match response {
  544. Err(err) => match err {
  545. cdk::Error::MultipleUnits => (),
  546. err => {
  547. bail!("Wrong mint error returned: {}", err.to_string());
  548. }
  549. },
  550. Ok(_) => {
  551. bail!("Should not have allowed to mint with multiple units");
  552. }
  553. }
  554. }
  555. Ok(())
  556. }
  557. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  558. async fn test_fake_mint_multiple_unit_melt() -> Result<()> {
  559. let wallet = Wallet::new(
  560. MINT_URL,
  561. CurrencyUnit::Sat,
  562. Arc::new(memory::empty().await?),
  563. &Mnemonic::generate(12)?.to_seed_normalized(""),
  564. None,
  565. )?;
  566. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  567. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  568. let proofs = wallet
  569. .mint(&mint_quote.id, SplitTarget::None, None)
  570. .await
  571. .unwrap();
  572. println!("Minted sat");
  573. let wallet_usd = Wallet::new(
  574. MINT_URL,
  575. CurrencyUnit::Usd,
  576. Arc::new(memory::empty().await?),
  577. &Mnemonic::generate(12)?.to_seed_normalized(""),
  578. None,
  579. )?;
  580. let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
  581. println!("Minted quote usd");
  582. wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?;
  583. let usd_proofs = wallet_usd
  584. .mint(&mint_quote.id, SplitTarget::None, None)
  585. .await
  586. .unwrap();
  587. {
  588. let inputs: Proofs = vec![
  589. proofs.first().expect("There is a proof").clone(),
  590. usd_proofs.first().expect("There is a proof").clone(),
  591. ];
  592. let input_amount: u64 = inputs.total_amount()?.into();
  593. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  594. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  595. let melt_request = MeltBolt11Request::new(melt_quote.id, inputs, None);
  596. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  597. let response = http_client.post_melt(melt_request.clone()).await;
  598. match response {
  599. Err(err) => match err {
  600. cdk::Error::MultipleUnits => (),
  601. err => {
  602. bail!("Wrong mint error returned: {}", err.to_string());
  603. }
  604. },
  605. Ok(_) => {
  606. bail!("Should not have allowed to melt with multiple units");
  607. }
  608. }
  609. }
  610. {
  611. let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
  612. let input_amount: u64 = inputs.total_amount()?.into();
  613. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  614. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  615. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  616. let usd_pre_mint = PreMintSecrets::random(
  617. usd_active_keyset_id,
  618. inputs.total_amount()? + 100.into(),
  619. &SplitTarget::None,
  620. )?;
  621. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  622. let mut usd_outputs = usd_pre_mint.blinded_messages();
  623. let mut sat_outputs = pre_mint.blinded_messages();
  624. usd_outputs.append(&mut sat_outputs);
  625. let quote = wallet.melt_quote(invoice.to_string(), None).await?;
  626. let melt_request = MeltBolt11Request::new(quote.id, inputs, Some(usd_outputs));
  627. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  628. let response = http_client.post_melt(melt_request.clone()).await;
  629. match response {
  630. Err(err) => match err {
  631. cdk::Error::MultipleUnits => (),
  632. err => {
  633. bail!("Wrong mint error returned: {}", err.to_string());
  634. }
  635. },
  636. Ok(_) => {
  637. bail!("Should not have allowed to melt with multiple units");
  638. }
  639. }
  640. }
  641. Ok(())
  642. }
  643. /// Test swap where input unit != output unit
  644. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  645. async fn test_fake_mint_input_output_mismatch() -> Result<()> {
  646. let wallet = Wallet::new(
  647. MINT_URL,
  648. CurrencyUnit::Sat,
  649. Arc::new(memory::empty().await?),
  650. &Mnemonic::generate(12)?.to_seed_normalized(""),
  651. None,
  652. )?;
  653. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  654. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  655. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  656. let wallet_usd = Wallet::new(
  657. MINT_URL,
  658. CurrencyUnit::Usd,
  659. Arc::new(memory::empty().await?),
  660. &Mnemonic::generate(12)?.to_seed_normalized(""),
  661. None,
  662. )?;
  663. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  664. let inputs = proofs;
  665. let pre_mint = PreMintSecrets::random(
  666. usd_active_keyset_id,
  667. inputs.total_amount()?,
  668. &SplitTarget::None,
  669. )?;
  670. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  671. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  672. let response = http_client.post_swap(swap_request.clone()).await;
  673. match response {
  674. Err(err) => match err {
  675. cdk::Error::UnitMismatch => (),
  676. err => bail!("Wrong error returned: {}", err),
  677. },
  678. Ok(_) => {
  679. bail!("Should not have allowed to mint with multiple units");
  680. }
  681. }
  682. Ok(())
  683. }
  684. /// Test swap where input is less the output
  685. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  686. async fn test_fake_mint_swap_inflated() -> Result<()> {
  687. let wallet = Wallet::new(
  688. MINT_URL,
  689. CurrencyUnit::Sat,
  690. Arc::new(memory::empty().await?),
  691. &Mnemonic::generate(12)?.to_seed_normalized(""),
  692. None,
  693. )?;
  694. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  695. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  696. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  697. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  698. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  699. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  700. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  701. let response = http_client.post_swap(swap_request.clone()).await;
  702. match response {
  703. Err(err) => match err {
  704. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  705. err => {
  706. bail!("Wrong mint error returned: {}", err.to_string());
  707. }
  708. },
  709. Ok(_) => {
  710. bail!("Should not have allowed to mint with multiple units");
  711. }
  712. }
  713. Ok(())
  714. }
  715. /// Test swap after failure
  716. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  717. async fn test_fake_mint_swap_spend_after_fail() -> Result<()> {
  718. let wallet = Wallet::new(
  719. MINT_URL,
  720. CurrencyUnit::Sat,
  721. Arc::new(memory::empty().await?),
  722. &Mnemonic::generate(12)?.to_seed_normalized(""),
  723. None,
  724. )?;
  725. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  726. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  727. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  728. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  729. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  730. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  731. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  732. let response = http_client.post_swap(swap_request.clone()).await;
  733. assert!(response.is_ok());
  734. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  735. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  736. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  737. let response = http_client.post_swap(swap_request.clone()).await;
  738. match response {
  739. Err(err) => match err {
  740. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  741. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  742. },
  743. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  744. }
  745. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  746. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  747. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  748. let response = http_client.post_swap(swap_request.clone()).await;
  749. match response {
  750. Err(err) => match err {
  751. cdk::Error::TokenAlreadySpent => (),
  752. err => {
  753. bail!("Wrong mint error returned: {}", err.to_string());
  754. }
  755. },
  756. Ok(_) => {
  757. bail!("Should not have allowed to mint with multiple units");
  758. }
  759. }
  760. Ok(())
  761. }
  762. /// Test swap after failure
  763. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  764. async fn test_fake_mint_melt_spend_after_fail() -> Result<()> {
  765. let wallet = Wallet::new(
  766. MINT_URL,
  767. CurrencyUnit::Sat,
  768. Arc::new(memory::empty().await?),
  769. &Mnemonic::generate(12)?.to_seed_normalized(""),
  770. None,
  771. )?;
  772. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  773. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  774. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  775. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  776. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  777. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  778. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  779. let response = http_client.post_swap(swap_request.clone()).await;
  780. assert!(response.is_ok());
  781. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  782. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  783. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  784. let response = http_client.post_swap(swap_request.clone()).await;
  785. match response {
  786. Err(err) => match err {
  787. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  788. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  789. },
  790. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  791. }
  792. let input_amount: u64 = proofs.total_amount()?.into();
  793. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  794. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  795. let melt_request = MeltBolt11Request::new(melt_quote.id, proofs, None);
  796. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  797. let response = http_client.post_melt(melt_request.clone()).await;
  798. match response {
  799. Err(err) => match err {
  800. cdk::Error::TokenAlreadySpent => (),
  801. err => {
  802. bail!("Wrong mint error returned: {}", err.to_string());
  803. }
  804. },
  805. Ok(_) => {
  806. bail!("Should not have allowed to melt with multiple units");
  807. }
  808. }
  809. Ok(())
  810. }
  811. /// Test swap where input unit != output unit
  812. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  813. async fn test_fake_mint_duplicate_proofs_swap() -> Result<()> {
  814. let wallet = Wallet::new(
  815. MINT_URL,
  816. CurrencyUnit::Sat,
  817. Arc::new(memory::empty().await?),
  818. &Mnemonic::generate(12)?.to_seed_normalized(""),
  819. None,
  820. )?;
  821. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  822. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  823. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  824. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  825. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  826. let pre_mint =
  827. PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;
  828. let swap_request = SwapRequest::new(inputs.clone(), pre_mint.blinded_messages());
  829. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  830. let response = http_client.post_swap(swap_request.clone()).await;
  831. match response {
  832. Err(err) => match err {
  833. cdk::Error::DuplicateInputs => (),
  834. err => {
  835. bail!(
  836. "Wrong mint error returned, expected duplicate inputs: {}",
  837. err.to_string()
  838. );
  839. }
  840. },
  841. Ok(_) => {
  842. bail!("Should not have allowed duplicate inputs");
  843. }
  844. }
  845. let blinded_message = pre_mint.blinded_messages();
  846. let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
  847. let swap_request = SwapRequest::new(inputs, outputs);
  848. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  849. let response = http_client.post_swap(swap_request.clone()).await;
  850. match response {
  851. Err(err) => match err {
  852. cdk::Error::DuplicateOutputs => (),
  853. err => {
  854. bail!(
  855. "Wrong mint error returned, expected duplicate outputs: {}",
  856. err.to_string()
  857. );
  858. }
  859. },
  860. Ok(_) => {
  861. bail!("Should not have allow duplicate inputs");
  862. }
  863. }
  864. Ok(())
  865. }
  866. /// Test duplicate proofs in melt
  867. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  868. async fn test_fake_mint_duplicate_proofs_melt() -> Result<()> {
  869. let wallet = Wallet::new(
  870. MINT_URL,
  871. CurrencyUnit::Sat,
  872. Arc::new(memory::empty().await?),
  873. &Mnemonic::generate(12)?.to_seed_normalized(""),
  874. None,
  875. )?;
  876. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  877. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  878. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  879. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  880. let invoice = create_fake_invoice(7000, "".to_string());
  881. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  882. let melt_request = MeltBolt11Request::new(melt_quote.id, inputs, None);
  883. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  884. let response = http_client.post_melt(melt_request.clone()).await;
  885. match response {
  886. Err(err) => match err {
  887. cdk::Error::DuplicateInputs => (),
  888. err => {
  889. bail!("Wrong mint error returned: {}", err.to_string());
  890. }
  891. },
  892. Ok(_) => {
  893. bail!("Should not have allow duplicate inputs");
  894. }
  895. }
  896. Ok(())
  897. }