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::{Identifier, PlaintextType, ProgramID, RecordType, StructType},
103    types::U8,
104};
105use snarkvm_utilities::cfg_iter;
106
107use indexmap::{IndexMap, IndexSet};
108use std::collections::BTreeSet;
109use tiny_keccak::{Hasher, Sha3 as TinySha3};
110
111#[derive(Copy, Clone, PartialEq, Eq, Hash)]
112enum ProgramLabel<N: Network> {
113    /// A program constructor.
114    Constructor,
115    /// A named component.
116    Identifier(Identifier<N>),
117}
118
119#[cfg(not(feature = "serial"))]
120use rayon::prelude::*;
121
122#[derive(Copy, Clone, PartialEq, Eq, Hash)]
123enum ProgramDefinition {
124    /// A program constructor.
125    Constructor,
126    /// A program mapping.
127    Mapping,
128    /// A program struct.
129    Struct,
130    /// A program record.
131    Record,
132    /// A program closure.
133    Closure,
134    /// A program function.
135    Function,
136}
137
138#[derive(Clone)]
139pub struct ProgramCore<N: Network> {
140    /// The ID of the program.
141    id: ProgramID<N>,
142    /// A map of the declared imports for the program.
143    imports: IndexMap<ProgramID<N>, Import<N>>,
144    /// A map of program labels to their program definitions.
145    components: IndexMap<ProgramLabel<N>, ProgramDefinition>,
146    /// An optional constructor for the program.
147    constructor: Option<ConstructorCore<N>>,
148    /// A map of the declared mappings for the program.
149    mappings: IndexMap<Identifier<N>, Mapping<N>>,
150    /// A map of the declared structs for the program.
151    structs: IndexMap<Identifier<N>, StructType<N>>,
152    /// A map of the declared record types for the program.
153    records: IndexMap<Identifier<N>, RecordType<N>>,
154    /// A map of the declared closures for the program.
155    closures: IndexMap<Identifier<N>, ClosureCore<N>>,
156    /// A map of the declared functions for the program.
157    functions: IndexMap<Identifier<N>, FunctionCore<N>>,
158}
159
160impl<N: Network> PartialEq for ProgramCore<N> {
161    /// Compares two programs for equality, verifying that the components are in the same order.
162    /// The order of the components must match to ensure that deployment tree is well-formed.
163    fn eq(&self, other: &Self) -> bool {
164        // Check that the number of components is the same.
165        if self.components.len() != other.components.len() {
166            return false;
167        }
168        // Check that the components match in order.
169        for (left, right) in self.components.iter().zip_eq(other.components.iter()) {
170            if left != right {
171                return false;
172            }
173        }
174        // Check that the remaining fields match.
175        self.id == other.id
176            && self.imports == other.imports
177            && self.mappings == other.mappings
178            && self.structs == other.structs
179            && self.records == other.records
180            && self.closures == other.closures
181            && self.functions == other.functions
182    }
183}
184
185impl<N: Network> Eq for ProgramCore<N> {}
186
187impl<N: Network> ProgramCore<N> {
188    /// A list of reserved keywords for Aleo programs, enforced at the parser level.
189    // New keywords should be enforced through `RESTRICTED_KEYWORDS` instead, if possible.
190    // Adding keywords to this list will require a backwards-compatible versioning for programs.
191    #[rustfmt::skip]
192    pub const KEYWORDS: &'static [&'static str] = &[
193        // Mode
194        "const",
195        "constant",
196        "public",
197        "private",
198        // Literals
199        "address",
200        "boolean",
201        "field",
202        "group",
203        "i8",
204        "i16",
205        "i32",
206        "i64",
207        "i128",
208        "u8",
209        "u16",
210        "u32",
211        "u64",
212        "u128",
213        "scalar",
214        "signature",
215        "string",
216        // Boolean
217        "true",
218        "false",
219        // Statements
220        "input",
221        "output",
222        "as",
223        "into",
224        // Record
225        "record",
226        "owner",
227        // Program
228        "transition",
229        "import",
230        "function",
231        "struct",
232        "closure",
233        "program",
234        "aleo",
235        "self",
236        "storage",
237        "mapping",
238        "key",
239        "value",
240        "async",
241        "finalize",
242        // Reserved (catch all)
243        "global",
244        "block",
245        "return",
246        "break",
247        "assert",
248        "continue",
249        "let",
250        "if",
251        "else",
252        "while",
253        "for",
254        "switch",
255        "case",
256        "default",
257        "match",
258        "enum",
259        "struct",
260        "union",
261        "trait",
262        "impl",
263        "type",
264        "future",
265    ];
266    /// A list of restricted keywords for Aleo programs, enforced at the VM-level for program hygiene.
267    /// Each entry is a tuple of the consensus version and a list of keywords.
268    /// If the current consensus version is greater than or equal to the specified version,
269    /// the keywords in the list should be restricted.
270    #[rustfmt::skip]
271    pub const RESTRICTED_KEYWORDS: &'static [(ConsensusVersion, &'static [&'static str])] = &[
272        (ConsensusVersion::V6, &["constructor"])
273    ];
274
275    /// Initializes an empty program.
276    #[inline]
277    pub fn new(id: ProgramID<N>) -> Result<Self> {
278        // Ensure the program name is valid.
279        ensure!(!Self::is_reserved_keyword(id.name()), "Program name is invalid: {}", id.name());
280
281        Ok(Self {
282            id,
283            imports: IndexMap::new(),
284            constructor: None,
285            components: IndexMap::new(),
286            mappings: IndexMap::new(),
287            structs: IndexMap::new(),
288            records: IndexMap::new(),
289            closures: IndexMap::new(),
290            functions: IndexMap::new(),
291        })
292    }
293
294    /// Initializes the credits program.
295    #[inline]
296    pub fn credits() -> Result<Self> {
297        Self::from_str(include_str!("./resources/credits.aleo"))
298    }
299
300    /// Returns the ID of the program.
301    pub const fn id(&self) -> &ProgramID<N> {
302        &self.id
303    }
304
305    /// Returns the imports in the program.
306    pub const fn imports(&self) -> &IndexMap<ProgramID<N>, Import<N>> {
307        &self.imports
308    }
309
310    /// Returns the constructor for the program.
311    pub const fn constructor(&self) -> Option<&ConstructorCore<N>> {
312        self.constructor.as_ref()
313    }
314
315    /// Returns the mappings in the program.
316    pub const fn mappings(&self) -> &IndexMap<Identifier<N>, Mapping<N>> {
317        &self.mappings
318    }
319
320    /// Returns the structs in the program.
321    pub const fn structs(&self) -> &IndexMap<Identifier<N>, StructType<N>> {
322        &self.structs
323    }
324
325    /// Returns the records in the program.
326    pub const fn records(&self) -> &IndexMap<Identifier<N>, RecordType<N>> {
327        &self.records
328    }
329
330    /// Returns the closures in the program.
331    pub const fn closures(&self) -> &IndexMap<Identifier<N>, ClosureCore<N>> {
332        &self.closures
333    }
334
335    /// Returns the functions in the program.
336    pub const fn functions(&self) -> &IndexMap<Identifier<N>, FunctionCore<N>> {
337        &self.functions
338    }
339
340    /// Returns `true` if the program contains an import with the given program ID.
341    pub fn contains_import(&self, id: &ProgramID<N>) -> bool {
342        self.imports.contains_key(id)
343    }
344
345    /// Returns `true` if the program contains a constructor.
346    pub const fn contains_constructor(&self) -> bool {
347        self.constructor.is_some()
348    }
349
350    /// Returns `true` if the program contains a mapping with the given name.
351    pub fn contains_mapping(&self, name: &Identifier<N>) -> bool {
352        self.mappings.contains_key(name)
353    }
354
355    /// Returns `true` if the program contains a struct with the given name.
356    pub fn contains_struct(&self, name: &Identifier<N>) -> bool {
357        self.structs.contains_key(name)
358    }
359
360    /// Returns `true` if the program contains a record with the given name.
361    pub fn contains_record(&self, name: &Identifier<N>) -> bool {
362        self.records.contains_key(name)
363    }
364
365    /// Returns `true` if the program contains a closure with the given name.
366    pub fn contains_closure(&self, name: &Identifier<N>) -> bool {
367        self.closures.contains_key(name)
368    }
369
370    /// Returns `true` if the program contains a function with the given name.
371    pub fn contains_function(&self, name: &Identifier<N>) -> bool {
372        self.functions.contains_key(name)
373    }
374
375    /// Returns the mapping with the given name.
376    pub fn get_mapping(&self, name: &Identifier<N>) -> Result<Mapping<N>> {
377        // Attempt to retrieve the mapping.
378        let mapping = self.mappings.get(name).cloned().ok_or_else(|| anyhow!("Mapping '{name}' is not defined."))?;
379        // Ensure the mapping name matches.
380        ensure!(mapping.name() == name, "Expected mapping '{name}', but found mapping '{}'", mapping.name());
381        // Return the mapping.
382        Ok(mapping)
383    }
384
385    /// Returns the struct with the given name.
386    pub fn get_struct(&self, name: &Identifier<N>) -> Result<&StructType<N>> {
387        // Attempt to retrieve the struct.
388        let struct_ = self.structs.get(name).ok_or_else(|| anyhow!("Struct '{name}' is not defined."))?;
389        // Ensure the struct name matches.
390        ensure!(struct_.name() == name, "Expected struct '{name}', but found struct '{}'", struct_.name());
391        // Ensure the struct contains members.
392        ensure!(!struct_.members().is_empty(), "Struct '{name}' is missing members.");
393        // Return the struct.
394        Ok(struct_)
395    }
396
397    /// Returns the record with the given name.
398    pub fn get_record(&self, name: &Identifier<N>) -> Result<&RecordType<N>> {
399        // Attempt to retrieve the record.
400        let record = self.records.get(name).ok_or_else(|| anyhow!("Record '{name}' is not defined."))?;
401        // Ensure the record name matches.
402        ensure!(record.name() == name, "Expected record '{name}', but found record '{}'", record.name());
403        // Return the record.
404        Ok(record)
405    }
406
407    /// Returns the closure with the given name.
408    pub fn get_closure(&self, name: &Identifier<N>) -> Result<ClosureCore<N>> {
409        // Attempt to retrieve the closure.
410        let closure = self.closures.get(name).cloned().ok_or_else(|| anyhow!("Closure '{name}' is not defined."))?;
411        // Ensure the closure name matches.
412        ensure!(closure.name() == name, "Expected closure '{name}', but found closure '{}'", closure.name());
413        // Ensure there are input statements in the closure.
414        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
415        // Ensure the number of inputs is within the allowed range.
416        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
417        // Ensure there are instructions in the closure.
418        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
419        // Ensure the number of outputs is within the allowed range.
420        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
421        // Return the closure.
422        Ok(closure)
423    }
424
425    /// Returns the function with the given name.
426    pub fn get_function(&self, name: &Identifier<N>) -> Result<FunctionCore<N>> {
427        self.get_function_ref(name).cloned()
428    }
429
430    /// Returns a reference to the function with the given name.
431    pub fn get_function_ref(&self, name: &Identifier<N>) -> Result<&FunctionCore<N>> {
432        // Attempt to retrieve the function.
433        let function = self.functions.get(name).ok_or(anyhow!("Function '{}/{name}' is not defined.", self.id))?;
434        // Ensure the function name matches.
435        ensure!(function.name() == name, "Expected function '{name}', but found function '{}'", function.name());
436        // Ensure the number of inputs is within the allowed range.
437        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
438        // Ensure the number of instructions is within the allowed range.
439        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
440        // Ensure the number of outputs is within the allowed range.
441        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
442        // Return the function.
443        Ok(function)
444    }
445
446    /// Adds a new import statement to the program.
447    ///
448    /// # Errors
449    /// This method will halt if the imported program was previously added.
450    #[inline]
451    fn add_import(&mut self, import: Import<N>) -> Result<()> {
452        // Retrieve the imported program name.
453        let import_name = *import.name();
454
455        // Ensure that the number of imports is within the allowed range.
456        ensure!(self.imports.len() < N::MAX_IMPORTS, "Program exceeds the maximum number of imports");
457
458        // Ensure the import name is new.
459        ensure!(self.is_unique_name(&import_name), "'{import_name}' is already in use.");
460        // Ensure the import name is not a reserved opcode.
461        ensure!(!Self::is_reserved_opcode(&import_name.to_string()), "'{import_name}' is a reserved opcode.");
462        // Ensure the import name is not a reserved keyword.
463        ensure!(!Self::is_reserved_keyword(&import_name), "'{import_name}' is a reserved keyword.");
464
465        // Ensure the import is new.
466        ensure!(
467            !self.imports.contains_key(import.program_id()),
468            "Import '{}' is already defined.",
469            import.program_id()
470        );
471
472        // Add the import statement to the program.
473        if self.imports.insert(*import.program_id(), import.clone()).is_some() {
474            bail!("'{}' already exists in the program.", import.program_id())
475        }
476        Ok(())
477    }
478
479    /// Adds a constructor to the program.
480    ///
481    /// # Errors
482    /// This method will halt if a constructor was previously added.
483    /// This method will halt if a constructor exceeds the maximum number of commands.
484    fn add_constructor(&mut self, constructor: ConstructorCore<N>) -> Result<()> {
485        // Ensure the program does not already have a constructor.
486        ensure!(self.constructor.is_none(), "Program already has a constructor.");
487        // Ensure the number of commands is within the allowed range.
488        ensure!(!constructor.commands().is_empty(), "Constructor must have at least one command");
489        ensure!(constructor.commands().len() <= N::MAX_COMMANDS, "Constructor exceeds maximum number of commands");
490        // Add the constructor to the components.
491        if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() {
492            bail!("Constructor already exists in the program.")
493        }
494        // Add the constructor to the program.
495        self.constructor = Some(constructor);
496        Ok(())
497    }
498
499    /// Adds a new mapping to the program.
500    ///
501    /// # Errors
502    /// This method will halt if the mapping name is already in use.
503    /// This method will halt if the mapping name is a reserved opcode or keyword.
504    #[inline]
505    fn add_mapping(&mut self, mapping: Mapping<N>) -> Result<()> {
506        // Retrieve the mapping name.
507        let mapping_name = *mapping.name();
508
509        // Ensure the program has not exceeded the maximum number of mappings.
510        ensure!(self.mappings.len() < N::MAX_MAPPINGS, "Program exceeds the maximum number of mappings");
511
512        // Ensure the mapping name is new.
513        ensure!(self.is_unique_name(&mapping_name), "'{mapping_name}' is already in use.");
514        // Ensure the mapping name is not a reserved keyword.
515        ensure!(!Self::is_reserved_keyword(&mapping_name), "'{mapping_name}' is a reserved keyword.");
516        // Ensure the mapping name is not a reserved opcode.
517        ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode.");
518
519        // Add the mapping name to the identifiers.
520        if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() {
521            bail!("'{mapping_name}' already exists in the program.")
522        }
523        // Add the mapping to the program.
524        if self.mappings.insert(mapping_name, mapping).is_some() {
525            bail!("'{mapping_name}' already exists in the program.")
526        }
527        Ok(())
528    }
529
530    /// Adds a new struct to the program.
531    ///
532    /// # Errors
533    /// This method will halt if the struct was previously added.
534    /// This method will halt if the struct name is already in use in the program.
535    /// This method will halt if the struct name is a reserved opcode or keyword.
536    /// This method will halt if any structs in the struct's members are not already defined.
537    #[inline]
538    fn add_struct(&mut self, struct_: StructType<N>) -> Result<()> {
539        // Retrieve the struct name.
540        let struct_name = *struct_.name();
541
542        // Ensure the program has not exceeded the maximum number of structs.
543        ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");
544
545        // Ensure the struct name is new.
546        ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
547        // Ensure the struct name is not a reserved opcode.
548        ensure!(!Self::is_reserved_opcode(&struct_name.to_string()), "'{struct_name}' is a reserved opcode.");
549        // Ensure the struct name is not a reserved keyword.
550        ensure!(!Self::is_reserved_keyword(&struct_name), "'{struct_name}' is a reserved keyword.");
551
552        // Ensure the struct contains members.
553        ensure!(!struct_.members().is_empty(), "Struct '{struct_name}' is missing members.");
554
555        // Ensure all struct members are well-formed.
556        // Note: This design ensures cyclic references are not possible.
557        for (identifier, plaintext_type) in struct_.members() {
558            // Ensure the member name is not a reserved keyword.
559            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
560            // Ensure the member type is already defined in the program.
561            match plaintext_type {
562                PlaintextType::Literal(_) => continue,
563                PlaintextType::Struct(member_identifier) => {
564                    // Ensure the member struct name exists in the program.
565                    if !self.structs.contains_key(member_identifier) {
566                        bail!("'{member_identifier}' in struct '{struct_name}' is not defined.")
567                    }
568                }
569                PlaintextType::Array(array_type) => {
570                    if let PlaintextType::Struct(struct_name) = array_type.base_element_type() {
571                        // Ensure the member struct name exists in the program.
572                        if !self.structs.contains_key(struct_name) {
573                            bail!("'{struct_name}' in array '{array_type}' is not defined.")
574                        }
575                    }
576                }
577            }
578        }
579
580        // Add the struct name to the identifiers.
581        if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() {
582            bail!("'{struct_name}' already exists in the program.")
583        }
584        // Add the struct to the program.
585        if self.structs.insert(struct_name, struct_).is_some() {
586            bail!("'{struct_name}' already exists in the program.")
587        }
588        Ok(())
589    }
590
591    /// Adds a new record to the program.
592    ///
593    /// # Errors
594    /// This method will halt if the record was previously added.
595    /// This method will halt if the record name is already in use in the program.
596    /// This method will halt if the record name is a reserved opcode or keyword.
597    /// This method will halt if any records in the record's members are not already defined.
598    #[inline]
599    fn add_record(&mut self, record: RecordType<N>) -> Result<()> {
600        // Retrieve the record name.
601        let record_name = *record.name();
602
603        // Ensure the program has not exceeded the maximum number of records.
604        ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");
605
606        // Ensure the record name is new.
607        ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
608        // Ensure the record name is not a reserved opcode.
609        ensure!(!Self::is_reserved_opcode(&record_name.to_string()), "'{record_name}' is a reserved opcode.");
610        // Ensure the record name is not a reserved keyword.
611        ensure!(!Self::is_reserved_keyword(&record_name), "'{record_name}' is a reserved keyword.");
612
613        // Ensure all record entries are well-formed.
614        // Note: This design ensures cyclic references are not possible.
615        for (identifier, entry_type) in record.entries() {
616            // Ensure the member name is not a reserved keyword.
617            ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
618            // Ensure the member type is already defined in the program.
619            match entry_type.plaintext_type() {
620                PlaintextType::Literal(_) => continue,
621                PlaintextType::Struct(identifier) => {
622                    if !self.structs.contains_key(identifier) {
623                        bail!("Struct '{identifier}' in record '{record_name}' is not defined.")
624                    }
625                }
626                PlaintextType::Array(array_type) => {
627                    if let PlaintextType::Struct(struct_name) = array_type.base_element_type() {
628                        // Ensure the member struct name exists in the program.
629                        if !self.structs.contains_key(struct_name) {
630                            bail!("'{struct_name}' in array '{array_type}' is not defined.")
631                        }
632                    }
633                }
634            }
635        }
636
637        // Add the record name to the identifiers.
638        if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() {
639            bail!("'{record_name}' already exists in the program.")
640        }
641        // Add the record to the program.
642        if self.records.insert(record_name, record).is_some() {
643            bail!("'{record_name}' already exists in the program.")
644        }
645        Ok(())
646    }
647
648    /// Adds a new closure to the program.
649    ///
650    /// # Errors
651    /// This method will halt if the closure was previously added.
652    /// This method will halt if the closure name is already in use in the program.
653    /// This method will halt if the closure name is a reserved opcode or keyword.
654    /// This method will halt if any registers are assigned more than once.
655    /// This method will halt if the registers are not incrementing monotonically.
656    /// This method will halt if an input type references a non-existent definition.
657    /// This method will halt if an operand register does not already exist in memory.
658    /// This method will halt if a destination register already exists in memory.
659    /// This method will halt if an output register does not already exist.
660    /// This method will halt if an output type references a non-existent definition.
661    #[inline]
662    fn add_closure(&mut self, closure: ClosureCore<N>) -> Result<()> {
663        // Retrieve the closure name.
664        let closure_name = *closure.name();
665
666        // Ensure the program has not exceeded the maximum number of closures.
667        ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");
668
669        // Ensure the closure name is new.
670        ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
671        // Ensure the closure name is not a reserved opcode.
672        ensure!(!Self::is_reserved_opcode(&closure_name.to_string()), "'{closure_name}' is a reserved opcode.");
673        // Ensure the closure name is not a reserved keyword.
674        ensure!(!Self::is_reserved_keyword(&closure_name), "'{closure_name}' is a reserved keyword.");
675
676        // Ensure there are input statements in the closure.
677        ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
678        // Ensure the number of inputs is within the allowed range.
679        ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
680        // Ensure there are instructions in the closure.
681        ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
682        // Ensure the number of outputs is within the allowed range.
683        ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
684
685        // Add the function name to the identifiers.
686        if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() {
687            bail!("'{closure_name}' already exists in the program.")
688        }
689        // Add the closure to the program.
690        if self.closures.insert(closure_name, closure).is_some() {
691            bail!("'{closure_name}' already exists in the program.")
692        }
693        Ok(())
694    }
695
696    /// Adds a new function to the program.
697    ///
698    /// # Errors
699    /// This method will halt if the function was previously added.
700    /// This method will halt if the function name is already in use in the program.
701    /// This method will halt if the function name is a reserved opcode or keyword.
702    /// This method will halt if any registers are assigned more than once.
703    /// This method will halt if the registers are not incrementing monotonically.
704    /// This method will halt if an input type references a non-existent definition.
705    /// This method will halt if an operand register does not already exist in memory.
706    /// This method will halt if a destination register already exists in memory.
707    /// This method will halt if an output register does not already exist.
708    /// This method will halt if an output type references a non-existent definition.
709    #[inline]
710    fn add_function(&mut self, function: FunctionCore<N>) -> Result<()> {
711        // Retrieve the function name.
712        let function_name = *function.name();
713
714        // Ensure the program has not exceeded the maximum number of functions.
715        ensure!(self.functions.len() < N::MAX_FUNCTIONS, "Program exceeds the maximum number of functions");
716
717        // Ensure the function name is new.
718        ensure!(self.is_unique_name(&function_name), "'{function_name}' is already in use.");
719        // Ensure the function name is not a reserved opcode.
720        ensure!(!Self::is_reserved_opcode(&function_name.to_string()), "'{function_name}' is a reserved opcode.");
721        // Ensure the function name is not a reserved keyword.
722        ensure!(!Self::is_reserved_keyword(&function_name), "'{function_name}' is a reserved keyword.");
723
724        // Ensure the number of inputs is within the allowed range.
725        ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
726        // Ensure the number of instructions is within the allowed range.
727        ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
728        // Ensure the number of outputs is within the allowed range.
729        ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
730
731        // Add the function name to the identifiers.
732        if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() {
733            bail!("'{function_name}' already exists in the program.")
734        }
735        // Add the function to the program.
736        if self.functions.insert(function_name, function).is_some() {
737            bail!("'{function_name}' already exists in the program.")
738        }
739        Ok(())
740    }
741
742    /// Returns `true` if the given name does not already exist in the program.
743    fn is_unique_name(&self, name: &Identifier<N>) -> bool {
744        !self.components.contains_key(&ProgramLabel::Identifier(*name))
745    }
746
747    /// Returns `true` if the given name is a reserved opcode.
748    pub fn is_reserved_opcode(name: &str) -> bool {
749        Instruction::<N>::is_reserved_opcode(name)
750    }
751
752    /// Returns `true` if the given name uses a reserved keyword.
753    pub fn is_reserved_keyword(name: &Identifier<N>) -> bool {
754        // Convert the given name to a string.
755        let name = name.to_string();
756        // Check if the name is a keyword.
757        Self::KEYWORDS.iter().any(|keyword| *keyword == name)
758    }
759
760    /// Returns an iterator over the restricted keywords for the given consensus version.
761    pub fn restricted_keywords_for_consensus_version(
762        consensus_version: ConsensusVersion,
763    ) -> impl Iterator<Item = &'static str> {
764        Self::RESTRICTED_KEYWORDS
765            .iter()
766            .filter(move |(version, _)| *version <= consensus_version)
767            .flat_map(|(_, keywords)| *keywords)
768            .copied()
769    }
770
771    /// Checks a program for restricted keywords for the given consensus version.
772    /// Returns an error if any restricted keywords are found.
773    /// Note: Restrictions are not enforced on the import names in case they were deployed before the restrictions were added.
774    pub fn check_restricted_keywords_for_consensus_version(&self, consensus_version: ConsensusVersion) -> Result<()> {
775        // Get all keywords that are restricted for the consensus version.
776        let keywords =
777            Program::<N>::restricted_keywords_for_consensus_version(consensus_version).collect::<IndexSet<_>>();
778        // Check if the program name is a restricted keywords.
779        let program_name = self.id().name().to_string();
780        if keywords.contains(&program_name.as_str()) {
781            bail!("Program name '{program_name}' is a restricted keyword for the current consensus version")
782        }
783        // Check that all top-level program components are not restricted keywords.
784        for component in self.components.keys() {
785            match component {
786                ProgramLabel::Identifier(identifier) => {
787                    if keywords.contains(identifier.to_string().as_str()) {
788                        bail!(
789                            "Program component '{identifier}' is a restricted keyword for the current consensus version"
790                        )
791                    }
792                }
793                ProgramLabel::Constructor => continue,
794            }
795        }
796        // Check that all record entry names are not restricted keywords.
797        for record_type in self.records().values() {
798            for entry_name in record_type.entries().keys() {
799                if keywords.contains(entry_name.to_string().as_str()) {
800                    bail!("Record entry '{entry_name}' is a restricted keyword for the current consensus version")
801                }
802            }
803        }
804        // Check that all struct member names are not restricted keywords.
805        for struct_type in self.structs().values() {
806            for member_name in struct_type.members().keys() {
807                if keywords.contains(member_name.to_string().as_str()) {
808                    bail!("Struct member '{member_name}' is a restricted keyword for the current consensus version")
809                }
810            }
811        }
812        // Check that all `finalize` positions.
813        // Note: It is sufficient to only check the positions in `FinalizeCore` since `FinalizeTypes::initialize` checks that every
814        // `Branch` instruction targets a valid position.
815        for function in self.functions().values() {
816            if let Some(finalize_logic) = function.finalize_logic() {
817                for position in finalize_logic.positions().keys() {
818                    if keywords.contains(position.to_string().as_str()) {
819                        bail!(
820                            "Finalize position '{position}' is a restricted keyword for the current consensus version"
821                        )
822                    }
823                }
824            }
825        }
826        Ok(())
827    }
828
829    /// Checks that the program structure is well-formed under the following rules:
830    ///  1. The program ID must not contain the keyword "aleo" in the program name.
831    ///  2. The record name must not contain the keyword "aleo".
832    ///  3. Record names must not be prefixes of other record names.
833    ///  4. Record entry names must not contain the keyword "aleo".
834    pub fn check_program_naming_structure(&self) -> Result<()> {
835        // 1. Check if the program ID contains the "aleo" substring
836        let program_id = self.id().name().to_string();
837        if program_id.contains("aleo") {
838            bail!("Program ID '{program_id}' can't contain the reserved keyword 'aleo'.");
839        }
840
841        // Fetch the record names in a sorted BTreeSet.
842        let record_names: BTreeSet<String> = self.records.keys().map(|name| name.to_string()).collect();
843
844        // 2. Check if any record name contains the "aleo" substring.
845        for record_name in &record_names {
846            if record_name.contains("aleo") {
847                bail!("Record name '{record_name}' can't contain the reserved keyword 'aleo'.");
848            }
849        }
850
851        // 3. Check if any of the record names are a prefix of another.
852        let mut record_names_iter = record_names.iter();
853        let mut previous_record_name = record_names_iter.next();
854        for record_name in record_names_iter {
855            if let Some(previous) = previous_record_name {
856                if record_name.starts_with(previous) {
857                    bail!("Record name '{previous}' can't be a prefix of record name '{record_name}'.");
858                }
859            }
860            previous_record_name = Some(record_name);
861        }
862
863        // 4. Check if any record entry names contain the "aleo" substring.
864        for record_entry_name in self.records.values().flat_map(|record_type| record_type.entries().keys()) {
865            if record_entry_name.to_string().contains("aleo") {
866                bail!("Record entry name '{record_entry_name}' can't contain the reserved keyword 'aleo'.");
867            }
868        }
869
870        Ok(())
871    }
872
873    /// Checks that the program does not make external calls to `credits.aleo/upgrade`.
874    pub fn check_external_calls_to_credits_upgrade(&self) -> Result<()> {
875        // Check if the program makes external calls to `credits.aleo/upgrade`.
876        cfg_iter!(self.functions()).flat_map(|(_, function)| function.instructions()).try_for_each(|instruction| {
877            if let Some(CallOperator::Locator(locator)) = instruction.call_operator() {
878                // Check if the locator is restricted.
879                if locator.to_string() == "credits.aleo/upgrade" {
880                    bail!("External call to restricted locator '{locator}'")
881                }
882            }
883            Ok(())
884        })?;
885        Ok(())
886    }
887
888    /// Returns `true` if a program contains any V9 syntax.
889    /// This includes `constructor`, `Operand::Edition`, `Operand::Checksum`, and `Operand::ProgramOwner`.
890    /// This is enforced to be `false` for programs before `ConsensusVersion::V9`.
891    #[inline]
892    pub fn contains_v9_syntax(&self) -> bool {
893        // Check if the program contains a constructor.
894        if self.contains_constructor() {
895            return true;
896        }
897        // Check each instruction and output in each function's finalize scope for the use of
898        // `Operand::Checksum`, `Operand::Edition` or `Operand::ProgramOwner`.
899        for function in self.functions().values() {
900            // Check the finalize scope if it exists.
901            if let Some(finalize_logic) = function.finalize_logic() {
902                // Check the command operands.
903                for command in finalize_logic.commands() {
904                    for operand in command.operands() {
905                        if matches!(operand, Operand::Checksum(_) | Operand::Edition(_) | Operand::ProgramOwner(_)) {
906                            return true;
907                        }
908                    }
909                }
910            }
911        }
912        // Return `false` since no V9 syntax was found.
913        false
914    }
915
916    /// Returns `true` if the program contains an array type with a size that exceeds the given maximum.
917    pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
918        self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
919            || self.structs.values().any(|struct_type| struct_type.exceeds_max_array_size(max_array_size))
920            || self.records.values().any(|record_type| record_type.exceeds_max_array_size(max_array_size))
921            || self.closures.values().any(|closure| closure.exceeds_max_array_size(max_array_size))
922            || self.functions.values().any(|function| function.exceeds_max_array_size(max_array_size))
923            || self.constructor.iter().any(|constructor| constructor.exceeds_max_array_size(max_array_size))
924    }
925
926    /// Returns `true` if a program contains any V11 syntax.
927    /// This includes:
928    /// 1. `.raw` hash or signature verification variants
929    /// 2. `ecdsa.verify.*` opcodes
930    /// 3. arrays that exceed the previous maximum length of 32.
931    #[inline]
932    pub fn contains_v11_syntax(&self) -> bool {
933        // The previous maximum array size before V11.
934        const V10_MAX_ARRAY_ELEMENTS: u32 = 32;
935
936        // Helper to check if any of the opcodes:
937        // - start with `ecdsa.verify`, `serialize`, or `deserialize`
938        // - end with `.raw` or `.native`
939        let has_op = |opcode: &str| {
940            opcode.starts_with("ecdsa.verify")
941                || opcode.starts_with("serialize")
942                || opcode.starts_with("deserialize")
943                || opcode.ends_with(".raw")
944                || opcode.ends_with(".native")
945        };
946
947        // Determine if any function instructions contain the new syntax.
948        let function_contains = cfg_iter!(self.functions())
949            .flat_map(|(_, function)| function.instructions())
950            .any(|instruction| has_op(*instruction.opcode()));
951
952        // Determine if any closure instructions contain the new syntax.
953        let closure_contains = cfg_iter!(self.closures())
954            .flat_map(|(_, closure)| closure.instructions())
955            .any(|instruction| has_op(*instruction.opcode()));
956
957        // Determine if any finalize commands or constructor commands contain the new syntax.
958        let command_contains = cfg_iter!(self.functions())
959            .flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
960            .flatten()
961            .chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
962            .any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
963
964        // Determine if any of the array types exceed the previous maximum length of 32.
965        let array_size_exceeds = self.exceeds_max_array_size(V10_MAX_ARRAY_ELEMENTS);
966
967        function_contains || closure_contains || command_contains || array_size_exceeds
968    }
969}
970
971impl<N: Network> TypeName for ProgramCore<N> {
972    /// Returns the type name as a string.
973    #[inline]
974    fn type_name() -> &'static str {
975        "program"
976    }
977}
978
979#[cfg(test)]
980mod tests {
981    use super::*;
982    use console::{
983        network::MainnetV0,
984        program::{Locator, ValueType},
985    };
986
987    type CurrentNetwork = MainnetV0;
988
989    #[test]
990    fn test_program_mapping() -> Result<()> {
991        // Create a new mapping.
992        let mapping = Mapping::<CurrentNetwork>::from_str(
993            r"
994mapping message:
995    key as field.public;
996    value as field.public;",
997        )?;
998
999        // Initialize a new program.
1000        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1001        // Ensure the mapping was added.
1002        assert!(program.contains_mapping(&Identifier::from_str("message")?));
1003        // Ensure the retrieved mapping matches.
1004        assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1005
1006        Ok(())
1007    }
1008
1009    #[test]
1010    fn test_program_struct() -> Result<()> {
1011        // Create a new struct.
1012        let struct_ = StructType::<CurrentNetwork>::from_str(
1013            r"
1014struct message:
1015    first as field;
1016    second as field;",
1017        )?;
1018
1019        // Initialize a new program.
1020        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1021        // Ensure the struct was added.
1022        assert!(program.contains_struct(&Identifier::from_str("message")?));
1023        // Ensure the retrieved struct matches.
1024        assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1025
1026        Ok(())
1027    }
1028
1029    #[test]
1030    fn test_program_record() -> Result<()> {
1031        // Create a new record.
1032        let record = RecordType::<CurrentNetwork>::from_str(
1033            r"
1034record foo:
1035    owner as address.private;
1036    first as field.private;
1037    second as field.public;",
1038        )?;
1039
1040        // Initialize a new program.
1041        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1042        // Ensure the record was added.
1043        assert!(program.contains_record(&Identifier::from_str("foo")?));
1044        // Ensure the retrieved record matches.
1045        assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1046
1047        Ok(())
1048    }
1049
1050    #[test]
1051    fn test_program_function() -> Result<()> {
1052        // Create a new function.
1053        let function = Function::<CurrentNetwork>::from_str(
1054            r"
1055function compute:
1056    input r0 as field.public;
1057    input r1 as field.private;
1058    add r0 r1 into r2;
1059    output r2 as field.private;",
1060        )?;
1061
1062        // Initialize a new program.
1063        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1064        // Ensure the function was added.
1065        assert!(program.contains_function(&Identifier::from_str("compute")?));
1066        // Ensure the retrieved function matches.
1067        assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1068
1069        Ok(())
1070    }
1071
1072    #[test]
1073    fn test_program_import() -> Result<()> {
1074        // Initialize a new program.
1075        let program = Program::<CurrentNetwork>::from_str(
1076            r"
1077import eth.aleo;
1078import usdc.aleo;
1079
1080program swap.aleo;
1081
1082// The `swap` function transfers ownership of the record
1083// for token A to the record owner of token B, and vice-versa.
1084function swap:
1085    // Input the record for token A.
1086    input r0 as eth.aleo/eth.record;
1087    // Input the record for token B.
1088    input r1 as usdc.aleo/usdc.record;
1089
1090    // Send the record for token A to the owner of token B.
1091    call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1092
1093    // Send the record for token B to the owner of token A.
1094    call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1095
1096    // Output the new record for token A.
1097    output r2 as eth.aleo/eth.record;
1098    // Output the new record for token B.
1099    output r4 as usdc.aleo/usdc.record;
1100    ",
1101        )
1102        .unwrap();
1103
1104        // Ensure the program imports exist.
1105        assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1106        assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1107
1108        // Retrieve the 'swap' function.
1109        let function = program.get_function(&Identifier::from_str("swap")?)?;
1110
1111        // Ensure there are two inputs.
1112        assert_eq!(function.inputs().len(), 2);
1113        assert_eq!(function.input_types().len(), 2);
1114
1115        // Declare the expected input types.
1116        let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1117        let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1118
1119        // Ensure the inputs are external records.
1120        assert_eq!(function.input_types()[0], expected_input_type_1);
1121        assert_eq!(function.input_types()[1], expected_input_type_2);
1122
1123        // Ensure the input variants are correct.
1124        assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1125        assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1126
1127        // Ensure there are two instructions.
1128        assert_eq!(function.instructions().len(), 2);
1129
1130        // Ensure the instructions are calls.
1131        assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1132        assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1133
1134        // Ensure there are two outputs.
1135        assert_eq!(function.outputs().len(), 2);
1136        assert_eq!(function.output_types().len(), 2);
1137
1138        // Declare the expected output types.
1139        let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1140        let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1141
1142        // Ensure the outputs are external records.
1143        assert_eq!(function.output_types()[0], expected_output_type_1);
1144        assert_eq!(function.output_types()[1], expected_output_type_2);
1145
1146        // Ensure the output variants are correct.
1147        assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1148        assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1149
1150        Ok(())
1151    }
1152
1153    #[test]
1154    fn test_program_with_constructor() {
1155        // Initialize a new program.
1156        let program_string = r"import credits.aleo;
1157
1158program good_constructor.aleo;
1159
1160constructor:
1161    assert.eq edition 0u16;
1162    assert.eq credits.aleo/edition 0u16;
1163    assert.neq checksum 0field;
1164    assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1165    set 1u8 into data[0u8];
1166
1167mapping data:
1168    key as u8.public;
1169    value as u8.public;
1170
1171function dummy:
1172
1173function check:
1174    async check into r0;
1175    output r0 as good_constructor.aleo/check.future;
1176
1177finalize check:
1178    get data[0u8] into r0;
1179    assert.eq r0 1u8;
1180";
1181        let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1182
1183        // Check that the string and bytes (de)serialization works.
1184        let serialized = program.to_string();
1185        let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1186        assert_eq!(program, deserialized);
1187
1188        let serialized = program.to_bytes_le().unwrap();
1189        let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1190        assert_eq!(program, deserialized);
1191
1192        // Check that the display works.
1193        let display = format!("{program}");
1194        assert_eq!(display, program_string);
1195
1196        // Ensure the program contains a constructor.
1197        assert!(program.contains_constructor());
1198        assert_eq!(program.constructor().unwrap().commands().len(), 5);
1199    }
1200
1201    #[test]
1202    fn test_program_equality_and_checksum() {
1203        fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1204            println!("Comparing programs:\n{program1}\n{program2}");
1205            let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1206            let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1207            assert_eq!(program1 == program2, expected_equal);
1208            assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1209        }
1210
1211        // Test two identical programs, with different whitespace.
1212        run_test(r"program test.aleo; function dummy:    ", r"program  test.aleo;     function dummy:   ", true);
1213
1214        // Test two programs, one with a different function name.
1215        run_test(r"program test.aleo; function dummy:    ", r"program test.aleo; function bummy:   ", false);
1216
1217        // Test two programs, one with a constructor and one without.
1218        run_test(
1219            r"program test.aleo; function dummy:    ",
1220            r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1221            false,
1222        );
1223
1224        // Test two programs, both with a struct and function, but in different order.
1225        run_test(
1226            r"program test.aleo; struct foo: data as u8; function dummy:",
1227            r"program test.aleo; function dummy: struct foo: data as u8;",
1228            false,
1229        );
1230    }
1231}