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 '{}' is not defined.", struct_name)
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!("'{}' already exists in the program.", struct_name)
583        }
584        // Add the struct to the program.
585        if self.structs.insert(struct_name, struct_).is_some() {
586            bail!("'{}' already exists in the program.", struct_name)
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
917impl<N: Network> TypeName for ProgramCore<N> {
918    /// Returns the type name as a string.
919    #[inline]
920    fn type_name() -> &'static str {
921        "program"
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928    use console::{
929        network::MainnetV0,
930        program::{Locator, ValueType},
931    };
932
933    type CurrentNetwork = MainnetV0;
934
935    #[test]
936    fn test_program_mapping() -> Result<()> {
937        // Create a new mapping.
938        let mapping = Mapping::<CurrentNetwork>::from_str(
939            r"
940mapping message:
941    key as field.public;
942    value as field.public;",
943        )?;
944
945        // Initialize a new program.
946        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
947        // Ensure the mapping was added.
948        assert!(program.contains_mapping(&Identifier::from_str("message")?));
949        // Ensure the retrieved mapping matches.
950        assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
951
952        Ok(())
953    }
954
955    #[test]
956    fn test_program_struct() -> Result<()> {
957        // Create a new struct.
958        let struct_ = StructType::<CurrentNetwork>::from_str(
959            r"
960struct message:
961    first as field;
962    second as field;",
963        )?;
964
965        // Initialize a new program.
966        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
967        // Ensure the struct was added.
968        assert!(program.contains_struct(&Identifier::from_str("message")?));
969        // Ensure the retrieved struct matches.
970        assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
971
972        Ok(())
973    }
974
975    #[test]
976    fn test_program_record() -> Result<()> {
977        // Create a new record.
978        let record = RecordType::<CurrentNetwork>::from_str(
979            r"
980record foo:
981    owner as address.private;
982    first as field.private;
983    second as field.public;",
984        )?;
985
986        // Initialize a new program.
987        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
988        // Ensure the record was added.
989        assert!(program.contains_record(&Identifier::from_str("foo")?));
990        // Ensure the retrieved record matches.
991        assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
992
993        Ok(())
994    }
995
996    #[test]
997    fn test_program_function() -> Result<()> {
998        // Create a new function.
999        let function = Function::<CurrentNetwork>::from_str(
1000            r"
1001function compute:
1002    input r0 as field.public;
1003    input r1 as field.private;
1004    add r0 r1 into r2;
1005    output r2 as field.private;",
1006        )?;
1007
1008        // Initialize a new program.
1009        let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1010        // Ensure the function was added.
1011        assert!(program.contains_function(&Identifier::from_str("compute")?));
1012        // Ensure the retrieved function matches.
1013        assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1014
1015        Ok(())
1016    }
1017
1018    #[test]
1019    fn test_program_import() -> Result<()> {
1020        // Initialize a new program.
1021        let program = Program::<CurrentNetwork>::from_str(
1022            r"
1023import eth.aleo;
1024import usdc.aleo;
1025
1026program swap.aleo;
1027
1028// The `swap` function transfers ownership of the record
1029// for token A to the record owner of token B, and vice-versa.
1030function swap:
1031    // Input the record for token A.
1032    input r0 as eth.aleo/eth.record;
1033    // Input the record for token B.
1034    input r1 as usdc.aleo/usdc.record;
1035
1036    // Send the record for token A to the owner of token B.
1037    call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1038
1039    // Send the record for token B to the owner of token A.
1040    call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1041
1042    // Output the new record for token A.
1043    output r2 as eth.aleo/eth.record;
1044    // Output the new record for token B.
1045    output r4 as usdc.aleo/usdc.record;
1046    ",
1047        )
1048        .unwrap();
1049
1050        // Ensure the program imports exist.
1051        assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1052        assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1053
1054        // Retrieve the 'swap' function.
1055        let function = program.get_function(&Identifier::from_str("swap")?)?;
1056
1057        // Ensure there are two inputs.
1058        assert_eq!(function.inputs().len(), 2);
1059        assert_eq!(function.input_types().len(), 2);
1060
1061        // Declare the expected input types.
1062        let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1063        let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1064
1065        // Ensure the inputs are external records.
1066        assert_eq!(function.input_types()[0], expected_input_type_1);
1067        assert_eq!(function.input_types()[1], expected_input_type_2);
1068
1069        // Ensure the input variants are correct.
1070        assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1071        assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1072
1073        // Ensure there are two instructions.
1074        assert_eq!(function.instructions().len(), 2);
1075
1076        // Ensure the instructions are calls.
1077        assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1078        assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1079
1080        // Ensure there are two outputs.
1081        assert_eq!(function.outputs().len(), 2);
1082        assert_eq!(function.output_types().len(), 2);
1083
1084        // Declare the expected output types.
1085        let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1086        let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1087
1088        // Ensure the outputs are external records.
1089        assert_eq!(function.output_types()[0], expected_output_type_1);
1090        assert_eq!(function.output_types()[1], expected_output_type_2);
1091
1092        // Ensure the output variants are correct.
1093        assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1094        assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1095
1096        Ok(())
1097    }
1098
1099    #[test]
1100    fn test_program_with_constructor() {
1101        // Initialize a new program.
1102        let program_string = r"import credits.aleo;
1103
1104program good_constructor.aleo;
1105
1106constructor:
1107    assert.eq edition 0u16;
1108    assert.eq credits.aleo/edition 0u16;
1109    assert.neq checksum 0field;
1110    assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1111    set 1u8 into data[0u8];
1112
1113mapping data:
1114    key as u8.public;
1115    value as u8.public;
1116
1117function dummy:
1118
1119function check:
1120    async check into r0;
1121    output r0 as good_constructor.aleo/check.future;
1122
1123finalize check:
1124    get data[0u8] into r0;
1125    assert.eq r0 1u8;
1126";
1127        let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1128
1129        // Check that the string and bytes (de)serialization works.
1130        let serialized = program.to_string();
1131        let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1132        assert_eq!(program, deserialized);
1133
1134        let serialized = program.to_bytes_le().unwrap();
1135        let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1136        assert_eq!(program, deserialized);
1137
1138        // Check that the display works.
1139        let display = format!("{program}");
1140        assert_eq!(display, program_string);
1141
1142        // Ensure the program contains a constructor.
1143        assert!(program.contains_constructor());
1144        assert_eq!(program.constructor().unwrap().commands().len(), 5);
1145    }
1146
1147    #[test]
1148    fn test_program_equality_and_checksum() {
1149        fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1150            println!("Comparing programs:\n{program1}\n{program2}");
1151            let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1152            let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1153            assert_eq!(program1 == program2, expected_equal);
1154            assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1155        }
1156
1157        // Test two identical programs, with different whitespace.
1158        run_test(r"program test.aleo; function dummy:    ", r"program  test.aleo;     function dummy:   ", true);
1159
1160        // Test two programs, one with a different function name.
1161        run_test(r"program test.aleo; function dummy:    ", r"program test.aleo; function bummy:   ", false);
1162
1163        // Test two programs, one with a constructor and one without.
1164        run_test(
1165            r"program test.aleo; function dummy:    ",
1166            r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1167            false,
1168        );
1169
1170        // Test two programs, both with a struct and function, but in different order.
1171        run_test(
1172            r"program test.aleo; struct foo: data as u8; function dummy:",
1173            r"program test.aleo; function dummy: struct foo: data as u8;",
1174            false,
1175        );
1176    }
1177}