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