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