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 #[inline]
974 pub fn contains_v12_syntax(&self) -> bool {
975 cfg_iter!(self.functions()).any(|(_, function)| {
978 function.finalize_logic().is_some_and(|finalize_logic| {
979 cfg_iter!(finalize_logic.commands()).any(|command| {
980 cfg_iter!(command.operands()).any(|operand| matches!(operand, Operand::BlockTimestamp))
981 })
982 })
983 })
984 }
985
986 #[inline]
990 pub fn contains_string_type(&self) -> bool {
991 self.mappings.values().any(|mapping| mapping.contains_string_type())
992 || self.structs.values().any(|struct_type| struct_type.contains_string_type())
993 || self.records.values().any(|record_type| record_type.contains_string_type())
994 || self.closures.values().any(|closure| closure.contains_string_type())
995 || self.functions.values().any(|function| function.contains_string_type())
996 || self.constructor.iter().any(|constructor| constructor.contains_string_type())
997 }
998}
999
1000impl<N: Network> TypeName for ProgramCore<N> {
1001 #[inline]
1003 fn type_name() -> &'static str {
1004 "program"
1005 }
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use super::*;
1011 use console::{
1012 network::MainnetV0,
1013 program::{Locator, ValueType},
1014 };
1015
1016 type CurrentNetwork = MainnetV0;
1017
1018 #[test]
1019 fn test_program_mapping() -> Result<()> {
1020 let mapping = Mapping::<CurrentNetwork>::from_str(
1022 r"
1023mapping message:
1024 key as field.public;
1025 value as field.public;",
1026 )?;
1027
1028 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1030 assert!(program.contains_mapping(&Identifier::from_str("message")?));
1032 assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1034
1035 Ok(())
1036 }
1037
1038 #[test]
1039 fn test_program_struct() -> Result<()> {
1040 let struct_ = StructType::<CurrentNetwork>::from_str(
1042 r"
1043struct message:
1044 first as field;
1045 second as field;",
1046 )?;
1047
1048 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1050 assert!(program.contains_struct(&Identifier::from_str("message")?));
1052 assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1054
1055 Ok(())
1056 }
1057
1058 #[test]
1059 fn test_program_record() -> Result<()> {
1060 let record = RecordType::<CurrentNetwork>::from_str(
1062 r"
1063record foo:
1064 owner as address.private;
1065 first as field.private;
1066 second as field.public;",
1067 )?;
1068
1069 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1071 assert!(program.contains_record(&Identifier::from_str("foo")?));
1073 assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1075
1076 Ok(())
1077 }
1078
1079 #[test]
1080 fn test_program_function() -> Result<()> {
1081 let function = Function::<CurrentNetwork>::from_str(
1083 r"
1084function compute:
1085 input r0 as field.public;
1086 input r1 as field.private;
1087 add r0 r1 into r2;
1088 output r2 as field.private;",
1089 )?;
1090
1091 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1093 assert!(program.contains_function(&Identifier::from_str("compute")?));
1095 assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1097
1098 Ok(())
1099 }
1100
1101 #[test]
1102 fn test_program_import() -> Result<()> {
1103 let program = Program::<CurrentNetwork>::from_str(
1105 r"
1106import eth.aleo;
1107import usdc.aleo;
1108
1109program swap.aleo;
1110
1111// The `swap` function transfers ownership of the record
1112// for token A to the record owner of token B, and vice-versa.
1113function swap:
1114 // Input the record for token A.
1115 input r0 as eth.aleo/eth.record;
1116 // Input the record for token B.
1117 input r1 as usdc.aleo/usdc.record;
1118
1119 // Send the record for token A to the owner of token B.
1120 call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1121
1122 // Send the record for token B to the owner of token A.
1123 call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1124
1125 // Output the new record for token A.
1126 output r2 as eth.aleo/eth.record;
1127 // Output the new record for token B.
1128 output r4 as usdc.aleo/usdc.record;
1129 ",
1130 )
1131 .unwrap();
1132
1133 assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1135 assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1136
1137 let function = program.get_function(&Identifier::from_str("swap")?)?;
1139
1140 assert_eq!(function.inputs().len(), 2);
1142 assert_eq!(function.input_types().len(), 2);
1143
1144 let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1146 let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1147
1148 assert_eq!(function.input_types()[0], expected_input_type_1);
1150 assert_eq!(function.input_types()[1], expected_input_type_2);
1151
1152 assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1154 assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1155
1156 assert_eq!(function.instructions().len(), 2);
1158
1159 assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1161 assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1162
1163 assert_eq!(function.outputs().len(), 2);
1165 assert_eq!(function.output_types().len(), 2);
1166
1167 let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1169 let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1170
1171 assert_eq!(function.output_types()[0], expected_output_type_1);
1173 assert_eq!(function.output_types()[1], expected_output_type_2);
1174
1175 assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1177 assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1178
1179 Ok(())
1180 }
1181
1182 #[test]
1183 fn test_program_with_constructor() {
1184 let program_string = r"import credits.aleo;
1186
1187program good_constructor.aleo;
1188
1189constructor:
1190 assert.eq edition 0u16;
1191 assert.eq credits.aleo/edition 0u16;
1192 assert.neq checksum 0field;
1193 assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1194 set 1u8 into data[0u8];
1195
1196mapping data:
1197 key as u8.public;
1198 value as u8.public;
1199
1200function dummy:
1201
1202function check:
1203 async check into r0;
1204 output r0 as good_constructor.aleo/check.future;
1205
1206finalize check:
1207 get data[0u8] into r0;
1208 assert.eq r0 1u8;
1209";
1210 let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1211
1212 let serialized = program.to_string();
1214 let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1215 assert_eq!(program, deserialized);
1216
1217 let serialized = program.to_bytes_le().unwrap();
1218 let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1219 assert_eq!(program, deserialized);
1220
1221 let display = format!("{program}");
1223 assert_eq!(display, program_string);
1224
1225 assert!(program.contains_constructor());
1227 assert_eq!(program.constructor().unwrap().commands().len(), 5);
1228 }
1229
1230 #[test]
1231 fn test_program_equality_and_checksum() {
1232 fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1233 println!("Comparing programs:\n{program1}\n{program2}");
1234 let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1235 let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1236 assert_eq!(program1 == program2, expected_equal);
1237 assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1238 }
1239
1240 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function dummy: ", true);
1242
1243 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function bummy: ", false);
1245
1246 run_test(
1248 r"program test.aleo; function dummy: ",
1249 r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1250 false,
1251 );
1252
1253 run_test(
1255 r"program test.aleo; struct foo: data as u8; function dummy:",
1256 r"program test.aleo; function dummy: struct foo: data as u8;",
1257 false,
1258 );
1259 }
1260}