123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- use std::sync::Arc;
- use anyhow::Result;
- use bip39::Mnemonic;
- use cdk::amount::SplitTarget;
- use cdk::cdk_database::WalletMemoryDatabase;
- use cdk::nuts::{
- CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintQuoteState, NotificationPayload,
- PreMintSecrets, State,
- };
- use cdk::wallet::client::{HttpClient, MintConnector};
- use cdk::wallet::{Wallet, WalletSubscription};
- use cdk_fake_wallet::{create_fake_invoice, FakeInvoiceDescription};
- use cdk_integration_tests::attempt_to_swap_pending;
- const MINT_URL: &str = "http://127.0.0.1:8086";
- // If both pay and check return pending input proofs should remain pending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_tokens_pending() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Pending,
- check_payment_state: MeltQuoteState::Pending,
- pay_err: false,
- check_err: false,
- };
- let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- attempt_to_swap_pending(&wallet).await?;
- Ok(())
- }
- // If the pay error fails and the check returns unknown or failed
- // The inputs proofs should be unset as spending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_payment_fail() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Unknown,
- check_payment_state: MeltQuoteState::Unknown,
- pay_err: true,
- check_err: false,
- };
- let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Failed,
- check_payment_state: MeltQuoteState::Failed,
- pay_err: true,
- check_err: false,
- };
- let invoice = create_fake_invoice(1000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- // The mint should have unset proofs from pending since payment failed
- let all_proof = wallet.get_unspent_proofs().await?;
- let states = wallet.check_proofs_spent(all_proof).await?;
- for state in states {
- assert!(state.state == State::Unspent);
- }
- let wallet_bal = wallet.total_balance().await?;
- assert!(wallet_bal == 100.into());
- Ok(())
- }
- // When both the pay_invoice and check_invoice both fail
- // the proofs should remain as pending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_payment_fail_and_check() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Unknown,
- check_payment_state: MeltQuoteState::Unknown,
- pay_err: true,
- check_err: true,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let pending = wallet
- .localstore
- .get_proofs(None, None, Some(vec![State::Pending]), None)
- .await?;
- assert!(!pending.is_empty());
- Ok(())
- }
- // In the case that the ln backend returns a failed status but does not error
- // The mint should do a second check, then remove proofs from pending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_payment_return_fail_status() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Failed,
- check_payment_state: MeltQuoteState::Failed,
- pay_err: false,
- check_err: false,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Unknown,
- check_payment_state: MeltQuoteState::Unknown,
- pay_err: false,
- check_err: false,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let pending = wallet
- .localstore
- .get_proofs(None, None, Some(vec![State::Pending]), None)
- .await?;
- assert!(pending.is_empty());
- Ok(())
- }
- // In the case that the ln backend returns a failed status but does not error
- // The mint should do a second check, then remove proofs from pending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_payment_error_unknown() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Failed,
- check_payment_state: MeltQuoteState::Unknown,
- pay_err: true,
- check_err: false,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Unknown,
- check_payment_state: MeltQuoteState::Unknown,
- pay_err: true,
- check_err: false,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- let pending = wallet
- .localstore
- .get_proofs(None, None, Some(vec![State::Pending]), None)
- .await?;
- assert!(pending.is_empty());
- Ok(())
- }
- // In the case that the ln backend returns an err
- // The mint should do a second check, that returns paid
- // Proofs should remain pending
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_payment_err_paid() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription {
- pay_invoice_state: MeltQuoteState::Failed,
- check_payment_state: MeltQuoteState::Paid,
- pay_err: true,
- check_err: false,
- };
- let invoice = create_fake_invoice(7000, serde_json::to_string(&fake_description).unwrap());
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- // The melt should error at the payment invoice command
- let melt = wallet.melt(&melt_quote.id).await;
- assert!(melt.is_err());
- attempt_to_swap_pending(&wallet).await?;
- Ok(())
- }
- #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
- async fn test_fake_melt_change_in_quote() -> Result<()> {
- let wallet = Wallet::new(
- MINT_URL,
- CurrencyUnit::Sat,
- Arc::new(WalletMemoryDatabase::default()),
- &Mnemonic::generate(12)?.to_seed_normalized(""),
- None,
- )?;
- let mint_quote = wallet.mint_quote(100.into(), None).await?;
- wait_for_mint_to_be_paid(&wallet, &mint_quote.id).await?;
- let _mint_amount = wallet
- .mint(&mint_quote.id, SplitTarget::default(), None)
- .await?;
- let fake_description = FakeInvoiceDescription::default();
- let invoice = create_fake_invoice(9000, serde_json::to_string(&fake_description).unwrap());
- let proofs = wallet.get_unspent_proofs().await?;
- let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
- let keyset = wallet.get_active_mint_keyset().await?;
- let premint_secrets = PreMintSecrets::random(keyset.id, 100.into(), &SplitTarget::default())?;
- let client = HttpClient::new(MINT_URL.parse()?);
- let melt_request = MeltBolt11Request {
- quote: melt_quote.id.clone(),
- inputs: proofs.clone(),
- outputs: Some(premint_secrets.blinded_messages()),
- };
- let melt_response = client.post_melt(melt_request).await?;
- assert!(melt_response.change.is_some());
- let check = wallet.melt_quote_status(&melt_quote.id).await?;
- let mut melt_change = melt_response.change.unwrap();
- melt_change.sort_by(|a, b| a.amount.cmp(&b.amount));
- let mut check = check.change.unwrap();
- check.sort_by(|a, b| a.amount.cmp(&b.amount));
- assert_eq!(melt_change, check);
- Ok(())
- }
- // Keep polling the state of the mint quote id until it's paid
- async fn wait_for_mint_to_be_paid(wallet: &Wallet, mint_quote_id: &str) -> Result<()> {
- let mut subscription = wallet
- .subscribe(WalletSubscription::Bolt11MintQuoteState(vec![
- mint_quote_id.to_owned(),
- ]))
- .await;
- while let Some(msg) = subscription.recv().await {
- if let NotificationPayload::MintQuoteBolt11Response(response) = msg {
- if response.state == MintQuoteState::Paid {
- break;
- }
- }
- }
- Ok(())
- }
|