diff --git a/crates/cdk-integration-tests/Cargo.toml b/crates/cdk-integration-tests/Cargo.toml index 8753408c1..a95d59622 100644 --- a/crates/cdk-integration-tests/Cargo.toml +++ b/crates/cdk-integration-tests/Cargo.toml @@ -57,6 +57,7 @@ getrandom = { version = "0.2", features = ["js"] } instant = { version = "0.1", features = ["wasm-bindgen", "inaccurate"] } [dev-dependencies] +async-trait = "0.1" rand = "0.8.5" bip39 = { version = "2.0", features = ["rand"] } anyhow = "1" diff --git a/crates/cdk-integration-tests/tests/integration_tests_pure.rs b/crates/cdk-integration-tests/tests/integration_tests_pure.rs new file mode 100644 index 000000000..fca993cf4 --- /dev/null +++ b/crates/cdk-integration-tests/tests/integration_tests_pure.rs @@ -0,0 +1,241 @@ +#[cfg(test)] +mod integration_tests_pure { + use std::assert_eq; + use std::collections::HashMap; + use std::fmt::{Debug, Formatter}; + use std::sync::Arc; + + use async_trait::async_trait; + use cdk::amount::SplitTarget; + use cdk::cdk_database::mint_memory::MintMemoryDatabase; + use cdk::cdk_database::WalletMemoryDatabase; + use cdk::nuts::{ + CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse, + MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request, + MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response, + MintQuoteState, Nuts, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse, + }; + use cdk::types::QuoteTTL; + use cdk::util::unix_time; + use cdk::wallet::client::MintConnector; + use cdk::{Amount, Error, Mint, Wallet}; + use cdk_integration_tests::create_backends_fake_wallet; + use rand::random; + use tokio::sync::Notify; + + struct DirectMintConnection { + mint: Arc, + } + + impl Debug for DirectMintConnection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DirectMintConnection {{ mint_info: {:?} }}", + self.mint.mint_info + ) + } + } + + #[async_trait] + impl MintConnector for DirectMintConnection { + async fn get_mint_keys(&self) -> Result, Error> { + self.mint.pubkeys().await.map(|pks| pks.keysets) + } + + async fn get_mint_keyset(&self, keyset_id: Id) -> Result { + self.mint + .keyset(&keyset_id) + .await + .and_then(|res| res.ok_or(Error::UnknownKeySet)) + } + + async fn get_mint_keysets(&self) -> Result { + self.mint.keysets().await + } + + async fn post_mint_quote( + &self, + request: MintQuoteBolt11Request, + ) -> Result { + self.mint.get_mint_bolt11_quote(request).await + } + + async fn get_mint_quote_status( + &self, + quote_id: &str, + ) -> Result { + self.mint.check_mint_quote(quote_id).await + } + + async fn post_mint(&self, request: MintBolt11Request) -> Result { + self.mint.process_mint_request(request).await + } + + async fn post_melt_quote( + &self, + request: MeltQuoteBolt11Request, + ) -> Result { + self.mint.get_melt_bolt11_quote(&request).await + } + + async fn get_melt_quote_status( + &self, + quote_id: &str, + ) -> Result { + self.mint.check_melt_quote(quote_id).await + } + + async fn post_melt( + &self, + request: MeltBolt11Request, + ) -> Result { + self.mint.melt_bolt11(&request).await + } + + async fn post_swap(&self, swap_request: SwapRequest) -> Result { + self.mint.process_swap_request(swap_request).await + } + + async fn get_mint_info(&self) -> Result { + Ok(self.mint.mint_info().clone().time(unix_time())) + } + + async fn post_check_state( + &self, + request: CheckStateRequest, + ) -> Result { + self.mint.check_state(&request).await + } + + async fn post_restore(&self, request: RestoreRequest) -> Result { + self.mint.restore(request).await + } + } + + fn get_mint_connector(mint: Arc) -> DirectMintConnection { + DirectMintConnection { mint } + } + + async fn create_and_start_test_mint() -> anyhow::Result> { + let fee: u64 = 0; + let mut supported_units = HashMap::new(); + supported_units.insert(CurrencyUnit::Sat, (fee, 32)); + + let nuts = Nuts::new() + .nut07(true) + .nut08(true) + .nut09(true) + .nut10(true) + .nut11(true) + .nut12(true) + .nut14(true); + + let mint_info = MintInfo::new().nuts(nuts); + + let quote_ttl = QuoteTTL::new(10000, 10000); + + let mint_url = "http://aaa"; + + let seed = random::<[u8; 32]>(); + let mint: Mint = Mint::new( + mint_url, + &seed, + mint_info, + quote_ttl, + Arc::new(MintMemoryDatabase::default()), + create_backends_fake_wallet(), + supported_units, + HashMap::new(), + ) + .await?; + + let mint_arc = Arc::new(mint); + + let mint_arc_clone = Arc::clone(&mint_arc); + let shutdown = Arc::new(Notify::new()); + tokio::spawn({ + let shutdown = Arc::clone(&shutdown); + async move { mint_arc_clone.wait_for_paid_invoices(shutdown).await } + }); + + Ok(mint_arc) + } + + fn create_test_wallet_for_mint(mint: Arc) -> anyhow::Result> { + let connector = get_mint_connector(mint); + + let seed = random::<[u8; 32]>(); + let mint_url = connector.mint.mint_url.to_string(); + let unit = CurrencyUnit::Sat; + + let localstore = WalletMemoryDatabase::default(); + let mut wallet = Wallet::new(&mint_url, unit, Arc::new(localstore), &seed, None)?; + + wallet.set_client(Arc::from(connector)); + + Ok(Arc::new(wallet)) + } + + /// Creates a mint quote for the given amount and checks its state in a loop. Returns when + /// amount is minted. + async fn receive(wallet: Arc, amount: u64) -> anyhow::Result { + let desired_amount = Amount::from(amount); + let quote = wallet.mint_quote(desired_amount, None).await?; + + loop { + let status = wallet.mint_quote_state("e.id).await?; + if status.state == MintQuoteState::Paid { + break; + } + } + + wallet + .mint("e.id, SplitTarget::default(), None) + .await + .map_err(Into::into) + } + + mod nut03 { + use cdk::nuts::nut00::ProofsMethods; + use cdk::wallet::SendKind; + + use crate::integration_tests_pure::*; + + #[tokio::test] + async fn test_swap_to_send() -> anyhow::Result<()> { + let mint_bob = create_and_start_test_mint().await?; + let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())?; + + // Alice gets 64 sats + receive(wallet_alice.clone(), 64).await?; + let balance_alice = wallet_alice.total_balance().await?; + assert_eq!(Amount::from(64), balance_alice); + + // Alice wants to send 40 sats, which internally swaps + let token = wallet_alice + .send( + Amount::from(40), + None, + None, + &SplitTarget::None, + &SendKind::OnlineExact, + false, + ) + .await?; + assert_eq!(Amount::from(40), token.proofs().total_amount()?); + assert_eq!(Amount::from(24), wallet_alice.total_balance().await?); + + // Alice sends cashu, Carol receives + let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())?; + let received_amount = wallet_carol + .receive_proofs(token.proofs(), SplitTarget::None, &[], &[]) + .await?; + + assert_eq!(Amount::from(40), received_amount); + assert_eq!(Amount::from(40), wallet_carol.total_balance().await?); + + Ok(()) + } + } +}