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