Skip to main content

miden_standards/code_builder/
mod.rs

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
33/// A value that can provide a compiled Miden library to the code builder.
34pub 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
65/// A source value that can be compiled into a note or transaction script.
66pub trait CodeBuilderScriptSource {
67    /// Parses this source into a library module, assigning `default_path` as the module path
68    /// when the source does not provide one.
69    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// CODE BUILDER
187// ================================================================================================
188
189/// A builder for compiling account components, note scripts, and transaction scripts with optional
190/// library dependencies.
191///
192/// The [`CodeBuilder`] simplifies the process of creating transaction scripts by providing:
193/// - A clean API for adding multiple libraries with static or dynamic linking
194/// - Automatic assembler configuration with all added libraries
195/// - Debug mode support
196/// - Builder pattern support for method chaining
197///
198/// ## Static vs Dynamic Linking
199///
200/// **Static Linking** (`link_static_library()` / `with_statically_linked_library()`):
201/// - Use when you control and know the library code
202/// - The library code is copied into the script code
203/// - Best for most user-written libraries and dependencies
204/// - Results in larger script size but ensures the code is always available
205///
206/// **Dynamic Linking** (`link_dynamic_library()` / `with_dynamically_linked_library()`):
207/// - Use when making Foreign Procedure Invocation (FPI) calls
208/// - The library code is available on-chain and referenced, not copied
209/// - Results in smaller script size but requires the code to be available on-chain
210///
211/// ## Typical Workflow
212///
213/// 1. Create a new CodeBuilder with debug mode preference
214/// 2. Add any required modules using `link_module()` or `with_linked_module()`
215/// 3. Add libraries using `link_static_library()` / `link_dynamic_library()` as appropriate
216/// 4. Compile your script with `compile_note_script()` or `compile_tx_script()`
217///
218/// Note that the compiling methods consume the CodeBuilder, so if you need to compile
219/// multiple scripts with the same configuration, you should clone the builder first.
220///
221/// ## Builder Pattern Example
222///
223/// ```no_run
224/// # use anyhow::Context;
225/// # use miden_standards::code_builder::CodeBuilder;
226/// # use miden_standards::StandardsLib;
227/// # use miden_protocol::assembly::Library;
228/// # use miden_protocol::ProtocolLib;
229/// # fn example() -> anyhow::Result<()> {
230/// # let module_code = "pub proc test push.1 add end";
231/// # let script_code = "@transaction_script pub proc main nop end";
232/// # // Create sample libraries for the example
233/// # let my_lib: Library = StandardsLib::default().into();
234/// # let fpi_lib: Library = ProtocolLib::default().into();
235/// let script = CodeBuilder::default()
236///     .with_linked_module("my::module", module_code).context("failed to link module")?
237///     .with_statically_linked_library(&my_lib).context("failed to link static library")?
238///     .with_dynamically_linked_library(&fpi_lib).context("failed to link dynamic library")?  // For FPI calls
239///     .compile_tx_script(script_code).context("failed to parse tx script")?;
240/// # Ok(())
241/// # }
242/// ```
243///
244/// # Note
245/// The CodeBuilder automatically includes the `miden` and `std` libraries, which
246/// provide access to transaction kernel procedures. Due to being available on-chain
247/// these libraries are linked dynamically and do not add to the size of built script.
248#[derive(Clone)]
249pub struct CodeBuilder {
250    assembler: Assembler,
251    source_manager: Arc<dyn SourceManagerSync>,
252    advice_map: AdviceMap,
253}
254
255impl CodeBuilder {
256    // CONSTRUCTORS
257    // --------------------------------------------------------------------------------------------
258
259    /// Creates a new CodeBuilder.
260    pub fn new() -> Self {
261        Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
262    }
263
264    /// Creates a new CodeBuilder with the specified source manager.
265    ///
266    /// # Arguments
267    /// * `source_manager` - The source manager to use with the internal `Assembler`
268    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    // CONFIGURATION
282    // --------------------------------------------------------------------------------------------
283
284    /// Configures the assembler to treat warning diagnostics as errors.
285    ///
286    /// When enabled, any warning emitted during compilation will be promoted to an error,
287    /// causing the compilation to fail.
288    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    // LIBRARY MANAGEMENT
294    // --------------------------------------------------------------------------------------------
295
296    /// Parses and links a module to the code builder.
297    ///
298    /// This method compiles the provided module code and adds it directly to the assembler
299    /// for use in script compilation.
300    ///
301    /// # Arguments
302    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
303    /// * `module_code` - The source code of the module to compile and link
304    ///
305    /// # Errors
306    /// Returns an error if:
307    /// - The module path is invalid
308    /// - The module code cannot be parsed
309    /// - The module cannot be assembled
310    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    /// Statically links the given library.
330    ///
331    /// Static linking means the library code is copied into the script code.
332    /// Use this for most libraries that are not available on-chain.
333    ///
334    /// # Arguments
335    /// * `library` - The compiled library to statically link
336    ///
337    /// # Errors
338    /// Returns an error if:
339    /// - adding the library to the assembler failed
340    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    /// Dynamically links a library.
349    ///
350    /// This is useful to dynamically link the [`Library`] of a foreign account
351    /// that is invoked using foreign procedure invocation (FPI). Its code is available
352    /// on-chain and so it does not have to be copied into the script code.
353    ///
354    /// For all other use cases not involving FPI, link the library statically.
355    ///
356    /// # Arguments
357    /// * `library` - The compiled library to dynamically link
358    ///
359    /// # Errors
360    /// Returns an error if the library cannot be added to the assembler
361    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    /// Builder-style method to statically link a library and return the modified builder.
370    ///
371    /// This enables method chaining for convenient builder patterns.
372    ///
373    /// # Arguments
374    /// * `library` - The compiled library to statically link
375    ///
376    /// # Errors
377    /// Returns an error if the library cannot be added to the assembler
378    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    /// Builder-style method to dynamically link a library and return the modified builder.
387    ///
388    /// This enables method chaining for convenient builder patterns.
389    ///
390    /// # Arguments
391    /// * `library` - The compiled library to dynamically link
392    ///
393    /// # Errors
394    /// Returns an error if the library cannot be added to the assembler
395    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    /// Builder-style method to link a module and return the modified builder.
404    ///
405    /// This enables method chaining for convenient builder patterns.
406    ///
407    /// # Arguments
408    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
409    /// * `module_code` - The source code of the module to compile and link
410    ///
411    /// # Errors
412    /// Returns an error if the module cannot be compiled or added to the assembler
413    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    // ADVICE MAP MANAGEMENT
423    // --------------------------------------------------------------------------------------------
424
425    /// Adds an entry to the advice map that will be included in compiled scripts.
426    ///
427    /// The advice map allows passing non-deterministic inputs to the VM that can be
428    /// accessed using `adv.push_mapval` instruction.
429    ///
430    /// # Arguments
431    /// * `key` - The key for the advice map entry (a Word)
432    /// * `value` - The values to associate with this key
433    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    /// Builder-style method to add an advice map entry.
438    ///
439    /// # Arguments
440    /// * `key` - The key for the advice map entry (a Word)
441    /// * `value` - The values to associate with this key
442    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    /// Extends the advice map with entries from another advice map.
448    ///
449    /// # Arguments
450    /// * `advice_map` - The advice map to merge into this builder's advice map
451    pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
452        self.advice_map.extend(advice_map);
453    }
454
455    /// Builder-style method to extend the advice map.
456    ///
457    /// # Arguments
458    /// * `advice_map` - The advice map to merge into this builder's advice map
459    pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
460        self.extend_advice_map(advice_map);
461        self
462    }
463
464    // PRIVATE HELPERS
465    // --------------------------------------------------------------------------------------------
466
467    /// Applies the advice map to a library if it's non-empty.
468    ///
469    /// This avoids cloning the MAST forest when there are no advice map entries.
470    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    // COMPILATION
479    // --------------------------------------------------------------------------------------------
480
481    /// Compiles the provided module path and MASM code into an [`AccountComponentCode`].
482    /// The resulting code can be used to create account components.
483    ///
484    /// # Arguments
485    /// * `component_path` - The path to the account code module (e.g., `my_account::my_module`)
486    /// * `component_code` - The account component source code
487    ///
488    /// # Errors
489    /// Returns an error if:
490    /// - Compiling the account component code fails
491    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    /// Compiles the provided MASM code into a [`TransactionScript`].
517    ///
518    /// The parsed script will have access to all modules that have been added to this builder.
519    ///
520    /// # Arguments
521    /// - `tx_script` - the transaction script source code which is expected to have a single public
522    ///   procedure marked with the @transaction_script attribute.
523    ///
524    /// # Errors
525    /// Returns an error if:
526    /// - The transaction script compiling fails
527    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    /// Compiles the provided MASM code into a [`NoteScript`].
566    ///
567    /// The parsed script will have access to all modules that have been added to this builder.
568    ///
569    /// # Arguments
570    /// - `source` - the note script source code which is expected to have a single public procedure
571    ///   marked with the @note_script attribute.
572    ///
573    /// # Errors
574    /// Returns an error if:
575    /// - The note script compiling fails
576    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(&note_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    // ACCESSORS
615    // --------------------------------------------------------------------------------------------
616
617    /// Access the [`Assembler`]'s [`SourceManagerSync`].
618    pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
619        self.source_manager.clone()
620    }
621
622    // TESTING CONVENIENCE FUNCTIONS
623    // --------------------------------------------------------------------------------------------
624
625    /// Returns a [`CodeBuilder`] with the transaction kernel as a library.
626    ///
627    /// This assembler is the same as [`TransactionKernel::assembler`] but additionally includes the
628    /// kernel library on the namespace of `miden::tx_kernel_core`. The `miden::tx_kernel_core`
629    /// library is added separately because even though the library (`api.masm`) and the kernel
630    /// binary (`main.masm`) include this code, it is not otherwise accessible. By adding it
631    /// separately, we can invoke procedures from the kernel library to test them individually.
632    #[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    /// Returns a [`CodeBuilder`] with the `mock::{account, faucet, util}` libraries.
642    ///
643    /// This assembler includes:
644    /// - [`MockAccountCodeExt::mock_account_library`][account_lib],
645    /// - [`MockAccountCodeExt::mock_faucet_library`][faucet_lib],
646    /// - [`mock_util_library`][util_lib]
647    ///
648    /// [account_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_library
649    /// [faucet_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_library
650    /// [util_lib]: crate::testing::mock_util_lib::mock_util_library
651    #[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    /// Returns the mock account and faucet libraries used in testing.
657    #[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        // Start with the builder linking against the transaction kernel, protocol library and
673        // standards library.
674        let mut builder = Self::with_kernel_library(source_manager);
675
676        // Add mock account/faucet libs (built in debug mode) and mock util.
677        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// TESTS
703// ================================================================================================
704
705#[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        // Test that the builder can be created successfully
716    }
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        // Test single library
795        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        // Test multiple libraries
804        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        // Test builder-style chaining with modules
845        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        // Test chaining multiple modules
868        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        // Create libraries using the assembler
903        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        // Test linking both static and dynamic libraries
931        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}