use alloc::vec::Vec;
use miden_protocol::account::AccountId;
use miden_protocol::assembly::Path;
use miden_protocol::asset::Asset;
use miden_protocol::crypto::rand::FeltRng;
use miden_protocol::errors::NoteError;
use miden_protocol::note::{
Note,
NoteAssets,
NoteAttachment,
NoteMetadata,
NoteRecipient,
NoteScript,
NoteStorage,
NoteTag,
NoteType,
};
use miden_protocol::utils::sync::LazyLock;
use miden_protocol::{Felt, Word};
use crate::StandardsLib;
const P2ID_SCRIPT_PATH: &str = "::miden::standards::notes::p2id::main";
static P2ID_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
let standards_lib = StandardsLib::default();
let path = Path::new(P2ID_SCRIPT_PATH);
NoteScript::from_library_reference(standards_lib.as_ref(), path)
.expect("Standards library contains P2ID note script procedure")
});
pub struct P2idNote;
impl P2idNote {
pub const NUM_STORAGE_ITEMS: usize = P2idNoteStorage::NUM_ITEMS;
pub fn script() -> NoteScript {
P2ID_SCRIPT.clone()
}
pub fn script_root() -> Word {
P2ID_SCRIPT.root()
}
pub fn create<R: FeltRng>(
sender: AccountId,
target: AccountId,
assets: Vec<Asset>,
note_type: NoteType,
attachment: NoteAttachment,
rng: &mut R,
) -> Result<Note, NoteError> {
let serial_num = rng.draw_word();
let recipient = P2idNoteStorage::new(target).into_recipient(serial_num);
let tag = NoteTag::with_account_target(target);
let metadata =
NoteMetadata::new(sender, note_type).with_tag(tag).with_attachment(attachment);
let vault = NoteAssets::new(assets)?;
Ok(Note::new(vault, metadata, recipient))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct P2idNoteStorage {
target: AccountId,
}
impl P2idNoteStorage {
pub const NUM_ITEMS: usize = 2;
pub fn new(target: AccountId) -> Self {
Self { target }
}
pub fn into_recipient(self, serial_num: Word) -> NoteRecipient {
NoteRecipient::new(serial_num, P2idNote::script(), NoteStorage::from(self))
}
pub fn target(&self) -> AccountId {
self.target
}
}
impl From<P2idNoteStorage> for NoteStorage {
fn from(storage: P2idNoteStorage) -> Self {
NoteStorage::new(vec![storage.target.suffix(), storage.target.prefix().as_felt()])
.expect("number of storage items should not exceed max storage items")
}
}
impl TryFrom<&[Felt]> for P2idNoteStorage {
type Error = NoteError;
fn try_from(note_storage: &[Felt]) -> Result<Self, Self::Error> {
if note_storage.len() != P2idNote::NUM_STORAGE_ITEMS {
return Err(NoteError::InvalidNoteStorageLength {
expected: P2idNote::NUM_STORAGE_ITEMS,
actual: note_storage.len(),
});
}
let target = AccountId::try_from_elements(note_storage[0], note_storage[1])
.map_err(|err| NoteError::other_with_source("failed to create account id", err))?;
Ok(Self { target })
}
}
#[cfg(test)]
mod tests {
use miden_protocol::Felt;
use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
use miden_protocol::errors::NoteError;
use super::*;
#[test]
fn try_from_valid_storage_succeeds() {
let target = AccountId::dummy(
[1u8; 15],
AccountIdVersion::Version0,
AccountType::FungibleFaucet,
AccountStorageMode::Private,
);
let storage = vec![target.suffix(), target.prefix().as_felt()];
let parsed =
P2idNoteStorage::try_from(storage.as_slice()).expect("storage should be valid");
assert_eq!(parsed.target(), target);
}
#[test]
fn try_from_invalid_length_returns_error() {
let storage = vec![Felt::ZERO];
let err = P2idNoteStorage::try_from(storage.as_slice())
.expect_err("should fail due to invalid length");
assert!(matches!(
err,
NoteError::InvalidNoteStorageLength {
expected: P2idNote::NUM_STORAGE_ITEMS,
actual: 1
}
));
}
#[test]
fn try_from_invalid_storage_contents_returns_error() {
let storage = vec![Felt::new(999u64), Felt::new(888u64)];
let err = P2idNoteStorage::try_from(storage.as_slice())
.expect_err("should fail due to invalid account id encoding");
assert!(matches!(err, NoteError::Other { source: Some(_), .. }));
}
}