/ vm / package / mod.rs
mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  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, AlphaFile, 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: AlphaFile<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 = AlphaFile::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              AlphaFile::<N>::main_exists_at(directory),
 99              "Missing '{}' at '{}'",
100              AlphaFile::<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 = AlphaFile::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) -> &AlphaFile<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 Alpha program file.
168                  let is_main = false;
169                  let import_program_file = AlphaFile::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 CurrentAlpha = alphavm_circuit::network::AlphaV0;
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      fn test_get_process() {
579          // Samples a new package at a temporary directory.
580          let (directory, package) = crate::package::test_helpers::sample_token_package();
581  
582          // Get the program process and check all instructions.
583          assert!(package.get_process().is_ok());
584  
585          // Proactively remove the temporary directory (to conserve space).
586          std::fs::remove_dir_all(directory).unwrap();
587      }
588  
589      #[test]
590      fn test_package_run_and_execute_match() {
591          // Initialize the program.
592          let program = Program::<CurrentNetwork>::from_str(
593              "
594  program foo.alpha;
595  
596  function bar:
597      input r0 as boolean.private;
598      assert.eq r0 false;",
599          )
600          .unwrap();
601  
602          // Samples a new package at a temporary directory.
603          let (directory, package) = crate::package::test_helpers::sample_package_with_program_and_imports(&program, &[]);
604  
605          // Ensure the build directory does *not* exist.
606          assert!(!package.build_directory().exists());
607          // Build the package.
608          package.build::<CurrentAlpha>().unwrap();
609          // Ensure the build directory exists.
610          assert!(package.build_directory().exists());
611  
612          // Initialize an RNG.
613          let rng = &mut TestRng::default();
614          // Sample the function inputs.
615          let private_key = PrivateKey::new(rng).unwrap();
616          let function_name = Identifier::from_str("bar").unwrap();
617          let inputs = vec![Value::from_str("true").unwrap()];
618  
619          // Construct the endpoint.
620          let endpoint = "https://api.explorer.alpha.org/v1".to_string();
621  
622          // Run the program function.
623          let run_result = package.run::<CurrentAlpha, _>(&private_key, function_name, &inputs, rng).ok();
624  
625          // Execute the program function.
626          let execute_result =
627              package.execute::<CurrentAlpha, _>(endpoint, &private_key, function_name, &inputs, rng).ok();
628  
629          match (run_result, execute_result) {
630              // If both results are `None`, then they both failed.
631              (None, None) => {}
632              // If both results are `Some`, then check that the responses match.
633              (Some((run_response, _)), Some((execute_response, _, _))) => {
634                  assert_eq!(run_response, execute_response);
635              }
636              // Otherwise, the results do not match.
637              _ => panic!("Run and execute results do not match"),
638          }
639  
640          // Proactively remove the temporary directory (to conserve space).
641          std::fs::remove_dir_all(directory).unwrap();
642      }
643  }