use anyhow::{Context, Result};
use miden_client::account::{AccountId, AccountType};
use miden_client::asset::FungibleAsset;
use miden_client::auth::RPO_FALCON_SCHEME_ID;
use miden_client::crypto::{FeltRng, MerkleStore, MerkleTree, NodeIndex, Poseidon2, RandomCoin};
use miden_client::note::{
Note,
NoteAssets,
NoteRecipient,
NoteStorage,
NoteTag,
NoteType,
PartialNoteMetadata,
};
use miden_client::store::{NoteFilter, TransactionFilter};
use miden_client::testing::common::*;
use miden_client::transaction::{
AdviceMap,
InputNote,
TransactionRequest,
TransactionRequestBuilder,
};
use miden_client::utils::{Deserializable, Serializable};
use miden_client::{Felt, Word, ZERO};
use crate::tests::config::ClientConfig;
const NOTE_ARGS: [Felt; 8] = [
Felt::new_unchecked(9),
Felt::new_unchecked(12),
Felt::new_unchecked(18),
Felt::new_unchecked(3),
Felt::new_unchecked(3),
Felt::new_unchecked(18),
Felt::new_unchecked(12),
Felt::new_unchecked(9),
];
pub async fn test_transaction_request(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
wait_for_node(&mut client).await;
client.sync_state().await?;
let (regular_account, _) =
insert_new_wallet(&mut client, AccountType::Private, &authenticator, RPO_FALCON_SCHEME_ID)
.await?;
let (fungible_faucet, _) = insert_new_fungible_faucet(
&mut client,
AccountType::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let note = mint_custom_note(&mut client, fungible_faucet.id(), regular_account.id()).await?;
client.sync_state().await?;
let note_args_commitment = Poseidon2::hash_elements(&NOTE_ARGS);
let note_args_map = vec![(note.clone(), Some(note_args_commitment))];
let mut advice_map = AdviceMap::default();
advice_map.insert(note_args_commitment, NOTE_ARGS.to_vec());
let code = "
begin
# We use the script argument to store the expected value to be compared
push.1.2.3.4
# => [[1,2,3,4], TX_SCRIPT_ARG]
assert_eqw
end
";
let tx_script = client.code_builder().compile_tx_script(code)?;
let transaction_request = TransactionRequestBuilder::new()
.input_notes(note_args_map.clone())
.custom_script(tx_script.clone())
.script_arg(Word::empty())
.extend_advice_map(advice_map.clone())
.build()?;
assert!(
client
.execute_transaction(regular_account.id(), transaction_request)
.await
.is_err()
);
let transaction_request = TransactionRequestBuilder::new()
.input_notes(note_args_map)
.custom_script(tx_script)
.script_arg([Felt::from(4u32), Felt::from(3u32), Felt::from(2u32), Felt::from(1u32)].into())
.extend_advice_map(advice_map)
.build()?;
let mut buffer = Vec::new();
transaction_request.write_into(&mut buffer);
let deserialized_transaction_request = TransactionRequest::read_from_bytes(&buffer)?;
assert_eq!(transaction_request, deserialized_transaction_request);
let tx_id = client.submit_new_transaction(regular_account.id(), transaction_request).await?;
let transaction = client
.get_transactions(TransactionFilter::Ids(vec![tx_id]))
.await?
.pop()
.with_context(|| "failed to find transaction after submission")?;
assert!(
transaction
.details
.input_note_nullifiers
.into_iter()
.any(|nullifier| nullifier == note.nullifier().as_word())
);
wait_for_tx(&mut client, tx_id).await?;
let input_note = client
.get_input_note(note.id())
.await?
.context("failed to find input note after consume transaction execution")?;
assert!(input_note.is_consumed());
Ok(())
}
pub async fn test_merkle_store(client_config: ClientConfig) -> Result<()> {
let (mut client, authenticator) = client_config.into_client().await?;
wait_for_node(&mut client).await;
client.sync_state().await?;
let (regular_account, _) =
insert_new_wallet(&mut client, AccountType::Private, &authenticator, RPO_FALCON_SCHEME_ID)
.await?;
let (fungible_faucet, _) = insert_new_fungible_faucet(
&mut client,
AccountType::Private,
&authenticator,
RPO_FALCON_SCHEME_ID,
)
.await?;
let note = mint_custom_note(&mut client, fungible_faucet.id(), regular_account.id()).await?;
client.sync_state().await?;
let note_args_commitment = Poseidon2::hash_elements(&NOTE_ARGS);
let note_args_map = vec![(note, Some(note_args_commitment))];
let mut advice_map = AdviceMap::default();
advice_map.insert(note_args_commitment, NOTE_ARGS.to_vec());
let leaves: Vec<Word> = [1, 2, 3, 4]
.iter()
.map(|&v| [Felt::new_unchecked(v), ZERO, ZERO, ZERO].into())
.collect();
let num_leaves = leaves.len();
let merkle_tree = MerkleTree::new(leaves)?;
let merkle_root = merkle_tree.root();
let merkle_store: MerkleStore = MerkleStore::from(&merkle_tree);
let mut code = format!(
"
begin
# leaf count -> mem[4000][0]
push.{num_leaves} push.4000 mem_store
# merkle root -> mem[4004]
push.{} push.4004 mem_storew_le dropw
",
merkle_root.to_hex()
);
for pos in 0..(num_leaves as u64) {
let expected_element =
merkle_store.get_node(merkle_root, NodeIndex::new(2u8, pos)?)?.to_hex();
code += format!(
"
# get element at index `pos` from the merkle store in mem[1000] and push it to stack
push.4000 push.{pos} exec.::miden::core::collections::mmr::get
# check the element matches what was inserted at `pos`
push.{expected_element} assert_eqw.err=\"element in merkle store didn't match expected\"
"
)
.as_str();
}
code += "end";
let tx_script = client.code_builder().compile_tx_script(&code)?;
let transaction_request = TransactionRequestBuilder::new()
.input_notes(note_args_map)
.custom_script(tx_script)
.extend_advice_map(advice_map)
.extend_merkle_store(merkle_store.inner_nodes())
.build()?;
execute_tx_and_sync(&mut client, regular_account.id(), transaction_request).await?;
client.sync_state().await?;
Ok(())
}
pub async fn test_onchain_notes_sync_with_tag(client_config: ClientConfig) -> Result<()> {
let (mut client_1, keystore_1) = client_config.clone().into_client().await?;
let (mut client_2, keystore_2) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
let (mut client_3, ..) = ClientConfig::default()
.with_rpc_endpoint(client_config.rpc_endpoint())
.into_client()
.await?;
wait_for_node(&mut client_3).await;
let (basic_account_1, ..) =
insert_new_wallet(&mut client_1, AccountType::Private, &keystore_1, RPO_FALCON_SCHEME_ID)
.await?;
insert_new_wallet(&mut client_2, AccountType::Private, &keystore_2, RPO_FALCON_SCHEME_ID)
.await?;
client_1.sync_state().await?;
client_2.sync_state().await?;
client_3.sync_state().await?;
let note_script = "
@note_script
pub proc main
push.1 push.1
assert_eq
end
";
let note_script = client_1.code_builder().compile_note_script(note_script)?;
let inputs = NoteStorage::new(vec![])?;
let serial_num = client_1.rng().draw_word();
let note_metadata = PartialNoteMetadata::new(basic_account_1.id(), NoteType::Public)
.with_tag(NoteTag::with_account_target(basic_account_1.id()));
let note_assets = NoteAssets::new(vec![])?;
let note_recipient = NoteRecipient::new(serial_num, note_script, inputs);
let note = Note::new(note_assets, note_metadata, note_recipient);
let tx_request =
TransactionRequestBuilder::new().own_output_notes(vec![note.clone()]).build()?;
let note = tx_request
.expected_output_own_notes()
.pop()
.with_context(|| "no expected output notes found in transaction request")?
.clone();
execute_tx_and_sync(&mut client_1, basic_account_1.id(), tx_request).await?;
client_2
.add_note_tag(NoteTag::with_account_target(basic_account_1.id()))
.await?;
client_2.sync_state().await?;
client_3.sync_state().await?;
let received_note: InputNote = client_2
.get_input_note(note.id())
.await?
.context("failed to find input note in client 2 after sync")?
.try_into()?;
assert_eq!(received_note.note().id(), note.id());
assert!(client_3.get_input_notes(NoteFilter::All).await?.is_empty());
Ok(())
}
async fn mint_custom_note(
client: &mut TestClient,
faucet_account_id: AccountId,
target_account_id: AccountId,
) -> Result<Note> {
let mut random_coin = RandomCoin::new(Default::default());
let note = create_custom_note(client, faucet_account_id, target_account_id, &mut random_coin)?;
let transaction_request =
TransactionRequestBuilder::new().own_output_notes(vec![note.clone()]).build()?;
execute_tx_and_sync(client, faucet_account_id, transaction_request).await?;
Ok(note)
}
fn create_custom_note(
client: &TestClient,
faucet_account_id: AccountId,
target_account_id: AccountId,
rng: &mut RandomCoin,
) -> Result<Note> {
let mem_addr: u32 = 1000;
let word_1: Word = NOTE_ARGS[0..4].try_into().unwrap();
let word_2: Word = NOTE_ARGS[4..8].try_into().unwrap();
let note_script = include_str!("../asm/custom_p2id.masm")
.replace("{expected_note_arg_1}", &word_1.to_hex())
.replace("{expected_note_arg_2}", &word_2.to_hex())
.replace("{mem_address}", &mem_addr.to_string())
.replace("{mem_address_2}", &(mem_addr + 4).to_string());
let note_script = client
.code_builder()
.compile_note_script(¬e_script)
.context("failed to compile custom note script")?;
let inputs =
NoteStorage::new(vec![target_account_id.suffix(), target_account_id.prefix().as_felt()])
.context("failed to create note inputs")?;
let serial_num = rng.draw_word();
let note_metadata = PartialNoteMetadata::new(faucet_account_id, NoteType::Private)
.with_tag(NoteTag::with_account_target(target_account_id));
let note_assets = NoteAssets::new(vec![
FungibleAsset::new(faucet_account_id, 10)
.context("failed to create fungible asset")?
.into(),
])
.context("failed to create note assets")?;
let note_recipient = NoteRecipient::new(serial_num, note_script, inputs);
Ok(Note::new(note_assets, note_metadata, note_recipient))
}