feat: unbalanced swap test

This commit is contained in:
thesimplekid
2024-09-06 09:11:45 +01:00
parent bf5b953738
commit 369184cf6a
5 changed files with 129 additions and 38 deletions

View File

@@ -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)
}

View File

@@ -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?;
let pre_swap_proofs = mint_proofs(MINT_URL, 1.into(), keyset_id, mint_keys).await?;
loop {
let status = wallet_client
.get_mint_quote_status(MINT_URL.parse()?, &mint_quote.quote)
.await?;
println!(
"Pre swap amount: {:?}",
Amount::try_sum(pre_swap_proofs.iter().map(|p| p.amount))?
);
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,
)?;
println!(
"Pre swap amounts: {:?}",
pre_swap_proofs
.iter()
.map(|p| p.amount)
.collect::<Vec<Amount>>()
);
// Construct messages that will overflow

View File

@@ -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")
}

View File

@@ -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")]

View File

@@ -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()),
}
}