use anyhow::{Context, Result};
use miden_client::account::component::BasicWallet;
use miden_client::account::{
Account,
AccountBuilder,
AccountBuilderSchemaCommitmentExt,
AccountId,
AccountType,
};
use miden_client::assembly::CodeBuilder;
use miden_client::asset::{Asset, AssetAmount, FungibleAsset};
use miden_client::auth::{NoAuth, TransactionAuthenticator};
use miden_client::block::BlockNumber;
use miden_client::crypto::FeltRng;
use miden_client::note::{
Note,
NoteAssets,
NoteDetails,
NoteFile,
NoteRecipient,
NoteScript,
NoteStorage,
NoteTag,
NoteType,
P2idNoteStorage,
PartialNoteMetadata,
};
use miden_client::store::{InputNoteState, TransactionFilter};
use miden_client::transaction::TransactionRequestBuilder;
use miden_client::{Client, ClientRng, Word};
use miden_protocol::MAX_TX_EXECUTION_CYCLES;
use miden_protocol::transaction::TransactionFee;
use rand::Rng;
use tracing::info;
use crate::ClientConfig;
use crate::fee_funding::fee_faucet_id;
pub async fn test_pass_through(client_config: ClientConfig) -> Result<()> {
const ASSET_AMOUNT: u64 = 1;
let mut client = client_config.clone().into_client().await?;
let mut client_2 = client_config.clone().into_client().await?;
client.wait_for_node().await;
client.sync_state().await?;
client_2.sync_state().await?;
let sender = client.insert_wallet(AccountType::Private).await?;
let target = client_2.insert_wallet(AccountType::Private).await?;
let pass_through_account = create_pass_through_account(&mut client).await?;
let btc_faucet_account = client.insert_faucet(AccountType::Private).await?;
info!(account_id = %sender.id(), faucet_id = %btc_faucet_account.id(), "Minting 1000 BTC for sender");
let tx_id = client
.mint_and_consume(sender.id(), btc_faucet_account.id(), NoteType::Public)
.await?;
client.wait_for_tx(tx_id).await?;
info!(sender_id = %sender.id(), target_id = %target.id(), "Creating pass-through note");
let asset = FungibleAsset::new(btc_faucet_account.id(), ASSET_AMOUNT)?;
let fee_asset = pass_through_fee_asset(
&mut client,
sender.id(),
target.id(),
asset.into(),
pass_through_account.id(),
)
.await?;
let (pass_through_note_1, pass_through_note_details_1) = create_pass_through_note(
sender.id(),
target.id(),
asset.into(),
fee_asset.map(Into::into),
client.rng(),
)?;
let (pass_through_note_2, pass_through_note_details_2) = create_pass_through_note(
sender.id(),
target.id(),
asset.into(),
fee_asset.map(Into::into),
client.rng(),
)?;
let tx_request = TransactionRequestBuilder::new()
.own_output_notes(vec![pass_through_note_1.clone(), pass_through_note_2.clone()])
.build()?;
client.execute_tx_and_sync(sender.id(), tx_request).await?;
info!(note_id = %pass_through_note_1.id(), pass_through_account = %pass_through_account.id(), "Consuming pass-through note");
client
.import_notes(&[
NoteFile::NoteId(pass_through_note_1.id()),
NoteFile::NoteId(pass_through_note_2.id()),
])
.await?;
client.sync_state().await?;
let input_note_record = client.get_input_note(pass_through_note_1.id()).await?.unwrap();
assert!(matches!(input_note_record.state(), InputNoteState::Committed { .. }));
let input_note_record = client.get_input_note(pass_through_note_2.id()).await?.unwrap();
assert!(matches!(input_note_record.state(), InputNoteState::Committed { .. }));
let tx_request = TransactionRequestBuilder::new()
.expected_output_recipients(vec![pass_through_note_details_1.recipient().clone()])
.build_consume_notes(vec![pass_through_note_1])
.unwrap();
let tx_id = client
.submit_new_transaction(pass_through_account.id(), tx_request.clone())
.await?;
client.wait_for_tx(tx_id).await?;
let tx_record = client
.get_transactions(TransactionFilter::Ids(vec![tx_id]))
.await?
.pop()
.unwrap();
assert_eq!(
tx_record.details.output_notes.get_note(0).metadata().sender(),
pass_through_account.id()
);
let retained_btc = client
.account_reader(pass_through_account.id())
.get_balance(btc_faucet_account.id())
.await?;
assert_eq!(
retained_btc,
AssetAmount::ZERO,
"pass-through account should not retain the forwarded asset"
);
if let Some(fee_asset) = fee_asset {
let retained_fee = client
.account_reader(pass_through_account.id())
.get_balance(fee_asset.faucet_id())
.await?;
assert_eq!(
retained_fee,
AssetAmount::ZERO,
"the note's fee asset should be spent in full on the fee"
);
}
let commitment_before_second_tx = client
.account_reader(pass_through_account.id())
.commitment()
.await
.expect("pass-through account should exist");
let tx_request = TransactionRequestBuilder::new()
.expected_output_recipients(vec![pass_through_note_details_2.recipient().clone()])
.build_consume_notes(vec![pass_through_note_2])
.unwrap();
let tx_id = client
.submit_new_transaction(pass_through_account.id(), tx_request.clone())
.await?;
client.wait_for_tx(tx_id).await?;
let tx_record = client
.get_transactions(TransactionFilter::Ids(vec![tx_id]))
.await?
.pop()
.unwrap();
assert_eq!(
tx_record.details.output_notes.get_note(0).metadata().sender(),
pass_through_account.id()
);
let commitment_after_second_tx = client
.account_reader(pass_through_account.id())
.commitment()
.await
.expect("pass-through account should exist");
assert_eq!(
commitment_after_second_tx, commitment_before_second_tx,
"pass-through transaction should not change account commitment"
);
Ok(())
}
async fn create_pass_through_account<AUTH: TransactionAuthenticator>(
client: &mut Client<AUTH>,
) -> Result<Account> {
let mut init_seed = [0u8; 32];
client.rng().fill_bytes(&mut init_seed);
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::Private)
.with_component(NoAuth)
.with_component(BasicWallet)
.build_with_schema_commitment()
.unwrap();
client.add_account(&account, false).await?;
Ok(account)
}
async fn pass_through_fee_asset<AUTH: TransactionAuthenticator + Sync + 'static>(
client: &mut Client<AUTH>,
sender: AccountId,
target: AccountId,
asset: Asset,
pass_through_account_id: AccountId,
) -> Result<Option<FungibleAsset>> {
let (genesis, _) = client
.get_block_header_by_num(BlockNumber::GENESIS)
.await?
.context("the genesis block header is not in the client's store")?;
let fee_parameters = genesis.fee_parameters();
if fee_parameters.verification_base_fee() == 0 {
return Ok(None);
}
let fee_faucet_id = fee_faucet_id(client).await?;
let worst_case_fee =
TransactionFee::new(MAX_TX_EXECUTION_CYCLES)?.compute_fee(fee_parameters)?;
let funding = FungibleAsset::new(fee_faucet_id, worst_case_fee.as_u64())?;
let (probe_note, probe_details) =
create_pass_through_note(sender, target, asset, Some(funding.into()), client.rng())?;
let request = TransactionRequestBuilder::new()
.expected_output_recipients(vec![probe_details.recipient().clone()])
.build_consume_notes(vec![probe_note])?;
let fee = client
.execute_transaction(pass_through_account_id, request)
.await?
.executed_transaction()
.compute_fee();
Ok(Some(FungibleAsset::new(fee_faucet_id, fee.as_u64())?))
}
fn get_pass_through_note_script() -> NoteScript {
let note_script_code = include_str!("../asm/PASS_THROUGH.masm");
CodeBuilder::new().compile_note_script(note_script_code).unwrap()
}
fn create_pass_through_note(
sender: AccountId,
target: AccountId,
asset: Asset,
fee_asset: Option<Asset>,
rng: &mut ClientRng,
) -> Result<(Note, NoteDetails)> {
let note_script = get_pass_through_note_script();
let asset_key: Word = asset.to_id_word();
let asset_value: Word = asset.to_value_word();
let target_recipient = P2idNoteStorage::new(target).into_recipient(rng.draw_word());
let inputs = NoteStorage::new(vec![
asset_key[0],
asset_key[1],
asset_key[2],
asset_key[3],
asset_value[0],
asset_value[1],
asset_value[2],
asset_value[3],
target_recipient.digest()[0],
target_recipient.digest()[1],
target_recipient.digest()[2],
target_recipient.digest()[3],
NoteType::Public.into(),
NoteTag::with_account_target(target).into(),
])?;
let serial_num = rng.draw_word();
let pass_through_recipient = NoteRecipient::new(serial_num, note_script, inputs);
let metadata = PartialNoteMetadata::new(sender, NoteType::Public)
.with_tag(NoteTag::with_account_target(target));
let mut note_assets = vec![asset];
note_assets.extend(fee_asset);
let note = Note::new(NoteAssets::new(note_assets)?, metadata, pass_through_recipient);
let pass_through_note_details =
NoteDetails::new(NoteAssets::new(vec![asset])?, target_recipient);
Ok((note, pass_through_note_details))
}