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}