diff --git a/compiler/compiler/tests/compile.rs b/compiler/compiler/tests/compile.rs index be8b448c40..f08f2dc14c 100644 --- a/compiler/compiler/tests/compile.rs +++ b/compiler/compiler/tests/compile.rs @@ -141,7 +141,7 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result(&bytecode).map_err(|err| err.into()))?; + 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..4a933ee6e9 100644 --- a/compiler/compiler/tests/execute.rs +++ b/compiler/compiler/tests/execute.rs @@ -185,7 +185,7 @@ fn run_test(test: Test, handler: &Handler, buf: &BufferEmitter) -> Result(&bytecode).map_err(|err| err.into()))?; + 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..b7754f08ae 100644 --- a/errors/src/errors/utils/util_errors.rs +++ b/errors/src/errors/utils/util_errors.rs @@ -49,9 +49,9 @@ create_messages!( @formatted snarkvm_parsing_error { - args: (), - msg: "SnarkVM failure to parse `.aleo` program".to_string(), - help: None, + args: (name: impl Display), + msg: format!("Failed to parse the source file for `{name}.aleo` into a valid Aleo program."), + help: Some("Make sure that you are using the correct `--network` and `--endpoint` options.".to_string()), } @formatted diff --git a/leo/cli/commands/query/mod.rs b/leo/cli/commands/query/mod.rs index 2ca7645165..1363a10a45 100644 --- a/leo/cli/commands/query/mod.rs +++ b/leo/cli/commands/query/mod.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . +use snarkvm::circuit::{AleoTestnetV0, AleoV0}; +use snarkvm::prelude::{MainnetV0, TestnetV0}; use super::*; mod block; @@ -41,6 +43,7 @@ mod utils; use utils::*; use leo_errors::UtilError; +use leo_retriever::{fetch_from_network, NetworkName, verify_valid_program}; /// Query live data from the Aleo network. #[derive(Parser, Debug)] @@ -72,49 +75,65 @@ 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)] diff --git a/utils/disassembler/src/lib.rs b/utils/disassembler/src/lib.rs index d8d1294357..6cb515f0ec 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,7 @@ 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..c561df236c 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::{ @@ -32,6 +32,7 @@ use std::{ marker::PhantomData, path::{Path, PathBuf}, }; +use std::str::FromStr; // Retriever is responsible for retrieving external programs pub struct Retriever { @@ -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()))?; } @@ -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); +// Fetch the given endpoint url and return the sanitized response. +pub fn fetch_from_network(url: &String) -> Result { 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 { - 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: &String, program: &String) -> Result<(), UtilError> { + match Program::::from_str(program) { + Ok(_) => Ok(()), + Err(_) => Err(UtilError::snarkvm_parsing_error(name, Default::default())), + } +}