Browse Source

feat: unbalanced swap test

thesimplekid 6 months ago
parent
commit
369184cf6a

+ 55 - 1
crates/cdk-integration-tests/src/lib.rs

@@ -8,8 +8,12 @@ use bip39::Mnemonic;
 use cdk::amount::{Amount, SplitTarget};
 use cdk::cdk_database::mint_memory::MintMemoryDatabase;
 use cdk::cdk_lightning::{MintLightning, MintMeltSettings};
+use cdk::dhke::construct_proofs;
 use cdk::mint::FeeReserve;
-use cdk::nuts::{CurrencyUnit, MintInfo, MintQuoteState, Nuts, PaymentMethod};
+use cdk::nuts::{
+    CurrencyUnit, Id, KeySet, MintInfo, MintQuoteState, Nuts, PaymentMethod, PreMintSecrets, Proofs,
+};
+use cdk::wallet::client::HttpClient;
 use cdk::{Mint, Wallet};
 use cdk_axum::LnKey;
 use cdk_fake_wallet::FakeWallet;
@@ -165,3 +169,53 @@ pub async fn wallet_mint(wallet: Arc<Wallet>, amount: Amount) -> Result<()> {
 
     Ok(())
 }
+
+pub async fn mint_proofs(
+    mint_url: &str,
+    amount: Amount,
+    keyset_id: Id,
+    mint_keys: &KeySet,
+) -> anyhow::Result<Proofs> {
+    println!("Minting for ecash");
+    println!();
+
+    let wallet_client = HttpClient::new();
+
+    let mint_quote = wallet_client
+        .post_mint_quote(mint_url.parse()?, 1.into(), CurrencyUnit::Sat)
+        .await?;
+
+    println!("Please pay: {}", mint_quote.request);
+
+    loop {
+        let status = wallet_client
+            .get_mint_quote_status(mint_url.parse()?, &mint_quote.quote)
+            .await?;
+
+        if status.state == MintQuoteState::Paid {
+            break;
+        }
+        println!("{:?}", status.state);
+
+        sleep(Duration::from_secs(2)).await;
+    }
+
+    let premint_secrets = PreMintSecrets::random(keyset_id, amount, &SplitTarget::default())?;
+
+    let mint_response = wallet_client
+        .post_mint(
+            mint_url.parse()?,
+            &mint_quote.quote,
+            premint_secrets.clone(),
+        )
+        .await?;
+
+    let pre_swap_proofs = construct_proofs(
+        mint_response.signatures,
+        premint_secrets.rs(),
+        premint_secrets.secrets(),
+        &mint_keys.clone().keys,
+    )?;
+
+    Ok(pre_swap_proofs)
+}

+ 16 - 37
crates/cdk-integration-tests/tests/overflow.rs

@@ -3,56 +3,35 @@ use std::time::Duration;
 use anyhow::{bail, Result};
 use cdk::amount::SplitTarget;
 use cdk::dhke::construct_proofs;
-use cdk::nuts::{CurrencyUnit, MintQuoteState, PreMintSecrets, SwapRequest};
+use cdk::nuts::{PreMintSecrets, SwapRequest};
 use cdk::Amount;
 use cdk::HttpClient;
