miden_protocol/transaction/
script.rs1use alloc::sync::Arc;
2use core::fmt::Display;
3
4use miden_crypto_derive::WordWrapper;
5use miden_mast_package::Package;
6use miden_processor::LoadedMastForest;
7
8use crate::Word;
9use crate::assembly::Path;
10use crate::assembly::mast::{MastForest, MastNodeId};
11use crate::script::{MastForestScript, MastForestScriptError};
12use crate::utils::serde::{
13 ByteReader,
14 ByteWriter,
15 Deserializable,
16 DeserializationError,
17 Serializable,
18};
19use crate::vm::AdviceMap;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, WordWrapper)]
26pub struct TransactionScriptRoot(Word);
27
28impl From<TransactionScriptRoot> for Word {
29 fn from(root: TransactionScriptRoot) -> Self {
30 root.0
31 }
32}
33
34impl Display for TransactionScriptRoot {
35 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36 Display::fmt(&self.0, f)
37 }
38}
39
40impl Serializable for TransactionScriptRoot {
41 fn write_into<W: ByteWriter>(&self, target: &mut W) {
42 target.write(self.0);
43 }
44
45 fn get_size_hint(&self) -> usize {
46 self.0.get_size_hint()
47 }
48}
49
50impl Deserializable for TransactionScriptRoot {
51 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
52 let word: Word = source.read()?;
53 Ok(Self::from_raw(word))
54 }
55}
56
57pub const TRANSACTION_SCRIPT_ATTRIBUTE: &str = "transaction_script";
62
63#[derive(Clone, Debug)]
71pub struct TransactionScript(MastForestScript);
72
73impl TransactionScript {
74 pub fn from_parts(
82 mast: Arc<MastForest>,
83 entrypoint: MastNodeId,
84 ) -> Result<Self, MastForestScriptError> {
85 MastForestScript::from_parts(mast, entrypoint).map(Self)
86 }
87
88 pub fn from_package(package: &Package) -> Result<Self, MastForestScriptError> {
100 MastForestScript::from_package(package, TRANSACTION_SCRIPT_ATTRIBUTE).map(Self)
101 }
102
103 pub fn from_package_reference(
121 package: &Package,
122 path: &Path,
123 ) -> Result<Self, MastForestScriptError> {
124 MastForestScript::from_package_reference(package, path, TRANSACTION_SCRIPT_ATTRIBUTE)
125 .map(Self)
126 }
127
128 pub fn mast(&self) -> Arc<MastForest> {
133 self.0.mast()
134 }
135
136 pub fn loaded_mast_forest(&self) -> LoadedMastForest {
138 self.0.loaded_mast_forest()
139 }
140
141 pub fn root(&self) -> TransactionScriptRoot {
143 TransactionScriptRoot::from_raw(self.0.digest())
144 }
145
146 pub fn entrypoint(&self) -> MastNodeId {
148 self.0.entrypoint()
149 }
150
151 pub fn with_advice_map(self, advice_map: AdviceMap) -> Self {
157 Self(self.0.with_advice_map(advice_map))
158 }
159}
160
161impl PartialEq for TransactionScript {
162 fn eq(&self, other: &Self) -> bool {
163 self.0 == other.0
164 }
165}
166
167#[cfg(test)]
168mod entrypoint_tests {
169 use alloc::sync::Arc;
170
171 use super::TransactionScript;
172 use crate::Word;
173 use crate::utils::create_external_node_forest;
174
175 #[test]
176 fn entrypoint_returns_the_script_entrypoint() {
177 let (mast, entrypoint) = create_external_node_forest(Word::empty());
178 let script = TransactionScript::from_parts(Arc::new(mast), entrypoint)
179 .expect("test MAST forest should contain its entrypoint");
180
181 assert_eq!(script.entrypoint(), entrypoint);
182 }
183}
184
185impl Eq for TransactionScript {}
186
187impl Serializable for TransactionScript {
191 fn write_into<W: ByteWriter>(&self, target: &mut W) {
192 self.0.write_into(target);
193 }
194
195 fn get_size_hint(&self) -> usize {
196 self.0.get_size_hint()
197 }
198}
199
200impl Deserializable for TransactionScript {
201 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
202 Ok(Self(MastForestScript::read_from(source)?))
203 }
204}
205
206#[cfg(test)]
210mod tests {
211 use miden_core::advice::AdviceMap;
212
213 use super::TransactionScript;
214
215 const TX_SCRIPT_SOURCE: &str = "
217 @transaction_script
218 pub proc main
219 push.1 drop
220 end
221 ";
222
223 #[test]
224 fn test_transaction_script_preserves_package_debug_info() {
225 use crate::testing::assembler::assemble_test_package;
226
227 let package = assemble_test_package(
228 "test-tx-script-debug-info",
229 "test::tx_script_debug_info",
230 TX_SCRIPT_SOURCE,
231 );
232 let script = TransactionScript::from_package(&package).unwrap();
233
234 assert!(script.loaded_mast_forest().package_debug_info().unwrap().is_some());
235 }
236
237 #[test]
238 fn test_transaction_script_with_advice_map() {
239 use miden_core::{Felt, Word};
240
241 use crate::testing::assembler::assemble_test_package;
242
243 let package = assemble_test_package(
244 "test-tx-script-with-advice-map",
245 "test::tx_script_with_advice_map",
246 TX_SCRIPT_SOURCE,
247 );
248 let script = TransactionScript::from_package(&package).unwrap();
249 assert!(script.mast().advice_map().is_empty());
250
251 let original_root = script.root();
253 let script = script.with_advice_map(AdviceMap::default());
254 assert_eq!(original_root, script.root());
255
256 let key = Word::from([1u32, 2, 3, 4]);
258 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
259 let mut advice_map = AdviceMap::default();
260 advice_map.insert(key, value.clone());
261
262 let script = script.with_advice_map(advice_map);
263
264 let mast = script.mast();
265 let stored = mast.advice_map().get(&key).expect("entry should be present");
266 assert_eq!(stored.as_ref(), value.as_slice());
267 }
268
269 #[test]
270 fn test_transaction_script_from_library_package() {
271 use assert_matches::assert_matches;
272
273 use crate::script::MastForestScriptError;
274 use crate::testing::assembler::assemble_test_package;
275 use crate::utils::serde::{Deserializable, Serializable};
276
277 let package = assemble_test_package("test-tx-script", "test::tx_script", TX_SCRIPT_SOURCE);
278
279 let script = TransactionScript::from_package(&package).unwrap();
280
281 let bytes = script.to_bytes();
283 let decoded = TransactionScript::read_from_bytes(&bytes).unwrap();
284 assert_eq!(script, decoded);
285
286 let no_attr = assemble_test_package(
288 "test-tx-script-no-attr",
289 "test::tx_script_no_attr",
290 "pub proc main push.1 drop end",
291 );
292 assert_matches!(
293 TransactionScript::from_package(&no_attr),
294 Err(MastForestScriptError::NoProcedureWithAttribute(_))
295 );
296
297 let multiple = assemble_test_package(
299 "test-tx-script-multiple",
300 "test::tx_script_multiple",
301 "@transaction_script pub proc main_a push.1 drop end
302 @transaction_script pub proc main_b push.2 drop end",
303 );
304 assert_matches!(
305 TransactionScript::from_package(&multiple),
306 Err(MastForestScriptError::MultipleProceduresWithAttribute(_))
307 );
308 }
309
310 #[test]
311 fn test_transaction_script_from_executable_package() {
312 use assert_matches::assert_matches;
313
314 use crate::assembly::Assembler;
315 use crate::script::MastForestScriptError;
316
317 let package = Assembler::default()
320 .assemble_program("test-tx-script-executable", "begin nop end")
321 .unwrap();
322 assert_matches!(
323 TransactionScript::from_package(&package),
324 Err(MastForestScriptError::ExecutablePackage)
325 );
326 }
327
328 #[test]
329 fn test_transaction_script_from_package_reference() {
330 use alloc::string::ToString;
331
332 use assert_matches::assert_matches;
333
334 use crate::Word;
335 use crate::assembly::Path;
336 use crate::script::MastForestScriptError;
337 use crate::testing::assembler::assemble_test_package;
338
339 let source = "
340 @transaction_script
341 pub proc main_a
342 push.1 drop
343 end
344
345 @transaction_script
346 pub proc main_b
347 push.2 drop
348 end
349
350 pub proc helper
351 push.3 drop
352 end
353 ";
354 let package =
355 assemble_test_package("test-tx-script-reference", "test::tx_script_reference", source);
356
357 for proc_name in ["main_a", "main_b"] {
360 let export = package
361 .manifest
362 .exports()
363 .find(|e| e.path().as_ref().to_string().ends_with(proc_name))
364 .unwrap();
365 let digest = export.as_procedure().unwrap().digest;
366
367 let script =
368 TransactionScript::from_package_reference(&package, export.path().as_ref())
369 .unwrap();
370 assert_eq!(Word::from(script.root()), digest);
371 }
372
373 assert_matches!(
375 TransactionScript::from_package_reference(&package, Path::new("::foo::bar::main")),
376 Err(MastForestScriptError::ProcedureNotFound(_))
377 );
378
379 let helper = package
381 .manifest
382 .exports()
383 .find(|e| e.path().as_ref().to_string().ends_with("helper"))
384 .unwrap();
385 assert_matches!(
386 TransactionScript::from_package_reference(&package, helper.path().as_ref()),
387 Err(MastForestScriptError::ProcedureMissingAttribute(_))
388 );
389 }
390}