use anyhow::{Context, Result};
use miden_client::account::AccountType;
use miden_client::asset::{AssetAmount, FungibleAsset};
use miden_client::note::{Note, NoteType, PswapNote};
use miden_client::testing::common::*;
use miden_client::transaction::{PswapTransactionData, TransactionRequestBuilder};
use tracing::info;
use crate::ClientConfig;
pub async fn test_pswap_full_fill_onchain(client_config: ClientConfig) -> Result<()> {
const OFFERED_AMOUNT: u64 = 100;
const REQUESTED_AMOUNT: u64 = 50;
let mut alice_client = client_config.clone().into_client().await?;
alice_client.wait_for_node().await;
let mut bob_client = client_config.clone().into_client().await?;
alice_client.sync_state().await?;
bob_client.sync_state().await?;
let alice_account = alice_client.insert_wallet(AccountType::Private).await?;
let bob_account = bob_client.insert_wallet(AccountType::Private).await?;
let btc_faucet_account = alice_client.insert_faucet(AccountType::Private).await?;
let eth_faucet_account = bob_client.insert_faucet(AccountType::Private).await?;
let tx_id = alice_client
.mint_and_consume(alice_account.id(), btc_faucet_account.id(), NoteType::Public)
.await?;
alice_client.wait_for_tx(tx_id).await?;
let tx_id = bob_client
.mint_and_consume(bob_account.id(), eth_faucet_account.id(), NoteType::Public)
.await?;
bob_client.wait_for_tx(tx_id).await?;
let offered_asset = FungibleAsset::new(btc_faucet_account.id(), OFFERED_AMOUNT)?;
let requested_asset = FungibleAsset::new(eth_faucet_account.id(), REQUESTED_AMOUNT)?;
info!("Executing PSWAP create transaction");
let tx_request = TransactionRequestBuilder::new().build_pswap_create(
&PswapTransactionData::new(alice_account.id(), offered_asset, requested_asset),
NoteType::Public,
NoteType::Public,
None,
alice_client.rng(),
)?;
let pswap_note = tx_request.expected_output_own_notes()[0].clone();
alice_client.execute_tx_and_sync(alice_account.id(), tx_request).await?;
let pswap_tag = PswapNote::create_tag(NoteType::Public, &offered_asset, &requested_asset);
info!(tag = %pswap_tag, "Adding PSWAP discovery tag to client 2");
bob_client.add_note_tag(pswap_tag).await?;
bob_client.sync_state().await?;
info!(note_id = %pswap_note.id(), account_id = %bob_account.id(), "Bob fully fills the PSWAP");
let consume_request = TransactionRequestBuilder::new().build_pswap_consume(
&pswap_note,
bob_account.id(),
AssetAmount::new(REQUESTED_AMOUNT).unwrap(),
AssetAmount::ZERO,
)?;
assert!(
consume_request.expected_future_notes().next().is_none(),
"the consumer should not track any future notes"
);
bob_client.execute_tx_and_sync(bob_account.id(), consume_request).await?;
alice_client.sync_state().await?;
let (payback_record, _) = alice_client
.get_consumable_notes(Some(alice_account.id()))
.await?
.into_iter()
.next()
.context("Alice should have discovered the payback note after the full fill")?;
let payback_note: Note = payback_record.try_into()?;
let consume_payback =
TransactionRequestBuilder::new().build_consume_notes(vec![payback_note])?;
alice_client.execute_tx_and_sync(alice_account.id(), consume_payback).await?;
let alice_account_reader = alice_client.account_reader(alice_account.id());
assert_eq!(
alice_account_reader.get_balance(btc_faucet_account.id()).await?,
AssetAmount::new(MINT_AMOUNT - OFFERED_AMOUNT).unwrap()
);
assert_eq!(
alice_account_reader.get_balance(eth_faucet_account.id()).await?,
AssetAmount::new(REQUESTED_AMOUNT).unwrap()
);
let bob_account_reader = bob_client.account_reader(bob_account.id());
assert_eq!(
bob_account_reader.get_balance(btc_faucet_account.id()).await?,
AssetAmount::new(OFFERED_AMOUNT).unwrap()
);
assert_eq!(
bob_account_reader.get_balance(eth_faucet_account.id()).await?,
AssetAmount::new(MINT_AMOUNT - REQUESTED_AMOUNT).unwrap()
);
Ok(())
}
pub async fn test_pswap_partial_fill_onchain(client_config: ClientConfig) -> Result<()> {
const OFFERED_AMOUNT: u64 = 100;
const REQUESTED_AMOUNT: u64 = 50;
const ACCOUNT_FILL: u64 = 25;
const EXPECTED_PAYOUT: u64 = 50;
const REMAINING_OFFERED: u64 = OFFERED_AMOUNT - EXPECTED_PAYOUT;
const REMAINING_REQUESTED: u64 = REQUESTED_AMOUNT - ACCOUNT_FILL;
let mut alice_client = client_config.clone().into_client().await?;
alice_client.wait_for_node().await;
let mut bob_client = client_config.clone().into_client().await?;
alice_client.sync_state().await?;
bob_client.sync_state().await?;
let alice_account = alice_client.insert_wallet(AccountType::Private).await?;
let bob_account = bob_client.insert_wallet(AccountType::Private).await?;
let btc_faucet_account = alice_client.insert_faucet(AccountType::Private).await?;
let eth_faucet_account = bob_client.insert_faucet(AccountType::Private).await?;
let tx_id = alice_client
.mint_and_consume(alice_account.id(), btc_faucet_account.id(), NoteType::Public)
.await?;
alice_client.wait_for_tx(tx_id).await?;
let tx_id = bob_client
.mint_and_consume(bob_account.id(), eth_faucet_account.id(), NoteType::Public)
.await?;
bob_client.wait_for_tx(tx_id).await?;
let offered_asset = FungibleAsset::new(btc_faucet_account.id(), OFFERED_AMOUNT)?;
let requested_asset = FungibleAsset::new(eth_faucet_account.id(), REQUESTED_AMOUNT)?;
let tx_request = TransactionRequestBuilder::new().build_pswap_create(
&PswapTransactionData::new(alice_account.id(), offered_asset, requested_asset),
NoteType::Public,
NoteType::Public,
None,
alice_client.rng(),
)?;
let pswap_note = tx_request.expected_output_own_notes()[0].clone();
alice_client.execute_tx_and_sync(alice_account.id(), tx_request).await?;
let pswap_tag = PswapNote::create_tag(NoteType::Public, &offered_asset, &requested_asset);
bob_client.add_note_tag(pswap_tag).await?;
bob_client.sync_state().await?;
info!(account_fill = ACCOUNT_FILL, "Bob partially fills the PSWAP");
let consume_request = TransactionRequestBuilder::new().build_pswap_consume(
&pswap_note,
bob_account.id(),
AssetAmount::new(ACCOUNT_FILL).unwrap(),
AssetAmount::ZERO,
)?;
assert!(
consume_request.expected_future_notes().next().is_none(),
"the consumer should not track any future notes"
);
bob_client.execute_tx_and_sync(bob_account.id(), consume_request).await?;
let bob_account_reader = bob_client.account_reader(bob_account.id());
assert_eq!(
bob_account_reader.get_balance(btc_faucet_account.id()).await?,
AssetAmount::new(EXPECTED_PAYOUT).unwrap(),
"Bob should have received a proportional share, not the full offered amount"
);
assert_eq!(
bob_account_reader.get_balance(eth_faucet_account.id()).await?,
AssetAmount::new(MINT_AMOUNT - ACCOUNT_FILL).unwrap(),
"Bob should have spent only the partial fill amount"
);
alice_client.sync_state().await?;
let order_id = PswapNote::try_from(&pswap_note)?.order_id();
let lineage = alice_client
.pswap_lineage(order_id)
.await?
.context("Alice should track a lineage for her PSWAP order")?;
assert_eq!(lineage.current_depth, 1, "a single partial fill advances the tip to depth 1");
assert_eq!(lineage.remaining_offered.as_u64(), REMAINING_OFFERED);
assert_eq!(lineage.remaining_requested.as_u64(), REMAINING_REQUESTED);
Ok(())
}
pub async fn test_pswap_cancel_onchain(client_config: ClientConfig) -> Result<()> {
const OFFERED_AMOUNT: u64 = 100;
const REQUESTED_AMOUNT: u64 = 50;
let mut alice_client = client_config.into_client().await?;
alice_client.wait_for_node().await;
alice_client.sync_state().await?;
let alice_account = alice_client.insert_wallet(AccountType::Private).await?;
let btc_faucet_account = alice_client.insert_faucet(AccountType::Private).await?;
let eth_faucet_account = alice_client.insert_faucet(AccountType::Private).await?;
let tx_id = alice_client
.mint_and_consume(alice_account.id(), btc_faucet_account.id(), NoteType::Private)
.await?;
alice_client.wait_for_tx(tx_id).await?;
let offered_asset = FungibleAsset::new(btc_faucet_account.id(), OFFERED_AMOUNT)?;
let requested_asset = FungibleAsset::new(eth_faucet_account.id(), REQUESTED_AMOUNT)?;
let create_request = TransactionRequestBuilder::new().build_pswap_create(
&PswapTransactionData::new(alice_account.id(), offered_asset, requested_asset),
NoteType::Private,
NoteType::Private,
None,
alice_client.rng(),
)?;
let pswap_note = create_request.expected_output_own_notes()[0].clone();
alice_client.execute_tx_and_sync(alice_account.id(), create_request).await?;
let alice_account_reader = alice_client.account_reader(alice_account.id());
assert_eq!(
alice_account_reader.get_balance(btc_faucet_account.id()).await?,
AssetAmount::new(MINT_AMOUNT - OFFERED_AMOUNT).unwrap(),
"creating the PSWAP should debit the offered asset"
);
info!(note_id = %pswap_note.id(), "Alice cancels the PSWAP");
let cancel_request =
TransactionRequestBuilder::new().build_pswap_cancel(pswap_note, alice_account.id())?;
alice_client.execute_tx_and_sync(alice_account.id(), cancel_request).await?;
let alice_account_reader = alice_client.account_reader(alice_account.id());
assert_eq!(
alice_account_reader.get_balance(btc_faucet_account.id()).await?,
AssetAmount::new(MINT_AMOUNT).unwrap(),
"cancelling should restore the offered asset to the creator"
);
Ok(())
}