fake_wallet.rs 33 KB

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