use anyhow::{Context, Result};
use miden_client::account::AccountType;
use miden_client::asset::FungibleAsset;
use miden_client::auth::RPO_FALCON_SCHEME_ID;
use miden_client::note::{Note, NoteType, PswapNote};
use miden_client::testing::common::*;
use miden_client::transaction::{PswapTransactionData, TransactionRequestBuilder};
use tracing::info;
use crate::tests::config::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, alice_authenticator) = client_config.clone().into_client().await?;
wait_for_node(&mut alice_client).await;
let (mut bob_client, bob_authenticator) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
alice_client.sync_state().await?;
bob_client.sync_state().await?;
let (alice_account, ..) = insert_new_wallet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (bob_account, ..) = insert_new_wallet(
&mut bob_client,
AccountType::Private,
&bob_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (btc_faucet_account, _) = insert_new_fungible_faucet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (eth_faucet_account, _) = insert_new_fungible_faucet(
&mut bob_client,
AccountType::Private,
&bob_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let tx_id = mint_and_consume(
&mut alice_client,
alice_account.id(),
btc_faucet_account.id(),
NoteType::Public,
)
.await;
wait_for_tx(&mut alice_client, tx_id).await?;
let tx_id = mint_and_consume(
&mut bob_client,
bob_account.id(),
eth_faucet_account.id(),
NoteType::Public,
)
.await;
wait_for_tx(&mut bob_client, 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();
execute_tx_and_sync(&mut alice_client, 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(),
REQUESTED_AMOUNT,
0,
)?;
assert!(
consume_request.expected_future_notes().next().is_none(),
"the consumer should not track any future notes"
);
execute_tx_and_sync(&mut bob_client, 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])?;
execute_tx_and_sync(&mut alice_client, 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?,
MINT_AMOUNT - OFFERED_AMOUNT
);
assert_eq!(
alice_account_reader.get_balance(eth_faucet_account.id()).await?,
REQUESTED_AMOUNT
);
let bob_account_reader = bob_client.account_reader(bob_account.id());
assert_eq!(bob_account_reader.get_balance(btc_faucet_account.id()).await?, OFFERED_AMOUNT);
assert_eq!(
bob_account_reader.get_balance(eth_faucet_account.id()).await?,
MINT_AMOUNT - REQUESTED_AMOUNT
);
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, alice_authenticator) = client_config.clone().into_client().await?;
wait_for_node(&mut alice_client).await;
let (mut bob_client, bob_authenticator) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
alice_client.sync_state().await?;
bob_client.sync_state().await?;
let (alice_account, ..) = insert_new_wallet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (bob_account, ..) = insert_new_wallet(
&mut bob_client,
AccountType::Private,
&bob_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (btc_faucet_account, _) = insert_new_fungible_faucet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (eth_faucet_account, _) = insert_new_fungible_faucet(
&mut bob_client,
AccountType::Private,
&bob_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let tx_id = mint_and_consume(
&mut alice_client,
alice_account.id(),
btc_faucet_account.id(),
NoteType::Public,
)
.await;
wait_for_tx(&mut alice_client, tx_id).await?;
let tx_id = mint_and_consume(
&mut bob_client,
bob_account.id(),
eth_faucet_account.id(),
NoteType::Public,
)
.await;
wait_for_tx(&mut bob_client, 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();
execute_tx_and_sync(&mut alice_client, 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(),
ACCOUNT_FILL,
0,
)?;
assert!(
consume_request.expected_future_notes().next().is_none(),
"the consumer should not track any future notes"
);
execute_tx_and_sync(&mut bob_client, 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?,
EXPECTED_PAYOUT,
"Bob should have received a proportional share, not the full offered amount"
);
assert_eq!(
bob_account_reader.get_balance(eth_faucet_account.id()).await?,
MINT_AMOUNT - ACCOUNT_FILL,
"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, alice_authenticator) = client_config.into_client().await?;
wait_for_node(&mut alice_client).await;
alice_client.sync_state().await?;
let (alice_account, ..) = insert_new_wallet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (btc_faucet_account, _) = insert_new_fungible_faucet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let (eth_faucet_account, _) = insert_new_fungible_faucet(
&mut alice_client,
AccountType::Private,
&alice_authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let tx_id = mint_and_consume(
&mut alice_client,
alice_account.id(),
btc_faucet_account.id(),
NoteType::Private,
)
.await;
wait_for_tx(&mut alice_client, 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();
execute_tx_and_sync(&mut alice_client, 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?,
MINT_AMOUNT - OFFERED_AMOUNT,
"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())?;
execute_tx_and_sync(&mut alice_client, 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?,
MINT_AMOUNT,
"cancelling should restore the offered asset to the creator"
);
Ok(())
}