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    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
33/// A value that can provide a compiled Miden package to the code builder.
34pub 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
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/// package dependencies.
191///
192/// The [`CodeBuilder`] simplifies the process of creating transaction scripts by providing:
193/// - A clean API for adding multiple packages with static or dynamic linking
194/// - Automatic assembler configuration with all added packages
195/// - Debug mode support
196/// - Builder pattern support for method chaining
197///
198/// ## Static vs Dynamic Linking
199///
200/// **Static Linking** (`link_static_package()` / `with_statically_linked_package()`):
201/// - Use when you control and know the package code
202/// - The package code is copied into the script code
203/// - Best for most user-written packages and dependencies
204/// - Results in larger script size but ensures the code is always available
205///
206/// **Dynamic Linking** (`link_dynamic_package()` / `with_dynamically_linked_package()`):
207/// - Use when making Foreign Procedure Invocation (FPI) calls
208/// - The package 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 packages using `link_static_package()` / `link_dynamic_package()` 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::Package;
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 packages for the example
233/// # let my_lib: Package = StandardsLib::default().into();
234/// # let fpi_lib: Package = ProtocolLib::default().into();
235/// let script = CodeBuilder::default()
236///     .with_linked_module("my::module", module_code)
237///     .context("failed to link module")?
238///     .with_statically_linked_package(&my_lib)
239///     .context("failed to link static package")?
240///     .with_dynamically_linked_package(&fpi_lib)
241///     .context("failed to link dynamic package")? // For FPI calls
242///     .compile_tx_script(script_code)
243///     .context("failed to parse tx script")?;
244/// # Ok(())
245/// # }
246/// ```
247///
248/// # Note
249/// The CodeBuilder automatically includes the `miden` and `std` libraries, which
250/// provide access to transaction kernel procedures. Due to being available on-chain
251/// these libraries are linked dynamically and do not add to the size of built script.
252#[derive(Clone)]
253pub struct CodeBuilder {
254    assembler: Assembler,
255    source_manager: Arc<dyn SourceManagerSync>,
256    advice_map: AdviceMap,
257}
258
259impl CodeBuilder {
260    // CONSTRUCTORS
261    // --------------------------------------------------------------------------------------------
262
263    /// Creates a new CodeBuilder.
264    pub fn new() -> Self {
265        Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
266    }
267
268    /// Creates a new CodeBuilder with the specified source manager.
269    ///
270    /// # Arguments
271    /// * `source_manager` - The source manager to use with the internal `Assembler`
272    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    // CONFIGURATION
286    // --------------------------------------------------------------------------------------------
287
288    /// Configures the assembler to treat warning diagnostics as errors.
289    ///
290    /// When enabled, any warning emitted during compilation will be promoted to an error,
291    /// causing the compilation to fail.
292    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    // PACKAGE MANAGEMENT
298    // --------------------------------------------------------------------------------------------
299
300    /// Parses and links a module to the code builder.
301    ///
302    /// This method compiles the provided module code and adds it directly to the assembler
303    /// for use in script compilation.
304    ///
305    /// # Arguments
306    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
307    /// * `module_code` - The source code of the module to compile and link
308    ///
309    /// # Errors
310    /// Returns an error if:
311    /// - The module path is invalid
312    /// - The module code cannot be parsed
313    /// - The module cannot be assembled
314    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    /// Statically links the given package.
334    ///
335    /// Static linking means the package code is copied into the script code.
336    /// Use this for most packages that are not available on-chain.
337    ///
338    /// # Arguments
339    /// * `package` - The compiled package to statically link
340    ///
341    /// # Errors
342    /// Returns an error if:
343    /// - adding the package to the assembler failed
344    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    /// Dynamically links a package.
353    ///
354    /// This is useful to dynamically link the [`Package`] of a foreign account
355    /// that is invoked using foreign procedure invocation (FPI). Its code is available
356    /// on-chain and so it does not have to be copied into the script code.
357    ///
358    /// For all other use cases not involving FPI, link the package statically.
359    ///
360    /// # Arguments
361    /// * `package` - The compiled package to dynamically link
362    ///
363    /// # Errors
364    /// Returns an error if the package cannot be added to the assembler
365    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    /// Builder-style method to statically link a package and return the modified builder.
374    ///
375    /// This enables method chaining for convenient builder patterns.
376    ///
377    /// # Arguments
378    /// * `package` - The compiled package to statically link
379    ///
380    /// # Errors
381    /// Returns an error if the package cannot be added to the assembler
382    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    /// Builder-style method to dynamically link a package and return the modified builder.
391    ///
392    /// This enables method chaining for convenient builder patterns.
393    ///
394    /// # Arguments
395    /// * `package` - The compiled package to dynamically link
396    ///
397    /// # Errors
398    /// Returns an error if the package cannot be added to the assembler
399    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    /// Builder-style method to link a module and return the modified builder.
408    ///
409    /// This enables method chaining for convenient builder patterns.
410    ///
411    /// # Arguments
412    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
413    /// * `module_code` - The source code of the module to compile and link
414    ///
415    /// # Errors
416    /// Returns an error if the module cannot be compiled or added to the assembler
417    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    // ADVICE MAP MANAGEMENT
427    // --------------------------------------------------------------------------------------------
428
429    /// Adds an entry to the advice map that will be included in compiled scripts.
430    ///
431    /// The advice map allows passing non-deterministic inputs to the VM that can be
432    /// accessed using `adv.push_mapval` instruction.
433    ///
434    /// # Arguments
435    /// * `key` - The key for the advice map entry (a Word)
436    /// * `value` - The values to associate with this key
437    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    /// Builder-style method to add an advice map entry.
442    ///
443    /// # Arguments
444    /// * `key` - The key for the advice map entry (a Word)
445    /// * `value` - The values to associate with this key
446    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    /// Extends the advice map with entries from another advice map.
452    ///
453    /// # Arguments
454    /// * `advice_map` - The advice map to merge into this builder's advice map
455    pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
456        self.advice_map.extend(advice_map);
457    }
458
459    /// Builder-style method to extend the advice map.
460    ///
461    /// # Arguments
462    /// * `advice_map` - The advice map to merge into this builder's advice map
463    pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
464        self.extend_advice_map(advice_map);
465        self
466    }
467
468    // PRIVATE HELPERS
469    // --------------------------------------------------------------------------------------------
470
471    /// Applies the advice map to a package if it's non-empty.
472    ///
473    /// This avoids cloning the MAST forest when there are no advice map entries.
474    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    // COMPILATION
483    // --------------------------------------------------------------------------------------------
484
485    /// Compiles the provided module path and MASM code into an [`AccountComponentCode`].
486    /// The resulting code can be used to create account components.
487    ///
488    /// # Arguments
489    /// * `component_path` - The path to the account code module (e.g., `my_account::my_module`)
490    /// * `component_code` - The account component source code
491    ///
492    /// # Errors
493    /// Returns an error if:
494    /// - Compiling the account component code fails
495    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    /// Compiles the provided MASM code into a [`TransactionScript`].
521    ///
522    /// The parsed script will have access to all modules that have been added to this builder.
523    ///
524    /// # Arguments
525    /// - `tx_script` - the transaction script source code which is expected to have a single public
526    ///   procedure marked with the @transaction_script attribute.
527    ///
528    /// # Errors
529    /// Returns an error if:
530    /// - The transaction script compiling fails
531    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    /// Compiles the provided MASM code into a [`NoteScript`].
570    ///
571    /// The parsed script will have access to all modules that have been added to this builder.
572    ///
573    /// # Arguments
574    /// - `source` - the note script source code which is expected to have a single public procedure
575    ///   marked with the @note_script attribute.
576    ///
577    /// # Errors
578    /// Returns an error if:
579    /// - The note script compiling fails
580    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(&note_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    // ACCESSORS
619    // --------------------------------------------------------------------------------------------
620
621    /// Access the [`Assembler`]'s [`SourceManagerSync`].
622    pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
623        self.source_manager.clone()
624    }
625
626    // TESTING CONVENIENCE FUNCTIONS
627    // --------------------------------------------------------------------------------------------
628
629    /// Returns a [`CodeBuilder`] with the transaction kernel core package linked.
630    ///
631    /// This assembler is the same as [`TransactionKernel::assembler`] but additionally includes the
632    /// kernel core package on the namespace of `miden::tx_kernel_core`. The `miden::tx_kernel_core`
633    /// package is added separately because even though the library (`api.masm`) and the kernel
634    /// binary (`main.masm`) include this code, it is not otherwise accessible. By adding it
635    /// separately, we can invoke procedures from the kernel core package to test them individually.
636    #[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    /// Returns a [`CodeBuilder`] with the `mock::{account, faucet, util}` packages.
646    ///
647    /// This assembler includes:
648    /// - [`MockAccountCodeExt::mock_account_package`][account_pkg],
649    /// - [`MockAccountCodeExt::mock_faucet_package`][faucet_pkg],
650    /// - [`mock_util_package`][util_pkg]
651    ///
652    /// [account_pkg]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_package
653    /// [faucet_pkg]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_package
654    /// [util_pkg]: crate::testing::mock_util_package::mock_util_package
655    #[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    /// Returns the mock account and faucet packages used in testing.
661    #[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        // Start with the builder linking against the transaction kernel, protocol package and
677        // standards package.
678        let mut builder = Self::with_kernel_core_package(source_manager);
679
680        // Add mock account/faucet packages (built in debug mode) and mock util.
681        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// TESTS
707// ================================================================================================
708
709#[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        // Test that the builder can be created successfully
720    }
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        // Test a single module
799        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        // Test multiple modules
808        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        // Test builder-style chaining with modules
849        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        // Test chaining multiple modules
872        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        // Create packages using the assembler
907        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        // Test linking both static and dynamic packages
935        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}