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;
10use miden_mast_package::debug_info::PackageDebugInfo;
11use miden_processor::LoadedMastForest;
12
13use super::Felt;
14use crate::assembly::mast::{MastForest, MastNodeId};
15use crate::assembly::{Library, Path};
16use crate::errors::NoteError;
17use crate::package::{loaded_mast_forest, package_debug_info};
18use crate::utils::create_external_node_forest;
19use crate::utils::serde::{
20 ByteReader,
21 ByteWriter,
22 Deserializable,
23 DeserializationError,
24 Serializable,
25};
26use crate::vm::{AdviceMap, Program};
27use crate::{PrettyPrint, Word};
28
29const NOTE_SCRIPT_ATTRIBUTE: &str = "note_script";
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
37pub struct NoteScriptRoot(Word);
38
39impl From<NoteScriptRoot> for Word {
40 fn from(root: NoteScriptRoot) -> Self {
41 root.0
42 }
43}
44
45impl Display for NoteScriptRoot {
46 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47 Display::fmt(&self.0, f)
48 }
49}
50
51impl Serializable for NoteScriptRoot {
52 fn write_into<W: ByteWriter>(&self, target: &mut W) {
53 target.write(self.0);
54 }
55
56 fn get_size_hint(&self) -> usize {
57 self.0.get_size_hint()
58 }
59}
60
61impl Deserializable for NoteScriptRoot {
62 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
63 let word: Word = source.read()?;
64 Ok(Self::from_raw(word))
65 }
66}
67
68#[derive(Debug, Clone)]
76pub struct NoteScript {
77 mast: Arc<MastForest>,
78 entrypoint: MastNodeId,
79 package_debug_info: Option<Arc<PackageDebugInfo>>,
80}
81
82impl NoteScript {
83 pub fn new(code: Program) -> Self {
92 Self {
93 entrypoint: code.entrypoint(),
94 mast: code.mast_forest().clone(),
95 package_debug_info: None,
96 }
97 }
98
99 pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
104 Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
105 }
106
107 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
112 assert!(mast.get_node_by_id(entrypoint).is_some());
113 Self {
114 mast,
115 entrypoint,
116 package_debug_info: None,
117 }
118 }
119
120 pub fn from_library(library: &Library) -> Result<Self, NoteError> {
130 let mut entrypoint = None;
131
132 for export in library.manifest.exports() {
133 if let Some(proc_export) = export.as_procedure() {
134 if proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
136 if entrypoint.is_some() {
137 return Err(NoteError::NoteScriptMultipleProceduresWithAttribute);
138 }
139 entrypoint = Some(
140 proc_export.node.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?,
141 );
142 }
143 }
144 }
145
146 let entrypoint = entrypoint.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?;
147
148 Ok(Self {
149 mast: library.mast_forest().clone(),
150 entrypoint,
151 package_debug_info: package_debug_info(library),
152 })
153 }
154
155 pub fn from_library_reference(library: &Library, path: &Path) -> Result<Self, NoteError> {
173 let export = library
175 .manifest
176 .exports()
177 .find(|e| e.path().as_ref() == path)
178 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
179
180 let proc_export = export
182 .as_procedure()
183 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
184
185 if !proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
186 return Err(NoteError::NoteScriptProcedureMissingAttribute(path.to_string().into()));
187 }
188
189 let digest = proc_export.digest;
191
192 let (mast, entrypoint) = create_external_node_forest(digest);
194
195 Ok(Self {
196 mast: Arc::new(mast),
197 entrypoint,
198 package_debug_info: package_debug_info(library),
199 })
200 }
201
202 pub fn from_package(package: &Package) -> Result<Self, NoteError> {
212 Ok(NoteScript::from_library(package))?
213 }
214
215 pub fn root(&self) -> NoteScriptRoot {
220 NoteScriptRoot::from_raw(self.mast[self.entrypoint].digest())
221 }
222
223 pub fn mast(&self) -> Arc<MastForest> {
225 self.mast.clone()
226 }
227
228 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
230 loaded_mast_forest(self.mast.clone(), self.package_debug_info.clone())
231 }
232
233 pub fn entrypoint(&self) -> MastNodeId {
235 self.entrypoint
236 }
237
238 pub fn clear_debug_info(&mut self) {
240 self.package_debug_info = None;
241 }
242
243 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
249 if advice_map.is_empty() {
250 return self;
251 }
252
253 let mast = (*self.mast).clone().with_advice_map(advice_map);
254 Self {
255 mast: Arc::new(mast),
256 entrypoint: self.entrypoint,
257 package_debug_info: self.package_debug_info,
258 }
259 }
260}
261
262impl PartialEq for NoteScript {
263 fn eq(&self, other: &Self) -> bool {
264 self.mast == other.mast && self.entrypoint == other.entrypoint
265 }
266}
267
268impl Eq for NoteScript {}
269
270impl From<&NoteScript> for Vec<Felt> {
274 fn from(script: &NoteScript) -> Self {
275 let mut bytes = script.mast.to_bytes();
276 let len = bytes.len();
277
278 let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
280 bytes.resize(bytes.len() + missing, 0);
281
282 let final_size = 2 + bytes.len();
283 let mut result = Vec::with_capacity(final_size);
284
285 result.push(Felt::from(u32::from(script.entrypoint)));
287 result.push(Felt::new_unchecked(len as u64));
288
289 let mut encoded: &[u8] = &bytes;
291 while encoded.len() >= 4 {
292 let (data, rest) =
293 encoded.split_first_chunk::<4>().expect("The length has been checked");
294 let number = u32::from_le_bytes(*data);
295 result.push(Felt::from(number));
296
297 encoded = rest;
298 }
299
300 result
301 }
302}
303
304impl From<NoteScript> for Vec<Felt> {
305 fn from(value: NoteScript) -> Self {
306 (&value).into()
307 }
308}
309
310impl AsRef<NoteScript> for NoteScript {
311 fn as_ref(&self) -> &NoteScript {
312 self
313 }
314}
315
316impl TryFrom<&[Felt]> for NoteScript {
320 type Error = DeserializationError;
321
322 fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
323 if elements.len() < 2 {
324 return Err(DeserializationError::UnexpectedEOF);
325 }
326
327 let entrypoint: u32 = elements[0]
328 .as_canonical_u64()
329 .try_into()
330 .map_err(|err: TryFromIntError| DeserializationError::InvalidValue(err.to_string()))?;
331 let len = elements[1].as_canonical_u64();
332 let mut data = Vec::with_capacity(elements.len() * 4);
333
334 for &felt in &elements[2..] {
335 let element: u32 =
336 felt.as_canonical_u64().try_into().map_err(|err: TryFromIntError| {
337 DeserializationError::InvalidValue(err.to_string())
338 })?;
339 data.extend(element.to_le_bytes())
340 }
341 data.truncate(len as usize);
342
343 let mast = MastForest::read_from_bytes(&data)?;
345 let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
346 Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
347 }
348}
349
350impl TryFrom<Vec<Felt>> for NoteScript {
351 type Error = DeserializationError;
352
353 fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
354 value.as_slice().try_into()
355 }
356}
357
358impl Serializable for NoteScript {
362 fn write_into<W: ByteWriter>(&self, target: &mut W) {
363 self.mast.write_into(target);
364 target.write_u32(u32::from(self.entrypoint));
365 }
366
367 fn get_size_hint(&self) -> usize {
368 let mast_size = self.mast.to_bytes().len();
372 let u32_size = 0u32.get_size_hint();
373
374 mast_size + u32_size
375 }
376}
377
378impl Deserializable for NoteScript {
379 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
380 let mast = MastForest::read_from(source)?;
381 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
382
383 Ok(Self::from_parts(Arc::new(mast), entrypoint))
384 }
385}
386
387impl PrettyPrint for NoteScript {
391 fn render(&self) -> miden_core::prettier::Document {
392 use miden_core::prettier::*;
393 let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
394
395 indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
396 }
397}
398
399impl Display for NoteScript {
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401 self.pretty_print(f)
402 }
403}
404
405#[cfg(test)]
409mod tests {
410
411 use super::{Felt, NoteScript, Vec};
412 use crate::testing::assembler::assemble_test_library;
413 use crate::testing::note::DEFAULT_NOTE_SCRIPT;
414
415 #[test]
416 fn test_note_script_to_from_felt() {
417 let script_src = DEFAULT_NOTE_SCRIPT;
418 let library =
419 assemble_test_library("test-note-script-roundtrip", "test::note_roundtrip", script_src);
420 let note_script = NoteScript::from_library(&library).unwrap();
421
422 let encoded: Vec<Felt> = (¬e_script).into();
423 let decoded: NoteScript = encoded.try_into().unwrap();
424
425 assert_eq!(note_script, decoded);
426 }
427
428 #[test]
429 fn test_note_script_preserves_package_debug_info() {
430 let library = assemble_test_library(
431 "test-note-script-debug-info",
432 "test::note_debug_info",
433 DEFAULT_NOTE_SCRIPT,
434 );
435 let note_script = NoteScript::from_library(&library).unwrap();
436
437 assert!(note_script.loaded_mast_forest().package_debug_info().unwrap().is_some());
438 }
439
440 #[test]
441 fn test_note_script_with_advice_map() {
442 use miden_core::advice::AdviceMap;
443
444 use crate::Word;
445
446 let library = assemble_test_library(
447 "test-note-script-with-advice-map",
448 "test::note_with_advice_map",
449 DEFAULT_NOTE_SCRIPT,
450 );
451 let script = NoteScript::from_library(&library).unwrap();
452
453 assert!(script.mast().advice_map().is_empty());
454
455 let original_root = script.root();
457 let script = script.with_advice_map(AdviceMap::default());
458 assert_eq!(original_root, script.root());
459
460 let key = Word::from([5u32, 6, 7, 8]);
462 let value = vec![Felt::new_unchecked(100)];
463 let mut advice_map = AdviceMap::default();
464 advice_map.insert(key, value.clone());
465
466 let script = script.with_advice_map(advice_map);
467
468 let mast = script.mast();
469 let stored = mast.advice_map().get(&key).expect("entry should be present");
470 assert_eq!(stored.as_ref(), value.as_slice());
471 }
472}