1use alloc::borrow::Cow;
2use alloc::boxed::Box;
3use alloc::string::{String, ToString};
4use alloc::sync::Arc;
5use alloc::vec::Vec;
6
7use miden_protocol::account::AccountComponentCode;
8use miden_protocol::assembly::diagnostics::Report;
9use miden_protocol::assembly::{
10 Assembler,
11 DefaultSourceManager,
12 Library,
13 Linkage,
14 Module,
15 ModuleKind,
16 ModuleParser,
17 Path,
18 SourceFile,
19 SourceManager,
20 SourceManagerSync,
21};
22use miden_protocol::note::NoteScript;
23use miden_protocol::transaction::{TransactionKernel, TransactionScript};
24use miden_protocol::vm::AdviceMap;
25use miden_protocol::{Felt, Word};
26
27use crate::errors::CodeBuilderError;
28use crate::standards_lib::StandardsLib;
29
30const NOTE_SCRIPT_MODULE_PATH: &str = "::note_script";
31const TX_SCRIPT_MODULE_PATH: &str = "::tx_script";
32
33pub trait CodeBuilderLibrary {
35 fn as_code_builder_library(&self) -> &Library;
36}
37
38impl<T> CodeBuilderLibrary for &T
39where
40 T: CodeBuilderLibrary + ?Sized,
41{
42 fn as_code_builder_library(&self) -> &Library {
43 (*self).as_code_builder_library()
44 }
45}
46
47impl CodeBuilderLibrary for Library {
48 fn as_code_builder_library(&self) -> &Library {
49 self
50 }
51}
52
53impl CodeBuilderLibrary for Box<Library> {
54 fn as_code_builder_library(&self) -> &Library {
55 self
56 }
57}
58
59impl CodeBuilderLibrary for AccountComponentCode {
60 fn as_code_builder_library(&self) -> &Library {
61 self.as_library()
62 }
63}
64
65pub trait CodeBuilderScriptSource {
67 fn parse_script(
70 self,
71 default_path: &Path,
72 warnings_as_errors: bool,
73 source_manager: Arc<dyn SourceManager>,
74 ) -> Result<Box<Module>, Report>;
75}
76
77fn parse_script_str(
78 source: impl AsRef<str>,
79 default_path: &Path,
80 warnings_as_errors: bool,
81 source_manager: Arc<dyn SourceManager>,
82) -> Result<Box<Module>, Report> {
83 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
84 parser.set_warnings_as_errors(warnings_as_errors);
85 parser.parse_str(Some(default_path), source.as_ref(), source_manager)
86}
87
88fn set_default_module_path(mut module: Box<Module>, default_path: &Path) -> Box<Module> {
89 if module.path().is_empty() {
90 module.set_path(default_path);
91 }
92 module
93}
94
95macro_rules! impl_script_source_for_str {
96 ($($source:ty),* $(,)?) => {
97 $(
98 impl CodeBuilderScriptSource for $source {
99 fn parse_script(
100 self,
101 default_path: &Path,
102 warnings_as_errors: bool,
103 source_manager: Arc<dyn SourceManager>,
104 ) -> Result<Box<Module>, Report> {
105 parse_script_str(self, default_path, warnings_as_errors, source_manager)
106 }
107 }
108 )*
109 };
110}
111
112impl_script_source_for_str!(&str, &String, String, Box<str>, Cow<'_, str>);
113
114impl CodeBuilderScriptSource for Arc<SourceFile> {
115 fn parse_script(
116 self,
117 default_path: &Path,
118 warnings_as_errors: bool,
119 source_manager: Arc<dyn SourceManager>,
120 ) -> Result<Box<Module>, Report> {
121 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
122 parser.set_warnings_as_errors(warnings_as_errors);
123 parser.parse(Some(default_path), self, source_manager)
124 }
125}
126
127impl CodeBuilderScriptSource for Module {
128 fn parse_script(
129 self,
130 default_path: &Path,
131 _warnings_as_errors: bool,
132 _source_manager: Arc<dyn SourceManager>,
133 ) -> Result<Box<Module>, Report> {
134 Ok(set_default_module_path(Box::new(self), default_path))
135 }
136}
137
138impl CodeBuilderScriptSource for Box<Module> {
139 fn parse_script(
140 self,
141 default_path: &Path,
142 _warnings_as_errors: bool,
143 _source_manager: Arc<dyn SourceManager>,
144 ) -> Result<Box<Module>, Report> {
145 Ok(set_default_module_path(self, default_path))
146 }
147}
148
149impl CodeBuilderScriptSource for Arc<Module> {
150 fn parse_script(
151 self,
152 default_path: &Path,
153 _warnings_as_errors: bool,
154 _source_manager: Arc<dyn SourceManager>,
155 ) -> Result<Box<Module>, Report> {
156 Ok(set_default_module_path(Box::new(Arc::unwrap_or_clone(self)), default_path))
157 }
158}
159
160#[cfg(feature = "std")]
161impl CodeBuilderScriptSource for &std::path::Path {
162 fn parse_script(
163 self,
164 default_path: &Path,
165 warnings_as_errors: bool,
166 source_manager: Arc<dyn SourceManager>,
167 ) -> Result<Box<Module>, Report> {
168 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
169 parser.set_warnings_as_errors(warnings_as_errors);
170 parser.parse_file(Some(default_path), self, source_manager)
171 }
172}
173
174#[cfg(feature = "std")]
175impl CodeBuilderScriptSource for std::path::PathBuf {
176 fn parse_script(
177 self,
178 default_path: &Path,
179 warnings_as_errors: bool,
180 source_manager: Arc<dyn SourceManager>,
181 ) -> Result<Box<Module>, Report> {
182 self.as_path().parse_script(default_path, warnings_as_errors, source_manager)
183 }
184}
185
186#[derive(Clone)]
249pub struct CodeBuilder {
250 assembler: Assembler,
251 source_manager: Arc<dyn SourceManagerSync>,
252 advice_map: AdviceMap,
253}
254
255impl CodeBuilder {
256 pub fn new() -> Self {
261 Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
262 }
263
264 pub fn with_source_manager(source_manager: Arc<dyn SourceManagerSync>) -> Self {
269 let mut assembler =
270 TransactionKernel::assembler_with_source_manager(source_manager.clone());
271 assembler
272 .link_package(Arc::new(StandardsLib::default().into()), Linkage::Dynamic)
273 .expect("linking std lib should work");
274 Self {
275 assembler,
276 source_manager,
277 advice_map: AdviceMap::default(),
278 }
279 }
280
281 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
289 self.assembler = self.assembler.with_warnings_as_errors(yes);
290 self
291 }
292
293 pub fn link_module(
311 &mut self,
312 module_path: impl AsRef<str>,
313 module_code: impl ToString,
314 ) -> Result<(), CodeBuilderError> {
315 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
316 let module = parser
317 .parse_str(Some(Path::new(module_path.as_ref())), module_code, self.source_manager())
318 .map_err(|err| {
319 CodeBuilderError::build_error_with_report("failed to parse module code", err)
320 })?;
321
322 self.assembler.compile_and_statically_link(module).map_err(|err| {
323 CodeBuilderError::build_error_with_report("failed to assemble module", err)
324 })?;
325
326 Ok(())
327 }
328
329 pub fn link_static_library(&mut self, library: &Library) -> Result<(), CodeBuilderError> {
341 self.assembler
342 .link_package(Arc::new(library.clone()), Linkage::Static)
343 .map_err(|err| {
344 CodeBuilderError::build_error_with_report("failed to add static library", err)
345 })
346 }
347
348 pub fn link_dynamic_library(&mut self, library: &Library) -> Result<(), CodeBuilderError> {
362 self.assembler
363 .link_package(Arc::new(library.clone()), Linkage::Dynamic)
364 .map_err(|err| {
365 CodeBuilderError::build_error_with_report("failed to add dynamic library", err)
366 })
367 }
368
369 pub fn with_statically_linked_library(
379 mut self,
380 library: &Library,
381 ) -> Result<Self, CodeBuilderError> {
382 self.link_static_library(library)?;
383 Ok(self)
384 }
385
386 pub fn with_dynamically_linked_library(
396 mut self,
397 library: impl CodeBuilderLibrary,
398 ) -> Result<Self, CodeBuilderError> {
399 self.link_dynamic_library(library.as_code_builder_library())?;
400 Ok(self)
401 }
402
403 pub fn with_linked_module(
414 mut self,
415 module_path: impl AsRef<str>,
416 module_code: impl ToString,
417 ) -> Result<Self, CodeBuilderError> {
418 self.link_module(module_path, module_code)?;
419 Ok(self)
420 }
421
422 pub fn add_advice_map_entry(&mut self, key: Word, value: impl Into<Vec<Felt>>) {
434 self.advice_map.insert(key, value.into());
435 }
436
437 pub fn with_advice_map_entry(mut self, key: Word, value: impl Into<Vec<Felt>>) -> Self {
443 self.add_advice_map_entry(key, value);
444 self
445 }
446
447 pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
452 self.advice_map.extend(advice_map);
453 }
454
455 pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
460 self.extend_advice_map(advice_map);
461 self
462 }
463
464 fn apply_advice_map_to_library(advice_map: AdviceMap, library: Library) -> Library {
471 if advice_map.is_empty() {
472 library
473 } else {
474 library.with_advice_map(advice_map)
475 }
476 }
477
478 pub fn compile_component_code(
492 self,
493 component_path: impl AsRef<str>,
494 component_code: impl ToString,
495 ) -> Result<AccountComponentCode, CodeBuilderError> {
496 let CodeBuilder { assembler, source_manager, advice_map } = self;
497
498 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
499 let module = parser
500 .parse_str(Some(Path::new(component_path.as_ref())), component_code, source_manager)
501 .map_err(|err| {
502 CodeBuilderError::build_error_with_report("failed to parse component code", err)
503 })?;
504
505 let library = assembler
506 .assemble_library("account-component", module, None::<Box<Module>>)
507 .map_err(|err| {
508 CodeBuilderError::build_error_with_report("failed to parse component code", err)
509 })?;
510
511 Ok(AccountComponentCode::from(Self::apply_advice_map_to_library(
512 advice_map, *library,
513 )))
514 }
515
516 pub fn compile_tx_script(
528 self,
529 tx_script: impl CodeBuilderScriptSource,
530 ) -> Result<TransactionScript, CodeBuilderError> {
531 let CodeBuilder { assembler, source_manager, advice_map } = self;
532
533 let module = tx_script
534 .parse_script(
535 Path::new(TX_SCRIPT_MODULE_PATH),
536 assembler.warnings_as_errors(),
537 source_manager,
538 )
539 .map_err(|err| {
540 CodeBuilderError::build_error_with_report(
541 "failed to parse transaction script library",
542 err,
543 )
544 })?;
545
546 let tx_script_lib = assembler
547 .assemble_library("transaction-script", module, None::<Box<Module>>)
548 .map_err(|err| {
549 CodeBuilderError::build_error_with_report(
550 "failed to parse transaction script library",
551 err,
552 )
553 })?;
554
555 let tx_script = TransactionScript::from_library(&tx_script_lib).map_err(|err| {
556 CodeBuilderError::build_error_with_source(
557 "failed to create transaction script from library",
558 err,
559 )
560 })?;
561
562 Ok(tx_script.with_advice_map(advice_map))
563 }
564
565 pub fn compile_note_script(
577 self,
578 source: impl CodeBuilderScriptSource,
579 ) -> Result<NoteScript, CodeBuilderError> {
580 let CodeBuilder { assembler, source_manager, advice_map } = self;
581
582 let module = source
583 .parse_script(
584 Path::new(NOTE_SCRIPT_MODULE_PATH),
585 assembler.warnings_as_errors(),
586 source_manager,
587 )
588 .map_err(|err| {
589 CodeBuilderError::build_error_with_report(
590 "failed to parse note script library",
591 err,
592 )
593 })?;
594
595 let note_script_lib = assembler
596 .assemble_library("note-script", module, None::<Box<Module>>)
597 .map_err(|err| {
598 CodeBuilderError::build_error_with_report(
599 "failed to parse note script library",
600 err,
601 )
602 })?;
603
604 let note_script = NoteScript::from_library(¬e_script_lib).map_err(|err| {
605 CodeBuilderError::build_error_with_source(
606 "failed to create note script from library",
607 err,
608 )
609 })?;
610
611 Ok(note_script.with_advice_map(advice_map))
612 }
613
614 pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
619 self.source_manager.clone()
620 }
621
622 #[cfg(any(feature = "testing", test))]
633 pub fn with_kernel_library(source_manager: Arc<dyn SourceManagerSync>) -> Self {
634 let mut builder = Self::with_source_manager(source_manager);
635 builder
636 .link_dynamic_library(&TransactionKernel::library())
637 .expect("failed to link transaction kernel library");
638 builder
639 }
640
641 #[cfg(any(feature = "testing", test))]
652 pub fn with_mock_libraries() -> Self {
653 Self::with_mock_libraries_with_source_manager(Arc::new(DefaultSourceManager::default()))
654 }
655
656 #[cfg(any(feature = "testing", test))]
658 pub fn mock_libraries() -> impl Iterator<Item = Library> {
659 use miden_protocol::account::AccountCode;
660
661 use crate::testing::mock_account_code::MockAccountCodeExt;
662
663 vec![AccountCode::mock_account_library(), AccountCode::mock_faucet_library()].into_iter()
664 }
665
666 #[cfg(any(feature = "testing", test))]
667 pub fn with_mock_libraries_with_source_manager(
668 source_manager: Arc<dyn SourceManagerSync>,
669 ) -> Self {
670 use crate::testing::mock_util_lib::mock_util_library;
671
672 let mut builder = Self::with_kernel_library(source_manager);
675
676 for library in Self::mock_libraries() {
678 builder
679 .link_dynamic_library(&library)
680 .expect("failed to link mock account libraries");
681 }
682 builder
683 .link_static_library(&mock_util_library())
684 .expect("failed to link mock util library");
685
686 builder
687 }
688}
689
690impl Default for CodeBuilder {
691 fn default() -> Self {
692 Self::new()
693 }
694}
695
696impl From<CodeBuilder> for Assembler {
697 fn from(builder: CodeBuilder) -> Self {
698 builder.assembler
699 }
700}
701
702#[cfg(test)]
706mod tests {
707 use anyhow::Context;
708 use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT;
709
710 use super::*;
711
712 #[test]
713 fn test_code_builder_new() {
714 let _builder = CodeBuilder::default();
715 }
717
718 #[test]
719 fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> {
720 let builder = CodeBuilder::default();
721 builder
722 .compile_tx_script("@transaction_script pub proc main nop end")
723 .context("failed to parse basic tx script")?;
724 Ok(())
725 }
726
727 #[test]
728 fn test_create_library_and_create_tx_script() -> anyhow::Result<()> {
729 let script_code = "
730 use external_contract::counter_contract
731
732 @transaction_script
733 pub proc main
734 call.counter_contract::increment
735 end
736 ";
737
738 let account_code = "
739 use miden::protocol::active_account
740 use miden::protocol::native_account
741 use miden::core::sys
742
743 pub proc increment
744 push.0
745 exec.active_account::get_item
746 push.1 add
747 push.0
748 exec.native_account::set_item
749 exec.sys::truncate_stack
750 end
751 ";
752
753 let library_path = "external_contract::counter_contract";
754
755 let mut builder_with_lib = CodeBuilder::default();
756 builder_with_lib
757 .link_module(library_path, account_code)
758 .context("failed to link module")?;
759 builder_with_lib
760 .compile_tx_script(script_code)
761 .context("failed to parse tx script")?;
762
763 Ok(())
764 }
765
766 #[test]
767 fn test_parse_library_and_add_to_builder() -> anyhow::Result<()> {
768 let script_code = "
769 use external_contract::counter_contract
770
771 @transaction_script
772 pub proc main
773 call.counter_contract::increment
774 end
775 ";
776
777 let account_code = "
778 use miden::protocol::active_account
779 use miden::protocol::native_account
780 use miden::core::sys
781
782 pub proc increment
783 push.0
784 exec.active_account::get_item
785 push.1 add
786 push.0
787 exec.native_account::set_item
788 exec.sys::truncate_stack
789 end
790 ";
791
792 let library_path = "external_contract::counter_contract";
793
794 let mut builder_with_lib = CodeBuilder::default();
796 builder_with_lib
797 .link_module(library_path, account_code)
798 .context("failed to link module")?;
799 builder_with_lib
800 .compile_tx_script(script_code)
801 .context("failed to parse tx script")?;
802
803 let mut builder_with_libs = CodeBuilder::default();
805 builder_with_libs
806 .link_module(library_path, account_code)
807 .context("failed to link first module")?;
808 builder_with_libs
809 .link_module("test::lib", "pub proc test nop end")
810 .context("failed to link second module")?;
811 builder_with_libs
812 .compile_tx_script(script_code)
813 .context("failed to parse tx script with multiple libraries")?;
814
815 Ok(())
816 }
817
818 #[test]
819 fn test_builder_style_chaining() -> anyhow::Result<()> {
820 let script_code = "
821 use external_contract::counter_contract
822
823 @transaction_script
824 pub proc main
825 call.counter_contract::increment
826 end
827 ";
828
829 let account_code = "
830 use miden::protocol::active_account
831 use miden::protocol::native_account
832 use miden::core::sys
833
834 pub proc increment
835 push.0
836 exec.active_account::get_item
837 push.1 add
838 push.0
839 exec.native_account::set_item
840 exec.sys::truncate_stack
841 end
842 ";
843
844 let builder = CodeBuilder::default()
846 .with_linked_module("external_contract::counter_contract", account_code)
847 .context("failed to link module")?;
848
849 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
850
851 Ok(())
852 }
853
854 #[test]
855 fn test_multiple_chained_modules() -> anyhow::Result<()> {
856 let script_code = "
857 use test::lib1
858 use test::lib2
859
860 @transaction_script
861 pub proc main
862 exec.lib1::test1
863 exec.lib2::test2
864 end
865 ";
866
867 let builder = CodeBuilder::default()
869 .with_linked_module("test::lib1", "pub proc test1 push.1 add end")
870 .context("failed to link first module")?
871 .with_linked_module("test::lib2", "pub proc test2 push.2 add end")
872 .context("failed to link second module")?;
873
874 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
875
876 Ok(())
877 }
878
879 #[test]
880 fn test_static_and_dynamic_linking() -> anyhow::Result<()> {
881 let script_code = "
882 use contracts::static_contract
883
884 @transaction_script
885 pub proc main
886 call.static_contract::increment_1
887 end
888 ";
889
890 let account_code_1 = "
891 pub proc increment_1
892 push.0 drop
893 end
894 ";
895
896 let account_code_2 = "
897 pub proc increment_2
898 push.0 drop
899 end
900 ";
901
902 let source_manager = Arc::new(DefaultSourceManager::default());
904 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
905 let static_module = parser
906 .parse_str(
907 Some(Path::new("contracts::static_contract")),
908 account_code_1,
909 source_manager.clone(),
910 )
911 .map_err(|e| anyhow::anyhow!("failed to parse static library: {}", e))?;
912 let dynamic_module = parser
913 .parse_str(
914 Some(Path::new("contracts::dynamic_contract")),
915 account_code_2,
916 source_manager.clone(),
917 )
918 .map_err(|e| anyhow::anyhow!("failed to parse dynamic library: {}", e))?;
919 let temp_assembler = TransactionKernel::assembler_with_source_manager(source_manager);
920
921 let static_lib = temp_assembler
922 .clone()
923 .assemble_library("static-contract", static_module, None::<&str>)
924 .map_err(|e| anyhow::anyhow!("failed to assemble static library: {}", e))?;
925
926 let dynamic_lib = temp_assembler
927 .assemble_library("dynamic-contract", dynamic_module, None::<&str>)
928 .map_err(|e| anyhow::anyhow!("failed to assemble dynamic library: {}", e))?;
929
930 let builder = CodeBuilder::default()
932 .with_statically_linked_library(&static_lib)
933 .context("failed to link static library")?
934 .with_dynamically_linked_library(&dynamic_lib)
935 .context("failed to link dynamic library")?;
936
937 builder
938 .compile_tx_script(script_code)
939 .context("failed to parse tx script with static and dynamic libraries")?;
940
941 Ok(())
942 }
943
944 #[test]
945 fn test_code_builder_warnings_as_errors() {
946 let assembler: Assembler = CodeBuilder::default().with_warnings_as_errors(true).into();
947 assert!(assembler.warnings_as_errors());
948 }
949
950 #[test]
951 fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> {
952 let key = Word::from([1u32, 2, 3, 4]);
953 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
954
955 let script = CodeBuilder::default()
956 .with_advice_map_entry(key, value.clone())
957 .compile_tx_script("@transaction_script pub proc main nop end")
958 .context("failed to compile tx script with advice map")?;
959
960 let mast = script.mast();
961 let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present");
962 assert_eq!(stored_value.as_ref(), value.as_slice());
963
964 Ok(())
965 }
966
967 #[test]
968 fn test_code_builder_extend_advice_map() -> anyhow::Result<()> {
969 let key1 = Word::from([1u32, 0, 0, 0]);
970 let key2 = Word::from([2u32, 0, 0, 0]);
971
972 let mut advice_map = AdviceMap::default();
973 advice_map.insert(key1, vec![Felt::ONE]);
974 advice_map.insert(key2, vec![Felt::new_unchecked(2)]);
975
976 let script = CodeBuilder::default()
977 .with_extended_advice_map(advice_map)
978 .compile_tx_script("@transaction_script pub proc main nop end")
979 .context("failed to compile tx script")?;
980
981 let mast = script.mast();
982 assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present");
983 assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present");
984
985 Ok(())
986 }
987
988 #[test]
989 fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> {
990 let key = Word::from([5u32, 6, 7, 8]);
991 let value = vec![Felt::new_unchecked(100)];
992
993 let script = CodeBuilder::default()
994 .with_advice_map_entry(key, value.clone())
995 .compile_note_script(DEFAULT_NOTE_SCRIPT)
996 .context("failed to compile note script with advice map")?;
997
998 let mast = script.mast();
999 let stored_value = mast
1000 .advice_map()
1001 .get(&key)
1002 .expect("advice map entry should be present in note script");
1003 assert_eq!(stored_value.as_ref(), value.as_slice());
1004
1005 Ok(())
1006 }
1007
1008 #[test]
1009 fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> {
1010 let key = Word::from([11u32, 22, 33, 44]);
1011 let value = vec![Felt::new_unchecked(500)];
1012
1013 let component_code = CodeBuilder::default()
1014 .with_advice_map_entry(key, value.clone())
1015 .compile_component_code("test::component", "pub proc test nop end")
1016 .context("failed to compile component code with advice map")?;
1017
1018 let mast = component_code.mast_forest();
1019 let stored_value = mast
1020 .advice_map()
1021 .get(&key)
1022 .expect("advice map entry should be present in component code");
1023 assert_eq!(stored_value.as_ref(), value.as_slice());
1024
1025 Ok(())
1026 }
1027}