miden_protocol/note/
script.rs1use alloc::string::ToString;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::fmt::Display;
5use core::num::TryFromIntError;
6
7use miden_core::mast::MastNodeExt;
8
9use super::Felt;
10use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId};
11use crate::assembly::{Library, Path};
12use crate::errors::NoteError;
13use crate::utils::serde::{
14 ByteReader,
15 ByteWriter,
16 Deserializable,
17 DeserializationError,
18 Serializable,
19};
20use crate::vm::{AdviceMap, Program};
21use crate::{PrettyPrint, Word};
22
23const NOTE_SCRIPT_ATTRIBUTE: &str = "note_script";
25
26#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct NoteScript {
35 mast: Arc<MastForest>,
36 entrypoint: MastNodeId,
37}
38
39impl NoteScript {
40 pub fn new(code: Program) -> Self {
45 Self {
46 entrypoint: code.entrypoint(),
47 mast: code.mast_forest().clone(),
48 }
49 }
50
51 pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
56 Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
57 }
58
59 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
64 assert!(mast.get_node_by_id(entrypoint).is_some());
65 Self { mast, entrypoint }
66 }
67
68 pub fn from_library(library: &Library) -> Result<Self, NoteError> {
78 let mut entrypoint = None;
79
80 for export in library.exports() {
81 if let Some(proc_export) = export.as_procedure() {
82 if proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
84 if entrypoint.is_some() {
85 return Err(NoteError::NoteScriptMultipleProceduresWithAttribute);
86 }
87 entrypoint = Some(proc_export.node);
88 }
89 }
90 }
91
92 let entrypoint = entrypoint.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?;
93
94 Ok(Self {
95 mast: library.mast_forest().clone(),
96 entrypoint,
97 })
98 }
99
100 pub fn from_library_reference(library: &Library, path: &Path) -> Result<Self, NoteError> {
118 let export = library
120 .exports()
121 .find(|e| e.path().as_ref() == path)
122 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
123
124 let proc_export = export
126 .as_procedure()
127 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
128
129 if !proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
130 return Err(NoteError::NoteScriptProcedureMissingAttribute(path.to_string().into()));
131 }
132
133 let digest = library.mast_forest()[proc_export.node].digest();
135
136 let (mast, entrypoint) = create_external_node_forest(digest);
138
139 Ok(Self { mast: Arc::new(mast), entrypoint })
140 }
141
142 pub fn root(&self) -> Word {
147 self.mast[self.entrypoint].digest()
148 }
149
150 pub fn mast(&self) -> Arc<MastForest> {
152 self.mast.clone()
153 }
154
155 pub fn entrypoint(&self) -> MastNodeId {
157 self.entrypoint
158 }
159
160 pub fn clear_debug_info(&mut self) {
165 let mut mast = self.mast.clone();
166 Arc::make_mut(&mut mast).clear_debug_info();
167 self.mast = mast;
168 }
169
170 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
176 if advice_map.is_empty() {
177 return self;
178 }
179
180 let mut mast = (*self.mast).clone();
181 mast.advice_map_mut().extend(advice_map);
182 Self {
183 mast: Arc::new(mast),
184 entrypoint: self.entrypoint,
185 }
186 }
187}
188
189impl From<&NoteScript> for Vec<Felt> {
193 fn from(script: &NoteScript) -> Self {
194 let mut bytes = script.mast.to_bytes();
195 let len = bytes.len();
196
197 let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
199 bytes.resize(bytes.len() + missing, 0);
200
201 let final_size = 2 + bytes.len();
202 let mut result = Vec::with_capacity(final_size);
203
204 result.push(Felt::from(u32::from(script.entrypoint)));
206 result.push(Felt::new(len as u64));
207
208 let mut encoded: &[u8] = &bytes;
210 while encoded.len() >= 4 {
211 let (data, rest) =
212 encoded.split_first_chunk::<4>().expect("The length has been checked");
213 let number = u32::from_le_bytes(*data);
214 result.push(Felt::new(number.into()));
215
216 encoded = rest;
217 }
218
219 result
220 }
221}
222
223impl From<NoteScript> for Vec<Felt> {
224 fn from(value: NoteScript) -> Self {
225 (&value).into()
226 }
227}
228
229impl AsRef<NoteScript> for NoteScript {
230 fn as_ref(&self) -> &NoteScript {
231 self
232 }
233}
234
235impl TryFrom<&[Felt]> for NoteScript {
239 type Error = DeserializationError;
240
241 fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
242 if elements.len() < 2 {
243 return Err(DeserializationError::UnexpectedEOF);
244 }
245
246 let entrypoint: u32 = elements[0]
247 .as_canonical_u64()
248 .try_into()
249 .map_err(|err: TryFromIntError| DeserializationError::InvalidValue(err.to_string()))?;
250 let len = elements[1].as_canonical_u64();
251 let mut data = Vec::with_capacity(elements.len() * 4);
252
253 for &felt in &elements[2..] {
254 let element: u32 =
255 felt.as_canonical_u64().try_into().map_err(|err: TryFromIntError| {
256 DeserializationError::InvalidValue(err.to_string())
257 })?;
258 data.extend(element.to_le_bytes())
259 }
260 data.shrink_to(len as usize);
261
262 let mast = MastForest::read_from_bytes(&data)?;
264 let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
265 Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
266 }
267}
268
269impl TryFrom<Vec<Felt>> for NoteScript {
270 type Error = DeserializationError;
271
272 fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
273 value.as_slice().try_into()
274 }
275}
276
277impl Serializable for NoteScript {
281 fn write_into<W: ByteWriter>(&self, target: &mut W) {
282 self.mast.write_into(target);
283 target.write_u32(u32::from(self.entrypoint));
284 }
285
286 fn get_size_hint(&self) -> usize {
287 let mast_size = self.mast.to_bytes().len();
291 let u32_size = 0u32.get_size_hint();
292
293 mast_size + u32_size
294 }
295}
296
297impl Deserializable for NoteScript {
298 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
299 let mast = MastForest::read_from(source)?;
300 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
301
302 Ok(Self::from_parts(Arc::new(mast), entrypoint))
303 }
304}
305
306impl PrettyPrint for NoteScript {
310 fn render(&self) -> miden_core::prettier::Document {
311 use miden_core::prettier::*;
312 let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
313
314 indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
315 }
316}
317
318impl Display for NoteScript {
319 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
320 self.pretty_print(f)
321 }
322}
323
324fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) {
333 let mut mast = MastForest::new();
334 let node_id = ExternalNodeBuilder::new(digest)
335 .add_to_forest(&mut mast)
336 .expect("adding external node to empty forest should not fail");
337 mast.make_root(node_id);
338 (mast, node_id)
339}
340
341#[cfg(test)]
345mod tests {
346 use super::{Felt, NoteScript, Vec};
347 use crate::assembly::Assembler;
348 use crate::testing::note::DEFAULT_NOTE_CODE;
349
350 #[test]
351 fn test_note_script_to_from_felt() {
352 let assembler = Assembler::default();
353 let script_src = DEFAULT_NOTE_CODE;
354 let program = assembler.assemble_program(script_src).unwrap();
355 let note_script = NoteScript::new(program);
356
357 let encoded: Vec<Felt> = (¬e_script).into();
358 let decoded: NoteScript = encoded.try_into().unwrap();
359
360 assert_eq!(note_script, decoded);
361 }
362
363 #[test]
364 fn test_note_script_with_advice_map() {
365 use miden_core::advice::AdviceMap;
366
367 use crate::Word;
368
369 let assembler = Assembler::default();
370 let program = assembler.assemble_program("begin nop end").unwrap();
371 let script = NoteScript::new(program);
372
373 assert!(script.mast().advice_map().is_empty());
374
375 let original_root = script.root();
377 let script = script.with_advice_map(AdviceMap::default());
378 assert_eq!(original_root, script.root());
379
380 let key = Word::from([5u32, 6, 7, 8]);
382 let value = vec![Felt::new(100)];
383 let mut advice_map = AdviceMap::default();
384 advice_map.insert(key, value.clone());
385
386 let script = script.with_advice_map(advice_map);
387
388 let mast = script.mast();
389 let stored = mast.advice_map().get(&key).expect("entry should be present");
390 assert_eq!(stored.as_ref(), value.as_slice());
391 }
392}