Skip to main content

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