Skip to main content

miden_standards/code_builder/
mod.rs

1use 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// CODE BUILDER
23// ================================================================================================
24
25/// A builder for compiling account components, note scripts, and transaction scripts with optional
26/// library dependencies.
27///
28/// The [`CodeBuilder`] simplifies the process of creating transaction scripts by providing:
29/// - A clean API for adding multiple libraries with static or dynamic linking
30/// - Automatic assembler configuration with all added libraries
31/// - Debug mode support
32/// - Builder pattern support for method chaining
33///
34/// ## Static vs Dynamic Linking
35///
36/// **Static Linking** (`link_static_library()` / `with_statically_linked_library()`):
37/// - Use when you control and know the library code
38/// - The library code is copied into the script code
39/// - Best for most user-written libraries and dependencies
40/// - Results in larger script size but ensures the code is always available
41///
42/// **Dynamic Linking** (`link_dynamic_library()` / `with_dynamically_linked_library()`):
43/// - Use when making Foreign Procedure Invocation (FPI) calls
44/// - The library code is available on-chain and referenced, not copied
45/// - Results in smaller script size but requires the code to be available on-chain
46///
47/// ## Typical Workflow
48///
49/// 1. Create a new CodeBuilder with debug mode preference
50/// 2. Add any required modules using `link_module()` or `with_linked_module()`
51/// 3. Add libraries using `link_static_library()` / `link_dynamic_library()` as appropriate
52/// 4. Compile your script with `compile_note_script()` or `compile_tx_script()`
53///
54/// Note that the compiling methods consume the CodeBuilder, so if you need to compile
55/// multiple scripts with the same configuration, you should clone the builder first.
56///
57/// ## Builder Pattern Example
58///
59/// ```no_run
60/// # use anyhow::Context;
61/// # use miden_standards::code_builder::CodeBuilder;
62/// # use miden_protocol::assembly::Library;
63/// # use miden_protocol::CoreLibrary;
64/// # fn example() -> anyhow::Result<()> {
65/// # let module_code = "pub proc test push.1 add end";
66/// # let script_code = "begin nop end";
67/// # // Create sample libraries for the example
68/// # let my_lib: Library = CoreLibrary::default().into(); // Convert CoreLibrary to Library
69/// # let fpi_lib: Library = CoreLibrary::default().into();
70/// let script = CodeBuilder::default()
71///     .with_linked_module("my::module", module_code).context("failed to link module")?
72///     .with_statically_linked_library(&my_lib).context("failed to link static library")?
73///     .with_dynamically_linked_library(&fpi_lib).context("failed to link dynamic library")?  // For FPI calls
74///     .compile_tx_script(script_code).context("failed to parse tx script")?;
75/// # Ok(())
76/// # }
77/// ```
78///
79/// # Note
80/// The CodeBuilder automatically includes the `miden` and `std` libraries, which
81/// provide access to transaction kernel procedures. Due to being available on-chain
82/// these libraries are linked dynamically and do not add to the size of built script.
83#[derive(Clone)]
84pub struct CodeBuilder {
85    assembler: Assembler,
86    source_manager: Arc<dyn SourceManagerSync>,
87    advice_map: AdviceMap,
88}
89
90impl CodeBuilder {
91    // CONSTRUCTORS
92    // --------------------------------------------------------------------------------------------
93
94    /// Creates a new CodeBuilder.
95    pub fn new() -> Self {
96        Self::with_source_manager(Arc::new(DefaultSourceManager::default()))
97    }
98
99    /// Creates a new CodeBuilder with the specified source manager.
100    ///
101    /// # Arguments
102    /// * `source_manager` - The source manager to use with the internal `Assembler`
103    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    // CONFIGURATION
115    // --------------------------------------------------------------------------------------------
116
117    /// Configures the assembler to treat warning diagnostics as errors.
118    ///
119    /// When enabled, any warning emitted during compilation will be promoted to an error,
120    /// causing the compilation to fail.
121    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    // LIBRARY MANAGEMENT
127    // --------------------------------------------------------------------------------------------
128
129    /// Parses and links a module to the code builder.
130    ///
131    /// This method compiles the provided module code and adds it directly to the assembler
132    /// for use in script compilation.
133    ///
134    /// # Arguments
135    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
136    /// * `module_code` - The source code of the module to compile and link
137    ///
138    /// # Errors
139    /// Returns an error if:
140    /// - The module path is invalid
141    /// - The module code cannot be parsed
142    /// - The module cannot be assembled
143    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    /// Statically links the given library.
163    ///
164    /// Static linking means the library code is copied into the script code.
165    /// Use this for most libraries that are not available on-chain.
166    ///
167    /// # Arguments
168    /// * `library` - The compiled library to statically link
169    ///
170    /// # Errors
171    /// Returns an error if:
172    /// - adding the library to the assembler failed
173    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    /// Dynamically links a library.
180    ///
181    /// This is useful to dynamically link the [`Library`] of a foreign account
182    /// that is invoked using foreign procedure invocation (FPI). Its code is available
183    /// on-chain and so it does not have to be copied into the script code.
184    ///
185    /// For all other use cases not involving FPI, link the library statically.
186    ///
187    /// # Arguments
188    /// * `library` - The compiled library to dynamically link
189    ///
190    /// # Errors
191    /// Returns an error if the library cannot be added to the assembler
192    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    /// Builder-style method to statically link a library and return the modified builder.
199    ///
200    /// This enables method chaining for convenient builder patterns.
201    ///
202    /// # Arguments
203    /// * `library` - The compiled library to statically link
204    ///
205    /// # Errors
206    /// Returns an error if the library cannot be added to the assembler
207    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    /// Builder-style method to dynamically link a library and return the modified builder.
216    ///
217    /// This enables method chaining for convenient builder patterns.
218    ///
219    /// # Arguments
220    /// * `library` - The compiled library to dynamically link
221    ///
222    /// # Errors
223    /// Returns an error if the library cannot be added to the assembler
224    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    /// Builder-style method to link a module and return the modified builder.
233    ///
234    /// This enables method chaining for convenient builder patterns.
235    ///
236    /// # Arguments
237    /// * `module_path` - The path identifier for the module (e.g., "my_lib::my_module")
238    /// * `module_code` - The source code of the module to compile and link
239    ///
240    /// # Errors
241    /// Returns an error if the module cannot be compiled or added to the assembler
242    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    // ADVICE MAP MANAGEMENT
252    // --------------------------------------------------------------------------------------------
253
254    /// Adds an entry to the advice map that will be included in compiled scripts.
255    ///
256    /// The advice map allows passing non-deterministic inputs to the VM that can be
257    /// accessed using `adv.push_mapval` instruction.
258    ///
259    /// # Arguments
260    /// * `key` - The key for the advice map entry (a Word)
261    /// * `value` - The values to associate with this key
262    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    /// Builder-style method to add an advice map entry.
267    ///
268    /// # Arguments
269    /// * `key` - The key for the advice map entry (a Word)
270    /// * `value` - The values to associate with this key
271    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    /// Extends the advice map with entries from another advice map.
277    ///
278    /// # Arguments
279    /// * `advice_map` - The advice map to merge into this builder's advice map
280    pub fn extend_advice_map(&mut self, advice_map: AdviceMap) {
281        self.advice_map.extend(advice_map);
282    }
283
284    /// Builder-style method to extend the advice map.
285    ///
286    /// # Arguments
287    /// * `advice_map` - The advice map to merge into this builder's advice map
288    pub fn with_extended_advice_map(mut self, advice_map: AdviceMap) -> Self {
289        self.extend_advice_map(advice_map);
290        self
291    }
292
293    // PRIVATE HELPERS
294    // --------------------------------------------------------------------------------------------
295
296    /// Applies the advice map to a program if it's non-empty.
297    ///
298    /// This avoids cloning the MAST forest when there are no advice map entries.
299    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    /// Applies the advice map to a library if it's non-empty.
311    ///
312    /// This avoids cloning the MAST forest when there are no advice map entries.
313    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    // COMPILATION
322    // --------------------------------------------------------------------------------------------
323
324    /// Compiles the provided module path and MASM code into an [`AccountComponentCode`].
325    /// The resulting code can be used to create account components.
326    ///
327    /// # Arguments
328    /// * `component_path` - The path to the account code module (e.g., `my_account::my_module`)
329    /// * `component_code` - The account component source code
330    ///
331    /// # Errors
332    /// Returns an error if:
333    /// - Compiling the account component code fails
334    pub fn compile_component_code(
335        self,
336        component_path: impl AsRef<str>,
337        component_code: impl Parse,
338    ) -> Result<AccountComponentCode, CodeBuilderError> {
339        let CodeBuilder { assembler, source_manager, advice_map } = self;
340
341        let mut parse_options = ParseOptions::for_library();
342        parse_options.path = Some(Path::new(component_path.as_ref()).into());
343
344        let module =
345            component_code
346                .parse_with_options(source_manager, parse_options)
347                .map_err(|err| {
348                    CodeBuilderError::build_error_with_report("failed to parse component code", err)
349                })?;
350
351        let library = assembler.assemble_library([module]).map_err(|err| {
352            CodeBuilderError::build_error_with_report("failed to parse component code", err)
353        })?;
354
355        Ok(AccountComponentCode::from(Self::apply_advice_map_to_library(
356            advice_map,
357            Arc::unwrap_or_clone(library),
358        )))
359    }
360
361    /// Compiles the provided MASM code into a [`TransactionScript`].
362    ///
363    /// The parsed script will have access to all modules that have been added to this builder.
364    ///
365    /// # Arguments
366    /// * `tx_script` - The transaction script source code
367    ///
368    /// # Errors
369    /// Returns an error if:
370    /// - The transaction script compiling fails
371    pub fn compile_tx_script(
372        self,
373        tx_script: impl Parse,
374    ) -> Result<TransactionScript, CodeBuilderError> {
375        let CodeBuilder { assembler, advice_map, .. } = self;
376
377        let program = assembler.assemble_program(tx_script).map_err(|err| {
378            CodeBuilderError::build_error_with_report("failed to parse transaction script", err)
379        })?;
380
381        Ok(TransactionScript::new(Self::apply_advice_map(advice_map, program)))
382    }
383
384    /// Compiles the provided MASM code into a [`NoteScript`].
385    ///
386    /// The parsed script will have access to all modules that have been added to this builder.
387    ///
388    /// # Arguments
389    /// * `program` - The note script source code
390    ///
391    /// # Errors
392    /// Returns an error if:
393    /// - The note script compiling fails
394    pub fn compile_note_script(self, source: impl Parse) -> Result<NoteScript, CodeBuilderError> {
395        let CodeBuilder { assembler, advice_map, .. } = self;
396
397        let program = assembler.assemble_program(source).map_err(|err| {
398            CodeBuilderError::build_error_with_report("failed to parse note script", err)
399        })?;
400
401        Ok(NoteScript::new(Self::apply_advice_map(advice_map, program)))
402    }
403
404    // ACCESSORS
405    // --------------------------------------------------------------------------------------------
406
407    /// Access the [`Assembler`]'s [`SourceManagerSync`].
408    pub fn source_manager(&self) -> Arc<dyn SourceManagerSync> {
409        self.source_manager.clone()
410    }
411
412    // TESTING CONVENIENCE FUNCTIONS
413    // --------------------------------------------------------------------------------------------
414
415    /// Returns a [`CodeBuilder`] with the transaction kernel as a library.
416    ///
417    /// This assembler is the same as [`TransactionKernel::assembler`] but additionally includes the
418    /// kernel library on the namespace of `$kernel`. The `$kernel` library is added separately
419    /// because even though the library (`api.masm`) and the kernel binary (`main.masm`) include
420    /// this code, it is not otherwise accessible. By adding it separately, we can invoke procedures
421    /// from the kernel library to test them individually.
422    #[cfg(any(feature = "testing", test))]
423    pub fn with_kernel_library(source_manager: Arc<dyn SourceManagerSync>) -> Self {
424        let mut builder = Self::with_source_manager(source_manager);
425        builder
426            .link_dynamic_library(&TransactionKernel::library())
427            .expect("failed to link kernel library");
428        builder
429    }
430
431    /// Returns a [`CodeBuilder`] with the `mock::{account, faucet, util}` libraries.
432    ///
433    /// This assembler includes:
434    /// - [`MockAccountCodeExt::mock_account_library`][account_lib],
435    /// - [`MockAccountCodeExt::mock_faucet_library`][faucet_lib],
436    /// - [`mock_util_library`][util_lib]
437    ///
438    /// [account_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_account_library
439    /// [faucet_lib]: crate::testing::mock_account_code::MockAccountCodeExt::mock_faucet_library
440    /// [util_lib]: crate::testing::mock_util_lib::mock_util_library
441    #[cfg(any(feature = "testing", test))]
442    pub fn with_mock_libraries() -> Self {
443        Self::with_mock_libraries_with_source_manager(Arc::new(DefaultSourceManager::default()))
444    }
445
446    /// Returns the mock account and faucet libraries used in testing.
447    #[cfg(any(feature = "testing", test))]
448    pub fn mock_libraries() -> impl Iterator<Item = Library> {
449        use miden_protocol::account::AccountCode;
450
451        use crate::testing::mock_account_code::MockAccountCodeExt;
452
453        vec![AccountCode::mock_account_library(), AccountCode::mock_faucet_library()].into_iter()
454    }
455
456    #[cfg(any(feature = "testing", test))]
457    pub fn with_mock_libraries_with_source_manager(
458        source_manager: Arc<dyn SourceManagerSync>,
459    ) -> Self {
460        use crate::testing::mock_util_lib::mock_util_library;
461
462        // Start with the builder linking against the transaction kernel, protocol library and
463        // standards library.
464        let mut builder = Self::with_source_manager(source_manager);
465
466        // Expose kernel procedures under `$kernel` for testing.
467        builder
468            .link_dynamic_library(&TransactionKernel::library())
469            .expect("failed to link kernel library");
470
471        // Add mock account/faucet libs (built in debug mode) and mock util.
472        for library in Self::mock_libraries() {
473            builder
474                .link_dynamic_library(&library)
475                .expect("failed to link mock account libraries");
476        }
477        builder
478            .link_static_library(&mock_util_library())
479            .expect("failed to link mock util library");
480
481        builder
482    }
483}
484
485impl Default for CodeBuilder {
486    fn default() -> Self {
487        Self::new()
488    }
489}
490
491impl From<CodeBuilder> for Assembler {
492    fn from(builder: CodeBuilder) -> Self {
493        builder.assembler
494    }
495}
496
497// TESTS
498// ================================================================================================
499
500#[cfg(test)]
501mod tests {
502    use anyhow::Context;
503    use miden_protocol::assembly::diagnostics::NamedSource;
504
505    use super::*;
506
507    #[test]
508    fn test_code_builder_new() {
509        let _builder = CodeBuilder::default();
510        // Test that the builder can be created successfully
511    }
512
513    #[test]
514    fn test_code_builder_basic_script_compiling() -> anyhow::Result<()> {
515        let builder = CodeBuilder::default();
516        builder
517            .compile_tx_script("begin nop end")
518            .context("failed to parse basic tx script")?;
519        Ok(())
520    }
521
522    #[test]
523    fn test_create_library_and_create_tx_script() -> anyhow::Result<()> {
524        let script_code = "
525            use external_contract::counter_contract
526
527            begin
528                call.counter_contract::increment
529            end
530        ";
531
532        let account_code = "
533            use miden::protocol::active_account
534            use miden::protocol::native_account
535            use miden::core::sys
536
537            pub proc increment
538                push.0
539                exec.active_account::get_item
540                push.1 add
541                push.0
542                exec.native_account::set_item
543                exec.sys::truncate_stack
544            end
545        ";
546
547        let library_path = "external_contract::counter_contract";
548
549        let mut builder_with_lib = CodeBuilder::default();
550        builder_with_lib
551            .link_module(library_path, account_code)
552            .context("failed to link module")?;
553        builder_with_lib
554            .compile_tx_script(script_code)
555            .context("failed to parse tx script")?;
556
557        Ok(())
558    }
559
560    #[test]
561    fn test_parse_library_and_add_to_builder() -> anyhow::Result<()> {
562        let script_code = "
563            use external_contract::counter_contract
564
565            begin
566                call.counter_contract::increment
567            end
568        ";
569
570        let account_code = "
571            use miden::protocol::active_account
572            use miden::protocol::native_account
573            use miden::core::sys
574
575            pub proc increment
576                push.0
577                exec.active_account::get_item
578                push.1 add
579                push.0
580                exec.native_account::set_item
581                exec.sys::truncate_stack
582            end
583        ";
584
585        let library_path = "external_contract::counter_contract";
586
587        // Test single library
588        let mut builder_with_lib = CodeBuilder::default();
589        builder_with_lib
590            .link_module(library_path, account_code)
591            .context("failed to link module")?;
592        builder_with_lib
593            .compile_tx_script(script_code)
594            .context("failed to parse tx script")?;
595
596        // Test multiple libraries
597        let mut builder_with_libs = CodeBuilder::default();
598        builder_with_libs
599            .link_module(library_path, account_code)
600            .context("failed to link first module")?;
601        builder_with_libs
602            .link_module("test::lib", "pub proc test nop end")
603            .context("failed to link second module")?;
604        builder_with_libs
605            .compile_tx_script(script_code)
606            .context("failed to parse tx script with multiple libraries")?;
607
608        Ok(())
609    }
610
611    #[test]
612    fn test_builder_style_chaining() -> anyhow::Result<()> {
613        let script_code = "
614            use external_contract::counter_contract
615
616            begin
617                call.counter_contract::increment
618            end
619        ";
620
621        let account_code = "
622            use miden::protocol::active_account
623            use miden::protocol::native_account
624            use miden::core::sys
625
626            pub proc increment
627                push.0
628                exec.active_account::get_item
629                push.1 add
630                push.0
631                exec.native_account::set_item
632                exec.sys::truncate_stack
633            end
634        ";
635
636        // Test builder-style chaining with modules
637        let builder = CodeBuilder::default()
638            .with_linked_module("external_contract::counter_contract", account_code)
639            .context("failed to link module")?;
640
641        builder.compile_tx_script(script_code).context("failed to parse tx script")?;
642
643        Ok(())
644    }
645
646    #[test]
647    fn test_multiple_chained_modules() -> anyhow::Result<()> {
648        let script_code =
649            "use test::lib1 use test::lib2 begin exec.lib1::test1 exec.lib2::test2 end";
650
651        // Test chaining multiple modules
652        let builder = CodeBuilder::default()
653            .with_linked_module("test::lib1", "pub proc test1 push.1 add end")
654            .context("failed to link first module")?
655            .with_linked_module("test::lib2", "pub proc test2 push.2 add end")
656            .context("failed to link second module")?;
657
658        builder.compile_tx_script(script_code).context("failed to parse tx script")?;
659
660        Ok(())
661    }
662
663    #[test]
664    fn test_static_and_dynamic_linking() -> anyhow::Result<()> {
665        let script_code = "
666            use contracts::static_contract
667
668            begin
669                call.static_contract::increment_1
670            end
671        ";
672
673        let account_code_1 = "
674            pub proc increment_1
675                push.0 drop
676            end
677        ";
678
679        let account_code_2 = "
680            pub proc increment_2
681                push.0 drop
682            end
683        ";
684
685        // Create libraries using the assembler
686        let temp_assembler = TransactionKernel::assembler();
687
688        let static_lib = temp_assembler
689            .clone()
690            .assemble_library([NamedSource::new("contracts::static_contract", account_code_1)])
691            .map_err(|e| anyhow::anyhow!("failed to assemble static library: {}", e))?;
692
693        let dynamic_lib = temp_assembler
694            .assemble_library([NamedSource::new("contracts::dynamic_contract", account_code_2)])
695            .map_err(|e| anyhow::anyhow!("failed to assemble dynamic library: {}", e))?;
696
697        // Test linking both static and dynamic libraries
698        let builder = CodeBuilder::default()
699            .with_statically_linked_library(&static_lib)
700            .context("failed to link static library")?
701            .with_dynamically_linked_library(&dynamic_lib)
702            .context("failed to link dynamic library")?;
703
704        builder
705            .compile_tx_script(script_code)
706            .context("failed to parse tx script with static and dynamic libraries")?;
707
708        Ok(())
709    }
710
711    #[test]
712    fn test_code_builder_warnings_as_errors() {
713        let assembler: Assembler = CodeBuilder::default().with_warnings_as_errors(true).into();
714        assert!(assembler.warnings_as_errors());
715    }
716
717    #[test]
718    fn test_code_builder_with_advice_map_entry() -> anyhow::Result<()> {
719        let key = Word::from([1u32, 2, 3, 4]);
720        let value = vec![Felt::new(42), Felt::new(43)];
721
722        let script = CodeBuilder::default()
723            .with_advice_map_entry(key, value.clone())
724            .compile_tx_script("begin nop end")
725            .context("failed to compile tx script with advice map")?;
726
727        let mast = script.mast();
728        let stored_value = mast.advice_map().get(&key).expect("advice map entry should be present");
729        assert_eq!(stored_value.as_ref(), value.as_slice());
730
731        Ok(())
732    }
733
734    #[test]
735    fn test_code_builder_extend_advice_map() -> anyhow::Result<()> {
736        let key1 = Word::from([1u32, 0, 0, 0]);
737        let key2 = Word::from([2u32, 0, 0, 0]);
738
739        let mut advice_map = AdviceMap::default();
740        advice_map.insert(key1, vec![Felt::new(1)]);
741        advice_map.insert(key2, vec![Felt::new(2)]);
742
743        let script = CodeBuilder::default()
744            .with_extended_advice_map(advice_map)
745            .compile_tx_script("begin nop end")
746            .context("failed to compile tx script")?;
747
748        let mast = script.mast();
749        assert!(mast.advice_map().get(&key1).is_some(), "key1 should be present");
750        assert!(mast.advice_map().get(&key2).is_some(), "key2 should be present");
751
752        Ok(())
753    }
754
755    #[test]
756    fn test_code_builder_advice_map_in_note_script() -> anyhow::Result<()> {
757        let key = Word::from([5u32, 6, 7, 8]);
758        let value = vec![Felt::new(100)];
759
760        let script = CodeBuilder::default()
761            .with_advice_map_entry(key, value.clone())
762            .compile_note_script("begin nop end")
763            .context("failed to compile note script with advice map")?;
764
765        let mast = script.mast();
766        let stored_value = mast
767            .advice_map()
768            .get(&key)
769            .expect("advice map entry should be present in note script");
770        assert_eq!(stored_value.as_ref(), value.as_slice());
771
772        Ok(())
773    }
774
775    #[test]
776    fn test_code_builder_advice_map_in_component_code() -> anyhow::Result<()> {
777        let key = Word::from([11u32, 22, 33, 44]);
778        let value = vec![Felt::new(500)];
779
780        let component_code = CodeBuilder::default()
781            .with_advice_map_entry(key, value.clone())
782            .compile_component_code("test::component", "pub proc test nop end")
783            .context("failed to compile component code with advice map")?;
784
785        let mast = component_code.mast_forest();
786        let stored_value = mast
787            .advice_map()
788            .get(&key)
789            .expect("advice map entry should be present in component code");
790        assert_eq!(stored_value.as_ref(), value.as_slice());
791
792        Ok(())
793    }
794}