use anyhow::Result;
use miden_client::account::component::{
AuthFalcon512RpoAcl,
AuthFalcon512RpoAclConfig,
BasicWallet,
};
use miden_client::account::{Account, AccountBuilder, AccountId, AccountStorageMode, AccountType};
use miden_client::assembly::CodeBuilder;
use miden_client::asset::{Asset, FungibleAsset};
use miden_client::auth::{AuthSchemeId, TransactionAuthenticator};
use miden_client::crypto::FeltRng;
use miden_client::crypto::rpo_falcon512::SecretKey;
use miden_client::note::{
Note,
NoteAssets,
NoteDetails,
NoteFile,
NoteInputs,
NoteMetadata,
NoteRecipient,
NoteScript,
NoteTag,
NoteType,
build_p2id_recipient,
};
use miden_client::store::{InputNoteState, TransactionFilter};
use miden_client::testing::common::*;
use miden_client::transaction::{OutputNote, TransactionRequestBuilder};
use miden_client::{Client, ClientRng, Word};
use rand::RngCore;
use crate::tests::config::ClientConfig;
pub async fn test_pass_through(client_config: ClientConfig) -> Result<()> {
const ASSET_AMOUNT: u64 = 1;
let (mut client, authenticator_1) = client_config.clone().into_client().await?;
let mut client_config_2 = client_config.as_parts();
client_config_2.2 = create_test_store_path();
let client_config_2 = ClientConfig {
rpc_endpoint: client_config_2.0,
rpc_timeout_ms: client_config_2.1,
store_config: client_config_2.2,
auth_path: client_config_2.3,
};
let (mut client_2, authenticator_2) = client_config_2.into_client().await?;
wait_for_node(&mut client).await;
client.sync_state().await?;
client_2.sync_state().await?;
let (sender, ..) = insert_new_wallet(
&mut client,
AccountStorageMode::Private,
&authenticator_1,
AuthSchemeId::Falcon512Rpo,
)
.await?;
let (target, ..) = insert_new_wallet(
&mut client_2,
AccountStorageMode::Private,
&authenticator_2,
AuthSchemeId::Falcon512Rpo,
)
.await?;
let pass_through_account = create_pass_through_account(&mut client).await?;
let (btc_faucet_account, ..) = insert_new_fungible_faucet(
&mut client,
AccountStorageMode::Private,
&authenticator_1,
AuthSchemeId::Falcon512Rpo,
)
.await?;
println!("minting 1000 btc for account A");
let tx_id =
mint_and_consume(&mut client, sender.id(), btc_faucet_account.id(), NoteType::Public).await;
wait_for_tx(&mut client, tx_id).await?;
println!("creating note with accountA");
let asset = FungibleAsset::new(btc_faucet_account.id(), ASSET_AMOUNT)?;
let (pass_through_note_1, pass_through_note_details_1) =
create_pass_through_note(sender.id(), target.id(), asset.into(), client.rng())?;
let (pass_through_note_2, pass_through_note_details_2) =
create_pass_through_note(sender.id(), target.id(), asset.into(), client.rng())?;
let tx_request = TransactionRequestBuilder::new()
.own_output_notes(vec![
OutputNote::Full(pass_through_note_1.clone()),
OutputNote::Full(pass_through_note_2.clone()),
])
.build()?;
execute_tx_and_sync(&mut client, sender.id(), tx_request).await?;
println!("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?;
wait_for_tx(&mut client, 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 pass_through_before_second_tx = client
.get_account(pass_through_account.id())
.await?
.expect("pass-through account should exist");
let commitment_before_second_tx = pass_through_before_second_tx.account_data().commitment();
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?;
wait_for_tx(&mut client, 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 pass_through_after_second_tx = client
.get_account(pass_through_account.id())
.await?
.expect("pass-through account should exist");
assert_eq!(
pass_through_after_second_tx.account_data().commitment(),
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 key_pair = SecretKey::with_rng(client.rng());
let pub_key = key_pair.public_key().to_commitment();
let acl_config = AuthFalcon512RpoAclConfig::new()
.with_allow_unauthorized_input_notes(true)
.with_allow_unauthorized_output_notes(true);
let auth_component = AuthFalcon512RpoAcl::new(pub_key.into(), acl_config).unwrap();
let account = AccountBuilder::new(init_seed)
.account_type(AccountType::RegularAccountImmutableCode)
.storage_mode(AccountStorageMode::Private)
.with_auth_component(auth_component)
.with_component(BasicWallet)
.build()
.unwrap();
client.add_account(&account, false).await?;
Ok(account)
}
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,
rng: &mut ClientRng,
) -> Result<(Note, NoteDetails)> {
let note_script = get_pass_through_note_script();
let asset_word: Word = asset.into();
let target_recipient = build_p2id_recipient(target, rng.draw_word())?;
let inputs = NoteInputs::new(vec![
asset_word[0],
asset_word[1],
asset_word[2],
asset_word[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 =
NoteMetadata::new(sender, NoteType::Public, NoteTag::with_account_target(target));
let note = Note::new(NoteAssets::new(vec![asset])?, metadata, pass_through_recipient);
let pass_through_note_details =
NoteDetails::new(NoteAssets::new(vec![asset])?, target_recipient);
Ok((note, pass_through_note_details))
}