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