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 Linkage,
13 Module,
14 ModuleKind,
15 ModuleParser,
16 Package,
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 CodeBuilderPackage {
35 fn as_code_builder_package(&self) -> &Package;
36}
37
38impl<T> CodeBuilderPackage for &T
39where
40 T: CodeBuilderPackage + ?Sized,
41{
42 fn as_code_builder_package(&self) -> &Package {
43 (*self).as_code_builder_package()
44 }
45}
46
47impl CodeBuilderPackage for Package {
48 fn as_code_builder_package(&self) -> &Package {
49 self
50 }
51}
52
53impl CodeBuilderPackage for Box<Package> {
54 fn as_code_builder_package(&self) -> &Package {
55 self
56 }
57}
58
59impl CodeBuilderPackage for AccountComponentCode {
60 fn as_code_builder_package(&self) -> &Package {
61 self.as_package()
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)]
253pub struct CodeBuilder {
254 assembler: Assembler,
255 source_manager: Arc<dyn SourceManagerSync>,
256 advice_map: AdviceMap,
257}
258
259impl CodeBuilder {
260 pub fn new() -> Self {
265 Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
266 }
267
268 pub fn with_source_manager(source_manager: Arc<dyn SourceManagerSync>) -> Self {
273 let mut assembler =
274 TransactionKernel::assembler_with_source_manager(source_manager.clone());
275 assembler
276 .link_package(StandardsLib::default().package(), Linkage::Dynamic)
277 .expect("linking std lib should work");
278 Self {
279 assembler,
280 source_manager,
281 advice_map: AdviceMap::default(),
282 }
283 }
284
285 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
293 self.assembler = self.assembler.with_warnings_as_errors(yes);
294 self
295 }
296
297 pub fn link_module(
315 &mut self,
316 module_path: impl AsRef<str>,
317 module_code: impl ToString,
318 ) -> Result<(), CodeBuilderError> {
319 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
320 let module = parser
321 .parse_str(Some(Path::new(module_path.as_ref())), module_code, self.source_manager())
322 .map_err(|err| {
323 CodeBuilderError::build_error_with_report("failed to parse module code", err)
324 })?;
325
326 self.assembler.compile_and_statically_link(module).map_err(|err| {
327 CodeBuilderError::build_error_with_report("failed to assemble module", err)
328 })?;
329
330 Ok(())
331 }
332
333 pub fn link_static_package(&mut self, package: &Package) -> Result<(), CodeBuilderError> {
345 self.assembler
346 .link_package(Arc::new(package.clone()), Linkage::Static)
347 .map_err(|err| {
348 CodeBuilderError::build_error_with_report("failed to add static package", err)
349 })
350 }
351
352 pub fn link_dynamic_package(&mut self, package: &Package) -> Result<(), CodeBuilderError> {
366 self.assembler
367 .link_package(Arc::new(package.clone()), Linkage::Dynamic)
368 .map_err(|err| {
369 CodeBuilderError::build_error_with_report("failed to add dynamic package", err)
370 })
371 }
372
373 pub fn with_statically_linked_package(
383 mut self,
384 package: &Package,
385 ) -> Result<Self, CodeBuilderError> {
386 self.link_static_package(package)?;
387 Ok(self)
388 }
389
390 pub fn with_dynamically_linked_package(
400 mut self,
401 package: impl CodeBuilderPackage,
402 ) -> Result<Self, CodeBuilderError> {
403 self.link_dynamic_package(package.as_code_builder_package())?;
404 Ok(self)
405 }
406
407 pub fn with_linked_module(
418 mut self,
419 module_path: impl AsRef<str>,
420 module_code: impl ToString,
421 ) -> Result<Self, CodeBuilderError> {
422 self.link_module(module_path, module_code)?;
423 Ok(self)
424 }
425
426 pub fn add_advice_map_entry(&mut self, key: Word, value: impl Into<Vec<Felt>>) {
438 self.advice_map.insert(key, value.into());
439 }
440
441 pub fn with_advice_map_entry(mut self, key: Word, value: impl Into<Vec<Felt>>) -> Self {
447 self.add_advice_map_entry(key, value);
448 self
449 }
450
451 pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
456 self.advice_map.extend(advice_map);
457 }
458
459 pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
464 self.extend_advice_map(advice_map);
465 self
466 }
467
468 fn apply_advice_map_to_package(advice_map: AdviceMap, package: Package) -> Package {
475 if advice_map.is_empty() {
476 package
477 } else {
478 package.with_advice_map(advice_map)
479 }
480 }
481
482 pub fn compile_component_code(
496 self,
497 component_path: impl AsRef<str>,
498 component_code: impl ToString,
499 ) -> Result<AccountComponentCode, CodeBuilderError> {
500 let CodeBuilder { assembler, source_manager, advice_map } = self;
501
502 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
503 let module = parser
504 .parse_str(Some(Path::new(component_path.as_ref())), component_code, source_manager)
505 .map_err(|err| {
506 CodeBuilderError::build_error_with_report("failed to parse component code", err)
507 })?;
508
509 let package = assembler
510 .assemble_library("account-component", module, None::<Box<Module>>)
511 .map_err(|err| {
512 CodeBuilderError::build_error_with_report("failed to parse component code", err)
513 })?;
514
515 Ok(AccountComponentCode::from(Self::apply_advice_map_to_package(
516 advice_map, *package,
517 )))
518 }
519
520 pub fn compile_tx_script(
532 self,
533 tx_script: impl CodeBuilderScriptSource,
534 ) -> Result<TransactionScript, CodeBuilderError> {
535 let CodeBuilder { assembler, source_manager, advice_map } = self;
536
537 let module = tx_script
538 .parse_script(
539 Path::new(TX_SCRIPT_MODULE_PATH),
540 assembler.warnings_as_errors(),
541 source_manager,
542 )
543 .map_err(|err| {
544 CodeBuilderError::build_error_with_report(
545 "failed to parse transaction script package",
546 err,
547 )
548 })?;
549
550 let tx_script_package = assembler
551 .assemble_library("transaction-script", module, None::<Box<Module>>)
552 .map_err(|err| {
553 CodeBuilderError::build_error_with_report(
554 "failed to parse transaction script package",
555 err,
556 )
557 })?;
558
559 let tx_script = TransactionScript::from_package(&tx_script_package).map_err(|err| {
560 CodeBuilderError::build_error_with_source(
561 "failed to create transaction script from package",
562 err,
563 )
564 })?;
565
566 Ok(tx_script.with_advice_map(advice_map))
567 }
568
569 pub fn compile_note_script(
581 self,
582 source: impl CodeBuilderScriptSource,
583 ) -> Result<NoteScript, CodeBuilderError> {
584 let CodeBuilder { assembler, source_manager, advice_map } = self;
585
586 let module = source
587 .parse_script(
588 Path::new(NOTE_SCRIPT_MODULE_PATH),
589 assembler.warnings_as_errors(),
590 source_manager,
591 )
592 .map_err(|err| {
593 CodeBuilderError::build_error_with_report(
594 "failed to parse note script package",
595 err,
596 )
597 })?;
598
599 let note_script_package = assembler
600 .assemble_library("note-script", module, None::<Box<Module>>)
601 .map_err(|err| {
602 CodeBuilderError::build_error_with_report(
603 "failed to parse note script package",
604 err,
605 )
606 })?;
607
608 let note_script = NoteScript::from_package(¬e_script_package).map_err(|err| {
609 CodeBuilderError::build_error_with_source(
610 "failed to create note script from package",
611 err,
612 )
613 })?;
614
615 Ok(note_script.with_advice_map(advice_map))
616 }
617
618 pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
623 self.source_manager.clone()
624 }
625
626 #[cfg(any(feature = "testing", test))]
637 pub fn with_kernel_core_package(source_manager: Arc<dyn SourceManagerSync>) -> Self {
638 let mut builder = Self::with_source_manager(source_manager);
639 builder
640 .link_dynamic_package(&TransactionKernel::core_package())
641 .expect("failed to link transaction kernel core package");
642 builder
643 }
644
645 #[cfg(any(feature = "testing", test))]
656 pub fn with_mock_packages() -> Self {
657 Self::with_mock_packages_with_source_manager(Arc::new(DefaultSourceManager::default()))
658 }
659
660 #[cfg(any(feature = "testing", test))]
662 pub fn mock_packages() -> impl Iterator<Item = Package> {
663 use miden_protocol::account::AccountCode;
664
665 use crate::testing::mock_account_code::MockAccountCodeExt;
666
667 vec![AccountCode::mock_account_package(), AccountCode::mock_faucet_package()].into_iter()
668 }
669
670 #[cfg(any(feature = "testing", test))]
671 pub fn with_mock_packages_with_source_manager(
672 source_manager: Arc<dyn SourceManagerSync>,
673 ) -> Self {
674 use crate::testing::mock_util_package::mock_util_package;
675
676 let mut builder = Self::with_kernel_core_package(source_manager);
679
680 for package in Self::mock_packages() {
682 builder
683 .link_dynamic_package(&package)
684 .expect("failed to link mock account packages");
685 }
686 builder
687 .link_static_package(&mock_util_package())
688 .expect("failed to link mock util package");
689
690 builder
691 }
692}
693
694impl Default for CodeBuilder {
695 fn default() -> Self {
696 Self::new()
697 }
698}
699
700impl From<CodeBuilder> for Assembler {
701 fn from(builder: CodeBuilder) -> Self {
702 builder.assembler
703 }
704}
705
706#[cfg(test)]
710mod tests {
711 use anyhow::Context;
712 use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT;
713
714 use super::*;
715
716 #[test]
717 fn test_code_builder_new() {
718 let _builder = CodeBuilder::default();
719 }
721
722 #[test]
723 fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> {
724 let builder = CodeBuilder::default();
725 builder
726 .compile_tx_script("@transaction_script pub proc main nop end")
727 .context("failed to parse basic tx script")?;
728 Ok(())
729 }
730
731 #[test]
732 fn test_create_package_and_create_tx_script() -> anyhow::Result<()> {
733 let script_code = "
734 use external_contract::counter_contract
735
736 @transaction_script
737 pub proc main
738 call.counter_contract::increment
739 end
740 ";
741
742 let account_code = "
743 use miden::protocol::active_account
744 use miden::protocol::native_account
745 use miden::core::sys
746
747 pub proc increment
748 push.0
749 exec.active_account::get_item
750 push.1 add
751 push.0
752 exec.native_account::set_item
753 exec.sys::truncate_stack
754 end
755 ";
756
757 let module_path = "external_contract::counter_contract";
758
759 let mut builder_with_lib = CodeBuilder::default();
760 builder_with_lib
761 .link_module(module_path, account_code)
762 .context("failed to link module")?;
763 builder_with_lib
764 .compile_tx_script(script_code)
765 .context("failed to parse tx script")?;
766
767 Ok(())
768 }
769
770 #[test]
771 fn test_parse_package_and_add_to_builder() -> anyhow::Result<()> {
772 let script_code = "
773 use external_contract::counter_contract
774
775 @transaction_script
776 pub proc main
777 call.counter_contract::increment
778 end
779 ";
780
781 let account_code = "
782 use miden::protocol::active_account
783 use miden::protocol::native_account
784 use miden::core::sys
785
786 pub proc increment
787 push.0
788 exec.active_account::get_item
789 push.1 add
790 push.0
791 exec.native_account::set_item
792 exec.sys::truncate_stack
793 end
794 ";
795
796 let module_path = "external_contract::counter_contract";
797
798 let mut builder_with_lib = CodeBuilder::default();
800 builder_with_lib
801 .link_module(module_path, account_code)
802 .context("failed to link module")?;
803 builder_with_lib
804 .compile_tx_script(script_code)
805 .context("failed to parse tx script")?;
806
807 let mut builder_with_libs = CodeBuilder::default();
809 builder_with_libs
810 .link_module(module_path, account_code)
811 .context("failed to link first module")?;
812 builder_with_libs
813 .link_module("test::lib", "pub proc test nop end")
814 .context("failed to link second module")?;
815 builder_with_libs
816 .compile_tx_script(script_code)
817 .context("failed to parse tx script with multiple modules")?;
818
819 Ok(())
820 }
821
822 #[test]
823 fn test_builder_style_chaining() -> anyhow::Result<()> {
824 let script_code = "
825 use external_contract::counter_contract
826
827 @transaction_script
828 pub proc main
829 call.counter_contract::increment
830 end
831 ";
832
833 let account_code = "
834 use miden::protocol::active_account
835 use miden::protocol::native_account
836 use miden::core::sys
837
838 pub proc increment
839 push.0
840 exec.active_account::get_item
841 push.1 add
842 push.0
843 exec.native_account::set_item
844 exec.sys::truncate_stack
845 end
846 ";
847
848 let builder = CodeBuilder::default()
850 .with_linked_module("external_contract::counter_contract", account_code)
851 .context("failed to link module")?;
852
853 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
854
855 Ok(())
856 }
857
858 #[test]
859 fn test_multiple_chained_modules() -> anyhow::Result<()> {
860 let script_code = "
861 use test::lib1
862 use test::lib2
863
864 @transaction_script
865 pub proc main
866 exec.lib1::test1
867 exec.lib2::test2
868 end
869 ";
870
871 let builder = CodeBuilder::default()
873 .with_linked_module("test::lib1", "pub proc test1 push.1 add end")
874 .context("failed to link first module")?
875 .with_linked_module("test::lib2", "pub proc test2 push.2 add end")
876 .context("failed to link second module")?;
877
878 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
879
880 Ok(())
881 }
882
883 #[test]
884 fn test_static_and_dynamic_linking() -> anyhow::Result<()> {
885 let script_code = "
886 use contracts::static_contract
887
888 @transaction_script
889 pub proc main
890 call.static_contract::increment_1
891 end
892 ";
893
894 let account_code_1 = "
895 pub proc increment_1
896 push.0 drop
897 end
898 ";
899
900 let account_code_2 = "
901 pub proc increment_2
902 push.0 drop
903 end
904 ";
905
906 let source_manager = Arc::new(DefaultSourceManager::default());
908 let mut parser = ModuleParser::new(Some(ModuleKind::Library));
909 let static_module = parser
910 .parse_str(
911 Some(Path::new("contracts::static_contract")),
912 account_code_1,
913 source_manager.clone(),
914 )
915 .map_err(|e| anyhow::anyhow!("failed to parse static package: {}", e))?;
916 let dynamic_module = parser
917 .parse_str(
918 Some(Path::new("contracts::dynamic_contract")),
919 account_code_2,
920 source_manager.clone(),
921 )
922 .map_err(|e| anyhow::anyhow!("failed to parse dynamic package: {}", e))?;
923 let temp_assembler = TransactionKernel::assembler_with_source_manager(source_manager);
924
925 let static_lib = temp_assembler
926 .clone()
927 .assemble_library("static-contract", static_module, None::<&str>)
928 .map_err(|e| anyhow::anyhow!("failed to assemble static package: {}", e))?;
929
930 let dynamic_lib = temp_assembler
931 .assemble_library("dynamic-contract", dynamic_module, None::<&str>)
932 .map_err(|e| anyhow::anyhow!("failed to assemble dynamic package: {}", e))?;
933
934 let builder = CodeBuilder::default()
936 .with_statically_linked_package(&static_lib)
937 .context("failed to link static package")?
938 .with_dynamically_linked_package(&dynamic_lib)
939 .context("failed to link dynamic package")?;
940
941 builder
942 .compile_tx_script(script_code)
943 .context("failed to parse tx script with static and dynamic packages")?;
944
945 Ok(())
946 }
947
948 #[test]
949 fn test_code_builder_warnings_as_errors() {
950 let assembler: Assembler = CodeBuilder::default().with_warnings_as_errors(true).into();
951 assert!(assembler.warnings_as_errors());
952 }
953
954 #[test]
955 fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> {
956 let key = Word::from([1u32, 2, 3, 4]);
957 let value = vec![Felt::new_unchecked(42), Felt::new_unchecked(43)];
958
959 let script = CodeBuilder::default()
960 .with_advice_map_entry(key, value.clone())
961 .compile_tx_script("@transaction_script pub proc main nop end")
962 .context("failed to compile tx script with advice map")?;
963
964 let mast = script.mast();
965 let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present");
966 assert_eq!(stored_value.as_ref(), value.as_slice());
967
968 Ok(())
969 }
970
971 #[test]
972 fn test_code_builder_extend_advice_map() -> anyhow::Result<()> {
973 let key1 = Word::from([1u32, 0, 0, 0]);
974 let key2 = Word::from([2u32, 0, 0, 0]);
975
976 let mut advice_map = AdviceMap::default();
977 advice_map.insert(key1, vec![Felt::ONE]);
978 advice_map.insert(key2, vec![Felt::new_unchecked(2)]);
979
980 let script = CodeBuilder::default()
981 .with_extended_advice_map(advice_map)
982 .compile_tx_script("@transaction_script pub proc main nop end")
983 .context("failed to compile tx script")?;
984
985 let mast = script.mast();
986 assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present");
987 assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present");
988
989 Ok(())
990 }
991
992 #[test]
993 fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> {
994 let key = Word::from([5u32, 6, 7, 8]);
995 let value = vec![Felt::new_unchecked(100)];
996
997 let script = CodeBuilder::default()
998 .with_advice_map_entry(key, value.clone())
999 .compile_note_script(DEFAULT_NOTE_SCRIPT)
1000 .context("failed to compile note script with advice map")?;
1001
1002 let mast = script.mast();
1003 let stored_value = mast
1004 .advice_map()
1005 .get(&key)
1006 .expect("advice map entry should be present in note script");
1007 assert_eq!(stored_value.as_ref(), value.as_slice());
1008
1009 Ok(())
1010 }
1011
1012 #[test]
1013 fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> {
1014 let key = Word::from([11u32, 22, 33, 44]);
1015 let value = vec![Felt::new_unchecked(500)];
1016
1017 let component_code = CodeBuilder::default()
1018 .with_advice_map_entry(key, value.clone())
1019 .compile_component_code("test::component", "pub proc test nop end")
1020 .context("failed to compile component code with advice map")?;
1021
1022 let mast = component_code.mast_forest();
1023 let stored_value = mast
1024 .advice_map()
1025 .get(&key)
1026 .expect("advice map entry should be present in component code");
1027 assert_eq!(stored_value.as_ref(), value.as_slice());
1028
1029 Ok(())
1030 }
1031}