diff --git a/crates/cdk-integration-tests/tests/overflow.rs b/crates/cdk-integration-tests/tests/overflow.rs new file mode 100644 index 00000000..a251e4fd --- /dev/null +++ b/crates/cdk-integration-tests/tests/overflow.rs @@ -0,0 +1,135 @@ +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::Amount; +use cdk::HttpClient; +use cdk_integration_tests::{create_backends_fake_wallet, start_mint, MINT_URL}; +use tokio::time::sleep; + +/// This attemps 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.clone(), 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, + )?; + + // Construct messages that will overflow + + let amount = 2_u64.pow(63); + + let pre_mint_amount = + PreMintSecrets::random(keyset_id.clone(), amount.into(), &SplitTarget::default())?; + let pre_mint_amount_two = + PreMintSecrets::random(keyset_id.clone(), amount.into(), &SplitTarget::default())?; + + let mut pre_mint = + PreMintSecrets::random(keyset_id.clone(), 1.into(), &SplitTarget::default())?; + + pre_mint.combine(pre_mint_amount); + pre_mint.combine(pre_mint_amount_two); + + 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) => return Ok(()), + }; + + let post_swap_proofs = construct_proofs( + swap_response.signatures, + pre_mint.rs(), + pre_mint.secrets(), + &mint_keys.clone().keys, + )?; + + println!( + "Pre swap amount: {:?}", + pre_swap_proofs.iter().map(|p| p.amount).sum::() + ); + println!( + "Post swap amount: {:?}", + post_swap_proofs.iter().map(|p| p.amount).sum::() + ); + + println!( + "Pre swap amounts: {:?}", + pre_swap_proofs + .iter() + .map(|p| p.amount) + .collect::>() + ); + println!( + "Post swap amounts: {:?}", + post_swap_proofs + .iter() + .map(|p| p.amount) + .collect::>() + ); + + bail!("Should not have been able to swap") +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +pub async fn test_overflow() -> 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 result = attempt_to_swap_by_overflowing().await; + + assert!(result.is_ok()); + + Ok(()) +}