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