Skip to main content

miden_standards/note/
utils.rs

1use miden_protocol::account::AccountId;
2use miden_protocol::asset::Asset;
3use miden_protocol::block::BlockNumber;
4use miden_protocol::errors::NoteError;
5use miden_protocol::note::{NoteInputs, NoteRecipient, NoteTag, NoteType};
6use miden_protocol::{Felt, Word};
7
8use super::well_known_note::WellKnownNote;
9
10/// Creates a [NoteRecipient] for the P2ID note.
11///
12/// Notes created with this recipient will be P2ID notes consumable by the specified target
13/// account.
14pub fn build_p2id_recipient(
15    target: AccountId,
16    serial_num: Word,
17) -> Result<NoteRecipient, NoteError> {
18    let note_script = WellKnownNote::P2ID.script();
19    let note_inputs = NoteInputs::new(vec![target.suffix(), target.prefix().as_felt()])?;
20
21    Ok(NoteRecipient::new(serial_num, note_script, note_inputs))
22}
23
24/// Creates a [NoteRecipient] for the P2IDE note.
25///
26/// Notes created with this recipient will be P2IDE notes consumable by the specified target
27/// account.
28pub fn build_p2ide_recipient(
29    target: AccountId,
30    reclaim_block_height: Option<BlockNumber>,
31    timelock_block_height: Option<BlockNumber>,
32    serial_num: Word,
33) -> Result<NoteRecipient, NoteError> {
34    let note_script = WellKnownNote::P2IDE.script();
35
36    let reclaim_height_u32 = reclaim_block_height.map_or(0, |bn| bn.as_u32());
37    let timelock_height_u32 = timelock_block_height.map_or(0, |bn| bn.as_u32());
38
39    let note_inputs = NoteInputs::new(vec![
40        target.suffix(),
41        target.prefix().into(),
42        Felt::new(reclaim_height_u32 as u64),
43        Felt::new(timelock_height_u32 as u64),
44    ])?;
45
46    Ok(NoteRecipient::new(serial_num, note_script, note_inputs))
47}
48
49/// Returns a note tag for a swap note with the specified parameters.
50///
51/// The tag is laid out as follows:
52///
53/// ```text
54/// [
55///   note_type (2 bits) | script_root (14 bits)
56///   | offered_asset_faucet_id (8 bits) | requested_asset_faucet_id (8 bits)
57/// ]
58/// ```
59///
60/// The script root serves as the use case identifier of the SWAP tag.
61pub fn build_swap_tag(
62    note_type: NoteType,
63    offered_asset: &Asset,
64    requested_asset: &Asset,
65) -> NoteTag {
66    let swap_root_bytes = WellKnownNote::SWAP.script().root().as_bytes();
67    // Construct the swap use case ID from the 14 most significant bits of the script root. This
68    // leaves the two most significant bits zero.
69    let mut swap_use_case_id = (swap_root_bytes[0] as u16) << 6;
70    swap_use_case_id |= (swap_root_bytes[1] >> 2) as u16;
71
72    // Get bits 0..8 from the faucet IDs of both assets which will form the tag payload.
73    let offered_asset_id: u64 = offered_asset.faucet_id_prefix().into();
74    let offered_asset_tag = (offered_asset_id >> 56) as u8;
75
76    let requested_asset_id: u64 = requested_asset.faucet_id_prefix().into();
77    let requested_asset_tag = (requested_asset_id >> 56) as u8;
78
79    let asset_pair = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16);
80
81    let tag =
82        ((note_type as u8 as u32) << 30) | ((swap_use_case_id as u32) << 16) | asset_pair as u32;
83
84    NoteTag::new(tag)
85}
86
87#[cfg(test)]
88mod tests {
89    use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType};
90    use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
91    use miden_protocol::{self};
92
93    use super::*;
94
95    #[test]
96    fn swap_tag() {
97        // Construct an ID that starts with 0xcdb1.
98        let mut fungible_faucet_id_bytes = [0; 15];
99        fungible_faucet_id_bytes[0] = 0xcd;
100        fungible_faucet_id_bytes[1] = 0xb1;
101
102        // Construct an ID that starts with 0xabec.
103        let mut non_fungible_faucet_id_bytes = [0; 15];
104        non_fungible_faucet_id_bytes[0] = 0xab;
105        non_fungible_faucet_id_bytes[1] = 0xec;
106
107        let offered_asset = Asset::Fungible(
108            FungibleAsset::new(
109                AccountId::dummy(
110                    fungible_faucet_id_bytes,
111                    AccountIdVersion::Version0,
112                    AccountType::FungibleFaucet,
113                    AccountStorageMode::Public,
114                ),
115                2500,
116            )
117            .unwrap(),
118        );
119
120        let requested_asset = Asset::NonFungible(
121            NonFungibleAsset::new(
122                &NonFungibleAssetDetails::new(
123                    AccountId::dummy(
124                        non_fungible_faucet_id_bytes,
125                        AccountIdVersion::Version0,
126                        AccountType::NonFungibleFaucet,
127                        AccountStorageMode::Public,
128                    )
129                    .prefix(),
130                    vec![0xaa, 0xbb, 0xcc, 0xdd],
131                )
132                .unwrap(),
133            )
134            .unwrap(),
135        );
136
137        // The fungible ID starts with 0xcdb1.
138        // The non fungible ID starts with 0xabec.
139        // The expected tag payload is thus 0xcdab.
140        let expected_asset_pair = 0xcdab;
141
142        let note_type = NoteType::Public;
143        let actual_tag = build_swap_tag(note_type, &offered_asset, &requested_asset);
144
145        assert_eq!(actual_tag.as_u32() as u16, expected_asset_pair, "asset pair should match");
146        assert_eq!((actual_tag.as_u32() >> 30) as u8, note_type as u8, "note type should match");
147        // Check the 8 bits of the first script root byte.
148        assert_eq!(
149            (actual_tag.as_u32() >> 22) as u8,
150            WellKnownNote::SWAP.script().root().as_bytes()[0],
151            "swap script root byte 0 should match"
152        );
153        // Extract the 6 bits of the second script root byte and shift for comparison.
154        assert_eq!(
155            ((actual_tag.as_u32() & 0b00000000_00111111_00000000_00000000) >> 16) as u8,
156            WellKnownNote::SWAP.script().root().as_bytes()[1] >> 2,
157            "swap script root byte 1 should match with the lower two bits set to zero"
158        );
159    }
160}