Skip to main content

snarkvm_synthesizer_program/
lib.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![forbid(unsafe_code)]
17#![allow(clippy::too_many_arguments)]
18#![warn(clippy::cast_possible_truncation)]
19
20extern crate snarkvm_circuit as circuit;
21extern crate snarkvm_console as console;
22
23pub type Program<N> = crate::ProgramCore<N>;
24pub type Function<N> = crate::FunctionCore<N>;
25pub type Finalize<N> = crate::FinalizeCore<N>;
26pub type Closure<N> = crate::ClosureCore<N>;
27pub type Constructor<N> = crate::ConstructorCore<N>;
28
29mod closure;
30pub use closure::*;
31
32mod constructor;
33pub use constructor::*;
34
35pub mod finalize;
36pub use finalize::*;
37
38mod function;
39pub use function::*;
40
41mod import;
42pub use import::*;
43
44pub mod logic;
45pub use logic::*;
46
47mod mapping;
48pub use mapping::*;
49
50mod traits;
51pub use traits::*;
52
53mod bytes;
54mod parse;
55mod serialize;
56mod to_checksum;
57
58use console::{
59    network::{
60        ConsensusVersion,
61        prelude::{
62            Debug,
63            Deserialize,
64            Deserializer,
65            Display,
66            Err,
67            Error,
68            ErrorKind,
69            Formatter,
70            FromBytes,
71            FromBytesDeserializer,
72            FromStr,
73            IoResult,
74            Itertools,
75            Network,
76            Parser,
77            ParserResult,
78            Read,
79            Result,
80            Sanitizer,
81            Serialize,
82            Serializer,
83            ToBytes,
84            ToBytesSerializer,
85            TypeName,
86            Write,
87            anyhow,
88            bail,
89            de,
90            ensure,
91            error,
92            fmt,
93            make_error,
94            many0,
95            many1,
96            map,
97            map_res,
98            tag,
99            take,
100        },
101    },
102    program::{
103        EntryType,
104        FinalizeType,
105        Identifier,
106        Locator,
107        PlaintextType,
108        ProgramID,
109        RecordType,
110        StructType,
111        ValueType,
112    },
113    types::U8,
114};
115use snarkvm_utilities::cfg_iter;
116
117use indexmap::{IndexMap, IndexSet};
118use std::collections::BTreeSet;
119use tiny_keccak::{Hasher, Sha3 as TinySha3};
120
121#[derive(Copy, Clone, PartialEq, Eq, Hash)]
122enum ProgramLabel<N: Network> {
123    /// A program constructor.
124    Constructor,
125    /// A named component.
126    Identifier(Identifier<N>),
127}
128
129#[cfg(not(feature = "serial"))]
130use rayon::prelude::*;
131
132#[derive(Copy, Clone, PartialEq, Eq, Hash)]
133enum ProgramDefinition {
134    /// A program constructor.
135    Constructor,
136    /// A program mapping.
137    Mapping,
138    /// A program struct.
139    Struct,
140    /// A program record.
141    Record,
142    /// A program closure.
143    Closure,
144    /// A program function.
145    Function,
146}
147
148#[derive(Clone)]
149pub struct ProgramCore<N: Network> {
150    /// The ID of the program.
151    id: ProgramID<N>,
152    /// A map of the declared imports for the program.
153    imports: IndexMap<ProgramID<N>, Import<N>>,
154    /// A map of program labels to their program definitions.
155    components: IndexMap<ProgramLabel<N>, ProgramDefinition>,
156    /// An optional constructor for the program.
157    constructor: Option<ConstructorCore<N>>,
158    /// A map of the declared mappings for the program.
159    mappings: IndexMap<Identifier<N>, Mapping<N>>,
160    /// A map of the declared structs for the program.
161    structs: IndexMap<Identifier<N>, StructType<N>>,
162    /// A map of the declared record types for the program.
163    records: IndexMap<Identifier<N>, RecordType<N>>,
164    /// A map of the declared closures for the program.
165    closures: IndexMap<Identifier<N>, ClosureCore<N>>,
166    /// A map of the declared functions for the program.
167    functions: IndexMap<Identifier<N>, FunctionCore<N>>,
168}
169
170impl<N: Network> PartialEq for ProgramCore<N> {
171    /// Compares two programs for equality, verifying that the components are in the same order.
172    /// The order of the components must match to ensure that deployment tree is well-formed.
173    fn eq(&self, other: &Self) -> bool {
174        // Check that the number of components is the same.
175        if self.components.len() != other.components.len() {
176            return false;
177        }
178        // Check that the components match in order.
179        for (left, right) in self.components.iter().zip_eq(other.components.iter()) {
180            if left != right {
181                return false;
182            }
183        }
184        // Check that the remaining fields match.
185        self.id == other.id
186            && self.imports == other.imports
187            && self.mappings == other.mappings
188            && self.structs == other.structs
189            && self.records == other.records
190            && self.closures == other.closures
191            && self.functions == other.functions
192    }
193}
194
195impl<N: Network> Eq for ProgramCore<N> {}
196
197impl<N: Network> ProgramCore<N> {
198    /// A list of reserved keywords for Aleo programs, enforced at the parser level.
199    // New keywords should be enforced through `RESTRICTED_KEYWORDS` instead, if possible.
200    // Adding keywords to this list will require a backwards-compatible versioning for programs.
201    #[rustfmt::skip]
202    pub const KEYWORDS: &'static [&'static str] = &[
203        // Mode
204        "const",
205        "constant",
206        "public",
207        "private",
208        // Literals
209        "address",
210        "boolean",
211        "field",
212        "group",
213        "i8",
214        "i16",
215        "i32",
216        "i64",
217        "i128",
218        "u8",
219        "u16",
220        "u32",
221        "u64",
222        "u128",
223        "scalar",
224        "signature",
225        "string",
226        // Boolean
227        "true",
228        "false",
229        // Statements
230        "input",
231        "output",
232        "as",
233        "into",
234        // Record
235        "record",
236        "owner",
237        // Program
238        "transition",
239        "import",
240        "function",
241        "struct",
242        "closure",
243        "program",
244        "aleo",
245        "self",
246        "storage",
247        "mapping",
248        "key",
249        "value",
250        "async",
251        "finalize",
252        // Reserved (catch all)
253        "global",
254        "block",
255        "return",
256        "break",
257        "assert",
258        "continue",
259        "let",
260        "if",
261        "else",
262        "while",
263        "for",
264        "switch",
265        "case",
266        "default",
267        "match",
268        "enum",
269        "struct",
270        "union",
271        "trait",
272        "impl",
273        "type",
274        "future",
275    ];
276    /// A list of restricted keywords for Aleo programs, enforced at the VM-level for program hygiene.
277    /// Each entry is a tuple of the consensus version and a list of keywords.
278    /// If the current consensus version is greater than or equal to the specified version,
279    /// the keywords in the list should be restricted.
280    #[rustfmt::skip]
281    pub const RESTRICTED_KEYWORDS: &'static [(ConsensusVersion, &'static [&'static str])] = &[
282        (ConsensusVersion::V6, &["constructor"])
283    ];
284
285    /// Initializes an empty program.
286    #[inline]
287    pub fn new(id: ProgramID<N>) -> Result<Self> {
288        // Ensure the program name is valid.
289        ensure!(!Self::is_reserved_keyword(id.name()), "Program name is invalid: {}", id.name());
290
291        Ok(Self {
292            id,
293            imports: IndexMap::new(),
294            constructor: None,
295            components: IndexMap::new(),
296            mappings: IndexMap::new(),
297            structs: IndexMap::new(),
298            records: IndexMap::new(),
299            closures: IndexMap::new(),
300            functions: IndexMap::new(),
301        })
302    }
303
304    /// Initializes the credits program.
305    #[inline]
306    pub fn credits() -> Result<Self> {
307        Self::from_str(include_str!("./resources/credits.aleo"))
308    }
309
310    /// Returns the ID of the program.
311    pub const fn id(&self) -> &ProgramID<N> {
312        &self.id
313    }
314
315    /// Returns the imports in the program.
316    pub const fn imports(&self) -> &IndexMap<ProgramID<N>, Import<N>> {
317        &self.imports
318    }
319
320    /// Returns the constructor for the program.
321    pub const fn constructor(&self) -> Option<&ConstructorCore<N>> {
322        self.constructor.as_ref()
323    }
324
325    /// Returns the mappings in the program.
326    pub const fn mappings(&self) -> &IndexMap<Identifier<N>, Mapping<N>> {
327        &self.mappings
328    }
329
330    /// Returns the structs in the program.
331    pub const fn structs(&self) -> &IndexMap<Identifier<N>, StructType<N>> {
332        &self.structs
333    }
334
335    /// Returns the records in the program.
336    pub const fn records(&self) -> &IndexMap<Identifier<N>, RecordType<N>> {
337        &self.records
338    }
339
340    /// Returns the closures in the program.
341    pub const fn closures(&self) -> &IndexMap<Identifier<N>, ClosureCore<N>> {
342        &self.closures
343    }
344
345    /// Returns the functions in the program.
346    pub const fn functions(&self) -> &IndexMap<Identifier<N>, FunctionCore<N>> {
347        &self.functions
348    }
349
350    /// Returns `true` if the program contains an import with the given program ID.
351    pub fn contains_import(&self, id: &ProgramID<N>) -> bool {
352        self.imports.contains_key(id)
353    }
354
355    /// Returns `true` if the program contains a constructor.
356    pub const fn contains_constructor(&self) -> bool {
357        self.constructor.is_some()
358    }
359
360    /// Returns `true` if the program contains a mapping with the given name.
361    pub fn contains_mapping(&self, name: &Identifier<N>) -> bool {
362        self.mappings.contains_key(name)
363    }
364
365    /// Returns `true` if the program contains a struct with the given name.
366    pub fn contains_struct(&self, name: &Identifier<N>) -> bool {
367        self.structs.contains_key(name)
368    }
369
370    /// Returns `true` if the program contains a record with the given name.
371    pub fn contains_record(&self, name: &Identifier<N>) -> bool {
372        self.records.contains_key(name)
373    }
374
375    /// Returns `true` if the program contains a closure with the given name.
376    pub fn contains_closure(&self, name: &Identifier<N>) -> bool {
377        self.closures.contains_key(name)
378    }
379
380    /// Returns `true` if the program contains a function with the given name.
381    pub fn contains_function(&self, name: &Identifier<N>) -> bool {
382        self.functions.contains_key(name)
383    }
384
385    /// Returns the mapping with the given name.
386    pub fn get_mapping(&self, name: &Identifier<N>) -> Result<Mapping<N>> {
387        // Attempt to retrieve the mapping.
388        let mapping = self.mappings.get(name).cloned().ok_or_else(|| anyhow!("Mapping '{name}' is not defined."))?;
389        // Ensure the mapping name matches.
390        ensure!(mapping.name() == name, "Expected mapping '{name}', but found mapping '{}'", mapping.name());
391        // Return the mapping.
392        Ok(mapping)
393    }
394
395    /// Returns the struct with the given name.
396    pub fn get_struct(&self, name: &Identifier<N>) -> Result<&StructType<N>> {
397        // Attempt to retrieve the struct.
398        let struct_ = self.structs.get(name).ok_or_else(|| anyhow!("Struct '{name}' is not defined."))?;
399        // Ensure the struct name matches.
400        ensure!(struct_.name() == name, "Expected struct '{name}', but found struct '{}'", struct_.name());
401        // Ensure the struct contains members.
402        ensure!(!struct_.members().is_empty(), "Struct '{name}' is missing members.");
403        // Return the struct.
404        Ok(struct_)
405    }
406
407    /// Returns the record with the given name.
408    pub fn get_record(&self, name: &Identifier<N>) -> Result<&RecordType<N>> {
409        // Attempt to retrieve the record.
410        let record = self.records.get(name).ok_or_else(|| anyhow!("Record '{name}' is not defined."))?;
411        // Ensure the record name matches.
412        ensure!(record.name() == name, "Expected record '{name}', but found record '{}'", record.name());
413        // Return the record.
414        Ok(record)
415    }
416
417    /// Returns the closure with the given name.
418    pub fn get_closure(&self, name: &Identifier<N>) -> Result<ClosureCore<N>> {
419        // Attempt to retrieve the closure.
420        let closure = self.closures.get(name).cloned().ok_or_else(|| anyhow!("Closure '{name}' is not defined."))?;
421        // Ensure the closure name matches.
422        ensure!(closure.name() == name, "Expected closure '{name}', but found closure '{}'", closure.name());
423        // Ensure there are input statements in the closure.
424        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
425        // Ensure the number of inputs is within the allowed range.
426        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
427        // Ensure there are instructions in the closure.
428        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
429        // Ensure the number of outputs is within the allowed range.
430        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
431        // Return the closure.
432        Ok(closure)
433    }
434
435    /// Returns the function with the given name.
436    pub fn get_function(&self, name: &Identifier<N>) -> Result<FunctionCore<N>> {
437        self.get_function_ref(name).cloned()
438    }
439
440    /// Returns a reference to the function with the given name.
441    pub fn get_function_ref(&self, name: &Identifier<N>) -> Result<&FunctionCore<N>> {
442        // Attempt to retrieve the function.
443        let function = self.functions.get(name).ok_or(anyhow!("Function '{}/{name}' is not defined.", self.id))?;
444        // Ensure the function name matches.
445        ensure!(function.name() == name, "Expected function '{name}', but found function '{}'", function.name());
446        // Ensure the number of inputs is within the allowed range.
447        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
448        // Ensure the number of instructions is within the allowed range.
449        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
450        // Ensure the number of outputs is within the allowed range.
451        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
452        // Return the function.
453        Ok(function)
454    }
455
456    /// Adds a new import statement to the program.
457    ///
458    /// # Errors
459    /// This method will halt if the imported program was previously added.
460    #[inline]
461    fn add_import(&mut self, import: Import<N>) -> Result<()> {
462        // Retrieve the imported program name.
463        let import_name = *import.name();
464
465        // Ensure that the number of imports is within the allowed range.
466        ensure!(self.imports.len() < N::MAX_IMPORTS, "Program exceeds the maximum number of imports");
467
468        // Ensure the import name is new.
469        ensure!(self.is_unique_name(&import_name), "'{import_name}' is already in use.");
470        // Ensure the import name is not a reserved opcode.
471        ensure!(!Self::is_reserved_opcode(&import_name.to_string()), "'{import_name}' is a reserved opcode.");
472        // Ensure the import name is not a reserved keyword.
473        ensure!(!Self::is_reserved_keyword(&import_name), "'{import_name}' is a reserved keyword.");
474
475        // Ensure the import is new.
476        ensure!(
477            !self.imports.contains_key(import.program_id()),
478            "Import '{}' is already defined.",
479            import.program_id()
480        );
481
482        // Add the import statement to the program.
483        if self.imports.insert(*import.program_id(), import.clone()).is_some() {
484            bail!("'{}' already exists in the program.", import.program_id())
485        }
486        Ok(())
487    }
488
489    /// Adds a constructor to the program.
490    ///
491    /// # Errors
492    /// This method will halt if a constructor was previously added.
493    /// This method will halt if a constructor exceeds the maximum number of commands.
494    fn add_constructor(&mut self, constructor: ConstructorCore<N>) -> Result<()> {
495        // Ensure the program does not already have a constructor.
496        ensure!(self.constructor.is_none(), "Program already has a constructor.");
497        // Ensure the number of commands is within the allowed range.
498        ensure!(!constructor.commands().is_empty(), "Constructor must have at least one command");
499        ensure!(constructor.commands().len() <= N::MAX_COMMANDS, "Constructor exceeds maximum number of commands");
500        // Add the constructor to the components.
501        if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() {
502            bail!("Constructor already exists in the program.")
503        }
504        // Add the constructor to the program.
505        self.constructor = Some(constructor);
506        Ok(())
507    }
508
509    /// Adds a new mapping to the program.
510    ///
511    /// # Errors
512    /// This method will halt if the mapping name is already in use.
513    /// This method will halt if the mapping name is a reserved opcode or keyword.
514    #[inline]
515    fn add_mapping(&mut self, mapping: Mapping<N>) -> Result<()> {
516        // Retrieve the mapping name.
517        let mapping_name = *mapping.name();
518
519        // Ensure the program has not exceeded the maximum number of mappings.
520        ensure!(self.mappings.len() < N::MAX_MAPPINGS, "Program exceeds the maximum number of mappings");
521
522        // Ensure the mapping name is new.
523        ensure!(self.is_unique_name(&mapping_name), "'{mapping_name}' is already in use.");
524        // Ensure the mapping name is not a reserved keyword.
525        ensure!(!Self::is_reserved_keyword(&mapping_name), "'{mapping_name}' is a reserved keyword.");
526        // Ensure the mapping name is not a reserved opcode.
527        ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode.");
528
529        // Add the mapping name to the identifiers.
530        if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() {
531            bail!("'{mapping_name}' already exists in the program.")
532        }
533        // Add the mapping to the program.
534        if self.mappings.insert(mapping_name, mapping).is_some() {
535            bail!("'{mapping_name}' already exists in the program.")
536        }
537        Ok(())
538    }
539
540    /// Adds a new struct to the program.
541    ///
542    /// # Errors
543    /// This method will halt if the struct was previously added.
544    /// This method will halt if the struct name is already in use in the program.
545    /// This method will halt if the struct name is a reserved opcode or keyword.
546    /// This method will halt if any structs in the struct's members are not already defined.
547    #[inline]
548    fn add_struct(&mut self, struct_: StructType<N>) -> Result<()> {
549        // Retrieve the struct name.
550        let struct_name = *struct_.name();
551
552        // Ensure the program has not exceeded the maximum number of structs.
553        ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");
554
555        // Ensure the struct name is new.
556        ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
557        // Ensure the struct name is not a reserved opcode.
558        ensure!(!Self::is_reserved_opcode(&struct_name.to_string()), "'{struct_name}' is a reserved opcode.");
559        // Ensure the struct name is not a reserved keyword.
560        ensure!(!Self::is_reserved_keyword(&struct_name), "'{struct_name}' is a reserved keyword.");
561
562        // Ensure the struct contains members.
563        ensure!(!struct_.members().is_empty(), "Struct '{struct_name}' is missing members.");
564
565        // Ensure all struct members are well-formed.
566        // Note: This design ensures cyclic references are not possible.
567        for (identifier, plaintext_type) in struct_.members() {
568            // Ensure the member name is not a reserved keyword.
569            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
570            // Ensure the member type is already defined in the program.
571            match plaintext_type {
572                PlaintextType::Literal(_) => continue,
573                PlaintextType::Struct(member_identifier) => {
574                    // Ensure the member struct name exists in the program.
575                    if !self.structs.contains_key(member_identifier) {
576                        bail!("'{member_identifier}' in struct '{struct_name}' is not defined.")
577                    }
578                }
579                PlaintextType::ExternalStruct(locator) => {
580                    if !self.imports.contains_key(locator.program_id()) {
581                        bail!(
582                            "External program {} referenced in struct '{struct_name}' does not exist",
583                            locator.program_id()
584                        );
585                    }
586                }
587                PlaintextType::Array(array_type) => {
588                    match array_type.base_element_type() {
589                        PlaintextType::Struct(struct_name) =>
590                        // Ensure the member struct name exists in the program.
591                        {
592                            if !self.structs.contains_key(struct_name) {
593                                bail!("'{struct_name}' in array '{array_type}' is not defined.")
594                            }
595                        }
596                        PlaintextType::ExternalStruct(locator) => {
597                            if !self.imports.contains_key(locator.program_id()) {
598                                bail!(
599                                    "External program {} in array '{array_type}' does not exist",
600                                    locator.program_id()
601                                );
602                            }
603                        }
604                        PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
605                    }
606                }
607            }
608        }
609
610        // Add the struct name to the identifiers.
611        if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() {
612            bail!("'{struct_name}' already exists in the program.")
613        }
614        // Add the struct to the program.
615        if self.structs.insert(struct_name, struct_).is_some() {
616            bail!("'{struct_name}' already exists in the program.")
617        }
618        Ok(())
619    }
620
621    /// Adds a new record to the program.
622    ///
623    /// # Errors
624    /// This method will halt if the record was previously added.
625    /// This method will halt if the record name is already in use in the program.
626    /// This method will halt if the record name is a reserved opcode or keyword.
627    /// This method will halt if any records in the record's members are not already defined.
628    #[inline]
629    fn add_record(&mut self, record: RecordType<N>) -> Result<()> {
630        // Retrieve the record name.
631        let record_name = *record.name();
632
633        // Ensure the program has not exceeded the maximum number of records.
634        ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");
635
636        // Ensure the record name is new.
637        ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
638        // Ensure the record name is not a reserved opcode.
639        ensure!(!Self::is_reserved_opcode(&record_name.to_string()), "'{record_name}' is a reserved opcode.");
640        // Ensure the record name is not a reserved keyword.
641        ensure!(!Self::is_reserved_keyword(&record_name), "'{record_name}' is a reserved keyword.");
642
643        // Ensure all record entries are well-formed.
644        // Note: This design ensures cyclic references are not possible.
645        for (identifier, entry_type) in record.entries() {
646            // Ensure the member name is not a reserved keyword.
647            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
648            // Ensure the member type is already defined in the program.
649            match entry_type.plaintext_type() {
650                PlaintextType::Literal(_) => continue,
651                PlaintextType::Struct(identifier) => {
652                    if !self.structs.contains_key(identifier) {
653                        bail!("Struct '{identifier}' in record '{record_name}' is not defined.")
654                    }
655                }
656                PlaintextType::ExternalStruct(locator) => {
657                    if !self.imports.contains_key(locator.program_id()) {
658                        bail!(
659                            "External program {} referenced in record '{record_name}' does not exist",
660                            locator.program_id()
661                        );
662                    }
663                }
664                PlaintextType::Array(array_type) => {
665                    match array_type.base_element_type() {
666                        PlaintextType::Struct(struct_name) =>
667                        // Ensure the member struct name exists in the program.
668                        {
669                            if !self.structs.contains_key(struct_name) {
670                                bail!("'{struct_name}' in array '{array_type}' is not defined.")
671                            }
672                        }
673                        PlaintextType::ExternalStruct(locator) => {
674                            if !self.imports.contains_key(locator.program_id()) {
675                                bail!(
676                                    "External program {} in array '{array_type}' does not exist",
677                                    locator.program_id()
678                                );
679                            }
680                        }
681                        PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
682                    }
683                }
684            }
685        }
686
687        // Add the record name to the identifiers.
688        if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() {
689            bail!("'{record_name}' already exists in the program.")
690        }
691        // Add the record to the program.
692        if self.records.insert(record_name, record).is_some() {
693            bail!("'{record_name}' already exists in the program.")
694        }
695        Ok(())
696    }
697
698    /// Adds a new closure to the program.
699    ///
700    /// # Errors
701    /// This method will halt if the closure was previously added.
702    /// This method will halt if the closure name is already in use in the program.
703    /// This method will halt if the closure name is a reserved opcode or keyword.
704    /// This method will halt if any registers are assigned more than once.
705    /// This method will halt if the registers are not incrementing monotonically.
706    /// This method will halt if an input type references a non-existent definition.
707    /// This method will halt if an operand register does not already exist in memory.
708    /// This method will halt if a destination register already exists in memory.
709    /// This method will halt if an output register does not already exist.
710    /// This method will halt if an output type references a non-existent definition.
711    #[inline]
712    fn add_closure(&mut self, closure: ClosureCore<N>) -> Result<()> {
713        // Retrieve the closure name.
714        let closure_name = *closure.name();
715
716        // Ensure the program has not exceeded the maximum number of closures.
717        ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");
718
719        // Ensure the closure name is new.
720        ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
721        // Ensure the closure name is not a reserved opcode.
722        ensure!(!Self::is_reserved_opcode(&closure_name.to_string()), "'{closure_name}' is a reserved opcode.");
723        // Ensure the closure name is not a reserved keyword.
724        ensure!(!Self::is_reserved_keyword(&closure_name), "'{closure_name}' is a reserved keyword.");
725
726        // Ensure there are input statements in the closure.
727        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
728        // Ensure the number of inputs is within the allowed range.
729        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
730        // Ensure there are instructions in the closure.
731        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
732        // Ensure the number of outputs is within the allowed range.
733        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
734
735        // Add the function name to the identifiers.
736        if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() {
737            bail!("'{closure_name}' already exists in the program.")
738        }
739        // Add the closure to the program.
740        if self.closures.insert(closure_name, closure).is_some() {
741            bail!("'{closure_name}' already exists in the program.")
742        }
743        Ok(())
744    }
745
746    /// Adds a new function to the program.
747    ///
748    /// # Errors
749    /// This method will halt if the function was previously added.
750    /// This method will halt if the function name is already in use in the program.
751    /// This method will halt if the function name is a reserved opcode or keyword.
752    /// This method will halt if any registers are assigned more than once.
753    /// This method will halt if the registers are not incrementing monotonically.
754    /// This method will halt if an input type references a non-existent definition.
755    /// This method will halt if an operand register does not already exist in memory.
756    /// This method will halt if a destination register already exists in memory.
757    /// This method will halt if an output register does not already exist.
758    /// This method will halt if an output type references a non-existent definition.
759    #[inline]
760    fn add_function(&mut self, function: FunctionCore<N>) -> Result<()> {
761        // Retrieve the function name.
762        let function_name = *function.name();
763
764        // Ensure the program has not exceeded the maximum number of functions.
765        ensure!(self.functions.len() < N::MAX_FUNCTIONS, "Program exceeds the maximum number of functions");
766
767        // Ensure the function name is new.
768        ensure!(self.is_unique_name(&function_name), "'{function_name}' is already in use.");
769        // Ensure the function name is not a reserved opcode.
770        ensure!(!Self::is_reserved_opcode(&function_name.to_string()), "'{function_name}' is a reserved opcode.");
771        // Ensure the function name is not a reserved keyword.
772        ensure!(!Self::is_reserved_keyword(&function_name), "'{function_name}' is a reserved keyword.");
773
774        // Ensure the number of inputs is within the allowed range.
775        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
776        // Ensure the number of instructions is within the allowed range.
777        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
778        // Ensure the number of outputs is within the allowed range.
779        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
780
781        // Add the function name to the identifiers.
782        if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() {
783            bail!("'{function_name}' already exists in the program.")
784        }
785        // Add the function to the program.
786        if self.functions.insert(function_name, function).is_some() {
787            bail!("'{function_name}' already exists in the program.")
788        }
789        Ok(())
790    }
791
792    /// Returns `true` if the given name does not already exist in the program.
793    fn is_unique_name(&self, name: &Identifier<N>) -> bool {
794        !self.components.contains_key(&ProgramLabel::Identifier(*name))
795    }
796
797    /// Returns `true` if the given name is a reserved opcode.
798    pub fn is_reserved_opcode(name: &str) -> bool {
799        Instruction::<N>::is_reserved_opcode(name)
800    }
801
802    /// Returns `true` if the given name uses a reserved keyword.
803    pub fn is_reserved_keyword(name: &Identifier<N>) -> bool {
804        // Convert the given name to a string.
805        let name = name.to_string();
806        // Check if the name is a keyword.
807        Self::KEYWORDS.iter().any(|keyword| *keyword == name)
808    }
809
810    /// Returns an iterator over the restricted keywords for the given consensus version.
811    pub fn restricted_keywords_for_consensus_version(
812        consensus_version: ConsensusVersion,
813    ) -> impl Iterator<Item = &'static str> {
814        Self::RESTRICTED_KEYWORDS
815            .iter()
816            .filter(move |(version, _)| *version <= consensus_version)
817            .flat_map(|(_, keywords)| *keywords)
818            .copied()
819    }
820
821    /// Checks a program for restricted keywords for the given consensus version.
822    /// Returns an error if any restricted keywords are found.
823    /// Note: Restrictions are not enforced on the import names in case they were deployed before the restrictions were added.
824    pub fn check_restricted_keywords_for_consensus_version(&self, consensus_version: ConsensusVersion) -> Result<()> {
825        // Get all keywords that are restricted for the consensus version.
826        let keywords =
827            Program::<N>::restricted_keywords_for_consensus_version(consensus_version).collect::<IndexSet<_>>();
828        // Check if the program name is a restricted keywords.
829        let program_name = self.id().name().to_string();
830        if keywords.contains(&program_name.as_str()) {
831            bail!("Program name '{program_name}' is a restricted keyword for the current consensus version")
832        }
833        // Check that all top-level program components are not restricted keywords.
834        for component in self.components.keys() {
835            match component {
836                ProgramLabel::Identifier(identifier) => {
837                    if keywords.contains(identifier.to_string().as_str()) {
838                        bail!(
839                            "Program component '{identifier}' is a restricted keyword for the current consensus version"
840                        )
841                    }
842                }
843                ProgramLabel::Constructor => continue,
844            }
845        }
846        // Check that all record entry names are not restricted keywords.
847        for record_type in self.records().values() {
848            for entry_name in record_type.entries().keys() {
849                if keywords.contains(entry_name.to_string().as_str()) {
850                    bail!("Record entry '{entry_name}' is a restricted keyword for the current consensus version")
851                }
852            }
853        }
854        // Check that all struct member names are not restricted keywords.
855        for struct_type in self.structs().values() {
856            for member_name in struct_type.members().keys() {
857                if keywords.contains(member_name.to_string().as_str()) {
858                    bail!("Struct member '{member_name}' is a restricted keyword for the current consensus version")
859                }
860            }
861        }
862        // Check that all `finalize` positions.
863        // Note: It is sufficient to only check the positions in `FinalizeCore` since `FinalizeTypes::initialize` checks that every
864        // `Branch` instruction targets a valid position.
865        for function in self.functions().values() {
866            if let Some(finalize_logic) = function.finalize_logic() {
867                for position in finalize_logic.positions().keys() {
868                    if keywords.contains(position.to_string().as_str()) {
869                        bail!(
870                            "Finalize position '{position}' is a restricted keyword for the current consensus version"
871                        )
872                    }
873                }
874            }
875        }
876        Ok(())
877    }
878
879    /// Checks that the program structure is well-formed under the following rules:
880    ///  1. The program ID must not contain the keyword "aleo" in the program name.
881    ///  2. The record name must not contain the keyword "aleo".
882    ///  3. Record names must not be prefixes of other record names.
883    ///  4. Record entry names must not contain the keyword "aleo".
884    pub fn check_program_naming_structure(&self) -> Result<()> {
885        // 1. Check if the program ID contains the "aleo" substring
886        let program_id = self.id().name().to_string();
887        if program_id.contains("aleo") {
888            bail!("Program ID '{program_id}' can't contain the reserved keyword 'aleo'.");
889        }
890
891        // Fetch the record names in a sorted BTreeSet.
892        let record_names: BTreeSet<String> = self.records.keys().map(|name| name.to_string()).collect();
893
894        // 2. Check if any record name contains the "aleo" substring.
895        for record_name in &record_names {
896            if record_name.contains("aleo") {
897                bail!("Record name '{record_name}' can't contain the reserved keyword 'aleo'.");
898            }
899        }
900
901        // 3. Check if any of the record names are a prefix of another.
902        let mut record_names_iter = record_names.iter();
903        let mut previous_record_name = record_names_iter.next();
904        for record_name in record_names_iter {
905            if let Some(previous) = previous_record_name {
906                if record_name.starts_with(previous) {
907                    bail!("Record name '{previous}' can't be a prefix of record name '{record_name}'.");
908                }
909            }
910            previous_record_name = Some(record_name);
911        }
912
913        // 4. Check if any record entry names contain the "aleo" substring.
914        for record_entry_name in self.records.values().flat_map(|record_type| record_type.entries().keys()) {
915            if record_entry_name.to_string().contains("aleo") {
916                bail!("Record entry name '{record_entry_name}' can't contain the reserved keyword 'aleo'.");
917            }
918        }
919
920        Ok(())
921    }
922
923    /// Checks that the program does not make external calls to `credits.aleo/upgrade`.
924    pub fn check_external_calls_to_credits_upgrade(&self) -> Result<()> {
925        // Check if the program makes external calls to `credits.aleo/upgrade`.
926        cfg_iter!(self.functions()).flat_map(|(_, function)| function.instructions()).try_for_each(|instruction| {
927            if let Some(CallOperator::Locator(locator)) = instruction.call_operator() {
928                // Check if the locator is restricted.
929                if locator.to_string() == "credits.aleo/upgrade" {
930                    bail!("External call to restricted locator '{locator}'")
931                }
932            }
933            Ok(())
934        })?;
935        Ok(())
936    }
937
938    /// Returns `true` if a program contains any V9 syntax.
939    /// This includes `constructor`, `Operand::Edition`, `Operand::Checksum`, and `Operand::ProgramOwner`.
940    /// This is enforced to be `false` for programs before `ConsensusVersion::V9`.
941    #[inline]
942    pub fn contains_v9_syntax(&self) -> bool {
943        // Check if the program contains a constructor.
944        if self.contains_constructor() {
945            return true;
946        }
947        // Check each instruction and output in each function's finalize scope for the use of
948        // `Operand::Checksum`, `Operand::Edition` or `Operand::ProgramOwner`.
949        for function in self.functions().values() {
950            // Check the finalize scope if it exists.
951            if let Some(finalize_logic) = function.finalize_logic() {
952                // Check the command operands.
953                for command in finalize_logic.commands() {
954                    for operand in command.operands() {
955                        if matches!(operand, Operand::Checksum(_) | Operand::Edition(_) | Operand::ProgramOwner(_)) {
956                            return true;
957                        }
958                    }
959                }
960            }
961        }
962        // Return `false` since no V9 syntax was found.
963        false
964    }
965
966    /// Returns whether this program explicitly refers to an external struct, like `other_program.aleo/StructType`?
967    ///
968    /// This function exists to check if programs to be deployed use external structs so they can be gated
969    /// by consensus version.
970    pub fn contains_external_struct(&self) -> bool {
971        self.mappings.values().any(|mapping| mapping.contains_external_struct())
972            || self
973                .structs
974                .values()
975                .flat_map(|struct_| struct_.members().values())
976                .any(|plaintext_type| plaintext_type.contains_external_struct())
977            || self
978                .records
979                .values()
980                .flat_map(|record| record.entries().values())
981                .any(|entry| entry.plaintext_type().contains_external_struct())
982            || self.closures.values().any(|closure| closure.contains_external_struct())
983            || self.functions.values().any(|function| function.contains_external_struct())
984            || self.constructor.iter().any(|constructor| constructor.contains_external_struct())
985    }
986
987    /// Returns `true` if this program violates pre-V13 rules for external records
988    /// or futures by containing registers with non-local struct types across program boundaries.
989    ///
990    /// Notes:
991    /// 1. We only need to check functions because closures and constructors cannot reference
992    ///    external records or futures.
993    /// 2. We need to check function inputs.
994    /// 3. No need to check instructions other than `Call`. The only other instruction that can
995    ///    refer to a record is a `cast` but we cannot cast to external records anyways.
996    ///4.  No need to check function outputs, because they have already been checked in either the
997    ///    inputs or call instruction.
998    pub fn violates_pre_v13_external_record_and_future_rules<F0, F1, F2, F3>(
999        &self,
1000        get_external_record: &F0,
1001        get_external_function: &F1,
1002        get_external_future: &F2,
1003        is_local_struct: &F3,
1004    ) -> bool
1005    where
1006        F0: Fn(&Locator<N>) -> Result<RecordType<N>>,
1007        F1: Fn(&Locator<N>) -> Result<FunctionCore<N>>,
1008        F2: Fn(&Locator<N>) -> Result<FinalizeCore<N>>,
1009        F3: Fn(&Identifier<N>) -> bool,
1010    {
1011        // Helper: does a plaintext type (possibly nested in arrays) reference a struct not defined locally?
1012        fn plaintext_uses_nonlocal_struct<N: Network>(
1013            ty: &PlaintextType<N>,
1014            is_local_struct: &impl Fn(&Identifier<N>) -> bool,
1015        ) -> bool {
1016            match ty {
1017                PlaintextType::Struct(name) => !is_local_struct(name),
1018                PlaintextType::Array(array_type) => {
1019                    plaintext_uses_nonlocal_struct(array_type.base_element_type(), is_local_struct)
1020                }
1021                _ => false,
1022            }
1023        }
1024
1025        // Helper: does a record contain any struct not defined locally?
1026        let record_uses_nonlocal_struct = |record: &RecordType<N>| {
1027            record.entries().iter().any(|(_, member)| match member {
1028                EntryType::Constant(ty) | EntryType::Private(ty) | EntryType::Public(ty) => {
1029                    plaintext_uses_nonlocal_struct(ty, is_local_struct)
1030                }
1031            })
1032        };
1033
1034        for function in self.functions.values() {
1035            // Scan function inputs for external record types.
1036            for input in function.inputs() {
1037                let ValueType::ExternalRecord(locator) = input.value_type() else {
1038                    continue;
1039                };
1040                let Ok(record) = get_external_record(locator) else {
1041                    continue;
1042                };
1043                if record_uses_nonlocal_struct(&record) {
1044                    return true;
1045                }
1046            }
1047
1048            // Scan instructions for calls to external programs.
1049            for instruction in function.instructions() {
1050                let Instruction::Call(call) = instruction else { continue };
1051                let CallOperator::Locator(locator) = call.operator() else { continue };
1052                let Ok(external_function) = get_external_function(locator) else {
1053                    continue;
1054                };
1055
1056                // Check if the outputs of the external function reference a struct that is not
1057                // locally available.
1058                for output in external_function.outputs() {
1059                    match output.value_type() {
1060                        ValueType::Record(identifier) => {
1061                            let locator = Locator::new(*locator.program_id(), *identifier);
1062                            let Ok(record) = get_external_record(&locator) else {
1063                                continue;
1064                            };
1065                            if record_uses_nonlocal_struct(&record) {
1066                                return true;
1067                            }
1068                        }
1069
1070                        ValueType::Future(loc) => {
1071                            let Ok(future) = get_external_future(loc) else {
1072                                continue;
1073                            };
1074                            for input in future.input_types() {
1075                                let FinalizeType::Plaintext(ty) = input else {
1076                                    continue;
1077                                };
1078
1079                                // We intentionally ignore `FinalizeType::Future(_)` here. Any such future
1080                                // originates from another program whose deployment would already have been
1081                                // validated under the same pre-V13 rules. At this point, only plaintext inputs
1082                                // can introduce new non-local struct violations.
1083
1084                                if plaintext_uses_nonlocal_struct(&ty, is_local_struct) {
1085                                    return true;
1086                                }
1087                            }
1088                        }
1089
1090                        _ => {}
1091                    }
1092                }
1093            }
1094        }
1095
1096        false
1097    }
1098
1099    /// Returns `true` if the program contains an array type with a size that exceeds the given maximum.
1100    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
1101        self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
1102            || self.structs.values().any(|struct_type| struct_type.exceeds_max_array_size(max_array_size))
1103            || self.records.values().any(|record_type| record_type.exceeds_max_array_size(max_array_size))
1104            || self.closures.values().any(|closure| closure.exceeds_max_array_size(max_array_size))
1105            || self.functions.values().any(|function| function.exceeds_max_array_size(max_array_size))
1106            || self.constructor.iter().any(|constructor| constructor.exceeds_max_array_size(max_array_size))
1107    }
1108
1109    /// Returns `true` if a program contains any V11 syntax.
1110    /// This includes:
1111    /// 1. `.raw` hash or signature verification variants
1112    /// 2. `ecdsa.verify.*` opcodes
1113    /// 3. arrays that exceed the previous maximum length of 32.
1114    #[inline]
1115    pub fn contains_v11_syntax(&self) -> bool {
1116        // The previous maximum array size before V11.
1117        const V10_MAX_ARRAY_ELEMENTS: u32 = 32;
1118
1119        // Helper to check if any of the opcodes:
1120        // - start with `ecdsa.verify`, `serialize`, or `deserialize`
1121        // - end with `.raw` or `.native`
1122        let has_op = |opcode: &str| {
1123            opcode.starts_with("ecdsa.verify")
1124                || opcode.starts_with("serialize")
1125                || opcode.starts_with("deserialize")
1126                || opcode.ends_with(".raw")
1127                || opcode.ends_with(".native")
1128        };
1129
1130        // Determine if any function instructions contain the new syntax.
1131        let function_contains = cfg_iter!(self.functions())
1132            .flat_map(|(_, function)| function.instructions())
1133            .any(|instruction| has_op(*instruction.opcode()));
1134
1135        // Determine if any closure instructions contain the new syntax.
1136        let closure_contains = cfg_iter!(self.closures())
1137            .flat_map(|(_, closure)| closure.instructions())
1138            .any(|instruction| has_op(*instruction.opcode()));
1139
1140        // Determine if any finalize commands or constructor commands contain the new syntax.
1141        let command_contains = cfg_iter!(self.functions())
1142            .flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
1143            .flatten()
1144            .chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
1145            .any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
1146
1147        // Determine if any of the array types exceed the previous maximum length of 32.
1148        let array_size_exceeds = self.exceeds_max_array_size(V10_MAX_ARRAY_ELEMENTS);
1149
1150        function_contains || closure_contains || command_contains || array_size_exceeds
1151    }
1152
1153    /// Returns `true` if a program contains any V12 syntax.
1154    /// This includes `Operand::BlockTimestamp`.
1155    /// This is enforced to be `false` for programs before `ConsensusVersion::V12`.
1156    #[inline]
1157    pub fn contains_v12_syntax(&self) -> bool {
1158        // Check each instruction and output in each function's finalize scope for the use of
1159        // `Operand::BlockTimestamp`.
1160        cfg_iter!(self.functions()).any(|(_, function)| {
1161            function.finalize_logic().is_some_and(|finalize_logic| {
1162                cfg_iter!(finalize_logic.commands()).any(|command| {
1163                    cfg_iter!(command.operands()).any(|operand| matches!(operand, Operand::BlockTimestamp))
1164                })
1165            })
1166        })
1167    }
1168
1169    /// Returns `true` if a program contains any string type.
1170    /// Before ConsensusVersion::V12, variable-length string sampling when using them as inputs caused deployment synthesis to be inconsistent and abort with probability 63/64.
1171    /// After ConsensusVersion::V12, string types are disallowed.
1172    #[inline]
1173    pub fn contains_string_type(&self) -> bool {
1174        self.mappings.values().any(|mapping| mapping.contains_string_type())
1175            || self.structs.values().any(|struct_type| struct_type.contains_string_type())
1176            || self.records.values().any(|record_type| record_type.contains_string_type())
1177            || self.closures.values().any(|closure| closure.contains_string_type())
1178            || self.functions.values().any(|function| function.contains_string_type())
1179            || self.constructor.iter().any(|constructor| constructor.contains_string_type())
1180    }
1181}
1182
1183impl<N: Network> TypeName for ProgramCore<N> {
1184    /// Returns the type name as a string.
1185    #[inline]
1186    fn type_name() -> &'static str {
1187        "program"
1188    }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193    use super::*;
1194    use console::{
1195        network::MainnetV0,
1196        program::{Locator, ValueType},
1197    };
1198
1199    type CurrentNetwork = MainnetV0;
1200
1201    #[test]
1202    fn test_program_mapping() -> Result<()> {
1203        // Create a new mapping.
1204        let mapping = Mapping::<CurrentNetwork>::from_str(
1205            r"
1206mapping message:
1207    key as field.public;
1208    value as field.public;",
1209        )?;
1210
1211        // Initialize a new program.
1212        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1213        // Ensure the mapping was added.
1214        assert!(program.contains_mapping(&Identifier::from_str("message")?));
1215        // Ensure the retrieved mapping matches.
1216        assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1217
1218        Ok(())
1219    }
1220
1221    #[test]
1222    fn test_program_struct() -> Result<()> {
1223        // Create a new struct.
1224        let struct_ = StructType::<CurrentNetwork>::from_str(
1225            r"
1226struct message:
1227    first as field;
1228    second as field;",
1229        )?;
1230
1231        // Initialize a new program.
1232        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1233        // Ensure the struct was added.
1234        assert!(program.contains_struct(&Identifier::from_str("message")?));
1235        // Ensure the retrieved struct matches.
1236        assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1237
1238        Ok(())
1239    }
1240
1241    #[test]
1242    fn test_program_record() -> Result<()> {
1243        // Create a new record.
1244        let record = RecordType::<CurrentNetwork>::from_str(
1245            r"
1246record foo:
1247    owner as address.private;
1248    first as field.private;
1249    second as field.public;",
1250        )?;
1251
1252        // Initialize a new program.
1253        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1254        // Ensure the record was added.
1255        assert!(program.contains_record(&Identifier::from_str("foo")?));
1256        // Ensure the retrieved record matches.
1257        assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1258
1259        Ok(())
1260    }
1261
1262    #[test]
1263    fn test_program_function() -> Result<()> {
1264        // Create a new function.
1265        let function = Function::<CurrentNetwork>::from_str(
1266            r"
1267function compute:
1268    input r0 as field.public;
1269    input r1 as field.private;
1270    add r0 r1 into r2;
1271    output r2 as field.private;",
1272        )?;
1273
1274        // Initialize a new program.
1275        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1276        // Ensure the function was added.
1277        assert!(program.contains_function(&Identifier::from_str("compute")?));
1278        // Ensure the retrieved function matches.
1279        assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1280
1281        Ok(())
1282    }
1283
1284    #[test]
1285    fn test_program_import() -> Result<()> {
1286        // Initialize a new program.
1287        let program = Program::<CurrentNetwork>::from_str(
1288            r"
1289import eth.aleo;
1290import usdc.aleo;
1291
1292program swap.aleo;
1293
1294// The `swap` function transfers ownership of the record
1295// for token A to the record owner of token B, and vice-versa.
1296function swap:
1297    // Input the record for token A.
1298    input r0 as eth.aleo/eth.record;
1299    // Input the record for token B.
1300    input r1 as usdc.aleo/usdc.record;
1301
1302    // Send the record for token A to the owner of token B.
1303    call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1304
1305    // Send the record for token B to the owner of token A.
1306    call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1307
1308    // Output the new record for token A.
1309    output r2 as eth.aleo/eth.record;
1310    // Output the new record for token B.
1311    output r4 as usdc.aleo/usdc.record;
1312    ",
1313        )
1314        .unwrap();
1315
1316        // Ensure the program imports exist.
1317        assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1318        assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1319
1320        // Retrieve the 'swap' function.
1321        let function = program.get_function(&Identifier::from_str("swap")?)?;
1322
1323        // Ensure there are two inputs.
1324        assert_eq!(function.inputs().len(), 2);
1325        assert_eq!(function.input_types().len(), 2);
1326
1327        // Declare the expected input types.
1328        let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1329        let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1330
1331        // Ensure the inputs are external records.
1332        assert_eq!(function.input_types()[0], expected_input_type_1);
1333        assert_eq!(function.input_types()[1], expected_input_type_2);
1334
1335        // Ensure the input variants are correct.
1336        assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1337        assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1338
1339        // Ensure there are two instructions.
1340        assert_eq!(function.instructions().len(), 2);
1341
1342        // Ensure the instructions are calls.
1343        assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1344        assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1345
1346        // Ensure there are two outputs.
1347        assert_eq!(function.outputs().len(), 2);
1348        assert_eq!(function.output_types().len(), 2);
1349
1350        // Declare the expected output types.
1351        let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1352        let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1353
1354        // Ensure the outputs are external records.
1355        assert_eq!(function.output_types()[0], expected_output_type_1);
1356        assert_eq!(function.output_types()[1], expected_output_type_2);
1357
1358        // Ensure the output variants are correct.
1359        assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1360        assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1361
1362        Ok(())
1363    }
1364
1365    #[test]
1366    fn test_program_with_constructor() {
1367        // Initialize a new program.
1368        let program_string = r"import credits.aleo;
1369
1370program good_constructor.aleo;
1371
1372constructor:
1373    assert.eq edition 0u16;
1374    assert.eq credits.aleo/edition 0u16;
1375    assert.neq checksum 0field;
1376    assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1377    set 1u8 into data[0u8];
1378
1379mapping data:
1380    key as u8.public;
1381    value as u8.public;
1382
1383function dummy:
1384
1385function check:
1386    async check into r0;
1387    output r0 as good_constructor.aleo/check.future;
1388
1389finalize check:
1390    get data[0u8] into r0;
1391    assert.eq r0 1u8;
1392";
1393        let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1394
1395        // Check that the string and bytes (de)serialization works.
1396        let serialized = program.to_string();
1397        let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1398        assert_eq!(program, deserialized);
1399
1400        let serialized = program.to_bytes_le().unwrap();
1401        let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1402        assert_eq!(program, deserialized);
1403
1404        // Check that the display works.
1405        let display = format!("{program}");
1406        assert_eq!(display, program_string);
1407
1408        // Ensure the program contains a constructor.
1409        assert!(program.contains_constructor());
1410        assert_eq!(program.constructor().unwrap().commands().len(), 5);
1411    }
1412
1413    #[test]
1414    fn test_program_equality_and_checksum() {
1415        fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1416            println!("Comparing programs:\n{program1}\n{program2}");
1417            let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1418            let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1419            assert_eq!(program1 == program2, expected_equal);
1420            assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1421        }
1422
1423        // Test two identical programs, with different whitespace.
1424        run_test(r"program test.aleo; function dummy:    ", r"program  test.aleo;     function dummy:   ", true);
1425
1426        // Test two programs, one with a different function name.
1427        run_test(r"program test.aleo; function dummy:    ", r"program test.aleo; function bummy:   ", false);
1428
1429        // Test two programs, one with a constructor and one without.
1430        run_test(
1431            r"program test.aleo; function dummy:    ",
1432            r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1433            false,
1434        );
1435
1436        // Test two programs, both with a struct and function, but in different order.
1437        run_test(
1438            r"program test.aleo; struct foo: data as u8; function dummy:",
1439            r"program test.aleo; function dummy: struct foo: data as u8;",
1440            false,
1441        );
1442    }
1443}