diff --git a/compiler/compiler/tests/compile.rs b/compiler/compiler/tests/compile.rs index be8b448c40..682a726941 100644 --- a/compiler/compiler/tests/compile.rs +++ b/compiler/compiler/tests/compile.rs @@ -140,8 +140,9 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result(&bytecode).map_err(|err| err.into()))?; + let stub = handler.extend_if_error( + disassemble_from_str::(&program_name, &bytecode).map_err(|err| err.into()), + )?; import_stubs.insert(Symbol::intern(&program_name), stub); // Hash the ast files. diff --git a/compiler/compiler/tests/execute.rs b/compiler/compiler/tests/execute.rs index 2b1f9ed19a..2b77af8a9f 100644 --- a/compiler/compiler/tests/execute.rs +++ b/compiler/compiler/tests/execute.rs @@ -184,8 +184,9 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result(&bytecode).map_err(|err| err.into()))?; + let stub = handler.extend_if_error( + disassemble_from_str::(&program_name, &bytecode).map_err(|err| err.into()), + )?; import_stubs.insert(Symbol::intern(&program_name), stub); // Hash the ast files. diff --git a/errors/src/errors/utils/util_errors.rs b/errors/src/errors/utils/util_errors.rs index ee94964810..dbab5af28d 100644 --- a/errors/src/errors/utils/util_errors.rs +++ b/errors/src/errors/utils/util_errors.rs @@ -49,8 +49,8 @@ create_messages!( @formatted snarkvm_parsing_error { - args: (), - msg: "SnarkVM failure to parse `.aleo` program".to_string(), + args: (name: impl Display), + msg: format!("Failed to parse the source file for `{name}.aleo` into a valid Aleo program."), help: None, } diff --git a/leo/cli/commands/query/mod.rs b/leo/cli/commands/query/mod.rs index 2ca7645165..b50ac2e88e 100644 --- a/leo/cli/commands/query/mod.rs +++ b/leo/cli/commands/query/mod.rs @@ -15,6 +15,7 @@ // along with the Leo library. If not, see . use super::*; +use snarkvm::prelude::{MainnetV0, TestnetV0}; mod block; use block::Block; @@ -41,6 +42,7 @@ mod utils; use utils::*; use leo_errors::UtilError; +use leo_retriever::{fetch_from_network, verify_valid_program, NetworkName}; /// Query live data from the Aleo network. #[derive(Parser, Debug)] @@ -72,51 +74,63 @@ impl Command for Query { } fn apply(self, context: Context, _: Self::Input) -> Result { - let recursive = context.recursive; - let output = match self.command { - QueryCommands::Block { command } => command.apply(context, ())?, - QueryCommands::Transaction { command } => command.apply(context, ())?, - QueryCommands::Program { command } => command.apply(context, ())?, - QueryCommands::Stateroot { command } => command.apply(context, ())?, - QueryCommands::Committee { command } => command.apply(context, ())?, - QueryCommands::Mempool { command } => { - if self.endpoint == "https://api.explorer.aleo.org/v1" { - tracing::warn!( - "⚠️ `leo query mempool` is only valid when using a custom endpoint. Specify one using `--endpoint`." - ); - } - command.apply(context, ())? - } - QueryCommands::Peers { command } => { - if self.endpoint == "https://api.explorer.aleo.org/v1" { - tracing::warn!( - "⚠️ `leo query peers` is only valid when using a custom endpoint. Specify one using `--endpoint`." - ); - } - command.apply(context, ())? - } - }; - - // Make GET request to retrieve on-chain state. - let url = format!("{}/{}/{}", self.endpoint, self.network, output); - let response = ureq::get(&url.clone()) - .set(&format!("X-Aleo-Leo-{}", env!("CARGO_PKG_VERSION")), "true") - .call() - .map_err(|err| UtilError::failed_to_retrieve_from_endpoint(err, Default::default()))?; - if response.status() == 200 { - // Unescape the newlines. - let result = response.into_string().unwrap().replace("\\n", "\n").replace('\"', ""); - if !recursive { - tracing::info!("✅ Successfully retrieved data from '{url}'.\n"); - println!("{}\n", result); - } - Ok(result) - } else { - Err(UtilError::network_error(url, response.status(), Default::default()).into()) + // Parse the network. + let network = NetworkName::try_from(self.network.as_str())?; + match network { + NetworkName::MainnetV0 => handle_query::(self, context), + NetworkName::TestnetV0 => handle_query::(self, context), } } } +// A helper function to handle the `query` command. +fn handle_query(query: Query, context: Context) -> Result<::Output> { + let recursive = context.recursive; + let (program, output) = match query.command { + QueryCommands::Block { command } => (None, command.apply(context, ())?), + QueryCommands::Transaction { command } => (None, command.apply(context, ())?), + QueryCommands::Program { command } => { + // Check if querying for program source code. + let program = + if command.mappings || command.mapping_value.is_some() { None } else { Some(command.name.clone()) }; + (program, command.apply(context, ())?) + } + QueryCommands::Stateroot { command } => (None, command.apply(context, ())?), + QueryCommands::Committee { command } => (None, command.apply(context, ())?), + QueryCommands::Mempool { command } => { + if query.endpoint == "https://api.explorer.aleo.org/v1" { + tracing::warn!( + "⚠️ `leo query mempool` is only valid when using a custom endpoint. Specify one using `--endpoint`." + ); + } + (None, command.apply(context, ())?) + } + QueryCommands::Peers { command } => { + if query.endpoint == "https://api.explorer.aleo.org/v1" { + tracing::warn!( + "⚠️ `leo query peers` is only valid when using a custom endpoint. Specify one using `--endpoint`." + ); + } + (None, command.apply(context, ())?) + } + }; + + // Make GET request to retrieve on-chain state. + let url = format!("{}/{}/{output}", query.endpoint, query.network); + let result = fetch_from_network(&url)?; + if !recursive { + tracing::info!("✅ Successfully retrieved data from '{url}'.\n"); + println!("{}\n", result); + } + + // Verify that the source file parses into a valid Aleo program. + if let Some(name) = program { + verify_valid_program::(&name, &result)?; + } + + Ok(result) +} + #[derive(Parser, Debug)] pub enum QueryCommands { #[clap(about = "Query block information")] diff --git a/utils/disassembler/src/lib.rs b/utils/disassembler/src/lib.rs index d8d1294357..b316ded004 100644 --- a/utils/disassembler/src/lib.rs +++ b/utils/disassembler/src/lib.rs @@ -86,10 +86,10 @@ pub fn disassemble, Command: Comman } } -pub fn disassemble_from_str(program: &str) -> Result { +pub fn disassemble_from_str(name: &str, program: &str) -> Result { match Program::::from_str(program) { Ok(p) => Ok(disassemble(p)), - Err(_) => Err(UtilError::snarkvm_parsing_error(Default::default())), + Err(_) => Err(UtilError::snarkvm_parsing_error(name, Default::default())), } } @@ -124,7 +124,8 @@ mod tests { create_session_if_not_set_then(|_| { let program_from_file = fs::read_to_string("../tmp/.aleo/registry/mainnet/zk_bitwise_stack_v0_0_2.aleo").unwrap(); - let _program = disassemble_from_str::(&program_from_file).unwrap(); + let _program = + disassemble_from_str::("zk_bitwise_stack_v0_0_2", &program_from_file).unwrap(); }); } } diff --git a/utils/retriever/src/retriever/mod.rs b/utils/retriever/src/retriever/mod.rs index 6996b35305..4c18e8337f 100644 --- a/utils/retriever/src/retriever/mod.rs +++ b/utils/retriever/src/retriever/mod.rs @@ -22,7 +22,7 @@ use leo_errors::UtilError; use leo_passes::{common::DiGraph, DiGraphError}; use leo_span::Symbol; -use snarkvm::prelude::Network; +use snarkvm::prelude::{Network, Program}; use indexmap::{IndexMap, IndexSet}; use std::{ @@ -31,6 +31,7 @@ use std::{ io::Read, marker::PhantomData, path::{Path, PathBuf}, + str::FromStr, }; // Retriever is responsible for retrieving external programs @@ -309,7 +310,7 @@ impl Retriever { })?; // Cache the disassembled stub - let stub: Stub = disassemble_from_str::(&content)?; + let stub: Stub = disassemble_from_str::(&name.to_string(), &content)?; if cur_context.add_stub(stub.clone()) { Err(UtilError::duplicate_dependency_name_error(stub.stub_id.name.name, Default::default()))?; } @@ -439,7 +440,7 @@ fn retrieve_from_network( // Check if the file is already cached in `~/.aleo/registry/{network}/{program}` let move_to_path = home_path.join(network.to_string()); let path = move_to_path.join(name.clone()); - let mut file_str: String; + let file_str: String; if !path.exists() { // Create directories along the way if they don't exist std::fs::create_dir_all(&move_to_path).map_err(|err| { @@ -450,13 +451,10 @@ fn retrieve_from_network( ) })?; - // Construct the endpoint for the network. - let endpoint = format!("{endpoint}/{network}"); - // Fetch from network - println!("Retrieving {name} from {endpoint}."); - file_str = fetch_from_network(&endpoint, name)?; - file_str = file_str.replace("\\n", "\n").replace('\"', ""); + println!("Retrieving {name} from {endpoint} on {network}."); + file_str = fetch_from_network(&format!("{endpoint}/{network}/program/{}", &name))?; + verify_valid_program::(name, &file_str)?; println!("Successfully retrieved {} from {:?}!", name, endpoint); // Write file to cache @@ -498,7 +496,7 @@ fn retrieve_from_network( })?; // Disassemble into Stub - let stub: Stub = disassemble_from_str::(&file_str)?; + let stub: Stub = disassemble_from_str::(name, &file_str)?; // Create entry for leo.lock Ok(( @@ -518,14 +516,23 @@ fn retrieve_from_network( )) } -fn fetch_from_network(endpoint: &String, program: &String) -> Result { - let url = format!("{}/program/{}", endpoint, program); - let response = ureq::get(&url.clone()) +// Fetch the given endpoint url and return the sanitized response. +pub fn fetch_from_network(url: &str) -> Result { + let response = ureq::get(url) + .set(&format!("X-Aleo-Leo-{}", env!("CARGO_PKG_VERSION")), "true") .call() .map_err(|err| UtilError::failed_to_retrieve_from_endpoint(err, Default::default()))?; if response.status() == 200 { - Ok(response.into_string().unwrap()) + Ok(response.into_string().unwrap().replace("\\n", "\n").replace('\"', "")) } else { Err(UtilError::network_error(url, response.status(), Default::default())) } } + +// Verify that a fetched program is valid aleo instructions. +pub fn verify_valid_program(name: &str, program: &str) -> Result<(), UtilError> { + match Program::::from_str(program) { + Ok(_) => Ok(()), + Err(_) => Err(UtilError::snarkvm_parsing_error(name, Default::default())), + } +}