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#[derive(Clone, Debug, PartialEq, Eq)]
24pub enum NoteFile {
25 NoteId(NoteId),
27 NoteDetails {
36 details: NoteDetails,
37 after_block_num: BlockNumber,
38 tag: Option<NoteTag>,
39 },
40 NoteWithProof(Note, NoteInclusionProof),
42}
43
44#[cfg(feature = "std")]
45impl NoteFile {
46 pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
48 fs::write(filepath, self.to_bytes())
49 }
50
51 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
79impl 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#[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}