mod.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 mod build; 17 mod clean; 18 mod deploy; 19 mod execute; 20 mod is_build_required; 21 mod run; 22 23 pub use deploy::{DeployRequest, DeployResponse}; 24 25 use crate::{ 26 algorithms::snark::varuna::VarunaVersion, 27 console::{ 28 account::PrivateKey, 29 network::{ConsensusVersion, Network}, 30 program::{Identifier, Locator, ProgramID, Response, Value}, 31 }, 32 file::{AVMFile, AleoFile, Manifest, ProverFile, README, VerifierFile}, 33 ledger::{ 34 block::Execution, 35 query::{Query, QueryTrait}, 36 store::helpers::memory::BlockMemory, 37 }, 38 prelude::{Deserialize, Deserializer, Serialize, SerializeStruct, Serializer}, 39 synthesizer::{ 40 process::{Assignments, CallMetrics, CallStack, Process}, 41 program::{CallOperator, Instruction, Program, StackTrait}, 42 }, 43 utilities::dev_println, 44 }; 45 46 use anyhow::{Result, bail, ensure}; 47 use core::str::FromStr; 48 use rand::{CryptoRng, Rng}; 49 use std::path::{Path, PathBuf}; 50 51 pub struct Package<N: Network> { 52 /// The program ID. 53 program_id: ProgramID<N>, 54 /// The directory path. 55 directory: PathBuf, 56 /// The manifest file. 57 manifest_file: Manifest<N>, 58 /// The program file. 59 program_file: AleoFile<N>, 60 } 61 62 impl<N: Network> Package<N> { 63 /// Creates a new package, at the given directory with the given program name. 64 pub fn create(directory: &Path, program_id: &ProgramID<N>) -> Result<Self> { 65 // Ensure the directory path does not exist. 66 ensure!(!directory.exists(), "The program directory already exists: {}", directory.display()); 67 // Ensure the program name is valid. 68 ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}"); 69 70 // Create the program directory. 71 if !directory.exists() { 72 std::fs::create_dir_all(directory)?; 73 } 74 75 // Create the manifest file. 76 let manifest_file = Manifest::create(directory, program_id)?; 77 // Create the program file. 78 let program_file = AleoFile::create(directory, program_id, true)?; 79 // Create the README file. 80 let _readme_file = README::create::<N>(directory, program_id)?; 81 82 Ok(Self { program_id: *program_id, directory: directory.to_path_buf(), manifest_file, program_file }) 83 } 84 85 /// Opens the package at the given directory with the given program name. 86 pub fn open(directory: &Path) -> Result<Self> { 87 // Ensure the directory path exists. 88 ensure!(directory.exists(), "The program directory does not exist: {}", directory.display()); 89 // Ensure the manifest file exists. 90 ensure!( 91 Manifest::<N>::exists_at(directory), 92 "Missing '{}' at '{}'", 93 Manifest::<N>::file_name(), 94 directory.display() 95 ); 96 // Ensure the main program file exists. 97 ensure!( 98 AleoFile::<N>::main_exists_at(directory), 99 "Missing '{}' at '{}'", 100 AleoFile::<N>::main_file_name(), 101 directory.display() 102 ); 103 104 // Open the manifest file. 105 let manifest_file = Manifest::open(directory)?; 106 // Retrieve the program ID. 107 let program_id = *manifest_file.program_id(); 108 // Ensure the program name is valid. 109 ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}"); 110 111 // Open the program file. 112 let program_file = AleoFile::open(directory, &program_id, true)?; 113 114 Ok(Self { program_id, directory: directory.to_path_buf(), manifest_file, program_file }) 115 } 116 117 /// Returns the program ID. 118 pub const fn program_id(&self) -> &ProgramID<N> { 119 &self.program_id 120 } 121 122 /// Returns the directory path. 123 pub const fn directory(&self) -> &PathBuf { 124 &self.directory 125 } 126 127 /// Returns the manifest file. 128 pub const fn manifest_file(&self) -> &Manifest<N> { 129 &self.manifest_file 130 } 131 132 /// Returns the program file. 133 pub const fn program_file(&self) -> &AleoFile<N> { 134 &self.program_file 135 } 136 137 /// Returns the program. 138 pub const fn program(&self) -> &Program<N> { 139 self.program_file.program() 140 } 141 142 /// Returns the build directory. 143 pub fn build_directory(&self) -> PathBuf { 144 self.directory.join("build") 145 } 146 147 /// Returns the imports directory. 148 pub fn imports_directory(&self) -> PathBuf { 149 self.directory.join("imports") 150 } 151 152 /// Returns a new process for the package. 153 pub fn get_process(&self) -> Result<Process<N>> { 154 // Load the default process. 155 let mut process = Process::load()?; 156 // Get the imported programs. 157 let imports_directory = self.imports_directory(); 158 let mut programs = self 159 .program() 160 .imports() 161 .keys() 162 .map(|program_id| { 163 // TODO (howardwu): Add the following checks: 164 // 1) the imported program ID exists *on-chain* (for the given network) 165 // 2) the AVM bytecode of the imported program matches the AVM bytecode of the program *on-chain* 166 // 3) consensus performs the exact same checks (in `verify_deployment`) 167 // Open the Aleo program file. 168 let is_main = false; 169 let import_program_file = AleoFile::open(&imports_directory, program_id, is_main)?; 170 // Get the program. 171 Ok(import_program_file.program().clone()) 172 }) 173 .collect::<Result<Vec<_>>>()?; 174 // Add the main program. 175 programs.push(self.program().clone()); 176 177 // Get the editions for the programs, if specified in the manifest. 178 let programs_and_editions = programs 179 .into_iter() 180 .map(|program| { 181 // Get the program ID. 182 let program_id = program.id(); 183 // Get the edition, if specified. 184 if let Some(edition) = self.manifest_file.editions().get(program_id) { 185 (program, *edition) 186 } else { 187 dev_println!( 188 " Could not find an edition for '{}' in the manifest, using edition 0...", 189 program_id.to_string() 190 ); 191 (program, 0) 192 } 193 }) 194 .collect::<Vec<_>>(); 195 196 // Load the programs. 197 process.add_programs_with_editions(&programs_and_editions)?; 198 199 Ok(process) 200 } 201 } 202 203 #[cfg(test)] 204 pub(crate) mod test_helpers { 205 use super::*; 206 use alphavm_console::{account::Address, network::MainnetV0, prelude::TestRng}; 207 208 use std::{fs::File, io::Write}; 209 210 use anyhow::anyhow; 211 212 type CurrentNetwork = MainnetV0; 213 214 fn temp_dir() -> PathBuf { 215 tempfile::tempdir().expect("Failed to open temporary directory").keep() 216 } 217 218 fn env_template() -> String { 219 r#" 220 NETWORK=mainnet 221 PRIVATE_KEY={{PASTE_YOUR_PRIVATE_KEY_HERE}} 222 "# 223 .to_string() 224 } 225 226 /// Loads the environment variables from the .env file. 227 fn dotenv_load() -> Result<()> { 228 // Load environment variables from .env file. 229 // Fails if .env file not found, not readable or invalid. 230 dotenvy::dotenv().map_err(|_| { 231 anyhow!( 232 "Missing a '.env' file. Create the '.env' file in your package's root directory with the following:\n\n{}\n", 233 env_template() 234 ) 235 })?; 236 Ok(()) 237 } 238 239 /// Returns the private key from the environment. 240 fn dotenv_private_key() -> Result<PrivateKey<CurrentNetwork>> { 241 if cfg!(test) { 242 let rng = &mut crate::utilities::TestRng::fixed(123456789); 243 PrivateKey::<CurrentNetwork>::new(rng) 244 } else { 245 use std::str::FromStr; 246 dotenv_load()?; 247 // Load the private key from the environment. 248 let private_key = dotenvy::var("PRIVATE_KEY").map_err(|e| anyhow!("Missing PRIVATE_KEY - {e}"))?; 249 // Parse the private key. 250 PrivateKey::<CurrentNetwork>::from_str(&private_key) 251 } 252 } 253 254 /// Samples a (temporary) package containing a `token.alpha` program. 255 pub(crate) fn sample_token_package() -> (PathBuf, Package<CurrentNetwork>) { 256 // Initialize the program. 257 let program = Program::<CurrentNetwork>::from_str( 258 " 259 program token.alpha; 260 261 record token: 262 owner as address.private; 263 amount as u64.private; 264 265 function initialize: 266 input r0 as address.private; 267 input r1 as u64.private; 268 cast r0 r1 into r2 as token.record; 269 output r2 as token.record; 270 271 function transfer: 272 input r0 as token.record; 273 input r1 as address.private; 274 input r2 as u64.private; 275 sub r0.amount r2 into r3; 276 cast r1 r2 into r4 as token.record; 277 cast r0.owner r3 into r5 as token.record; 278 output r4 as token.record; 279 output r5 as token.record;", 280 ) 281 .unwrap(); 282 283 // Sample the package using the program. 284 sample_package_with_program_and_imports(&program, &[]) 285 } 286 287 /// Samples a (temporary) package containing a `wallet.alpha` program which imports `token.alpha`. 288 pub(crate) fn sample_wallet_package() -> (PathBuf, Package<CurrentNetwork>) { 289 // Initialize the imported program. 290 let imported_program = Program::<CurrentNetwork>::from_str( 291 " 292 program token.alpha; 293 294 record token: 295 owner as address.private; 296 amount as u64.private; 297 298 function initialize: 299 input r0 as address.private; 300 input r1 as u64.private; 301 cast r0 r1 into r2 as token.record; 302 output r2 as token.record; 303 304 function transfer: 305 input r0 as token.record; 306 input r1 as address.private; 307 input r2 as u64.private; 308 sub r0.amount r2 into r3; 309 cast r1 r2 into r4 as token.record; 310 cast r0.owner r3 into r5 as token.record; 311 output r4 as token.record; 312 output r5 as token.record;", 313 ) 314 .unwrap(); 315 316 // Initialize the main program. 317 let main_program = Program::<CurrentNetwork>::from_str( 318 " 319 import token.alpha; 320 321 program wallet.alpha; 322 323 function transfer: 324 input r0 as token.alpha/token.record; 325 input r1 as address.private; 326 input r2 as u64.private; 327 call token.alpha/transfer r0 r1 r2 into r3 r4; 328 output r3 as token.alpha/token.record; 329 output r4 as token.alpha/token.record;", 330 ) 331 .unwrap(); 332 333 // Sample the package using the main program and imported program. 334 sample_package_with_program_and_imports(&main_program, &[imported_program]) 335 } 336 337 /// Samples a (temporary) package containing a `grandparent.alpha` program which imports `parent.alpha` which imports `child.alpha`. 338 pub(crate) fn sample_nested_package() -> (PathBuf, Package<CurrentNetwork>) { 339 // Initialize the child program. 340 let child_program = Program::<CurrentNetwork>::from_str( 341 " 342 program child.alpha; 343 344 record A: 345 owner as address.private; 346 val as u32.private; 347 348 function mint: 349 input r0 as address.private; 350 input r1 as u32.private; 351 cast r0 r1 into r2 as A.record; 352 output r2 as A.record;", 353 ) 354 .unwrap(); 355 356 // Initialize the parent program. 357 let parent_program = Program::<CurrentNetwork>::from_str( 358 " 359 import child.alpha; 360 361 program parent.alpha; 362 363 function wrapper_mint: 364 input r0 as address.private; 365 input r1 as u32.private; 366 call child.alpha/mint r0 r1 into r2; 367 output r2 as child.alpha/A.record;", 368 ) 369 .unwrap(); 370 371 // Initialize the grandparent program. 372 let grandparent_program = Program::<CurrentNetwork>::from_str( 373 " 374 import child.alpha; 375 import parent.alpha; 376 377 program grandparent.alpha; 378 379 function double_wrapper_mint: 380 input r0 as address.private; 381 input r1 as u32.private; 382 call parent.alpha/wrapper_mint r0 r1 into r2; 383 output r2 as child.alpha/A.record;", 384 ) 385 .unwrap(); 386 387 // Sample the package using the main program and imported program. 388 sample_package_with_program_and_imports(&grandparent_program, &[child_program, parent_program]) 389 } 390 391 /// Samples a (temporary) package containing a `transfer.alpha` program which imports `credits.alpha`. 392 pub(crate) fn sample_transfer_package() -> (PathBuf, Package<CurrentNetwork>) { 393 // Initialize the imported program. 394 let imported_program = Program::credits().unwrap(); 395 396 // Initialize the main program. 397 let main_program = Program::<CurrentNetwork>::from_str( 398 " 399 import credits.alpha; 400 401 program transfer.alpha; 402 403 function main: 404 input r0 as credits.alpha/credits.record; 405 input r1 as address.private; 406 input r2 as u64.private; 407 call credits.alpha/transfer_private r0 r1 r2 into r3 r4; 408 output r3 as credits.alpha/credits.record; 409 output r4 as credits.alpha/credits.record;", 410 ) 411 .unwrap(); 412 413 // Sample the package using the main program and imported program. 414 sample_package_with_program_and_imports(&main_program, &[imported_program]) 415 } 416 417 /// Samples a (temporary) package using a main program and imported programs. 418 pub(crate) fn sample_package_with_program_and_imports( 419 main_program: &Program<CurrentNetwork>, 420 imported_programs: &[Program<CurrentNetwork>], 421 ) -> (PathBuf, Package<CurrentNetwork>) { 422 // Initialize a temporary directory. 423 let directory = temp_dir(); 424 425 // If there are imports, create the imports directory. 426 if !imported_programs.is_empty() { 427 let imports_directory = directory.join("imports"); 428 std::fs::create_dir_all(&imports_directory).unwrap(); 429 430 // Add the imported programs. 431 for imported_program in imported_programs { 432 let imported_program_id = imported_program.id(); 433 434 // Write the imported program string to an imports file in the temporary directory. 435 let import_filepath = imports_directory.join(imported_program_id.to_string()); 436 let mut file = File::create(import_filepath).unwrap(); 437 file.write_all(imported_program.to_string().as_bytes()).unwrap(); 438 } 439 } 440 441 // Initialize the main program ID. 442 let main_program_id = main_program.id(); 443 444 // Write the program string to a file in the temporary directory. 445 let main_filepath = directory.join("main.alpha"); 446 let mut file = File::create(main_filepath).unwrap(); 447 file.write_all(main_program.to_string().as_bytes()).unwrap(); 448 449 // Create the manifest file. 450 let _manifest_file = Manifest::create(&directory, main_program_id).unwrap(); 451 452 // Open the package at the temporary directory. 453 let package = Package::<MainnetV0>::open(&directory).unwrap(); 454 assert_eq!(package.program_id(), main_program_id); 455 456 // Return the temporary directory and the package. 457 (directory, package) 458 } 459 460 /// Samples a candidate input to execute the sample package. 461 pub(crate) fn sample_package_run( 462 program_id: &ProgramID<CurrentNetwork>, 463 ) -> (PrivateKey<CurrentNetwork>, Identifier<CurrentNetwork>, Vec<Value<CurrentNetwork>>) { 464 // Initialize an RNG. 465 let rng = &mut TestRng::default(); 466 467 match program_id.to_string().as_str() { 468 "token.alpha" => { 469 // Sample a random private key. 470 let private_key = dotenv_private_key().unwrap(); 471 let caller = Address::try_from(&private_key).unwrap(); 472 473 // Initialize the function name. 474 let function_name = Identifier::from_str("initialize").unwrap(); 475 476 // Initialize the function inputs. 477 let r0 = Value::from_str(&caller.to_string()).unwrap(); 478 let r1 = Value::from_str("100u64").unwrap(); 479 480 (private_key, function_name, vec![r0, r1]) 481 } 482 "wallet.alpha" => { 483 // Initialize caller 0. 484 let caller0_private_key = dotenv_private_key().unwrap(); 485 let caller0 = Address::try_from(&caller0_private_key).unwrap(); 486 487 // Initialize caller 1. 488 let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap(); 489 let caller1 = Address::try_from(&caller1_private_key).unwrap(); 490 491 // Declare the function name. 492 let function_name = Identifier::from_str("transfer").unwrap(); 493 494 // Initialize the function inputs. 495 let r0 = Value::<CurrentNetwork>::from_str(&format!( 496 "{{ owner: {caller0}.private, amount: 100u64.private, _nonce: 0group.public }}" 497 )) 498 .unwrap(); 499 let r1 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap(); 500 let r2 = Value::<CurrentNetwork>::from_str("99u64").unwrap(); 501 502 (caller0_private_key, function_name, vec![r0, r1, r2]) 503 } 504 "grandparent.alpha" => { 505 // Initialize caller 0. 506 let caller0_private_key = dotenv_private_key().unwrap(); 507 508 // Initialize caller 1. 509 let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap(); 510 let caller1 = Address::try_from(&caller1_private_key).unwrap(); 511 512 // Declare the function name. 513 let function_name = Identifier::from_str("double_wrapper_mint").unwrap(); 514 515 // Initialize the function inputs. 516 let r0 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap(); 517 let r1 = Value::<CurrentNetwork>::from_str("1u32").unwrap(); 518 519 (caller0_private_key, function_name, vec![r0, r1]) 520 } 521 _ => panic!("Invalid program ID for sample package (while testing)"), 522 } 523 } 524 } 525 526 #[cfg(test)] 527 mod tests { 528 use super::*; 529 use crate::prelude::MainnetV0; 530 use alphavm_utilities::TestRng; 531 532 type CurrentAleo = alphavm_circuit::network::AleoV0; 533 type CurrentNetwork = MainnetV0; 534 535 #[test] 536 fn test_imports_directory() { 537 // Samples a new package at a temporary directory. 538 let (directory, package) = crate::package::test_helpers::sample_token_package(); 539 540 // Ensure the imports directory is correct. 541 assert_eq!(package.imports_directory(), directory.join("imports")); 542 // Ensure the imports directory does *not* exist, when the package does not contain imports. 543 assert!(!package.imports_directory().exists()); 544 545 // Proactively remove the temporary directory (to conserve space). 546 std::fs::remove_dir_all(directory).unwrap(); 547 } 548 549 #[test] 550 fn test_imports_directory_with_an_import() { 551 // Samples a new package with an import at a temporary directory. 552 let (directory, package) = crate::package::test_helpers::sample_wallet_package(); 553 554 // Ensure the imports directory is correct. 555 assert_eq!(package.imports_directory(), directory.join("imports")); 556 // Ensure the imports directory exists, as the package contains an import. 557 assert!(package.imports_directory().exists()); 558 559 // Proactively remove the temporary directory (to conserve space). 560 std::fs::remove_dir_all(directory).unwrap(); 561 } 562 563 #[test] 564 fn test_build_directory() { 565 // Samples a new package at a temporary directory. 566 let (directory, package) = crate::package::test_helpers::sample_token_package(); 567 568 // Ensure the build directory is correct. 569 assert_eq!(package.build_directory(), directory.join("build")); 570 // Ensure the build directory does *not* exist, when the package has not been built. 571 assert!(!package.build_directory().exists()); 572 573 // Proactively remove the temporary directory (to conserve space). 574 std::fs::remove_dir_all(directory).unwrap(); 575 } 576 577 #[test] 578 #[ignore] // TODO: Requires credits.alpha → credits.alpha migration 579 fn test_get_process() { 580 // Samples a new package at a temporary directory. 581 let (directory, package) = crate::package::test_helpers::sample_token_package(); 582 583 // Get the program process and check all instructions. 584 assert!(package.get_process().is_ok()); 585 586 // Proactively remove the temporary directory (to conserve space). 587 std::fs::remove_dir_all(directory).unwrap(); 588 } 589 590 #[test] 591 #[ignore] // TODO: Requires credits.alpha → credits.alpha migration 592 fn test_package_run_and_execute_match() { 593 // Initialize the program. 594 let program = Program::<CurrentNetwork>::from_str( 595 " 596 program foo.alpha; 597 598 function bar: 599 input r0 as boolean.private; 600 assert.eq r0 false;", 601 ) 602 .unwrap(); 603 604 // Samples a new package at a temporary directory. 605 let (directory, package) = crate::package::test_helpers::sample_package_with_program_and_imports(&program, &[]); 606 607 // Ensure the build directory does *not* exist. 608 assert!(!package.build_directory().exists()); 609 // Build the package. 610 package.build::<CurrentAleo>().unwrap(); 611 // Ensure the build directory exists. 612 assert!(package.build_directory().exists()); 613 614 // Initialize an RNG. 615 let rng = &mut TestRng::default(); 616 // Sample the function inputs. 617 let private_key = PrivateKey::new(rng).unwrap(); 618 let function_name = Identifier::from_str("bar").unwrap(); 619 let inputs = vec![Value::from_str("true").unwrap()]; 620 621 // Construct the endpoint. 622 let endpoint = "https://api.explorer.alpha.org/v1".to_string(); 623 624 // Run the program function. 625 let run_result = package.run::<CurrentAleo, _>(&private_key, function_name, &inputs, rng).ok(); 626 627 // Execute the program function. 628 let execute_result = 629 package.execute::<CurrentAleo, _>(endpoint, &private_key, function_name, &inputs, rng).ok(); 630 631 match (run_result, execute_result) { 632 // If both results are `None`, then they both failed. 633 (None, None) => {} 634 // If both results are `Some`, then check that the responses match. 635 (Some((run_response, _)), Some((execute_response, _, _))) => { 636 assert_eq!(run_response, execute_response); 637 } 638 // Otherwise, the results do not match. 639 _ => panic!("Run and execute results do not match"), 640 } 641 642 // Proactively remove the temporary directory (to conserve space). 643 std::fs::remove_dir_all(directory).unwrap(); 644 } 645 }