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 ;
@@ -13,9 +13,11 @@ use miden_processor::LoadedMastForest;
1313
1414use super :: { Felt , Hasher , Word } ;
1515use crate :: account:: auth:: { PublicKeyCommitment , Signature } ;
16+ use crate :: assembly:: { Library , Path } ;
1617use crate :: errors:: TransactionScriptError ;
1718use crate :: note:: { NoteId , NoteRecipient } ;
1819use crate :: package:: { loaded_mast_forest, package_debug_info} ;
20+ use crate :: utils:: create_external_node_forest;
1921use crate :: utils:: serde:: {
2022 ByteReader ,
2123 ByteWriter ,
@@ -321,6 +323,9 @@ impl Deserializable for TransactionScriptRoot {
321323// TRANSACTION SCRIPT
322324// ================================================================================================
323325
326+ /// The attribute name used to mark the entrypoint procedure in a transaction script library.
327+ pub const TRANSACTION_SCRIPT_ATTRIBUTE : & str = "transaction_script" ;
328+
324329/// Transaction script.
325330///
326331/// A transaction script is a program that is executed in a transaction after all input notes
@@ -340,6 +345,8 @@ impl TransactionScript {
340345 // --------------------------------------------------------------------------------------------
341346
342347 /// Returns a new [TransactionScript] instantiated with the provided code.
348+ // TODO: we can remove this `Program` based constructor once the compiler integrates the
349+ // `@transaction_script` attribute (https://github.com/0xMiden/compiler/issues/1190).
343350 pub fn new ( code : Program ) -> Self {
344351 Self :: from_parts ( code. mast_forest ( ) . clone ( ) , code. entrypoint ( ) )
345352 }
@@ -376,6 +383,88 @@ impl TransactionScript {
376383 } )
377384 }
378385
386+ /// Returns a new [TransactionScript] instantiated from the provided library.
387+ ///
388+ /// The library must contain exactly one procedure with the `@transaction_script` attribute,
389+ /// which will be used as the entrypoint.
390+ ///
391+ /// # Errors
392+ /// Returns an error if:
393+ /// - The library does not contain a procedure with the `@transaction_script` attribute.
394+ /// - The library contains multiple procedures with the `@transaction_script` attribute.
395+ pub fn from_library ( library : & Library ) -> Result < Self , TransactionScriptError > {
396+ let mut entrypoint = None ;
397+
398+ for export in library. manifest . exports ( ) {
399+ if let Some ( proc_export) = export. as_procedure ( )
400+ && proc_export. attributes . has ( TRANSACTION_SCRIPT_ATTRIBUTE )
401+ {
402+ if entrypoint. is_some ( ) {
403+ return Err ( TransactionScriptError :: MultipleProceduresWithAttribute ) ;
404+ }
405+ entrypoint =
406+ Some ( proc_export. node . ok_or ( TransactionScriptError :: NoProcedureWithAttribute ) ?) ;
407+ }
408+ }
409+
410+ let entrypoint = entrypoint. ok_or ( TransactionScriptError :: NoProcedureWithAttribute ) ?;
411+
412+ Ok ( Self {
413+ mast : library. mast_forest ( ) . clone ( ) ,
414+ entrypoint,
415+ package_debug_info : package_debug_info ( library) ,
416+ } )
417+ }
418+
419+ /// Returns a new [TransactionScript] containing only a reference to a procedure in the
420+ /// provided library.
421+ ///
422+ /// This method is useful when a library contains multiple transaction scripts and you need
423+ /// to extract a specific one by its fully qualified path (e.g.,
424+ /// `::miden::standards::tx_scripts::send_notes::main`).
425+ ///
426+ /// The procedure at the specified path must have the `@transaction_script` attribute.
427+ ///
428+ /// Note: This method creates a minimal [MastForest] containing only an external node
429+ /// referencing the procedure's digest, rather than copying the entire library. The actual
430+ /// procedure code will be resolved at runtime via the `MastForestStore`.
431+ ///
432+ /// # Errors
433+ /// Returns an error if:
434+ /// - The library does not contain a procedure at the specified path.
435+ /// - The procedure at the specified path does not have the `@transaction_script` attribute.
436+ pub fn from_library_reference (
437+ library : & Library ,
438+ path : & Path ,
439+ ) -> Result < Self , TransactionScriptError > {
440+ // Find the export matching the path
441+ let export =
442+ library. manifest . exports ( ) . find ( |e| e. path ( ) . as_ref ( ) == path) . ok_or_else ( || {
443+ TransactionScriptError :: ProcedureNotFound ( path. to_string ( ) . into ( ) )
444+ } ) ?;
445+
446+ // Get the procedure export and verify it has the @transaction_script attribute
447+ let proc_export = export
448+ . as_procedure ( )
449+ . ok_or_else ( || TransactionScriptError :: ProcedureNotFound ( path. to_string ( ) . into ( ) ) ) ?;
450+
451+ if !proc_export. attributes . has ( TRANSACTION_SCRIPT_ATTRIBUTE ) {
452+ return Err ( TransactionScriptError :: ProcedureMissingAttribute ( path. to_string ( ) . into ( ) ) ) ;
453+ }
454+
455+ // Get the digest of the procedure from the library
456+ let digest = proc_export. digest ;
457+
458+ // Create a minimal MastForest with just an external node referencing the digest
459+ let ( mast, entrypoint) = create_external_node_forest ( digest) ;
460+
461+ Ok ( Self {
462+ mast : Arc :: new ( mast) ,
463+ entrypoint,
464+ package_debug_info : package_debug_info ( library) ,
465+ } )
466+ }
467+
379468 // PUBLIC ACCESSORS
380469 // --------------------------------------------------------------------------------------------
381470
@@ -503,4 +592,116 @@ mod tests {
503592 let stored = mast. advice_map ( ) . get ( & key) . expect ( "entry should be present" ) ;
504593 assert_eq ! ( stored. as_ref( ) , value. as_slice( ) ) ;
505594 }
595+
596+ #[ test]
597+ fn test_transaction_script_from_library ( ) {
598+ use assert_matches:: assert_matches;
599+
600+ use super :: TransactionScript ;
601+ use crate :: errors:: TransactionScriptError ;
602+ use crate :: testing:: assembler:: assemble_test_library;
603+ use crate :: utils:: serde:: { Deserializable , Serializable } ;
604+
605+ let source = "
606+ @transaction_script
607+ pub proc main
608+ push.1 drop
609+ end
610+ " ;
611+ let library = assemble_test_library ( "test-tx-script" , "test::tx_script" , source) ;
612+
613+ let script = TransactionScript :: from_library ( & library) . unwrap ( ) ;
614+
615+ // the script must round-trip through serialization unchanged
616+ let bytes = script. to_bytes ( ) ;
617+ let decoded = TransactionScript :: read_from_bytes ( & bytes) . unwrap ( ) ;
618+ assert_eq ! ( script, decoded) ;
619+
620+ // a library without the attribute is rejected
621+ let no_attr = assemble_test_library (
622+ "test-tx-script-no-attr" ,
623+ "test::tx_script_no_attr" ,
624+ "pub proc main push.1 drop end" ,
625+ ) ;
626+ assert_matches ! (
627+ TransactionScript :: from_library( & no_attr) ,
628+ Err ( TransactionScriptError :: NoProcedureWithAttribute )
629+ ) ;
630+
631+ // a library with multiple tagged procedures is rejected
632+ let multiple = assemble_test_library (
633+ "test-tx-script-multiple" ,
634+ "test::tx_script_multiple" ,
635+ "@transaction_script pub proc main_a push.1 drop end
636+ @transaction_script pub proc main_b push.2 drop end" ,
637+ ) ;
638+ assert_matches ! (
639+ TransactionScript :: from_library( & multiple) ,
640+ Err ( TransactionScriptError :: MultipleProceduresWithAttribute )
641+ ) ;
642+ }
643+
644+ #[ test]
645+ fn test_transaction_script_from_library_reference ( ) {
646+ use alloc:: string:: ToString ;
647+
648+ use assert_matches:: assert_matches;
649+
650+ use super :: TransactionScript ;
651+ use crate :: Word ;
652+ use crate :: assembly:: Path ;
653+ use crate :: errors:: TransactionScriptError ;
654+ use crate :: testing:: assembler:: assemble_test_library;
655+
656+ let source = "
657+ @transaction_script
658+ pub proc main_a
659+ push.1 drop
660+ end
661+
662+ @transaction_script
663+ pub proc main_b
664+ push.2 drop
665+ end
666+
667+ pub proc helper
668+ push.3 drop
669+ end
670+ " ;
671+ let library =
672+ assemble_test_library ( "test-tx-script-reference" , "test::tx_script_reference" , source) ;
673+
674+ // each tagged procedure can be extracted selectively, and the resulting script's root
675+ // matches the digest of the referenced procedure
676+ for proc_name in [ "main_a" , "main_b" ] {
677+ let export = library
678+ . manifest
679+ . exports ( )
680+ . find ( |e| e. path ( ) . as_ref ( ) . to_string ( ) . ends_with ( proc_name) )
681+ . unwrap ( ) ;
682+ let digest = export. as_procedure ( ) . unwrap ( ) . digest ;
683+
684+ let script =
685+ TransactionScript :: from_library_reference ( & library, export. path ( ) . as_ref ( ) )
686+ . unwrap ( ) ;
687+ assert_eq ! ( Word :: from( script. root( ) ) , digest) ;
688+ }
689+
690+ // an unknown path is rejected
691+ assert_matches ! (
692+ TransactionScript :: from_library_reference( & library, Path :: new( "::foo::bar::main" ) ) ,
693+ Err ( TransactionScriptError :: ProcedureNotFound ( _) )
694+ ) ;
695+
696+ // a procedure without the attribute is rejected
697+ let helper = library
698+ . manifest
699+ . exports ( )
700+ . find ( |e| e. path ( ) . as_ref ( ) . to_string ( ) . ends_with ( "helper" ) )
701+ . unwrap ( ) ;
702+ assert_matches ! (
703+ TransactionScript :: from_library_reference( & library, helper. path( ) . as_ref( ) ) ,
704+ Err ( TransactionScriptError :: ProcedureMissingAttribute ( _) )
705+ ) ;
706+ }
506707}
0 commit comments