integration_tests_pure.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #[cfg(test)]
  2. mod integration_tests_pure {
  3. use std::assert_eq;
  4. use std::collections::HashMap;
  5. use std::fmt::{Debug, Formatter};
  6. use std::str::FromStr;
  7. use std::sync::Arc;
  8. use async_trait::async_trait;
  9. use cdk::amount::SplitTarget;
  10. use cdk::cdk_database::mint_memory::MintMemoryDatabase;
  11. use cdk::cdk_database::WalletMemoryDatabase;
  12. use cdk::nuts::nut00::ProofsMethods;
  13. use cdk::nuts::{
  14. CheckStateRequest, CheckStateResponse, CurrencyUnit, Id, KeySet, KeysetResponse,
  15. MeltBolt11Request, MeltQuoteBolt11Request, MeltQuoteBolt11Response, MintBolt11Request,
  16. MintBolt11Response, MintInfo, MintQuoteBolt11Request, MintQuoteBolt11Response,
  17. MintQuoteState, Nuts, RestoreRequest, RestoreResponse, SwapRequest, SwapResponse,
  18. };
  19. use cdk::types::QuoteTTL;
  20. use cdk::util::unix_time;
  21. use cdk::wallet::client::MintConnector;
  22. use cdk::{Amount, Error, Mint, Wallet};
  23. use cdk_integration_tests::create_backends_fake_wallet;
  24. use rand::random;
  25. use tokio::sync::Notify;
  26. use uuid::Uuid;
  27. struct DirectMintConnection {
  28. mint: Arc<Mint>,
  29. }
  30. impl Debug for DirectMintConnection {
  31. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  32. write!(
  33. f,
  34. "DirectMintConnection {{ mint_info: {:?} }}",
  35. self.mint.config.mint_info()
  36. )
  37. }
  38. }
  39. /// Implements the generic [MintConnector] (i.e. use the interface that expects to communicate
  40. /// to a generic mint, where we don't know that quote ID's are [Uuid]s) for [DirectMintConnection],
  41. /// where we know we're dealing with a mint that uses [Uuid]s for quotes.
  42. /// Convert the requests and responses between the [String] and [Uuid] variants as necessary.
  43. #[async_trait]
  44. impl MintConnector for DirectMintConnection {
  45. async fn get_mint_keys(&self) -> Result<Vec<KeySet>, Error> {
  46. self.mint.pubkeys().await.map(|pks| pks.keysets)
  47. }
  48. async fn get_mint_keyset(&self, keyset_id: Id) -> Result<KeySet, Error> {
  49. self.mint
  50. .keyset(&keyset_id)
  51. .await
  52. .and_then(|res| res.ok_or(Error::UnknownKeySet))
  53. }
  54. async fn get_mint_keysets(&self) -> Result<KeysetResponse, Error> {
  55. self.mint.keysets().await
  56. }
  57. async fn post_mint_quote(
  58. &self,
  59. request: MintQuoteBolt11Request,
  60. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  61. self.mint
  62. .get_mint_bolt11_quote(request)
  63. .await
  64. .map(Into::into)
  65. }
  66. async fn get_mint_quote_status(
  67. &self,
  68. quote_id: &str,
  69. ) -> Result<MintQuoteBolt11Response<String>, Error> {
  70. let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
  71. self.mint
  72. .check_mint_quote(&quote_id_uuid)
  73. .await
  74. .map(Into::into)
  75. }
  76. async fn post_mint(
  77. &self,
  78. request: MintBolt11Request<String>,
  79. ) -> Result<MintBolt11Response, Error> {
  80. let request_uuid = request.try_into().unwrap();
  81. self.mint.process_mint_request(request_uuid).await
  82. }
  83. async fn post_melt_quote(
  84. &self,
  85. request: MeltQuoteBolt11Request,
  86. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  87. self.mint
  88. .get_melt_bolt11_quote(&request)
  89. .await
  90. .map(Into::into)
  91. }
  92. async fn get_melt_quote_status(
  93. &self,
  94. quote_id: &str,
  95. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  96. let quote_id_uuid = Uuid::from_str(quote_id).unwrap();
  97. self.mint
  98. .check_melt_quote(&quote_id_uuid)
  99. .await
  100. .map(Into::into)
  101. }
  102. async fn post_melt(
  103. &self,
  104. request: MeltBolt11Request<String>,
  105. ) -> Result<MeltQuoteBolt11Response<String>, Error> {
  106. let request_uuid = request.try_into().unwrap();
  107. self.mint.melt_bolt11(&request_uuid).await.map(Into::into)
  108. }
  109. async fn post_swap(&self, swap_request: SwapRequest) -> Result<SwapResponse, Error> {
  110. self.mint.process_swap_request(swap_request).await
  111. }
  112. async fn get_mint_info(&self) -> Result<MintInfo, Error> {
  113. Ok(self.mint.mint_info().clone().time(unix_time()))
  114. }
  115. async fn post_check_state(
  116. &self,
  117. request: CheckStateRequest,
  118. ) -> Result<CheckStateResponse, Error> {
  119. self.mint.check_state(&request).await
  120. }
  121. async fn post_restore(&self, request: RestoreRequest) -> Result<RestoreResponse, Error> {
  122. self.mint.restore(request).await
  123. }
  124. }
  125. fn get_mint_connector(mint: Arc<Mint>) -> DirectMintConnection {
  126. DirectMintConnection { mint }
  127. }
  128. async fn create_and_start_test_mint() -> anyhow::Result<Arc<Mint>> {
  129. let fee: u64 = 0;
  130. let mut supported_units = HashMap::new();
  131. supported_units.insert(CurrencyUnit::Sat, (fee, 32));
  132. let nuts = Nuts::new()
  133. .nut07(true)
  134. .nut08(true)
  135. .nut09(true)
  136. .nut10(true)
  137. .nut11(true)
  138. .nut12(true)
  139. .nut14(true);
  140. let mint_info = MintInfo::new().nuts(nuts);
  141. let quote_ttl = QuoteTTL::new(10000, 10000);
  142. let mint_url = "http://aaa";
  143. let seed = random::<[u8; 32]>();
  144. let mint: Mint = Mint::new(
  145. mint_url,
  146. &seed,
  147. mint_info,
  148. quote_ttl,
  149. Arc::new(MintMemoryDatabase::default()),
  150. create_backends_fake_wallet(),
  151. supported_units,
  152. HashMap::new(),
  153. )
  154. .await?;
  155. let mint_arc = Arc::new(mint);
  156. let mint_arc_clone = Arc::clone(&mint_arc);
  157. let shutdown = Arc::new(Notify::new());
  158. tokio::spawn({
  159. let shutdown = Arc::clone(&shutdown);
  160. async move { mint_arc_clone.wait_for_paid_invoices(shutdown).await }
  161. });
  162. Ok(mint_arc)
  163. }
  164. fn create_test_wallet_for_mint(mint: Arc<Mint>) -> anyhow::Result<Arc<Wallet>> {
  165. let connector = get_mint_connector(mint);
  166. let seed = random::<[u8; 32]>();
  167. let mint_url = connector.mint.config.mint_url().to_string();
  168. let unit = CurrencyUnit::Sat;
  169. let localstore = WalletMemoryDatabase::default();
  170. let mut wallet = Wallet::new(&mint_url, unit, Arc::new(localstore), &seed, None)?;
  171. wallet.set_client(connector);
  172. Ok(Arc::new(wallet))
  173. }
  174. /// Creates a mint quote for the given amount and checks its state in a loop. Returns when
  175. /// amount is minted.
  176. async fn receive(wallet: Arc<Wallet>, amount: u64) -> anyhow::Result<Amount> {
  177. let desired_amount = Amount::from(amount);
  178. let quote = wallet.mint_quote(desired_amount, None).await?;
  179. loop {
  180. let status = wallet.mint_quote_state(&quote.id).await?;
  181. if status.state == MintQuoteState::Paid {
  182. break;
  183. }
  184. }
  185. Ok(wallet
  186. .mint(&quote.id, SplitTarget::default(), None)
  187. .await?
  188. .total_amount()?)
  189. }
  190. mod nut03 {
  191. use cdk::nuts::nut00::ProofsMethods;
  192. use cdk::wallet::SendKind;
  193. use crate::integration_tests_pure::*;
  194. #[tokio::test]
  195. async fn test_swap_to_send() -> anyhow::Result<()> {
  196. let mint_bob = create_and_start_test_mint().await?;
  197. let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())?;
  198. // Alice gets 64 sats
  199. receive(wallet_alice.clone(), 64).await?;
  200. let balance_alice = wallet_alice.total_balance().await?;
  201. assert_eq!(Amount::from(64), balance_alice);
  202. // Alice wants to send 40 sats, which internally swaps
  203. let token = wallet_alice
  204. .send(
  205. Amount::from(40),
  206. None,
  207. None,
  208. &SplitTarget::None,
  209. &SendKind::OnlineExact,
  210. false,
  211. )
  212. .await?;
  213. assert_eq!(Amount::from(40), token.proofs().total_amount()?);
  214. assert_eq!(Amount::from(24), wallet_alice.total_balance().await?);
  215. // Alice sends cashu, Carol receives
  216. let wallet_carol = create_test_wallet_for_mint(mint_bob.clone())?;
  217. let received_amount = wallet_carol
  218. .receive_proofs(token.proofs(), SplitTarget::None, &[], &[])
  219. .await?;
  220. assert_eq!(Amount::from(40), received_amount);
  221. assert_eq!(Amount::from(40), wallet_carol.total_balance().await?);
  222. Ok(())
  223. }
  224. }
  225. }