1use alloc::string::ToString;
2
3use super::execution_hint::NoteExecutionHint;
4use super::{
5 AccountId,
6 ByteReader,
7 ByteWriter,
8 Deserializable,
9 DeserializationError,
10 Felt,
11 NoteError,
12 NoteTag,
13 NoteType,
14 Serializable,
15 Word,
16};
17
18#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct NoteMetadata {
51 sender: AccountId,
53
54 note_type: NoteType,
56
57 tag: NoteTag,
59
60 aux: Felt,
62
63 execution_hint: NoteExecutionHint,
65}
66
67impl NoteMetadata {
68 pub fn new(
73 sender: AccountId,
74 note_type: NoteType,
75 tag: NoteTag,
76 execution_hint: NoteExecutionHint,
77 aux: Felt,
78 ) -> Result<Self, NoteError> {
79 let tag = tag.validate(note_type)?;
80 Ok(Self {
81 sender,
82 note_type,
83 tag,
84 aux,
85 execution_hint,
86 })
87 }
88
89 pub fn sender(&self) -> AccountId {
91 self.sender
92 }
93
94 pub fn note_type(&self) -> NoteType {
96 self.note_type
97 }
98
99 pub fn tag(&self) -> NoteTag {
101 self.tag
102 }
103
104 pub fn execution_hint(&self) -> NoteExecutionHint {
106 self.execution_hint
107 }
108
109 pub fn aux(&self) -> Felt {
111 self.aux
112 }
113
114 pub fn is_private(&self) -> bool {
116 self.note_type == NoteType::Private
117 }
118}
119
120impl From<NoteMetadata> for Word {
121 fn from(metadata: NoteMetadata) -> Self {
125 (&metadata).into()
126 }
127}
128
129impl From<&NoteMetadata> for Word {
130 fn from(metadata: &NoteMetadata) -> Self {
134 let mut elements = Word::empty();
135 elements[0] = metadata.sender.prefix().as_felt();
136 elements[1] = merge_id_type_and_hint_tag(
137 metadata.sender.suffix(),
138 metadata.note_type,
139 metadata.execution_hint,
140 );
141 elements[2] = merge_note_tag_and_hint_payload(metadata.execution_hint, metadata.tag);
142 elements[3] = metadata.aux;
143 elements
144 }
145}
146
147impl TryFrom<Word> for NoteMetadata {
148 type Error = NoteError;
149
150 fn try_from(elements: Word) -> Result<Self, Self::Error> {
154 let sender_id_prefix: Felt = elements[0];
155
156 let (sender_id_suffix, note_type, execution_hint_tag) =
157 unmerge_id_type_and_hint_tag(elements[1])?;
158
159 let sender = AccountId::try_from([sender_id_prefix, sender_id_suffix])
160 .map_err(NoteError::NoteSenderInvalidAccountId)?;
161
162 let (execution_hint, note_tag) =
163 unmerge_note_tag_and_hint_payload(elements[2], execution_hint_tag)?;
164
165 Self::new(sender, note_type, note_tag, execution_hint, elements[3])
166 }
167}
168
169impl Serializable for NoteMetadata {
173 fn write_into<W: ByteWriter>(&self, target: &mut W) {
174 Word::from(self).write_into(target);
175 }
176}
177
178impl Deserializable for NoteMetadata {
179 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
180 let word = Word::read_from(source)?;
181 Self::try_from(word).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
182 }
183}
184
185fn merge_id_type_and_hint_tag(
202 sender_id_suffix: Felt,
203 note_type: NoteType,
204 note_execution_hint: NoteExecutionHint,
205) -> Felt {
206 let mut merged = sender_id_suffix.as_int();
207
208 let type_bits = note_type as u8;
209 let (tag_bits, _) = note_execution_hint.into_parts();
210
211 debug_assert!(type_bits & 0b1111_1100 == 0, "note type must not contain values >= 4");
212 debug_assert!(
213 tag_bits & 0b1100_0000 == 0,
214 "note execution hint tag must not contain values >= 64"
215 );
216
217 merged |= (type_bits << 6) as u64;
220 merged |= tag_bits as u64;
221
222 Felt::try_from(merged).expect("encoded value should be a valid felt")
225}
226
227fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), NoteError> {
230 let element = element.as_int();
231
232 let least_significant_byte = element as u8;
234 let note_type_bits = (least_significant_byte & 0b1100_0000) >> 6;
235 let tag_bits = least_significant_byte & 0b0011_1111;
236
237 let note_type = NoteType::try_from(note_type_bits)?;
238
239 let element = element & 0xffff_ffff_ffff_ff00;
241
242 let sender_id_suffix = Felt::try_from(element).expect("element should still be valid");
245
246 Ok((sender_id_suffix, note_type, tag_bits))
247}
248
249fn merge_note_tag_and_hint_payload(
259 note_execution_hint: NoteExecutionHint,
260 note_tag: NoteTag,
261) -> Felt {
262 let (_, payload) = note_execution_hint.into_parts();
263 let note_tag: u32 = note_tag.into();
264
265 debug_assert_ne!(
266 payload,
267 u32::MAX,
268 "payload should never be u32::MAX as it would produce an invalid felt"
269 );
270
271 let felt_int = ((payload as u64) << 32) | (note_tag as u64);
272
273 Felt::try_from(felt_int).expect("bytes should be a valid felt")
276}
277
278fn unmerge_note_tag_and_hint_payload(
281 element: Felt,
282 note_execution_hint_tag: u8,
283) -> Result<(NoteExecutionHint, NoteTag), NoteError> {
284 let element = element.as_int();
285
286 let payload = (element >> 32) as u32;
287 let note_tag = (element & 0xffff_ffff) as u32;
288
289 let execution_hint = NoteExecutionHint::from_parts(note_execution_hint_tag, payload)?;
290 let note_tag = NoteTag::from(note_tag);
291
292 Ok((execution_hint, note_tag))
293}
294
295#[cfg(test)]
299mod tests {
300
301 use anyhow::Context;
302
303 use super::*;
304 use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
305
306 #[test]
307 fn note_metadata_serde() -> anyhow::Result<()> {
308 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
311 let note_type = NoteType::Public;
312 let tag = NoteTag::from_account_id(sender);
313 let aux = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap();
314
315 for execution_hint in [
316 NoteExecutionHint::always(),
317 NoteExecutionHint::none(),
318 NoteExecutionHint::on_block_slot(10, 11, 12),
319 NoteExecutionHint::after_block((u32::MAX - 1).into()).unwrap(),
320 ] {
321 let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux).unwrap();
322 NoteMetadata::read_from_bytes(&metadata.to_bytes())
323 .context(format!("failed for execution hint {execution_hint:?}"))?;
324 }
325
326 Ok(())
327 }
328
329 #[test]
330 fn merge_and_unmerge_id_type_and_hint() {
331 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
334 let sender_id_suffix = sender.suffix();
335
336 let note_type = NoteType::Public;
337 let note_execution_hint = NoteExecutionHint::OnBlockSlot {
338 round_len: 10,
339 slot_len: 11,
340 slot_offset: 12,
341 };
342
343 let merged_value =
344 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
345 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
346 unmerge_id_type_and_hint_tag(merged_value).unwrap();
347
348 assert_eq!(note_type, extracted_note_type);
349 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
350 assert_eq!(sender_id_suffix, extracted_suffix);
351
352 let note_type = NoteType::Private;
353 let note_execution_hint = NoteExecutionHint::Always;
354
355 let merged_value =
356 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
357 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
358 unmerge_id_type_and_hint_tag(merged_value).unwrap();
359
360 assert_eq!(note_type, extracted_note_type);
361 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
362 assert_eq!(sender_id_suffix, extracted_suffix);
363
364 let note_type = NoteType::Private;
365 let note_execution_hint = NoteExecutionHint::None;
366
367 let merged_value =
368 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
369 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
370 unmerge_id_type_and_hint_tag(merged_value).unwrap();
371
372 assert_eq!(note_type, extracted_note_type);
373 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
374 assert_eq!(sender_id_suffix, extracted_suffix);
375 }
376}