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 vm_core::utils::SliceReader;
11use vm_core::utils::{ByteReader, ByteWriter, Deserializable, Serializable};
12use vm_processor::DeserializationError;
13
14use super::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag};
15use crate::block::BlockNumber;
16
17const MAGIC: &str = "note";
18
19pub enum NoteFile {
24 NoteId(NoteId),
26 NoteDetails {
35 details: NoteDetails,
36 after_block_num: BlockNumber,
37 tag: Option<NoteTag>,
38 },
39 NoteWithProof(Note, NoteInclusionProof),
41}
42
43#[cfg(feature = "std")]
44impl NoteFile {
45 pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
47 fs::write(filepath, self.to_bytes())
48 }
49
50 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
78impl 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#[cfg(test)]
136mod tests {
137 use alloc::vec::Vec;
138
139 use vm_core::{
140 Felt,
141 utils::{Deserializable, Serializable},
142 };
143
144 use crate::{
145 account::AccountId,
146 asset::{Asset, FungibleAsset},
147 block::BlockNumber,
148 note::{
149 Note, NoteAssets, NoteFile, NoteInclusionProof, NoteInputs, NoteMetadata,
150 NoteRecipient, NoteScript, NoteTag, NoteType,
151 },
152 testing::account_id::{
153 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
154 },
155 };
156
157 fn create_example_note() -> Note {
158 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
159 let target =
160 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
161
162 let serial_num = [Felt::new(0), Felt::new(1), Felt::new(2), Felt::new(3)];
163 let script = NoteScript::mock();
164 let note_inputs = NoteInputs::new(vec![target.prefix().into()]).unwrap();
165 let recipient = NoteRecipient::new(serial_num, script, note_inputs);
166
167 let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap());
168 let metadata = NoteMetadata::new(
169 faucet,
170 NoteType::Public,
171 NoteTag::from(123),
172 crate::note::NoteExecutionHint::None,
173 Felt::new(0),
174 )
175 .unwrap();
176
177 Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient)
178 }
179
180 #[test]
181 fn serialized_note_magic() {
182 let note = create_example_note();
183 let file = NoteFile::NoteId(note.id());
184 let mut buffer = Vec::new();
185 file.write_into(&mut buffer);
186
187 let magic_value = &buffer[..4];
188 assert_eq!(magic_value, b"note");
189 }
190
191 #[test]
192 fn serialize_id() {
193 let note = create_example_note();
194 let file = NoteFile::NoteId(note.id());
195 let mut buffer = Vec::new();
196 file.write_into(&mut buffer);
197
198 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
199
200 match file_copy {
201 NoteFile::NoteId(note_id) => {
202 assert_eq!(note.id(), note_id);
203 },
204 _ => panic!("Invalid note file variant"),
205 }
206 }
207
208 #[test]
209 fn serialize_details() {
210 let note = create_example_note();
211 let file = NoteFile::NoteDetails {
212 details: note.details.clone(),
213 after_block_num: 456.into(),
214 tag: Some(NoteTag::from(123)),
215 };
216 let mut buffer = Vec::new();
217 file.write_into(&mut buffer);
218
219 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
220
221 match file_copy {
222 NoteFile::NoteDetails { details, after_block_num, tag } => {
223 assert_eq!(details, note.details);
224 assert_eq!(after_block_num, 456.into());
225 assert_eq!(tag, Some(NoteTag::from(123)));
226 },
227 _ => panic!("Invalid note file variant"),
228 }
229 }
230
231 #[test]
232 fn serialize_with_proof() {
233 let note = create_example_note();
234 let mock_inclusion_proof =
235 NoteInclusionProof::new(BlockNumber::from(0), 0, Default::default()).unwrap();
236 let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone());
237 let mut buffer = Vec::new();
238 file.write_into(&mut buffer);
239
240 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
241
242 match file_copy {
243 NoteFile::NoteWithProof(note_copy, inclusion_proof_copy) => {
244 assert_eq!(note, note_copy);
245 assert_eq!(inclusion_proof_copy, mock_inclusion_proof);
246 },
247 _ => panic!("Invalid note file variant"),
248 }
249 }
250}