miden_objects/note/
file.rs

1#[cfg(feature = "std")]
2use std::{
3    fs::{self, File},
4    io::{self, Read},
5    path::Path,
6    vec::Vec,
7};
8
9#[cfg(feature = "std")]
10use miden_core::utils::SliceReader;
11use miden_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
12use miden_processor::DeserializationError;
13
14use super::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag};
15use crate::block::BlockNumber;
16
17const MAGIC: &str = "note";
18
19// NOTE FILE
20// ================================================================================================
21
22/// A serialized representation of a note.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum NoteFile {
25    /// The note's details aren't known.
26    NoteId(NoteId),
27    /// The note may or may not have already been recorded on chain.
28    ///
29    /// The `after_block_num` specifies the block after which the note is expected to appear on
30    /// chain. Though this should be treated as a hint (i.e., there is no guarantee that the note
31    /// will appear on chain or that it will in fact appear after the specified block).
32    ///
33    /// An optional tag specifies the tag associated with the note, though this also should be
34    /// treated as a hint.
35    NoteDetails {
36        details: NoteDetails,
37        after_block_num: BlockNumber,
38        tag: Option<NoteTag>,
39    },
40    /// The note has been recorded on chain.
41    NoteWithProof(Note, NoteInclusionProof),
42}
43
44#[cfg(feature = "std")]
45impl NoteFile {
46    /// Serializes and writes binary [NoteFile] to specified file
47    pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
48        fs::write(filepath, self.to_bytes())
49    }
50
51    /// Reads from file and tries to deserialize an [NoteFile]
52    pub fn read(filepath: impl AsRef<Path>) -> io::Result<Self> {
53        let mut file = File::open(filepath)?;
54        let mut buffer = Vec::new();
55
56        file.read_to_end(&mut buffer)?;
57        let mut reader = SliceReader::new(&buffer);
58
59        Ok(NoteFile::read_from(&mut reader).map_err(|_| io::ErrorKind::InvalidData)?)
60    }
61}
62
63impl From<NoteDetails> for NoteFile {
64    fn from(details: NoteDetails) -> Self {
65        NoteFile::NoteDetails {
66            details,
67            after_block_num: 0.into(),
68            tag: None,
69        }
70    }
71}
72
73impl From<NoteId> for NoteFile {
74    fn from(note_id: NoteId) -> Self {
75        NoteFile::NoteId(note_id)
76    }
77}
78
79// SERIALIZATION
80// ================================================================================================
81
82impl Serializable for NoteFile {
83    fn write_into<W: ByteWriter>(&self, target: &mut W) {
84        target.write_bytes(MAGIC.as_bytes());
85        match self {
86            NoteFile::NoteId(note_id) => {
87                target.write_u8(0);
88                note_id.write_into(target);
89            },
90            NoteFile::NoteDetails { details, after_block_num, tag } => {
91                target.write_u8(1);
92                details.write_into(target);
93                after_block_num.write_into(target);
94                tag.write_into(target);
95            },
96            NoteFile::NoteWithProof(note, proof) => {
97                target.write_u8(2);
98                note.write_into(target);
99                proof.write_into(target);
100            },
101        }
102    }
103}
104
105impl Deserializable for NoteFile {
106    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
107        let magic_value = source.read_string(4)?;
108        if magic_value != MAGIC {
109            return Err(DeserializationError::InvalidValue(format!(
110                "invalid note file marker: {magic_value}"
111            )));
112        }
113        match source.read_u8()? {
114            0 => Ok(NoteFile::NoteId(NoteId::read_from(source)?)),
115            1 => {
116                let details = NoteDetails::read_from(source)?;
117                let after_block_num = BlockNumber::read_from(source)?;
118                let tag = Option::<NoteTag>::read_from(source)?;
119                Ok(NoteFile::NoteDetails { details, after_block_num, tag })
120            },
121            2 => {
122                let note = Note::read_from(source)?;
123                let proof = NoteInclusionProof::read_from(source)?;
124                Ok(NoteFile::NoteWithProof(note, proof))
125            },
126            v => {
127                Err(DeserializationError::InvalidValue(format!("unknown variant {v} for NoteFile")))
128            },
129        }
130    }
131}
132
133// TESTS
134// ================================================================================================
135
136#[cfg(test)]
137mod tests {
138    use alloc::vec::Vec;
139
140    use miden_core::Felt;
141    use miden_core::utils::{Deserializable, Serializable};
142
143    use crate::Word;
144    use crate::account::AccountId;
145    use crate::asset::{Asset, FungibleAsset};
146    use crate::block::BlockNumber;
147    use crate::note::{
148        Note,
149        NoteAssets,
150        NoteFile,
151        NoteInclusionProof,
152        NoteInputs,
153        NoteMetadata,
154        NoteRecipient,
155        NoteScript,
156        NoteTag,
157        NoteType,
158    };
159    use crate::testing::account_id::{
160        ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
161        ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
162    };
163
164    fn create_example_note() -> Note {
165        let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
166        let target =
167            AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
168
169        let serial_num = Word::from([0, 1, 2, 3u32]);
170        let script = NoteScript::mock();
171        let note_inputs = NoteInputs::new(vec![target.prefix().into()]).unwrap();
172        let recipient = NoteRecipient::new(serial_num, script, note_inputs);
173
174        let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap());
175        let metadata = NoteMetadata::new(
176            faucet,
177            NoteType::Public,
178            NoteTag::from(123),
179            crate::note::NoteExecutionHint::None,
180            Felt::new(0),
181        )
182        .unwrap();
183
184        Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient)
185    }
186
187    #[test]
188    fn serialized_note_magic() {
189        let note = create_example_note();
190        let file = NoteFile::NoteId(note.id());
191        let mut buffer = Vec::new();
192        file.write_into(&mut buffer);
193
194        let magic_value = &buffer[..4];
195        assert_eq!(magic_value, b"note");
196    }
197
198    #[test]
199    fn serialize_id() {
200        let note = create_example_note();
201        let file = NoteFile::NoteId(note.id());
202        let mut buffer = Vec::new();
203        file.write_into(&mut buffer);
204
205        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
206
207        match file_copy {
208            NoteFile::NoteId(note_id) => {
209                assert_eq!(note.id(), note_id);
210            },
211            _ => panic!("Invalid note file variant"),
212        }
213    }
214
215    #[test]
216    fn serialize_details() {
217        let note = create_example_note();
218        let file = NoteFile::NoteDetails {
219            details: note.details.clone(),
220            after_block_num: 456.into(),
221            tag: Some(NoteTag::from(123)),
222        };
223        let mut buffer = Vec::new();
224        file.write_into(&mut buffer);
225
226        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
227
228        match file_copy {
229            NoteFile::NoteDetails { details, after_block_num, tag } => {
230                assert_eq!(details, note.details);
231                assert_eq!(after_block_num, 456.into());
232                assert_eq!(tag, Some(NoteTag::from(123)));
233            },
234            _ => panic!("Invalid note file variant"),
235        }
236    }
237
238    #[test]
239    fn serialize_with_proof() {
240        let note = create_example_note();
241        let mock_inclusion_proof =
242            NoteInclusionProof::new(BlockNumber::from(0), 0, Default::default()).unwrap();
243        let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone());
244        let mut buffer = Vec::new();
245        file.write_into(&mut buffer);
246
247        let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
248
249        match file_copy {
250            NoteFile::NoteWithProof(note_copy, inclusion_proof_copy) => {
251                assert_eq!(note, note_copy);
252                assert_eq!(inclusion_proof_copy, mock_inclusion_proof);
253            },
254            _ => panic!("Invalid note file variant"),
255        }
256    }
257}