miden_objects/note/
script.rs

1use alloc::sync::Arc;
2use alloc::vec::Vec;
3use core::fmt::Display;
4
5use miden_processor::MastNodeExt;
6
7use super::Felt;
8use crate::assembly::mast::{MastForest, MastNodeId};
9use crate::utils::serde::{
10    ByteReader,
11    ByteWriter,
12    Deserializable,
13    DeserializationError,
14    Serializable,
15};
16use crate::vm::Program;
17use crate::{NoteError, PrettyPrint, Word};
18
19// NOTE SCRIPT
20// ================================================================================================
21
22/// An executable program of a note.
23///
24/// A note's script represents a program which must be executed for a note to be consumed. As such
25/// it defines the rules and side effects of consuming a given note.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct NoteScript {
28    mast: Arc<MastForest>,
29    entrypoint: MastNodeId,
30}
31
32impl NoteScript {
33    // CONSTRUCTORS
34    // --------------------------------------------------------------------------------------------
35
36    /// Returns a new [NoteScript] instantiated from the provided program.
37    pub fn new(code: Program) -> Self {
38        Self {
39            entrypoint: code.entrypoint(),
40            mast: code.mast_forest().clone(),
41        }
42    }
43
44    /// Returns a new [NoteScript] deserialized from the provided bytes.
45    ///
46    /// # Errors
47    /// Returns an error if note script deserialization fails.
48    pub fn from_bytes(bytes: &[u8]) -> Result<Self, NoteError> {
49        Self::read_from_bytes(bytes).map_err(NoteError::NoteScriptDeserializationError)
50    }
51
52    /// Returns a new [NoteScript] instantiated from the provided components.
53    ///
54    /// # Panics
55    /// Panics if the specified entrypoint is not in the provided MAST forest.
56    pub fn from_parts(mast: Arc<MastForest>, entrypoint: MastNodeId) -> Self {
57        assert!(mast.get_node_by_id(entrypoint).is_some());
58        Self { mast, entrypoint }
59    }
60
61    // PUBLIC ACCESSORS
62    // --------------------------------------------------------------------------------------------
63
64    /// Returns the commitment of this note script (i.e., the script's MAST root).
65    pub fn root(&self) -> Word {
66        self.mast[self.entrypoint].digest()
67    }
68
69    /// Returns a reference to the [MastForest] backing this note script.
70    pub fn mast(&self) -> Arc<MastForest> {
71        self.mast.clone()
72    }
73
74    /// Returns an entrypoint node ID of the current script.
75    pub fn entrypoint(&self) -> MastNodeId {
76        self.entrypoint
77    }
78}
79
80// CONVERSIONS INTO NOTE SCRIPT
81// ================================================================================================
82
83impl From<&NoteScript> for Vec<Felt> {
84    fn from(script: &NoteScript) -> Self {
85        let mut bytes = script.mast.to_bytes();
86        let len = bytes.len();
87
88        // Pad the data so that it can be encoded with u32
89        let missing = if !len.is_multiple_of(4) { 4 - (len % 4) } else { 0 };
90        bytes.resize(bytes.len() + missing, 0);
91
92        let final_size = 2 + bytes.len();
93        let mut result = Vec::with_capacity(final_size);
94
95        // Push the length, this is used to remove the padding later
96        result.push(Felt::from(u32::from(script.entrypoint)));
97        result.push(Felt::new(len as u64));
98
99        // A Felt can not represent all u64 values, so the data is encoded using u32.
100        let mut encoded: &[u8] = &bytes;
101        while encoded.len() >= 4 {
102            let (data, rest) =
103                encoded.split_first_chunk::<4>().expect("The length has been checked");
104            let number = u32::from_le_bytes(*data);
105            result.push(Felt::new(number.into()));
106
107            encoded = rest;
108        }
109
110        result
111    }
112}
113
114impl From<NoteScript> for Vec<Felt> {
115    fn from(value: NoteScript) -> Self {
116        (&value).into()
117    }
118}
119
120impl AsRef<NoteScript> for NoteScript {
121    fn as_ref(&self) -> &NoteScript {
122        self
123    }
124}
125
126// CONVERSIONS FROM NOTE SCRIPT
127// ================================================================================================
128
129impl TryFrom<&[Felt]> for NoteScript {
130    type Error = DeserializationError;
131
132    fn try_from(elements: &[Felt]) -> Result<Self, Self::Error> {
133        if elements.len() < 2 {
134            return Err(DeserializationError::UnexpectedEOF);
135        }
136
137        let entrypoint: u32 = elements[0].try_into().map_err(DeserializationError::InvalidValue)?;
138        let len = elements[1].as_int();
139        let mut data = Vec::with_capacity(elements.len() * 4);
140
141        for &felt in &elements[2..] {
142            let v: u32 = felt.try_into().map_err(DeserializationError::InvalidValue)?;
143            data.extend(v.to_le_bytes())
144        }
145        data.shrink_to(len as usize);
146
147        let mast = MastForest::read_from_bytes(&data)?;
148        let entrypoint = MastNodeId::from_u32_safe(entrypoint, &mast)?;
149        Ok(NoteScript::from_parts(Arc::new(mast), entrypoint))
150    }
151}
152
153impl TryFrom<Vec<Felt>> for NoteScript {
154    type Error = DeserializationError;
155
156    fn try_from(value: Vec<Felt>) -> Result<Self, Self::Error> {
157        value.as_slice().try_into()
158    }
159}
160
161// SERIALIZATION
162// ================================================================================================
163
164impl Serializable for NoteScript {
165    fn write_into<W: ByteWriter>(&self, target: &mut W) {
166        self.mast.write_into(target);
167        target.write_u32(u32::from(self.entrypoint));
168    }
169}
170
171impl Deserializable for NoteScript {
172    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
173        let mast = MastForest::read_from(source)?;
174        let entrypoint = MastNodeId::from_u32_safe(source.read_u32()?, &mast)?;
175
176        Ok(Self::from_parts(Arc::new(mast), entrypoint))
177    }
178}
179
180// PRETTY-PRINTING
181// ================================================================================================
182
183impl PrettyPrint for NoteScript {
184    fn render(&self) -> miden_core::prettier::Document {
185        use miden_core::prettier::*;
186        let entrypoint = self.mast[self.entrypoint].to_pretty_print(&self.mast);
187
188        indent(4, const_text("begin") + nl() + entrypoint.render()) + nl() + const_text("end")
189    }
190}
191
192impl Display for NoteScript {
193    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
194        self.pretty_print(f)
195    }
196}
197
198// TESTS
199// ================================================================================================
200
201#[cfg(test)]
202mod tests {
203    use super::{Felt, NoteScript, Vec};
204    use crate::assembly::Assembler;
205    use crate::testing::note::DEFAULT_NOTE_CODE;
206
207    #[test]
208    fn test_note_script_to_from_felt() {
209        let assembler = Assembler::default();
210        let tx_script_src = DEFAULT_NOTE_CODE;
211        let program = assembler.assemble_program(tx_script_src).unwrap();
212        let note_script = NoteScript::new(program);
213
214        let encoded: Vec<Felt> = (&note_script).into();
215        let decoded: NoteScript = encoded.try_into().unwrap();
216
217        assert_eq!(note_script, decoded);
218    }
219}