Skip to main content

snarkvm_synthesizer_program/
lib.rs

1// Copyright (c) 2019-2026 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        consensus_config_value,
62        prelude::{
63            Debug,
64            Deserialize,
65            Deserializer,
66            Display,
67            Err,
68            Error,
69            ErrorKind,
70            Formatter,
71            FromBytes,
72            FromBytesDeserializer,
73            FromStr,
74            IoResult,
75            Itertools,
76            Network,
77            Parser,
78            ParserResult,
79            Read,
80            Result,
81            Sanitizer,
82            Serialize,
83            Serializer,
84            ToBytes,
85            ToBytesSerializer,
86            TypeName,
87            Write,
88            anyhow,
89            bail,
90            de,
91            ensure,
92            error,
93            fmt,
94            make_error,
95            many0,
96            many1,
97            map,
98            map_res,
99            tag,
100            take,
101        },
102    },
103    program::{
104        EntryType,
105        FinalizeType,
106        Identifier,
107        Locator,
108        PlaintextType,
109        ProgramID,
110        RecordType,
111        RegisterType,
112        StructType,
113        ValueType,
114    },
115    types::U8,
116};
117use snarkvm_utilities::{cfg_find_map, cfg_iter};
118
119use indexmap::{IndexMap, IndexSet};
120use std::collections::BTreeSet;
121use tiny_keccak::{Hasher, Sha3 as TinySha3};
122
123#[derive(Copy, Clone, PartialEq, Eq, Hash)]
124enum ProgramLabel<N: Network> {
125    /// A program constructor.
126    Constructor,
127    /// A named component.
128    Identifier(Identifier<N>),
129}
130
131#[cfg(not(feature = "serial"))]
132use rayon::prelude::*;
133
134#[derive(Copy, Clone, PartialEq, Eq, Hash)]
135enum ProgramDefinition {
136    /// A program constructor.
137    Constructor,
138    /// A program mapping.
139    Mapping,
140    /// A program struct.
141    Struct,
142    /// A program record.
143    Record,
144    /// A program closure.
145    Closure,
146    /// A program function.
147    Function,
148}
149
150#[derive(Clone)]
151pub struct ProgramCore<N: Network> {
152    /// The ID of the program.
153    id: ProgramID<N>,
154    /// A map of the declared imports for the program.
155    imports: IndexMap<ProgramID<N>, Import<N>>,
156    /// A map of program labels to their program definitions.
157    components: IndexMap<ProgramLabel<N>, ProgramDefinition>,
158    /// An optional constructor for the program.
159    constructor: Option<ConstructorCore<N>>,
160    /// A map of the declared mappings for the program.
161    mappings: IndexMap<Identifier<N>, Mapping<N>>,
162    /// A map of the declared structs for the program.
163    structs: IndexMap<Identifier<N>, StructType<N>>,
164    /// A map of the declared record types for the program.
165    records: IndexMap<Identifier<N>, RecordType<N>>,
166    /// A map of the declared closures for the program.
167    closures: IndexMap<Identifier<N>, ClosureCore<N>>,
168    /// A map of the declared functions for the program.
169    functions: IndexMap<Identifier<N>, FunctionCore<N>>,
170}
171
172impl<N: Network> PartialEq for ProgramCore<N> {
173    /// Compares two programs for equality, verifying that the components are in the same order.
174    /// The order of the components must match to ensure that deployment tree is well-formed.
175    fn eq(&self, other: &Self) -> bool {
176        // Check that the number of components is the same.
177        if self.components.len() != other.components.len() {
178            return false;
179        }
180        // Check that the components match in order.
181        for (left, right) in self.components.iter().zip_eq(other.components.iter()) {
182            if left != right {
183                return false;
184            }
185        }
186        // Check that the remaining fields match.
187        self.id == other.id
188            && self.imports == other.imports
189            && self.mappings == other.mappings
190            && self.structs == other.structs
191            && self.records == other.records
192            && self.closures == other.closures
193            && self.functions == other.functions
194    }
195}
196
197impl<N: Network> Eq for ProgramCore<N> {}
198
199impl<N: Network> ProgramCore<N> {
200    /// A list of reserved keywords for Aleo programs, enforced at the parser level.
201    // New keywords should be enforced through `RESTRICTED_KEYWORDS` instead, if possible.
202    // Adding keywords to this list will require a backwards-compatible versioning for programs.
203    #[rustfmt::skip]
204    pub const KEYWORDS: &'static [&'static str] = &[
205        // Mode
206        "const",
207        "constant",
208        "public",
209        "private",
210        // Literals
211        "address",
212        "boolean",
213        "field",
214        "group",
215        "i8",
216        "i16",
217        "i32",
218        "i64",
219        "i128",
220        "u8",
221        "u16",
222        "u32",
223        "u64",
224        "u128",
225        "scalar",
226        "signature",
227        "string",
228        // Boolean
229        "true",
230        "false",
231        // Statements
232        "input",
233        "output",
234        "as",
235        "into",
236        // Record
237        "record",
238        "owner",
239        // Program
240        "transition",
241        "import",
242        "function",
243        "struct",
244        "closure",
245        "program",
246        "aleo",
247        "self",
248        "storage",
249        "mapping",
250        "key",
251        "value",
252        "async",
253        "finalize",
254        // Reserved (catch all)
255        "global",
256        "block",
257        "return",
258        "break",
259        "assert",
260        "continue",
261        "let",
262        "if",
263        "else",
264        "while",
265        "for",
266        "switch",
267        "case",
268        "default",
269        "match",
270        "enum",
271        "struct",
272        "union",
273        "trait",
274        "impl",
275        "type",
276        "future",
277    ];
278    /// A list of restricted keywords for Aleo programs, enforced at the VM-level for program hygiene.
279    /// Each entry is a tuple of the consensus version and a list of keywords.
280    /// If the current consensus version is greater than or equal to the specified version,
281    /// the keywords in the list should be restricted.
282    #[rustfmt::skip]
283    pub const RESTRICTED_KEYWORDS: &'static [(ConsensusVersion, &'static [&'static str])] = &[
284        (ConsensusVersion::V6, &["constructor"]),
285        (ConsensusVersion::V14, &["dynamic", "identifier"]),
286    ];
287
288    /// Initializes an empty program.
289    #[inline]
290    pub fn new(id: ProgramID<N>) -> Result<Self> {
291        // Ensure the program name is valid.
292        ensure!(!Self::is_reserved_keyword(id.name()), "Program name is invalid: {}", id.name());
293
294        Ok(Self {
295            id,
296            imports: IndexMap::new(),
297            constructor: None,
298            components: IndexMap::new(),
299            mappings: IndexMap::new(),
300            structs: IndexMap::new(),
301            records: IndexMap::new(),
302            closures: IndexMap::new(),
303            functions: IndexMap::new(),
304        })
305    }
306
307    /// Initializes the credits program.
308    #[inline]
309    pub fn credits() -> Result<Self> {
310        Self::from_str(include_str!("./resources/credits.aleo"))
311    }
312
313    /// Returns the ID of the program.
314    pub const fn id(&self) -> &ProgramID<N> {
315        &self.id
316    }
317
318    /// Returns the imports in the program.
319    pub const fn imports(&self) -> &IndexMap<ProgramID<N>, Import<N>> {
320        &self.imports
321    }
322
323    /// Returns the constructor for the program.
324    pub const fn constructor(&self) -> Option<&ConstructorCore<N>> {
325        self.constructor.as_ref()
326    }
327
328    /// Returns the mappings in the program.
329    pub const fn mappings(&self) -> &IndexMap<Identifier<N>, Mapping<N>> {
330        &self.mappings
331    }
332
333    /// Returns the structs in the program.
334    pub const fn structs(&self) -> &IndexMap<Identifier<N>, StructType<N>> {
335        &self.structs
336    }
337
338    /// Returns the records in the program.
339    pub const fn records(&self) -> &IndexMap<Identifier<N>, RecordType<N>> {
340        &self.records
341    }
342
343    /// Returns the closures in the program.
344    pub const fn closures(&self) -> &IndexMap<Identifier<N>, ClosureCore<N>> {
345        &self.closures
346    }
347
348    /// Returns the functions in the program.
349    pub const fn functions(&self) -> &IndexMap<Identifier<N>, FunctionCore<N>> {
350        &self.functions
351    }
352
353    /// Returns `true` if the program contains an import with the given program ID.
354    pub fn contains_import(&self, id: &ProgramID<N>) -> bool {
355        self.imports.contains_key(id)
356    }
357
358    /// Returns `true` if the program contains a constructor.
359    pub const fn contains_constructor(&self) -> bool {
360        self.constructor.is_some()
361    }
362
363    /// Returns `true` if the program contains a mapping with the given name.
364    pub fn contains_mapping(&self, name: &Identifier<N>) -> bool {
365        self.mappings.contains_key(name)
366    }
367
368    /// Returns `true` if the program contains a struct with the given name.
369    pub fn contains_struct(&self, name: &Identifier<N>) -> bool {
370        self.structs.contains_key(name)
371    }
372
373    /// Returns `true` if the program contains a record with the given name.
374    pub fn contains_record(&self, name: &Identifier<N>) -> bool {
375        self.records.contains_key(name)
376    }
377
378    /// Returns `true` if the program contains a closure with the given name.
379    pub fn contains_closure(&self, name: &Identifier<N>) -> bool {
380        self.closures.contains_key(name)
381    }
382
383    /// Returns `true` if the program contains a function with the given name.
384    pub fn contains_function(&self, name: &Identifier<N>) -> bool {
385        self.functions.contains_key(name)
386    }
387
388    /// Returns the mapping with the given name.
389    pub fn get_mapping(&self, name: &Identifier<N>) -> Result<Mapping<N>> {
390        // Attempt to retrieve the mapping.
391        let mapping = self.mappings.get(name).cloned().ok_or_else(|| anyhow!("Mapping '{name}' is not defined."))?;
392        // Ensure the mapping name matches.
393        ensure!(mapping.name() == name, "Expected mapping '{name}', but found mapping '{}'", mapping.name());
394        // Return the mapping.
395        Ok(mapping)
396    }
397
398    /// Returns the struct with the given name.
399    pub fn get_struct(&self, name: &Identifier<N>) -> Result<&StructType<N>> {
400        // Attempt to retrieve the struct.
401        let struct_ = self.structs.get(name).ok_or_else(|| anyhow!("Struct '{name}' is not defined."))?;
402        // Ensure the struct name matches.
403        ensure!(struct_.name() == name, "Expected struct '{name}', but found struct '{}'", struct_.name());
404        // Ensure the struct contains members.
405        ensure!(!struct_.members().is_empty(), "Struct '{name}' is missing members.");
406        // Return the struct.
407        Ok(struct_)
408    }
409
410    /// Returns the record with the given name.
411    pub fn get_record(&self, name: &Identifier<N>) -> Result<&RecordType<N>> {
412        // Attempt to retrieve the record.
413        let record = self.records.get(name).ok_or_else(|| anyhow!("Record '{name}' is not defined."))?;
414        // Ensure the record name matches.
415        ensure!(record.name() == name, "Expected record '{name}', but found record '{}'", record.name());
416        // Return the record.
417        Ok(record)
418    }
419
420    /// Returns the closure with the given name.
421    pub fn get_closure(&self, name: &Identifier<N>) -> Result<ClosureCore<N>> {
422        // Attempt to retrieve the closure.
423        let closure = self.closures.get(name).cloned().ok_or_else(|| anyhow!("Closure '{name}' is not defined."))?;
424        // Ensure the closure name matches.
425        ensure!(closure.name() == name, "Expected closure '{name}', but found closure '{}'", closure.name());
426        // Ensure there are input statements in the closure.
427        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
428        // Ensure the number of inputs is within the allowed range.
429        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
430        // Ensure there are instructions in the closure.
431        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
432        // Ensure the number of outputs is within the allowed range.
433        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
434        // Return the closure.
435        Ok(closure)
436    }
437
438    /// Returns the function with the given name.
439    pub fn get_function(&self, name: &Identifier<N>) -> Result<FunctionCore<N>> {
440        self.get_function_ref(name).cloned()
441    }
442
443    /// Returns a reference to the function with the given name.
444    pub fn get_function_ref(&self, name: &Identifier<N>) -> Result<&FunctionCore<N>> {
445        // Attempt to retrieve the function.
446        let function = self.functions.get(name).ok_or(anyhow!("Function '{}/{name}' is not defined.", self.id))?;
447        // Ensure the function name matches.
448        ensure!(function.name() == name, "Expected function '{name}', but found function '{}'", function.name());
449        // Ensure the number of inputs is within the allowed range.
450        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
451        // Ensure the number of instructions is within the allowed range.
452        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
453        // Ensure the number of outputs is within the allowed range.
454        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
455        // Return the function.
456        Ok(function)
457    }
458
459    /// Adds a new import statement to the program.
460    ///
461    /// # Errors
462    /// This method will halt if the imported program was previously added.
463    #[inline]
464    fn add_import(&mut self, import: Import<N>) -> Result<()> {
465        // Retrieve the imported program name.
466        let import_name = *import.name();
467
468        // Ensure that the number of imports is within the allowed range.
469        ensure!(self.imports.len() < N::MAX_IMPORTS, "Program exceeds the maximum number of imports");
470
471        // Ensure the import name is new.
472        ensure!(self.is_unique_name(&import_name), "'{import_name}' is already in use.");
473        // Ensure the import name is not a reserved opcode.
474        ensure!(!Self::is_reserved_opcode(&import_name.to_string()), "'{import_name}' is a reserved opcode.");
475        // Ensure the import name is not a reserved keyword.
476        ensure!(!Self::is_reserved_keyword(&import_name), "'{import_name}' is a reserved keyword.");
477
478        // Ensure the import is new.
479        ensure!(
480            !self.imports.contains_key(import.program_id()),
481            "Import '{}' is already defined.",
482            import.program_id()
483        );
484
485        // Add the import statement to the program.
486        if self.imports.insert(*import.program_id(), import.clone()).is_some() {
487            bail!("'{}' already exists in the program.", import.program_id())
488        }
489        Ok(())
490    }
491
492    /// Adds a constructor to the program.
493    ///
494    /// # Errors
495    /// This method will halt if a constructor was previously added.
496    /// This method will halt if a constructor exceeds the maximum number of commands.
497    fn add_constructor(&mut self, constructor: ConstructorCore<N>) -> Result<()> {
498        // Ensure the program does not already have a constructor.
499        ensure!(self.constructor.is_none(), "Program already has a constructor.");
500        // Ensure the number of commands is within the allowed range.
501        ensure!(!constructor.commands().is_empty(), "Constructor must have at least one command");
502        ensure!(constructor.commands().len() <= N::MAX_COMMANDS, "Constructor exceeds maximum number of commands");
503        // Add the constructor to the components.
504        if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() {
505            bail!("Constructor already exists in the program.")
506        }
507        // Add the constructor to the program.
508        self.constructor = Some(constructor);
509        Ok(())
510    }
511
512    /// Adds a new mapping to the program.
513    ///
514    /// # Errors
515    /// This method will halt if the mapping name is already in use.
516    /// This method will halt if the mapping name is a reserved opcode or keyword.
517    #[inline]
518    fn add_mapping(&mut self, mapping: Mapping<N>) -> Result<()> {
519        // Retrieve the mapping name.
520        let mapping_name = *mapping.name();
521
522        // Ensure the program has not exceeded the maximum number of mappings.
523        ensure!(self.mappings.len() < N::MAX_MAPPINGS, "Program exceeds the maximum number of mappings");
524
525        // Ensure the mapping name is new.
526        ensure!(self.is_unique_name(&mapping_name), "'{mapping_name}' is already in use.");
527        // Ensure the mapping name is not a reserved keyword.
528        ensure!(!Self::is_reserved_keyword(&mapping_name), "'{mapping_name}' is a reserved keyword.");
529        // Ensure the mapping name is not a reserved opcode.
530        ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode.");
531
532        // Add the mapping name to the identifiers.
533        if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() {
534            bail!("'{mapping_name}' already exists in the program.")
535        }
536        // Add the mapping to the program.
537        if self.mappings.insert(mapping_name, mapping).is_some() {
538            bail!("'{mapping_name}' already exists in the program.")
539        }
540        Ok(())
541    }
542
543    /// Adds a new struct to the program.
544    ///
545    /// # Errors
546    /// This method will halt if the struct was previously added.
547    /// This method will halt if the struct name is already in use in the program.
548    /// This method will halt if the struct name is a reserved opcode or keyword.
549    /// This method will halt if any structs in the struct's members are not already defined.
550    #[inline]
551    fn add_struct(&mut self, struct_: StructType<N>) -> Result<()> {
552        // Retrieve the struct name.
553        let struct_name = *struct_.name();
554
555        // Ensure the program has not exceeded the maximum number of structs.
556        ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");
557
558        // Ensure the struct name is new.
559        ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
560        // Ensure the struct name is not a reserved opcode.
561        ensure!(!Self::is_reserved_opcode(&struct_name.to_string()), "'{struct_name}' is a reserved opcode.");
562        // Ensure the struct name is not a reserved keyword.
563        ensure!(!Self::is_reserved_keyword(&struct_name), "'{struct_name}' is a reserved keyword.");
564
565        // Ensure the struct contains members.
566        ensure!(!struct_.members().is_empty(), "Struct '{struct_name}' is missing members.");
567
568        // Ensure all struct members are well-formed.
569        // Note: This design ensures cyclic references are not possible.
570        for (identifier, plaintext_type) in struct_.members() {
571            // Ensure the member name is not a reserved keyword.
572            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
573            // Ensure the member type is already defined in the program.
574            match plaintext_type {
575                PlaintextType::Literal(_) => continue,
576                PlaintextType::Struct(member_identifier) => {
577                    // Ensure the member struct name exists in the program.
578                    if !self.structs.contains_key(member_identifier) {
579                        bail!("'{member_identifier}' in struct '{struct_name}' is not defined.")
580                    }
581                }
582                PlaintextType::ExternalStruct(locator) => {
583                    if !self.imports.contains_key(locator.program_id()) {
584                        bail!(
585                            "External program {} referenced in struct '{struct_name}' does not exist",
586                            locator.program_id()
587                        );
588                    }
589                }
590                PlaintextType::Array(array_type) => {
591                    match array_type.base_element_type() {
592                        PlaintextType::Struct(struct_name) =>
593                        // Ensure the member struct name exists in the program.
594                        {
595                            if !self.structs.contains_key(struct_name) {
596                                bail!("'{struct_name}' in array '{array_type}' is not defined.")
597                            }
598                        }
599                        PlaintextType::ExternalStruct(locator) => {
600                            if !self.imports.contains_key(locator.program_id()) {
601                                bail!(
602                                    "External program {} in array '{array_type}' does not exist",
603                                    locator.program_id()
604                                );
605                            }
606                        }
607                        PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
608                    }
609                }
610            }
611        }
612
613        // Add the struct name to the identifiers.
614        if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() {
615            bail!("'{struct_name}' already exists in the program.")
616        }
617        // Add the struct to the program.
618        if self.structs.insert(struct_name, struct_).is_some() {
619            bail!("'{struct_name}' already exists in the program.")
620        }
621        Ok(())
622    }
623
624    /// Adds a new record to the program.
625    ///
626    /// # Errors
627    /// This method will halt if the record was previously added.
628    /// This method will halt if the record name is already in use in the program.
629    /// This method will halt if the record name is a reserved opcode or keyword.
630    /// This method will halt if any records in the record's members are not already defined.
631    #[inline]
632    fn add_record(&mut self, record: RecordType<N>) -> Result<()> {
633        // Retrieve the record name.
634        let record_name = *record.name();
635
636        // Ensure the program has not exceeded the maximum number of records.
637        ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");
638
639        // Ensure the record name is new.
640        ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
641        // Ensure the record name is not a reserved opcode.
642        ensure!(!Self::is_reserved_opcode(&record_name.to_string()), "'{record_name}' is a reserved opcode.");
643        // Ensure the record name is not a reserved keyword.
644        ensure!(!Self::is_reserved_keyword(&record_name), "'{record_name}' is a reserved keyword.");
645
646        // Ensure all record entries are well-formed.
647        // Note: This design ensures cyclic references are not possible.
648        for (identifier, entry_type) in record.entries() {
649            // Ensure the member name is not a reserved keyword.
650            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
651            // Ensure the member type is already defined in the program.
652            match entry_type.plaintext_type() {
653                PlaintextType::Literal(_) => continue,
654                PlaintextType::Struct(identifier) => {
655                    if !self.structs.contains_key(identifier) {
656                        bail!("Struct '{identifier}' in record '{record_name}' is not defined.")
657                    }
658                }
659                PlaintextType::ExternalStruct(locator) => {
660                    if !self.imports.contains_key(locator.program_id()) {
661                        bail!(
662                            "External program {} referenced in record '{record_name}' does not exist",
663                            locator.program_id()
664                        );
665                    }
666                }
667                PlaintextType::Array(array_type) => {
668                    match array_type.base_element_type() {
669                        PlaintextType::Struct(struct_name) =>
670                        // Ensure the member struct name exists in the program.
671                        {
672                            if !self.structs.contains_key(struct_name) {
673                                bail!("'{struct_name}' in array '{array_type}' is not defined.")
674                            }
675                        }
676                        PlaintextType::ExternalStruct(locator) => {
677                            if !self.imports.contains_key(locator.program_id()) {
678                                bail!(
679                                    "External program {} in array '{array_type}' does not exist",
680                                    locator.program_id()
681                                );
682                            }
683                        }
684                        PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
685                    }
686                }
687            }
688        }
689
690        // Add the record name to the identifiers.
691        if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() {
692            bail!("'{record_name}' already exists in the program.")
693        }
694        // Add the record to the program.
695        if self.records.insert(record_name, record).is_some() {
696            bail!("'{record_name}' already exists in the program.")
697        }
698        Ok(())
699    }
700
701    /// Adds a new closure to the program.
702    ///
703    /// # Errors
704    /// This method will halt if the closure was previously added.
705    /// This method will halt if the closure name is already in use in the program.
706    /// This method will halt if the closure name is a reserved opcode or keyword.
707    /// This method will halt if any registers are assigned more than once.
708    /// This method will halt if the registers are not incrementing monotonically.
709    /// This method will halt if an input type references a non-existent definition.
710    /// This method will halt if an operand register does not already exist in memory.
711    /// This method will halt if a destination register already exists in memory.
712    /// This method will halt if an output register does not already exist.
713    /// This method will halt if an output type references a non-existent definition.
714    #[inline]
715    fn add_closure(&mut self, closure: ClosureCore<N>) -> Result<()> {
716        // Retrieve the closure name.
717        let closure_name = *closure.name();
718
719        // Ensure the program has not exceeded the maximum number of closures.
720        ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");
721
722        // Ensure the closure name is new.
723        ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
724        // Ensure the closure name is not a reserved opcode.
725        ensure!(!Self::is_reserved_opcode(&closure_name.to_string()), "'{closure_name}' is a reserved opcode.");
726        // Ensure the closure name is not a reserved keyword.
727        ensure!(!Self::is_reserved_keyword(&closure_name), "'{closure_name}' is a reserved keyword.");
728
729        // Ensure there are input statements in the closure.
730        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
731        // Ensure the number of inputs is within the allowed range.
732        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
733        // Ensure there are instructions in the closure.
734        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
735        // Ensure the number of outputs is within the allowed range.
736        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
737
738        // Add the function name to the identifiers.
739        if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() {
740            bail!("'{closure_name}' already exists in the program.")
741        }
742        // Add the closure to the program.
743        if self.closures.insert(closure_name, closure).is_some() {
744            bail!("'{closure_name}' already exists in the program.")
745        }
746        Ok(())
747    }
748
749    /// Adds a new function to the program.
750    ///
751    /// # Errors
752    /// This method will halt if the function was previously added.
753    /// This method will halt if the function name is already in use in the program.
754    /// This method will halt if the function name is a reserved opcode or keyword.
755    /// This method will halt if any registers are assigned more than once.
756    /// This method will halt if the registers are not incrementing monotonically.
757    /// This method will halt if an input type references a non-existent definition.
758    /// This method will halt if an operand register does not already exist in memory.
759    /// This method will halt if a destination register already exists in memory.
760    /// This method will halt if an output register does not already exist.
761    /// This method will halt if an output type references a non-existent definition.
762    #[inline]
763    fn add_function(&mut self, function: FunctionCore<N>) -> Result<()> {
764        // Retrieve the function name.
765        let function_name = *function.name();
766
767        // Ensure the program has not exceeded the maximum number of functions.
768        ensure!(self.functions.len() < N::MAX_FUNCTIONS, "Program exceeds the maximum number of functions");
769
770        // Ensure the function name is new.
771        ensure!(self.is_unique_name(&function_name), "'{function_name}' is already in use.");
772        // Ensure the function name is not a reserved opcode.
773        ensure!(!Self::is_reserved_opcode(&function_name.to_string()), "'{function_name}' is a reserved opcode.");
774        // Ensure the function name is not a reserved keyword.
775        ensure!(!Self::is_reserved_keyword(&function_name), "'{function_name}' is a reserved keyword.");
776
777        // Ensure the number of inputs is within the allowed range.
778        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
779        // Ensure the number of instructions is within the allowed range.
780        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
781        // Ensure the number of outputs is within the allowed range.
782        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
783
784        // Add the function name to the identifiers.
785        if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() {
786            bail!("'{function_name}' already exists in the program.")
787        }
788        // Add the function to the program.
789        if self.functions.insert(function_name, function).is_some() {
790            bail!("'{function_name}' already exists in the program.")
791        }
792        Ok(())
793    }
794
795    /// Returns `true` if the given name does not already exist in the program.
796    fn is_unique_name(&self, name: &Identifier<N>) -> bool {
797        !self.components.contains_key(&ProgramLabel::Identifier(*name))
798    }
799
800    /// Returns `true` if the given name is a reserved opcode.
801    pub fn is_reserved_opcode(name: &str) -> bool {
802        Instruction::<N>::is_reserved_opcode(name)
803    }
804
805    /// Returns `true` if the given name uses a reserved keyword.
806    pub fn is_reserved_keyword(name: &Identifier<N>) -> bool {
807        // Convert the given name to a string.
808        let name = name.to_string();
809        // Check if the name is a keyword.
810        Self::KEYWORDS.iter().any(|keyword| *keyword == name)
811    }
812
813    /// Returns an iterator over the restricted keywords for the given consensus version.
814    pub fn restricted_keywords_for_consensus_version(
815        consensus_version: ConsensusVersion,
816    ) -> impl Iterator<Item = &'static str> {
817        Self::RESTRICTED_KEYWORDS
818            .iter()
819            .filter(move |(version, _)| *version <= consensus_version)
820            .flat_map(|(_, keywords)| *keywords)
821            .copied()
822    }
823
824    /// Checks a program for restricted keywords for the given consensus version.
825    /// Returns an error if any restricted keywords are found.
826    /// Note: Restrictions are not enforced on the import names in case they were deployed before the restrictions were added.
827    pub fn check_restricted_keywords_for_consensus_version(&self, consensus_version: ConsensusVersion) -> Result<()> {
828        // Get all keywords that are restricted for the consensus version.
829        let keywords =
830            Program::<N>::restricted_keywords_for_consensus_version(consensus_version).collect::<IndexSet<_>>();
831        // Check if the program name is a restricted keywords.
832        let program_name = self.id().name().to_string();
833        if keywords.contains(&program_name.as_str()) {
834            bail!("Program name '{program_name}' is a restricted keyword for the current consensus version")
835        }
836        // Check that all top-level program components are not restricted keywords.
837        for component in self.components.keys() {
838            match component {
839                ProgramLabel::Identifier(identifier) => {
840                    if keywords.contains(identifier.to_string().as_str()) {
841                        bail!(
842                            "Program component '{identifier}' is a restricted keyword for the current consensus version"
843                        )
844                    }
845                }
846                ProgramLabel::Constructor => continue,
847            }
848        }
849        // Check that all record entry names are not restricted keywords.
850        for record_type in self.records().values() {
851            for entry_name in record_type.entries().keys() {
852                if keywords.contains(entry_name.to_string().as_str()) {
853                    bail!("Record entry '{entry_name}' is a restricted keyword for the current consensus version")
854                }
855            }
856        }
857        // Check that all struct member names are not restricted keywords.
858        for struct_type in self.structs().values() {
859            for member_name in struct_type.members().keys() {
860                if keywords.contains(member_name.to_string().as_str()) {
861                    bail!("Struct member '{member_name}' is a restricted keyword for the current consensus version")
862                }
863            }
864        }
865        // Check that all `finalize` positions.
866        // Note: It is sufficient to only check the positions in `FinalizeCore` since `FinalizeTypes::initialize` checks that every
867        // `Branch` instruction targets a valid position.
868        for function in self.functions().values() {
869            if let Some(finalize_logic) = function.finalize_logic() {
870                for position in finalize_logic.positions().keys() {
871                    if keywords.contains(position.to_string().as_str()) {
872                        bail!(
873                            "Finalize position '{position}' is a restricted keyword for the current consensus version"
874                        )
875                    }
876                }
877            }
878        }
879        Ok(())
880    }
881
882    /// Checks that the program structure is well-formed under the following rules:
883    ///  1. The program ID must not contain the keyword "aleo" in the program name.
884    ///  2. The record name must not contain the keyword "aleo".
885    ///  3. Record names must not be prefixes of other record names.
886    ///  4. Record entry names must not contain the keyword "aleo".
887    pub fn check_program_naming_structure(&self) -> Result<()> {
888        // 1. Check if the program ID contains the "aleo" substring
889        let program_id = self.id().name().to_string();
890        if program_id.contains("aleo") {
891            bail!("Program ID '{program_id}' can't contain the reserved keyword 'aleo'.");
892        }
893
894        // Fetch the record names in a sorted BTreeSet.
895        let record_names: BTreeSet<String> = self.records.keys().map(|name| name.to_string()).collect();
896
897        // 2. Check if any record name contains the "aleo" substring.
898        for record_name in &record_names {
899            if record_name.contains("aleo") {
900                bail!("Record name '{record_name}' can't contain the reserved keyword 'aleo'.");
901            }
902        }
903
904        // 3. Check if any of the record names are a prefix of another.
905        let mut record_names_iter = record_names.iter();
906        let mut previous_record_name = record_names_iter.next();
907        for record_name in record_names_iter {
908            if let Some(previous) = previous_record_name {
909                if record_name.starts_with(previous) {
910                    bail!("Record name '{previous}' can't be a prefix of record name '{record_name}'.");
911                }
912            }
913            previous_record_name = Some(record_name);
914        }
915
916        // 4. Check if any record entry names contain the "aleo" substring.
917        for record_entry_name in self.records.values().flat_map(|record_type| record_type.entries().keys()) {
918            if record_entry_name.to_string().contains("aleo") {
919                bail!("Record entry name '{record_entry_name}' can't contain the reserved keyword 'aleo'.");
920            }
921        }
922
923        Ok(())
924    }
925
926    /// Checks that the program does not make external calls to `credits.aleo/upgrade`.
927    pub fn check_external_calls_to_credits_upgrade(&self) -> Result<()> {
928        // Check if the program makes external calls to `credits.aleo/upgrade`.
929        cfg_iter!(self.functions()).flat_map(|(_, function)| function.instructions()).try_for_each(|instruction| {
930            if let Some(CallOperator::Locator(locator)) = instruction.call_operator() {
931                // Check if the locator is restricted.
932                if locator.to_string() == "credits.aleo/upgrade" {
933                    bail!("External call to restricted locator '{locator}'")
934                }
935            }
936            Ok(())
937        })?;
938        Ok(())
939    }
940
941    /// Returns `true` if a program contains any V9 syntax.
942    /// This includes `constructor`, `Operand::Edition`, `Operand::Checksum`, and `Operand::ProgramOwner`.
943    /// This is enforced to be `false` for programs before `ConsensusVersion::V9`.
944    #[inline]
945    pub fn contains_v9_syntax(&self) -> bool {
946        // Check if the program contains a constructor.
947        if self.contains_constructor() {
948            return true;
949        }
950        // Check each instruction and output in each function's finalize scope for the use of
951        // `Operand::Checksum`, `Operand::Edition` or `Operand::ProgramOwner`.
952        for function in self.functions().values() {
953            // Check the finalize scope if it exists.
954            if let Some(finalize_logic) = function.finalize_logic() {
955                // Check the command operands.
956                for command in finalize_logic.commands() {
957                    for operand in command.operands() {
958                        if matches!(operand, Operand::Checksum(_) | Operand::Edition(_) | Operand::ProgramOwner(_)) {
959                            return true;
960                        }
961                    }
962                }
963            }
964        }
965        // Return `false` since no V9 syntax was found.
966        false
967    }
968
969    /// Returns whether this program explicitly refers to an external struct, like `other_program.aleo/StructType`?
970    ///
971    /// This function exists to check if programs to be deployed use external structs so they can be gated
972    /// by consensus version.
973    pub fn contains_external_struct(&self) -> bool {
974        self.mappings.values().any(|mapping| mapping.contains_external_struct())
975            || self
976                .structs
977                .values()
978                .flat_map(|struct_| struct_.members().values())
979                .any(|plaintext_type| plaintext_type.contains_external_struct())
980            || self
981                .records
982                .values()
983                .flat_map(|record| record.entries().values())
984                .any(|entry| entry.plaintext_type().contains_external_struct())
985            || self.closures.values().any(|closure| closure.contains_external_struct())
986            || self.functions.values().any(|function| function.contains_external_struct())
987            || self.constructor.iter().any(|constructor| constructor.contains_external_struct())
988    }
989
990    /// Returns `true` if this program violates pre-V13 rules for external records
991    /// or futures by containing registers with non-local struct types across program boundaries.
992    ///
993    /// Notes:
994    /// 1. We only need to check functions because closures and constructors cannot reference
995    ///    external records or futures.
996    /// 2. We need to check function inputs.
997    /// 3. No need to check instructions other than `Call`. The only other instruction that can
998    ///    refer to a record is a `cast` but we cannot cast to external records anyways.
999    ///4.  No need to check function outputs, because they have already been checked in either the
1000    ///    inputs or call instruction.
1001    pub fn violates_pre_v13_external_record_and_future_rules<F0, F1, F2, F3>(
1002        &self,
1003        get_external_record: &F0,
1004        get_external_function: &F1,
1005        get_external_future: &F2,
1006        is_local_struct: &F3,
1007    ) -> bool
1008    where
1009        F0: Fn(&Locator<N>) -> Result<RecordType<N>>,
1010        F1: Fn(&Locator<N>) -> Result<FunctionCore<N>>,
1011        F2: Fn(&Locator<N>) -> Result<FinalizeCore<N>>,
1012        F3: Fn(&Identifier<N>) -> bool,
1013    {
1014        // Helper: does a plaintext type (possibly nested in arrays) reference a struct not defined locally?
1015        fn plaintext_uses_nonlocal_struct<N: Network>(
1016            ty: &PlaintextType<N>,
1017            is_local_struct: &impl Fn(&Identifier<N>) -> bool,
1018        ) -> bool {
1019            match ty {
1020                PlaintextType::Struct(name) => !is_local_struct(name),
1021                PlaintextType::Array(array_type) => {
1022                    plaintext_uses_nonlocal_struct(array_type.base_element_type(), is_local_struct)
1023                }
1024                _ => false,
1025            }
1026        }
1027
1028        // Helper: does a record contain any struct not defined locally?
1029        let record_uses_nonlocal_struct = |record: &RecordType<N>| {
1030            record.entries().iter().any(|(_, member)| match member {
1031                EntryType::Constant(ty) | EntryType::Private(ty) | EntryType::Public(ty) => {
1032                    plaintext_uses_nonlocal_struct(ty, is_local_struct)
1033                }
1034            })
1035        };
1036
1037        for function in self.functions.values() {
1038            // Scan function inputs for external record types.
1039            for input in function.inputs() {
1040                let ValueType::ExternalRecord(locator) = input.value_type() else {
1041                    continue;
1042                };
1043                let Ok(record) = get_external_record(locator) else {
1044                    continue;
1045                };
1046                if record_uses_nonlocal_struct(&record) {
1047                    return true;
1048                }
1049            }
1050
1051            // Scan instructions for calls to external programs.
1052            for instruction in function.instructions() {
1053                let Instruction::Call(call) = instruction else { continue };
1054                let CallOperator::Locator(locator) = call.operator() else { continue };
1055                let Ok(external_function) = get_external_function(locator) else {
1056                    continue;
1057                };
1058
1059                // Check if the outputs of the external function reference a struct that is not
1060                // locally available.
1061                for output in external_function.outputs() {
1062                    match output.value_type() {
1063                        ValueType::Record(identifier) => {
1064                            let locator = Locator::new(*locator.program_id(), *identifier);
1065                            let Ok(record) = get_external_record(&locator) else {
1066                                continue;
1067                            };
1068                            if record_uses_nonlocal_struct(&record) {
1069                                return true;
1070                            }
1071                        }
1072
1073                        ValueType::Future(loc) => {
1074                            let Ok(future) = get_external_future(loc) else {
1075                                continue;
1076                            };
1077                            for input in future.input_types() {
1078                                let FinalizeType::Plaintext(ty) = input else {
1079                                    continue;
1080                                };
1081
1082                                // We intentionally ignore `FinalizeType::Future(_)` here. Any such future
1083                                // originates from another program whose deployment would already have been
1084                                // validated under the same pre-V13 rules. At this point, only plaintext inputs
1085                                // can introduce new non-local struct violations.
1086
1087                                if plaintext_uses_nonlocal_struct(&ty, is_local_struct) {
1088                                    return true;
1089                                }
1090                            }
1091                        }
1092
1093                        _ => {}
1094                    }
1095                }
1096            }
1097        }
1098
1099        false
1100    }
1101
1102    /// Returns `true` if the program contains an array type with a size that exceeds the given maximum.
1103    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
1104        self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
1105            || self.structs.values().any(|struct_type| struct_type.exceeds_max_array_size(max_array_size))
1106            || self.records.values().any(|record_type| record_type.exceeds_max_array_size(max_array_size))
1107            || self.closures.values().any(|closure| closure.exceeds_max_array_size(max_array_size))
1108            || self.functions.values().any(|function| function.exceeds_max_array_size(max_array_size))
1109            || self.constructor.iter().any(|constructor| constructor.exceeds_max_array_size(max_array_size))
1110    }
1111
1112    /// Returns `true` if a program contains any V11 syntax.
1113    /// This includes:
1114    /// 1. `.raw` hash or signature verification variants
1115    /// 2. `ecdsa.verify.*` opcodes
1116    #[inline]
1117    pub fn contains_v11_syntax(&self) -> bool {
1118        // Helper to check if any of the opcodes:
1119        // - start with `ecdsa.verify`, `serialize`, or `deserialize`
1120        // - end with `.raw` or `.native`
1121        let has_op = |opcode: &str| {
1122            opcode.starts_with("ecdsa.verify")
1123                || opcode.starts_with("serialize")
1124                || opcode.starts_with("deserialize")
1125                || opcode.ends_with(".raw")
1126                || opcode.ends_with(".native")
1127        };
1128
1129        // Determine if any function instructions contain the new syntax.
1130        let function_contains = cfg_iter!(self.functions())
1131            .flat_map(|(_, function)| function.instructions())
1132            .any(|instruction| has_op(*instruction.opcode()));
1133
1134        // Determine if any closure instructions contain the new syntax.
1135        let closure_contains = cfg_iter!(self.closures())
1136            .flat_map(|(_, closure)| closure.instructions())
1137            .any(|instruction| has_op(*instruction.opcode()));
1138
1139        // Determine if any finalize commands or constructor commands contain the new syntax.
1140        let command_contains = cfg_iter!(self.functions())
1141            .flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
1142            .flatten()
1143            .chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
1144            .any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
1145
1146        function_contains || closure_contains || command_contains
1147    }
1148
1149    /// Returns `true` if a program contains any V12 syntax.
1150    /// This includes `Operand::BlockTimestamp`.
1151    /// This is enforced to be `false` for programs before `ConsensusVersion::V12`.
1152    #[inline]
1153    pub fn contains_v12_syntax(&self) -> bool {
1154        // Check each instruction and output in each function's finalize scope for the use of
1155        // `Operand::BlockTimestamp`.
1156        cfg_iter!(self.functions()).any(|(_, function)| {
1157            function.finalize_logic().is_some_and(|finalize_logic| {
1158                cfg_iter!(finalize_logic.commands()).any(|command| {
1159                    cfg_iter!(command.operands()).any(|operand| matches!(operand, Operand::BlockTimestamp))
1160                })
1161            })
1162        })
1163    }
1164
1165    /// Checks that the program size does not exceed the maximum allowed size for the given block height.
1166    pub fn check_program_size(&self, block_height: u32) -> Result<()> {
1167        // Calculate the program size.
1168        let program_size = self.to_string().len();
1169        // Determine the maximum allowed program size for the current consensus version.
1170        let maximum_allowed_program_size = consensus_config_value!(N, MAX_PROGRAM_SIZE, block_height)
1171            .ok_or(anyhow!("Failed to fetch maximum program size"))?;
1172
1173        ensure!(
1174            program_size <= maximum_allowed_program_size,
1175            "Program size of {program_size} bytes exceeds the maximum allowed size of {maximum_allowed_program_size} bytes for the current height {block_height} (consensus version {}).",
1176            N::CONSENSUS_VERSION(block_height)?
1177        );
1178
1179        Ok(())
1180    }
1181
1182    /// Checks that the program writes size does not exceed the maximum allowed size for the given block height.
1183    pub fn check_program_writes(&self, block_height: u32) -> Result<()> {
1184        // Determine the maximum allowed number of writes for the current consensus version.
1185        let max_num_writes = consensus_config_value!(N, MAX_WRITES, block_height)
1186            .ok_or(anyhow!("Failed to fetch maximum number of writes"))?;
1187
1188        // Check if the constructor exceeds the maximum number of writes.
1189        if self.constructor().is_some_and(|constructor| constructor.num_writes() > max_num_writes) {
1190            bail!(
1191                "Program constructor exceeds the maximum allowed writes ({max_num_writes}) for the current height {block_height} (consensus version {}).",
1192                N::CONSENSUS_VERSION(block_height)?
1193            );
1194        }
1195
1196        // Find the first function whose finalize logic exceeds the maximum writes.
1197        if let Some(name) = cfg_find_map!(self.functions(), |function| {
1198            function
1199                .finalize_logic()
1200                .is_some_and(|finalize| finalize.num_writes() > max_num_writes)
1201                .then(|| *function.name())
1202        }) {
1203            bail!(
1204                "Program function '{name}' exceeds the maximum allowed writes ({max_num_writes}) for the current height {block_height} (consensus version {}).",
1205                N::CONSENSUS_VERSION(block_height)?
1206            );
1207        }
1208
1209        Ok(())
1210    }
1211
1212    /// Returns `true` if a program contains any V14 syntax.
1213    /// This includes:
1214    /// 1. `snark.verify.*` opcodes.
1215    /// 2. `Operand::AleoGenerator` or `Operand::AleoGeneratorPowers` operands.
1216    /// 3. `Literal::Identifier` operands.
1217    /// 4. `call.dynamic` instructions.
1218    /// 5. `get.record.dynamic` instructions.
1219    /// 6. `cast` instructions targeting `dynamic.record`.
1220    /// 7. `dynamic.record` or `dynamic.future` in function input or output types.
1221    /// 8. `contains.dynamic`, `get.dynamic`, or `get.or_use.dynamic` commands in finalize blocks.
1222    /// 9. `dynamic.record` or `dynamic.future` in closure input or output types.
1223    /// 10. `dynamic.future` in finalize block input types.
1224    /// 11. Identifier types in any type declarations.
1225    ///
1226    /// This is enforced to be `false` for programs before `ConsensusVersion::V14`.
1227    #[inline]
1228    pub fn contains_v14_syntax(&self) -> Result<bool> {
1229        /// Returns `true` if the instruction uses V14-only opcodes or operands (infallible checks).
1230        fn is_v14_instruction<N: Network>(instr: &Instruction<N>) -> bool {
1231            matches!(instr, Instruction::CallDynamic(_) | Instruction::GetRecordDynamic(_))
1232                || matches!(instr, Instruction::Cast(cast) if *cast.cast_type() == CastType::DynamicRecord)
1233                || instr.opcode().starts_with("snark.verify")
1234                || cfg_iter!(instr.operands()).any(|operand| {
1235                    matches!(
1236                        operand,
1237                        Operand::AleoGenerator
1238                            | Operand::AleoGeneratorPowers(_)
1239                            | Operand::Literal(console::program::Literal::Identifier(..))
1240                    )
1241                })
1242        }
1243
1244        /// Returns `true` if the command uses V14-only syntax (infallible checks).
1245        fn is_v14_command<N: Network>(cmd: &Command<N>) -> bool {
1246            matches!(cmd, Command::ContainsDynamic(_) | Command::GetDynamic(_) | Command::GetOrUseDynamic(_))
1247                || matches!(cmd, Command::Instruction(instr) if is_v14_instruction(instr))
1248        }
1249
1250        // Helper to check if a value type is a V14-only dynamic type.
1251        let is_dynamic_value_type =
1252            |vt: &ValueType<N>| matches!(vt, ValueType::DynamicRecord | ValueType::DynamicFuture);
1253
1254        // Helper to check if a register type is a V14-only dynamic type.
1255        let is_dynamic_register_type =
1256            |rt: &RegisterType<N>| matches!(rt, RegisterType::DynamicRecord | RegisterType::DynamicFuture);
1257
1258        // Check functions: instructions, finalize commands, and type declarations.
1259        for (_, function) in self.functions() {
1260            if function.instructions().iter().any(is_v14_instruction)
1261                || function.inputs().iter().any(|input| is_dynamic_value_type(input.value_type()))
1262                || function.outputs().iter().any(|output| is_dynamic_value_type(output.value_type()))
1263            {
1264                return Ok(true);
1265            }
1266            if let Some(finalize) = function.finalize_logic() {
1267                if finalize.inputs().iter().any(|input| matches!(input.finalize_type(), FinalizeType::DynamicFuture))
1268                    || finalize.commands().iter().any(is_v14_command)
1269                {
1270                    return Ok(true);
1271                }
1272            }
1273            if function.contains_identifier_type()? {
1274                return Ok(true);
1275            }
1276        }
1277
1278        // Check closures: instructions and type declarations.
1279        for (_, closure) in self.closures() {
1280            if closure.instructions().iter().any(is_v14_instruction)
1281                || closure.inputs().iter().any(|input| is_dynamic_register_type(input.register_type()))
1282                || closure.outputs().iter().any(|output| is_dynamic_register_type(output.register_type()))
1283            {
1284                return Ok(true);
1285            }
1286            if closure.contains_identifier_type()? {
1287                return Ok(true);
1288            }
1289        }
1290
1291        // Check constructor commands and identifier types.
1292        if let Some(constructor) = &self.constructor {
1293            if constructor.commands().iter().any(is_v14_command) {
1294                return Ok(true);
1295            }
1296            if constructor.contains_identifier_type()? {
1297                return Ok(true);
1298            }
1299        }
1300
1301        // Check remaining type definitions: mappings, structs, records.
1302        for mapping in self.mappings.values() {
1303            if mapping.contains_identifier_type()? {
1304                return Ok(true);
1305            }
1306        }
1307        for struct_type in self.structs.values() {
1308            if struct_type.contains_identifier_type()? {
1309                return Ok(true);
1310            }
1311        }
1312        for record_type in self.records.values() {
1313            if record_type.contains_identifier_type()? {
1314                return Ok(true);
1315            }
1316        }
1317
1318        Ok(false)
1319    }
1320
1321    /// Returns `true` if a program contains any string type.
1322    /// Before ConsensusVersion::V12, variable-length string sampling when using them as inputs caused deployment synthesis to be inconsistent and abort with probability 63/64.
1323    /// After ConsensusVersion::V12, string types are disallowed.
1324    #[inline]
1325    pub fn contains_string_type(&self) -> bool {
1326        self.mappings.values().any(|mapping| mapping.contains_string_type())
1327            || self.structs.values().any(|struct_type| struct_type.contains_string_type())
1328            || self.records.values().any(|record_type| record_type.contains_string_type())
1329            || self.closures.values().any(|closure| closure.contains_string_type())
1330            || self.functions.values().any(|function| function.contains_string_type())
1331            || self.constructor.iter().any(|constructor| constructor.contains_string_type())
1332    }
1333}
1334
1335impl<N: Network> TypeName for ProgramCore<N> {
1336    /// Returns the type name as a string.
1337    #[inline]
1338    fn type_name() -> &'static str {
1339        "program"
1340    }
1341}
1342
1343#[cfg(test)]
1344mod tests {
1345    use super::*;
1346    use console::{
1347        network::MainnetV0,
1348        program::{Locator, ValueType},
1349    };
1350
1351    type CurrentNetwork = MainnetV0;
1352
1353    #[test]
1354    fn test_program_mapping() -> Result<()> {
1355        // Create a new mapping.
1356        let mapping = Mapping::<CurrentNetwork>::from_str(
1357            r"
1358mapping message:
1359    key as field.public;
1360    value as field.public;",
1361        )?;
1362
1363        // Initialize a new program.
1364        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1365        // Ensure the mapping was added.
1366        assert!(program.contains_mapping(&Identifier::from_str("message")?));
1367        // Ensure the retrieved mapping matches.
1368        assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1369
1370        Ok(())
1371    }
1372
1373    #[test]
1374    fn test_program_struct() -> Result<()> {
1375        // Create a new struct.
1376        let struct_ = StructType::<CurrentNetwork>::from_str(
1377            r"
1378struct message:
1379    first as field;
1380    second as field;",
1381        )?;
1382
1383        // Initialize a new program.
1384        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1385        // Ensure the struct was added.
1386        assert!(program.contains_struct(&Identifier::from_str("message")?));
1387        // Ensure the retrieved struct matches.
1388        assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1389
1390        Ok(())
1391    }
1392
1393    #[test]
1394    fn test_program_record() -> Result<()> {
1395        // Create a new record.
1396        let record = RecordType::<CurrentNetwork>::from_str(
1397            r"
1398record foo:
1399    owner as address.private;
1400    first as field.private;
1401    second as field.public;",
1402        )?;
1403
1404        // Initialize a new program.
1405        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1406        // Ensure the record was added.
1407        assert!(program.contains_record(&Identifier::from_str("foo")?));
1408        // Ensure the retrieved record matches.
1409        assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1410
1411        Ok(())
1412    }
1413
1414    #[test]
1415    fn test_program_function() -> Result<()> {
1416        // Create a new function.
1417        let function = Function::<CurrentNetwork>::from_str(
1418            r"
1419function compute:
1420    input r0 as field.public;
1421    input r1 as field.private;
1422    add r0 r1 into r2;
1423    output r2 as field.private;",
1424        )?;
1425
1426        // Initialize a new program.
1427        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1428        // Ensure the function was added.
1429        assert!(program.contains_function(&Identifier::from_str("compute")?));
1430        // Ensure the retrieved function matches.
1431        assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1432
1433        Ok(())
1434    }
1435
1436    #[test]
1437    fn test_program_import() -> Result<()> {
1438        // Initialize a new program.
1439        let program = Program::<CurrentNetwork>::from_str(
1440            r"
1441import eth.aleo;
1442import usdc.aleo;
1443
1444program swap.aleo;
1445
1446// The `swap` function transfers ownership of the record
1447// for token A to the record owner of token B, and vice-versa.
1448function swap:
1449    // Input the record for token A.
1450    input r0 as eth.aleo/eth.record;
1451    // Input the record for token B.
1452    input r1 as usdc.aleo/usdc.record;
1453
1454    // Send the record for token A to the owner of token B.
1455    call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1456
1457    // Send the record for token B to the owner of token A.
1458    call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1459
1460    // Output the new record for token A.
1461    output r2 as eth.aleo/eth.record;
1462    // Output the new record for token B.
1463    output r4 as usdc.aleo/usdc.record;
1464    ",
1465        )
1466        .unwrap();
1467
1468        // Ensure the program imports exist.
1469        assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1470        assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1471
1472        // Retrieve the 'swap' function.
1473        let function = program.get_function(&Identifier::from_str("swap")?)?;
1474
1475        // Ensure there are two inputs.
1476        assert_eq!(function.inputs().len(), 2);
1477        assert_eq!(function.input_types().len(), 2);
1478
1479        // Declare the expected input types.
1480        let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1481        let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1482
1483        // Ensure the inputs are external records.
1484        assert_eq!(function.input_types()[0], expected_input_type_1);
1485        assert_eq!(function.input_types()[1], expected_input_type_2);
1486
1487        // Ensure the input variants are correct.
1488        assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1489        assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1490
1491        // Ensure there are two instructions.
1492        assert_eq!(function.instructions().len(), 2);
1493
1494        // Ensure the instructions are calls.
1495        assert_eq!(function.instructions()[0].opcode(), Opcode::Call("call"));
1496        assert_eq!(function.instructions()[1].opcode(), Opcode::Call("call"));
1497
1498        // Ensure there are two outputs.
1499        assert_eq!(function.outputs().len(), 2);
1500        assert_eq!(function.output_types().len(), 2);
1501
1502        // Declare the expected output types.
1503        let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1504        let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1505
1506        // Ensure the outputs are external records.
1507        assert_eq!(function.output_types()[0], expected_output_type_1);
1508        assert_eq!(function.output_types()[1], expected_output_type_2);
1509
1510        // Ensure the output variants are correct.
1511        assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1512        assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1513
1514        Ok(())
1515    }
1516
1517    #[test]
1518    fn test_program_with_constructor() {
1519        // Initialize a new program.
1520        let program_string = r"import credits.aleo;
1521
1522program good_constructor.aleo;
1523
1524constructor:
1525    assert.eq edition 0u16;
1526    assert.eq credits.aleo/edition 0u16;
1527    assert.neq checksum 0field;
1528    assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1529    set 1u8 into data[0u8];
1530
1531mapping data:
1532    key as u8.public;
1533    value as u8.public;
1534
1535function dummy:
1536
1537function check:
1538    async check into r0;
1539    output r0 as good_constructor.aleo/check.future;
1540
1541finalize check:
1542    get data[0u8] into r0;
1543    assert.eq r0 1u8;
1544";
1545        let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1546
1547        // Check that the string and bytes (de)serialization works.
1548        let serialized = program.to_string();
1549        let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1550        assert_eq!(program, deserialized);
1551
1552        let serialized = program.to_bytes_le().unwrap();
1553        let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1554        assert_eq!(program, deserialized);
1555
1556        // Check that the display works.
1557        let display = format!("{program}");
1558        assert_eq!(display, program_string);
1559
1560        // Ensure the program contains a constructor.
1561        assert!(program.contains_constructor());
1562        assert_eq!(program.constructor().unwrap().commands().len(), 5);
1563    }
1564
1565    #[test]
1566    fn test_program_equality_and_checksum() {
1567        fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1568            println!("Comparing programs:\n{program1}\n{program2}");
1569            let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1570            let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1571            assert_eq!(program1 == program2, expected_equal);
1572            assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1573        }
1574
1575        // Test two identical programs, with different whitespace.
1576        run_test(r"program test.aleo; function dummy:    ", r"program  test.aleo;     function dummy:   ", true);
1577
1578        // Test two programs, one with a different function name.
1579        run_test(r"program test.aleo; function dummy:    ", r"program test.aleo; function bummy:   ", false);
1580
1581        // Test two programs, one with a constructor and one without.
1582        run_test(
1583            r"program test.aleo; function dummy:    ",
1584            r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1585            false,
1586        );
1587
1588        // Test two programs, both with a struct and function, but in different order.
1589        run_test(
1590            r"program test.aleo; struct foo: data as u8; function dummy:",
1591            r"program test.aleo; function dummy: struct foo: data as u8;",
1592            false,
1593        );
1594    }
1595
1596    #[test]
1597    fn test_contains_v14_syntax() -> Result<()> {
1598        // A baseline program with no V14 syntax.
1599        let no_v14 = Program::<CurrentNetwork>::from_str(
1600            r"program test.aleo;
1601function foo:
1602    input r0 as u64.public;
1603    output r0 as u64.public;",
1604        )?;
1605        assert!(!no_v14.contains_v14_syntax()?);
1606
1607        // A program with `dynamic.record` as a function input type.
1608        let dynamic_record_input = Program::<CurrentNetwork>::from_str(
1609            r"program test.aleo;
1610function foo:
1611    input r0 as dynamic.record;",
1612        )?;
1613        assert!(dynamic_record_input.contains_v14_syntax()?);
1614
1615        // A program with `dynamic.future` as a function output type.
1616        let dynamic_future_output = Program::<CurrentNetwork>::from_str(
1617            r"program test.aleo;
1618function foo:
1619    output r0 as dynamic.future;",
1620        )?;
1621        assert!(dynamic_future_output.contains_v14_syntax()?);
1622
1623        // A program with a `call.dynamic` instruction.
1624        let call_dynamic = Program::<CurrentNetwork>::from_str(
1625            r"program test.aleo;
1626function foo:
1627    input r0 as field.public;
1628    input r1 as field.public;
1629    input r2 as field.public;
1630    call.dynamic r0 r1 r2 into r3 (as u64.public);
1631    output r3 as u64.public;",
1632        )?;
1633        assert!(call_dynamic.contains_v14_syntax()?);
1634
1635        // A program with a `get.record.dynamic` instruction.
1636        let get_record_dynamic = Program::<CurrentNetwork>::from_str(
1637            r"program test.aleo;
1638function foo:
1639    input r0 as dynamic.record;
1640    get.record.dynamic r0.amount into r1 as field;
1641    output r1 as field.public;",
1642        )?;
1643        assert!(get_record_dynamic.contains_v14_syntax()?);
1644
1645        // A program with a `cast ... as dynamic.record` instruction.
1646        let cast_dynamic_record = Program::<CurrentNetwork>::from_str(
1647            r"program test.aleo;
1648record token:
1649    owner as address.private;
1650    amount as u64.private;
1651function foo:
1652    input r0 as token.record;
1653    cast r0 into r1 as dynamic.record;
1654    output r0.owner as address.private;",
1655        )?;
1656        assert!(cast_dynamic_record.contains_v14_syntax()?);
1657
1658        // A program with `contains.dynamic` in a finalize block.
1659        let contains_dynamic = Program::<CurrentNetwork>::from_str(
1660            r"program test.aleo;
1661function bar:
1662    input r0 as field.public;
1663    input r1 as field.public;
1664    input r2 as field.public;
1665    input r3 as field.public;
1666    async bar r0 r1 r2 r3 into r4;
1667    output r4 as test.aleo/bar.future;
1668finalize bar:
1669    input r0 as field.public;
1670    input r1 as field.public;
1671    input r2 as field.public;
1672    input r3 as field.public;
1673    contains.dynamic r0 r1 r2[r3] into r4;",
1674        )?;
1675        assert!(contains_dynamic.contains_v14_syntax()?);
1676
1677        // A program with `get.dynamic` in a finalize block.
1678        let get_dynamic = Program::<CurrentNetwork>::from_str(
1679            r"program test.aleo;
1680function bar:
1681    input r0 as field.public;
1682    input r1 as field.public;
1683    input r2 as field.public;
1684    input r3 as field.public;
1685    async bar r0 r1 r2 r3 into r4;
1686    output r4 as test.aleo/bar.future;
1687finalize bar:
1688    input r0 as field.public;
1689    input r1 as field.public;
1690    input r2 as field.public;
1691    input r3 as field.public;
1692    get.dynamic r0 r1 r2[r3] into r4 as field;",
1693        )?;
1694        assert!(get_dynamic.contains_v14_syntax()?);
1695
1696        // A program with `dynamic.future` as a finalize input type.
1697        let dynamic_future_finalize_input = Program::<CurrentNetwork>::from_str(
1698            r"program test.aleo;
1699function foo:
1700    input r0 as field.public;
1701    async foo r0 into r1;
1702    output r1 as test.aleo/foo.future;
1703finalize foo:
1704    input r0 as dynamic.future;
1705    await r0;",
1706        )?;
1707        assert!(dynamic_future_finalize_input.contains_v14_syntax()?);
1708
1709        // A program with `dynamic.record` as a closure input type.
1710        let closure_dynamic_input = Program::<CurrentNetwork>::from_str(
1711            r"program test.aleo;
1712closure bar:
1713    input r0 as dynamic.record;
1714    input r1 as field;
1715    add r1 r1 into r2;",
1716        )?;
1717        assert!(closure_dynamic_input.contains_v14_syntax()?);
1718
1719        // A program with `dynamic.record` as a closure output type.
1720        let closure_dynamic_output = Program::<CurrentNetwork>::from_str(
1721            r"program test.aleo;
1722closure bar:
1723    input r0 as field;
1724    add r0 r0 into r1;
1725    output r1 as dynamic.record;",
1726        )?;
1727        assert!(closure_dynamic_output.contains_v14_syntax()?);
1728
1729        // A program with `get.or_use.dynamic` in a finalize block.
1730        let get_or_use_dynamic = Program::<CurrentNetwork>::from_str(
1731            r"program test.aleo;
1732function bar:
1733    input r0 as field.public;
1734    input r1 as field.public;
1735    input r2 as field.public;
1736    input r3 as field.public;
1737    input r4 as u64.public;
1738    async bar r0 r1 r2 r3 r4 into r5;
1739    output r5 as test.aleo/bar.future;
1740finalize bar:
1741    input r0 as field.public;
1742    input r1 as field.public;
1743    input r2 as field.public;
1744    input r3 as field.public;
1745    input r4 as u64.public;
1746    get.or_use.dynamic r0 r1 r2[r3] r4 into r5 as u64;",
1747        )?;
1748        assert!(get_or_use_dynamic.contains_v14_syntax()?);
1749
1750        // A program with `snark.verify` in a finalize block.
1751        let snark_verify = Program::<CurrentNetwork>::from_str(
1752            r"program test.aleo;
1753function foo:
1754    input r0 as [u8; 8u32].public;
1755    input r1 as [field; 1u32].public;
1756    input r2 as [u8; 8u32].public;
1757    async foo r0 r1 r2 into r3;
1758    output r3 as test.aleo/foo.future;
1759finalize foo:
1760    input r0 as [u8; 8u32].public;
1761    input r1 as [field; 1u32].public;
1762    input r2 as [u8; 8u32].public;
1763    snark.verify r0 1u8 r1 r2 into r3;",
1764        )?;
1765        assert!(snark_verify.contains_v14_syntax()?);
1766
1767        // A program with `snark.verify.batch` in a finalize block.
1768        let snark_verify_batch = Program::<CurrentNetwork>::from_str(
1769            r"program test.aleo;
1770function foo:
1771    input r0 as [[u8; 8u32]; 1u32].public;
1772    input r1 as [[[field; 1u32]; 1u32]; 1u32].public;
1773    input r2 as [u8; 8u32].public;
1774    async foo r0 r1 r2 into r3;
1775    output r3 as test.aleo/foo.future;
1776finalize foo:
1777    input r0 as [[u8; 8u32]; 1u32].public;
1778    input r1 as [[[field; 1u32]; 1u32]; 1u32].public;
1779    input r2 as [u8; 8u32].public;
1780    snark.verify.batch r0 1u8 r1 r2 into r3;",
1781        )?;
1782        assert!(snark_verify_batch.contains_v14_syntax()?);
1783
1784        // A program with `aleo::GENERATOR` as an operand.
1785        let aleo_generator = Program::<CurrentNetwork>::from_str(
1786            r"program test.aleo;
1787function foo:
1788    input r0 as scalar.public;
1789    mul aleo::GENERATOR r0 into r1;
1790    output r1 as group.public;",
1791        )?;
1792        assert!(aleo_generator.contains_v14_syntax()?);
1793
1794        // A program with `aleo::GENERATOR_POWERS` as an operand.
1795        let aleo_generator_powers = Program::<CurrentNetwork>::from_str(
1796            r"program test.aleo;
1797function foo:
1798    input r0 as scalar.public;
1799    mul aleo::GENERATOR_POWERS[0u32] r0 into r1;
1800    output r1 as group.public;",
1801        )?;
1802        assert!(aleo_generator_powers.contains_v14_syntax()?);
1803
1804        // A program with `dynamic.future` as a closure output type.
1805        let closure_dynamic_future_output = Program::<CurrentNetwork>::from_str(
1806            r"program test.aleo;
1807closure bar:
1808    input r0 as field;
1809    add r0 r0 into r1;
1810    output r1 as dynamic.future;",
1811        )?;
1812        assert!(closure_dynamic_future_output.contains_v14_syntax()?);
1813
1814        // A program with a V14 opcode (`aleo::GENERATOR`) in a constructor block.
1815        let constructor_v14 = Program::<CurrentNetwork>::from_str(
1816            r"program test.aleo;
1817function dummy:
1818    input r0 as field.public;
1819    output r0 as field.public;
1820constructor:
1821    assert.eq aleo::GENERATOR aleo::GENERATOR;",
1822        )?;
1823        assert!(constructor_v14.contains_v14_syntax()?);
1824
1825        Ok(())
1826    }
1827}