miden_protocol/note/
script.rs1use alloc::string::ToString;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::fmt::Display;
5
6use miden_processor::MastNodeExt;
7
8use super::Felt;
9use crate::assembly::mast::{ExternalNodeBuilder, MastForest, MastForestContributor, MastNodeId};
10use crate::assembly::{Library, Path};
11use crate::errors::NoteError;
12use crate::utils::serde::{
13 ByteReader,
14 ByteWriter,
15 Deserializable,
16 DeserializationError,
17 Serializable,
18};
19use crate::vm::{AdviceMap, Program};
20use crate::{PrettyPrint, Word};
21
22const NOTE_SCRIPT_ATTRIBUTE: &str = "note_script";
24
25#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct NoteScript {
34 mast: Arc<MastForest>,
35 entrypoint: MastNodeId,
36}
37
38impl NoteScript {
39 pub fn new(code: Program) -> Self {
44 Self {
45 entrypoint: code.entrypoint(),
46 mast: code.mast_forest().clone(),
47 }
48 }
49
50 pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
55 Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
56 }
57
58 pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
63 assert!(mast.get_node_by_id(entrypoint).is_some());
64 Self { mast, entrypoint }
65 }
66
67 pub fn from_library(library: &Library) -> Result<Self, NoteError> {
77 let mut entrypoint = None;
78
79 for export in library.exports() {
80 if let Some(proc_export) = export.as_procedure() {
81 if proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
83 if entrypoint.is_some() {
84 return Err(NoteError::NoteScriptMultipleProceduresWithAttribute);
85 }
86 entrypoint = Some(proc_export.node);
87 }
88 }
89 }
90
91 let entrypoint = entrypoint.ok_or(NoteError::NoteScriptNoProcedureWithAttribute)?;
92
93 Ok(Self {
94 mast: library.mast_forest().clone(),
95 entrypoint,
96 })
97 }
98
99 pub fn from_library_reference(library: &Library, path: &Path) -> Result<Self, NoteError> {
117 let export = library
119 .exports()
120 .find(|e| e.path().as_ref() == path)
121 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
122
123 let proc_export = export
125 .as_procedure()
126 .ok_or_else(|| NoteError::NoteScriptProcedureNotFound(path.to_string().into()))?;
127
128 if !proc_export.attributes.has(NOTE_SCRIPT_ATTRIBUTE) {
129 return Err(NoteError::NoteScriptProcedureMissingAttribute(path.to_string().into()));
130 }
131
132 let digest = library.mast_forest()[proc_export.node].digest();
134
135 let (mast, entrypoint) = create_external_node_forest(digest);
137
138 Ok(Self { mast: Arc::new(mast), entrypoint })
139 }
140
141 pub fn root(&self) -> Word {
146 self.mast[self.entrypoint].digest()
147 }
148
149 pub fn mast(&self) -> Arc<MastForest> {
151 self.mast.clone()
152 }
153
154 pub fn entrypoint(&self) -> MastNodeId {
156 self.entrypoint
157 }
158
159 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
165 if advice_map.is_empty() {
166 return self;
167 }
168
169 let mut mast = (*self.mast).clone();
170 mast.advice_map_mut().extend(advice_map);
171 Self {
172 mast: Arc::new(mast),
173 entrypoint: self.entrypoint,
174 }
175 }
176}
177
178impl From<&NoteScript> for Vec<Felt> {
182 fn from(script: &NoteScript) -> Self {
183 let mut bytes = script.mast.to_bytes();
184 let len = bytes.len();
185
186 let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
188 bytes.resize(bytes.len() + missing, 0);
189
190 let final_size = 2 + bytes.len();
191 let mut result = Vec::with_capacity(final_size);
192
193 result.push(Felt::from(u32::from(script.entrypoint)));
195 result.push(Felt::new(len as u64));
196
197 let mut encoded: &[u8] = &bytes;
199 while encoded.len() >= 4 {
200 let (data, rest) =
201 encoded.split_first_chunk::<4>().expect("The length has been checked");
202 let number = u32::from_le_bytes(*data);
203 result.push(Felt::new(number.into()));
204
205 encoded = rest;
206 }
207
208 result
209 }
210}
211
212impl From<NoteScript> for Vec<Felt> {
213 fn from(value: NoteScript) -> Self {
214 (&value).into()
215 }
216}
217
218impl AsRef<NoteScript> for NoteScript {
219 fn as_ref(&self) -> &NoteScript {
220 self
221 }
222}
223
224impl TryFrom<&[Felt]> for NoteScript {
228 type Error = DeserializationError;
229
230 fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
231 if elements.len() < 2 {
232 return Err(DeserializationError::UnexpectedEOF);
233 }
234
235 let entrypoint: u32 = elements[0].try_into().map_err(DeserializationError::InvalidValue)?;
236 let len = elements[1].as_int();
237 let mut data = Vec::with_capacity(elements.len() * 4);
238
239 for &felt in &elements[2..] {
240 let v: u32 = felt.try_into().map_err(DeserializationError::InvalidValue)?;
241 data.extend(v.to_le_bytes())
242 }
243 data.shrink_to(len as usize);
244
245 let mast = MastForest::read_from_bytes(&data)?;
246 let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
247 Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
248 }
249}
250
251impl TryFrom<Vec<Felt>> for NoteScript {
252 type Error = DeserializationError;
253
254 fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
255 value.as_slice().try_into()
256 }
257}
258
259impl Serializable for NoteScript {
263 fn write_into<W: ByteWriter>(&self, target: &mut W) {
264 self.mast.write_into(target);
265 target.write_u32(u32::from(self.entrypoint));
266 }
267}
268
269impl Deserializable for NoteScript {
270 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
271 let mast = MastForest::read_from(source)?;
272 let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
273
274 Ok(Self::from_parts(Arc::new(mast), entrypoint))
275 }
276}
277
278impl PrettyPrint for NoteScript {
282 fn render(&self) -> miden_core::prettier::Document {
283 use miden_core::prettier::*;
284 let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
285
286 indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
287 }
288}
289
290impl Display for NoteScript {
291 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
292 self.pretty_print(f)
293 }
294}
295
296fn create_external_node_forest(digest: Word) -> (MastForest, MastNodeId) {
305 let mut mast = MastForest::new();
306 let node_id = ExternalNodeBuilder::new(digest)
307 .add_to_forest(&mut mast)
308 .expect("adding external node to empty forest should not fail");
309 mast.make_root(node_id);
310 (mast, node_id)
311}
312
313#[cfg(test)]
317mod tests {
318 use super::{Felt, NoteScript, Vec};
319 use crate::assembly::Assembler;
320 use crate::testing::note::DEFAULT_NOTE_CODE;
321
322 #[test]
323 fn test_note_script_to_from_felt() {
324 let assembler = Assembler::default();
325 let tx_script_src = DEFAULT_NOTE_CODE;
326 let program = assembler.assemble_program(tx_script_src).unwrap();
327 let note_script = NoteScript::new(program);
328
329 let encoded: Vec<Felt> = (¬e_script).into();
330 let decoded: NoteScript = encoded.try_into().unwrap();
331
332 assert_eq!(note_script, decoded);
333 }
334
335 #[test]
336 fn test_note_script_with_advice_map() {
337 use miden_core::{AdviceMap, Word};
338
339 let assembler = Assembler::default();
340 let program = assembler.assemble_program("begin nop end").unwrap();
341 let script = NoteScript::new(program);
342
343 assert!(script.mast().advice_map().is_empty());
344
345 let original_root = script.root();
347 let script = script.with_advice_map(AdviceMap::default());
348 assert_eq!(original_root, script.root());
349
350 let key = Word::from([5u32, 6, 7, 8]);
352 let value = vec![Felt::new(100)];
353 let mut advice_map = AdviceMap::default();
354 advice_map.insert(key, value.clone());
355
356 let script = script.with_advice_map(advice_map);
357
358 let mast = script.mast();
359 let stored = mast.advice_map().get(&key).expect("entry should be present");
360 assert_eq!(stored.as_ref(), value.as_slice());
361 }
362}