miden_standards/code_builder/
mod.rs1use alloc::sync::Arc;
2use alloc::vec::Vec;
3
4use miden_protocol::account::AccountComponentCode;
5use miden_protocol::assembly::{
6 Assembler,
7 DefaultSourceManager,
8 Library,
9 Parse,
10 ParseOptions,
11 Path,
12 SourceManagerSync,
13};
14use miden_protocol::note::NoteScript;
15use miden_protocol::transaction::{TransactionKernel, TransactionScript};
16use miden_protocol::vm::AdviceMap;
17use miden_protocol::{Felt, Word};
18
19use crate::errors::CodeBuilderError;
20use crate::standards_lib::StandardsLib;
21
22#[derive(Clone)]
84pub struct CodeBuilder {
85 assembler: Assembler,
86 source_manager: Arc<dyn SourceManagerSync>,
87 advice_map: AdviceMap,
88}
89
90impl CodeBuilder {
91 pub fn new() -> Self {
96 Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
97 }
98
99 pub fn with_source_manager(source_manager: Arc<dyn SourceManagerSync>) -> Self {
104 let assembler = TransactionKernel::assembler_with_source_manager(source_manager.clone())
105 .with_dynamic_library(StandardsLib::default())
106 .expect("linking std lib should work");
107 Self {
108 assembler,
109 source_manager,
110 advice_map: AdviceMap::default(),
111 }
112 }
113
114 pub fn with_warnings_as_errors(mut self, yes: bool) -> Self {
122 self.assembler = self.assembler.with_warnings_as_errors(yes);
123 self
124 }
125
126 pub fn link_module(
144 &mut self,
145 module_path: impl AsRef<str>,
146 module_code: impl Parse,
147 ) -> Result<(), CodeBuilderError> {
148 let mut parse_options = ParseOptions::for_library();
149 parse_options.path = Some(Path::new(module_path.as_ref()).into());
150
151 let module = module_code.parse_with_options(self.source_manager(), parse_options).map_err(
152 |err| CodeBuilderError::build_error_with_report("failed to parse module code", err),
153 )?;
154
155 self.assembler.compile_and_statically_link(module).map_err(|err| {
156 CodeBuilderError::build_error_with_report("failed to assemble module", err)
157 })?;
158
159 Ok(())
160 }
161
162 pub fn link_static_library(&mut self, library: &Library) -> Result<(), CodeBuilderError> {
174 self.assembler.link_static_library(library).map_err(|err| {
175 CodeBuilderError::build_error_with_report("failed to add static library", err)
176 })
177 }
178
179 pub fn link_dynamic_library(&mut self, library: &Library) -> Result<(), CodeBuilderError> {
193 self.assembler.link_dynamic_library(library).map_err(|err| {
194 CodeBuilderError::build_error_with_report("failed to add dynamic library", err)
195 })
196 }
197
198 pub fn with_statically_linked_library(
208 mut self,
209 library: &Library,
210 ) -> Result<Self, CodeBuilderError> {
211 self.link_static_library(library)?;
212 Ok(self)
213 }
214
215 pub fn with_dynamically_linked_library(
225 mut self,
226 library: impl AsRef<Library>,
227 ) -> Result<Self, CodeBuilderError> {
228 self.link_dynamic_library(library.as_ref())?;
229 Ok(self)
230 }
231
232 pub fn with_linked_module(
243 mut self,
244 module_path: impl AsRef<str>,
245 module_code: impl Parse,
246 ) -> Result<Self, CodeBuilderError> {
247 self.link_module(module_path, module_code)?;
248 Ok(self)
249 }
250
251 pub fn add_advice_map_entry(&mut self, key: Word, value: impl Into<Vec<Felt>>) {
263 self.advice_map.insert(key, value.into());
264 }
265
266 pub fn with_advice_map_entry(mut self, key: Word, value: impl Into<Vec<Felt>>) -> Self {
272 self.add_advice_map_entry(key, value);
273 self
274 }
275
276 pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
281 self.advice_map.extend(advice_map);
282 }
283
284 pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
289 self.extend_advice_map(advice_map);
290 self
291 }
292
293 fn apply_advice_map(
300 advice_map: AdviceMap,
301 program: miden_protocol::vm::Program,
302 ) -> miden_protocol::vm::Program {
303 if advice_map.is_empty() {
304 program
305 } else {
306 program.with_advice_map(advice_map)
307 }
308 }
309
310 fn apply_advice_map_to_library(advice_map: AdviceMap, library: Library) -> Library {
314 if advice_map.is_empty() {
315 library
316 } else {
317 library.with_advice_map(advice_map)
318 }
319 }
320
321 pub fn compile_component_code(
335 self,
336 component_path: impl AsRef<str>,
337 component_code: impl Parse,
338 ) -> Result<AccountComponentCode, CodeBuilderError> {
339 let CodeBuilder { assembler, source_manager, advice_map } = self;
340
341 let mut parse_options = ParseOptions::for_library();
342 parse_options.path = Some(Path::new(component_path.as_ref()).into());
343
344 let module =
345 component_code
346 .parse_with_options(source_manager, parse_options)
347 .map_err(|err| {
348 CodeBuilderError::build_error_with_report("failed to parse component code", err)
349 })?;
350
351 let library = assembler.assemble_library([module]).map_err(|err| {
352 CodeBuilderError::build_error_with_report("failed to parse component code", err)
353 })?;
354
355 Ok(AccountComponentCode::from(Self::apply_advice_map_to_library(
356 advice_map,
357 Arc::unwrap_or_clone(library),
358 )))
359 }
360
361 pub fn compile_tx_script(
372 self,
373 tx_script: impl Parse,
374 ) -> Result<TransactionScript, CodeBuilderError> {
375 let CodeBuilder { assembler, advice_map, .. } = self;
376
377 let program = assembler.assemble_program(tx_script).map_err(|err| {
378 CodeBuilderError::build_error_with_report("failed to parse transaction script", err)
379 })?;
380
381 Ok(TransactionScript::new(Self::apply_advice_map(advice_map, program)))
382 }
383
384 pub fn compile_note_script(self, source: impl Parse) -> Result<NoteScript, CodeBuilderError> {
396 let CodeBuilder { assembler, advice_map, .. } = self;
397
398 let note_script_lib = assembler.assemble_library([source]).map_err(|err| {
399 CodeBuilderError::build_error_with_report("failed to parse note script library", err)
400 })?;
401
402 NoteScript::from_library(&Self::apply_advice_map_to_library(
403 advice_map,
404 Arc::unwrap_or_clone(note_script_lib),
405 ))
406 .map_err(|err| {
407 CodeBuilderError::build_error_with_source(
408 "failed to create note script from library",
409 err,
410 )
411 })
412 }
413
414 pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
419 self.source_manager.clone()
420 }
421
422 #[cfg(any(feature = "testing", test))]
433 pub fn with_kernel_library(source_manager: Arc<dyn SourceManagerSync>) -> Self {
434 let mut builder = Self::with_source_manager(source_manager);
435 builder
436 .link_dynamic_library(&TransactionKernel::library())
437 .expect("failed to link kernel library");
438 builder
439 }
440
441 #[cfg(any(feature = "testing", test))]
452 pub fn with_mock_libraries() -> Self {
453 Self::with_mock_libraries_with_source_manager(Arc::new(DefaultSourceManager::default()))
454 }
455
456 #[cfg(any(feature = "testing", test))]
458 pub fn mock_libraries() -> impl Iterator<Item = Library> {
459 use miden_protocol::account::AccountCode;
460
461 use crate::testing::mock_account_code::MockAccountCodeExt;
462
463 vec![AccountCode::mock_account_library(), AccountCode::mock_faucet_library()].into_iter()
464 }
465
466 #[cfg(any(feature = "testing", test))]
467 pub fn with_mock_libraries_with_source_manager(
468 source_manager: Arc<dyn SourceManagerSync>,
469 ) -> Self {
470 use crate::testing::mock_util_lib::mock_util_library;
471
472 let mut builder = Self::with_source_manager(source_manager);
475
476 builder
478 .link_dynamic_library(&TransactionKernel::library())
479 .expect("failed to link kernel library");
480
481 for library in Self::mock_libraries() {
483 builder
484 .link_dynamic_library(&library)
485 .expect("failed to link mock account libraries");
486 }
487 builder
488 .link_static_library(&mock_util_library())
489 .expect("failed to link mock util library");
490
491 builder
492 }
493}
494
495impl Default for CodeBuilder {
496 fn default() -> Self {
497 Self::new()
498 }
499}
500
501impl From<CodeBuilder> for Assembler {
502 fn from(builder: CodeBuilder) -> Self {
503 builder.assembler
504 }
505}
506
507#[cfg(test)]
511mod tests {
512 use anyhow::Context;
513 use miden_protocol::assembly::diagnostics::NamedSource;
514 use miden_protocol::testing::note::DEFAULT_NOTE_SCRIPT;
515
516 use super::*;
517
518 #[test]
519 fn test_code_builder_new() {
520 let _builder = CodeBuilder::default();
521 }
523
524 #[test]
525 fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> {
526 let builder = CodeBuilder::default();
527 builder
528 .compile_tx_script("begin nop end")
529 .context("failed to parse basic tx script")?;
530 Ok(())
531 }
532
533 #[test]
534 fn test_create_library_and_create_tx_script() -> anyhow::Result<()> {
535 let script_code = "
536 use external_contract::counter_contract
537
538 begin
539 call.counter_contract::increment
540 end
541 ";
542
543 let account_code = "
544 use miden::protocol::active_account
545 use miden::protocol::native_account
546 use miden::core::sys
547
548 pub proc increment
549 push.0
550 exec.active_account::get_item
551 push.1 add
552 push.0
553 exec.native_account::set_item
554 exec.sys::truncate_stack
555 end
556 ";
557
558 let library_path = "external_contract::counter_contract";
559
560 let mut builder_with_lib = CodeBuilder::default();
561 builder_with_lib
562 .link_module(library_path, account_code)
563 .context("failed to link module")?;
564 builder_with_lib
565 .compile_tx_script(script_code)
566 .context("failed to parse tx script")?;
567
568 Ok(())
569 }
570
571 #[test]
572 fn test_parse_library_and_add_to_builder() -> anyhow::Result<()> {
573 let script_code = "
574 use external_contract::counter_contract
575
576 begin
577 call.counter_contract::increment
578 end
579 ";
580
581 let account_code = "
582 use miden::protocol::active_account
583 use miden::protocol::native_account
584 use miden::core::sys
585
586 pub proc increment
587 push.0
588 exec.active_account::get_item
589 push.1 add
590 push.0
591 exec.native_account::set_item
592 exec.sys::truncate_stack
593 end
594 ";
595
596 let library_path = "external_contract::counter_contract";
597
598 let mut builder_with_lib = CodeBuilder::default();
600 builder_with_lib
601 .link_module(library_path, account_code)
602 .context("failed to link module")?;
603 builder_with_lib
604 .compile_tx_script(script_code)
605 .context("failed to parse tx script")?;
606
607 let mut builder_with_libs = CodeBuilder::default();
609 builder_with_libs
610 .link_module(library_path, account_code)
611 .context("failed to link first module")?;
612 builder_with_libs
613 .link_module("test::lib", "pub proc test nop end")
614 .context("failed to link second module")?;
615 builder_with_libs
616 .compile_tx_script(script_code)
617 .context("failed to parse tx script with multiple libraries")?;
618
619 Ok(())
620 }
621
622 #[test]
623 fn test_builder_style_chaining() -> anyhow::Result<()> {
624 let script_code = "
625 use external_contract::counter_contract
626
627 begin
628 call.counter_contract::increment
629 end
630 ";
631
632 let account_code = "
633 use miden::protocol::active_account
634 use miden::protocol::native_account
635 use miden::core::sys
636
637 pub proc increment
638 push.0
639 exec.active_account::get_item
640 push.1 add
641 push.0
642 exec.native_account::set_item
643 exec.sys::truncate_stack
644 end
645 ";
646
647 let builder = CodeBuilder::default()
649 .with_linked_module("external_contract::counter_contract", account_code)
650 .context("failed to link module")?;
651
652 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
653
654 Ok(())
655 }
656
657 #[test]
658 fn test_multiple_chained_modules() -> anyhow::Result<()> {
659 let script_code =
660 "use test::lib1 use test::lib2 begin exec.lib1::test1 exec.lib2::test2 end";
661
662 let builder = CodeBuilder::default()
664 .with_linked_module("test::lib1", "pub proc test1 push.1 add end")
665 .context("failed to link first module")?
666 .with_linked_module("test::lib2", "pub proc test2 push.2 add end")
667 .context("failed to link second module")?;
668
669 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
670
671 Ok(())
672 }
673
674 #[test]
675 fn test_static_and_dynamic_linking() -> anyhow::Result<()> {
676 let script_code = "
677 use contracts::static_contract
678
679 begin
680 call.static_contract::increment_1
681 end
682 ";
683
684 let account_code_1 = "
685 pub proc increment_1
686 push.0 drop
687 end
688 ";
689
690 let account_code_2 = "
691 pub proc increment_2
692 push.0 drop
693 end
694 ";
695
696 let temp_assembler = TransactionKernel::assembler();
698
699 let static_lib = temp_assembler
700 .clone()
701 .assemble_library([NamedSource::new("contracts::static_contract", account_code_1)])
702 .map_err(|e| anyhow::anyhow!("failed to assemble static library: {}", e))?;
703
704 let dynamic_lib = temp_assembler
705 .assemble_library([NamedSource::new("contracts::dynamic_contract", account_code_2)])
706 .map_err(|e| anyhow::anyhow!("failed to assemble dynamic library: {}", e))?;
707
708 let builder = CodeBuilder::default()
710 .with_statically_linked_library(&static_lib)
711 .context("failed to link static library")?
712 .with_dynamically_linked_library(&dynamic_lib)
713 .context("failed to link dynamic library")?;
714
715 builder
716 .compile_tx_script(script_code)
717 .context("failed to parse tx script with static and dynamic libraries")?;
718
719 Ok(())
720 }
721
722 #[test]
723 fn test_code_builder_warnings_as_errors() {
724 let assembler: Assembler = CodeBuilder::default().with_warnings_as_errors(true).into();
725 assert!(assembler.warnings_as_errors());
726 }
727
728 #[test]
729 fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> {
730 let key = Word::from([1u32, 2, 3, 4]);
731 let value = vec![Felt::new(42), Felt::new(43)];
732
733 let script = CodeBuilder::default()
734 .with_advice_map_entry(key, value.clone())
735 .compile_tx_script("begin nop end")
736 .context("failed to compile tx script with advice map")?;
737
738 let mast = script.mast();
739 let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present");
740 assert_eq!(stored_value.as_ref(), value.as_slice());
741
742 Ok(())
743 }
744
745 #[test]
746 fn test_code_builder_extend_advice_map() -> anyhow::Result<()> {
747 let key1 = Word::from([1u32, 0, 0, 0]);
748 let key2 = Word::from([2u32, 0, 0, 0]);
749
750 let mut advice_map = AdviceMap::default();
751 advice_map.insert(key1, vec![Felt::new(1)]);
752 advice_map.insert(key2, vec![Felt::new(2)]);
753
754 let script = CodeBuilder::default()
755 .with_extended_advice_map(advice_map)
756 .compile_tx_script("begin nop end")
757 .context("failed to compile tx script")?;
758
759 let mast = script.mast();
760 assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present");
761 assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present");
762
763 Ok(())
764 }
765
766 #[test]
767 fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> {
768 let key = Word::from([5u32, 6, 7, 8]);
769 let value = vec![Felt::new(100)];
770
771 let script = CodeBuilder::default()
772 .with_advice_map_entry(key, value.clone())
773 .compile_note_script(DEFAULT_NOTE_SCRIPT)
774 .context("failed to compile note script with advice map")?;
775
776 let mast = script.mast();
777 let stored_value = mast
778 .advice_map()
779 .get(&key)
780 .expect("advice map entry should be present in note script");
781 assert_eq!(stored_value.as_ref(), value.as_slice());
782
783 Ok(())
784 }
785
786 #[test]
787 fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> {
788 let key = Word::from([11u32, 22, 33, 44]);
789 let value = vec![Felt::new(500)];
790
791 let component_code = CodeBuilder::default()
792 .with_advice_map_entry(key, value.clone())
793 .compile_component_code("test::component", "pub proc test nop end")
794 .context("failed to compile component code with advice map")?;
795
796 let mast = component_code.mast_forest();
797 let stored_value = mast
798 .advice_map()
799 .get(&key)
800 .expect("advice map entry should be present in component code");
801 assert_eq!(stored_value.as_ref(), value.as_slice());
802
803 Ok(())
804 }
805}