use alloc::string::ToString;
use super::execution_hint::NoteExecutionHint;
use super::{
AccountId,
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Felt,
NoteError,
NoteTag,
NoteType,
Serializable,
Word,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct NoteMetadata {
sender: AccountId,
note_type: NoteType,
tag: NoteTag,
aux: Felt,
execution_hint: NoteExecutionHint,
}
impl NoteMetadata {
pub fn new(
sender: AccountId,
note_type: NoteType,
tag: NoteTag,
execution_hint: NoteExecutionHint,
aux: Felt,
) -> Result<Self, NoteError> {
let tag = tag.validate(note_type)?;
Ok(Self {
sender,
note_type,
tag,
aux,
execution_hint,
})
}
pub fn sender(&self) -> AccountId {
self.sender
}
pub fn note_type(&self) -> NoteType {
self.note_type
}
pub fn tag(&self) -> NoteTag {
self.tag
}
pub fn execution_hint(&self) -> NoteExecutionHint {
self.execution_hint
}
pub fn aux(&self) -> Felt {
self.aux
}
pub fn is_private(&self) -> bool {
self.note_type == NoteType::Private
}
}
impl From<NoteMetadata> for Word {
fn from(metadata: NoteMetadata) -> Self {
(&metadata).into()
}
}
impl From<&NoteMetadata> for Word {
fn from(metadata: &NoteMetadata) -> Self {
let mut elements = Word::empty();
elements[0] = metadata.sender.prefix().as_felt();
elements[1] = merge_id_type_and_hint_tag(
metadata.sender.suffix(),
metadata.note_type,
metadata.execution_hint,
);
elements[2] = merge_note_tag_and_hint_payload(metadata.execution_hint, metadata.tag);
elements[3] = metadata.aux;
elements
}
}
impl TryFrom<Word> for NoteMetadata {
type Error = NoteError;
fn try_from(elements: Word) -> Result<Self, Self::Error> {
let sender_id_prefix: Felt = elements[0];
let (sender_id_suffix, note_type, execution_hint_tag) =
unmerge_id_type_and_hint_tag(elements[1])?;
let sender = AccountId::try_from([sender_id_prefix, sender_id_suffix])
.map_err(NoteError::NoteSenderInvalidAccountId)?;
let (execution_hint, note_tag) =
unmerge_note_tag_and_hint_payload(elements[2], execution_hint_tag)?;
Self::new(sender, note_type, note_tag, execution_hint, elements[3])
}
}
impl Serializable for NoteMetadata {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
Word::from(self).write_into(target);
}
}
impl Deserializable for NoteMetadata {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let word = Word::read_from(source)?;
Self::try_from(word).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
fn merge_id_type_and_hint_tag(
sender_id_suffix: Felt,
note_type: NoteType,
note_execution_hint: NoteExecutionHint,
) -> Felt {
let mut merged = sender_id_suffix.as_int();
let type_bits = note_type as u8;
let (tag_bits, _) = note_execution_hint.into_parts();
debug_assert!(type_bits & 0b1111_1100 == 0, "note type must not contain values >= 4");
debug_assert!(
tag_bits & 0b1100_0000 == 0,
"note execution hint tag must not contain values >= 64"
);
merged |= (type_bits << 6) as u64;
merged |= tag_bits as u64;
Felt::try_from(merged).expect("encoded value should be a valid felt")
}
fn unmerge_id_type_and_hint_tag(element: Felt) -> Result<(Felt, NoteType, u8), NoteError> {
let element = element.as_int();
let least_significant_byte = element as u8;
let note_type_bits = (least_significant_byte & 0b1100_0000) >> 6;
let tag_bits = least_significant_byte & 0b0011_1111;
let note_type = NoteType::try_from(note_type_bits)?;
let element = element & 0xffff_ffff_ffff_ff00;
let sender_id_suffix = Felt::try_from(element).expect("element should still be valid");
Ok((sender_id_suffix, note_type, tag_bits))
}
fn merge_note_tag_and_hint_payload(
note_execution_hint: NoteExecutionHint,
note_tag: NoteTag,
) -> Felt {
let (_, payload) = note_execution_hint.into_parts();
let note_tag: u32 = note_tag.into();
debug_assert_ne!(
payload,
u32::MAX,
"payload should never be u32::MAX as it would produce an invalid felt"
);
let felt_int = ((payload as u64) << 32) | (note_tag as u64);
Felt::try_from(felt_int).expect("bytes should be a valid felt")
}
fn unmerge_note_tag_and_hint_payload(
element: Felt,
note_execution_hint_tag: u8,
) -> Result<(NoteExecutionHint, NoteTag), NoteError> {
let element = element.as_int();
let payload = (element >> 32) as u32;
let note_tag = (element & 0xffff_ffff) as u32;
let execution_hint = NoteExecutionHint::from_parts(note_execution_hint_tag, payload)?;
let note_tag = NoteTag::from(note_tag);
Ok((execution_hint, note_tag))
}
#[cfg(test)]
mod tests {
use anyhow::Context;
use super::*;
use crate::testing::account_id::ACCOUNT_ID_MAX_ONES;
#[test]
fn note_metadata_serde() -> anyhow::Result<()> {
let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
let note_type = NoteType::Public;
let tag = NoteTag::from_account_id(sender);
let aux = Felt::try_from(0xffff_ffff_0000_0000u64).unwrap();
for execution_hint in [
NoteExecutionHint::always(),
NoteExecutionHint::none(),
NoteExecutionHint::on_block_slot(10, 11, 12),
NoteExecutionHint::after_block((u32::MAX - 1).into()).unwrap(),
] {
let metadata = NoteMetadata::new(sender, note_type, tag, execution_hint, aux).unwrap();
NoteMetadata::read_from_bytes(&metadata.to_bytes())
.context(format!("failed for execution hint {execution_hint:?}"))?;
}
Ok(())
}
#[test]
fn merge_and_unmerge_id_type_and_hint() {
let sender = AccountId::try_from(ACCOUNT_ID_MAX_ONES).unwrap();
let sender_id_suffix = sender.suffix();
let note_type = NoteType::Public;
let note_execution_hint = NoteExecutionHint::OnBlockSlot {
round_len: 10,
slot_len: 11,
slot_offset: 12,
};
let merged_value =
merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
unmerge_id_type_and_hint_tag(merged_value).unwrap();
assert_eq!(note_type, extracted_note_type);
assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
assert_eq!(sender_id_suffix, extracted_suffix);
let note_type = NoteType::Private;
let note_execution_hint = NoteExecutionHint::Always;
let merged_value =
merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
unmerge_id_type_and_hint_tag(merged_value).unwrap();
assert_eq!(note_type, extracted_note_type);
assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
assert_eq!(sender_id_suffix, extracted_suffix);
let note_type = NoteType::Private;
let note_execution_hint = NoteExecutionHint::None;
let merged_value =
merge_id_type_and_hint_tag(sender_id_suffix, note_type, note_execution_hint);
let (extracted_suffix, extracted_note_type, extracted_note_execution_hint_tag) =
unmerge_id_type_and_hint_tag(merged_value).unwrap();
assert_eq!(note_type, extracted_note_type);
assert_eq!(note_execution_hint.into_parts().0, extracted_note_execution_hint_tag);
assert_eq!(sender_id_suffix, extracted_suffix);
}
}