1use alloc::string::ToString;
2
3use super::{
4 execution_hint::NoteExecutionHint, AccountId, ByteReader, ByteWriter, Deserializable,
5 DeserializationError, Felt, NoteError, NoteTag, NoteType, Serializable, Word,
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
40pub struct NoteMetadata {
41 sender: AccountId,
43
44 note_type: NoteType,
46
47 tag: NoteTag,
49
50 aux: Felt,
52
53 execution_hint: NoteExecutionHint,
55}
56
57impl NoteMetadata {
58 pub fn new(
63 sender: AccountId,
64 note_type: NoteType,
65 tag: NoteTag,
66 execution_hint: NoteExecutionHint,
67 aux: Felt,
68 ) -> Result<Self, NoteError> {
69 let tag = tag.validate(note_type)?;
70 Ok(Self {
71 sender,
72 note_type,
73 tag,
74 aux,
75 execution_hint,
76 })
77 }
78
79 pub fn sender(&self) -> AccountId {
81 self.sender
82 }
83
84 pub fn note_type(&self) -> NoteType {
86 self.note_type
87 }
88
89 pub fn tag(&self) -> NoteTag {
91 self.tag
92 }
93
94 pub fn execution_hint(&self) -> NoteExecutionHint {
96 self.execution_hint
97 }
98
99 pub fn aux(&self) -> Felt {
101 self.aux
102 }
103
104 pub fn is_private(&self) -> bool {
106 self.note_type == NoteType::Private
107 }
108}
109
110impl From<NoteMetadata> for Word {
111 fn from(metadata: NoteMetadata) -> Self {
115 (&metadata).into()
116 }
117}
118
119impl From<&NoteMetadata> for Word {
120 fn from(metadata: &NoteMetadata) -> Self {
124 let mut elements = Word::default();
125 elements[0] = metadata.sender.prefix().as_felt();
126 elements[1] = merge_id_type_and_hint_tag(
127 metadata.sender.suffix(),
128 metadata.note_type,
129 metadata.execution_hint,
130 );
131 elements[2] = merge_note_tag_and_hint_payload(metadata.execution_hint, metadata.tag);
132 elements[3] = metadata.aux;
133 elements
134 }
135}
136
137impl TryFrom<Word> for NoteMetadata {
138 type Error = NoteError;
139
140 fn try_from(elements: Word) -> Result<Self, Self::Error> {
144 let sender_id_prefix: Felt = elements[0];
145
146 let (sender_id_suffix, note_type, execution_hint_tag) =
147 unmerge_id_type_and_hint_tag(elements[1])?;
148
149 let sender = AccountId::try_from([sender_id_prefix, sender_id_suffix])
150 .map_err(NoteError::NoteSenderInvalidAccountId)?;
151
152 let (execution_hint, note_tag) =
153 unmerge_note_tag_and_hint_payload(elements[2], execution_hint_tag)?;
154
155 Self::new(sender, note_type, note_tag, execution_hint, elements[3])
156 }
157}
158
159impl Serializable for NoteMetadata {
163 fn write_into<W: ByteWriter>(&self, target: &mut W) {
164 Word::from(self).write_into(target);
165 }
166}
167
168impl Deserializable for NoteMetadata {
169 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
170 let word = Word::read_from(source)?;
171 Self::try_from(word).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
172 }
173}
174
175fn merge_id_type_and_hint_tag(
192 sender_id_suffix: Felt,
193 note_type: NoteType,
194 note_execution_hint: NoteExecutionHint,
195) -> Felt {
196 let mut merged = sender_id_suffix.as_int();
197
198 let type_bits = note_type as u8;
199 let (tag_bits, _) = note_execution_hint.into_parts();
200
201 debug_assert!(type_bits & 0b1111_1100 == 0, "note type must not contain values >= 4");
202 debug_assert!(
203 tag_bits & 0b1100_0000 == 0,
204 "note execution hint tag must not contain values >= 64"
205 );
206
207 merged |= (type_bits << 6) as u64;
210 merged |= tag_bits as u64;
211
212 Felt::try_from(merged).expect("encoded value should be a valid felt")
215}
216
217fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), NoteError> {
220 let element = element.as_int();
221
222 let least_significant_byte = element as u8;
224 let note_type_bits = (least_significant_byte & 0b1100_0000) >> 6;
225 let tag_bits = least_significant_byte & 0b0011_1111;
226
227 let note_type = NoteType::try_from(note_type_bits)?;
228
229 let element = element & 0xffff_ffff_ffff_ff00;
231
232 let sender_id_suffix = Felt::try_from(element).expect("element should still be valid");
235
236 Ok((sender_id_suffix, note_type, tag_bits))
237}
238
239fn merge_note_tag_and_hint_payload(
249 note_execution_hint: NoteExecutionHint,
250 note_tag: NoteTag,
251) -> Felt {
252 let (_, payload) = note_execution_hint.into_parts();
253 let note_tag: u32 = note_tag.into();
254
255 debug_assert_ne!(
256 payload,
257 u32::MAX,
258 "payload should never be u32::MAX as it would produce an invalid felt"
259 );
260
261 let felt_int = ((payload as u64) << 32) | (note_tag as u64);
262
263 Felt::try_from(felt_int).expect("bytes should be a valid felt")
266}
267
268fn unmerge_note_tag_and_hint_payload(
271 element: Felt,
272 note_execution_hint_tag: u8,
273) -> Result<(NoteExecutionHint, NoteTag), NoteError> {
274 let element = element.as_int();
275
276 let payload = (element >> 32) as u32;
277 let note_tag = (element & 0xffff_ffff) as u32;
278
279 let execution_hint = NoteExecutionHint::from_parts(note_execution_hint_tag, payload)?;
280 let note_tag = NoteTag::from(note_tag);
281
282 Ok((execution_hint, note_tag))
283}
284
285#[cfg(test)]
289mod tests {
290
291 use anyhow::Context;
292
293 use super::*;
294 use crate::{note::NoteExecutionMode, testing::account_id::ACCOUNT_ID_MAX_ONES};
295
296 #[test]
297 fn note_metadata_serde() -> anyhow::Result<()> {
298 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
301 let note_type = NoteType::Public;
302 let tag = NoteTag::from_account_id(sender, NoteExecutionMode::Local).unwrap();
303 let aux = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap();
304
305 for execution_hint in [
306 NoteExecutionHint::always(),
307 NoteExecutionHint::none(),
308 NoteExecutionHint::on_block_slot(10, 11, 12),
309 NoteExecutionHint::after_block((u32::MAX - 1).into()).unwrap(),
310 ] {
311 let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux).unwrap();
312 NoteMetadata::read_from_bytes(&metadata.to_bytes())
313 .context(format!("failed for execution hint {execution_hint:?}"))?;
314 }
315
316 Ok(())
317 }
318
319 #[test]
320 fn merge_and_unmerge_id_type_and_hint() {
321 let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
324 let sender_id_suffix = sender.suffix();
325
326 let note_type = NoteType::Public;
327 let note_execution_hint = NoteExecutionHint::OnBlockSlot {
328 round_len: 10,
329 slot_len: 11,
330 slot_offset: 12,
331 };
332
333 let merged_value =
334 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
335 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
336 unmerge_id_type_and_hint_tag(merged_value).unwrap();
337
338 assert_eq!(note_type, extracted_note_type);
339 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
340 assert_eq!(sender_id_suffix, extracted_suffix);
341
342 let note_type = NoteType::Private;
343 let note_execution_hint = NoteExecutionHint::Always;
344
345 let merged_value =
346 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
347 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
348 unmerge_id_type_and_hint_tag(merged_value).unwrap();
349
350 assert_eq!(note_type, extracted_note_type);
351 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
352 assert_eq!(sender_id_suffix, extracted_suffix);
353
354 let note_type = NoteType::Private;
355 let note_execution_hint = NoteExecutionHint::None;
356
357 let merged_value =
358 merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
359 let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
360 unmerge_id_type_and_hint_tag(merged_value).unwrap();
361
362 assert_eq!(note_type, extracted_note_type);
363 assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
364 assert_eq!(sender_id_suffix, extracted_suffix);
365 }
366}