miden_objects/note/
script.rs

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