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