11use alloc:: collections:: BTreeMap ;
2- use alloc:: string:: String ;
2+ use alloc:: string:: { String , ToString } ;
33use alloc:: sync:: Arc ;
44use alloc:: vec:: Vec ;
55use core:: fmt:: Display ;
@@ -11,6 +11,8 @@ use miden_mast_package::Package;
1111
1212use super :: { Felt , Hasher , Word } ;
1313use crate :: account:: auth:: { PublicKeyCommitment , Signature } ;
14+ use crate :: assembly:: mast:: { ExternalNodeBuilder , MastForestContributor } ;
15+ use crate :: assembly:: { Library , Path } ;
1416use crate :: errors:: TransactionScriptError ;
1517use crate :: note:: { NoteId , NoteRecipient } ;
1618use crate :: utils:: serde:: {
@@ -318,6 +320,9 @@ impl Deserializable for TransactionScriptRoot {
318320// TRANSACTION SCRIPT
319321// ================================================================================================
320322
323+ /// The attribute name used to mark the entrypoint procedure in a transaction script library.
324+ const TRANSACTION_SCRIPT_ATTRIBUTE : & str = "transaction_script" ;
325+
321326/// Transaction script.
322327///
323328/// A transaction script is a program that is executed in a transaction after all input notes
@@ -336,6 +341,8 @@ impl TransactionScript {
336341 // --------------------------------------------------------------------------------------------
337342
338343 /// Returns a new [TransactionScript] instantiated with the provided code.
344+ // TODO: we can remove this `Program` based constructor once the compiler integrates the
345+ // `@transaction_script` attribute (https://github.com/0xMiden/compiler/issues/1190).
339346 pub fn new ( code : Program ) -> Self {
340347 Self :: from_parts ( code. mast_forest ( ) . clone ( ) , code. entrypoint ( ) )
341348 }
@@ -364,6 +371,76 @@ impl TransactionScript {
364371 Ok ( TransactionScript :: new ( program) )
365372 }
366373
374+ /// Returns a new [TransactionScript] instantiated from the provided library.
375+ ///
376+ /// The library must contain exactly one procedure with the `@transaction_script` attribute,
377+ /// which will be used as the entrypoint.
378+ ///
379+ /// # Errors
380+ /// Returns an error if:
381+ /// - The library does not contain a procedure with the `@transaction_script` attribute.
382+ /// - The library contains multiple procedures with the `@transaction_script` attribute.
383+ pub fn from_library ( library : & Library ) -> Result < Self , TransactionScriptError > {
384+ let mut entrypoint = None ;
385+
386+ for export in library. exports ( ) {
387+ if let Some ( proc_export) = export. as_procedure ( )
388+ && proc_export. attributes . has ( TRANSACTION_SCRIPT_ATTRIBUTE )
389+ {
390+ if entrypoint. is_some ( ) {
391+ return Err ( TransactionScriptError :: MultipleProceduresWithAttribute ) ;
392+ }
393+ entrypoint = Some ( proc_export. node ) ;
394+ }
395+ }
396+
397+ let entrypoint = entrypoint. ok_or ( TransactionScriptError :: NoProcedureWithAttribute ) ?;
398+
399+ Ok ( Self {
400+ mast : library. mast_forest ( ) . clone ( ) ,
401+ entrypoint,
402+ } )
403+ }
404+
405+ /// Returns a new [TransactionScript] containing only a reference to a procedure in the provided
406+ /// library.
407+ ///
408+ /// This method is useful when a library contains multiple transaction scripts and you need to
409+ /// extract a specific one by its fully qualified path.
410+ ///
411+ /// The procedure at the specified path must have the `@transaction_script` attribute.
412+ ///
413+ /// Note: This method creates a minimal [MastForest] containing only an external node
414+ /// referencing the procedure's digest, rather than copying the entire library. The actual
415+ /// procedure code will be resolved at runtime via the `MastForestStore`.
416+ ///
417+ /// # Errors
418+ /// Returns an error if:
419+ /// - The library does not contain a procedure at the specified path.
420+ /// - The procedure at the specified path does not have the `@transaction_script` attribute.
421+ pub fn from_library_reference (
422+ library : & Library ,
423+ path : & Path ,
424+ ) -> Result < Self , TransactionScriptError > {
425+ let export = library
426+ . exports ( )
427+ . find ( |e| e. path ( ) . as_ref ( ) == path)
428+ . ok_or_else ( || TransactionScriptError :: ProcedureNotFound ( path. to_string ( ) . into ( ) ) ) ?;
429+
430+ let proc_export = export
431+ . as_procedure ( )
432+ . ok_or_else ( || TransactionScriptError :: ProcedureNotFound ( path. to_string ( ) . into ( ) ) ) ?;
433+
434+ if !proc_export. attributes . has ( TRANSACTION_SCRIPT_ATTRIBUTE ) {
435+ return Err ( TransactionScriptError :: ProcedureMissingAttribute ( path. to_string ( ) . into ( ) ) ) ;
436+ }
437+
438+ let digest = library. mast_forest ( ) [ proc_export. node ] . digest ( ) ;
439+ let ( mast, entrypoint) = create_external_node_forest ( digest) ;
440+
441+ Ok ( Self { mast : Arc :: new ( mast) , entrypoint } )
442+ }
443+
367444 // PUBLIC ACCESSORS
368445 // --------------------------------------------------------------------------------------------
369446
@@ -396,6 +473,17 @@ impl TransactionScript {
396473 }
397474}
398475
476+ /// Creates a minimal [MastForest] containing a single external node referencing the provided
477+ /// digest, returning the forest together with the id of the (root) external node.
478+ fn create_external_node_forest ( digest : Word ) -> ( MastForest , MastNodeId ) {
479+ let mut mast = MastForest :: new ( ) ;
480+ let node_id = ExternalNodeBuilder :: new ( digest)
481+ . add_to_forest ( & mut mast)
482+ . expect ( "adding external node to empty forest should not fail" ) ;
483+ mast. make_root ( node_id) ;
484+ ( mast, node_id)
485+ }
486+
399487// SERIALIZATION
400488// ================================================================================================
401489
@@ -461,4 +549,48 @@ mod tests {
461549 let stored = mast. advice_map ( ) . get ( & key) . expect ( "entry should be present" ) ;
462550 assert_eq ! ( stored. as_ref( ) , value. as_slice( ) ) ;
463551 }
552+
553+ #[ test]
554+ fn test_transaction_script_from_library ( ) {
555+ use assert_matches:: assert_matches;
556+
557+ use super :: TransactionScript ;
558+ use crate :: assembly:: Assembler ;
559+ use crate :: errors:: TransactionScriptError ;
560+ use crate :: utils:: serde:: { Deserializable , Serializable } ;
561+
562+ let source = "
563+ @transaction_script
564+ pub proc main
565+ push.1 drop
566+ end
567+ " ;
568+ let library = Assembler :: default ( ) . assemble_library ( [ source] ) . unwrap ( ) ;
569+
570+ let script = TransactionScript :: from_library ( & library) . unwrap ( ) ;
571+
572+ // the script must round-trip through serialization unchanged
573+ let bytes = script. to_bytes ( ) ;
574+ let decoded = TransactionScript :: read_from_bytes ( & bytes) . unwrap ( ) ;
575+ assert_eq ! ( script, decoded) ;
576+
577+ // a library without the attribute is rejected
578+ let no_attr = Assembler :: default ( )
579+ . assemble_library ( [ "pub proc main push.1 drop end" ] )
580+ . unwrap ( ) ;
581+ assert_matches ! (
582+ TransactionScript :: from_library( & no_attr) ,
583+ Err ( TransactionScriptError :: NoProcedureWithAttribute )
584+ ) ;
585+
586+ // a library with multiple tagged procedures is rejected
587+ let multiple = Assembler :: default ( )
588+ . assemble_library ( [ "@transaction_script pub proc main_a push.1 drop end
589+ @transaction_script pub proc main_b push.2 drop end" ] )
590+ . unwrap ( ) ;
591+ assert_matches ! (
592+ TransactionScript :: from_library( & multiple) ,
593+ Err ( TransactionScriptError :: MultipleProceduresWithAttribute )
594+ ) ;
595+ }
464596}
0 commit comments