miden_standards/note/
swap.rs1use alloc::vec::Vec;
2
3use miden_protocol::account::AccountId;
4use miden_protocol::assembly::Path;
5use miden_protocol::asset::Asset;
6use miden_protocol::crypto::rand::FeltRng;
7use miden_protocol::errors::NoteError;
8use miden_protocol::note::{
9 Note,
10 NoteAssets,
11 NoteAttachment,
12 NoteDetails,
13 NoteMetadata,
14 NoteRecipient,
15 NoteScript,
16 NoteStorage,
17 NoteTag,
18 NoteType,
19};
20use miden_protocol::utils::sync::LazyLock;
21use miden_protocol::{Felt, Word};
22
23use crate::StandardsLib;
24use crate::note::P2idNoteStorage;
25
26const SWAP_SCRIPT_PATH: &str = "::miden::standards::notes::swap::main";
31
32static SWAP_SCRIPT: LazyLock<NoteScript> = LazyLock::new(|| {
34 let standards_lib = StandardsLib::default();
35 let path = Path::new(SWAP_SCRIPT_PATH);
36 NoteScript::from_library_reference(standards_lib.as_ref(), path)
37 .expect("Standards library contains SWAP note script procedure")
38});
39
40pub struct SwapNote;
45
46impl SwapNote {
47 pub const NUM_STORAGE_ITEMS: usize = 16;
52
53 pub fn script() -> NoteScript {
58 SWAP_SCRIPT.clone()
59 }
60
61 pub fn script_root() -> Word {
63 SWAP_SCRIPT.root()
64 }
65
66 pub fn create<R: FeltRng>(
79 sender: AccountId,
80 offered_asset: Asset,
81 requested_asset: Asset,
82 swap_note_type: NoteType,
83 swap_note_attachment: NoteAttachment,
84 payback_note_type: NoteType,
85 payback_note_attachment: NoteAttachment,
86 rng: &mut R,
87 ) -> Result<(Note, NoteDetails), NoteError> {
88 if requested_asset == offered_asset {
89 return Err(NoteError::other("requested asset same as offered asset"));
90 }
91
92 let note_script = Self::script();
93
94 let payback_serial_num = rng.draw_word();
95 let payback_recipient = P2idNoteStorage::new(sender).into_recipient(payback_serial_num);
96
97 let requested_asset_word: Word = requested_asset.into();
98 let payback_tag = NoteTag::with_account_target(sender);
99
100 let attachment_scheme = Felt::from(payback_note_attachment.attachment_scheme().as_u32());
101 let attachment_kind = Felt::from(payback_note_attachment.attachment_kind().as_u8());
102 let attachment = payback_note_attachment.content().to_word();
103
104 let mut inputs = Vec::with_capacity(16);
105 inputs.extend_from_slice(&[
106 payback_note_type.into(),
107 payback_tag.into(),
108 attachment_scheme,
109 attachment_kind,
110 ]);
111 inputs.extend_from_slice(attachment.as_elements());
112 inputs.extend_from_slice(requested_asset_word.as_elements());
113 inputs.extend_from_slice(payback_recipient.digest().as_elements());
114 let inputs = NoteStorage::new(inputs)?;
115
116 let tag = Self::build_tag(swap_note_type, &offered_asset, &requested_asset);
118 let serial_num = rng.draw_word();
119
120 let metadata = NoteMetadata::new(sender, swap_note_type)
122 .with_tag(tag)
123 .with_attachment(swap_note_attachment);
124 let assets = NoteAssets::new(vec![offered_asset])?;
125 let recipient = NoteRecipient::new(serial_num, note_script, inputs);
126 let note = Note::new(assets, metadata, recipient);
127
128 let payback_assets = NoteAssets::new(vec![requested_asset])?;
130 let payback_note = NoteDetails::new(payback_assets, payback_recipient);
131
132 Ok((note, payback_note))
133 }
134
135 pub fn build_tag(
148 note_type: NoteType,
149 offered_asset: &Asset,
150 requested_asset: &Asset,
151 ) -> NoteTag {
152 let swap_root_bytes = Self::script().root().as_bytes();
153 let mut swap_use_case_id = (swap_root_bytes[0] as u16) << 6;
156 swap_use_case_id |= (swap_root_bytes[1] >> 2) as u16;
157
158 let offered_asset_id: u64 = offered_asset.faucet_id_prefix().into();
160 let offered_asset_tag = (offered_asset_id >> 56) as u8;
161
162 let requested_asset_id: u64 = requested_asset.faucet_id_prefix().into();
163 let requested_asset_tag = (requested_asset_id >> 56) as u8;
164
165 let asset_pair = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16);
166
167 let tag = ((note_type as u8 as u32) << 30)
168 | ((swap_use_case_id as u32) << 16)
169 | asset_pair as u32;
170
171 NoteTag::new(tag)
172 }
173}
174
175#[cfg(test)]
179mod tests {
180 use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType};
181 use miden_protocol::asset::{FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails};
182 use miden_protocol::{self};
183
184 use super::*;
185
186 #[test]
187 fn swap_tag() {
188 let mut fungible_faucet_id_bytes = [0; 15];
190 fungible_faucet_id_bytes[0] = 0xcd;
191 fungible_faucet_id_bytes[1] = 0xb1;
192
193 let mut non_fungible_faucet_id_bytes = [0; 15];
195 non_fungible_faucet_id_bytes[0] = 0xab;
196 non_fungible_faucet_id_bytes[1] = 0xec;
197
198 let offered_asset = Asset::Fungible(
199 FungibleAsset::new(
200 AccountId::dummy(
201 fungible_faucet_id_bytes,
202 AccountIdVersion::Version0,
203 AccountType::FungibleFaucet,
204 AccountStorageMode::Public,
205 ),
206 2500,
207 )
208 .unwrap(),
209 );
210
211 let requested_asset = Asset::NonFungible(
212 NonFungibleAsset::new(
213 &NonFungibleAssetDetails::new(
214 AccountId::dummy(
215 non_fungible_faucet_id_bytes,
216 AccountIdVersion::Version0,
217 AccountType::NonFungibleFaucet,
218 AccountStorageMode::Public,
219 )
220 .prefix(),
221 vec![0xaa, 0xbb, 0xcc, 0xdd],
222 )
223 .unwrap(),
224 )
225 .unwrap(),
226 );
227
228 let expected_asset_pair = 0xcdab;
232
233 let note_type = NoteType::Public;
234 let actual_tag = SwapNote::build_tag(note_type, &offered_asset, &requested_asset);
235
236 assert_eq!(actual_tag.as_u32() as u16, expected_asset_pair, "asset pair should match");
237 assert_eq!((actual_tag.as_u32() >> 30) as u8, note_type as u8, "note type should match");
238 assert_eq!(
240 (actual_tag.as_u32() >> 22) as u8,
241 SwapNote::script_root().as_bytes()[0],
242 "swap script root byte 0 should match"
243 );
244 assert_eq!(
246 ((actual_tag.as_u32() & 0b00000000_00111111_00000000_00000000) >> 16) as u8,
247 SwapNote::script_root().as_bytes()[1] >> 2,
248 "swap script root byte 1 should match with the lower two bits set to zero"
249 );
250 }
251}