miden_protocol/note/
script.rs1use alloc::string::{String, ToString};
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::fmt::Display;
5use core::num::TryFromIntError;
6
7use miden_core::mast::MastNodeExt;
8use miden_crypto_derive::WordWrapper;
9use miden_mast_package::Package;
10
11use super::Felt;
12use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId};
13use crate::assembly::{Library, Path};
14use crate::errors::NoteError;
15use crate::utils::serde::{
16 ByteReader,
17 ByteWriter,
18 Deserializable,
19 DeserializationError,
20 Serializable,
21};
22use crate::vm::{AdviceMap, Program};
23use crate::{PrettyPrint, Word};
24
25const NOTE_SCRIPT_ATTRIBUTE: &str = "note_script";
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
33pub struct NoteScriptRoot(Word);
34
35impl From<NoteScriptRoot> for Word {
36 fn from(root: NoteScriptRoot) -> Self {
37 root.0
38 }
39}
40
41impl Display for NoteScriptRoot {
42 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43 Display::fmt(&self.0, f)
44 }
45}
46
47impl Serializable for NoteScriptRoot {
48 fn write_into<W: ByteWriter>(&self, target: &mut W) {
49 target.write(self.0);
50 }
51
52 fn get_size_hint(&self) -> usize {
53 self.0.get_size_hint()
54 }
55}
56
57impl Deserializable for NoteScriptRoot {
58 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
59 let word: Word = source.read()?;
60 Ok(Self::from_raw(word))
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct NoteScript {
73 mast: Arc<MastForest>,
74 entrypoint: MastNodeId,
75}
76
77impl NoteScript {
78 pub fn new(code: Program) -> Self {
87 Self {
88 entrypoint: code.entrypoint(),
89 mast: code.mast_forest().clone(),
90 }
91 }
92
93 pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
98 Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
99 }
100
101 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
106 assert!(mast.get_node_by_id(entrypoint).is_some());
107 Self { mast, entrypoint }
108 }
109
110 pub fn from_library(library: &Library) -> Result<Self, NoteError> {
120 let mut entrypoint = None;
121
122 for export in library.exports() {
123 if let Some(proc_export) = export.as_procedure() {
124 if proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
126 if entrypoint.is_some() {
127 return Err(NoteError::NoteScriptMultipleProceduresWithAttribute);
128 }
129 entrypoint = Some(proc_export.node);
130 }
131 }
132 }
133
134 let entrypoint = entrypoint.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?;
135
136 Ok(Self {
137 mast: library.mast_forest().clone(),
138 entrypoint,
139 })
140 }
141
142 pub fn from_library_reference(library: &Library, path: &Path) -> Result<Self, NoteError> {
160 let export = library
162 .exports()
163 .find(|e| e.path().as_ref() == path)
164 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
165
166 let proc_export = export
168 .as_procedure()
169 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
170
171 if !proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
172 return Err(NoteError::NoteScriptProcedureMissingAttribute(path.to_string().into()));
173 }
174
175 let digest = library.mast_forest()[proc_export.node].digest();
177
178 let (mast, entrypoint) = create_external_node_forest(digest);
180
181 Ok(Self { mast: Arc::new(mast), entrypoint })
182 }
183
184 pub fn from_package(package: &Package) -> Result<Self, NoteError> {
194 Ok(NoteScript::from_library(&package.mast))?
195 }
196
197 pub fn root(&self) -> NoteScriptRoot {
202 NoteScriptRoot::from_raw(self.mast[self.entrypoint].digest())
203 }
204
205 pub fn mast(&self) -> Arc<MastForest> {
207 self.mast.clone()
208 }
209
210 pub fn entrypoint(&self) -> MastNodeId {
212 self.entrypoint
213 }
214
215 pub fn clear_debug_info(&mut self) {
220 let mut mast = self.mast.clone();
221 Arc::make_mut(&mut mast).clear_debug_info();
222 self.mast = mast;
223 }
224
225 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
231 if advice_map.is_empty() {
232 return self;
233 }
234
235 let mut mast = (*self.mast).clone();
236 mast.advice_map_mut().extend(advice_map);
237 Self {
238 mast: Arc::new(mast),
239 entrypoint: self.entrypoint,
240 }
241 }
242}
243
244impl From<&NoteScript> for Vec<Felt> {
248 fn from(script: &NoteScript) -> Self {
249 let mut bytes = script.mast.to_bytes();
250 let len = bytes.len();
251
252 let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
254 bytes.resize(bytes.len() + missing, 0);
255
256 let final_size = 2 + bytes.len();
257 let mut result = Vec::with_capacity(final_size);
258
259 result.push(Felt::from(u32::from(script.entrypoint)));
261 result.push(Felt::new_unchecked(len as u64));
262
263 let mut encoded: &[u8] = &bytes;
265 while encoded.len() >= 4 {
266 let (data, rest) =
267 encoded.split_first_chunk::<4>().expect("The length has been checked");
268 let number = u32::from_le_bytes(*data);
269 result.push(Felt::from(number));
270
271 encoded = rest;
272 }
273
274 result
275 }
276}
277
278impl From<NoteScript> for Vec<Felt> {
279 fn from(value: NoteScript) -> Self {
280 (&value).into()
281 }
282}
283
284impl AsRef<NoteScript> for NoteScript {
285 fn as_ref(&self) -> &NoteScript {
286 self
287 }
288}
289
290impl TryFrom<&[Felt]> for NoteScript {
294 type Error = DeserializationError;
295
296 fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
297 if elements.len() < 2 {
298 return Err(DeserializationError::UnexpectedEOF);
299 }
300
301 let entrypoint: u32 = elements[0]
302 .as_canonical_u64()
303 .try_into()
304 .map_err(|err: TryFromIntError| DeserializationError::InvalidValue(err.to_string()))?;
305 let len = elements[1].as_canonical_u64();
306 let mut data = Vec::with_capacity(elements.len() * 4);
307
308 for &felt in &elements[2..] {
309 let element: u32 =
310 felt.as_canonical_u64().try_into().map_err(|err: TryFromIntError| {
311 DeserializationError::InvalidValue(err.to_string())
312 })?;
313 data.extend(element.to_le_bytes())
314 }
315 data.shrink_to(len as usize);
316
317 let mast = MastForest::read_from_bytes(&data)?;
319 let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
320 Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
321 }
322}
323
324impl TryFrom<Vec<Felt>> for NoteScript {
325 type Error = DeserializationError;
326
327 fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
328 value.as_slice().try_into()
329 }
330}
331
332impl Serializable for NoteScript {
336 fn write_into<W: ByteWriter>(&self, target: &mut W) {
337 self.mast.write_into(target);
338 target.write_u32(u32::from(self.entrypoint));
339 }
340
341 fn get_size_hint(&self) -> usize {
342 let mast_size = self.mast.to_bytes().len();
346 let u32_size = 0u32.get_size_hint();
347
348 mast_size + u32_size
349 }
350}
351
352impl Deserializable for NoteScript {
353 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
354 let mast = MastForest::read_from(source)?;
355 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
356
357 Ok(Self::from_parts(Arc::new(mast), entrypoint))
358 }
359}
360
361impl PrettyPrint for NoteScript {
365 fn render(&self) -> miden_core::prettier::Document {
366 use miden_core::prettier::*;
367 let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
368
369 indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
370 }
371}
372
373impl Display for NoteScript {
374 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 self.pretty_print(f)
376 }
377}
378
379fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) {
388 let mut mast = MastForest::new();
389 let node_id = ExternalNodeBuilder::new(digest)
390 .add_to_forest(&mut mast)
391 .expect("adding external node to empty forest should not fail");
392 mast.make_root(node_id);
393 (mast, node_id)
394}
395
396#[cfg(test)]
400mod tests {
401 use super::{Felt, NoteScript, Vec};
402 use crate::assembly::Assembler;
403 use crate::testing::note::DEFAULT_NOTE_SCRIPT;
404
405 #[test]
406 fn test_note_script_to_from_felt() {
407 let assembler = Assembler::default();
408 let script_src = DEFAULT_NOTE_SCRIPT;
409 let library = assembler.assemble_library([script_src]).unwrap();
410 let note_script = NoteScript::from_library(&library).unwrap();
411
412 let encoded: Vec<Felt> = (¬e_script).into();
413 let decoded: NoteScript = encoded.try_into().unwrap();
414
415 assert_eq!(note_script, decoded);
416 }
417
418 #[test]
419 fn test_note_script_with_advice_map() {
420 use miden_core::advice::AdviceMap;
421
422 use crate::Word;
423
424 let assembler = Assembler::default();
425 let library = assembler.assemble_library([DEFAULT_NOTE_SCRIPT]).unwrap();
426 let script = NoteScript::from_library(&library).unwrap();
427
428 assert!(script.mast().advice_map().is_empty());
429
430 let original_root = script.root();
432 let script = script.with_advice_map(AdviceMap::default());
433 assert_eq!(original_root, script.root());
434
435 let key = Word::from([5u32, 6, 7, 8]);
437 let value = vec![Felt::new_unchecked(100)];
438 let mut advice_map = AdviceMap::default();
439 advice_map.insert(key, value.clone());
440
441 let script = script.with_advice_map(advice_map);
442
443 let mast = script.mast();
444 let stored = mast.advice_map().get(&key).expect("entry should be present");
445 assert_eq!(stored.as_ref(), value.as_slice());
446 }
447}