use miden_protocol::Word;
use miden_protocol::account::Account;
use miden_protocol::asset::{Asset, FungibleAsset};
use miden_protocol::crypto::rand::RandomCoin;
use miden_protocol::note::{NoteAttachment, NoteAttachmentScheme, NoteType};
use miden_protocol::testing::account_id::ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE;
use miden_standards::testing::mock_account::MockAccountExt;
use miden_standards::testing::note::NoteBuilder;
use rstest::rstest;
use crate::kernel_tests::tx::ExecutionOutputExt;
use crate::{Auth, TestTransactionBuilder, TransactionContext};
fn two_note_tx() -> anyhow::Result<TransactionContext> {
let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce);
let mut rng = RandomCoin::new(Word::from([1, 2, 3, 4u32]));
let bare_note = NoteBuilder::new(account.id(), &mut rng).build()?;
let rich_note = NoteBuilder::new(account.id(), &mut rng)
.note_type(NoteType::Public)
.add_assets(vec![FungibleAsset::mock(150)])
.attachment(NoteAttachment::with_word(
NoteAttachmentScheme::new(10)?,
Word::from([3, 4, 5, 6u32]),
))
.attachment(NoteAttachment::with_word(
NoteAttachmentScheme::new(20)?,
Word::from([7, 8, 9, 10u32]),
))
.build()?;
TestTransactionBuilder::new(account)
.extend_input_notes(vec![bare_note, rich_note])
.build()
}
#[rstest]
#[tokio::test]
async fn compute_active_and_input_note_id_matches_rust(
#[values(0, 1)] note_index: u8,
#[values("compute_active_note_id", "compute_input_note_id")] procedure: &str,
) -> anyhow::Result<()> {
let tx_context = two_note_tx()?;
let setup_code = if procedure == "compute_active_note_id" {
format!(
"push.{note_index} exec.memory::get_input_note_ptr exec.memory::set_active_input_note_ptr"
)
} else {
format!("push.{note_index}")
};
let code = format!(
r#"
use miden::tx_kernel_core::memory
use miden::tx_kernel_core::prologue
use miden::standards::note::note_id
begin
exec.prologue::prepare_transaction
{setup_code}
exec.note_id::{procedure}
# => [NOTE_ID]
# truncate the stack
swapw dropw
end
"#
);
let exec_output = tx_context.execute_code(&code).await?;
let expected = tx_context.input_notes().get_note(note_index as usize).note().id();
assert_eq!(exec_output.get_stack_word(0), expected.as_word());
Ok(())
}
#[rstest]
#[tokio::test]
async fn find_input_note_by_id_returns_index(#[values(0, 1)] note_index: u8) -> anyhow::Result<()> {
let tx_context = two_note_tx()?;
let note_id = tx_context.input_notes().get_note(note_index as usize).note().id();
let code = format!(
r#"
use miden::core::sys
use miden::tx_kernel_core::prologue
use miden::standards::note::note_id
begin
exec.prologue::prepare_transaction
push.{note_id}
exec.note_id::find_input_note_by_id
# => [is_found, note_idx]
# truncate the stack
exec.sys::truncate_stack
end
"#,
note_id = note_id.as_word(),
);
let exec_output = tx_context.execute_code(&code).await?;
assert_eq!(exec_output.get_stack_word(0), Word::from([1, note_index as u32, 0, 0]));
Ok(())
}
#[tokio::test]
async fn find_input_note_by_id_reports_missing_note() -> anyhow::Result<()> {
let tx_context = two_note_tx()?;
let unknown_id = Word::from([11, 12, 13, 14u32]);
let code = format!(
r#"
use miden::core::sys
use miden::tx_kernel_core::prologue
use miden::standards::note::note_id
begin
exec.prologue::prepare_transaction
push.{unknown_id}
exec.note_id::find_input_note_by_id
# => [is_found, note_idx]
# truncate the stack
exec.sys::truncate_stack
end
"#
);
let exec_output = tx_context.execute_code(&code).await?;
assert_eq!(exec_output.get_stack_word(0), Word::empty());
Ok(())
}
#[tokio::test]
async fn compute_output_note_id_matches_rust() -> anyhow::Result<()> {
let account = Account::mock(ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_UPDATABLE_CODE, Auth::IncrNonce);
let rng = RandomCoin::new(Word::from([1, 2, 3, 4u32]));
let asset: Asset = FungibleAsset::mock(150);
let attachment_scheme = NoteAttachmentScheme::new(10)?;
let attachment_word = Word::from([3, 4, 5, 6u32]);
let expected_note = NoteBuilder::new(account.id(), rng)
.note_type(NoteType::Public)
.add_assets(vec![asset])
.attachment(NoteAttachment::with_word(attachment_scheme, attachment_word))
.build()?;
let tag = expected_note.metadata().tag();
let tx_context = TestTransactionBuilder::new(account).build()?;
let code = format!(
r#"
use miden::core::sys
use miden::protocol::output_note
use miden::tx_kernel_core::prologue
use miden::standards::note::note_id
begin
exec.prologue::prepare_transaction
push.{recipient}
push.{note_type}
push.{tag}
call.::mock::account::create_note
# => [note_idx]
dup push.{asset_value} push.{asset_id}
# => [ASSET_ID, ASSET_VALUE, note_idx, note_idx]
exec.output_note::add_asset
# => [note_idx]
dup push.{attachment_word} push.{attachment_scheme}
# => [attachment_scheme, ATTACHMENT, note_idx, note_idx]
exec.output_note::add_word_attachment
# => [note_idx]
exec.note_id::compute_output_note_id
# => [NOTE_ID]
# truncate the stack
exec.sys::truncate_stack
end
"#,
recipient = expected_note.recipient().digest(),
note_type = NoteType::Public as u8,
tag = tag,
asset_value = asset.to_value_word(),
asset_id = asset.to_id_word(),
attachment_word = attachment_word,
attachment_scheme = attachment_scheme.as_u16(),
);
let exec_output = tx_context.execute_code(&code).await?;
assert_eq!(exec_output.get_stack_word(0), expected_note.id().as_word());
Ok(())
}