fake_wallet.rs 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132
  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. /// Tests that when both pay and check return pending status, 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. /// Tests that if the pay error fails and the check returns unknown or failed,
  44. /// the input proofs should be unset as spending (returned to unspent state)
  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. /// Tests that when both the pay_invoice and check_invoice both fail,
  92. /// the proofs should remain in pending state
  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. /// Tests that when the ln backend returns a failed status but does not error,
  126. /// the mint should do a second check, then remove proofs from pending state
  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. /// Tests that when the ln backend returns an error with unknown status,
  171. /// the mint should do a second check, then remove proofs from pending state
  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. /// Tests that when the ln backend returns an error but the second check returns paid,
  216. /// proofs should remain in pending state
  217. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  218. async fn test_fake_melt_payment_err_paid() -> Result<()> {
  219. let wallet = Wallet::new(
  220. MINT_URL,
  221. CurrencyUnit::Sat,
  222. Arc::new(memory::empty().await?),
  223. &Mnemonic::generate(12)?.to_seed_normalized(""),
  224. None,
  225. )?;
  226. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  227. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  228. let _mint_amount = wallet
  229. .mint(&mint_quote.id, SplitTarget::default(), None)
  230. .await?;
  231. let fake_description = FakeInvoiceDescription {
  232. pay_invoice_state: MeltQuoteState::Failed,
  233. check_payment_state: MeltQuoteState::Paid,
  234. pay_err: true,
  235. check_err: false,
  236. };
  237. let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
  238. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  239. // The melt should error at the payment invoice command
  240. let melt = wallet.melt(&melt_quote.id).await;
  241. assert!(melt.is_err());
  242. attempt_to_swap_pending(&wallet).await?;
  243. Ok(())
  244. }
  245. /// Tests that the correct database type is used based on environment variables
  246. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  247. async fn test_database_type() -> Result<()> {
  248. // Get the database type and work dir from environment
  249. let db_type = std::env::var("CDK_MINTD_DATABASE").expect("MINT_DATABASE env var should be set");
  250. let work_dir =
  251. std::env::var("CDK_MINTD_WORK_DIR").expect("CDK_MINTD_WORK_DIR env var should be set");
  252. // Check that the correct database file exists
  253. match db_type.as_str() {
  254. "REDB" => {
  255. let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.redb");
  256. assert!(
  257. db_path.exists(),
  258. "Expected redb database file to exist at {:?}",
  259. db_path
  260. );
  261. }
  262. "SQLITE" => {
  263. let db_path = std::path::Path::new(&work_dir).join("cdk-mintd.sqlite");
  264. assert!(
  265. db_path.exists(),
  266. "Expected sqlite database file to exist at {:?}",
  267. db_path
  268. );
  269. }
  270. "MEMORY" => {
  271. // Memory database has no file to check
  272. println!("Memory database in use - no file to check");
  273. }
  274. _ => bail!("Unknown database type: {}", db_type),
  275. }
  276. Ok(())
  277. }
  278. /// Tests minting tokens with a valid witness signature
  279. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  280. async fn test_fake_mint_with_witness() -> Result<()> {
  281. let wallet = Wallet::new(
  282. MINT_URL,
  283. CurrencyUnit::Sat,
  284. Arc::new(memory::empty().await?),
  285. &Mnemonic::generate(12)?.to_seed_normalized(""),
  286. None,
  287. )?;
  288. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  289. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  290. let proofs = wallet
  291. .mint(&mint_quote.id, SplitTarget::default(), None)
  292. .await?;
  293. let mint_amount = proofs.total_amount()?;
  294. assert!(mint_amount == 100.into());
  295. Ok(())
  296. }
  297. /// Tests that minting without a witness signature fails with the correct error
  298. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  299. async fn test_fake_mint_without_witness() -> Result<()> {
  300. let wallet = Wallet::new(
  301. MINT_URL,
  302. CurrencyUnit::Sat,
  303. Arc::new(memory::empty().await?),
  304. &Mnemonic::generate(12)?.to_seed_normalized(""),
  305. None,
  306. )?;
  307. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  308. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  309. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  310. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  311. let premint_secrets =
  312. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  313. let request = MintBolt11Request {
  314. quote: mint_quote.id,
  315. outputs: premint_secrets.blinded_messages(),
  316. signature: None,
  317. };
  318. let response = http_client.post_mint(request.clone()).await;
  319. match response {
  320. Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()),
  321. Err(err) => bail!("Wrong mint response for minting without witness: {}", err),
  322. Ok(_) => bail!("Minting should not have succeed without a witness"),
  323. }
  324. }
  325. /// Tests that minting with an incorrect witness signature fails with the correct error
  326. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  327. async fn test_fake_mint_with_wrong_witness() -> Result<()> {
  328. let wallet = Wallet::new(
  329. MINT_URL,
  330. CurrencyUnit::Sat,
  331. Arc::new(memory::empty().await?),
  332. &Mnemonic::generate(12)?.to_seed_normalized(""),
  333. None,
  334. )?;
  335. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  336. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  337. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  338. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  339. let premint_secrets =
  340. PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap();
  341. let mut request = MintBolt11Request {
  342. quote: mint_quote.id,
  343. outputs: premint_secrets.blinded_messages(),
  344. signature: None,
  345. };
  346. let secret_key = SecretKey::generate();
  347. request.sign(secret_key)?;
  348. let response = http_client.post_mint(request.clone()).await;
  349. match response {
  350. Err(cdk::error::Error::SignatureMissingOrInvalid) => Ok(()),
  351. Err(err) => bail!("Wrong mint response for minting without witness: {}", err),
  352. Ok(_) => bail!("Minting should not have succeed without a witness"),
  353. }
  354. }
  355. /// Tests that attempting to mint more tokens than allowed by the quote fails
  356. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  357. async fn test_fake_mint_inflated() -> Result<()> {
  358. let wallet = Wallet::new(
  359. MINT_URL,
  360. CurrencyUnit::Sat,
  361. Arc::new(memory::empty().await?),
  362. &Mnemonic::generate(12)?.to_seed_normalized(""),
  363. None,
  364. )?;
  365. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  366. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  367. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  368. let pre_mint = PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None)?;
  369. let quote_info = wallet
  370. .localstore
  371. .get_mint_quote(&mint_quote.id)
  372. .await?
  373. .expect("there is a quote");
  374. let mut mint_request = MintBolt11Request {
  375. quote: mint_quote.id,
  376. outputs: pre_mint.blinded_messages(),
  377. signature: None,
  378. };
  379. if let Some(secret_key) = quote_info.secret_key {
  380. mint_request.sign(secret_key)?;
  381. }
  382. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  383. let response = http_client.post_mint(mint_request.clone()).await;
  384. match response {
  385. Err(err) => match err {
  386. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  387. err => {
  388. bail!("Wrong mint error returned: {}", err.to_string());
  389. }
  390. },
  391. Ok(_) => {
  392. bail!("Should not have allowed second payment");
  393. }
  394. }
  395. Ok(())
  396. }
  397. /// Tests that attempting to mint with multiple currency units in the same request fails
  398. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  399. async fn test_fake_mint_multiple_units() -> Result<()> {
  400. let wallet = Wallet::new(
  401. MINT_URL,
  402. CurrencyUnit::Sat,
  403. Arc::new(memory::empty().await?),
  404. &Mnemonic::generate(12)?.to_seed_normalized(""),
  405. None,
  406. )?;
  407. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  408. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  409. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  410. let pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?;
  411. let wallet_usd = Wallet::new(
  412. MINT_URL,
  413. CurrencyUnit::Usd,
  414. Arc::new(memory::empty().await?),
  415. &Mnemonic::generate(12)?.to_seed_normalized(""),
  416. None,
  417. )?;
  418. let active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  419. let usd_pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?;
  420. let quote_info = wallet
  421. .localstore
  422. .get_mint_quote(&mint_quote.id)
  423. .await?
  424. .expect("there is a quote");
  425. let mut sat_outputs = pre_mint.blinded_messages();
  426. let mut usd_outputs = usd_pre_mint.blinded_messages();
  427. sat_outputs.append(&mut usd_outputs);
  428. let mut mint_request = MintBolt11Request {
  429. quote: mint_quote.id,
  430. outputs: sat_outputs,
  431. signature: None,
  432. };
  433. if let Some(secret_key) = quote_info.secret_key {
  434. mint_request.sign(secret_key)?;
  435. }
  436. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  437. let response = http_client.post_mint(mint_request.clone()).await;
  438. match response {
  439. Err(err) => match err {
  440. cdk::Error::MultipleUnits => (),
  441. err => {
  442. bail!("Wrong mint error returned: {}", err.to_string());
  443. }
  444. },
  445. Ok(_) => {
  446. bail!("Should not have allowed to mint with multiple units");
  447. }
  448. }
  449. Ok(())
  450. }
  451. /// Tests that attempting to swap tokens with multiple currency units fails
  452. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  453. async fn test_fake_mint_multiple_unit_swap() -> Result<()> {
  454. let wallet = Wallet::new(
  455. MINT_URL,
  456. CurrencyUnit::Sat,
  457. Arc::new(memory::empty().await?),
  458. &Mnemonic::generate(12)?.to_seed_normalized(""),
  459. None,
  460. )?;
  461. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  462. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  463. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  464. let wallet_usd = Wallet::new(
  465. MINT_URL,
  466. CurrencyUnit::Usd,
  467. Arc::new(memory::empty().await?),
  468. &Mnemonic::generate(12)?.to_seed_normalized(""),
  469. None,
  470. )?;
  471. let mint_quote = wallet_usd.mint_quote(100.into(), None).await?;
  472. wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?;
  473. let usd_proofs = wallet_usd
  474. .mint(&mint_quote.id, SplitTarget::None, None)
  475. .await?;
  476. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  477. {
  478. let inputs: Proofs = vec![
  479. proofs.first().expect("There is a proof").clone(),
  480. usd_proofs.first().expect("There is a proof").clone(),
  481. ];
  482. let pre_mint =
  483. PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;
  484. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  485. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  486. let response = http_client.post_swap(swap_request.clone()).await;
  487. match response {
  488. Err(err) => match err {
  489. cdk::Error::MultipleUnits => (),
  490. err => {
  491. bail!("Wrong mint error returned: {}", err.to_string());
  492. }
  493. },
  494. Ok(_) => {
  495. bail!("Should not have allowed to mint with multiple units");
  496. }
  497. }
  498. }
  499. {
  500. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  501. let inputs: Proofs = proofs.into_iter().take(2).collect();
  502. let total_inputs = inputs.total_amount()?;
  503. let half = total_inputs / 2.into();
  504. let usd_pre_mint = PreMintSecrets::random(usd_active_keyset_id, half, &SplitTarget::None)?;
  505. let pre_mint =
  506. PreMintSecrets::random(active_keyset_id, total_inputs - half, &SplitTarget::None)?;
  507. let mut usd_outputs = usd_pre_mint.blinded_messages();
  508. let mut sat_outputs = pre_mint.blinded_messages();
  509. usd_outputs.append(&mut sat_outputs);
  510. let swap_request = SwapRequest::new(inputs, usd_outputs);
  511. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  512. let response = http_client.post_swap(swap_request.clone()).await;
  513. match response {
  514. Err(err) => match err {
  515. cdk::Error::MultipleUnits => (),
  516. err => {
  517. bail!("Wrong mint error returned: {}", err.to_string());
  518. }
  519. },
  520. Ok(_) => {
  521. bail!("Should not have allowed to mint with multiple units");
  522. }
  523. }
  524. }
  525. Ok(())
  526. }
  527. /// Tests that attempting to melt tokens with multiple currency units fails
  528. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  529. async fn test_fake_mint_multiple_unit_melt() -> Result<()> {
  530. let wallet = Wallet::new(
  531. MINT_URL,
  532. CurrencyUnit::Sat,
  533. Arc::new(memory::empty().await?),
  534. &Mnemonic::generate(12)?.to_seed_normalized(""),
  535. None,
  536. )?;
  537. let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap();
  538. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  539. let proofs = wallet
  540. .mint(&mint_quote.id, SplitTarget::None, None)
  541. .await
  542. .unwrap();
  543. println!("Minted sat");
  544. let wallet_usd = Wallet::new(
  545. MINT_URL,
  546. CurrencyUnit::Usd,
  547. Arc::new(memory::empty().await?),
  548. &Mnemonic::generate(12)?.to_seed_normalized(""),
  549. None,
  550. )?;
  551. let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap();
  552. println!("Minted quote usd");
  553. wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?;
  554. let usd_proofs = wallet_usd
  555. .mint(&mint_quote.id, SplitTarget::None, None)
  556. .await
  557. .unwrap();
  558. {
  559. let inputs: Proofs = vec![
  560. proofs.first().expect("There is a proof").clone(),
  561. usd_proofs.first().expect("There is a proof").clone(),
  562. ];
  563. let input_amount: u64 = inputs.total_amount()?.into();
  564. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  565. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  566. let melt_request = MeltBolt11Request::new(melt_quote.id, inputs, None);
  567. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  568. let response = http_client.post_melt(melt_request.clone()).await;
  569. match response {
  570. Err(err) => match err {
  571. cdk::Error::MultipleUnits => (),
  572. err => {
  573. bail!("Wrong mint error returned: {}", err.to_string());
  574. }
  575. },
  576. Ok(_) => {
  577. bail!("Should not have allowed to melt with multiple units");
  578. }
  579. }
  580. }
  581. {
  582. let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()];
  583. let input_amount: u64 = inputs.total_amount()?.into();
  584. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  585. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  586. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  587. let usd_pre_mint = PreMintSecrets::random(
  588. usd_active_keyset_id,
  589. inputs.total_amount()? + 100.into(),
  590. &SplitTarget::None,
  591. )?;
  592. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  593. let mut usd_outputs = usd_pre_mint.blinded_messages();
  594. let mut sat_outputs = pre_mint.blinded_messages();
  595. usd_outputs.append(&mut sat_outputs);
  596. let quote = wallet.melt_quote(invoice.to_string(), None).await?;
  597. let melt_request = MeltBolt11Request::new(quote.id, inputs, Some(usd_outputs));
  598. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  599. let response = http_client.post_melt(melt_request.clone()).await;
  600. match response {
  601. Err(err) => match err {
  602. cdk::Error::MultipleUnits => (),
  603. err => {
  604. bail!("Wrong mint error returned: {}", err.to_string());
  605. }
  606. },
  607. Ok(_) => {
  608. bail!("Should not have allowed to melt with multiple units");
  609. }
  610. }
  611. }
  612. Ok(())
  613. }
  614. /// Tests that swapping tokens where input unit doesn't match output unit fails
  615. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  616. async fn test_fake_mint_input_output_mismatch() -> Result<()> {
  617. let wallet = Wallet::new(
  618. MINT_URL,
  619. CurrencyUnit::Sat,
  620. Arc::new(memory::empty().await?),
  621. &Mnemonic::generate(12)?.to_seed_normalized(""),
  622. None,
  623. )?;
  624. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  625. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  626. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  627. let wallet_usd = Wallet::new(
  628. MINT_URL,
  629. CurrencyUnit::Usd,
  630. Arc::new(memory::empty().await?),
  631. &Mnemonic::generate(12)?.to_seed_normalized(""),
  632. None,
  633. )?;
  634. let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id;
  635. let inputs = proofs;
  636. let pre_mint = PreMintSecrets::random(
  637. usd_active_keyset_id,
  638. inputs.total_amount()?,
  639. &SplitTarget::None,
  640. )?;
  641. let swap_request = SwapRequest::new(inputs, pre_mint.blinded_messages());
  642. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  643. let response = http_client.post_swap(swap_request.clone()).await;
  644. match response {
  645. Err(err) => match err {
  646. cdk::Error::UnitMismatch => (),
  647. err => bail!("Wrong error returned: {}", err),
  648. },
  649. Ok(_) => {
  650. bail!("Should not have allowed to mint with multiple units");
  651. }
  652. }
  653. Ok(())
  654. }
  655. /// Tests that swapping tokens where output amount is greater than input amount fails
  656. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  657. async fn test_fake_mint_swap_inflated() -> Result<()> {
  658. let wallet = Wallet::new(
  659. MINT_URL,
  660. CurrencyUnit::Sat,
  661. Arc::new(memory::empty().await?),
  662. &Mnemonic::generate(12)?.to_seed_normalized(""),
  663. None,
  664. )?;
  665. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  666. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  667. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  668. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  669. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  670. let swap_request = SwapRequest::new(proofs, 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::TransactionUnbalanced(_, _, _) => (),
  676. err => {
  677. bail!("Wrong mint error returned: {}", err.to_string());
  678. }
  679. },
  680. Ok(_) => {
  681. bail!("Should not have allowed to mint with multiple units");
  682. }
  683. }
  684. Ok(())
  685. }
  686. /// Tests that tokens cannot be spent again after a failed swap attempt
  687. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  688. async fn test_fake_mint_swap_spend_after_fail() -> Result<()> {
  689. let wallet = Wallet::new(
  690. MINT_URL,
  691. CurrencyUnit::Sat,
  692. Arc::new(memory::empty().await?),
  693. &Mnemonic::generate(12)?.to_seed_normalized(""),
  694. None,
  695. )?;
  696. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  697. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  698. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  699. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  700. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  701. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  702. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  703. let response = http_client.post_swap(swap_request.clone()).await;
  704. assert!(response.is_ok());
  705. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  706. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  707. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  708. let response = http_client.post_swap(swap_request.clone()).await;
  709. match response {
  710. Err(err) => match err {
  711. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  712. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  713. },
  714. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  715. }
  716. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  717. let swap_request = SwapRequest::new(proofs, pre_mint.blinded_messages());
  718. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  719. let response = http_client.post_swap(swap_request.clone()).await;
  720. match response {
  721. Err(err) => match err {
  722. cdk::Error::TokenAlreadySpent => (),
  723. err => {
  724. bail!("Wrong mint error returned: {}", err.to_string());
  725. }
  726. },
  727. Ok(_) => {
  728. bail!("Should not have allowed to mint with multiple units");
  729. }
  730. }
  731. Ok(())
  732. }
  733. /// Tests that tokens cannot be melted after a failed swap attempt
  734. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  735. async fn test_fake_mint_melt_spend_after_fail() -> Result<()> {
  736. let wallet = Wallet::new(
  737. MINT_URL,
  738. CurrencyUnit::Sat,
  739. Arc::new(memory::empty().await?),
  740. &Mnemonic::generate(12)?.to_seed_normalized(""),
  741. None,
  742. )?;
  743. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  744. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  745. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  746. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  747. let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?;
  748. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  749. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  750. let response = http_client.post_swap(swap_request.clone()).await;
  751. assert!(response.is_ok());
  752. let pre_mint = PreMintSecrets::random(active_keyset_id, 101.into(), &SplitTarget::None)?;
  753. let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
  754. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  755. let response = http_client.post_swap(swap_request.clone()).await;
  756. match response {
  757. Err(err) => match err {
  758. cdk::Error::TransactionUnbalanced(_, _, _) => (),
  759. err => bail!("Wrong mint error returned expected TransactionUnbalanced, got: {err}"),
  760. },
  761. Ok(_) => bail!("Should not have allowed swap with unbalanced"),
  762. }
  763. let input_amount: u64 = proofs.total_amount()?.into();
  764. let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string());
  765. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  766. let melt_request = MeltBolt11Request::new(melt_quote.id, proofs, None);
  767. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  768. let response = http_client.post_melt(melt_request.clone()).await;
  769. match response {
  770. Err(err) => match err {
  771. cdk::Error::TokenAlreadySpent => (),
  772. err => {
  773. bail!("Wrong mint error returned: {}", err.to_string());
  774. }
  775. },
  776. Ok(_) => {
  777. bail!("Should not have allowed to melt with multiple units");
  778. }
  779. }
  780. Ok(())
  781. }
  782. /// Tests that attempting to swap with duplicate proofs fails
  783. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  784. async fn test_fake_mint_duplicate_proofs_swap() -> Result<()> {
  785. let wallet = Wallet::new(
  786. MINT_URL,
  787. CurrencyUnit::Sat,
  788. Arc::new(memory::empty().await?),
  789. &Mnemonic::generate(12)?.to_seed_normalized(""),
  790. None,
  791. )?;
  792. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  793. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  794. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  795. let active_keyset_id = wallet.get_active_mint_keyset().await?.id;
  796. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  797. let pre_mint =
  798. PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?;
  799. let swap_request = SwapRequest::new(inputs.clone(), pre_mint.blinded_messages());
  800. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  801. let response = http_client.post_swap(swap_request.clone()).await;
  802. match response {
  803. Err(err) => match err {
  804. cdk::Error::DuplicateInputs => (),
  805. err => {
  806. bail!(
  807. "Wrong mint error returned, expected duplicate inputs: {}",
  808. err.to_string()
  809. );
  810. }
  811. },
  812. Ok(_) => {
  813. bail!("Should not have allowed duplicate inputs");
  814. }
  815. }
  816. let blinded_message = pre_mint.blinded_messages();
  817. let outputs = vec![blinded_message[0].clone(), blinded_message[0].clone()];
  818. let swap_request = SwapRequest::new(inputs, outputs);
  819. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  820. let response = http_client.post_swap(swap_request.clone()).await;
  821. match response {
  822. Err(err) => match err {
  823. cdk::Error::DuplicateOutputs => (),
  824. err => {
  825. bail!(
  826. "Wrong mint error returned, expected duplicate outputs: {}",
  827. err.to_string()
  828. );
  829. }
  830. },
  831. Ok(_) => {
  832. bail!("Should not have allow duplicate inputs");
  833. }
  834. }
  835. Ok(())
  836. }
  837. /// Tests that attempting to melt with duplicate proofs fails
  838. #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
  839. async fn test_fake_mint_duplicate_proofs_melt() -> Result<()> {
  840. let wallet = Wallet::new(
  841. MINT_URL,
  842. CurrencyUnit::Sat,
  843. Arc::new(memory::empty().await?),
  844. &Mnemonic::generate(12)?.to_seed_normalized(""),
  845. None,
  846. )?;
  847. let mint_quote = wallet.mint_quote(100.into(), None).await?;
  848. wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
  849. let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?;
  850. let inputs = vec![proofs[0].clone(), proofs[0].clone()];
  851. let invoice = create_fake_invoice(7000, "".to_string());
  852. let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
  853. let melt_request = MeltBolt11Request::new(melt_quote.id, inputs, None);
  854. let http_client = HttpClient::new(MINT_URL.parse()?, None);
  855. let response = http_client.post_melt(melt_request.clone()).await;
  856. match response {
  857. Err(err) => match err {
  858. cdk::Error::DuplicateInputs => (),
  859. err => {
  860. bail!("Wrong mint error returned: {}", err.to_string());
  861. }
  862. },
  863. Ok(_) => {
  864. bail!("Should not have allow duplicate inputs");
  865. }
  866. }
  867. Ok(())
  868. }