-use cdk_integration_tests::{create_backends_fake_wallet, start_mint, MINT_URL};
-use tokio::time::sleep;
+use cdk_integration_tests::{create_backends_fake_wallet, mint_proofs, start_mint, MINT_URL};
 
 /// This attempts to swap for more outputs then inputs.
 /// This will work if the mint does not check for outputs amounts overflowing
 async fn attempt_to_swap_by_overflowing() -> Result<()> {
     let wallet_client = HttpClient::new();
-
     let mint_keys = wallet_client.get_mint_keys(MINT_URL.parse()?).await?;
 
     let mint_keys = mint_keys.first().unwrap();
 
     let keyset_id = mint_keys.id;
 
-    let mint_quote = wallet_client
-        .post_mint_quote(MINT_URL.parse()?, 100.into(), CurrencyUnit::Sat)
-        .await?;
-
-    loop {
-        let status = wallet_client
-            .get_mint_quote_status(MINT_URL.parse()?, &mint_quote.quote)
-            .await?;
-
-        if status.state == MintQuoteState::Paid {
-            break;
-        }
-        println!("{:?}", status);
-
-        sleep(Duration::from_secs(2)).await;
-    }
-
-    let premint_secrets = PreMintSecrets::random(keyset_id, 1.into(), &SplitTarget::default())?;
-
-    let mint_response = wallet_client
-        .post_mint(
-            MINT_URL.parse()?,
-            &mint_quote.quote,
-            premint_secrets.clone(),
-        )
-        .await?;
-
-    let pre_swap_proofs = construct_proofs(
-        mint_response.signatures,
-        premint_secrets.rs(),
-        premint_secrets.secrets(),
-        &mint_keys.clone().keys,
-    )?;
+    let pre_swap_proofs = mint_proofs(MINT_URL, 1.into(), keyset_id, mint_keys).await?;
+
+    println!(
+        "Pre swap amount: {:?}",
+        Amount::try_sum(pre_swap_proofs.iter().map(|p| p.amount))?
+    );
+
+    println!(
+        "Pre swap amounts: {:?}",
+        pre_swap_proofs
+            .iter()
+            .map(|p| p.amount)
+            .collect::<Vec<Amount>>()
+    );
 
     // Construct messages that will overflow
 

+ 54 - 0
crates/cdk-integration-tests/tests/unbalanced.rs

@@ -0,0 +1,54 @@
+//! Test that if a wallet attempts to swap for less outputs then inputs correct error is returned
+
+use std::time::Duration;
+
+use anyhow::{bail, Result};
+use cdk::amount::SplitTarget;
+use cdk::nuts::{PreMintSecrets, SwapRequest};
+use cdk::HttpClient;
+use cdk_integration_tests::{create_backends_fake_wallet, mint_proofs, start_mint, MINT_URL};
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
+pub async fn test_unbalanced_swap() -> Result<()> {
+    tokio::spawn(async move {
+        let ln_backends = create_backends_fake_wallet();
+
+        start_mint(ln_backends).await.expect("Could not start mint")
+    });
+
+    // Wait for mint server to start
+    tokio::time::sleep(Duration::from_millis(500)).await;
+
+    let wallet_client = HttpClient::new();
+    let mint_keys = wallet_client.get_mint_keys(MINT_URL.parse()?).await?;
+
+    let mint_keys = mint_keys.first().unwrap();
+
+    let keyset_id = mint_keys.id;
+
+    let pre_swap_proofs = mint_proofs(MINT_URL, 10.into(), keyset_id, mint_keys).await?;
+
+    let pre_mint = PreMintSecrets::random(keyset_id, 9.into(), &SplitTarget::default())?;
+
+    let swap_request = SwapRequest::new(pre_swap_proofs.clone(), pre_mint.blinded_messages());
+
+    let _swap_response = match wallet_client
+        .post_swap(MINT_URL.parse()?, swap_request)
+        .await
+    {
+        Ok(res) => res,
+        // In the context of this test an error response here is good.
+        // It means the mint does not allow us to swap for more then we should by overflowing
+        Err(err) => match err {
+            cdk::wallet::error::Error::TransactionUnbalanced => {
+                return Ok(());
+            }
+            _ => {
+                println!("{}", err);
+                bail!("Wrong error code returned");
+            }
+        },
+    };
+
+    bail!("Transaction should not have succeeded")
+}

+ 2 - 2
crates/cdk/src/mint/error.rs

@@ -24,10 +24,10 @@ pub enum Error {
     #[error("Amount Overflow")]
     AmountOverflow,
     /// Not engough inputs provided
-    #[error("Inputs: `{0}`, Outputs: `{0}`, Expected Fee: `{0}`")]
+    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
     InsufficientInputs(u64, u64, u64),
     /// Transaction unbalanced
-    #[error("Inputs: `{0}`, Outputs: `{0}`, Expected Fee: `{0}`")]
+    #[error("Inputs: `{0}`, Outputs: `{1}`, Expected Fee: `{2}`")]
     TransactionUnbalanced(u64, u64, u64),
     /// Duplicate proofs provided
     #[error("Duplicate proofs")]

+ 4 - 0
crates/cdk/src/wallet/error.rs

@@ -91,6 +91,9 @@ pub enum Error {
     /// Max Fee Ecxeded
     #[error("Max fee exceeded")]
     MaxFeeExceeded,
+    /// Transaction unbalanced
+    #[error("Transaction is unbalanced")]
+    TransactionUnbalanced,
     /// CDK Error
     #[error(transparent)]
     Cashu(#[from] crate::error::Error),
@@ -138,6 +141,7 @@ impl From<ErrorResponse> for Error {
             ErrorCode::QuoteNotPaid => Self::QuoteNotePaid,
             ErrorCode::TokenAlreadySpent => Self::TokenAlreadySpent,
             ErrorCode::KeysetNotFound => Self::KeysetNotFound,
+            ErrorCode::TransactionUnbalanced => Self::TransactionUnbalanced,
             _ => Self::UnknownErrorResponse(err.to_string()),
         }
     }