|
@@ -1,88 +1,370 @@
|
|
|
-//! Mint integration tests
|
|
|
+//! Mint tests
|
|
|
|
|
|
+use cdk::amount::{Amount, SplitTarget};
|
|
|
+use cdk::dhke::construct_proofs;
|
|
|
+use cdk::util::unix_time;
|
|
|
use std::collections::HashMap;
|
|
|
use std::sync::Arc;
|
|
|
-use std::time::Duration;
|
|
|
+use tokio::sync::OnceCell;
|
|
|
|
|
|
use anyhow::{bail, Result};
|
|
|
use bip39::Mnemonic;
|
|
|
-use cdk::amount::SplitTarget;
|
|
|
-use cdk::cdk_database::WalletMemoryDatabase;
|
|
|
-use cdk::nuts::CurrencyUnit;
|
|
|
-use cdk::wallet::SendKind;
|
|
|
-use cdk::Error;
|
|
|
-use cdk::Wallet;
|
|
|
-use cdk_integration_tests::{create_backends_fake_wallet, start_mint, wallet_mint, MINT_URL};
|
|
|
+use cdk::cdk_database::mint_memory::MintMemoryDatabase;
|
|
|
+use cdk::nuts::{
|
|
|
+ CurrencyUnit, Id, MintBolt11Request, MintInfo, Nuts, PreMintSecrets, Proofs, SecretKey,
|
|
|
+ SpendingConditions, SwapRequest,
|
|
|
+};
|
|
|
+use cdk::Mint;
|
|
|
|
|
|
-#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
-pub async fn test_mint_double_receive() -> Result<()> {
|
|
|
- tokio::spawn(async move {
|
|
|
- let ln_backends = create_backends_fake_wallet();
|
|
|
+pub const MINT_URL: &str = "http://127.0.0.1:8088";
|
|
|
+
|
|
|
+static INSTANCE: OnceCell<Mint> = OnceCell::const_new();
|
|
|
|
|
|
- let mut supported_units = HashMap::new();
|
|
|
- supported_units.insert(CurrencyUnit::Sat, (0, 64));
|
|
|
+async fn new_mint(fee: u64) -> Mint {
|
|
|
+ let mut supported_units = HashMap::new();
|
|
|
+ supported_units.insert(CurrencyUnit::Sat, (fee, 32));
|
|
|
|
|
|
- start_mint(ln_backends, supported_units)
|
|
|
- .await
|
|
|
- .expect("Could not start mint")
|
|
|
- });
|
|
|
+ let nuts = Nuts::new()
|
|
|
+ .nut07(true)
|
|
|
+ .nut08(true)
|
|
|
+ .nut09(true)
|
|
|
+ .nut10(true)
|
|
|
+ .nut11(true)
|
|
|
+ .nut12(true)
|
|
|
+ .nut14(true);
|
|
|
|
|
|
- tokio::time::sleep(Duration::from_millis(500)).await;
|
|
|
+ let mint_info = MintInfo::new().nuts(nuts);
|
|
|
|
|
|
- let mnemonic = Mnemonic::generate(12)?;
|
|
|
+ let mnemonic = Mnemonic::generate(12).unwrap();
|
|
|
|
|
|
- let wallet = Wallet::new(
|
|
|
+ let mint = Mint::new(
|
|
|
MINT_URL,
|
|
|
- CurrencyUnit::Sat,
|
|
|
- Arc::new(WalletMemoryDatabase::default()),
|
|
|
&mnemonic.to_seed_normalized(""),
|
|
|
- None,
|
|
|
- )?;
|
|
|
+ mint_info,
|
|
|
+ Arc::new(MintMemoryDatabase::default()),
|
|
|
+ supported_units,
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ mint
|
|
|
+}
|
|
|
|
|
|
- let wallet = Arc::new(wallet);
|
|
|
+async fn initialize() -> &'static Mint {
|
|
|
+ INSTANCE.get_or_init(|| new_mint(0)).await
|
|
|
+}
|
|
|
|
|
|
- wallet_mint(Arc::clone(&wallet), 100.into(), SplitTarget::default()).await?;
|
|
|
- println!("Minted");
|
|
|
+async fn mint_proofs(
|
|
|
+ mint: &Mint,
|
|
|
+ amount: Amount,
|
|
|
+ split_target: &SplitTarget,
|
|
|
+ keys: cdk::nuts::Keys,
|
|
|
+) -> Result<Proofs> {
|
|
|
+ let request_lookup = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
|
- let token = wallet
|
|
|
- .send(
|
|
|
- 10.into(),
|
|
|
- None,
|
|
|
- None,
|
|
|
- &SplitTarget::default(),
|
|
|
- &SendKind::default(),
|
|
|
- false,
|
|
|
+ let mint_quote = mint
|
|
|
+ .new_mint_quote(
|
|
|
+ MINT_URL.parse()?,
|
|
|
+ "".to_string(),
|
|
|
+ CurrencyUnit::Sat,
|
|
|
+ amount,
|
|
|
+ unix_time() + 36000,
|
|
|
+ request_lookup.to_string(),
|
|
|
)
|
|
|
.await?;
|
|
|
|
|
|
- let mnemonic = Mnemonic::generate(12)?;
|
|
|
+ mint.pay_mint_quote_for_request_id(&request_lookup).await?;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
|
|
|
- let wallet_two = Wallet::new(
|
|
|
- MINT_URL,
|
|
|
- CurrencyUnit::Sat,
|
|
|
- Arc::new(WalletMemoryDatabase::default()),
|
|
|
- &mnemonic.to_seed_normalized(""),
|
|
|
- None,
|
|
|
+ let premint = PreMintSecrets::random(keyset_id, amount, split_target)?;
|
|
|
+
|
|
|
+ let mint_request = MintBolt11Request {
|
|
|
+ quote: mint_quote.id,
|
|
|
+ outputs: premint.blinded_messages(),
|
|
|
+ };
|
|
|
+
|
|
|
+ let after_mint = mint.process_mint_request(mint_request).await?;
|
|
|
+
|
|
|
+ let proofs = construct_proofs(
|
|
|
+ after_mint.signatures,
|
|
|
+ premint.rs(),
|
|
|
+ premint.secrets(),
|
|
|
+ &keys,
|
|
|
)?;
|
|
|
|
|
|
- let rec = wallet_two
|
|
|
- .receive(&token.to_string(), SplitTarget::default(), &[], &[])
|
|
|
- .await?;
|
|
|
- println!("Received: {}", rec);
|
|
|
-
|
|
|
- // Attempt to receive again
|
|
|
- if let Err(err) = wallet
|
|
|
- .receive(&token.to_string(), SplitTarget::default(), &[], &[])
|
|
|
- .await
|
|
|
- {
|
|
|
- match err {
|
|
|
- Error::TokenAlreadySpent => (),
|
|
|
+ Ok(proofs)
|
|
|
+}
|
|
|
+
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+async fn test_mint_double_spend() -> Result<()> {
|
|
|
+ let mint = initialize().await;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ let swap = mint.process_swap_request(swap_request).await;
|
|
|
+
|
|
|
+ assert!(swap.is_ok());
|
|
|
+
|
|
|
+ let preswap_two = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_two_request = SwapRequest::new(proofs, preswap_two.blinded_messages());
|
|
|
+
|
|
|
+ match mint.process_swap_request(swap_two_request).await {
|
|
|
+ Ok(_) => bail!("Proofs double spent"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TokenAlreadySpent => (),
|
|
|
+ _ => bail!("Wrong error returned"),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+/// This attempts to swap for more outputs then inputs.
|
|
|
+/// This will work if the mint does not check for outputs amounts overflowing
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+async fn test_attempt_to_swap_by_overflowing() -> Result<()> {
|
|
|
+ let mint = initialize().await;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
|
|
|
+
|
|
|
+ let amount = 2_u64.pow(63);
|
|
|
+
|
|
|
+ let pre_mint_amount =
|
|
|
+ PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
|
|
|
+ let pre_mint_amount_two =
|
|
|
+ PreMintSecrets::random(keyset_id, amount.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let mut pre_mint = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ pre_mint.combine(pre_mint_amount);
|
|
|
+ pre_mint.combine(pre_mint_amount_two);
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), pre_mint.blinded_messages());
|
|
|
+
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap occurred with overflow"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::NUT03(cdk::nuts::nut03::Error::Amount(_)) => (),
|
|
|
+ _ => {
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned in swap overflow")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+pub async fn test_p2pk_swap() -> Result<()> {
|
|
|
+ let mint = initialize().await;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
|
|
|
+
|
|
|
+ let secret = SecretKey::generate();
|
|
|
+
|
|
|
+ let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
|
|
|
+
|
|
|
+ let pre_swap = PreMintSecrets::with_conditions(
|
|
|
+ keyset_id,
|
|
|
+ 100.into(),
|
|
|
+ &SplitTarget::default(),
|
|
|
+ &spending_conditions,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().cloned().unwrap().keys;
|
|
|
+
|
|
|
+ let post_swap = mint.process_swap_request(swap_request).await?;
|
|
|
+
|
|
|
+ let mut proofs = construct_proofs(
|
|
|
+ post_swap.signatures,
|
|
|
+ pre_swap.rs(),
|
|
|
+ pre_swap.secrets(),
|
|
|
+ &keys,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ let pre_swap = PreMintSecrets::random(keyset_id, 100.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
|
|
|
+
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Proofs spent without sig"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::NUT11(cdk::nuts::nut11::Error::SignaturesNotProvided) => (),
|
|
|
_ => {
|
|
|
- println!("{}", err);
|
|
|
- bail!("Expected an already spent error");
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned")
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
}
|
|
|
|
|
|
+ for proof in &mut proofs {
|
|
|
+ proof.sign_p2pk(secret.clone())?;
|
|
|
+ }
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), pre_swap.blinded_messages());
|
|
|
+
|
|
|
+ let attempt_swap = mint.process_swap_request(swap_request).await;
|
|
|
+
|
|
|
+ assert!(attempt_swap.is_ok());
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+async fn test_swap_unbalanced() -> Result<()> {
|
|
|
+ let mint = initialize().await;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let proofs = mint_proofs(mint, 100.into(), &SplitTarget::default(), keys).await?;
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 95.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => bail!("Wrong error returned"),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 101.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => bail!("Wrong error returned"),
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+async fn test_swap_overpay_underpay_fee() -> Result<()> {
|
|
|
+ let mint = new_mint(1).await;
|
|
|
+
|
|
|
+ mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1).await?;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let proofs = mint_proofs(&mint, 1000.into(), &SplitTarget::default(), keys).await?;
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 9998.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ // Attempt to swap overpaying fee
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => {
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ // Attempt to swap underpaying fee
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => {
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+}
|
|
|
+
|
|
|
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
|
|
+async fn test_mint_enforce_fee() -> Result<()> {
|
|
|
+ let mint = new_mint(1).await;
|
|
|
+
|
|
|
+ let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
|
|
|
+ let keyset_id = Id::from(&keys);
|
|
|
+
|
|
|
+ let mut proofs = mint_proofs(&mint, 1010.into(), &SplitTarget::Value(1.into()), keys).await?;
|
|
|
+
|
|
|
+ let five_proofs: Vec<_> = proofs.drain(..5).collect();
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 5.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ // Attempt to swap underpaying fee
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => {
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 4.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(five_proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ let _ = mint.process_swap_request(swap_request).await?;
|
|
|
+
|
|
|
+ let thousnad_proofs: Vec<_> = proofs.drain(..1001).collect();
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 1000.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ // Attempt to swap underpaying fee
|
|
|
+ match mint.process_swap_request(swap_request).await {
|
|
|
+ Ok(_) => bail!("Swap was allowed unbalanced"),
|
|
|
+ Err(err) => match err {
|
|
|
+ cdk::Error::TransactionUnbalanced(_, _, _) => (),
|
|
|
+ _ => {
|
|
|
+ println!("{:?}", err);
|
|
|
+ bail!("Wrong error returned")
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ let preswap = PreMintSecrets::random(keyset_id, 999.into(), &SplitTarget::default())?;
|
|
|
+
|
|
|
+ let swap_request = SwapRequest::new(thousnad_proofs.clone(), preswap.blinded_messages());
|
|
|
+
|
|
|
+ let _ = mint.process_swap_request(swap_request).await?;
|
|
|
+
|
|
|
Ok(())
|
|
|
}
|