miden_objects/note/
script.rs

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