1#[cfg(feature = "std")]
2use std::{
3 fs::{self, File},
4 io::{self, Read},
5 path::Path,
6 vec::Vec,
7};
8
9use super::{Note, NoteDetails, NoteId, NoteInclusionProof, NoteTag};
10use crate::block::BlockNumber;
11#[cfg(feature = "std")]
12use crate::utils::serde::SliceReader;
13use crate::utils::serde::{
14 ByteReader,
15 ByteWriter,
16 Deserializable,
17 DeserializationError,
18 Serializable,
19};
20
21const MAGIC: &str = "note";
22
23#[derive(Clone, Debug, PartialEq, Eq)]
28#[allow(clippy::large_enum_variant)]
29pub enum NoteFile {
30 NoteId(NoteId),
32 NoteDetails {
41 details: NoteDetails,
42 after_block_num: BlockNumber,
43 tag: Option<NoteTag>,
44 },
45 NoteWithProof(Note, NoteInclusionProof),
47}
48
49#[cfg(feature = "std")]
50impl NoteFile {
51 pub fn write(&self, filepath: impl AsRef<Path>) -> io::Result<()> {
53 fs::write(filepath, self.to_bytes())
54 }
55
56 pub fn read(filepath: impl AsRef<Path>) -> io::Result<Self> {
58 let mut file = File::open(filepath)?;
59 let mut buffer = Vec::new();
60
61 file.read_to_end(&mut buffer)?;
62 let mut reader = SliceReader::new(&buffer);
63
64 Ok(NoteFile::read_from(&mut reader).map_err(|_| io::ErrorKind::InvalidData)?)
65 }
66}
67
68impl From<NoteDetails> for NoteFile {
69 fn from(details: NoteDetails) -> Self {
70 NoteFile::NoteDetails {
71 details,
72 after_block_num: 0.into(),
73 tag: None,
74 }
75 }
76}
77
78impl From<NoteId> for NoteFile {
79 fn from(note_id: NoteId) -> Self {
80 NoteFile::NoteId(note_id)
81 }
82}
83
84impl Serializable for NoteFile {
88 fn write_into<W: ByteWriter>(&self, target: &mut W) {
89 target.write_bytes(MAGIC.as_bytes());
90 match self {
91 NoteFile::NoteId(note_id) => {
92 target.write_u8(0);
93 note_id.write_into(target);
94 },
95 NoteFile::NoteDetails { details, after_block_num, tag } => {
96 target.write_u8(1);
97 details.write_into(target);
98 after_block_num.write_into(target);
99 tag.write_into(target);
100 },
101 NoteFile::NoteWithProof(note, proof) => {
102 target.write_u8(2);
103 note.write_into(target);
104 proof.write_into(target);
105 },
106 }
107 }
108}
109
110impl Deserializable for NoteFile {
111 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
112 let magic_value = source.read_string(4)?;
113 if magic_value != MAGIC {
114 return Err(DeserializationError::InvalidValue(format!(
115 "invalid note file marker: {magic_value}"
116 )));
117 }
118 match source.read_u8()? {
119 0 => Ok(NoteFile::NoteId(NoteId::read_from(source)?)),
120 1 => {
121 let details = NoteDetails::read_from(source)?;
122 let after_block_num = BlockNumber::read_from(source)?;
123 let tag = Option::<NoteTag>::read_from(source)?;
124 Ok(NoteFile::NoteDetails { details, after_block_num, tag })
125 },
126 2 => {
127 let note = Note::read_from(source)?;
128 let proof = NoteInclusionProof::read_from(source)?;
129 Ok(NoteFile::NoteWithProof(note, proof))
130 },
131 v => {
132 Err(DeserializationError::InvalidValue(format!("unknown variant {v} for NoteFile")))
133 },
134 }
135 }
136}
137
138#[cfg(test)]
142mod tests {
143 use alloc::vec::Vec;
144
145 use crate::Word;
146 use crate::account::AccountId;
147 use crate::asset::{Asset, FungibleAsset};
148 use crate::block::BlockNumber;
149 use crate::note::{
150 Note,
151 NoteAssets,
152 NoteFile,
153 NoteInclusionProof,
154 NoteRecipient,
155 NoteScript,
156 NoteStorage,
157 NoteTag,
158 NoteType,
159 PartialNoteMetadata,
160 };
161 use crate::testing::account_id::{
162 ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET,
163 ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE,
164 };
165 use crate::utils::serde::{Deserializable, Serializable};
166
167 fn create_example_note() -> Note {
168 let faucet = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap();
169 let target =
170 AccountId::try_from(ACCOUNT_ID_REGULAR_PRIVATE_ACCOUNT_UPDATABLE_CODE).unwrap();
171
172 let serial_num = Word::from([0, 1, 2, 3u32]);
173 let script = NoteScript::mock();
174 let note_storage = NoteStorage::new(vec![target.prefix().into()]).unwrap();
175 let recipient = NoteRecipient::new(serial_num, script, note_storage);
176
177 let asset = Asset::Fungible(FungibleAsset::new(faucet, 100).unwrap());
178 let metadata =
179 PartialNoteMetadata::new(faucet, NoteType::Public).with_tag(NoteTag::from(123));
180
181 Note::new(NoteAssets::new(vec![asset]).unwrap(), metadata, recipient)
182 }
183
184 #[test]
185 fn serialized_note_magic() {
186 let note = create_example_note();
187 let file = NoteFile::NoteId(note.id());
188 let mut buffer = Vec::new();
189 file.write_into(&mut buffer);
190
191 let magic_value = &buffer[..4];
192 assert_eq!(magic_value, b"note");
193 }
194
195 #[test]
196 fn serialize_id() {
197 let note = create_example_note();
198 let file = NoteFile::NoteId(note.id());
199 let mut buffer = Vec::new();
200 file.write_into(&mut buffer);
201
202 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
203
204 match file_copy {
205 NoteFile::NoteId(note_id) => {
206 assert_eq!(note.id(), note_id);
207 },
208 _ => panic!("Invalid note file variant"),
209 }
210 }
211
212 #[test]
213 fn serialize_details() {
214 let note = create_example_note();
215 let file = NoteFile::NoteDetails {
216 details: note.details.clone(),
217 after_block_num: 456.into(),
218 tag: Some(NoteTag::from(123)),
219 };
220 let mut buffer = Vec::new();
221 file.write_into(&mut buffer);
222
223 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
224
225 match file_copy {
226 NoteFile::NoteDetails { details, after_block_num, tag } => {
227 assert_eq!(details, note.details);
228 assert_eq!(after_block_num, 456.into());
229 assert_eq!(tag, Some(NoteTag::from(123)));
230 },
231 _ => panic!("Invalid note file variant"),
232 }
233 }
234
235 #[test]
236 fn serialize_with_proof() {
237 let note = create_example_note();
238 let mock_inclusion_proof =
239 NoteInclusionProof::new(BlockNumber::from(0), 0, Default::default()).unwrap();
240 let file = NoteFile::NoteWithProof(note.clone(), mock_inclusion_proof.clone());
241 let mut buffer = Vec::new();
242 file.write_into(&mut buffer);
243
244 let file_copy = NoteFile::read_from_bytes(&buffer).unwrap();
245
246 match file_copy {
247 NoteFile::NoteWithProof(note_copy, inclusion_proof_copy) => {
248 assert_eq!(note, note_copy);
249 assert_eq!(inclusion_proof_copy, mock_inclusion_proof);
250 },
251 _ => panic!("Invalid note file variant"),
252 }
253 }
254}