From 05259b99f48fb674e95b40ee0c59f6884312356d Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 13:04:59 +0000 Subject: [PATCH 1/7] fix: mint with outputs greater then total or multiple units --- crates/cdk-common/src/error.rs | 16 +-- .../src/init_fake_wallet.rs | 14 ++ .../tests/fake_wallet.rs | 122 ++++++++++++++++++ crates/cdk/src/mint/melt.rs | 12 +- crates/cdk/src/mint/mint_nut04.rs | 39 +++++- crates/cdk/src/wallet/receive.rs | 2 +- 6 files changed, 186 insertions(+), 19 deletions(-) diff --git a/crates/cdk-common/src/error.rs b/crates/cdk-common/src/error.rs index 27b66676..e179c7ef 100644 --- a/crates/cdk-common/src/error.rs +++ b/crates/cdk-common/src/error.rs @@ -157,9 +157,6 @@ pub enum Error { /// Receive can only be used with tokens from single mint #[error("Multiple mint tokens not supported by receive. Please deconstruct the token and use receive with_proof")] MultiMintTokenNotSupported, - /// Unit Not supported - #[error("Unit not supported for method")] - UnitUnsupported, /// Preimage not provided #[error("Preimage not provided")] PreimageNotProvided, @@ -233,6 +230,9 @@ pub enum Error { /// NUT03 error #[error(transparent)] NUT03(#[from] crate::nuts::nut03::Error), + /// NUT04 error + #[error(transparent)] + NUT04(#[from] crate::nuts::nut04::Error), /// NUT05 error #[error(transparent)] NUT05(#[from] crate::nuts::nut05::Error), @@ -329,7 +329,7 @@ impl From for ErrorResponse { detail: None, }, Error::UnsupportedUnit => ErrorResponse { - code: ErrorCode::UnitUnsupported, + code: ErrorCode::UnsupportedUnit, error: Some(err.to_string()), detail: None, }, @@ -412,7 +412,7 @@ impl From for Error { ErrorCode::KeysetNotFound => Self::UnknownKeySet, ErrorCode::KeysetInactive => Self::InactiveKeyset, ErrorCode::BlindedMessageAlreadySigned => Self::BlindedMessageAlreadySigned, - ErrorCode::UnitUnsupported => Self::UnitUnsupported, + ErrorCode::UnsupportedUnit => Self::UnsupportedUnit, ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced(0, 0, 0), ErrorCode::MintingDisabled => Self::MintingDisabled, ErrorCode::InvoiceAlreadyPaid => Self::RequestAlreadyPaid, @@ -449,7 +449,7 @@ pub enum ErrorCode { /// Blinded Message Already signed BlindedMessageAlreadySigned, /// Unsupported unit - UnitUnsupported, + UnsupportedUnit, /// Token already issed for quote TokensAlreadyIssued, /// Minting Disabled @@ -478,7 +478,7 @@ impl ErrorCode { 10003 => Self::TokenNotVerified, 11001 => Self::TokenAlreadySpent, 11002 => Self::TransactionUnbalanced, - 11005 => Self::UnitUnsupported, + 11005 => Self::UnsupportedUnit, 11006 => Self::AmountOutofLimitRange, 11007 => Self::TokenPending, 12001 => Self::KeysetNotFound, @@ -502,7 +502,7 @@ impl ErrorCode { Self::TokenNotVerified => 10003, Self::TokenAlreadySpent => 11001, Self::TransactionUnbalanced => 11002, - Self::UnitUnsupported => 11005, + Self::UnsupportedUnit => 11005, Self::AmountOutofLimitRange => 11006, Self::TokenPending => 11007, Self::KeysetNotFound => 12001, diff --git a/crates/cdk-integration-tests/src/init_fake_wallet.rs b/crates/cdk-integration-tests/src/init_fake_wallet.rs index 831ff079..46a55216 100644 --- a/crates/cdk-integration-tests/src/init_fake_wallet.rs +++ b/crates/cdk-integration-tests/src/init_fake_wallet.rs @@ -46,6 +46,20 @@ where Arc::new(fake_wallet), ); + let fee_reserve = FeeReserve { + min_fee_reserve: 1.into(), + percent_fee_reserve: 1.0, + }; + + let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); + + mint_builder = mint_builder.add_ln_backend( + CurrencyUnit::Usd, + PaymentMethod::Bolt11, + MintMeltLimits::new(1, 5_000), + Arc::new(fake_wallet), + ); + let mnemonic = Mnemonic::generate(12)?; mint_builder = mint_builder diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index e3c1fa4b..983ad341 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -476,3 +476,125 @@ async fn test_fake_mint_with_wrong_witness() -> Result<()> { Ok(_) => bail!("Minting should not have succeed without a witness"), } } + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_fake_mint_inflated() -> 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, 60).await?; + + let active_keyset_id = wallet.get_active_mint_keyset().await?.id; + + let pre_mint = PreMintSecrets::random(active_keyset_id, 500.into(), &SplitTarget::None)?; + + let quote_info = wallet + .localstore + .get_mint_quote(&mint_quote.id) + .await? + .expect("there is a quote"); + + let mut mint_request = MintBolt11Request { + quote: mint_quote.id, + outputs: pre_mint.blinded_messages(), + signature: None, + }; + + if let Some(secret_key) = quote_info.secret_key { + mint_request.sign(secret_key)?; + } + let http_client = HttpClient::new(MINT_URL.parse()?); + + let response = http_client.post_mint(mint_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::TransactionUnbalanced(_, _, _) => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed second payment"); + } + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_fake_mint_multiple_units() -> 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, 60).await?; + + let active_keyset_id = wallet.get_active_mint_keyset().await?.id; + + let pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?; + + let wallet_usd = Wallet::new( + MINT_URL, + CurrencyUnit::Usd, + Arc::new(WalletMemoryDatabase::default()), + &Mnemonic::generate(12)?.to_seed_normalized(""), + None, + )?; + + let active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id; + + let usd_pre_mint = PreMintSecrets::random(active_keyset_id, 50.into(), &SplitTarget::None)?; + + let quote_info = wallet + .localstore + .get_mint_quote(&mint_quote.id) + .await? + .expect("there is a quote"); + + let mut sat_outputs = pre_mint.blinded_messages(); + + let mut usd_outputs = usd_pre_mint.blinded_messages(); + + sat_outputs.append(&mut usd_outputs); + + let mut mint_request = MintBolt11Request { + quote: mint_quote.id, + outputs: sat_outputs, + signature: None, + }; + + if let Some(secret_key) = quote_info.secret_key { + mint_request.sign(secret_key)?; + } + let http_client = HttpClient::new(MINT_URL.parse()?); + + let response = http_client.post_mint(mint_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::UnsupportedUnit => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed to mint with multiple units"); + } + } + + Ok(()) +} diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index 89ba6d4a..e6eb86e9 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -41,7 +41,7 @@ impl Mint { let settings = nut05 .get_settings(&unit, &method) - .ok_or(Error::UnitUnsupported)?; + .ok_or(Error::UnsupportedUnit)?; if matches!(options, Some(MeltOptions::Mpp { mpp: _ })) { // Verify there is no corresponding mint quote. @@ -103,7 +103,7 @@ impl Mint { .ok_or_else(|| { tracing::info!("Could not get ln backend for {}, bolt11 ", unit); - Error::UnitUnsupported + Error::UnsupportedUnit })?; let payment_quote = ln.get_payment_quote(melt_request).await.map_err(|err| { @@ -113,7 +113,7 @@ impl Mint { err ); - Error::UnitUnsupported + Error::UnsupportedUnit })?; // We only want to set the msats_to_pay of the melt quote if the invoice is amountless @@ -224,7 +224,7 @@ impl Mint { Some( to_unit(partial_msats, &CurrencyUnit::Msat, &melt_quote.unit) - .map_err(|_| Error::UnitUnsupported)?, + .map_err(|_| Error::UnsupportedUnit)?, ) } false => None, @@ -233,7 +233,7 @@ impl Mint { let amount_to_pay = match partial_amount { Some(amount_to_pay) => amount_to_pay, None => to_unit(invoice_amount_msats, &CurrencyUnit::Msat, &melt_quote.unit) - .map_err(|_| Error::UnitUnsupported)?, + .map_err(|_| Error::UnsupportedUnit)?, }; let inputs_amount_quote_unit = melt_request.proofs_amount().map_err(|_| { @@ -502,7 +502,7 @@ impl Mint { tracing::error!("Could not reset melt quote state: {}", err); } - return Err(Error::UnitUnsupported); + return Err(Error::UnsupportedUnit); } }; diff --git a/crates/cdk/src/mint/mint_nut04.rs b/crates/cdk/src/mint/mint_nut04.rs index ab2ef58a..717e8181 100644 --- a/crates/cdk/src/mint/mint_nut04.rs +++ b/crates/cdk/src/mint/mint_nut04.rs @@ -1,3 +1,6 @@ +use std::collections::HashSet; + +use cdk_common::Id; use tracing::instrument; use uuid::Uuid; @@ -49,7 +52,7 @@ impl Mint { } } None => { - return Err(Error::UnitUnsupported); + return Err(Error::UnsupportedUnit); } } @@ -77,7 +80,7 @@ impl Mint { .ok_or_else(|| { tracing::info!("Bolt11 mint request for unsupported unit"); - Error::UnitUnsupported + Error::UnsupportedUnit })?; let mint_ttl = self.localstore.get_quote_ttl().await?.mint_ttl; @@ -292,6 +295,35 @@ impl Mint { mint_request.verify_signature(pubkey)?; } + // We check the the total value of blinded messages == mint quote + if mint_request.total_amount()? != mint_quote.amount { + return Err(Error::TransactionUnbalanced( + mint_quote.amount.into(), + mint_request.total_amount()?.into(), + 0, + )); + } + + let keyset_ids: HashSet = mint_request.outputs.iter().map(|b| b.keyset_id).collect(); + + let mut keyset_units = HashSet::new(); + + for keyset_id in keyset_ids { + let keyset = self.keyset(&keyset_id).await?.ok_or(Error::UnknownKeySet)?; + + keyset_units.insert(keyset.unit); + } + + if keyset_units.len() != 1 { + tracing::debug!("Client attempted to mint with outputs of multiple units"); + return Err(Error::UnsupportedUnit); + } + + if keyset_units.iter().next().expect("Checked len above") != &mint_quote.unit { + tracing::debug!("Client attempted to mint with unit not in quote"); + return Err(Error::UnsupportedUnit); + } + let blinded_messages: Vec = mint_request .outputs .iter() @@ -315,8 +347,7 @@ impl Mint { self.localstore .update_mint_quote_state(&mint_request.quote, MintQuoteState::Paid) - .await - .unwrap(); + .await?; return Err(Error::BlindedMessageAlreadySigned); } diff --git a/crates/cdk/src/wallet/receive.rs b/crates/cdk/src/wallet/receive.rs index 92d06161..1baddf34 100644 --- a/crates/cdk/src/wallet/receive.rs +++ b/crates/cdk/src/wallet/receive.rs @@ -195,7 +195,7 @@ impl Wallet { let unit = token.unit().unwrap_or_default(); if unit != self.unit { - return Err(Error::UnitUnsupported); + return Err(Error::UnsupportedUnit); } let proofs = token.proofs(); From e31de19ba545230c62377c0d909ce98318f758be Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 13:44:27 +0000 Subject: [PATCH 2/7] chore: add tests for swap with multiple units --- crates/cdk-fake-wallet/src/lib.rs | 7 +- .../tests/fake_wallet.rs | 138 +++++++++++++++++- crates/cdk/src/mint/swap.rs | 2 +- misc/fake_itests.sh | 2 +- 4 files changed, 142 insertions(+), 7 deletions(-) diff --git a/crates/cdk-fake-wallet/src/lib.rs b/crates/cdk-fake-wallet/src/lib.rs index 1aab2dd3..d4ce15be 100644 --- a/crates/cdk-fake-wallet/src/lib.rs +++ b/crates/cdk-fake-wallet/src/lib.rs @@ -15,7 +15,7 @@ use async_trait::async_trait; use bitcoin::hashes::{sha256, Hash}; use bitcoin::secp256k1::rand::{thread_rng, Rng}; use bitcoin::secp256k1::{Secp256k1, SecretKey}; -use cdk::amount::{to_unit, Amount, MSAT_IN_SAT}; +use cdk::amount::{Amount, MSAT_IN_SAT}; use cdk::cdk_lightning::{ self, CreateInvoiceResponse, MintLightning, PayInvoiceResponse, PaymentQuoteResponse, Settings, }; @@ -199,14 +199,15 @@ impl MintLightning for FakeWallet { async fn create_invoice( &self, amount: Amount, - unit: &CurrencyUnit, + _unit: &CurrencyUnit, description: String, unix_expiry: u64, ) -> Result { let time_now = unix_time(); assert!(unix_expiry > time_now); - let amount_msat = to_unit(amount, unit, &CurrencyUnit::Msat)?; + // Since this is fake we just use the amount no matter the unit to create an invoice + let amount_msat = amount; let invoice = create_fake_invoice(amount_msat.into(), description); diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index 983ad341..422a077c 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -6,8 +6,8 @@ use cdk::amount::SplitTarget; use cdk::cdk_database::WalletMemoryDatabase; use cdk::nuts::nut00::ProofsMethods; use cdk::nuts::{ - CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintBolt11Request, PreMintSecrets, SecretKey, - State, + CurrencyUnit, MeltBolt11Request, MeltQuoteState, MintBolt11Request, PreMintSecrets, Proofs, + SecretKey, State, SwapRequest, }; use cdk::wallet::client::{HttpClient, MintConnector}; use cdk::wallet::Wallet; @@ -598,3 +598,137 @@ async fn test_fake_mint_multiple_units() -> Result<()> { Ok(()) } + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_fake_mint_multiple_unit_swap() -> 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, 60).await?; + + let proofs = wallet.mint(&mint_quote.id, SplitTarget::None, None).await?; + + let wallet_usd = Wallet::new( + MINT_URL, + CurrencyUnit::Usd, + Arc::new(WalletMemoryDatabase::default()), + &Mnemonic::generate(12)?.to_seed_normalized(""), + None, + )?; + + let mint_quote = wallet_usd.mint_quote(100.into(), None).await?; + + wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?; + + let usd_proofs = wallet_usd + .mint(&mint_quote.id, SplitTarget::None, None) + .await?; + + let active_keyset_id = wallet.get_active_mint_keyset().await?.id; + + { + let inputs: Proofs = vec![ + proofs.first().expect("There is a proof").clone(), + usd_proofs.first().expect("There is a proof").clone(), + ]; + + let pre_mint = + PreMintSecrets::random(active_keyset_id, inputs.total_amount()?, &SplitTarget::None)?; + + let swap_request = SwapRequest { + inputs, + outputs: pre_mint.blinded_messages(), + }; + + let http_client = HttpClient::new(MINT_URL.parse()?); + let response = http_client.post_swap(swap_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::UnsupportedUnit => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed to mint with multiple units"); + } + } + } + + { + let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id; + let inputs: Proofs = proofs.into_iter().take(2).collect(); + + let total_inputs = inputs.total_amount()?; + + let half = total_inputs / 2.into(); + let usd_pre_mint = PreMintSecrets::random(usd_active_keyset_id, half, &SplitTarget::None)?; + let pre_mint = + PreMintSecrets::random(active_keyset_id, total_inputs - half, &SplitTarget::None)?; + + let mut usd_outputs = usd_pre_mint.blinded_messages(); + let mut sat_outputs = pre_mint.blinded_messages(); + + usd_outputs.append(&mut sat_outputs); + + let swap_request = SwapRequest { + inputs, + outputs: usd_outputs, + }; + + let http_client = HttpClient::new(MINT_URL.parse()?); + let response = http_client.post_swap(swap_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::UnsupportedUnit => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed to mint with multiple units"); + } + } + } + + // let mut sat_outputs = pre_mint.blinded_messages(); + + // let mut usd_outputs = usd_pre_mint.blinded_messages(); + + // sat_outputs.append(&mut usd_outputs); + + // let mut mint_request = MintBolt11Request { + // quote: mint_quote.id, + // outputs: sat_outputs, + // signature: None, + // }; + + // if let Some(secret_key) = quote_info.secret_key { + // mint_request.sign(secret_key)?; + // } + + // let response = http_client.post_mint(mint_request.clone()).await; + + // match response { + // Err(err) => match err { + // cdk::Error::UnsupportedUnit => (), + // err => { + // bail!("Wrong mint error returned: {}", err.to_string()); + // } + // }, + // Ok(_) => { + // bail!("Should not have allowed to mint with multiple units"); + // } + // } + + Ok(()) +} diff --git a/crates/cdk/src/mint/swap.rs b/crates/cdk/src/mint/swap.rs index 6dcf31fa..dba94ecc 100644 --- a/crates/cdk/src/mint/swap.rs +++ b/crates/cdk/src/mint/swap.rs @@ -132,7 +132,7 @@ impl Mint { self.localstore .update_proofs_states(&input_ys, State::Unspent) .await?; - return Err(Error::MultipleUnits); + return Err(Error::UnsupportedUnit); } let EnforceSigFlag { diff --git a/misc/fake_itests.sh b/misc/fake_itests.sh index deba74fd..2df02f2a 100755 --- a/misc/fake_itests.sh +++ b/misc/fake_itests.sh @@ -76,7 +76,7 @@ done # Run cargo test -cargo test -p cdk-integration-tests --test fake_wallet +cargo test -p cdk-integration-tests --test fake_wallet test_fake_mint_multiple_unit_swap # cargo test -p cdk-integration-tests --test mint # Capture the exit status of cargo test From e8a85bf0976ad264ccbacedfd56a3956ff2f7c2f Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 14:56:00 +0000 Subject: [PATCH 3/7] feat: tests for melt multi unit --- .../src/init_fake_wallet.rs | 4 +- .../tests/fake_wallet.rs | 134 +++++++++++++++--- crates/cdk/src/mint/melt.rs | 2 +- misc/fake_itests.sh | 2 +- 4 files changed, 115 insertions(+), 27 deletions(-) diff --git a/crates/cdk-integration-tests/src/init_fake_wallet.rs b/crates/cdk-integration-tests/src/init_fake_wallet.rs index 46a55216..3f1f5e22 100644 --- a/crates/cdk-integration-tests/src/init_fake_wallet.rs +++ b/crates/cdk-integration-tests/src/init_fake_wallet.rs @@ -30,7 +30,7 @@ where let fee_reserve = FeeReserve { min_fee_reserve: 1.into(), - percent_fee_reserve: 1.0, + percent_fee_reserve: 0.0, }; let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); @@ -48,7 +48,7 @@ where let fee_reserve = FeeReserve { min_fee_reserve: 1.into(), - percent_fee_reserve: 1.0, + percent_fee_reserve: 0.0, }; let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0); diff --git a/crates/cdk-integration-tests/tests/fake_wallet.rs b/crates/cdk-integration-tests/tests/fake_wallet.rs index 422a077c..b22cb721 100644 --- a/crates/cdk-integration-tests/tests/fake_wallet.rs +++ b/crates/cdk-integration-tests/tests/fake_wallet.rs @@ -700,35 +700,123 @@ async fn test_fake_mint_multiple_unit_swap() -> Result<()> { } } - // let mut sat_outputs = pre_mint.blinded_messages(); + Ok(()) +} - // let mut usd_outputs = usd_pre_mint.blinded_messages(); +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_fake_mint_multiple_unit_melt() -> Result<()> { + let wallet = Wallet::new( + MINT_URL, + CurrencyUnit::Sat, + Arc::new(WalletMemoryDatabase::default()), + &Mnemonic::generate(12)?.to_seed_normalized(""), + None, + )?; - // sat_outputs.append(&mut usd_outputs); + let mint_quote = wallet.mint_quote(100.into(), None).await.unwrap(); - // let mut mint_request = MintBolt11Request { - // quote: mint_quote.id, - // outputs: sat_outputs, - // signature: None, - // }; + wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?; - // if let Some(secret_key) = quote_info.secret_key { - // mint_request.sign(secret_key)?; - // } + let proofs = wallet + .mint(&mint_quote.id, SplitTarget::None, None) + .await + .unwrap(); - // let response = http_client.post_mint(mint_request.clone()).await; + println!("Minted sat"); - // match response { - // Err(err) => match err { - // cdk::Error::UnsupportedUnit => (), - // err => { - // bail!("Wrong mint error returned: {}", err.to_string()); - // } - // }, - // Ok(_) => { - // bail!("Should not have allowed to mint with multiple units"); - // } - // } + let wallet_usd = Wallet::new( + MINT_URL, + CurrencyUnit::Usd, + Arc::new(WalletMemoryDatabase::default()), + &Mnemonic::generate(12)?.to_seed_normalized(""), + None, + )?; + + let mint_quote = wallet_usd.mint_quote(100.into(), None).await.unwrap(); + println!("Minted quote usd"); + + wait_for_mint_to_be_paid(&wallet_usd, &mint_quote.id, 60).await?; + + let usd_proofs = wallet_usd + .mint(&mint_quote.id, SplitTarget::None, None) + .await + .unwrap(); + + { + let inputs: Proofs = vec![ + proofs.first().expect("There is a proof").clone(), + usd_proofs.first().expect("There is a proof").clone(), + ]; + + let input_amount: u64 = inputs.total_amount()?.into(); + let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string()); + let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?; + + let melt_request = MeltBolt11Request { + quote: melt_quote.id, + inputs, + outputs: None, + }; + + let http_client = HttpClient::new(MINT_URL.parse()?); + let response = http_client.post_melt(melt_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::UnsupportedUnit => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed to melt with multiple units"); + } + } + } + + { + let inputs: Proofs = vec![proofs.first().expect("There is a proof").clone()]; + + let input_amount: u64 = inputs.total_amount()?.into(); + + let invoice = create_fake_invoice((input_amount - 1) * 1000, "".to_string()); + let active_keyset_id = wallet.get_active_mint_keyset().await?.id; + let usd_active_keyset_id = wallet_usd.get_active_mint_keyset().await?.id; + + let usd_pre_mint = PreMintSecrets::random( + usd_active_keyset_id, + inputs.total_amount()? + 100.into(), + &SplitTarget::None, + )?; + let pre_mint = PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::None)?; + + let mut usd_outputs = usd_pre_mint.blinded_messages(); + let mut sat_outputs = pre_mint.blinded_messages(); + + usd_outputs.append(&mut sat_outputs); + let quote = wallet.melt_quote(invoice.to_string(), None).await?; + + let melt_request = MeltBolt11Request { + quote: quote.id, + inputs, + outputs: Some(usd_outputs), + }; + + let http_client = HttpClient::new(MINT_URL.parse()?); + let response = http_client.post_melt(melt_request.clone()).await; + + match response { + Err(err) => match err { + cdk::Error::UnsupportedUnit => (), + err => { + bail!("Wrong mint error returned: {}", err.to_string()); + } + }, + Ok(_) => { + bail!("Should not have allowed to melt with multiple units"); + } + } + } Ok(()) } diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index e6eb86e9..7cd935f6 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -366,7 +366,7 @@ impl Mint { // Check that all input and output proofs are the same unit if keyset_units.len().gt(&1) { - return Err(Error::MultipleUnits); + return Err(Error::UnsupportedUnit); } tracing::debug!("Verified melt quote: {}", melt_request.quote); diff --git a/misc/fake_itests.sh b/misc/fake_itests.sh index 2df02f2a..deba74fd 100755 --- a/misc/fake_itests.sh +++ b/misc/fake_itests.sh @@ -76,7 +76,7 @@ done # Run cargo test -cargo test -p cdk-integration-tests --test fake_wallet test_fake_mint_multiple_unit_swap +cargo test -p cdk-integration-tests --test fake_wallet # cargo test -p cdk-integration-tests --test mint # Capture the exit status of cargo test From 0adf4e210db4be3c1eef57753b0129593bdd085e Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 16:40:11 +0000 Subject: [PATCH 4/7] fix: cache test mint --- crates/cdk-integration-tests/tests/regtest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cdk-integration-tests/tests/regtest.rs b/crates/cdk-integration-tests/tests/regtest.rs index 791d94f9..5545a295 100644 --- a/crates/cdk-integration-tests/tests/regtest.rs +++ b/crates/cdk-integration-tests/tests/regtest.rs @@ -440,7 +440,7 @@ async fn test_cached_mint() -> Result<()> { let active_keyset_id = wallet.get_active_mint_keyset().await?.id; let http_client = HttpClient::new(get_mint_url().as_str().parse()?); let premint_secrets = - PreMintSecrets::random(active_keyset_id, 31.into(), &SplitTarget::default()).unwrap(); + PreMintSecrets::random(active_keyset_id, 100.into(), &SplitTarget::default()).unwrap(); let mut request = MintBolt11Request { quote: quote.id, From a44b49ce151523029103aee5aff391469e468a0a Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 16:41:14 +0000 Subject: [PATCH 5/7] chore: change log --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7367a59b..6fc958bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,10 @@ ### Removed +#[0.6.2] +### Fixed +cdk: Missing check on mint that outputs equals the quote amount ([thesimplekid]). + #[0.6.1] ### Added cdk-mintd: Get work-dir from env var ([thesimplekid]) From 5af1be369c231144c6d186f8d615bac19dd38152 Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 17:17:35 +0000 Subject: [PATCH 6/7] fix: reset quote status --- crates/cdk/src/mint/mint_nut04.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/cdk/src/mint/mint_nut04.rs b/crates/cdk/src/mint/mint_nut04.rs index 717e8181..c5bbbf7a 100644 --- a/crates/cdk/src/mint/mint_nut04.rs +++ b/crates/cdk/src/mint/mint_nut04.rs @@ -278,12 +278,20 @@ impl Mint { match state { MintQuoteState::Unpaid => { + let _state = self + .localstore + .update_mint_quote_state(&mint_request.quote, MintQuoteState::Unpaid) + .await?; return Err(Error::UnpaidQuote); } MintQuoteState::Pending => { return Err(Error::PendingQuote); } MintQuoteState::Issued => { + let _state = self + .localstore + .update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued) + .await?; return Err(Error::IssuedQuote); } MintQuoteState::Paid => (), From f495a2cf04a5c4436b4982a5dbbf58f0d3c0513b Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Tue, 4 Feb 2025 17:22:04 +0000 Subject: [PATCH 7/7] chore: release mintd:v0.6.2,cdk:0.6.1 --- CHANGELOG.md | 3 ++- crates/cdk-common/Cargo.toml | 2 +- crates/cdk-mintd/Cargo.toml | 4 ++-- crates/cdk/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc958bc..febc2383 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,9 +37,10 @@ ### Removed -#[0.6.2] +#[cdk:v0.6.1,cdk-mintd:v0.6.2] ### Fixed cdk: Missing check on mint that outputs equals the quote amount ([thesimplekid]). +cdk: Reset mint quote status if in state that cannot continue ([thesimeokid]). #[0.6.1] ### Added diff --git a/crates/cdk-common/Cargo.toml b/crates/cdk-common/Cargo.toml index 0a8e3591..dee0bb63 100644 --- a/crates/cdk-common/Cargo.toml +++ b/crates/cdk-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cdk-common" -version = "0.6.0" +version = "0.6.1" edition = "2021" description = "CDK common types and traits" rust-version = "1.63.0" # MSRV diff --git a/crates/cdk-mintd/Cargo.toml b/crates/cdk-mintd/Cargo.toml index fcf92447..550d415a 100644 --- a/crates/cdk-mintd/Cargo.toml +++ b/crates/cdk-mintd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cdk-mintd" -version = "0.6.1" +version = "0.6.2" edition = "2021" authors = ["CDK Developers"] license = "MIT" @@ -12,7 +12,7 @@ description = "CDK mint binary" [dependencies] anyhow = "1" axum = "0.6.20" -cdk = { path = "../cdk", version = "0.6.0", default-features = false, features = [ +cdk = { path = "../cdk", version = "0.6.1", default-features = false, features = [ "mint", ] } cdk-redb = { path = "../cdk-redb", version = "0.6.0", default-features = false, features = [ diff --git a/crates/cdk/Cargo.toml b/crates/cdk/Cargo.toml index 5c28749c..720f770c 100644 --- a/crates/cdk/Cargo.toml +++ b/crates/cdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cdk" -version = "0.6.0" +version = "0.6.1" edition = "2021" authors = ["CDK Developers"] description = "Core Cashu Development Kit library implementing the Cashu protocol" @@ -21,7 +21,7 @@ http_subscription = [] [dependencies] -cdk-common = { path = "../cdk-common", version = "0.6.0" } +cdk-common = { path = "../cdk-common", version = "0.6.1" } cbor-diag = "0.1.12" async-trait = "0.1" anyhow = { version = "1.0.43", features = ["backtrace"] }