1#![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 Constructor,
115 Identifier(Identifier<N>),
117}
118
119#[cfg(not(feature = "serial"))]
120use rayon::prelude::*;
121
122#[derive(Copy, Clone, PartialEq, Eq, Hash)]
123enum ProgramDefinition {
124 Constructor,
126 Mapping,
128 Struct,
130 Record,
132 Closure,
134 Function,
136}
137
138#[derive(Clone)]
139pub struct ProgramCore<N: Network> {
140 id: ProgramID<N>,
142 imports: IndexMap<ProgramID<N>, Import<N>>,
144 components: IndexMap<ProgramLabel<N>, ProgramDefinition>,
146 constructor: Option<ConstructorCore<N>>,
148 mappings: IndexMap<Identifier<N>, Mapping<N>>,
150 structs: IndexMap<Identifier<N>, StructType<N>>,
152 records: IndexMap<Identifier<N>, RecordType<N>>,
154 closures: IndexMap<Identifier<N>, ClosureCore<N>>,
156 functions: IndexMap<Identifier<N>, FunctionCore<N>>,
158}
159
160impl<N: Network> PartialEq for ProgramCore<N> {
161 fn eq(&self, other: &Self) -> bool {
164 if self.components.len() != other.components.len() {
166 return false;
167 }
168 for (left, right) in self.components.iter().zip_eq(other.components.iter()) {
170 if left != right {
171 return false;
172 }
173 }
174 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 #[rustfmt::skip]
192 pub const KEYWORDS: &'static [&'static str] = &[
193 "const",
195 "constant",
196 "public",
197 "private",
198 "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 "true",
218 "false",
219 "input",
221 "output",
222 "as",
223 "into",
224 "record",
226 "owner",
227 "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 "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 #[rustfmt::skip]
271 pub const RESTRICTED_KEYWORDS: &'static [(ConsensusVersion, &'static [&'static str])] = &[
272 (ConsensusVersion::V6, &["constructor"])
273 ];
274
275 #[inline]
277 pub fn new(id: ProgramID<N>) -> Result<Self> {
278 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 #[inline]
296 pub fn credits() -> Result<Self> {
297 Self::from_str(include_str!("./resources/credits.aleo"))
298 }
299
300 pub const fn id(&self) -> &ProgramID<N> {
302 &self.id
303 }
304
305 pub const fn imports(&self) -> &IndexMap<ProgramID<N>, Import<N>> {
307 &self.imports
308 }
309
310 pub const fn constructor(&self) -> Option<&ConstructorCore<N>> {
312 self.constructor.as_ref()
313 }
314
315 pub const fn mappings(&self) -> &IndexMap<Identifier<N>, Mapping<N>> {
317 &self.mappings
318 }
319
320 pub const fn structs(&self) -> &IndexMap<Identifier<N>, StructType<N>> {
322 &self.structs
323 }
324
325 pub const fn records(&self) -> &IndexMap<Identifier<N>, RecordType<N>> {
327 &self.records
328 }
329
330 pub const fn closures(&self) -> &IndexMap<Identifier<N>, ClosureCore<N>> {
332 &self.closures
333 }
334
335 pub const fn functions(&self) -> &IndexMap<Identifier<N>, FunctionCore<N>> {
337 &self.functions
338 }
339
340 pub fn contains_import(&self, id: &ProgramID<N>) -> bool {
342 self.imports.contains_key(id)
343 }
344
345 pub const fn contains_constructor(&self) -> bool {
347 self.constructor.is_some()
348 }
349
350 pub fn contains_mapping(&self, name: &Identifier<N>) -> bool {
352 self.mappings.contains_key(name)
353 }
354
355 pub fn contains_struct(&self, name: &Identifier<N>) -> bool {
357 self.structs.contains_key(name)
358 }
359
360 pub fn contains_record(&self, name: &Identifier<N>) -> bool {
362 self.records.contains_key(name)
363 }
364
365 pub fn contains_closure(&self, name: &Identifier<N>) -> bool {
367 self.closures.contains_key(name)
368 }
369
370 pub fn contains_function(&self, name: &Identifier<N>) -> bool {
372 self.functions.contains_key(name)
373 }
374
375 pub fn get_mapping(&self, name: &Identifier<N>) -> Result<Mapping<N>> {
377 let mapping = self.mappings.get(name).cloned().ok_or_else(|| anyhow!("Mapping '{name}' is not defined."))?;
379 ensure!(mapping.name() == name, "Expected mapping '{name}', but found mapping '{}'", mapping.name());
381 Ok(mapping)
383 }
384
385 pub fn get_struct(&self, name: &Identifier<N>) -> Result<&StructType<N>> {
387 let struct_ = self.structs.get(name).ok_or_else(|| anyhow!("Struct '{name}' is not defined."))?;
389 ensure!(struct_.name() == name, "Expected struct '{name}', but found struct '{}'", struct_.name());
391 ensure!(!struct_.members().is_empty(), "Struct '{name}' is missing members.");
393 Ok(struct_)
395 }
396
397 pub fn get_record(&self, name: &Identifier<N>) -> Result<&RecordType<N>> {
399 let record = self.records.get(name).ok_or_else(|| anyhow!("Record '{name}' is not defined."))?;
401 ensure!(record.name() == name, "Expected record '{name}', but found record '{}'", record.name());
403 Ok(record)
405 }
406
407 pub fn get_closure(&self, name: &Identifier<N>) -> Result<ClosureCore<N>> {
409 let closure = self.closures.get(name).cloned().ok_or_else(|| anyhow!("Closure '{name}' is not defined."))?;
411 ensure!(closure.name() == name, "Expected closure '{name}', but found closure '{}'", closure.name());
413 ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
415 ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
417 ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
419 ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
421 Ok(closure)
423 }
424
425 pub fn get_function(&self, name: &Identifier<N>) -> Result<FunctionCore<N>> {
427 self.get_function_ref(name).cloned()
428 }
429
430 pub fn get_function_ref(&self, name: &Identifier<N>) -> Result<&FunctionCore<N>> {
432 let function = self.functions.get(name).ok_or(anyhow!("Function '{}/{name}' is not defined.", self.id))?;
434 ensure!(function.name() == name, "Expected function '{name}', but found function '{}'", function.name());
436 ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
438 ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
440 ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
442 Ok(function)
444 }
445
446 #[inline]
451 fn add_import(&mut self, import: Import<N>) -> Result<()> {
452 let import_name = *import.name();
454
455 ensure!(self.imports.len() < N::MAX_IMPORTS, "Program exceeds the maximum number of imports");
457
458 ensure!(self.is_unique_name(&import_name), "'{import_name}' is already in use.");
460 ensure!(!Self::is_reserved_opcode(&import_name.to_string()), "'{import_name}' is a reserved opcode.");
462 ensure!(!Self::is_reserved_keyword(&import_name), "'{import_name}' is a reserved keyword.");
464
465 ensure!(
467 !self.imports.contains_key(import.program_id()),
468 "Import '{}' is already defined.",
469 import.program_id()
470 );
471
472 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 fn add_constructor(&mut self, constructor: ConstructorCore<N>) -> Result<()> {
485 ensure!(self.constructor.is_none(), "Program already has a constructor.");
487 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 if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() {
492 bail!("Constructor already exists in the program.")
493 }
494 self.constructor = Some(constructor);
496 Ok(())
497 }
498
499 #[inline]
505 fn add_mapping(&mut self, mapping: Mapping<N>) -> Result<()> {
506 let mapping_name = *mapping.name();
508
509 ensure!(self.mappings.len() < N::MAX_MAPPINGS, "Program exceeds the maximum number of mappings");
511
512 ensure!(self.is_unique_name(&mapping_name), "'{mapping_name}' is already in use.");
514 ensure!(!Self::is_reserved_keyword(&mapping_name), "'{mapping_name}' is a reserved keyword.");
516 ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode.");
518
519 if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() {
521 bail!("'{mapping_name}' already exists in the program.")
522 }
523 if self.mappings.insert(mapping_name, mapping).is_some() {
525 bail!("'{mapping_name}' already exists in the program.")
526 }
527 Ok(())
528 }
529
530 #[inline]
538 fn add_struct(&mut self, struct_: StructType<N>) -> Result<()> {
539 let struct_name = *struct_.name();
541
542 ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");
544
545 ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
547 ensure!(!Self::is_reserved_opcode(&struct_name.to_string()), "'{struct_name}' is a reserved opcode.");
549 ensure!(!Self::is_reserved_keyword(&struct_name), "'{struct_name}' is a reserved keyword.");
551
552 ensure!(!struct_.members().is_empty(), "Struct '{struct_name}' is missing members.");
554
555 for (identifier, plaintext_type) in struct_.members() {
558 ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
560 match plaintext_type {
562 PlaintextType::Literal(_) => continue,
563 PlaintextType::Struct(member_identifier) => {
564 if !self.structs.contains_key(member_identifier) {
566 bail!("'{member_identifier}' in struct '{struct_name}' is not defined.")
567 }
568 }
569 PlaintextType::Array(array_type) => {
570 if let PlaintextType::Struct(struct_name) = array_type.base_element_type() {
571 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 if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() {
582 bail!("'{struct_name}' already exists in the program.")
583 }
584 if self.structs.insert(struct_name, struct_).is_some() {
586 bail!("'{struct_name}' already exists in the program.")
587 }
588 Ok(())
589 }
590
591 #[inline]
599 fn add_record(&mut self, record: RecordType<N>) -> Result<()> {
600 let record_name = *record.name();
602
603 ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");
605
606 ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
608 ensure!(!Self::is_reserved_opcode(&record_name.to_string()), "'{record_name}' is a reserved opcode.");
610 ensure!(!Self::is_reserved_keyword(&record_name), "'{record_name}' is a reserved keyword.");
612
613 for (identifier, entry_type) in record.entries() {
616 ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
618 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 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 if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() {
639 bail!("'{record_name}' already exists in the program.")
640 }
641 if self.records.insert(record_name, record).is_some() {
643 bail!("'{record_name}' already exists in the program.")
644 }
645 Ok(())
646 }
647
648 #[inline]
662 fn add_closure(&mut self, closure: ClosureCore<N>) -> Result<()> {
663 let closure_name = *closure.name();
665
666 ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");
668
669 ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
671 ensure!(!Self::is_reserved_opcode(&closure_name.to_string()), "'{closure_name}' is a reserved opcode.");
673 ensure!(!Self::is_reserved_keyword(&closure_name), "'{closure_name}' is a reserved keyword.");
675
676 ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
678 ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
680 ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
682 ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
684
685 if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() {
687 bail!("'{closure_name}' already exists in the program.")
688 }
689 if self.closures.insert(closure_name, closure).is_some() {
691 bail!("'{closure_name}' already exists in the program.")
692 }
693 Ok(())
694 }
695
696 #[inline]
710 fn add_function(&mut self, function: FunctionCore<N>) -> Result<()> {
711 let function_name = *function.name();
713
714 ensure!(self.functions.len() < N::MAX_FUNCTIONS, "Program exceeds the maximum number of functions");
716
717 ensure!(self.is_unique_name(&function_name), "'{function_name}' is already in use.");
719 ensure!(!Self::is_reserved_opcode(&function_name.to_string()), "'{function_name}' is a reserved opcode.");
721 ensure!(!Self::is_reserved_keyword(&function_name), "'{function_name}' is a reserved keyword.");
723
724 ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
726 ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
728 ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
730
731 if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() {
733 bail!("'{function_name}' already exists in the program.")
734 }
735 if self.functions.insert(function_name, function).is_some() {
737 bail!("'{function_name}' already exists in the program.")
738 }
739 Ok(())
740 }
741
742 fn is_unique_name(&self, name: &Identifier<N>) -> bool {
744 !self.components.contains_key(&ProgramLabel::Identifier(*name))
745 }
746
747 pub fn is_reserved_opcode(name: &str) -> bool {
749 Instruction::<N>::is_reserved_opcode(name)
750 }
751
752 pub fn is_reserved_keyword(name: &Identifier<N>) -> bool {
754 let name = name.to_string();
756 Self::KEYWORDS.iter().any(|keyword| *keyword == name)
758 }
759
760 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 pub fn check_restricted_keywords_for_consensus_version(&self, consensus_version: ConsensusVersion) -> Result<()> {
775 let keywords =
777 Program::<N>::restricted_keywords_for_consensus_version(consensus_version).collect::<IndexSet<_>>();
778 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 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 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 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 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 pub fn check_program_naming_structure(&self) -> Result<()> {
835 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 let record_names: BTreeSet<String> = self.records.keys().map(|name| name.to_string()).collect();
843
844 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 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 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 pub fn check_external_calls_to_credits_upgrade(&self) -> Result<()> {
875 cfg_iter!(self.functions()).flat_map(|(_, function)| function.instructions()).try_for_each(|instruction| {
877 if let Some(CallOperator::Locator(locator)) = instruction.call_operator() {
878 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 #[inline]
892 pub fn contains_v9_syntax(&self) -> bool {
893 if self.contains_constructor() {
895 return true;
896 }
897 for function in self.functions().values() {
900 if let Some(finalize_logic) = function.finalize_logic() {
902 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 false
914 }
915
916 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
918 self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
919 || self.structs.values().any(|struct_type| struct_type.exceeds_max_array_size(max_array_size))
920 || self.records.values().any(|record_type| record_type.exceeds_max_array_size(max_array_size))
921 || self.closures.values().any(|closure| closure.exceeds_max_array_size(max_array_size))
922 || self.functions.values().any(|function| function.exceeds_max_array_size(max_array_size))
923 || self.constructor.iter().any(|constructor| constructor.exceeds_max_array_size(max_array_size))
924 }
925
926 #[inline]
932 pub fn contains_v11_syntax(&self) -> bool {
933 const V10_MAX_ARRAY_ELEMENTS: u32 = 32;
935
936 let has_op = |opcode: &str| {
940 opcode.starts_with("ecdsa.verify")
941 || opcode.starts_with("serialize")
942 || opcode.starts_with("deserialize")
943 || opcode.ends_with(".raw")
944 || opcode.ends_with(".native")
945 };
946
947 let function_contains = cfg_iter!(self.functions())
949 .flat_map(|(_, function)| function.instructions())
950 .any(|instruction| has_op(*instruction.opcode()));
951
952 let closure_contains = cfg_iter!(self.closures())
954 .flat_map(|(_, closure)| closure.instructions())
955 .any(|instruction| has_op(*instruction.opcode()));
956
957 let command_contains = cfg_iter!(self.functions())
959 .flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
960 .flatten()
961 .chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
962 .any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
963
964 let array_size_exceeds = self.exceeds_max_array_size(V10_MAX_ARRAY_ELEMENTS);
966
967 function_contains || closure_contains || command_contains || array_size_exceeds
968 }
969}
970
971impl<N: Network> TypeName for ProgramCore<N> {
972 #[inline]
974 fn type_name() -> &'static str {
975 "program"
976 }
977}
978
979#[cfg(test)]
980mod tests {
981 use super::*;
982 use console::{
983 network::MainnetV0,
984 program::{Locator, ValueType},
985 };
986
987 type CurrentNetwork = MainnetV0;
988
989 #[test]
990 fn test_program_mapping() -> Result<()> {
991 let mapping = Mapping::<CurrentNetwork>::from_str(
993 r"
994mapping message:
995 key as field.public;
996 value as field.public;",
997 )?;
998
999 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1001 assert!(program.contains_mapping(&Identifier::from_str("message")?));
1003 assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1005
1006 Ok(())
1007 }
1008
1009 #[test]
1010 fn test_program_struct() -> Result<()> {
1011 let struct_ = StructType::<CurrentNetwork>::from_str(
1013 r"
1014struct message:
1015 first as field;
1016 second as field;",
1017 )?;
1018
1019 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1021 assert!(program.contains_struct(&Identifier::from_str("message")?));
1023 assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1025
1026 Ok(())
1027 }
1028
1029 #[test]
1030 fn test_program_record() -> Result<()> {
1031 let record = RecordType::<CurrentNetwork>::from_str(
1033 r"
1034record foo:
1035 owner as address.private;
1036 first as field.private;
1037 second as field.public;",
1038 )?;
1039
1040 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1042 assert!(program.contains_record(&Identifier::from_str("foo")?));
1044 assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1046
1047 Ok(())
1048 }
1049
1050 #[test]
1051 fn test_program_function() -> Result<()> {
1052 let function = Function::<CurrentNetwork>::from_str(
1054 r"
1055function compute:
1056 input r0 as field.public;
1057 input r1 as field.private;
1058 add r0 r1 into r2;
1059 output r2 as field.private;",
1060 )?;
1061
1062 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1064 assert!(program.contains_function(&Identifier::from_str("compute")?));
1066 assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1068
1069 Ok(())
1070 }
1071
1072 #[test]
1073 fn test_program_import() -> Result<()> {
1074 let program = Program::<CurrentNetwork>::from_str(
1076 r"
1077import eth.aleo;
1078import usdc.aleo;
1079
1080program swap.aleo;
1081
1082// The `swap` function transfers ownership of the record
1083// for token A to the record owner of token B, and vice-versa.
1084function swap:
1085 // Input the record for token A.
1086 input r0 as eth.aleo/eth.record;
1087 // Input the record for token B.
1088 input r1 as usdc.aleo/usdc.record;
1089
1090 // Send the record for token A to the owner of token B.
1091 call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1092
1093 // Send the record for token B to the owner of token A.
1094 call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1095
1096 // Output the new record for token A.
1097 output r2 as eth.aleo/eth.record;
1098 // Output the new record for token B.
1099 output r4 as usdc.aleo/usdc.record;
1100 ",
1101 )
1102 .unwrap();
1103
1104 assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1106 assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1107
1108 let function = program.get_function(&Identifier::from_str("swap")?)?;
1110
1111 assert_eq!(function.inputs().len(), 2);
1113 assert_eq!(function.input_types().len(), 2);
1114
1115 let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1117 let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1118
1119 assert_eq!(function.input_types()[0], expected_input_type_1);
1121 assert_eq!(function.input_types()[1], expected_input_type_2);
1122
1123 assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1125 assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1126
1127 assert_eq!(function.instructions().len(), 2);
1129
1130 assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1132 assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1133
1134 assert_eq!(function.outputs().len(), 2);
1136 assert_eq!(function.output_types().len(), 2);
1137
1138 let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1140 let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1141
1142 assert_eq!(function.output_types()[0], expected_output_type_1);
1144 assert_eq!(function.output_types()[1], expected_output_type_2);
1145
1146 assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1148 assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1149
1150 Ok(())
1151 }
1152
1153 #[test]
1154 fn test_program_with_constructor() {
1155 let program_string = r"import credits.aleo;
1157
1158program good_constructor.aleo;
1159
1160constructor:
1161 assert.eq edition 0u16;
1162 assert.eq credits.aleo/edition 0u16;
1163 assert.neq checksum 0field;
1164 assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1165 set 1u8 into data[0u8];
1166
1167mapping data:
1168 key as u8.public;
1169 value as u8.public;
1170
1171function dummy:
1172
1173function check:
1174 async check into r0;
1175 output r0 as good_constructor.aleo/check.future;
1176
1177finalize check:
1178 get data[0u8] into r0;
1179 assert.eq r0 1u8;
1180";
1181 let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1182
1183 let serialized = program.to_string();
1185 let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1186 assert_eq!(program, deserialized);
1187
1188 let serialized = program.to_bytes_le().unwrap();
1189 let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1190 assert_eq!(program, deserialized);
1191
1192 let display = format!("{program}");
1194 assert_eq!(display, program_string);
1195
1196 assert!(program.contains_constructor());
1198 assert_eq!(program.constructor().unwrap().commands().len(), 5);
1199 }
1200
1201 #[test]
1202 fn test_program_equality_and_checksum() {
1203 fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1204 println!("Comparing programs:\n{program1}\n{program2}");
1205 let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1206 let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1207 assert_eq!(program1 == program2, expected_equal);
1208 assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1209 }
1210
1211 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function dummy: ", true);
1213
1214 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function bummy: ", false);
1216
1217 run_test(
1219 r"program test.aleo; function dummy: ",
1220 r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1221 false,
1222 );
1223
1224 run_test(
1226 r"program test.aleo; struct foo: data as u8; function dummy:",
1227 r"program test.aleo; function dummy: struct foo: data as u8;",
1228 false,
1229 );
1230 }
1231}