diff --git a/.github/workflows/leo-login-logout.yml b/.github/workflows/leo-login-logout.yml index 46883c4a87..95a17e42ed 100644 --- a/.github/workflows/leo-login-logout.yml +++ b/.github/workflows/leo-login-logout.yml @@ -43,6 +43,5 @@ jobs: leo login -u "$USER" -p "$PASS" leo add argus4130/xnor leo remove xnor - leo clean leo logout diff --git a/Cargo.lock b/Cargo.lock index 5dd9b34569..29c100ee7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,6 +1010,15 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.17" @@ -1388,6 +1397,7 @@ dependencies = [ name = "leo-lang" version = "1.2.2" dependencies = [ + "anyhow", "clap", "colored", "console", @@ -1416,6 +1426,7 @@ dependencies = [ "snarkvm-gadgets", "snarkvm-models", "snarkvm-utilities", + "structopt", "thiserror", "toml", "tracing", @@ -2052,6 +2063,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2 1.0.24", "quote 1.0.7", + "syn 1.0.60", "version_check", ] @@ -2809,6 +2821,30 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "structopt" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2 1.0.24", + "quote 1.0.7", + "syn 1.0.60", +] + [[package]] name = "subtle" version = "1.0.0" @@ -3152,6 +3188,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + [[package]] name = "unicode-width" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 4169d8a2d3..d5882916dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,12 @@ default-features = false [dependencies.snarkvm-utilities] version = "0.0.3" +[dependencies.anyhow] +version = "1.0" + +[dependencies.structopt] +version = "0.3" + [dependencies.clap] version = "2.33.3" diff --git a/leo/api.rs b/leo/api.rs new file mode 100644 index 0000000000..23e12b3578 --- /dev/null +++ b/leo/api.rs @@ -0,0 +1,202 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use anyhow::{anyhow, Error, Result}; +use reqwest::{ + blocking::{Client, Response}, + Method, + StatusCode, +}; +use serde::Serialize; + +/// Trait describes API Routes and Request bodies, struct which implements +/// Route MUST also support Serialize to be usable in Api::run_route(r: Route) +pub trait Route { + /// Whether to use bearer auth or not. Some routes may have additional + /// features for logged-in users, so authorization token should be sent + /// if it is created of course + const AUTH: bool; + + /// HTTP method to use when requesting + const METHOD: Method; + + /// URL path without first forward slash (e.g. v1/package/fetch) + const PATH: &'static str; + + /// Output type for this route. For login it is simple - String + /// But for other routes may be more complex. + type Output; + + /// Process reqwest Response and turn it into Output + fn process(&self, res: Response) -> Result; + + /// Transform specific status codes into correct errors for this route. + /// For example 404 on package fetch should mean that 'Package is not found' + fn status_to_err(&self, _status: StatusCode) -> Error { + anyhow!("Unidentified API error") + } +} + +/// REST API handler with reqwest::blocking inside +#[derive(Clone, Debug)] +pub struct Api { + host: String, + client: Client, + /// Authorization token for API requests + auth_token: Option, +} + +impl Api { + /// Create new instance of API, set host and Client is going to be + /// created and set automatically + pub fn new(host: String, auth_token: Option) -> Api { + Api { + client: Client::new(), + auth_token, + host, + } + } + + /// Get token for bearer auth, should be passed into Api through Context + pub fn auth_token(&self) -> Option { + self.auth_token.clone() + } + + /// Set authorization token for future requests + pub fn set_auth_token(&mut self, token: String) { + self.auth_token = Some(token); + } + + /// Run specific route struct. Turn struct into request body + /// and use type constants and Route implementation to get request params + pub fn run_route(&self, route: T) -> Result + where + T: Route, + T: Serialize, + { + let mut res = self.client.request(T::METHOD, &format!("{}{}", self.host, T::PATH)); + + // add body for POST and PUT requests + if T::METHOD == Method::POST || T::METHOD == Method::PUT { + res = res.json(&route); + }; + + // if Route::Auth is true and token is present - pass it + if T::AUTH && self.auth_token().is_some() { + res = res.bearer_auth(&self.auth_token().unwrap()); + }; + + // only one error is possible here + let res = res.send().map_err(|_| anyhow!("Unable to connect to Aleo PM"))?; + + // where magic begins + route.process(res) + } +} + +// -------------------------------------------------- +// | Defining routes | +// -------------------------------------------------- + +/// Handler for 'fetch' route - fetch packages from Aleo PM +/// Route: POST /v1/package/fetch +#[derive(Serialize, Debug)] +pub struct Fetch { + pub author: String, + pub package_name: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, +} + +impl Route for Fetch { + type Output = Response; + + const AUTH: bool = true; + const METHOD: Method = Method::POST; + const PATH: &'static str = "api/package/fetch"; + + fn process(&self, res: Response) -> Result { + // check status code first + if res.status() != 200 { + return Err(self.status_to_err(res.status())); + }; + + Ok(res) + } + + fn status_to_err(&self, status: StatusCode) -> Error { + match status { + StatusCode::BAD_REQUEST => anyhow!("Package is not found - check author and/or package name"), + // TODO: we should return 404 on not found author/package + // and return BAD_REQUEST if data format is incorrect or some of the arguments + // were not passed + StatusCode::NOT_FOUND => anyhow!("Package is hidden"), + _ => anyhow!("Unknown API error: {}", status), + } + } +} + +/// Handler for 'login' route - send username and password and receive JWT +/// Route: POST /v1/account/authenticate +#[derive(Serialize)] +pub struct Login { + pub email_username: String, + pub password: String, +} + +impl Route for Login { + type Output = Response; + + const AUTH: bool = false; + const METHOD: Method = Method::POST; + const PATH: &'static str = "api/account/authenticate"; + + fn process(&self, res: Response) -> Result { + if res.status() != 200 { + return Err(self.status_to_err(res.status())); + } + + Ok(res) + } + + fn status_to_err(&self, status: StatusCode) -> Error { + match status { + StatusCode::BAD_REQUEST => anyhow!("This username is not yet registered or the password is incorrect"), + // TODO: NOT_FOUND here should be replaced, this error code has no relation to what this route is doing + StatusCode::NOT_FOUND => anyhow!("Incorrect password"), + _ => anyhow!("Unknown API error: {}", status), + } + } +} + +/// Handler for 'my_profile' route. Meant to be used to get profile details but +/// in current application is used to check if user is logged in. Any non-200 response +/// is treated as Unauthorized +#[derive(Serialize)] +pub struct Profile {} + +impl Route for Profile { + type Output = bool; + + const AUTH: bool = true; + const METHOD: Method = Method::GET; + const PATH: &'static str = "api/account/my_profile"; + + fn process(&self, res: Response) -> Result { + // this may be extended for more precise error handling + Ok(res.status() == 200) + } +} diff --git a/leo/cli.rs b/leo/cli.rs deleted file mode 100644 index fb224e06ec..0000000000 --- a/leo/cli.rs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use crate::{cli_types::*, errors::CLIError, logger, updater::Updater}; - -use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; - -pub trait CLI { - type Options; - type Output; - - const ABOUT: AboutType; - const ARGUMENTS: &'static [ArgumentType]; - const FLAGS: &'static [FlagType]; - const NAME: NameType; - const OPTIONS: &'static [OptionType]; - const SUBCOMMANDS: &'static [SubCommandType]; - - #[allow(clippy::new_ret_no_self)] - #[cfg_attr(tarpaulin, skip)] - fn new<'a, 'b>() -> App<'a, 'b> { - let arguments = &Self::ARGUMENTS - .iter() - .map(|a| { - let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4); - if !a.2.is_empty() { - args = args.possible_values(a.2); - } - args - }) - .collect::>>(); - let flags = &Self::FLAGS - .iter() - .map(|a| Arg::from_usage(a)) - .collect::>>(); - let options = &Self::OPTIONS - .iter() - .map(|a| match !a.2.is_empty() { - true => Arg::from_usage(a.0) - .conflicts_with_all(a.1) - .possible_values(a.2) - .requires_all(a.3), - false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3), - }) - .collect::>>(); - let subcommands = Self::SUBCOMMANDS.iter().map(|s| { - SubCommand::with_name(s.0) - .about(s.1) - .args( - &s.2.iter() - .map(|a| { - let mut args = Arg::with_name(a.0).help(a.1).required(a.3).index(a.4); - if !a.2.is_empty() { - args = args.possible_values(a.2); - } - args - }) - .collect::>>(), - ) - .args( - &s.3.iter() - .map(|a| Arg::from_usage(a)) - .collect::>>(), - ) - .args( - &s.4.iter() - .map(|a| match !a.2.is_empty() { - true => Arg::from_usage(a.0) - .conflicts_with_all(a.1) - .possible_values(a.2) - .requires_all(a.3), - false => Arg::from_usage(a.0).conflicts_with_all(a.1).requires_all(a.3), - }) - .collect::>>(), - ) - .settings(s.5) - }); - - SubCommand::with_name(Self::NAME) - .about(Self::ABOUT) - .settings(&[ - AppSettings::ColoredHelp, - AppSettings::DisableHelpSubcommand, - AppSettings::DisableVersion, - ]) - .args(arguments) - .args(flags) - .args(options) - .subcommands(subcommands) - } - - #[cfg_attr(tarpaulin, skip)] - fn process(arguments: &ArgMatches) -> Result<(), CLIError> { - // Set logging environment - match arguments.is_present("debug") { - true => logger::init_logger("leo", 2), - false => logger::init_logger("leo", 1), - } - - if arguments.subcommand().0 != "update" { - Updater::print_cli(); - } - - let options = Self::parse(arguments)?; - let _output = Self::output(options)?; - Ok(()) - } - - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result; - - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result; -} diff --git a/leo/cli_types.rs b/leo/cli_types.rs deleted file mode 100644 index 5fac70df71..0000000000 --- a/leo/cli_types.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use clap::AppSettings; - -pub type NameType = &'static str; - -pub type AboutType = &'static str; - -pub type DescriptionType = &'static str; - -pub type RequiredType = bool; - -pub type PossibleValuesType = &'static [&'static str]; - -pub type IndexType = u64; - -pub type ArgumentType = (NameType, DescriptionType, PossibleValuesType, RequiredType, IndexType); - -// Format -// "[flag] -f --flag 'Add flag description here'" -pub type FlagType = &'static str; - -// Format -// (argument, conflicts, possible_values, requires) -pub type OptionType = ( - &'static str, - &'static [&'static str], - &'static [&'static str], - &'static [&'static str], -); - -pub type SubCommandType = ( - NameType, - AboutType, - &'static [ArgumentType], - &'static [FlagType], - &'static [OptionType], - &'static [AppSettings], -); diff --git a/leo/commands/add.rs b/leo/commands/add.rs deleted file mode 100644 index d531057895..0000000000 --- a/leo/commands/add.rs +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -// -// Usage: -// -// leo add -a author -p package_name -v version -// leo add -a author -p package_name -// - -use crate::{ - cli::CLI, - cli_types::*, - config::*, - errors::{AddError::*, CLIError}, -}; - -use leo_package::{ - imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME}, - root::Manifest, -}; - -use std::{ - collections::HashMap, - convert::TryFrom, - env::current_dir, - fs::{create_dir_all, File}, - io::{Read, Write}, -}; - -pub const ADD_URL: &str = "v1/package/fetch"; - -#[derive(Debug)] -pub struct AddCommand; - -impl CLI for AddCommand { - // Format: author, package_name, version - type Options = (Option, Option, Option); - type Output = (); - - const ABOUT: AboutType = "Install a package from the Aleo Package Manager"; - const ARGUMENTS: &'static [ArgumentType] = &[ - // (name, description, possible_values, required, index) - ( - "REMOTE", - "Install a package from the Aleo Package Manager with the given remote", - &[], - false, - 1u64, - ), - ]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "add"; - const OPTIONS: &'static [OptionType] = &[ - // (argument, conflicts, possible_values, requires) - ("[author] -a --author= 'Specify a package author'", &[], &[], &[ - "package", - ]), - ( - "[package] -p --package= 'Specify a package name'", - &[], - &[], - &["author"], - ), - ( - "[version] -v --version=[version] 'Specify a package version'", - &[], - &[], - &["author", "package"], - ), - ]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - fn parse(arguments: &clap::ArgMatches) -> Result { - // TODO update to new package manager API without an author field - if arguments.is_present("author") && arguments.is_present("package") { - return Ok(( - arguments.value_of("author").map(|s| s.to_string()), - arguments.value_of("package").map(|s| s.to_string()), - arguments.value_of("version").map(|s| s.to_string()), - )); - } - - match arguments.value_of("REMOTE") { - Some(remote) => { - let values: Vec<&str> = remote.split('/').collect(); - - if values.len() != 2 { - return Err(InvalidRemote.into()); - } - - let author = values[0].to_string(); - let package = values[1].to_string(); - - Ok((Some(author), Some(package), None)) - } - None => Ok((None, None, None)), - } - } - - fn output(options: Self::Options) -> Result { - // Begin "Adding" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Adding"); - let _enter = span.enter(); - let path = current_dir()?; - - // Enforce that the current directory is a leo package - Manifest::try_from(path.as_path())?; - - let (response, package_name) = match options { - (Some(author), Some(package_name), version) => { - let client = reqwest::blocking::Client::new(); - let url = format!("{}{}", PACKAGE_MANAGER_URL, ADD_URL); - - let mut json = HashMap::new(); - json.insert("author", author); - json.insert("package_name", package_name.clone()); - - if let Some(version) = version { - json.insert("version", version); - } - - let result = match read_token() { - Ok(token) => { - tracing::info!("Logged in, using token to authorize"); - client.post(&url).bearer_auth(token) - } - Err(_) => client.post(&url), - } - .json(&json) - .send(); - - match result { - Ok(response) => (response, package_name), - //Cannot connect to the server - Err(_error) => { - return Err( - ConnectionUnavailable("Could not connect to the Aleo Package Manager".into()).into(), - ); - } - } - } - _ => return Err(MissingAuthorOrPackageName.into()), - }; - - let mut path = current_dir()?; - ImportsDirectory::create(&path)?; - path.push(IMPORTS_DIRECTORY_NAME); - path.push(package_name); - create_dir_all(&path)?; - - let bytes = response.bytes()?; - let reader = std::io::Cursor::new(bytes); - - let mut zip_arhive = match zip::ZipArchive::new(reader) { - Ok(zip) => zip, - Err(error) => return Err(ZipError(error.to_string().into()).into()), - }; - - for i in 0..zip_arhive.len() { - let file = match zip_arhive.by_index(i) { - Ok(file) => file, - Err(error) => return Err(ZipError(error.to_string().into()).into()), - }; - - let file_name = file.name(); - - let mut file_path = path.clone(); - file_path.push(file_name); - - if file_name.ends_with('/') { - create_dir_all(file_path)?; - } else { - if let Some(parent_directory) = path.parent() { - create_dir_all(parent_directory)?; - } - - File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::>())?; - } - } - - tracing::info!("Successfully added a package\n"); - Ok(()) - } -} diff --git a/leo/commands/build.rs b/leo/commands/build.rs index 35a11ecf43..ce108a854e 100644 --- a/leo/commands/build.rs +++ b/leo/commands/build.rs @@ -15,55 +15,50 @@ // along with the Leo library. If not, see . use crate::{ - cli::*, - cli_types::*, - errors::CLIError, + commands::Command, + context::Context, synthesizer::{CircuitSynthesizer, SerializedCircuit}, }; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_package::{ inputs::*, outputs::{ChecksumFile, CircuitFile, OutputsDirectory, OUTPUTS_DIRECTORY_NAME}, - root::Manifest, source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, }; +use anyhow::Result; use snarkvm_curves::{bls12_377::Bls12_377, edwards_bls12::Fq}; use snarkvm_models::gadgets::r1cs::ConstraintSystem; +use std::convert::TryFrom; +use structopt::StructOpt; +use tracing::span::Span; + +/// Compile and build program command +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Build {} + +impl Build { + pub fn new() -> Build { + Build {} + } +} -use clap::ArgMatches; -use std::{convert::TryFrom, env::current_dir, time::Instant}; - -#[derive(Debug)] -pub struct BuildCommand; - -impl CLI for BuildCommand { - type Options = (); +impl Command for Build { + type Input = (); type Output = Option<(Compiler, bool)>; - const ABOUT: AboutType = "Compile the current package as a program"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "build"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Build") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - #[cfg_attr(tarpaulin, skip)] - fn output(_options: Self::Options) -> Result { - // Begin "Compiling" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Compiling"); - let enter = span.enter(); - - let path = current_dir()?; - - // Get the package name - let manifest = Manifest::try_from(path.as_path())?; - let package_name = manifest.get_package_name(); + fn apply(self, ctx: Context, _: Self::Input) -> Result { + let path = ctx.dir()?; + let package_name = ctx.manifest()?.get_package_name(); // Sanitize the package path to the root directory let mut package_path = path.clone(); @@ -77,9 +72,6 @@ impl CLI for BuildCommand { tracing::info!("Starting..."); - // Start the timer - let start = Instant::now(); - // Compile the package starting with the lib.leo file if LibraryFile::exists_at(&package_path) { // Construct the path to the library file in the source directory @@ -185,21 +177,9 @@ impl CLI for BuildCommand { tracing::info!("Complete"); - // Drop "Compiling" context for console logging - drop(enter); - - // Begin "Done" context for console logging todo: @collin figure a way to get this output with tracing without dropping span - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { - tracing::info!("Finished in {} milliseconds\n", start.elapsed().as_millis()); - }); - return Ok(Some((program, checksum_differs))); } - drop(enter); - - // Return None when compiling a package for publishing - // The published package does not need to have a main.leo Ok(None) } } diff --git a/leo/commands/clean.rs b/leo/commands/clean.rs index c7d950e3bb..91240f1be7 100644 --- a/leo/commands/clean.rs +++ b/leo/commands/clean.rs @@ -14,45 +14,40 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{cli::*, cli_types::*, errors::CLIError}; -use leo_package::{ - outputs::{ChecksumFile, ProofFile, ProvingKeyFile, VerificationKeyFile}, - root::Manifest, -}; - -use clap::ArgMatches; +use crate::{commands::Command, context::Context}; use leo_compiler::OutputFile; -use leo_package::outputs::CircuitFile; -use std::{convert::TryFrom, env::current_dir}; +use leo_package::outputs::{ChecksumFile, CircuitFile, ProofFile, ProvingKeyFile, VerificationKeyFile}; + +use anyhow::Result; +use structopt::StructOpt; +use tracing::span::Span; + +/// Clean outputs folder command +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Clean {} -#[derive(Debug)] -pub struct CleanCommand; +impl Clean { + pub fn new() -> Clean { + Clean {} + } +} -impl CLI for CleanCommand { - type Options = (); +impl Command for Clean { + type Input = (); type Output = (); - const ABOUT: AboutType = "Clean the output directory"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "clean"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Cleaning") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - #[cfg_attr(tarpaulin, skip)] - fn output(_options: Self::Options) -> Result { - // Begin "Clean" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Cleaning"); - let enter = span.enter(); - - // Get the package name - let path = current_dir()?; - let package_name = Manifest::try_from(path.as_path())?.get_package_name(); + fn apply(self, ctx: Context, _: Self::Input) -> Result { + let path = ctx.dir()?; + let package_name = ctx.manifest()?.get_package_name(); // Remove the checksum from the output directory ChecksumFile::new(&package_name).remove(&path)?; @@ -72,14 +67,6 @@ impl CLI for CleanCommand { // Remove the proof from the output directory ProofFile::new(&package_name).remove(&path)?; - // Drop "Compiling" context for console logging - drop(enter); - - // Begin "Done" context for console logging - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { - tracing::info!("Program workspace cleaned\n"); - }); - Ok(()) } } diff --git a/leo/commands/deploy.rs b/leo/commands/deploy.rs index 7cbd1d5b5a..f27da1dae5 100644 --- a/leo/commands/deploy.rs +++ b/leo/commands/deploy.rs @@ -14,65 +14,36 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - commands::BuildCommand, - errors::{CLIError, RunError}, -}; -use leo_package::{ - root::Manifest, - source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, -}; +use crate::{commands::Command, context::Context}; -use clap::ArgMatches; -use std::{convert::TryFrom, env::current_dir}; +use anyhow::Result; +use structopt::StructOpt; +use tracing::span::Span; -#[derive(Debug)] -pub struct DeployCommand; +/// Deploy Leo program to the network +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Deploy {} -impl CLI for DeployCommand { - type Options = (); +impl Deploy { + pub fn new() -> Deploy { + Deploy {} + } +} + +impl Command for Deploy { + type Input = (); type Output = (); - const ABOUT: AboutType = "Deploy the current package as a program to the network (*)"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "deploy"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Deploy") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { - // Begin "Deploy" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Deploying"); - let _enter = span.enter(); - - let path = current_dir()?; - - match BuildCommand::output(options)? { - Some((_program, _checksum_differs)) => { - // Get the package name - let _package_name = Manifest::try_from(path.as_path())?.get_package_name(); - - tracing::error!("Unimplemented - `leo deploy`"); - - Ok(()) - } - None => { - let mut main_file_path = path; - main_file_path.push(SOURCE_DIRECTORY_NAME); - main_file_path.push(MAIN_FILENAME); - - Err(CLIError::RunError(RunError::MainFileDoesNotExist( - main_file_path.into_os_string(), - ))) - } - } + fn apply(self, _: Context, _: Self::Input) -> Result { + unimplemented!("Deploy command has not been implemented yet"); } } diff --git a/leo/commands/init.rs b/leo/commands/init.rs index bf0d4a956e..7ebe2a3214 100644 --- a/leo/commands/init.rs +++ b/leo/commands/init.rs @@ -14,58 +14,50 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - errors::{CLIError, InitError}, -}; +use crate::{commands::Command, context::Context}; use leo_package::LeoPackage; -use clap::ArgMatches; +use anyhow::{anyhow, Result}; use std::env::current_dir; +use structopt::StructOpt; +use tracing::span::Span; -#[derive(Debug)] -pub struct InitCommand; +/// Init Leo project command within current directory +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Init {} -impl CLI for InitCommand { - type Options = bool; - type Output = (); +impl Init { + pub fn new() -> Init { + Init {} + } +} - const ABOUT: AboutType = "Create a new Leo package in an existing directory"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")]; - const NAME: NameType = "init"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; +impl Command for Init { + type Input = (); + type Output = (); - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - Ok(arguments.is_present("lib")) + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Initializing") } - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { - // Begin "Initializing" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Initializing"); - let _enter = span.enter(); + fn prelude(&self) -> Result { + Ok(()) + } + fn apply(self, _: Context, _: Self::Input) -> Result { let path = current_dir()?; - - // Derive the package name let package_name = path .file_stem() - .ok_or_else(|| InitError::ProjectNameInvalid(path.as_os_str().to_owned()))? + .ok_or_else(|| anyhow!("Project name invalid"))? .to_string_lossy() .to_string(); - // Verify the directory does not exist if !path.exists() { - return Err(InitError::DirectoryDoesNotExist(path.as_os_str().to_owned()).into()); + return Err(anyhow!("Directory does not exist")); } - LeoPackage::initialize(&package_name, options, &path)?; - - tracing::info!("Successfully initialized package \"{}\"\n", package_name); + LeoPackage::initialize(&package_name, false, &path)?; Ok(()) } diff --git a/leo/commands/lint.rs b/leo/commands/lint.rs index 8db6b7577b..0aeda51844 100644 --- a/leo/commands/lint.rs +++ b/leo/commands/lint.rs @@ -14,65 +14,36 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - commands::BuildCommand, - errors::{CLIError, RunError}, -}; -use leo_package::{ - root::Manifest, - source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, -}; +use crate::{commands::Command, context::Context}; -use clap::ArgMatches; -use std::{convert::TryFrom, env::current_dir}; +use anyhow::Result; +use structopt::StructOpt; +use tracing::span::Span; -#[derive(Debug)] -pub struct LintCommand; +/// Lint Leo code command +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Lint {} -impl CLI for LintCommand { - type Options = (); +impl Lint { + pub fn new() -> Lint { + Lint {} + } +} + +impl Command for Lint { + type Input = (); type Output = (); - const ABOUT: AboutType = "Lints the Leo files in the package (*)"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "lint"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Linting") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { - // Begin "Linting" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Linting"); - let _enter = span.enter(); - - let path = current_dir()?; - - match BuildCommand::output(options)? { - Some((_program, _checksum_differs)) => { - // Get the package name - let _package_name = Manifest::try_from(path.as_path())?.get_package_name(); - - tracing::error!("Unimplemented - `leo lint`"); - - Ok(()) - } - None => { - let mut main_file_path = path; - main_file_path.push(SOURCE_DIRECTORY_NAME); - main_file_path.push(MAIN_FILENAME); - - Err(CLIError::RunError(RunError::MainFileDoesNotExist( - main_file_path.into_os_string(), - ))) - } - } + fn apply(self, _: Context, _: Self::Input) -> Result { + unimplemented!("Lint command has not been implemented yet"); } } diff --git a/leo/commands/login.rs b/leo/commands/login.rs deleted file mode 100644 index 1e29c3b1ff..0000000000 --- a/leo/commands/login.rs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -// -// Usage: -// -// leo login -// leo login -u username -p password -// - -use crate::{ - cli::CLI, - cli_types::*, - config::*, - errors::{CLIError, LoginError::*}, -}; - -use std::collections::HashMap; - -pub const LOGIN_URL: &str = "v1/account/authenticate"; -pub const PROFILE_URL: &str = "v1/account/my_profile"; - -#[derive(Debug)] -pub struct LoginCommand; - -impl CLI for LoginCommand { - // Format: token, username, password - type Options = (Option, Option, Option); - type Output = String; - - const ABOUT: AboutType = "Login to the Aleo Package Manager"; - const ARGUMENTS: &'static [ArgumentType] = &[ - // (name, description, possible_values, required, index) - ( - "NAME", - "Sets the authentication token for login to the package manager", - &[], - false, - 1u64, - ), - ]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "login"; - const OPTIONS: &'static [OptionType] = &[ - // (argument, conflicts, possible_values, requires) - ("[username] -u --user=[username] 'Sets a username'", &[], &[], &[]), - ("[password] -p --password=[password] 'Sets a password'", &[], &[], &[]), - ]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - fn parse(arguments: &clap::ArgMatches) -> Result { - if arguments.is_present("username") && arguments.is_present("password") { - return Ok(( - None, - Some(arguments.value_of("username").unwrap().to_string()), - Some(arguments.value_of("password").unwrap().to_string()), - )); - } - - match arguments.value_of("NAME") { - Some(name) => Ok((Some(name.to_string()), None, None)), - None => Ok((None, None, None)), - } - } - - fn output(options: Self::Options) -> Result { - // Begin "Login" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Login"); - let _enter = span.enter(); - - let token = match options { - // Login using existing token - (Some(token), _, _) => Some(token), - - // Login using username and password - (None, Some(username), Some(password)) => { - // prepare JSON data to be sent - let mut json = HashMap::new(); - json.insert("email_username", username); - json.insert("password", password); - - let client = reqwest::blocking::Client::new(); - let url = format!("{}{}", PACKAGE_MANAGER_URL, LOGIN_URL); - let response: HashMap = match client.post(&url).json(&json).send() { - Ok(result) => match result.json() { - Ok(json) => json, - Err(_error) => { - return Err(WrongLoginOrPassword.into()); - } - }, - //Cannot connect to the server - Err(_error) => { - return Err(NoConnectionFound.into()); - } - }; - - match response.get("token") { - Some(token) => Some(token.clone()), - None => { - return Err(CannotGetToken.into()); - } - } - } - - // Login using stored JWT credentials. - // TODO (raychu86) Package manager re-authentication from token - (_, _, _) => { - let token = read_token().map_err(|_| -> CLIError { NoCredentialsProvided.into() })?; - - Some(token) - } - }; - - match token { - Some(token) => { - write_token(token.as_str())?; - - tracing::info!("success"); - - Ok(token) - } - _ => { - tracing::error!("Failed to login. Please run `leo login -h` for help."); - - Err(NoCredentialsProvided.into()) - } - } - } -} diff --git a/leo/commands/mod.rs b/leo/commands/mod.rs index 96f2279e4a..8a6ca36171 100644 --- a/leo/commands/mod.rs +++ b/leo/commands/mod.rs @@ -14,53 +14,125 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -pub mod add; -pub use self::add::*; +use crate::context::{get_context, Context}; +use anyhow::Result; +use std::time::Instant; +use tracing::span::Span; + +// local program commands pub mod build; -pub use self::build::*; +pub use build::Build; pub mod clean; -pub use self::clean::*; +pub use clean::Clean; pub mod deploy; -pub use self::deploy::*; +pub use deploy::Deploy; pub mod init; -pub use self::init::*; +pub use init::Init; pub mod lint; -pub use self::lint::*; - -pub mod login; -pub use self::login::*; - -pub mod logout; -pub use self::logout::*; +pub use lint::Lint; pub mod new; -pub use self::new::*; +pub use new::New; pub mod prove; -pub use self::prove::*; - -pub mod publish; -pub use self::publish::*; +pub use prove::Prove; pub mod run; -pub use self::run::*; +pub use run::Run; pub mod setup; -pub use self::setup::*; +pub use setup::Setup; pub mod test; -pub use self::test::*; - -pub mod remove; -pub use self::remove::*; +pub use test::Test; pub mod update; -pub use self::update::*; +pub use update::{Sub as UpdateAutomatic, Update}; pub mod watch; -pub use self::watch::*; +pub use watch::Watch; + +// Aleo PM related commands +pub mod package; + +/// Base trait for Leo CLI, see methods and their documentation for details +pub trait Command { + /// If current command requires running another command before + /// and needs its output results, this is the place to set. + /// Example: type Input: ::Out + type Input; + + /// Define output of the command to be reused as an Input for another + /// command. If this command is not used as a prelude for another, keep empty + type Output; + + /// Returns project context, currently keeping it simple but it is possible + /// that in the future leo will not depend on current directory, and we're keeping + /// option for extending current core + fn context(&self) -> Result { + get_context() + } + + /// Add span to the logger tracing::span. + /// Due to specifics of macro implementation it is impossible to set + /// span name with non-literal i.e. dynamic variable even if this + /// variable is &'static str + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Leo") + } + + /// Run prelude and get Input for current command. As simple as that. + /// But due to inability to pass default implementation of a type, this + /// method must be present in every trait implementation. + fn prelude(&self) -> Result + where + Self: std::marker::Sized; + + /// Core of the execution - do what is necessary. This function is run within + /// context of 'execute' function, which sets logging and timers + fn apply(self, ctx: Context, input: Self::Input) -> Result + where + Self: std::marker::Sized; + + /// Wrapper around apply function, sets up tracing, time tracking and context + fn execute(self) -> Result + where + Self: std::marker::Sized, + { + let input = self.prelude()?; + + // create span for this command + let span = self.log_span(); + let span = span.enter(); + + // calculate execution time for each run + let timer = Instant::now(); + + let context = self.context()?; + let out = self.apply(context, input); + + drop(span); + + // use done context to print time + tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { + tracing::info!("Finished in {} milliseconds \n", timer.elapsed().as_millis()); + }); + + out + } + + /// Execute command but empty the result. Comes in handy where there's a + /// need to make match arms compatible while keeping implementation-specific + /// output possible. Errors however are all of the type Error + fn try_execute(self) -> Result<()> + where + Self: std::marker::Sized, + { + self.execute().map(|_| Ok(()))? + } +} diff --git a/leo/commands/new.rs b/leo/commands/new.rs index bc42eb7477..2509e7563e 100644 --- a/leo/commands/new.rs +++ b/leo/commands/new.rs @@ -14,81 +14,56 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - errors::{CLIError, NewError}, -}; +use crate::{commands::Command, context::Context}; use leo_package::LeoPackage; -use clap::ArgMatches; +use anyhow::{anyhow, Result}; use std::{env::current_dir, fs}; +use structopt::StructOpt; +use tracing::span::Span; + +/// Create new Leo project +#[derive(StructOpt, Debug)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct New { + #[structopt(name = "NAME", help = "Set package name")] + name: String, +} -#[derive(Debug)] -pub struct NewCommand; +impl New { + pub fn new(name: String) -> New { + New { name } + } +} -impl CLI for NewCommand { - type Options = (Option, bool); +impl Command for New { + type Input = (); type Output = (); - const ABOUT: AboutType = "Create a new Leo package in a new directory"; - const ARGUMENTS: &'static [ArgumentType] = &[ - // (name, description, possible_values, required, index) - ( - "NAME", - "Sets the resulting package name, defaults to the directory name", - &[], - true, - 1u64, - ), - ]; - const FLAGS: &'static [FlagType] = &[("--lib"), ("--bin")]; - const NAME: NameType = "new"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - let is_lib = arguments.is_present("lib"); - match arguments.value_of("NAME") { - Some(name) => Ok((Some(name.to_string()), is_lib)), - None => Ok((None, is_lib)), - } + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "New") } - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { - // Begin "Initializing" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Initializing"); - let _enter = span.enter(); + fn prelude(&self) -> Result { + Ok(()) + } + fn apply(self, _: Context, _: Self::Input) -> Result { let mut path = current_dir()?; - - // Derive the package name - let package_name = match options.0 { - Some(name) => name, - None => path - .file_stem() - .ok_or_else(|| NewError::ProjectNameInvalid(path.as_os_str().to_owned()))? - .to_string_lossy() - .to_string(), - }; + let package_name = self.name; // Derive the package directory path path.push(&package_name); // Verify the package directory path does not exist yet if path.exists() { - return Err(NewError::DirectoryAlreadyExists(path.as_os_str().to_owned()).into()); + return Err(anyhow!("Directory already exists {:?}", path)); } // Create the package directory - fs::create_dir_all(&path) - .map_err(|error| NewError::CreatingRootDirectory(path.as_os_str().to_owned(), error))?; - - LeoPackage::initialize(&package_name, options.1, &path)?; + fs::create_dir_all(&path).map_err(|err| anyhow!("Could not create directory {}", err))?; - tracing::info!("Successfully initialized package \"{}\"\n", package_name); + LeoPackage::initialize(&package_name, false, &path)?; Ok(()) } diff --git a/leo/commands/package/add.rs b/leo/commands/package/add.rs new file mode 100644 index 0000000000..6d2da368b4 --- /dev/null +++ b/leo/commands/package/add.rs @@ -0,0 +1,156 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{api::Fetch, commands::Command, context::Context}; +use leo_package::imports::{ImportsDirectory, IMPORTS_DIRECTORY_NAME}; + +use anyhow::{anyhow, Result}; +use std::{ + fs::{create_dir_all, File}, + io::{Read, Write}, +}; +use structopt::StructOpt; +use tracing::Span; + +/// Add package from Aleo Package Manager +#[derive(StructOpt, Debug)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Add { + #[structopt(name = "REMOTE")] + remote: Option, + + #[structopt(name = "author", help = "Specify a package author", long = "author", short = "a")] + author: Option, + + #[structopt(name = "package", help = "Specify a package name", long = "package", short = "p")] + package: Option, + + #[structopt(name = "version", help = "Specify a package version", long = "version", short = "v")] + version: Option, +} + +impl Add { + pub fn new( + remote: Option, + author: Option, + package: Option, + version: Option, + ) -> Add { + Add { + remote, + author, + package, + version, + } + } + + /// Try to parse author/package string from self.remote + pub fn try_read_arguments(&self) -> Result<(String, String)> { + if let Some(val) = &self.remote { + let v: Vec<&str> = val.split('/').collect(); + if v.len() == 2 { + Ok((v[0].to_string(), v[1].to_string())) + } else { + Err(anyhow!( + "Incorrect argument, please use --help for information on command use" + )) + } + } else if let (Some(author), Some(package)) = (&self.author, &self.package) { + Ok((author.clone(), package.clone())) + } else { + Err(anyhow!( + "Incorrect argument, please use --help for information on command use" + )) + } + } +} + +impl Command for Add { + type Input = (); + type Output = (); + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Adding") + } + + fn prelude(&self) -> Result { + Ok(()) + } + + fn apply(self, ctx: Context, _: Self::Input) -> Result { + // checking that manifest exists... + if ctx.manifest().is_err() { + return Err(anyhow!("Package Manifest not found, try running leo init or leo new")); + }; + + let (author, package_name) = match self.try_read_arguments() { + Ok((author, package)) => (author, package), + Err(err) => return Err(err), + }; + let version = self.version; + + // build request body (Options are skipped when sealizing) + let fetch = Fetch { + author, + package_name: package_name.clone(), + version, + }; + + let bytes = ctx.api.run_route(fetch)?.bytes()?; + let mut path = ctx.dir()?; + + { + // setup directory structure since request was success + ImportsDirectory::create(&path)?; + path.push(IMPORTS_DIRECTORY_NAME); + path.push(package_name); + create_dir_all(&path)?; + }; + + let reader = std::io::Cursor::new(bytes); + + let mut zip_archive = match zip::ZipArchive::new(reader) { + Ok(zip) => zip, + Err(error) => return Err(anyhow!(error)), + }; + + for i in 0..zip_archive.len() { + let file = match zip_archive.by_index(i) { + Ok(file) => file, + Err(error) => return Err(anyhow!(error)), + }; + + let file_name = file.name(); + + let mut file_path = path.clone(); + file_path.push(file_name); + + if file_name.ends_with('/') { + create_dir_all(file_path)?; + } else { + if let Some(parent_directory) = path.parent() { + create_dir_all(parent_directory)?; + } + + File::create(file_path)?.write_all(&file.bytes().map(|e| e.unwrap()).collect::>())?; + } + } + + tracing::info!("Successfully added a package"); + + Ok(()) + } +} diff --git a/leo/commands/package/login.rs b/leo/commands/package/login.rs new file mode 100644 index 0000000000..7e3950b03c --- /dev/null +++ b/leo/commands/package/login.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{ + api::{Login as LoginRoute, Profile as ProfileRoute}, + commands::Command, + config::*, + context::Context, +}; + +use anyhow::{anyhow, Result}; +use std::collections::HashMap; +use structopt::StructOpt; +use tracing::Span; + +/// Login to Aleo PM and store credentials locally +#[derive(StructOpt, Debug)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Login { + #[structopt(name = "AUTH_TOKEN", about = "Pass authorization token")] + token: Option, + + #[structopt(short = "u", long = "user", about = "Username for Aleo PM")] + user: Option, + + #[structopt(short = "p", long = "password", about = "Password for Aleo PM")] + pass: Option, +} + +impl Login { + pub fn new(token: Option, user: Option, pass: Option) -> Login { + Login { token, user, pass } + } +} + +impl Command for Login { + type Input = (); + type Output = String; + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Login") + } + + fn prelude(&self) -> Result { + Ok(()) + } + + fn apply(self, ctx: Context, _: Self::Input) -> Result { + // quick hack to check if user is already logged in. ;) + if ctx.api.auth_token().is_some() { + tracing::info!("You are already logged in"); + return Ok(ctx.api.auth_token().unwrap()); + }; + + let mut api = ctx.api; + + // ...or trying to use arguments to either get token or user-pass + let token = match (self.token, self.user, self.pass) { + // Login using existing token, use get_profile route for that + (Some(token), _, _) => { + tracing::info!("Token passed, checking..."); + + api.set_auth_token(token.clone()); + + let is_ok = api.run_route(ProfileRoute {})?; + if !is_ok { + return Err(anyhow!("Supplied token is incorrect")); + }; + + token + } + + // Login using username and password + (None, Some(email_username), Some(password)) => { + let login = LoginRoute { + email_username, + password, + }; + + let res = api.run_route(login)?; + let mut res: HashMap = res.json()?; + + let tok_opt = res.remove("token"); + if tok_opt.is_none() { + return Err(anyhow!("Unable to get token")); + }; + + tok_opt.unwrap() + } + + // In case token or login/pass were not passed as arguments + (_, _, _) => return Err(anyhow!("No credentials provided")), + }; + + // write token either after logging or if it was passed + write_token(token.as_str())?; + + tracing::info!("Success! You are now logged in!"); + + Ok(token) + } +} diff --git a/leo/commands/logout.rs b/leo/commands/package/logout.rs similarity index 63% rename from leo/commands/logout.rs rename to leo/commands/package/logout.rs index 439570e051..0d844ff7ed 100644 --- a/leo/commands/logout.rs +++ b/leo/commands/package/logout.rs @@ -14,40 +14,37 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -// -// Usage: -// -// leo logout -// +use crate::{commands::Command, config::remove_token, context::Context}; -#[derive(Debug)] -pub struct LogoutCommand; - -use crate::{cli::CLI, cli_types::*, config::remove_token, errors::CLIError}; +use anyhow::Result; use std::io::ErrorKind; +use structopt::StructOpt; +use tracing::Span; + +/// Remove credentials for Aleo PM from .leo directory +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Logout {} -impl CLI for LogoutCommand { - type Options = (); +impl Logout { + pub fn new() -> Logout { + Logout {} + } +} + +impl Command for Logout { + type Input = (); type Output = (); - const ABOUT: AboutType = "Logout from Aleo Package Manager"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "logout"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Logout") + } - /// no options and no arguments for this buddy - fn parse(_: &clap::ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - /// as simple as it could be - remove credentials file - fn output(_: Self::Options) -> Result { - // we gotta do something about this span issue :confused: - let span = tracing::span!(tracing::Level::INFO, "Logout"); - let _ent = span.enter(); - + fn apply(self, _ctx: Context, _: Self::Input) -> Result { // the only error we're interested here is NotFound // however err in this case can also be of kind PermissionDenied or other if let Err(err) = remove_token() { diff --git a/leo/errors/mod.rs b/leo/commands/package/mod.rs similarity index 77% rename from leo/errors/mod.rs rename to leo/commands/package/mod.rs index 1370f5bef7..f3b292715f 100644 --- a/leo/errors/mod.rs +++ b/leo/commands/package/mod.rs @@ -14,11 +14,19 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -pub mod cli; -pub use self::cli::*; +pub mod add; +pub use add::Add; -pub mod commands; -pub use self::commands::*; +pub mod login; +pub use login::Login; -pub mod updater; -pub use self::updater::*; +pub mod logout; +pub use logout::Logout; + +pub mod publish; +pub use publish::Publish; + +pub mod remove; +pub use remove::Remove; + +pub use super::*; diff --git a/leo/commands/publish.rs b/leo/commands/package/publish.rs similarity index 51% rename from leo/commands/publish.rs rename to leo/commands/package/publish.rs index e25b4e7093..7b1fceb3a6 100644 --- a/leo/commands/publish.rs +++ b/leo/commands/package/publish.rs @@ -14,29 +14,20 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . +use super::build::Build; use crate::{ - cli::*, - cli_types::*, - commands::{BuildCommand, LoginCommand}, - config::{read_token, PACKAGE_MANAGER_URL}, - errors::{ - commands::PublishError::{ConnectionUnavalaible, PackageNotPublished}, - CLIError, - PublishError::{MissingPackageDescription, MissingPackageLicense, MissingPackageRemote}, - }, -}; -use leo_package::{ - outputs::OutputsDirectory, - root::{Manifest, ZipFile}, + commands::Command, + context::{Context, PACKAGE_MANAGER_URL}, }; +use leo_package::{outputs::OutputsDirectory, root::ZipFile}; -use clap::ArgMatches; +use anyhow::{anyhow, Result}; use reqwest::{ blocking::{multipart::Form, Client}, header::{HeaderMap, HeaderValue}, }; use serde::Deserialize; -use std::{convert::TryFrom, env::current_dir}; +use structopt::StructOpt; pub const PUBLISH_URL: &str = "v1/package/publish"; @@ -45,54 +36,47 @@ struct ResponseJson { package_id: String, } -#[derive(Debug)] -pub struct PublishCommand; - -impl CLI for PublishCommand { - type Options = (); - type Output = Option; +/// Publish package to Aleo Package Manager +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Publish {} - const ABOUT: AboutType = "Publish the current package to the Aleo Package Manager"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "publish"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { - Ok(()) +impl Publish { + pub fn new() -> Publish { + Publish {} } +} - #[cfg_attr(tarpaulin, skip)] - fn output(_options: Self::Options) -> Result { - // Build all program files. - let _output = BuildCommand::output(())?; +impl Command for Publish { + type Input = ::Output; + type Output = Option; - // Begin "Publishing" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Publishing"); - let _enter = span.enter(); + /// Build program before publishing + fn prelude(&self) -> Result { + Build::new().execute() + } + fn apply(self, ctx: Context, _input: Self::Input) -> Result { // Get the package manifest - let path = current_dir()?; - let package_manifest = Manifest::try_from(path.as_path())?; - - let package_name = package_manifest.get_package_name(); - let package_version = package_manifest.get_package_version(); - - if package_manifest.get_package_description().is_none() { - return Err(MissingPackageDescription.into()); - } - - if package_manifest.get_package_license().is_none() { - return Err(MissingPackageLicense.into()); - } - - let package_remote = match package_manifest.get_package_remote() { - Some(remote) => remote, - None => return Err(MissingPackageRemote.into()), + let path = ctx.dir()?; + let manifest = ctx.manifest()?; + + let package_name = manifest.get_package_name(); + let package_version = manifest.get_package_version(); + + match ( + manifest.get_package_description(), + manifest.get_package_license(), + manifest.get_package_remote(), + ) { + (None, _, _) => return Err(anyhow!("No package description")), + (_, None, _) => return Err(anyhow!("Missing package license")), + (_, _, None) => return Err(anyhow!("Missing package remote")), + (_, _, _) => (), }; + let package_remote = manifest.get_package_remote().unwrap(); + // Create the output directory OutputsDirectory::create(&path)?; @@ -115,18 +99,9 @@ impl CLI for PublishCommand { // Client for make POST request let client = Client::new(); - // Get token to make an authorized request - let token = match read_token() { - Ok(token) => token, - - // If not logged in, then try logging in using JWT. - Err(_error) => { - tracing::warn!("You should be logged in before attempting to publish a package"); - tracing::info!("Trying to log in using JWT..."); - let options = (None, None, None); - - LoginCommand::output(options)? - } + let token = match ctx.api.auth_token() { + Some(token) => token, + None => return Err(anyhow!("Login before publishing package: try leo login --help")), }; // Headers for request to publish package @@ -149,12 +124,12 @@ impl CLI for PublishCommand { Ok(json) => json, Err(error) => { tracing::warn!("{:?}", error); - return Err(PackageNotPublished("Package not published".into()).into()); + return Err(anyhow!("Package not published")); } }, Err(error) => { tracing::warn!("{:?}", error); - return Err(ConnectionUnavalaible("Connection error".into()).into()); + return Err(anyhow!("Connection unavailable")); } }; diff --git a/leo/commands/package/remove.rs b/leo/commands/package/remove.rs new file mode 100644 index 0000000000..2d988fd55e --- /dev/null +++ b/leo/commands/package/remove.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{commands::Command, context::Context}; +use leo_package::LeoPackage; + +use anyhow::Result; +use structopt::StructOpt; +use tracing::span::Span; + +/// Remove imported package +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Remove { + #[structopt(name = "PACKAGE")] + name: String, +} + +impl Remove { + pub fn new(name: String) -> Remove { + Remove { name } + } +} + +impl Command for Remove { + type Input = (); + type Output = (); + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Removing") + } + + fn prelude(&self) -> Result { + Ok(()) + } + + fn apply(self, ctx: Context, _: Self::Input) -> Result { + let path = ctx.dir()?; + let package_name = self.name; + + LeoPackage::remove_imported_package(&package_name, &path)?; + tracing::info!("Successfully removed package \"{}\"\n", package_name); + + Ok(()) + } +} diff --git a/leo/commands/prove.rs b/leo/commands/prove.rs index 8a06b55e1d..bd240be342 100644 --- a/leo/commands/prove.rs +++ b/leo/commands/prove.rs @@ -14,73 +14,62 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{cli::*, cli_types::*, commands::SetupCommand, errors::CLIError}; -use leo_package::{outputs::ProofFile, root::Manifest}; - +use super::setup::Setup; +use crate::{commands::Command, context::Context}; +use leo_package::outputs::ProofFile; use snarkvm_algorithms::snark::groth16::{Groth16, PreparedVerifyingKey, Proof}; use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_models::algorithms::SNARK; use snarkvm_utilities::bytes::ToBytes; -use clap::ArgMatches; +use anyhow::Result; use rand::thread_rng; -use std::{convert::TryFrom, env::current_dir, time::Instant}; +use structopt::StructOpt; +use tracing::span::Span; + +/// Run the program and produce a proof +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Prove { + #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] + skip_key_check: bool, +} -#[derive(Debug)] -pub struct ProveCommand; +impl Prove { + pub fn new(skip_key_check: bool) -> Prove { + Prove { skip_key_check } + } +} -impl CLI for ProveCommand { - type Options = bool; +impl Command for Prove { + type Input = ::Output; type Output = (Proof, PreparedVerifyingKey); - const ABOUT: AboutType = "Run the program and produce a proof"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[("--skip-key-check")]; - const NAME: NameType = "prove"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - Ok(!arguments.is_present("skip-key-check")) + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Proving") } - #[cfg_attr(tarpaulin, skip)] - fn output(do_setup_check: Self::Options) -> Result { - let (program, parameters, prepared_verifying_key) = SetupCommand::output(do_setup_check)?; + fn prelude(&self) -> Result { + Setup::new(self.skip_key_check).execute() + } - // Begin "Proving" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Proving"); - let enter = span.enter(); + fn apply(self, ctx: Context, input: Self::Input) -> Result { + let (program, parameters, prepared_verifying_key) = input; // Get the package name - let path = current_dir()?; - let package_name = Manifest::try_from(path.as_path())?.get_package_name(); + let path = ctx.dir()?; + let package_name = ctx.manifest()?.get_package_name(); tracing::info!("Starting..."); - // Start the timer - let start = Instant::now(); - let rng = &mut thread_rng(); let program_proof = Groth16::>::prove(¶meters, &program, rng)?; - // Finish the timer - let end = start.elapsed().as_millis(); - // Write the proof file to the output directory let mut proof = vec![]; program_proof.write(&mut proof)?; ProofFile::new(&package_name).write_to(&path, &proof)?; - // Drop "Proving" context for console logging - drop(enter); - - // Begin "Done" context for console logging - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { - tracing::info!("Finished in {:?} milliseconds\n", end); - }); - Ok((program_proof, prepared_verifying_key)) } } diff --git a/leo/commands/remove.rs b/leo/commands/remove.rs deleted file mode 100644 index 8be8ea1b1c..0000000000 --- a/leo/commands/remove.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use crate::{cli::*, cli_types::*, errors::CLIError}; -use leo_package::LeoPackage; - -use clap::ArgMatches; -use std::env::current_dir; - -#[derive(Debug)] -pub struct RemoveCommand; - -impl CLI for RemoveCommand { - type Options = Option; - type Output = (); - - const ABOUT: AboutType = "Uninstall a package from the current package"; - const ARGUMENTS: &'static [ArgumentType] = &[ - // (name, description, possible_values, required, index) - ( - "NAME", - "Removes the package from the current directory", - &[], - true, - 1u64, - ), - ]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "remove"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - Ok(match arguments.value_of("NAME") { - Some(name) => Some(name.to_string()), - None => unreachable!(), - }) - } - - #[cfg_attr(tarpaulin, skip)] - fn output(options: Self::Options) -> Result { - // Begin "Removing" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Removing"); - let _enter = span.enter(); - - let path = current_dir()?; - - if let Some(package_name) = options { - LeoPackage::remove_imported_package(&package_name, &path)?; - tracing::info!("Successfully removed package \"{}\"\n", package_name); - } - - Ok(()) - } -} diff --git a/leo/commands/run.rs b/leo/commands/run.rs index ee99079e26..ffb05896fa 100644 --- a/leo/commands/run.rs +++ b/leo/commands/run.rs @@ -14,48 +14,48 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{cli::*, cli_types::*, commands::ProveCommand, errors::CLIError}; +use super::prove::Prove; +use crate::{commands::Command, context::Context}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; +use anyhow::Result; use snarkvm_algorithms::snark::groth16::Groth16; use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_models::algorithms::SNARK; +use structopt::StructOpt; +use tracing::span::Span; + +/// Build, Prove and Run Leo program with inputs +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Run { + #[structopt(long = "skip-key-check", help = "Skip key verification on Setup stage")] + skip_key_check: bool, +} -use clap::ArgMatches; -use std::time::Instant; - -#[derive(Debug)] -pub struct RunCommand; +impl Run { + pub fn new(skip_key_check: bool) -> Run { + Run { skip_key_check } + } +} -impl CLI for RunCommand { - type Options = bool; +impl Command for Run { + type Input = ::Output; type Output = (); - const ABOUT: AboutType = "Run a program with input variables"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[("--skip-key-check")]; - const NAME: NameType = "run"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - Ok(!arguments.is_present("skip-key-check")) + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Verifying") } - #[cfg_attr(tarpaulin, skip)] - fn output(do_setup_check: Self::Options) -> Result<(), CLIError> { - let (proof, prepared_verifying_key) = ProveCommand::output(do_setup_check)?; + fn prelude(&self) -> Result { + Prove::new(self.skip_key_check).execute() + } - // Begin "Verifying" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Verifying"); - let enter = span.enter(); + fn apply(self, _ctx: Context, input: Self::Input) -> Result { + let (proof, prepared_verifying_key) = input; tracing::info!("Starting..."); - // Start the timer - let start = Instant::now(); - // Run the verifier let is_success = Groth16::, Vec>::verify( &prepared_verifying_key, @@ -63,23 +63,12 @@ impl CLI for RunCommand { &proof, )?; - // End the timer - let end = start.elapsed().as_millis(); - // Log the verifier output match is_success { true => tracing::info!("Proof is valid"), false => tracing::error!("Proof is invalid"), }; - // Drop "Verifying" context for console logging - drop(enter); - - // Begin "Done" context for console logging - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { - tracing::info!("Finished in {:?} milliseconds\n", end); - }); - Ok(()) } } diff --git a/leo/commands/setup.rs b/leo/commands/setup.rs index 95b11c674b..550934f1e2 100644 --- a/leo/commands/setup.rs +++ b/leo/commands/setup.rs @@ -14,81 +14,71 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - commands::BuildCommand, - errors::{CLIError, RunError}, -}; +use super::build::Build; +use crate::{commands::Command, context::Context}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_package::{ outputs::{ProvingKeyFile, VerificationKeyFile}, - root::Manifest, source::{MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, }; +use anyhow::{anyhow, Result}; +use rand::thread_rng; use snarkvm_algorithms::snark::groth16::{Groth16, Parameters, PreparedVerifyingKey, VerifyingKey}; use snarkvm_curves::bls12_377::{Bls12_377, Fr}; use snarkvm_models::algorithms::snark::SNARK; +use structopt::StructOpt; +use tracing::span::Span; + +/// Executes the setup command for a Leo program +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Setup { + #[structopt(long = "skip-key-check", help = "Skip key verification")] + skip_key_check: bool, +} -use clap::ArgMatches; -use rand::thread_rng; -use std::{convert::TryFrom, env::current_dir, time::Instant}; - -#[derive(Debug)] -pub struct SetupCommand; +impl Setup { + pub fn new(skip_key_check: bool) -> Setup { + Setup { skip_key_check } + } +} -impl CLI for SetupCommand { - type Options = bool; +impl Command for Setup { + type Input = ::Output; type Output = ( Compiler, Parameters, PreparedVerifyingKey, ); - const ABOUT: AboutType = "Run a program setup"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[("--skip-key-check")]; - const NAME: NameType = "setup"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Setup") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(arguments: &ArgMatches) -> Result { - Ok(!arguments.is_present("skip-key-check")) + fn prelude(&self) -> Result { + Build::new().execute() } - #[cfg_attr(tarpaulin, skip)] - fn output(do_check: Self::Options) -> Result { - // Get the package name - let path = current_dir()?; - let package_name = Manifest::try_from(path.as_path())?.get_package_name(); + fn apply(self, ctx: Context, input: Self::Input) -> Result { + let path = ctx.dir()?; + let package_name = ctx.manifest()?.get_package_name(); - match BuildCommand::output(())? { + match input { Some((program, checksum_differs)) => { - // Begin "Setup" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Setup"); - let enter = span.enter(); - // Check if a proving key and verification key already exists let keys_exist = ProvingKeyFile::new(&package_name).exists_at(&path) && VerificationKeyFile::new(&package_name).exists_at(&path); // If keys do not exist or the checksum differs, run the program setup // If keys do not exist or the checksum differs, run the program setup - let (end, proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs { + let (proving_key, prepared_verifying_key) = if !keys_exist || checksum_differs { tracing::info!("Starting..."); - // Start the timer for setup - let setup_start = Instant::now(); - // Run the program setup operation let rng = &mut thread_rng(); let (proving_key, prepared_verifying_key) = - Groth16::, Vec>::setup(&program, rng).unwrap(); - - // End the timer - let end = setup_start.elapsed().as_millis(); + Groth16::, Vec>::setup(&program, rng)?; // TODO (howardwu): Convert parameters to a 'proving key' struct for serialization. // Write the proving key file to the output directory @@ -107,20 +97,19 @@ impl CLI for SetupCommand { let _ = verification_key_file.write_to(&path, &verification_key)?; tracing::info!("Complete"); - (end, proving_key, prepared_verifying_key) + (proving_key, prepared_verifying_key) } else { tracing::info!("Detected saved setup"); - // Start the timer for setup - let setup_start = Instant::now(); - // Read the proving key file from the output directory tracing::info!("Loading proving key..."); - if !do_check { + + if self.skip_key_check { tracing::info!("Skipping curve check"); } let proving_key_bytes = ProvingKeyFile::new(&package_name).read_from(&path)?; - let proving_key = Parameters::::read(proving_key_bytes.as_slice(), do_check)?; + let proving_key = + Parameters::::read(proving_key_bytes.as_slice(), !self.skip_key_check)?; tracing::info!("Complete"); // Read the verification key file from the output directory @@ -132,20 +121,9 @@ impl CLI for SetupCommand { let prepared_verifying_key = PreparedVerifyingKey::::from(verifying_key); tracing::info!("Complete"); - // End the timer - let end = setup_start.elapsed().as_millis(); - - (end, proving_key, prepared_verifying_key) + (proving_key, prepared_verifying_key) }; - // Drop "Setup" context for console logging - drop(enter); - - // Begin "Done" context for console logging - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { - tracing::info!("Finished in {:?} milliseconds\n", end); - }); - Ok((program, proving_key, prepared_verifying_key)) } None => { @@ -153,9 +131,7 @@ impl CLI for SetupCommand { main_file_path.push(SOURCE_DIRECTORY_NAME); main_file_path.push(MAIN_FILENAME); - Err(CLIError::RunError(RunError::MainFileDoesNotExist( - main_file_path.into_os_string(), - ))) + Err(anyhow!("Unable to build, check that main file exists")) } } } diff --git a/leo/commands/test.rs b/leo/commands/test.rs index 6f8a18886e..f9bf89a003 100644 --- a/leo/commands/test.rs +++ b/leo/commands/test.rs @@ -14,67 +14,75 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{ - cli::*, - cli_types::*, - errors::{CLIError, TestError::ProgramFileDoesNotExist}, -}; +use crate::{commands::Command, context::Context}; use leo_compiler::{compiler::Compiler, group::targets::edwards_bls12::EdwardsGroupType}; use leo_package::{ inputs::*, outputs::{OutputsDirectory, OUTPUTS_DIRECTORY_NAME}, - root::Manifest, - source::{LibraryFile, MainFile, LIBRARY_FILENAME, MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, + source::{MainFile, MAIN_FILENAME, SOURCE_DIRECTORY_NAME}, }; +use anyhow::{anyhow, Result}; use snarkvm_curves::edwards_bls12::Fq; +use std::{convert::TryFrom, path::PathBuf, time::Instant}; +use structopt::StructOpt; +use tracing::span::Span; + +/// Build program and run tests command +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Test { + #[structopt(short = "f", long = "file", name = "file")] + files: Vec, +} -use clap::ArgMatches; -use std::{convert::TryFrom, env::current_dir, time::Instant}; - -#[derive(Debug)] -pub struct TestCommand; +impl Test { + pub fn new(files: Vec) -> Test { + Test { files } + } +} -impl CLI for TestCommand { - type Options = (); +impl Command for Test { + type Input = (); type Output = (); - const ABOUT: AboutType = "Compile and run all tests in the current package"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "test"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Test") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - #[cfg_attr(tarpaulin, skip)] - fn output(_options: Self::Options) -> Result { - let path = current_dir()?; - + fn apply(self, ctx: Context, _: Self::Input) -> Result { // Get the package name - let manifest = Manifest::try_from(path.as_path())?; - let package_name = manifest.get_package_name(); + let package_name = ctx.manifest()?.get_package_name(); // Sanitize the package path to the root directory - let mut package_path = path; + let mut package_path = ctx.dir()?; if package_path.is_file() { package_path.pop(); } - let mut file_path = package_path.clone(); - file_path.push(SOURCE_DIRECTORY_NAME); + let mut to_test: Vec = Vec::new(); - // Verify a main or library file exists - if MainFile::exists_at(&package_path) { + // if -f flag was used, then we'll test only this files + if !self.files.is_empty() { + to_test.extend(self.files.iter().cloned()); + + // if args were not passed - try main file + } else if MainFile::exists_at(&package_path) { + let mut file_path = package_path.clone(); + file_path.push(SOURCE_DIRECTORY_NAME); file_path.push(MAIN_FILENAME); - } else if LibraryFile::exists_at(&package_path) { - file_path.push(LIBRARY_FILENAME); + to_test.push(file_path); + + // when no main file and no files marked - error } else { - return Err(ProgramFileDoesNotExist(package_path.into()).into()); + return Err(anyhow!( + "Program file does not exist {}", + package_path.to_string_lossy() + )); } // Construct the path to the output directory; @@ -84,49 +92,45 @@ impl CLI for TestCommand { // Create the output directory OutputsDirectory::create(&package_path)?; - // Begin "Test" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Test"); - let enter = span.enter(); - - // Start the timer - let start = Instant::now(); - - // Parse the current main program file - let program = - Compiler::::parse_program_without_input(package_name, file_path, output_directory)?; - - // Parse all inputs as input pairs - let pairs = InputPairs::try_from(package_path.as_path())?; - - // Run tests - let temporary_program = program; - let (passed, failed) = temporary_program.compile_test_constraints(pairs)?; - - // Drop "Test" context for console logging - drop(enter); - - // Set the result of the test command to passed if no tests failed. - if failed == 0 { - // Begin "Done" context for console logging - tracing::span!(tracing::Level::INFO, "Done").in_scope(|| { + // Finally test every passed file + for file_path in to_test { + tracing::info!("Running tests in file {:?}", file_path); + + let input_pairs = match InputPairs::try_from(package_path.as_path()) { + Ok(pairs) => pairs, + Err(_) => { + tracing::warn!("Unable to find inputs, ignore this message or put them into /inputs folder"); + InputPairs::new() + } + }; + + let timer = Instant::now(); + let program = Compiler::::parse_program_without_input( + package_name.clone(), + file_path, + output_directory.clone(), + )?; + + let temporary_program = program; + let (passed, failed) = temporary_program.compile_test_constraints(input_pairs)?; + let time_taken = timer.elapsed().as_millis(); + + if failed == 0 { tracing::info!( "Tests passed in {} milliseconds. {} passed; {} failed;\n", - start.elapsed().as_millis(), + time_taken, passed, failed ); - }); - } else { - // Begin "Done" context for console logging - tracing::span!(tracing::Level::ERROR, "Done").in_scope(|| { + } else { tracing::error!( "Tests failed in {} milliseconds. {} passed; {} failed;\n", - start.elapsed().as_millis(), + time_taken, passed, failed ); - }); - }; + } + } Ok(()) } diff --git a/leo/commands/update.rs b/leo/commands/update.rs index dad803c947..09e0ca640a 100644 --- a/leo/commands/update.rs +++ b/leo/commands/update.rs @@ -14,179 +14,97 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{cli::CLI, cli_types::*, config::Config, updater::Updater}; - -use clap::AppSettings; - -#[derive(Debug)] -pub struct UpdateCommand; - -impl CLI for UpdateCommand { - // (show_all_versions, quiet) - type Options = Option<(bool, bool, bool)>; - type Output = (); - - const ABOUT: AboutType = "Update Leo to the latest version"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[ - "[list] -l --list 'List all available versions of Leo'", - "[quiet] -q --quiet 'Suppress outputs to terminal'", - "[studio] -s --studio 'For Aleo Studio only'", - ]; - const NAME: NameType = "update"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[ - // (name, description, options, settings) - ( - UpdateAutomatic::NAME, - UpdateAutomatic::ABOUT, - UpdateAutomatic::ARGUMENTS, - UpdateAutomatic::FLAGS, - &UpdateAutomatic::OPTIONS, - &[ - AppSettings::ColoredHelp, - AppSettings::DisableHelpSubcommand, - AppSettings::DisableVersion, - ], - ), - ]; - - fn parse(arguments: &clap::ArgMatches) -> Result { - if let ("automatic", Some(arguments)) = arguments.subcommand() { - // Run the `automatic` subcommand - let options = UpdateAutomatic::parse(arguments)?; - let _output = UpdateAutomatic::output(options)?; - return Ok(None); - }; - - let show_all_versions = arguments.is_present("list"); - let quiet = arguments.is_present("quiet"); - let studio = arguments.is_present("studio"); - - Ok(Some((show_all_versions, quiet, studio))) - } - - fn output(options: Self::Options) -> Result { - // Begin "Updating" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Updating"); - let _enter = span.enter(); - - let (show_all_versions, quiet, studio) = match options { - Some(options) => options, - None => return Ok(()), - }; - - match show_all_versions { - true => match Updater::show_available_releases() { - Ok(_) => return Ok(()), - Err(e) => { - tracing::error!("Could not fetch that latest version of Leo"); - tracing::error!("{}", e); - } - }, - false => { - let config = Config::read_config()?; +use crate::{commands::Command, config::Config, context::Context, updater::Updater}; + +use anyhow::{anyhow, Result}; +use structopt::StructOpt; +use tracing::span::Span; + +/// Setting for automatic updates of Leo +#[derive(Debug, StructOpt, PartialEq)] +pub enum Sub { + Automatic { + #[structopt(name = "bool", help = "Boolean value: true or false", parse(try_from_str))] + value: bool, + }, +} - // If update is run with studio and the automatic update is off, finish quietly - if studio && !config.update.automatic { - return Ok(()); - } +/// Update Leo to the latest version +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Update { + /// List all available versions of Leo + #[structopt(short, long)] + list: bool, + + /// For Aleo Studio only + #[structopt(short, long)] + studio: bool, + + /// Setting for automatic updates of Leo + #[structopt(subcommand)] + automatic: Option, +} - match Updater::update_to_latest_release(!quiet) { - Ok(status) => { - if !quiet { - if status.uptodate() { - tracing::info!("Leo is already on the latest version"); - } else if status.updated() { - tracing::info!("Leo has successfully updated to version {}", status.version()); - } - } - return Ok(()); - } - Err(e) => { - if !quiet { - tracing::error!("Could not update Leo to the latest version"); - tracing::error!("{}", e); - } - } - } - } +impl Update { + pub fn new(list: bool, studio: bool, automatic: Option) -> Update { + Update { + list, + studio, + automatic, } - Ok(()) } } -//TODO (raychu86) Move this to dedicated file/module -#[derive(Debug)] -pub struct UpdateAutomatic; - -impl CLI for UpdateAutomatic { - // (is_automatic, quiet) - type Options = (Option, bool); +impl Command for Update { + type Input = (); type Output = (); - const ABOUT: AboutType = "Setting for automatic updates of Leo"; - const ARGUMENTS: &'static [ArgumentType] = &[ - // (name, description, possible_values, required, index) - ( - "automatic", - "Enable or disable automatic updates", - &["true", "false"], - false, - 1u64, - ), - ]; - const FLAGS: &'static [FlagType] = &["[quiet] -q --quiet 'Suppress outputs to terminal'"]; - const NAME: NameType = "automatic"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; - - fn parse(arguments: &clap::ArgMatches) -> Result { - let quiet = arguments.is_present("quiet"); - - match arguments.value_of("automatic") { - Some(automatic) => { - // TODO enforce that the possible values is true or false - let automatic = match automatic { - "true" => Some(true), - "false" => Some(false), - _ => unreachable!(), - }; - - Ok((automatic, quiet)) - } - None => Ok((None, quiet)), - } + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Updating") } - fn output(options: Self::Options) -> Result { - // Begin "Settings" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Settings"); - let enter = span.enter(); + fn prelude(&self) -> Result { + Ok(()) + } - // If a boolean value is provided, update the saved - // `automatic` configuration value to this boolean value. - if let Some(automatic) = options.0 { - Config::set_update_automatic(automatic)?; + fn apply(self, _: Context, _: Self::Input) -> Result { + // if --list is passed - simply list everything and exit + if self.list { + return Updater::show_available_releases().map_err(|e| anyhow!("Could not fetch versions: {}", e)); } - // If --quiet is not enabled, log the output. - if !options.1 { - // Read the `automatic` value now. - let automatic = Config::read_config()?.update.automatic; + // in case automatic subcommand was called + if let Some(Sub::Automatic { value }) = self.automatic { + Config::set_update_automatic(value)?; - // Log the output. - tracing::debug!("automatic = {}", automatic); - match automatic { - true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released."), + match value { + true => tracing::info!("Automatic updates are enabled. Leo will update as new versions are released"), false => { - tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released.") + tracing::info!("Automatic updates are disabled. Leo will not update as new versions are released") } }; + + return Ok(()); + } + + let config = Config::read_config()?; + // If update is run with studio and the automatic update is off, finish quietly + if self.studio && !config.update.automatic { + return Ok(()); } - // Drop "Settings" context for console logging. - drop(enter); + match Updater::update_to_latest_release(true) { + Ok(status) => match (status.uptodate(), status.updated()) { + (true, _) => tracing::info!("Leo is already on the latest version"), + (_, true) => tracing::info!("Leo has successfully updated to version {}", status.version()), + (_, _) => (), + }, + Err(e) => { + tracing::error!("Could not update Leo to the latest version"); + tracing::error!("{}", e); + } + } Ok(()) } diff --git a/leo/commands/watch.rs b/leo/commands/watch.rs index a4ef19067e..16ae65c2a5 100644 --- a/leo/commands/watch.rs +++ b/leo/commands/watch.rs @@ -14,42 +14,55 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{cli::CLI, cli_types::*, commands::BuildCommand, errors::CLIError}; -use clap::ArgMatches; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; +use super::build::Build; +use crate::{commands::Command, context::Context}; + use std::{sync::mpsc::channel, time::Duration}; +use anyhow::{anyhow, Result}; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; +use structopt::StructOpt; +use tracing::span::Span; + const LEO_SOURCE_DIR: &str = "src/"; -// Time interval for watching files, in seconds -const INTERVAL: u64 = 3; +/// Watch file changes in src/ directory and run Build Command +#[derive(StructOpt, Debug, Default)] +#[structopt(setting = structopt::clap::AppSettings::ColoredHelp)] +pub struct Watch { + /// Set up watch interval + #[structopt(short, long, default_value = "3")] + interval: u64, +} -pub struct WatchCommand; +impl Watch { + pub fn new(interval: u64) -> Watch { + Watch { interval } + } +} -impl CLI for WatchCommand { - type Options = (); +impl Command for Watch { + type Input = (); type Output = (); - const ABOUT: AboutType = "Watch for changes of Leo source files"; - const ARGUMENTS: &'static [ArgumentType] = &[]; - const FLAGS: &'static [FlagType] = &[]; - const NAME: NameType = "watch"; - const OPTIONS: &'static [OptionType] = &[]; - const SUBCOMMANDS: &'static [SubCommandType] = &[]; + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Watching") + } - #[cfg_attr(tarpaulin, skip)] - fn parse(_arguments: &ArgMatches) -> Result { + fn prelude(&self) -> Result { Ok(()) } - fn output(_options: Self::Options) -> Result { - // Begin "Watching" context for console logging - let span = tracing::span!(tracing::Level::INFO, "Watching"); - let _enter = span.enter(); - + fn apply(self, _ctx: Context, _: Self::Input) -> Result { let (tx, rx) = channel(); - let mut watcher = watcher(tx, Duration::from_secs(INTERVAL)).unwrap(); - watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).unwrap(); + let mut watcher = watcher(tx, Duration::from_secs(self.interval)).unwrap(); + + watcher.watch(LEO_SOURCE_DIR, RecursiveMode::Recursive).map_err(|e| { + anyhow!( + "Unable to watch, check that directory contains Leo.toml file. Error: {}", + e + ) + })?; tracing::info!("Watching Leo source code"); @@ -57,7 +70,7 @@ impl CLI for WatchCommand { match rx.recv() { // See changes on the write event Ok(DebouncedEvent::Write(_write)) => { - match BuildCommand::output(()) { + match Build::new().execute() { Ok(_output) => { tracing::info!("Built successfully"); } diff --git a/leo/config.rs b/leo/config.rs index 5fa4ddcedb..efc1ca4ed3 100644 --- a/leo/config.rs +++ b/leo/config.rs @@ -14,11 +14,6 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::errors::CLIError; - -use dirs::home_dir; -use lazy_static::lazy_static; -use serde::{Deserialize, Serialize}; use std::{ fs::{self, create_dir_all, File}, io, @@ -26,7 +21,10 @@ use std::{ path::{Path, PathBuf}, }; -pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/"; +use anyhow::Error; +use dirs::home_dir; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; pub const LEO_CREDENTIALS_FILE: &str = "credentials"; pub const LEO_CONFIG_FILE: &str = "config.toml"; @@ -75,7 +73,7 @@ impl Default for Config { impl Config { /// Read the config from the `config.toml` file - pub fn read_config() -> Result { + pub fn read_config() -> Result { let config_dir = LEO_CONFIG_DIRECTORY.clone(); let config_path = LEO_CONFIG_PATH.clone(); @@ -112,7 +110,7 @@ impl Config { } /// Update the `automatic` configuration in the `config.toml` file. - pub fn set_update_automatic(automatic: bool) -> Result<(), CLIError> { + pub fn set_update_automatic(automatic: bool) -> Result<(), Error> { let mut config = Self::read_config()?; if config.update.automatic != automatic { diff --git a/leo/context.rs b/leo/context.rs new file mode 100644 index 0000000000..077f382428 --- /dev/null +++ b/leo/context.rs @@ -0,0 +1,72 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{api::Api, config}; +use leo_package::root::Manifest; + +use anyhow::Result; +use std::{convert::TryFrom, env::current_dir, path::PathBuf}; + +pub const PACKAGE_MANAGER_URL: &str = "https://api.aleo.pm/"; + +/// Project context, manifest, current directory etc +/// All the info that is relevant in most of the commands +#[derive(Clone)] +pub struct Context { + /// Api client for Aleo PM + pub api: Api, + + /// Path at which the command is called, None when default + pub path: Option, +} + +impl Context { + pub fn dir(&self) -> Result { + match &self.path { + Some(path) => Ok(path.clone()), + None => Ok(current_dir()?), + } + } + + /// Get package manifest for current context + pub fn manifest(&self) -> Result { + Ok(Manifest::try_from(self.dir()?.as_path())?) + } +} + +/// Create a new context for the current directory. +pub fn create_context(path: PathBuf) -> Result { + let token = match config::read_token() { + Ok(token) => Some(token), + Err(_) => None, + }; + + let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token); + + Ok(Context { api, path: Some(path) }) +} + +/// Returns project context. +pub fn get_context() -> Result { + let token = match config::read_token() { + Ok(token) => Some(token), + Err(_) => None, + }; + + let api = Api::new(PACKAGE_MANAGER_URL.to_string(), token); + + Ok(Context { api, path: None }) +} diff --git a/leo/errors/cli.rs b/leo/errors/cli.rs deleted file mode 100644 index 3d33adfef1..0000000000 --- a/leo/errors/cli.rs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use crate::errors::*; -use leo_compiler::errors::OutputFileError; -use leo_package::errors::*; - -#[derive(Debug, Error)] -pub enum CLIError { - #[error("{}", _0)] - AddError(AddError), - - #[error("{}", _0)] - BuildError(BuildError), - - #[error("{}", _0)] - ZipFileError(ZipFileError), - - #[error("{}", _0)] - ChecksumFileError(ChecksumFileError), - - #[error("{}", _0)] - CircuitFileError(CircuitFileError), - - #[error("{}: {}", _0, _1)] - Crate(&'static str, String), - - #[error("{}", _0)] - GitignoreError(GitignoreError), - - #[error("{}", _0)] - InitError(InitError), - - #[error("{}", _0)] - ImportsDirectoryError(ImportsDirectoryError), - - #[error("{}", _0)] - InputsDirectoryError(InputsDirectoryError), - - #[error("{}", _0)] - InputFileError(InputFileError), - - #[error("{}", _0)] - LibraryFileError(LibraryFileError), - - #[error("{}", _0)] - LoginError(LoginError), - - #[error("{}", _0)] - MainFileError(MainFileError), - - #[error("{}", _0)] - ManifestError(ManifestError), - - #[error("{}", _0)] - NewError(NewError), - - #[error("{}", _0)] - OutputFileError(OutputFileError), - - #[error("{}", _0)] - OutputsDirectoryError(OutputsDirectoryError), - - #[error("{}", _0)] - PackageError(PackageError), - - #[error("{}", _0)] - ProofFileError(ProofFileError), - - #[error("{}", _0)] - ProvingKeyFileError(ProvingKeyFileError), - - #[error("{}", _0)] - PublishError(PublishError), - - #[error("{}", _0)] - READMEError(READMEError), - - #[error("{}", _0)] - RunError(RunError), - - #[error("{}", _0)] - SNARKError(snarkvm_errors::algorithms::snark::SNARKError), - - #[error("{}", _0)] - SourceDirectoryError(SourceDirectoryError), - - #[error("{}", _0)] - StateFileError(StateFileError), - - #[error("{}", _0)] - TestError(TestError), - - #[error("TomlSerError: {0}")] - TomlSerError(#[from] toml::ser::Error), - - #[error("TomlDeError: {0}")] - TomlDeError(#[from] toml::de::Error), - - #[error("{}", _0)] - VerificationKeyFileError(VerificationKeyFileError), -} - -macro_rules! impl_cli_error { - ($($t:tt), +) => { - $(impl From<$t> for CLIError { - fn from(error: $t) -> Self { - tracing::error!("{}\n", error); - - CLIError::$t(error) - } - })* - } -} - -impl_cli_error!( - AddError, - BuildError, - CircuitFileError, - ChecksumFileError, - GitignoreError, - ImportsDirectoryError, - InitError, - InputsDirectoryError, - InputFileError, - LibraryFileError, - LoginError, - MainFileError, - ManifestError, - NewError, - OutputFileError, - OutputsDirectoryError, - PackageError, - ProofFileError, - ProvingKeyFileError, - PublishError, - READMEError, - RunError, - SourceDirectoryError, - StateFileError, - TestError, - VerificationKeyFileError, - ZipFileError -); - -impl From for CLIError { - fn from(error: clap::Error) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("clap", error.to_string()) - } -} - -impl From for CLIError { - fn from(error: leo_compiler::errors::CompilerError) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("leo-compiler", "Program failed due to previous error".into()) - } -} - -impl From for CLIError { - fn from(error: leo_input::errors::InputParserError) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("leo-input", "Program failed due to previous error".into()) - } -} - -impl From for CLIError { - fn from(error: reqwest::Error) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("rewquest", error.to_string()) - } -} - -impl From for CLIError { - fn from(error: snarkvm_errors::algorithms::snark::SNARKError) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("snarkvm_errors", error.to_string()) - } -} - -impl From for CLIError { - fn from(error: snarkvm_errors::gadgets::SynthesisError) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("snarkvm_errors", error.to_string()) - } -} - -impl From for CLIError { - fn from(error: serde_json::error::Error) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("serde_json", error.to_string()) - } -} - -impl From for CLIError { - fn from(error: std::io::Error) -> Self { - tracing::error!("{}\n", error); - CLIError::Crate("std::io", error.to_string()) - } -} diff --git a/leo/errors/commands/add.rs b/leo/errors/commands/add.rs deleted file mode 100644 index bbcd33f45b..0000000000 --- a/leo/errors/commands/add.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use std::ffi::OsString; - -#[derive(Debug, Error)] -pub enum AddError { - #[error("Connection unavailable {:?}", _0)] - ConnectionUnavailable(OsString), - - #[error("Missing author or package name: leo add author/package")] - MissingAuthorOrPackageName, - - #[error("Invalid remote")] - InvalidRemote, - - #[error("Not authorized in package manager. Use leo login to sign in")] - NotAuthorized, - - #[error("{:?}", _0)] - ZipError(OsString), -} diff --git a/leo/errors/commands/build.rs b/leo/errors/commands/build.rs deleted file mode 100644 index 9d478b2c68..0000000000 --- a/leo/errors/commands/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use leo_package::errors::ManifestError; - -use std::ffi::OsString; - -#[derive(Debug, Error)] -pub enum BuildError { - #[error("main file {:?} does not exist", _0)] - MainFileDoesNotExist(OsString), - - #[error("{}", _0)] - ManifestError(#[from] ManifestError), -} diff --git a/leo/errors/commands/init.rs b/leo/errors/commands/init.rs deleted file mode 100644 index caa2945bde..0000000000 --- a/leo/errors/commands/init.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use leo_package::errors::ManifestError; - -use std::{ffi::OsString, io}; - -#[derive(Debug, Error)] -pub enum InitError { - #[error("root directory {:?} creating: {}", _0, _1)] - CreatingRootDirectory(OsString, io::Error), - - #[error("directory {:?} does not exist", _0)] - DirectoryDoesNotExist(OsString), - - #[error("{}", _0)] - ManifestError(#[from] ManifestError), - - #[error("package name is missing - {:?}", _0)] - ProjectNameInvalid(OsString), -} diff --git a/leo/errors/commands/login.rs b/leo/errors/commands/login.rs deleted file mode 100644 index ce506b84e9..0000000000 --- a/leo/errors/commands/login.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -#[derive(Debug, Error)] -pub enum LoginError { - #[error("No token was provided in the response")] - CannotGetToken, - - #[error("Could not connect to the package manager")] - NoConnectionFound, - - #[error("No login credentials were provided")] - NoCredentialsProvided, - - #[error("Wrong login or password")] - WrongLoginOrPassword, -} diff --git a/leo/errors/commands/mod.rs b/leo/errors/commands/mod.rs deleted file mode 100644 index 079c24deec..0000000000 --- a/leo/errors/commands/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -pub mod add; -pub use self::add::*; - -pub mod build; -pub use self::build::*; - -pub mod init; -pub use self::init::*; - -pub mod login; -pub use self::login::*; - -pub mod new; -pub use self::new::*; - -pub mod publish; -pub use self::publish::*; - -pub mod run; -pub use self::run::*; - -pub mod test; -pub use self::test::*; diff --git a/leo/errors/commands/new.rs b/leo/errors/commands/new.rs deleted file mode 100644 index 9df2571e87..0000000000 --- a/leo/errors/commands/new.rs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use leo_package::errors::ManifestError; - -use std::{ffi::OsString, io}; - -#[derive(Debug, Error)] -pub enum NewError { - #[error("root directory {:?} creating: {}", _0, _1)] - CreatingRootDirectory(OsString, io::Error), - - #[error("directory {:?} already exists", _0)] - DirectoryAlreadyExists(OsString), - - #[error("{}", _0)] - ManifestError(#[from] ManifestError), - - #[error("package name is missing - {:?}", _0)] - ProjectNameInvalid(OsString), -} diff --git a/leo/errors/commands/publish.rs b/leo/errors/commands/publish.rs deleted file mode 100644 index 1a8026b088..0000000000 --- a/leo/errors/commands/publish.rs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use std::ffi::OsString; - -#[derive(Debug, Error)] -pub enum PublishError { - #[error("connection unavailable {:?}", _0)] - ConnectionUnavalaible(OsString), - - #[error("package toml file is missing a description")] - MissingPackageDescription, - - #[error("package toml file is missing a license")] - MissingPackageLicense, - - #[error("package toml file is missing a remote")] - MissingPackageRemote, - - #[error("package not published {:?}", _0)] - PackageNotPublished(OsString), -} diff --git a/leo/errors/commands/run.rs b/leo/errors/commands/run.rs deleted file mode 100644 index 98bfd7d28d..0000000000 --- a/leo/errors/commands/run.rs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -use leo_package::errors::ManifestError; - -use std::ffi::OsString; - -#[derive(Debug, Error)] -pub enum RunError { - #[error("main file {:?} does not exist", _0)] - MainFileDoesNotExist(OsString), - - #[error("{}", _0)] - ManifestError(#[from] ManifestError), -} diff --git a/leo/errors/commands/test.rs b/leo/errors/commands/test.rs deleted file mode 100644 index 5ba1914378..0000000000 --- a/leo/errors/commands/test.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . -use std::ffi::OsString; - -#[derive(Debug, Error)] -pub enum TestError { - #[error("could not find main or library file in {:?}", _0)] - ProgramFileDoesNotExist(OsString), -} diff --git a/leo/errors/updater.rs b/leo/errors/updater.rs deleted file mode 100644 index e885b47cbb..0000000000 --- a/leo/errors/updater.rs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2019-2021 Aleo Systems Inc. -// This file is part of the Leo library. - -// The Leo library is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// The Leo library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with the Leo library. If not, see . - -#[derive(Debug, Error)] -pub enum UpdaterError { - #[error("{}: {}", _0, _1)] - Crate(&'static str, String), - - #[error("The current version {} is more recent than the release version {}", _0, _1)] - OldReleaseVersion(String, String), -} - -impl From for UpdaterError { - fn from(error: self_update::errors::Error) -> Self { - tracing::error!("{}\n", error); - UpdaterError::Crate("self_update", error.to_string()) - } -} diff --git a/leo/lib.rs b/leo/lib.rs index d419917b72..5c1c7c74e2 100644 --- a/leo/lib.rs +++ b/leo/lib.rs @@ -14,15 +14,13 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -#[macro_use] -extern crate thiserror; - -pub mod cli; -pub mod cli_types; +pub mod api; pub mod commands; -#[cfg_attr(tarpaulin, skip)] pub mod config; -pub mod errors; +pub mod context; pub mod logger; pub mod synthesizer; pub mod updater; + +#[cfg(test)] +mod tests; diff --git a/leo/logger.rs b/leo/logger.rs index 4873c7781f..c6da4abdd8 100644 --- a/leo/logger.rs +++ b/leo/logger.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use colored::Colorize; use std::fmt; + +use colored::Colorize; use tracing::{event::Event, subscriber::Subscriber}; use tracing_subscriber::{ fmt::{format::*, time::*, FmtContext, FormattedFields}, diff --git a/leo/main.rs b/leo/main.rs index af03a4fb3b..6fe3cf92ac 100644 --- a/leo/main.rs +++ b/leo/main.rs @@ -14,80 +14,197 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use leo_lang::{cli::*, commands::*, errors::CLIError, logger, updater::Updater}; - -use clap::{App, AppSettings, Arg}; - -#[cfg_attr(tarpaulin, skip)] -fn main() -> Result<(), CLIError> { - let app = App::new("leo") - .version(env!("CARGO_PKG_VERSION")) - .about("Leo compiler and package manager") - .author("The Aleo Team ") - .settings(&[ - AppSettings::ColoredHelp, - AppSettings::DisableHelpSubcommand, - AppSettings::DisableVersion, - ]) - .args(&[Arg::with_name("debug") - .short("d") - .long("debug") - .help("Enables debugging mode") - .global(true)]) - .subcommands(vec![ - NewCommand::new().display_order(0), - InitCommand::new().display_order(1), - BuildCommand::new().display_order(2), - WatchCommand::new().display_order(3), - TestCommand::new().display_order(4), - SetupCommand::new().display_order(5), - ProveCommand::new().display_order(6), - RunCommand::new().display_order(7), - LoginCommand::new().display_order(8), - AddCommand::new().display_order(9), - RemoveCommand::new().display_order(10), - PublishCommand::new().display_order(11), - DeployCommand::new().display_order(12), - CleanCommand::new().display_order(13), - LintCommand::new().display_order(14), - UpdateCommand::new().display_order(15), - LogoutCommand::new().display_order(16), - ]) - .set_term_width(0); - - let mut help = app.clone(); - let arguments = app.get_matches(); - - match arguments.subcommand() { - ("new", Some(arguments)) => NewCommand::process(arguments), - ("init", Some(arguments)) => InitCommand::process(arguments), - ("build", Some(arguments)) => BuildCommand::process(arguments), - ("watch", Some(arguments)) => WatchCommand::process(arguments), - ("test", Some(arguments)) => TestCommand::process(arguments), - ("setup", Some(arguments)) => SetupCommand::process(arguments), - ("prove", Some(arguments)) => ProveCommand::process(arguments), - ("run", Some(arguments)) => RunCommand::process(arguments), - ("login", Some(arguments)) => LoginCommand::process(arguments), - ("add", Some(arguments)) => AddCommand::process(arguments), - ("remove", Some(arguments)) => RemoveCommand::process(arguments), - ("publish", Some(arguments)) => PublishCommand::process(arguments), - ("deploy", Some(arguments)) => DeployCommand::process(arguments), - ("clean", Some(arguments)) => CleanCommand::process(arguments), - ("lint", Some(arguments)) => LintCommand::process(arguments), - ("update", Some(arguments)) => UpdateCommand::process(arguments), - ("logout", Some(arguments)) => LogoutCommand::process(arguments), - _ => { - // Set logging environment - match arguments.is_present("debug") { - true => logger::init_logger("leo", 2), - false => logger::init_logger("leo", 1), - } - - Updater::print_cli(); - - help.print_help()?; - println!(); - Ok(()) +pub mod api; +pub mod commands; +pub mod config; +pub mod context; +pub mod logger; +pub mod synthesizer; +pub mod updater; + +use commands::{ + package::{Add, Login, Logout, Publish, Remove}, + Build, + Clean, + Command, + Deploy, + Init, + Lint, + New, + Prove, + Run, + Setup, + Test, + Update, + Watch, +}; + +use anyhow::Error; +use std::process::exit; +use structopt::{clap::AppSettings, StructOpt}; + +/// CLI Arguments entry point - includes global parameters and subcommands +#[derive(StructOpt, Debug)] +#[structopt(name = "leo", author = "The Aleo Team ", setting = AppSettings::ColoredHelp)] +struct Opt { + #[structopt(short, long, help = "Print additional information for debugging")] + debug: bool, + + #[structopt(short, long, help = "Suppress CLI output")] + quiet: bool, + + #[structopt(subcommand)] + command: CommandOpts, +} + +///Leo compiler and package manager +#[derive(StructOpt, Debug)] +#[structopt(setting = AppSettings::ColoredHelp)] +enum CommandOpts { + #[structopt(about = "Create a new Leo package in an existing directory")] + Init { + #[structopt(flatten)] + command: Init, + }, + + #[structopt(about = "Create a new Leo package in a new directory")] + New { + #[structopt(flatten)] + command: New, + }, + + #[structopt(about = "Compile the current package as a program")] + Build { + #[structopt(flatten)] + command: Build, + }, + + #[structopt(about = "Run a program setup")] + Setup { + #[structopt(flatten)] + command: Setup, + }, + + #[structopt(about = "Run the program and produce a proof")] + Prove { + #[structopt(flatten)] + command: Prove, + }, + + #[structopt(about = "Run a program with input variables")] + Run { + #[structopt(flatten)] + command: Run, + }, + + #[structopt(about = "Clean the output directory")] + Clean { + #[structopt(flatten)] + command: Clean, + }, + + #[structopt(about = "Watch for changes of Leo source files")] + Watch { + #[structopt(flatten)] + command: Watch, + }, + + #[structopt(about = "Update Leo to the latest version")] + Update { + #[structopt(flatten)] + command: Update, + }, + + #[structopt(about = "Compile and run all tests in the current package")] + Test { + #[structopt(flatten)] + command: Test, + }, + + #[structopt(about = "Install a package from the Aleo Package Manager")] + Add { + #[structopt(flatten)] + command: Add, + }, + + #[structopt(about = "Login to the Aleo Package Manager")] + Login { + #[structopt(flatten)] + command: Login, + }, + + #[structopt(about = "Logout of the Aleo Package Manager")] + Logout { + #[structopt(flatten)] + command: Logout, + }, + + #[structopt(about = "Publish the current package to the Aleo Package Manager")] + Publish { + #[structopt(flatten)] + command: Publish, + }, + + #[structopt(about = "Uninstall a package from the current package")] + Remove { + #[structopt(flatten)] + command: Remove, + }, + + #[structopt(about = "Lints the Leo files in the package (*)")] + Lint { + #[structopt(flatten)] + command: Lint, + }, + + #[structopt(about = "Deploy the current package as a program to the network (*)")] + Deploy { + #[structopt(flatten)] + command: Deploy, + }, +} + +fn main() { + // read command line arguments + let opt = Opt::from_args(); + + if !opt.quiet { + // init logger with optional debug flag + logger::init_logger("leo", match opt.debug { + false => 1, + true => 2, + }); + } + + handle_error(match opt.command { + CommandOpts::Init { command } => command.try_execute(), + CommandOpts::New { command } => command.try_execute(), + CommandOpts::Build { command } => command.try_execute(), + CommandOpts::Setup { command } => command.try_execute(), + CommandOpts::Prove { command } => command.try_execute(), + CommandOpts::Test { command } => command.try_execute(), + CommandOpts::Run { command } => command.try_execute(), + CommandOpts::Clean { command } => command.try_execute(), + CommandOpts::Watch { command } => command.try_execute(), + CommandOpts::Update { command } => command.try_execute(), + + CommandOpts::Add { command } => command.try_execute(), + CommandOpts::Login { command } => command.try_execute(), + CommandOpts::Logout { command } => command.try_execute(), + CommandOpts::Publish { command } => command.try_execute(), + CommandOpts::Remove { command } => command.try_execute(), + + CommandOpts::Lint { command } => command.try_execute(), + CommandOpts::Deploy { command } => command.try_execute(), + }); +} + +fn handle_error(res: Result) -> T { + match res { + Ok(t) => t, + Err(err) => { + eprintln!("Error: {}", err); + exit(1); } } } diff --git a/leo/synthesizer/serialized_circuit.rs b/leo/synthesizer/serialized_circuit.rs index 6527a5277c..6f52560e1d 100644 --- a/leo/synthesizer/serialized_circuit.rs +++ b/leo/synthesizer/serialized_circuit.rs @@ -14,8 +14,9 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex}; +use std::convert::TryFrom; +use serde::{Deserialize, Serialize}; use snarkvm_curves::bls12_377::Bls12_377; use snarkvm_errors::curves::FieldError; use snarkvm_models::{ @@ -23,8 +24,7 @@ use snarkvm_models::{ gadgets::r1cs::{ConstraintSystem, Index}, }; -use serde::{Deserialize, Serialize}; -use std::convert::TryFrom; +use crate::synthesizer::{CircuitSynthesizer, SerializedField, SerializedIndex}; #[derive(Serialize, Deserialize)] pub struct SerializedCircuit { diff --git a/leo/synthesizer/serialized_field.rs b/leo/synthesizer/serialized_field.rs index 6e1cbc9aa1..8c75dbae7e 100644 --- a/leo/synthesizer/serialized_field.rs +++ b/leo/synthesizer/serialized_field.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use snarkvm_errors::curves::FieldError; -use snarkvm_models::curves::{Field, Fp256, Fp256Parameters}; +use std::{convert::TryFrom, str::FromStr}; use num_bigint::BigUint; use serde::{Deserialize, Serialize}; -use std::{convert::TryFrom, str::FromStr}; +use snarkvm_errors::curves::FieldError; +use snarkvm_models::curves::{Field, Fp256, Fp256Parameters}; #[derive(Serialize, Deserialize)] pub struct SerializedField(pub String); diff --git a/leo/synthesizer/serialized_index.rs b/leo/synthesizer/serialized_index.rs index 283309530c..029edef18a 100644 --- a/leo/synthesizer/serialized_index.rs +++ b/leo/synthesizer/serialized_index.rs @@ -14,9 +14,8 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use snarkvm_models::gadgets::r1cs::Index; - use serde::{Deserialize, Serialize}; +use snarkvm_models::gadgets::r1cs::Index; #[derive(Serialize, Deserialize)] pub enum SerializedIndex { diff --git a/leo/tests/mod.rs b/leo/tests/mod.rs new file mode 100644 index 0000000000..9ecbf11522 --- /dev/null +++ b/leo/tests/mod.rs @@ -0,0 +1,130 @@ +// Copyright (C) 2019-2021 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use std::path::PathBuf; + +use anyhow::Result; + +use crate::{ + commands::{ + package::{Login, Logout}, + Build, + Command, + Prove, + Run, + Setup, + Test, + Update, + UpdateAutomatic, + }, + context::{create_context, Context}, +}; + +/// Path to the only complex Leo program that we have +/// - relative to source dir - where Cargo.toml is located +const PEDERSEN_HASH_PATH: &str = "./examples/pedersen-hash/"; + +#[test] +pub fn build_pedersen_hash() -> Result<()> { + Build::new().apply(ctx()?, ())?; + Ok(()) +} + +#[test] +pub fn setup_pedersen_hash() -> Result<()> { + let build = Build::new().apply(ctx()?, ())?; + Setup::new(false).apply(ctx()?, build.clone())?; + Setup::new(true).apply(ctx()?, build)?; + Ok(()) +} + +#[test] +pub fn prove_pedersen_hash() -> Result<()> { + let build = Build::new().apply(ctx()?, ())?; + let setup = Setup::new(false).apply(ctx()?, build)?; + Prove::new(false).apply(ctx()?, setup.clone())?; + Prove::new(true).apply(ctx()?, setup)?; + Ok(()) +} + +#[test] +pub fn run_pedersen_hash() -> Result<()> { + let build = Build::new().apply(ctx()?, ())?; + let setup = Setup::new(false).apply(ctx()?, build)?; + let prove = Prove::new(false).apply(ctx()?, setup)?; + Run::new(false).apply(ctx()?, prove.clone())?; + Run::new(true).apply(ctx()?, prove)?; + Ok(()) +} + +#[test] +pub fn test_pedersen_hash() -> Result<()> { + let mut main_file = PathBuf::from(PEDERSEN_HASH_PATH); + main_file.push("src/main.leo"); + + Test::new(Vec::new()).apply(ctx()?, ())?; + Test::new(vec![main_file]).apply(ctx()?, ())?; + Ok(()) +} + +#[test] +pub fn test_logout() -> Result<()> { + Logout::new().apply(ctx()?, ())?; + Ok(()) +} + +// Decided to not go all-in on error messages since they might change in the future +// So this test only tells that error cases are errors +#[test] +pub fn login_incorrect_credentials_or_token() -> Result<()> { + // no credentials passed + let login = Login::new(None, None, None).apply(ctx()?, ()); + assert!(login.is_err()); + + // incorrect token + let login = Login::new(Some("none".to_string()), None, None).apply(ctx()?, ()); + assert!(login.is_err()); + + // only user, no pass + let login = Login::new(None, Some("user".to_string()), None).apply(ctx()?, ()); + assert!(login.is_err()); + + // no user, only pass + let login = Login::new(None, None, Some("pass".to_string())).apply(ctx()?, ()); + assert!(login.is_err()); + + Ok(()) +} + +#[test] +pub fn leo_update_and_update_automatic() -> Result<()> { + Update::new(true, true, None).apply(ctx()?, ())?; + Update::new(false, true, None).apply(ctx()?, ())?; + Update::new(false, false, None).apply(ctx()?, ())?; + + Update::new(false, false, Some(UpdateAutomatic::Automatic { value: true })).apply(ctx()?, ())?; + Update::new(false, false, Some(UpdateAutomatic::Automatic { value: false })).apply(ctx()?, ())?; + + Ok(()) +} + +/// Create context for Pedersen Hash example +fn ctx() -> Result { + let path = PathBuf::from(&PEDERSEN_HASH_PATH); + let ctx = create_context(path)?; + + Ok(ctx) +} diff --git a/leo/updater.rs b/leo/updater.rs index fe50b3ba90..cc5c8972da 100644 --- a/leo/updater.rs +++ b/leo/updater.rs @@ -13,8 +13,10 @@ // You should have received a copy of the GNU General Public License // along with the Leo library. If not, see . -use crate::{config::Config, errors::UpdaterError}; +use crate::config::Config; + +use anyhow::{anyhow, Result}; use colored::Colorize; use self_update::{backends::github, version::bump_is_greater, Status}; @@ -27,7 +29,7 @@ impl Updater { const LEO_REPO_OWNER: &'static str = "AleoHQ"; /// Show all available releases for `leo`. - pub fn show_available_releases() -> Result<(), UpdaterError> { + pub fn show_available_releases() -> Result<()> { let releases = github::ReleaseList::configure() .repo_owner(Self::LEO_REPO_OWNER) .repo_name(Self::LEO_REPO_NAME) @@ -46,7 +48,7 @@ impl Updater { } /// Update `leo` to the latest release. - pub fn update_to_latest_release(show_output: bool) -> Result { + pub fn update_to_latest_release(show_output: bool) -> Result { let status = github::Update::configure() .repo_owner(Self::LEO_REPO_OWNER) .repo_name(Self::LEO_REPO_NAME) @@ -62,7 +64,7 @@ impl Updater { } /// Check if there is an available update for `leo` and return the newest release. - pub fn update_available() -> Result { + pub fn update_available() -> Result { let updater = github::Update::configure() .repo_owner(Self::LEO_REPO_OWNER) .repo_name(Self::LEO_REPO_NAME) @@ -76,7 +78,11 @@ impl Updater { if bump_is_greater(¤t_version, &latest_release.version)? { Ok(latest_release.version) } else { - Err(UpdaterError::OldReleaseVersion(current_version, latest_release.version)) + Err(anyhow!( + "Old release version {} {}", + current_version, + latest_release.version + )) } }