Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Added `SignedBlock` struct ([#2355](https://github.com/0xMiden/miden-base/pull/2235)).
- Added `PackageKind` and `ProcedureExport` ([#2358](https://github.com/0xMiden/miden-base/pull/2358)).
- [BREAKING] Added `get_asset` and `get_initial_asset` kernel procedures and removed `get_balance`, `get_initial_balance` and `has_non_fungible_asset` kernel procedures ([#2369](https://github.com/0xMiden/miden-base/pull/2369)).
- Added `p2id::new` MASM constructor procedure for creating P2ID notes from MASM code ([#2381](https://github.com/0xMiden/miden-base/pull/2381)).
- Introduced `TokenMetadata` type to encapsulate fungible faucet metadata ([#2344](https://github.com/0xMiden/miden-base/issues/2344)).
- Added `StandardNote::from_script_root()` and `StandardNote::name()` methods, and exposed `NoteType` `PUBLIC`/`PRIVATE` masks as public constants ([#2411](https://github.com/0xMiden/miden-base/pull/2411)).
- Resolve standard note scripts directly in `TransactionExecutorHost` instead of querying the data store ([#2417](https://github.com/0xMiden/miden-base/pull/2417)).
Expand Down
51 changes: 51 additions & 0 deletions crates/miden-standards/asm/standards/notes/p2id.masm
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use miden::protocol::active_account
use miden::protocol::account_id
use miden::protocol::active_note
use miden::protocol::note
use miden::protocol::output_note
use miden::standards::wallets::basic->basic_wallet

# ERRORS
Expand Down Expand Up @@ -52,3 +54,52 @@ pub proc main
exec.basic_wallet::add_assets_to_account
# => []
end

#! Creates a new P2ID output note from the given inputs.
#!
#! This procedure handles:
#! - Writing note storage to memory in the expected layout ([suffix, prefix] to match
#! existing P2ID storage format)
#! - Obtaining the note script root via procref
#! - Building the recipient and creating the note
#!
#! Inputs: [target_id_prefix, target_id_suffix, tag, note_type, SERIAL_NUM]
#! Outputs: [note_idx]
#!
#! Where:
#! - target_id_prefix is the prefix felt of the target account ID.
#! - target_id_suffix is the suffix felt of the target account ID.
#! - tag is the note tag to be included in the note.
#! - note_type is the storage type of the note (1 = public, 2 = private).
#! - SERIAL_NUM is the serial number of the note (4 elements).
#! - note_idx is the index of the created note.
#!
#! Invocation: exec
@locals(2)
pub proc new
# => [target_id_prefix, target_id_suffix, tag, note_type, SERIAL_NUM]

loc_store.1 loc_store.0
# => [tag, note_type, SERIAL_NUM]

movdn.5 movdn.5
# => [SERIAL_NUM, tag, note_type]

procref.main
# => [SCRIPT_ROOT, SERIAL_NUM, tag, note_type]

swapw
# => [SERIAL_NUM, SCRIPT_ROOT, tag, note_type]

push.2 locaddr.0
# => [storage_ptr, num_storage_items=2, SERIAL_NUM, SCRIPT_ROOT, tag, note_type]
Comment on lines +94 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to introduce a pub const NUM_STORAGE_ITEMS = 2 and use it here, as well as in main (for the eq.2 check). This also makes it easy to access the note's number of storage items from MASM, i.e. miden::standards::notes::p2id::NUM_STORAGE_ITEMS, which may be useful in some scenarios.


exec.note::build_recipient
# => [RECIPIENT, tag, note_type]

movup.5 movup.5
# => [tag, note_type, RECIPIENT]

exec.output_note::create
# => [note_idx]
end
95 changes: 94 additions & 1 deletion crates/miden-testing/tests/scripts/p2id.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use miden_protocol::account::Account;
use miden_protocol::asset::{Asset, AssetVault, FungibleAsset};
use miden_protocol::crypto::rand::RpoRandomCoin;
use miden_protocol::note::{NoteAttachment, NoteType};
use miden_protocol::note::{NoteAttachment, NoteTag, NoteType};
use miden_protocol::testing::account_id::{
ACCOUNT_ID_PRIVATE_FUNGIBLE_FAUCET,
ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_2,
Expand Down Expand Up @@ -274,3 +274,96 @@ async fn test_create_consume_multiple_notes() -> anyhow::Result<()> {
assert_eq!(account.vault().get_balance(FungibleAsset::mock_issuer())?, 5);
Ok(())
}

/// Tests the P2ID `new` MASM constructor procedure.
/// This test verifies that calling `p2id::new` from a transaction script creates an output note
/// with the same recipient as `P2idNote::build_recipient` would create.
#[tokio::test]
async fn test_p2id_new_constructor() -> anyhow::Result<()> {
let mut builder = MockChain::builder();

let sender_account =
builder.add_existing_wallet_with_assets(Auth::BasicAuth, [FungibleAsset::mock(100)])?;
let target_account = builder.add_existing_wallet(Auth::BasicAuth)?;

let mock_chain = builder.build()?;

// Create a serial number for the note
let serial_num = Word::from([1u32, 2u32, 3u32, 4u32]);

// Build the expected recipient using the Rust implementation
let expected_recipient = P2idNote::build_recipient(target_account.id(), serial_num)?;

// Create a note tag for the target account
let tag = NoteTag::with_account_target(target_account.id());

// Build a transaction script that uses p2id::new to create a note
let tx_script_src = format!(
r#"
use miden::standards::notes::p2id

begin
# Push inputs for p2id::new
# Inputs: [target_id_prefix, target_id_suffix, tag, note_type, SERIAL_NUM]
push.{serial_num}
push.{note_type}
push.{tag}
push.{target_suffix}
push.{target_prefix}
# => [target_id_prefix, target_id_suffix, tag, note_type, SERIAL_NUM]

exec.p2id::new
# => [note_idx]

# Add an asset to the created note
push.{asset}
call.::miden::standards::wallets::basic::move_asset_to_note

# Clean up stack
dropw dropw dropw dropw
end
"#,
target_prefix = target_account.id().prefix().as_felt(),
target_suffix = target_account.id().suffix(),
tag = Felt::from(tag),
note_type = NoteType::Public as u8,
serial_num = serial_num,
asset = Word::from(FungibleAsset::mock(50)),
);

let tx_script = CodeBuilder::default().compile_tx_script(&tx_script_src)?;

// Build expected output note
let expected_output_note = P2idNote::create(
sender_account.id(),
target_account.id(),
vec![FungibleAsset::mock(50)],
NoteType::Public,
NoteAttachment::default(),
&mut RpoRandomCoin::new(serial_num),
)?;

let tx_context = mock_chain
.build_tx_context(sender_account.id(), &[], &[])?
.extend_expected_output_notes(vec![OutputNote::Full(expected_output_note)])
.tx_script(tx_script)
.build()?;

let executed_transaction = tx_context.execute().await?;

// Verify that one note was created
assert_eq!(executed_transaction.output_notes().num_notes(), 1);

// Get the created note's recipient and verify it matches
let output_note = executed_transaction.output_notes().get_note(0);
let created_recipient = output_note.recipient().expect("output note should have recipient");

// Verify the recipient matches what we expected
assert_eq!(
created_recipient.digest(),
expected_recipient.digest(),
"The recipient created by p2id::new should match P2idNote::build_recipient"
);

Ok(())
}