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