fake_wallet.rs 41 KB

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