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::{
103 EntryType,
104 FinalizeType,
105 Identifier,
106 Locator,
107 PlaintextType,
108 ProgramID,
109 RecordType,
110 StructType,
111 ValueType,
112 },
113 types::U8,
114};
115use snarkvm_utilities::cfg_iter;
116
117use indexmap::{IndexMap, IndexSet};
118use std::collections::BTreeSet;
119use tiny_keccak::{Hasher, Sha3 as TinySha3};
120
121#[derive(Copy, Clone, PartialEq, Eq, Hash)]
122enum ProgramLabel<N: Network> {
123 Constructor,
125 Identifier(Identifier<N>),
127}
128
129#[cfg(not(feature = "serial"))]
130use rayon::prelude::*;
131
132#[derive(Copy, Clone, PartialEq, Eq, Hash)]
133enum ProgramDefinition {
134 Constructor,
136 Mapping,
138 Struct,
140 Record,
142 Closure,
144 Function,
146}
147
148#[derive(Clone)]
149pub struct ProgramCore<N: Network> {
150 id: ProgramID<N>,
152 imports: IndexMap<ProgramID<N>, Import<N>>,
154 components: IndexMap<ProgramLabel<N>, ProgramDefinition>,
156 constructor: Option<ConstructorCore<N>>,
158 mappings: IndexMap<Identifier<N>, Mapping<N>>,
160 structs: IndexMap<Identifier<N>, StructType<N>>,
162 records: IndexMap<Identifier<N>, RecordType<N>>,
164 closures: IndexMap<Identifier<N>, ClosureCore<N>>,
166 functions: IndexMap<Identifier<N>, FunctionCore<N>>,
168}
169
170impl<N: Network> PartialEq for ProgramCore<N> {
171 fn eq(&self, other: &Self) -> bool {
174 if self.components.len() != other.components.len() {
176 return false;
177 }
178 for (left, right) in self.components.iter().zip_eq(other.components.iter()) {
180 if left != right {
181 return false;
182 }
183 }
184 self.id == other.id
186 && self.imports == other.imports
187 && self.mappings == other.mappings
188 && self.structs == other.structs
189 && self.records == other.records
190 && self.closures == other.closures
191 && self.functions == other.functions
192 }
193}
194
195impl<N: Network> Eq for ProgramCore<N> {}
196
197impl<N: Network> ProgramCore<N> {
198 #[rustfmt::skip]
202 pub const KEYWORDS: &'static [&'static str] = &[
203 "const",
205 "constant",
206 "public",
207 "private",
208 "address",
210 "boolean",
211 "field",
212 "group",
213 "i8",
214 "i16",
215 "i32",
216 "i64",
217 "i128",
218 "u8",
219 "u16",
220 "u32",
221 "u64",
222 "u128",
223 "scalar",
224 "signature",
225 "string",
226 "true",
228 "false",
229 "input",
231 "output",
232 "as",
233 "into",
234 "record",
236 "owner",
237 "transition",
239 "import",
240 "function",
241 "struct",
242 "closure",
243 "program",
244 "aleo",
245 "self",
246 "storage",
247 "mapping",
248 "key",
249 "value",
250 "async",
251 "finalize",
252 "global",
254 "block",
255 "return",
256 "break",
257 "assert",
258 "continue",
259 "let",
260 "if",
261 "else",
262 "while",
263 "for",
264 "switch",
265 "case",
266 "default",
267 "match",
268 "enum",
269 "struct",
270 "union",
271 "trait",
272 "impl",
273 "type",
274 "future",
275 ];
276 #[rustfmt::skip]
281 pub const RESTRICTED_KEYWORDS: &'static [(ConsensusVersion, &'static [&'static str])] = &[
282 (ConsensusVersion::V6, &["constructor"])
283 ];
284
285 #[inline]
287 pub fn new(id: ProgramID<N>) -> Result<Self> {
288 ensure!(!Self::is_reserved_keyword(id.name()), "Program name is invalid: {}", id.name());
290
291 Ok(Self {
292 id,
293 imports: IndexMap::new(),
294 constructor: None,
295 components: IndexMap::new(),
296 mappings: IndexMap::new(),
297 structs: IndexMap::new(),
298 records: IndexMap::new(),
299 closures: IndexMap::new(),
300 functions: IndexMap::new(),
301 })
302 }
303
304 #[inline]
306 pub fn credits() -> Result<Self> {
307 Self::from_str(include_str!("./resources/credits.aleo"))
308 }
309
310 pub const fn id(&self) -> &ProgramID<N> {
312 &self.id
313 }
314
315 pub const fn imports(&self) -> &IndexMap<ProgramID<N>, Import<N>> {
317 &self.imports
318 }
319
320 pub const fn constructor(&self) -> Option<&ConstructorCore<N>> {
322 self.constructor.as_ref()
323 }
324
325 pub const fn mappings(&self) -> &IndexMap<Identifier<N>, Mapping<N>> {
327 &self.mappings
328 }
329
330 pub const fn structs(&self) -> &IndexMap<Identifier<N>, StructType<N>> {
332 &self.structs
333 }
334
335 pub const fn records(&self) -> &IndexMap<Identifier<N>, RecordType<N>> {
337 &self.records
338 }
339
340 pub const fn closures(&self) -> &IndexMap<Identifier<N>, ClosureCore<N>> {
342 &self.closures
343 }
344
345 pub const fn functions(&self) -> &IndexMap<Identifier<N>, FunctionCore<N>> {
347 &self.functions
348 }
349
350 pub fn contains_import(&self, id: &ProgramID<N>) -> bool {
352 self.imports.contains_key(id)
353 }
354
355 pub const fn contains_constructor(&self) -> bool {
357 self.constructor.is_some()
358 }
359
360 pub fn contains_mapping(&self, name: &Identifier<N>) -> bool {
362 self.mappings.contains_key(name)
363 }
364
365 pub fn contains_struct(&self, name: &Identifier<N>) -> bool {
367 self.structs.contains_key(name)
368 }
369
370 pub fn contains_record(&self, name: &Identifier<N>) -> bool {
372 self.records.contains_key(name)
373 }
374
375 pub fn contains_closure(&self, name: &Identifier<N>) -> bool {
377 self.closures.contains_key(name)
378 }
379
380 pub fn contains_function(&self, name: &Identifier<N>) -> bool {
382 self.functions.contains_key(name)
383 }
384
385 pub fn get_mapping(&self, name: &Identifier<N>) -> Result<Mapping<N>> {
387 let mapping = self.mappings.get(name).cloned().ok_or_else(|| anyhow!("Mapping '{name}' is not defined."))?;
389 ensure!(mapping.name() == name, "Expected mapping '{name}', but found mapping '{}'", mapping.name());
391 Ok(mapping)
393 }
394
395 pub fn get_struct(&self, name: &Identifier<N>) -> Result<&StructType<N>> {
397 let struct_ = self.structs.get(name).ok_or_else(|| anyhow!("Struct '{name}' is not defined."))?;
399 ensure!(struct_.name() == name, "Expected struct '{name}', but found struct '{}'", struct_.name());
401 ensure!(!struct_.members().is_empty(), "Struct '{name}' is missing members.");
403 Ok(struct_)
405 }
406
407 pub fn get_record(&self, name: &Identifier<N>) -> Result<&RecordType<N>> {
409 let record = self.records.get(name).ok_or_else(|| anyhow!("Record '{name}' is not defined."))?;
411 ensure!(record.name() == name, "Expected record '{name}', but found record '{}'", record.name());
413 Ok(record)
415 }
416
417 pub fn get_closure(&self, name: &Identifier<N>) -> Result<ClosureCore<N>> {
419 let closure = self.closures.get(name).cloned().ok_or_else(|| anyhow!("Closure '{name}' is not defined."))?;
421 ensure!(closure.name() == name, "Expected closure '{name}', but found closure '{}'", closure.name());
423 ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
425 ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
427 ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
429 ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
431 Ok(closure)
433 }
434
435 pub fn get_function(&self, name: &Identifier<N>) -> Result<FunctionCore<N>> {
437 self.get_function_ref(name).cloned()
438 }
439
440 pub fn get_function_ref(&self, name: &Identifier<N>) -> Result<&FunctionCore<N>> {
442 let function = self.functions.get(name).ok_or(anyhow!("Function '{}/{name}' is not defined.", self.id))?;
444 ensure!(function.name() == name, "Expected function '{name}', but found function '{}'", function.name());
446 ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
448 ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
450 ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
452 Ok(function)
454 }
455
456 #[inline]
461 fn add_import(&mut self, import: Import<N>) -> Result<()> {
462 let import_name = *import.name();
464
465 ensure!(self.imports.len() < N::MAX_IMPORTS, "Program exceeds the maximum number of imports");
467
468 ensure!(self.is_unique_name(&import_name), "'{import_name}' is already in use.");
470 ensure!(!Self::is_reserved_opcode(&import_name.to_string()), "'{import_name}' is a reserved opcode.");
472 ensure!(!Self::is_reserved_keyword(&import_name), "'{import_name}' is a reserved keyword.");
474
475 ensure!(
477 !self.imports.contains_key(import.program_id()),
478 "Import '{}' is already defined.",
479 import.program_id()
480 );
481
482 if self.imports.insert(*import.program_id(), import.clone()).is_some() {
484 bail!("'{}' already exists in the program.", import.program_id())
485 }
486 Ok(())
487 }
488
489 fn add_constructor(&mut self, constructor: ConstructorCore<N>) -> Result<()> {
495 ensure!(self.constructor.is_none(), "Program already has a constructor.");
497 ensure!(!constructor.commands().is_empty(), "Constructor must have at least one command");
499 ensure!(constructor.commands().len() <= N::MAX_COMMANDS, "Constructor exceeds maximum number of commands");
500 if self.components.insert(ProgramLabel::Constructor, ProgramDefinition::Constructor).is_some() {
502 bail!("Constructor already exists in the program.")
503 }
504 self.constructor = Some(constructor);
506 Ok(())
507 }
508
509 #[inline]
515 fn add_mapping(&mut self, mapping: Mapping<N>) -> Result<()> {
516 let mapping_name = *mapping.name();
518
519 ensure!(self.mappings.len() < N::MAX_MAPPINGS, "Program exceeds the maximum number of mappings");
521
522 ensure!(self.is_unique_name(&mapping_name), "'{mapping_name}' is already in use.");
524 ensure!(!Self::is_reserved_keyword(&mapping_name), "'{mapping_name}' is a reserved keyword.");
526 ensure!(!Self::is_reserved_opcode(&mapping_name.to_string()), "'{mapping_name}' is a reserved opcode.");
528
529 if self.components.insert(ProgramLabel::Identifier(mapping_name), ProgramDefinition::Mapping).is_some() {
531 bail!("'{mapping_name}' already exists in the program.")
532 }
533 if self.mappings.insert(mapping_name, mapping).is_some() {
535 bail!("'{mapping_name}' already exists in the program.")
536 }
537 Ok(())
538 }
539
540 #[inline]
548 fn add_struct(&mut self, struct_: StructType<N>) -> Result<()> {
549 let struct_name = *struct_.name();
551
552 ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");
554
555 ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
557 ensure!(!Self::is_reserved_opcode(&struct_name.to_string()), "'{struct_name}' is a reserved opcode.");
559 ensure!(!Self::is_reserved_keyword(&struct_name), "'{struct_name}' is a reserved keyword.");
561
562 ensure!(!struct_.members().is_empty(), "Struct '{struct_name}' is missing members.");
564
565 for (identifier, plaintext_type) in struct_.members() {
568 ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
570 match plaintext_type {
572 PlaintextType::Literal(_) => continue,
573 PlaintextType::Struct(member_identifier) => {
574 if !self.structs.contains_key(member_identifier) {
576 bail!("'{member_identifier}' in struct '{struct_name}' is not defined.")
577 }
578 }
579 PlaintextType::ExternalStruct(locator) => {
580 if !self.imports.contains_key(locator.program_id()) {
581 bail!(
582 "External program {} referenced in struct '{struct_name}' does not exist",
583 locator.program_id()
584 );
585 }
586 }
587 PlaintextType::Array(array_type) => {
588 match array_type.base_element_type() {
589 PlaintextType::Struct(struct_name) =>
590 {
592 if !self.structs.contains_key(struct_name) {
593 bail!("'{struct_name}' in array '{array_type}' is not defined.")
594 }
595 }
596 PlaintextType::ExternalStruct(locator) => {
597 if !self.imports.contains_key(locator.program_id()) {
598 bail!(
599 "External program {} in array '{array_type}' does not exist",
600 locator.program_id()
601 );
602 }
603 }
604 PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
605 }
606 }
607 }
608 }
609
610 if self.components.insert(ProgramLabel::Identifier(struct_name), ProgramDefinition::Struct).is_some() {
612 bail!("'{struct_name}' already exists in the program.")
613 }
614 if self.structs.insert(struct_name, struct_).is_some() {
616 bail!("'{struct_name}' already exists in the program.")
617 }
618 Ok(())
619 }
620
621 #[inline]
629 fn add_record(&mut self, record: RecordType<N>) -> Result<()> {
630 let record_name = *record.name();
632
633 ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");
635
636 ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
638 ensure!(!Self::is_reserved_opcode(&record_name.to_string()), "'{record_name}' is a reserved opcode.");
640 ensure!(!Self::is_reserved_keyword(&record_name), "'{record_name}' is a reserved keyword.");
642
643 for (identifier, entry_type) in record.entries() {
646 ensure!(!Self::is_reserved_keyword(identifier), "'{identifier}' is a reserved keyword.");
648 match entry_type.plaintext_type() {
650 PlaintextType::Literal(_) => continue,
651 PlaintextType::Struct(identifier) => {
652 if !self.structs.contains_key(identifier) {
653 bail!("Struct '{identifier}' in record '{record_name}' is not defined.")
654 }
655 }
656 PlaintextType::ExternalStruct(locator) => {
657 if !self.imports.contains_key(locator.program_id()) {
658 bail!(
659 "External program {} referenced in record '{record_name}' does not exist",
660 locator.program_id()
661 );
662 }
663 }
664 PlaintextType::Array(array_type) => {
665 match array_type.base_element_type() {
666 PlaintextType::Struct(struct_name) =>
667 {
669 if !self.structs.contains_key(struct_name) {
670 bail!("'{struct_name}' in array '{array_type}' is not defined.")
671 }
672 }
673 PlaintextType::ExternalStruct(locator) => {
674 if !self.imports.contains_key(locator.program_id()) {
675 bail!(
676 "External program {} in array '{array_type}' does not exist",
677 locator.program_id()
678 );
679 }
680 }
681 PlaintextType::Array(..) | PlaintextType::Literal(..) => {}
682 }
683 }
684 }
685 }
686
687 if self.components.insert(ProgramLabel::Identifier(record_name), ProgramDefinition::Record).is_some() {
689 bail!("'{record_name}' already exists in the program.")
690 }
691 if self.records.insert(record_name, record).is_some() {
693 bail!("'{record_name}' already exists in the program.")
694 }
695 Ok(())
696 }
697
698 #[inline]
712 fn add_closure(&mut self, closure: ClosureCore<N>) -> Result<()> {
713 let closure_name = *closure.name();
715
716 ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");
718
719 ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
721 ensure!(!Self::is_reserved_opcode(&closure_name.to_string()), "'{closure_name}' is a reserved opcode.");
723 ensure!(!Self::is_reserved_keyword(&closure_name), "'{closure_name}' is a reserved keyword.");
725
726 ensure!(!closure.inputs().is_empty(), "Cannot evaluate a closure without input statements");
728 ensure!(closure.inputs().len() <= N::MAX_INPUTS, "Closure exceeds maximum number of inputs");
730 ensure!(!closure.instructions().is_empty(), "Cannot evaluate a closure without instructions");
732 ensure!(closure.outputs().len() <= N::MAX_OUTPUTS, "Closure exceeds maximum number of outputs");
734
735 if self.components.insert(ProgramLabel::Identifier(closure_name), ProgramDefinition::Closure).is_some() {
737 bail!("'{closure_name}' already exists in the program.")
738 }
739 if self.closures.insert(closure_name, closure).is_some() {
741 bail!("'{closure_name}' already exists in the program.")
742 }
743 Ok(())
744 }
745
746 #[inline]
760 fn add_function(&mut self, function: FunctionCore<N>) -> Result<()> {
761 let function_name = *function.name();
763
764 ensure!(self.functions.len() < N::MAX_FUNCTIONS, "Program exceeds the maximum number of functions");
766
767 ensure!(self.is_unique_name(&function_name), "'{function_name}' is already in use.");
769 ensure!(!Self::is_reserved_opcode(&function_name.to_string()), "'{function_name}' is a reserved opcode.");
771 ensure!(!Self::is_reserved_keyword(&function_name), "'{function_name}' is a reserved keyword.");
773
774 ensure!(function.inputs().len() <= N::MAX_INPUTS, "Function exceeds maximum number of inputs");
776 ensure!(function.instructions().len() <= N::MAX_INSTRUCTIONS, "Function exceeds maximum instructions");
778 ensure!(function.outputs().len() <= N::MAX_OUTPUTS, "Function exceeds maximum number of outputs");
780
781 if self.components.insert(ProgramLabel::Identifier(function_name), ProgramDefinition::Function).is_some() {
783 bail!("'{function_name}' already exists in the program.")
784 }
785 if self.functions.insert(function_name, function).is_some() {
787 bail!("'{function_name}' already exists in the program.")
788 }
789 Ok(())
790 }
791
792 fn is_unique_name(&self, name: &Identifier<N>) -> bool {
794 !self.components.contains_key(&ProgramLabel::Identifier(*name))
795 }
796
797 pub fn is_reserved_opcode(name: &str) -> bool {
799 Instruction::<N>::is_reserved_opcode(name)
800 }
801
802 pub fn is_reserved_keyword(name: &Identifier<N>) -> bool {
804 let name = name.to_string();
806 Self::KEYWORDS.iter().any(|keyword| *keyword == name)
808 }
809
810 pub fn restricted_keywords_for_consensus_version(
812 consensus_version: ConsensusVersion,
813 ) -> impl Iterator<Item = &'static str> {
814 Self::RESTRICTED_KEYWORDS
815 .iter()
816 .filter(move |(version, _)| *version <= consensus_version)
817 .flat_map(|(_, keywords)| *keywords)
818 .copied()
819 }
820
821 pub fn check_restricted_keywords_for_consensus_version(&self, consensus_version: ConsensusVersion) -> Result<()> {
825 let keywords =
827 Program::<N>::restricted_keywords_for_consensus_version(consensus_version).collect::<IndexSet<_>>();
828 let program_name = self.id().name().to_string();
830 if keywords.contains(&program_name.as_str()) {
831 bail!("Program name '{program_name}' is a restricted keyword for the current consensus version")
832 }
833 for component in self.components.keys() {
835 match component {
836 ProgramLabel::Identifier(identifier) => {
837 if keywords.contains(identifier.to_string().as_str()) {
838 bail!(
839 "Program component '{identifier}' is a restricted keyword for the current consensus version"
840 )
841 }
842 }
843 ProgramLabel::Constructor => continue,
844 }
845 }
846 for record_type in self.records().values() {
848 for entry_name in record_type.entries().keys() {
849 if keywords.contains(entry_name.to_string().as_str()) {
850 bail!("Record entry '{entry_name}' is a restricted keyword for the current consensus version")
851 }
852 }
853 }
854 for struct_type in self.structs().values() {
856 for member_name in struct_type.members().keys() {
857 if keywords.contains(member_name.to_string().as_str()) {
858 bail!("Struct member '{member_name}' is a restricted keyword for the current consensus version")
859 }
860 }
861 }
862 for function in self.functions().values() {
866 if let Some(finalize_logic) = function.finalize_logic() {
867 for position in finalize_logic.positions().keys() {
868 if keywords.contains(position.to_string().as_str()) {
869 bail!(
870 "Finalize position '{position}' is a restricted keyword for the current consensus version"
871 )
872 }
873 }
874 }
875 }
876 Ok(())
877 }
878
879 pub fn check_program_naming_structure(&self) -> Result<()> {
885 let program_id = self.id().name().to_string();
887 if program_id.contains("aleo") {
888 bail!("Program ID '{program_id}' can't contain the reserved keyword 'aleo'.");
889 }
890
891 let record_names: BTreeSet<String> = self.records.keys().map(|name| name.to_string()).collect();
893
894 for record_name in &record_names {
896 if record_name.contains("aleo") {
897 bail!("Record name '{record_name}' can't contain the reserved keyword 'aleo'.");
898 }
899 }
900
901 let mut record_names_iter = record_names.iter();
903 let mut previous_record_name = record_names_iter.next();
904 for record_name in record_names_iter {
905 if let Some(previous) = previous_record_name {
906 if record_name.starts_with(previous) {
907 bail!("Record name '{previous}' can't be a prefix of record name '{record_name}'.");
908 }
909 }
910 previous_record_name = Some(record_name);
911 }
912
913 for record_entry_name in self.records.values().flat_map(|record_type| record_type.entries().keys()) {
915 if record_entry_name.to_string().contains("aleo") {
916 bail!("Record entry name '{record_entry_name}' can't contain the reserved keyword 'aleo'.");
917 }
918 }
919
920 Ok(())
921 }
922
923 pub fn check_external_calls_to_credits_upgrade(&self) -> Result<()> {
925 cfg_iter!(self.functions()).flat_map(|(_, function)| function.instructions()).try_for_each(|instruction| {
927 if let Some(CallOperator::Locator(locator)) = instruction.call_operator() {
928 if locator.to_string() == "credits.aleo/upgrade" {
930 bail!("External call to restricted locator '{locator}'")
931 }
932 }
933 Ok(())
934 })?;
935 Ok(())
936 }
937
938 #[inline]
942 pub fn contains_v9_syntax(&self) -> bool {
943 if self.contains_constructor() {
945 return true;
946 }
947 for function in self.functions().values() {
950 if let Some(finalize_logic) = function.finalize_logic() {
952 for command in finalize_logic.commands() {
954 for operand in command.operands() {
955 if matches!(operand, Operand::Checksum(_) | Operand::Edition(_) | Operand::ProgramOwner(_)) {
956 return true;
957 }
958 }
959 }
960 }
961 }
962 false
964 }
965
966 pub fn contains_external_struct(&self) -> bool {
971 self.mappings.values().any(|mapping| mapping.contains_external_struct())
972 || self
973 .structs
974 .values()
975 .flat_map(|struct_| struct_.members().values())
976 .any(|plaintext_type| plaintext_type.contains_external_struct())
977 || self
978 .records
979 .values()
980 .flat_map(|record| record.entries().values())
981 .any(|entry| entry.plaintext_type().contains_external_struct())
982 || self.closures.values().any(|closure| closure.contains_external_struct())
983 || self.functions.values().any(|function| function.contains_external_struct())
984 || self.constructor.iter().any(|constructor| constructor.contains_external_struct())
985 }
986
987 pub fn violates_pre_v13_external_record_and_future_rules<F0, F1, F2, F3>(
999 &self,
1000 get_external_record: &F0,
1001 get_external_function: &F1,
1002 get_external_future: &F2,
1003 is_local_struct: &F3,
1004 ) -> bool
1005 where
1006 F0: Fn(&Locator<N>) -> Result<RecordType<N>>,
1007 F1: Fn(&Locator<N>) -> Result<FunctionCore<N>>,
1008 F2: Fn(&Locator<N>) -> Result<FinalizeCore<N>>,
1009 F3: Fn(&Identifier<N>) -> bool,
1010 {
1011 fn plaintext_uses_nonlocal_struct<N: Network>(
1013 ty: &PlaintextType<N>,
1014 is_local_struct: &impl Fn(&Identifier<N>) -> bool,
1015 ) -> bool {
1016 match ty {
1017 PlaintextType::Struct(name) => !is_local_struct(name),
1018 PlaintextType::Array(array_type) => {
1019 plaintext_uses_nonlocal_struct(array_type.base_element_type(), is_local_struct)
1020 }
1021 _ => false,
1022 }
1023 }
1024
1025 let record_uses_nonlocal_struct = |record: &RecordType<N>| {
1027 record.entries().iter().any(|(_, member)| match member {
1028 EntryType::Constant(ty) | EntryType::Private(ty) | EntryType::Public(ty) => {
1029 plaintext_uses_nonlocal_struct(ty, is_local_struct)
1030 }
1031 })
1032 };
1033
1034 for function in self.functions.values() {
1035 for input in function.inputs() {
1037 let ValueType::ExternalRecord(locator) = input.value_type() else {
1038 continue;
1039 };
1040 let Ok(record) = get_external_record(locator) else {
1041 continue;
1042 };
1043 if record_uses_nonlocal_struct(&record) {
1044 return true;
1045 }
1046 }
1047
1048 for instruction in function.instructions() {
1050 let Instruction::Call(call) = instruction else { continue };
1051 let CallOperator::Locator(locator) = call.operator() else { continue };
1052 let Ok(external_function) = get_external_function(locator) else {
1053 continue;
1054 };
1055
1056 for output in external_function.outputs() {
1059 match output.value_type() {
1060 ValueType::Record(identifier) => {
1061 let locator = Locator::new(*locator.program_id(), *identifier);
1062 let Ok(record) = get_external_record(&locator) else {
1063 continue;
1064 };
1065 if record_uses_nonlocal_struct(&record) {
1066 return true;
1067 }
1068 }
1069
1070 ValueType::Future(loc) => {
1071 let Ok(future) = get_external_future(loc) else {
1072 continue;
1073 };
1074 for input in future.input_types() {
1075 let FinalizeType::Plaintext(ty) = input else {
1076 continue;
1077 };
1078
1079 if plaintext_uses_nonlocal_struct(&ty, is_local_struct) {
1085 return true;
1086 }
1087 }
1088 }
1089
1090 _ => {}
1091 }
1092 }
1093 }
1094 }
1095
1096 false
1097 }
1098
1099 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
1101 self.mappings.values().any(|mapping| mapping.exceeds_max_array_size(max_array_size))
1102 || self.structs.values().any(|struct_type| struct_type.exceeds_max_array_size(max_array_size))
1103 || self.records.values().any(|record_type| record_type.exceeds_max_array_size(max_array_size))
1104 || self.closures.values().any(|closure| closure.exceeds_max_array_size(max_array_size))
1105 || self.functions.values().any(|function| function.exceeds_max_array_size(max_array_size))
1106 || self.constructor.iter().any(|constructor| constructor.exceeds_max_array_size(max_array_size))
1107 }
1108
1109 #[inline]
1115 pub fn contains_v11_syntax(&self) -> bool {
1116 const V10_MAX_ARRAY_ELEMENTS: u32 = 32;
1118
1119 let has_op = |opcode: &str| {
1123 opcode.starts_with("ecdsa.verify")
1124 || opcode.starts_with("serialize")
1125 || opcode.starts_with("deserialize")
1126 || opcode.ends_with(".raw")
1127 || opcode.ends_with(".native")
1128 };
1129
1130 let function_contains = cfg_iter!(self.functions())
1132 .flat_map(|(_, function)| function.instructions())
1133 .any(|instruction| has_op(*instruction.opcode()));
1134
1135 let closure_contains = cfg_iter!(self.closures())
1137 .flat_map(|(_, closure)| closure.instructions())
1138 .any(|instruction| has_op(*instruction.opcode()));
1139
1140 let command_contains = cfg_iter!(self.functions())
1142 .flat_map(|(_, function)| function.finalize_logic().map(|finalize| finalize.commands()))
1143 .flatten()
1144 .chain(cfg_iter!(self.constructor).flat_map(|constructor| constructor.commands()))
1145 .any(|command| matches!(command, Command::Instruction(instruction) if has_op(*instruction.opcode())));
1146
1147 let array_size_exceeds = self.exceeds_max_array_size(V10_MAX_ARRAY_ELEMENTS);
1149
1150 function_contains || closure_contains || command_contains || array_size_exceeds
1151 }
1152
1153 #[inline]
1157 pub fn contains_v12_syntax(&self) -> bool {
1158 cfg_iter!(self.functions()).any(|(_, function)| {
1161 function.finalize_logic().is_some_and(|finalize_logic| {
1162 cfg_iter!(finalize_logic.commands()).any(|command| {
1163 cfg_iter!(command.operands()).any(|operand| matches!(operand, Operand::BlockTimestamp))
1164 })
1165 })
1166 })
1167 }
1168
1169 #[inline]
1173 pub fn contains_string_type(&self) -> bool {
1174 self.mappings.values().any(|mapping| mapping.contains_string_type())
1175 || self.structs.values().any(|struct_type| struct_type.contains_string_type())
1176 || self.records.values().any(|record_type| record_type.contains_string_type())
1177 || self.closures.values().any(|closure| closure.contains_string_type())
1178 || self.functions.values().any(|function| function.contains_string_type())
1179 || self.constructor.iter().any(|constructor| constructor.contains_string_type())
1180 }
1181}
1182
1183impl<N: Network> TypeName for ProgramCore<N> {
1184 #[inline]
1186 fn type_name() -> &'static str {
1187 "program"
1188 }
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193 use super::*;
1194 use console::{
1195 network::MainnetV0,
1196 program::{Locator, ValueType},
1197 };
1198
1199 type CurrentNetwork = MainnetV0;
1200
1201 #[test]
1202 fn test_program_mapping() -> Result<()> {
1203 let mapping = Mapping::<CurrentNetwork>::from_str(
1205 r"
1206mapping message:
1207 key as field.public;
1208 value as field.public;",
1209 )?;
1210
1211 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {mapping}"))?;
1213 assert!(program.contains_mapping(&Identifier::from_str("message")?));
1215 assert_eq!(mapping.to_string(), program.get_mapping(&Identifier::from_str("message")?)?.to_string());
1217
1218 Ok(())
1219 }
1220
1221 #[test]
1222 fn test_program_struct() -> Result<()> {
1223 let struct_ = StructType::<CurrentNetwork>::from_str(
1225 r"
1226struct message:
1227 first as field;
1228 second as field;",
1229 )?;
1230
1231 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {struct_}"))?;
1233 assert!(program.contains_struct(&Identifier::from_str("message")?));
1235 assert_eq!(&struct_, program.get_struct(&Identifier::from_str("message")?)?);
1237
1238 Ok(())
1239 }
1240
1241 #[test]
1242 fn test_program_record() -> Result<()> {
1243 let record = RecordType::<CurrentNetwork>::from_str(
1245 r"
1246record foo:
1247 owner as address.private;
1248 first as field.private;
1249 second as field.public;",
1250 )?;
1251
1252 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {record}"))?;
1254 assert!(program.contains_record(&Identifier::from_str("foo")?));
1256 assert_eq!(&record, program.get_record(&Identifier::from_str("foo")?)?);
1258
1259 Ok(())
1260 }
1261
1262 #[test]
1263 fn test_program_function() -> Result<()> {
1264 let function = Function::<CurrentNetwork>::from_str(
1266 r"
1267function compute:
1268 input r0 as field.public;
1269 input r1 as field.private;
1270 add r0 r1 into r2;
1271 output r2 as field.private;",
1272 )?;
1273
1274 let program = Program::<CurrentNetwork>::from_str(&format!("program unknown.aleo; {function}"))?;
1276 assert!(program.contains_function(&Identifier::from_str("compute")?));
1278 assert_eq!(function, program.get_function(&Identifier::from_str("compute")?)?);
1280
1281 Ok(())
1282 }
1283
1284 #[test]
1285 fn test_program_import() -> Result<()> {
1286 let program = Program::<CurrentNetwork>::from_str(
1288 r"
1289import eth.aleo;
1290import usdc.aleo;
1291
1292program swap.aleo;
1293
1294// The `swap` function transfers ownership of the record
1295// for token A to the record owner of token B, and vice-versa.
1296function swap:
1297 // Input the record for token A.
1298 input r0 as eth.aleo/eth.record;
1299 // Input the record for token B.
1300 input r1 as usdc.aleo/usdc.record;
1301
1302 // Send the record for token A to the owner of token B.
1303 call eth.aleo/transfer r0 r1.owner r0.amount into r2 r3;
1304
1305 // Send the record for token B to the owner of token A.
1306 call usdc.aleo/transfer r1 r0.owner r1.amount into r4 r5;
1307
1308 // Output the new record for token A.
1309 output r2 as eth.aleo/eth.record;
1310 // Output the new record for token B.
1311 output r4 as usdc.aleo/usdc.record;
1312 ",
1313 )
1314 .unwrap();
1315
1316 assert!(program.contains_import(&ProgramID::from_str("eth.aleo")?));
1318 assert!(program.contains_import(&ProgramID::from_str("usdc.aleo")?));
1319
1320 let function = program.get_function(&Identifier::from_str("swap")?)?;
1322
1323 assert_eq!(function.inputs().len(), 2);
1325 assert_eq!(function.input_types().len(), 2);
1326
1327 let expected_input_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1329 let expected_input_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1330
1331 assert_eq!(function.input_types()[0], expected_input_type_1);
1333 assert_eq!(function.input_types()[1], expected_input_type_2);
1334
1335 assert_eq!(function.input_types()[0].variant(), expected_input_type_1.variant());
1337 assert_eq!(function.input_types()[1].variant(), expected_input_type_2.variant());
1338
1339 assert_eq!(function.instructions().len(), 2);
1341
1342 assert_eq!(function.instructions()[0].opcode(), Opcode::Call);
1344 assert_eq!(function.instructions()[1].opcode(), Opcode::Call);
1345
1346 assert_eq!(function.outputs().len(), 2);
1348 assert_eq!(function.output_types().len(), 2);
1349
1350 let expected_output_type_1 = ValueType::ExternalRecord(Locator::from_str("eth.aleo/eth")?);
1352 let expected_output_type_2 = ValueType::ExternalRecord(Locator::from_str("usdc.aleo/usdc")?);
1353
1354 assert_eq!(function.output_types()[0], expected_output_type_1);
1356 assert_eq!(function.output_types()[1], expected_output_type_2);
1357
1358 assert_eq!(function.output_types()[0].variant(), expected_output_type_1.variant());
1360 assert_eq!(function.output_types()[1].variant(), expected_output_type_2.variant());
1361
1362 Ok(())
1363 }
1364
1365 #[test]
1366 fn test_program_with_constructor() {
1367 let program_string = r"import credits.aleo;
1369
1370program good_constructor.aleo;
1371
1372constructor:
1373 assert.eq edition 0u16;
1374 assert.eq credits.aleo/edition 0u16;
1375 assert.neq checksum 0field;
1376 assert.eq credits.aleo/checksum 6192738754253668739186185034243585975029374333074931926190215457304721124008field;
1377 set 1u8 into data[0u8];
1378
1379mapping data:
1380 key as u8.public;
1381 value as u8.public;
1382
1383function dummy:
1384
1385function check:
1386 async check into r0;
1387 output r0 as good_constructor.aleo/check.future;
1388
1389finalize check:
1390 get data[0u8] into r0;
1391 assert.eq r0 1u8;
1392";
1393 let program = Program::<CurrentNetwork>::from_str(program_string).unwrap();
1394
1395 let serialized = program.to_string();
1397 let deserialized = Program::<CurrentNetwork>::from_str(&serialized).unwrap();
1398 assert_eq!(program, deserialized);
1399
1400 let serialized = program.to_bytes_le().unwrap();
1401 let deserialized = Program::<CurrentNetwork>::from_bytes_le(&serialized).unwrap();
1402 assert_eq!(program, deserialized);
1403
1404 let display = format!("{program}");
1406 assert_eq!(display, program_string);
1407
1408 assert!(program.contains_constructor());
1410 assert_eq!(program.constructor().unwrap().commands().len(), 5);
1411 }
1412
1413 #[test]
1414 fn test_program_equality_and_checksum() {
1415 fn run_test(program1: &str, program2: &str, expected_equal: bool) {
1416 println!("Comparing programs:\n{program1}\n{program2}");
1417 let program1 = Program::<CurrentNetwork>::from_str(program1).unwrap();
1418 let program2 = Program::<CurrentNetwork>::from_str(program2).unwrap();
1419 assert_eq!(program1 == program2, expected_equal);
1420 assert_eq!(program1.to_checksum() == program2.to_checksum(), expected_equal);
1421 }
1422
1423 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function dummy: ", true);
1425
1426 run_test(r"program test.aleo; function dummy: ", r"program test.aleo; function bummy: ", false);
1428
1429 run_test(
1431 r"program test.aleo; function dummy: ",
1432 r"program test.aleo; constructor: assert.eq true true; function dummy: ",
1433 false,
1434 );
1435
1436 run_test(
1438 r"program test.aleo; struct foo: data as u8; function dummy:",
1439 r"program test.aleo; function dummy: struct foo: data as u8;",
1440 false,
1441 );
1442 }
1443}