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> {
395 let CodeBuilder { assembler, advice_map, .. } = self;
396
397 let program = assembler.assemble_program(source).map_err(|err| {
398 CodeBuilderError::build_error_with_report("failed to parse note script", err)
399 })?;
400
401 Ok(NoteScript::new(Self::apply_advice_map(advice_map, program)))
402 }
403
404 pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
409 self.source_manager.clone()
410 }
411
412 #[cfg(any(feature = "testing", test))]
423 pub fn with_kernel_library(source_manager: Arc<dyn SourceManagerSync>) -> Self {
424 let mut builder = Self::with_source_manager(source_manager);
425 builder
426 .link_dynamic_library(&TransactionKernel::library())
427 .expect("failed to link kernel library");
428 builder
429 }
430
431 #[cfg(any(feature = "testing", test))]
442 pub fn with_mock_libraries() -> Self {
443 Self::with_mock_libraries_with_source_manager(Arc::new(DefaultSourceManager::default()))
444 }
445
446 #[cfg(any(feature = "testing", test))]
448 pub fn mock_libraries() -> impl Iterator<Item = Library> {
449 use miden_protocol::account::AccountCode;
450
451 use crate::testing::mock_account_code::MockAccountCodeExt;
452
453 vec![AccountCode::mock_account_library(), AccountCode::mock_faucet_library()].into_iter()
454 }
455
456 #[cfg(any(feature = "testing", test))]
457 pub fn with_mock_libraries_with_source_manager(
458 source_manager: Arc<dyn SourceManagerSync>,
459 ) -> Self {
460 use crate::testing::mock_util_lib::mock_util_library;
461
462 let mut builder = Self::with_source_manager(source_manager);
465
466 builder
468 .link_dynamic_library(&TransactionKernel::library())
469 .expect("failed to link kernel library");
470
471 for library in Self::mock_libraries() {
473 builder
474 .link_dynamic_library(&library)
475 .expect("failed to link mock account libraries");
476 }
477 builder
478 .link_static_library(&mock_util_library())
479 .expect("failed to link mock util library");
480
481 builder
482 }
483}
484
485impl Default for CodeBuilder {
486 fn default() -> Self {
487 Self::new()
488 }
489}
490
491impl From<CodeBuilder> for Assembler {
492 fn from(builder: CodeBuilder) -> Self {
493 builder.assembler
494 }
495}
496
497#[cfg(test)]
501mod tests {
502 use anyhow::Context;
503 use miden_protocol::assembly::diagnostics::NamedSource;
504
505 use super::*;
506
507 #[test]
508 fn test_code_builder_new() {
509 let _builder = CodeBuilder::default();
510 }
512
513 #[test]
514 fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> {
515 let builder = CodeBuilder::default();
516 builder
517 .compile_tx_script("begin nop end")
518 .context("failed to parse basic tx script")?;
519 Ok(())
520 }
521
522 #[test]
523 fn test_create_library_and_create_tx_script() -> anyhow::Result<()> {
524 let script_code = "
525 use external_contract::counter_contract
526
527 begin
528 call.counter_contract::increment
529 end
530 ";
531
532 let account_code = "
533 use miden::protocol::active_account
534 use miden::protocol::native_account
535 use miden::core::sys
536
537 pub proc increment
538 push.0
539 exec.active_account::get_item
540 push.1 add
541 push.0
542 exec.native_account::set_item
543 exec.sys::truncate_stack
544 end
545 ";
546
547 let library_path = "external_contract::counter_contract";
548
549 let mut builder_with_lib = CodeBuilder::default();
550 builder_with_lib
551 .link_module(library_path, account_code)
552 .context("failed to link module")?;
553 builder_with_lib
554 .compile_tx_script(script_code)
555 .context("failed to parse tx script")?;
556
557 Ok(())
558 }
559
560 #[test]
561 fn test_parse_library_and_add_to_builder() -> anyhow::Result<()> {
562 let script_code = "
563 use external_contract::counter_contract
564
565 begin
566 call.counter_contract::increment
567 end
568 ";
569
570 let account_code = "
571 use miden::protocol::active_account
572 use miden::protocol::native_account
573 use miden::core::sys
574
575 pub proc increment
576 push.0
577 exec.active_account::get_item
578 push.1 add
579 push.0
580 exec.native_account::set_item
581 exec.sys::truncate_stack
582 end
583 ";
584
585 let library_path = "external_contract::counter_contract";
586
587 let mut builder_with_lib = CodeBuilder::default();
589 builder_with_lib
590 .link_module(library_path, account_code)
591 .context("failed to link module")?;
592 builder_with_lib
593 .compile_tx_script(script_code)
594 .context("failed to parse tx script")?;
595
596 let mut builder_with_libs = CodeBuilder::default();
598 builder_with_libs
599 .link_module(library_path, account_code)
600 .context("failed to link first module")?;
601 builder_with_libs
602 .link_module("test::lib", "pub proc test nop end")
603 .context("failed to link second module")?;
604 builder_with_libs
605 .compile_tx_script(script_code)
606 .context("failed to parse tx script with multiple libraries")?;
607
608 Ok(())
609 }
610
611 #[test]
612 fn test_builder_style_chaining() -> anyhow::Result<()> {
613 let script_code = "
614 use external_contract::counter_contract
615
616 begin
617 call.counter_contract::increment
618 end
619 ";
620
621 let account_code = "
622 use miden::protocol::active_account
623 use miden::protocol::native_account
624 use miden::core::sys
625
626 pub proc increment
627 push.0
628 exec.active_account::get_item
629 push.1 add
630 push.0
631 exec.native_account::set_item
632 exec.sys::truncate_stack
633 end
634 ";
635
636 let builder = CodeBuilder::default()
638 .with_linked_module("external_contract::counter_contract", account_code)
639 .context("failed to link module")?;
640
641 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
642
643 Ok(())
644 }
645
646 #[test]
647 fn test_multiple_chained_modules() -> anyhow::Result<()> {
648 let script_code =
649 "use test::lib1 use test::lib2 begin exec.lib1::test1 exec.lib2::test2 end";
650
651 let builder = CodeBuilder::default()
653 .with_linked_module("test::lib1", "pub proc test1 push.1 add end")
654 .context("failed to link first module")?
655 .with_linked_module("test::lib2", "pub proc test2 push.2 add end")
656 .context("failed to link second module")?;
657
658 builder.compile_tx_script(script_code).context("failed to parse tx script")?;
659
660 Ok(())
661 }
662
663 #[test]
664 fn test_static_and_dynamic_linking() -> anyhow::Result<()> {
665 let script_code = "
666 use contracts::static_contract
667
668 begin
669 call.static_contract::increment_1
670 end
671 ";
672
673 let account_code_1 = "
674 pub proc increment_1
675 push.0 drop
676 end
677 ";
678
679 let account_code_2 = "
680 pub proc increment_2
681 push.0 drop
682 end
683 ";
684
685 let temp_assembler = TransactionKernel::assembler();
687
688 let static_lib = temp_assembler
689 .clone()
690 .assemble_library([NamedSource::new("contracts::static_contract", account_code_1)])
691 .map_err(|e| anyhow::anyhow!("failed to assemble static library: {}", e))?;
692
693 let dynamic_lib = temp_assembler
694 .assemble_library([NamedSource::new("contracts::dynamic_contract", account_code_2)])
695 .map_err(|e| anyhow::anyhow!("failed to assemble dynamic library: {}", e))?;
696
697 let builder = CodeBuilder::default()
699 .with_statically_linked_library(&static_lib)
700 .context("failed to link static library")?
701 .with_dynamically_linked_library(&dynamic_lib)
702 .context("failed to link dynamic library")?;
703
704 builder
705 .compile_tx_script(script_code)
706 .context("failed to parse tx script with static and dynamic libraries")?;
707
708 Ok(())
709 }
710
711 #[test]
712 fn test_code_builder_warnings_as_errors() {
713 let assembler: Assembler = CodeBuilder::default().with_warnings_as_errors(true).into();
714 assert!(assembler.warnings_as_errors());
715 }
716
717 #[test]
718 fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> {
719 let key = Word::from([1u32, 2, 3, 4]);
720 let value = vec![Felt::new(42), Felt::new(43)];
721
722 let script = CodeBuilder::default()
723 .with_advice_map_entry(key, value.clone())
724 .compile_tx_script("begin nop end")
725 .context("failed to compile tx script with advice map")?;
726
727 let mast = script.mast();
728 let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present");
729 assert_eq!(stored_value.as_ref(), value.as_slice());
730
731 Ok(())
732 }
733
734 #[test]
735 fn test_code_builder_extend_advice_map() -> anyhow::Result<()> {
736 let key1 = Word::from([1u32, 0, 0, 0]);
737 let key2 = Word::from([2u32, 0, 0, 0]);
738
739 let mut advice_map = AdviceMap::default();
740 advice_map.insert(key1, vec![Felt::new(1)]);
741 advice_map.insert(key2, vec![Felt::new(2)]);
742
743 let script = CodeBuilder::default()
744 .with_extended_advice_map(advice_map)
745 .compile_tx_script("begin nop end")
746 .context("failed to compile tx script")?;
747
748 let mast = script.mast();
749 assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present");
750 assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present");
751
752 Ok(())
753 }
754
755 #[test]
756 fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> {
757 let key = Word::from([5u32, 6, 7, 8]);
758 let value = vec![Felt::new(100)];
759
760 let script = CodeBuilder::default()
761 .with_advice_map_entry(key, value.clone())
762 .compile_note_script("begin nop end")
763 .context("failed to compile note script with advice map")?;
764
765 let mast = script.mast();
766 let stored_value = mast
767 .advice_map()
768 .get(&key)
769 .expect("advice map entry should be present in note script");
770 assert_eq!(stored_value.as_ref(), value.as_slice());
771
772 Ok(())
773 }
774
775 #[test]
776 fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> {
777 let key = Word::from([11u32, 22, 33, 44]);
778 let value = vec![Felt::new(500)];
779
780 let component_code = CodeBuilder::default()
781 .with_advice_map_entry(key, value.clone())
782 .compile_component_code("test::component", "pub proc test nop end")
783 .context("failed to compile component code with advice map")?;
784
785 let mast = component_code.mast_forest();
786 let stored_value = mast
787 .advice_map()
788 .get(&key)
789 .expect("advice map entry should be present in component code");
790 assert_eq!(stored_value.as_ref(), value.as_slice());
791
792 Ok(())
793 }
794}