fake_wallet.rs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224
  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 {
  268. quote: melt_quote.id.clone(),
  269. inputs: proofs.clone(),
  270. outputs: 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 {
  515. inputs,
  516. outputs: pre_mint.blinded_messages(),
  517. };
  518. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  519. let response = http_client.post_swap(swap_request.clone()).await;
  520. match response {
  521. Err(err) => match err {
  522. cdk::Error::MultipleUnits => (),
  523. err => {
  524. bail!("Wrong mint error returned: {}", err.to_string());
  525. }
  526. },
  527. Ok(_) => {
  528. bail!("Should not have allowed to mint with multiple units");
  529. }
  530. }
  531. }
  532. {
  533. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  534. let inputs: Proofs = proofs.into_iter().take(2).collect();
  535. let total_inputs = inputs.total_amount()?;
  536. let half = total_inputs / 2.into();
  537. let usd_pre_mint = PreMintSecrets::random(usd_active_keyset_id, half, &SplitTarget::None)?;
  538. let pre_mint =
  539. PreMintSecrets::random(active_keyset_id, total_inputs - half, &SplitTarget::None)?;
  540. let mut usd_outputs = usd_pre_mint.blinded_messages();
  541. let mut sat_outputs = pre_mint.blinded_messages();
  542. usd_outputs.append(&mut sat_outputs);
  543. let swap_request = SwapRequest {
  544. inputs,
  545. outputs: usd_outputs,
  546. };
  547. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  548. let response = http_client.post_swap(swap_request.clone()).await;
  549. match response {
  550. Err(err) => match err {
  551. cdk::Error::MultipleUnits => (),
  552. err => {
  553. bail!("Wrong mint error returned: {}", err.to_string());
  554. }
  555. },
  556. Ok(_) => {
  557. bail!("Should not have allowed to mint with multiple units");
  558. }
  559. }
  560. }
  561. Ok(())
  562. }
  563. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  564. async fn test_fake_mint_multiple_unit_melt() -> Result<()> {
  565. let wallet = Wallet::new(
  566. MINT_URL,
  567. CurrencyUnit::Sat,
  568. Arc::new(memory::empty().await?),
  569. &Mnemonic::generate(12)?.to_seed_normalized(""),
  570. None,
  571. )?;
  572. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  573. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  574. let proofs = wallet
  575. .mint(&mint_quote.id, SplitTarget::None, None)
  576. .await
  577. .unwrap();
  578. println!("Minted sat");
  579. let wallet_usd = Wallet::new(
  580. MINT_URL,
  581. CurrencyUnit::Usd,
  582. Arc::new(memory::empty().await?),
  583. &Mnemonic::generate(12)?.to_seed_normalized(""),
  584. None,
  585. )?;
  586. let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
  587. println!("Minted quote usd");
  588. wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?;
  589. let usd_proofs = wallet_usd
  590. .mint(&mint_quote.id, SplitTarget::None, None)
  591. .await
  592. .unwrap();
  593. {
  594. let inputs: Proofs = vec![
  595. proofs.first().expect("There is a proof").clone(),
  596. usd_proofs.first().expect("There is a proof").clone(),
  597. ];
  598. let input_amount: u64 = inputs.total_amount()?.into();
  599. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  600. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  601. let melt_request = MeltBolt11Request {
  602. quote: melt_quote.id,
  603. inputs,
  604. outputs: None,
  605. };
  606. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  607. let response = http_client.post_melt(melt_request.clone()).await;
  608. match response {
  609. Err(err) => match err {
  610. cdk::Error::MultipleUnits => (),
  611. err => {
  612. bail!("Wrong mint error returned: {}", err.to_string());
  613. }
  614. },
  615. Ok(_) => {
  616. bail!("Should not have allowed to melt with multiple units");
  617. }
  618. }
  619. }
  620. {
  621. let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
  622. let input_amount: u64 = inputs.total_amount()?.into();
  623. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  624. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  625. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  626. let usd_pre_mint = PreMintSecrets::random(
  627. usd_active_keyset_id,
  628. inputs.total_amount()? + 100.into(),
  629. &SplitTarget::None,
  630. )?;
  631. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  632. let mut usd_outputs = usd_pre_mint.blinded_messages();
  633. let mut sat_outputs = pre_mint.blinded_messages();
  634. usd_outputs.append(&mut sat_outputs);
  635. let quote = wallet.melt_quote(invoice.to_string(), None).await?;
  636. let melt_request = MeltBolt11Request {
  637. quote: quote.id,
  638. inputs,
  639. outputs: Some(usd_outputs),
  640. };
  641. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  642. let response = http_client.post_melt(melt_request.clone()).await;
  643. match response {
  644. Err(err) => match err {
  645. cdk::Error::MultipleUnits => (),
  646. err => {
  647. bail!("Wrong mint error returned: {}", err.to_string());
  648. }
  649. },
  650. Ok(_) => {
  651. bail!("Should not have allowed to melt with multiple units");
  652. }
  653. }
  654. }
  655. Ok(())
  656. }
  657. /// Test swap where input unit != output unit
  658. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  659. async fn test_fake_mint_input_output_mismatch() -> Result<()> {
  660. let wallet = Wallet::new(
  661. MINT_URL,
  662. CurrencyUnit::Sat,
  663. Arc::new(memory::empty().await?),
  664. &Mnemonic::generate(12)?.to_seed_normalized(""),
  665. None,
  666. )?;
  667. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  668. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  669. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  670. let wallet_usd = Wallet::new(
  671. MINT_URL,
  672. CurrencyUnit::Usd,
  673. Arc::new(memory::empty().await?),
  674. &Mnemonic::generate(12)?.to_seed_normalized(""),
  675. None,
  676. )?;
  677. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  678. let inputs = proofs;
  679. let pre_mint = PreMintSecrets::random(
  680. usd_active_keyset_id,
  681. inputs.total_amount()?,
  682. &SplitTarget::None,
  683. )?;
  684. let swap_request = SwapRequest {
  685. inputs,
  686. outputs: pre_mint.blinded_messages(),
  687. };
  688. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  689. let response = http_client.post_swap(swap_request.clone()).await;
  690. match response {
  691. Err(err) => match err {
  692. cdk::Error::UnitMismatch => (),
  693. err => bail!("Wrong error returned: {}", err),
  694. },
  695. Ok(_) => {
  696. bail!("Should not have allowed to mint with multiple units");
  697. }
  698. }
  699. Ok(())
  700. }
  701. /// Test swap where input is less the output
  702. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  703. async fn test_fake_mint_swap_inflated() -> Result<()> {
  704. let wallet = Wallet::new(
  705. MINT_URL,
  706. CurrencyUnit::Sat,
  707. Arc::new(memory::empty().await?),
  708. &Mnemonic::generate(12)?.to_seed_normalized(""),
  709. None,
  710. )?;
  711. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  712. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  713. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  714. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  715. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  716. let swap_request = SwapRequest {
  717. inputs: proofs,
  718. outputs: pre_mint.blinded_messages(),
  719. };
  720. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  721. let response = http_client.post_swap(swap_request.clone()).await;
  722. match response {
  723. Err(err) => match err {
  724. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  725. err => {
  726. bail!("Wrong mint error returned: {}", err.to_string());
  727. }
  728. },
  729. Ok(_) => {
  730. bail!("Should not have allowed to mint with multiple units");
  731. }
  732. }
  733. Ok(())
  734. }
  735. /// Test swap after failure
  736. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  737. async fn test_fake_mint_swap_spend_after_fail() -> Result<()> {
  738. let wallet = Wallet::new(
  739. MINT_URL,
  740. CurrencyUnit::Sat,
  741. Arc::new(memory::empty().await?),
  742. &Mnemonic::generate(12)?.to_seed_normalized(""),
  743. None,
  744. )?;
  745. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  746. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  747. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  748. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  749. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  750. let swap_request = SwapRequest {
  751. inputs: proofs.clone(),
  752. outputs: pre_mint.blinded_messages(),
  753. };
  754. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  755. let response = http_client.post_swap(swap_request.clone()).await;
  756. assert!(response.is_ok());
  757. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  758. let swap_request = SwapRequest {
  759. inputs: proofs.clone(),
  760. outputs: pre_mint.blinded_messages(),
  761. };
  762. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  763. let response = http_client.post_swap(swap_request.clone()).await;
  764. match response {
  765. Err(err) => match err {
  766. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  767. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  768. },
  769. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  770. }
  771. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  772. let swap_request = SwapRequest {
  773. inputs: proofs,
  774. outputs: pre_mint.blinded_messages(),
  775. };
  776. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  777. let response = http_client.post_swap(swap_request.clone()).await;
  778. match response {
  779. Err(err) => match err {
  780. cdk::Error::TokenAlreadySpent => (),
  781. err => {
  782. bail!("Wrong mint error returned: {}", err.to_string());
  783. }
  784. },
  785. Ok(_) => {
  786. bail!("Should not have allowed to mint with multiple units");
  787. }
  788. }
  789. Ok(())
  790. }
  791. /// Test swap after failure
  792. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  793. async fn test_fake_mint_melt_spend_after_fail() -> Result<()> {
  794. let wallet = Wallet::new(
  795. MINT_URL,
  796. CurrencyUnit::Sat,
  797. Arc::new(memory::empty().await?),
  798. &Mnemonic::generate(12)?.to_seed_normalized(""),
  799. None,
  800. )?;
  801. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  802. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  803. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  804. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  805. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  806. let swap_request = SwapRequest {
  807. inputs: proofs.clone(),
  808. outputs: pre_mint.blinded_messages(),
  809. };
  810. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  811. let response = http_client.post_swap(swap_request.clone()).await;
  812. assert!(response.is_ok());
  813. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  814. let swap_request = SwapRequest {
  815. inputs: proofs.clone(),
  816. outputs: pre_mint.blinded_messages(),
  817. };
  818. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  819. let response = http_client.post_swap(swap_request.clone()).await;
  820. match response {
  821. Err(err) => match err {
  822. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  823. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  824. },
  825. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  826. }
  827. let input_amount: u64 = proofs.total_amount()?.into();
  828. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  829. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  830. let melt_request = MeltBolt11Request {
  831. quote: melt_quote.id,
  832. inputs: proofs,
  833. outputs: None,
  834. };
  835. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  836. let response = http_client.post_melt(melt_request.clone()).await;
  837. match response {
  838. Err(err) => match err {
  839. cdk::Error::TokenAlreadySpent => (),
  840. err => {
  841. bail!("Wrong mint error returned: {}", err.to_string());
  842. }
  843. },
  844. Ok(_) => {
  845. bail!("Should not have allowed to melt with multiple units");
  846. }
  847. }
  848. Ok(())
  849. }
  850. /// Test swap where input unit != output unit
  851. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  852. async fn test_fake_mint_duplicate_proofs_swap() -> Result<()> {
  853. let wallet = Wallet::new(
  854. MINT_URL,
  855. CurrencyUnit::Sat,
  856. Arc::new(memory::empty().await?),
  857. &Mnemonic::generate(12)?.to_seed_normalized(""),
  858. None,
  859. )?;
  860. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  861. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  862. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  863. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  864. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  865. let pre_mint =
  866. PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;
  867. let swap_request = SwapRequest {
  868. inputs: inputs.clone(),
  869. outputs: pre_mint.blinded_messages(),
  870. };
  871. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  872. let response = http_client.post_swap(swap_request.clone()).await;
  873. match response {
  874. Err(err) => match err {
  875. cdk::Error::DuplicateInputs => (),
  876. err => {
  877. bail!(
  878. "Wrong mint error returned, expected duplicate inputs: {}",
  879. err.to_string()
  880. );
  881. }
  882. },
  883. Ok(_) => {
  884. bail!("Should not have allowed duplicate inputs");
  885. }
  886. }
  887. let blinded_message = pre_mint.blinded_messages();
  888. let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
  889. let swap_request = SwapRequest { inputs, outputs };
  890. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  891. let response = http_client.post_swap(swap_request.clone()).await;
  892. match response {
  893. Err(err) => match err {
  894. cdk::Error::DuplicateOutputs => (),
  895. err => {
  896. bail!(
  897. "Wrong mint error returned, expected duplicate outputs: {}",
  898. err.to_string()
  899. );
  900. }
  901. },
  902. Ok(_) => {
  903. bail!("Should not have allow duplicate inputs");
  904. }
  905. }
  906. Ok(())
  907. }
  908. /// Test duplicate proofs in melt
  909. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  910. async fn test_fake_mint_duplicate_proofs_melt() -> Result<()> {
  911. let wallet = Wallet::new(
  912. MINT_URL,
  913. CurrencyUnit::Sat,
  914. Arc::new(memory::empty().await?),
  915. &Mnemonic::generate(12)?.to_seed_normalized(""),
  916. None,
  917. )?;
  918. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  919. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  920. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  921. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  922. let invoice = create_fake_invoice(7000, "".to_string());
  923. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  924. let melt_request = MeltBolt11Request {
  925. quote: melt_quote.id,
  926. inputs,
  927. outputs: None,
  928. };
  929. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  930. let response = http_client.post_melt(melt_request.clone()).await;
  931. match response {
  932. Err(err) => match err {
  933. cdk::Error::DuplicateInputs => (),
  934. err => {
  935. bail!("Wrong mint error returned: {}", err.to_string());
  936. }
  937. },
  938. Ok(_) => {
  939. bail!("Should not have allow duplicate inputs");
  940. }
  941. }
  942. Ok(())
  943. }