mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-09-17 16:58:21 +03:00
Merge branch 'mainnet' into fix/use-generic-network-when-possible
This commit is contained in:
commit
5f89f620e9
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1972,6 +1972,7 @@ dependencies = [
|
||||
"toml 0.8.12",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"ureq",
|
||||
"walkdir",
|
||||
"zip",
|
||||
]
|
||||
|
@ -64,6 +64,9 @@ default = [ ]
|
||||
ci_skip = [ "leo-compiler/ci_skip" ]
|
||||
noconfig = [ ]
|
||||
|
||||
[dependencies]
|
||||
ureq = "2.9.7"
|
||||
|
||||
[dependencies.leo-ast]
|
||||
path = "./compiler/ast"
|
||||
version = "=1.11.0"
|
||||
|
@ -371,10 +371,17 @@ create_messages!(
|
||||
}
|
||||
|
||||
@backtraced
|
||||
missing_on_chain_program_name {
|
||||
args: (),
|
||||
msg: "The name of the program to execute on-chain is missing.".to_string(),
|
||||
help: Some("Either set `--local` to execute the local program on chain, or set `--program <PROGRAM>`.".to_string()),
|
||||
invalid_file_name_dependency {
|
||||
args: (name: impl Display),
|
||||
msg: format!("The dependency program name `{name}` is invalid."),
|
||||
help: Some("Aleo program names must only contain lower case letters, numbers and underscores.".to_string()),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
dependency_not_found {
|
||||
args: (name: impl Display),
|
||||
msg: format!("The dependency program `{name}` was not found among the manifest's dependencies."),
|
||||
help: None,
|
||||
}
|
||||
|
||||
@backtraced
|
||||
@ -383,4 +390,11 @@ create_messages!(
|
||||
msg: format!("Conflicting program names given to execute on chain: `{first}` and `{second}`."),
|
||||
help: Some("Either set `--local` to execute the local program on chain, or set `--program <PROGRAM>`.".to_string()),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
missing_on_chain_program_name {
|
||||
args: (),
|
||||
msg: "The name of the program to execute on-chain is missing.".to_string(),
|
||||
help: Some("Either set `--local` to execute the local program on chain, or set `--program <PROGRAM>`.".to_string()),
|
||||
}
|
||||
);
|
||||
|
@ -140,8 +140,8 @@ create_messages!(
|
||||
|
||||
@formatted
|
||||
failed_to_retrieve_from_endpoint {
|
||||
args: (endpoint: impl Display, error: impl ErrorArg),
|
||||
msg: format!("Failed to retrieve from endpoint `{endpoint}`. Error: {error}"),
|
||||
args: (error: impl ErrorArg),
|
||||
msg: format!("{error}"),
|
||||
help: None,
|
||||
}
|
||||
|
||||
@ -151,4 +151,46 @@ create_messages!(
|
||||
msg: format!("Compiled file at `{path}` does not exist, cannot compile parent."),
|
||||
help: Some("If you were using the `--non-recursive` flag, remove it and try again.".to_string()),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_input_id_len {
|
||||
args: (input: impl Display, expected_type: impl Display),
|
||||
msg: format!("Invalid input: {input}."),
|
||||
help: Some(format!("Type `{expected_type}` must contain exactly 61 lowercase characters or numbers.")),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_input_id {
|
||||
args: (input: impl Display, expected_type: impl Display, expected_preface: impl Display),
|
||||
msg: format!("Invalid input: {input}."),
|
||||
help: Some(format!("Type `{expected_type}` must start with \"{expected_preface}\".")),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_numerical_input {
|
||||
args: (input: impl Display),
|
||||
msg: format!("Invalid numerical input: {input}."),
|
||||
help: Some("Input must be a valid u32.".to_string()),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_range {
|
||||
args: (),
|
||||
msg: "The range must be less than or equal to 50 blocks.".to_string(),
|
||||
help: None,
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_height_or_hash {
|
||||
args: (input: impl Display),
|
||||
msg: format!("Invalid input: {input}."),
|
||||
help: Some("Input must be a valid height or hash. Valid hashes are 61 characters long, composed of only numbers and lower case letters, and be prefaced with \"ab1\".".to_string()),
|
||||
}
|
||||
|
||||
@backtraced
|
||||
invalid_field {
|
||||
args: (field: impl Display),
|
||||
msg: format!("Invalid field: {field}."),
|
||||
help: Some("Field element must be numerical string with optional \"field\" suffix.".to_string()),
|
||||
}
|
||||
);
|
||||
|
@ -33,10 +33,10 @@ pub struct CLI {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
|
||||
#[clap(long, global = true, help = "Optional path to Leo program root folder")]
|
||||
#[clap(long, global = true, help = "Path to Leo program root folder")]
|
||||
path: Option<PathBuf>,
|
||||
|
||||
#[clap(long, global = true, help = "Optional path to aleo program registry.")]
|
||||
#[clap(long, global = true, help = "Path to aleo program registry")]
|
||||
pub home: Option<PathBuf>,
|
||||
}
|
||||
|
||||
@ -48,11 +48,6 @@ enum Commands {
|
||||
#[clap(subcommand)]
|
||||
command: Account,
|
||||
},
|
||||
#[clap(about = "Add a new on-chain or local dependency to the current package.")]
|
||||
Add {
|
||||
#[clap(flatten)]
|
||||
command: Add,
|
||||
},
|
||||
#[clap(about = "Create a new Leo package in a new directory")]
|
||||
New {
|
||||
#[clap(flatten)]
|
||||
@ -78,12 +73,26 @@ enum Commands {
|
||||
#[clap(flatten)]
|
||||
command: Deploy,
|
||||
},
|
||||
|
||||
#[clap(about = "Query live data from the Aleo network")]
|
||||
Query {
|
||||
#[clap(flatten)]
|
||||
command: Query,
|
||||
},
|
||||
#[clap(about = "Compile the current package as a program")]
|
||||
Build {
|
||||
#[clap(flatten)]
|
||||
command: Build,
|
||||
},
|
||||
#[clap(about = "Add a new on-chain or local dependency to the current package.")]
|
||||
Add {
|
||||
#[clap(flatten)]
|
||||
command: Add,
|
||||
},
|
||||
#[clap(about = "Remove a dependency from the current package.")]
|
||||
Remove {
|
||||
#[clap(flatten)]
|
||||
command: Remove,
|
||||
},
|
||||
#[clap(about = "Clean the output directory")]
|
||||
Clean {
|
||||
#[clap(flatten)]
|
||||
@ -140,11 +149,13 @@ pub fn run_with_args(cli: CLI) -> Result<()> {
|
||||
|
||||
command.try_execute(context)
|
||||
}
|
||||
Commands::Query { command } => command.try_execute(context),
|
||||
Commands::Clean { command } => command.try_execute(context),
|
||||
Commands::Deploy { command } => command.try_execute(context),
|
||||
Commands::Example { command } => command.try_execute(context),
|
||||
Commands::Run { command } => command.try_execute(context),
|
||||
Commands::Execute { command } => command.try_execute(context),
|
||||
Commands::Remove { command } => command.try_execute(context),
|
||||
Commands::Update { command } => command.try_execute(context),
|
||||
}
|
||||
}
|
||||
@ -401,6 +412,7 @@ function external_nested_function:
|
||||
name: "nested_example_layer_0".to_string(),
|
||||
local: None,
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(project_directory.clone()),
|
||||
@ -494,6 +506,7 @@ program child.aleo {
|
||||
name: "parent".to_string(),
|
||||
local: Some(parent_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(grandparent_directory.clone()),
|
||||
@ -508,6 +521,7 @@ program child.aleo {
|
||||
name: "child".to_string(),
|
||||
local: Some(child_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(grandparent_directory.clone()),
|
||||
@ -522,6 +536,7 @@ program child.aleo {
|
||||
name: "child".to_string(),
|
||||
local: Some(child_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(parent_directory.clone()),
|
||||
@ -645,6 +660,7 @@ program outer.aleo {
|
||||
name: "inner_1".to_string(),
|
||||
local: Some(inner_1_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(outer_directory.clone()),
|
||||
@ -659,6 +675,7 @@ program outer.aleo {
|
||||
name: "inner_2".to_string(),
|
||||
local: Some(inner_2_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(outer_directory.clone()),
|
||||
@ -812,6 +829,7 @@ program outer_2.aleo {
|
||||
name: "inner_1".to_string(),
|
||||
local: Some(inner_1_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(outer_directory.clone()),
|
||||
@ -826,6 +844,7 @@ program outer_2.aleo {
|
||||
name: "inner_2".to_string(),
|
||||
local: Some(inner_2_directory.clone()),
|
||||
network: "mainnet".to_string(),
|
||||
clear: false,
|
||||
},
|
||||
},
|
||||
path: Some(outer_directory.clone()),
|
||||
|
@ -18,18 +18,21 @@ use super::*;
|
||||
use leo_retriever::{Dependency, Location, Manifest, NetworkName};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Clean outputs folder command
|
||||
/// Add a new on-chain or local dependency to the current package.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(name = "leo", author = "The Aleo Team <hello@aleo.org>", version)]
|
||||
pub struct Add {
|
||||
#[clap(name = "NAME", help = "The dependency name")]
|
||||
#[clap(name = "NAME", help = "The dependency name. Ex: `credits.aleo` or `credits`.")]
|
||||
pub(crate) name: String,
|
||||
|
||||
#[clap(short = 'l', long, help = "Optional path to local dependency")]
|
||||
#[clap(short = 'l', long, help = "Path to local dependency")]
|
||||
pub(crate) local: Option<PathBuf>,
|
||||
|
||||
#[clap(short = 'n', long, help = "Optional name of the network to use", default_value = "mainnet")]
|
||||
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
|
||||
pub(crate) network: String,
|
||||
|
||||
#[clap(short = 'c', long, help = "Clear all previous dependencies.", default_value = "false")]
|
||||
pub(crate) clear: bool,
|
||||
}
|
||||
|
||||
impl Command for Add {
|
||||
@ -47,41 +50,74 @@ impl Command for Add {
|
||||
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
let path = context.dir()?;
|
||||
|
||||
// Deserialize the manifest
|
||||
// Deserialize the manifest.
|
||||
let program_data: String = std::fs::read_to_string(path.join("program.json"))
|
||||
.map_err(|err| PackageError::failed_to_read_file(path.to_str().unwrap(), err))?;
|
||||
let manifest: Manifest = serde_json::from_str(&program_data)
|
||||
.map_err(|err| PackageError::failed_to_deserialize_manifest_file(path.to_str().unwrap(), err))?;
|
||||
|
||||
// Allow both `credits.aleo` and `credits` syntax
|
||||
let name = match self.name {
|
||||
name if name.ends_with(".aleo") => name,
|
||||
name => format!("{}.aleo", name),
|
||||
// Make sure the program name is valid.
|
||||
// Allow both `credits.aleo` and `credits` syntax.
|
||||
let name: String = match &self.name {
|
||||
name if name.ends_with(".aleo")
|
||||
&& Package::<CurrentNetwork>::is_aleo_name_valid(&name[0..self.name.len() - 5]) =>
|
||||
{
|
||||
name.clone()
|
||||
}
|
||||
name if Package::<CurrentNetwork>::is_aleo_name_valid(name) => format!("{name}.aleo"),
|
||||
name => return Err(PackageError::invalid_file_name_dependency(name).into()),
|
||||
};
|
||||
|
||||
// Add dependency section to manifest if it doesn't exist
|
||||
let mut dependencies = match manifest.dependencies() {
|
||||
Some(ref dependencies) => dependencies
|
||||
// Add dependency section to manifest if it doesn't exist.
|
||||
let mut dependencies = match (self.clear, manifest.dependencies()) {
|
||||
(false, Some(ref dependencies)) => dependencies
|
||||
.iter()
|
||||
.filter_map(|dependency| {
|
||||
// Overwrite old dependencies of the same name.
|
||||
if dependency.name() == &name {
|
||||
println!("{} already exists as a dependency. Overwriting.", name);
|
||||
let msg = match (dependency.path(), dependency.network()) {
|
||||
(Some(local_path), _) => {
|
||||
format!("local dependency at path `{}`", local_path.to_str().unwrap().replace('\"', ""))
|
||||
}
|
||||
(_, Some(network)) => {
|
||||
format!("network dependency from `{}`", network)
|
||||
}
|
||||
_ => "git dependency".to_string(),
|
||||
};
|
||||
tracing::warn!("⚠️ Program `{name}` already exists as a {msg}. Overwriting.");
|
||||
None
|
||||
} else if self.local.is_some() && &self.local == dependency.path() {
|
||||
// Overwrite old dependencies at the same local path.
|
||||
tracing::warn!(
|
||||
"⚠️ Path `{}` already exists as the location for local dependency `{}`. Overwriting.",
|
||||
self.local.clone().unwrap().to_str().unwrap().replace('\"', ""),
|
||||
dependency.name()
|
||||
);
|
||||
None
|
||||
} else {
|
||||
Some(dependency.clone())
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
// Add new dependency to manifest
|
||||
// Add new dependency to the manifest.
|
||||
dependencies.push(match self.local {
|
||||
Some(local_path) => Dependency::new(name, Location::Local, None, Some(local_path)),
|
||||
None => Dependency::new(name, Location::Network, Some(NetworkName::from(&self.network)), None),
|
||||
Some(local_path) => {
|
||||
tracing::info!(
|
||||
"✅ Added local dependency to program `{name}` at path `{}`.",
|
||||
local_path.to_str().unwrap().replace('\"', "")
|
||||
);
|
||||
Dependency::new(name, Location::Local, None, Some(local_path))
|
||||
}
|
||||
None => {
|
||||
tracing::info!("✅ Added network dependency to program `{name}` from network `{}`.", self.network);
|
||||
Dependency::new(name, Location::Network, Some(NetworkName::from(&self.network)), None)
|
||||
}
|
||||
});
|
||||
|
||||
// Update manifest
|
||||
// Update the manifest file.
|
||||
let new_manifest = Manifest::new(
|
||||
manifest.program(),
|
||||
manifest.version(),
|
||||
|
@ -189,10 +189,6 @@ fn compile_leo_file(
|
||||
options: BuildOptions,
|
||||
stubs: IndexMap<Symbol, Stub>,
|
||||
) -> Result<()> {
|
||||
// Construct the Leo file name with extension `foo.leo`.
|
||||
let file_name =
|
||||
file_path.file_name().and_then(|name| name.to_str()).ok_or_else(PackageError::failed_to_get_file_name)?;
|
||||
|
||||
// Construct program name from the program_id found in `package.json`.
|
||||
let program_name = program_id.name().to_string();
|
||||
|
||||
@ -202,7 +198,7 @@ fn compile_leo_file(
|
||||
|
||||
// Create a new instance of the Leo compiler.
|
||||
let mut compiler = Compiler::<CurrentNetwork>::new(
|
||||
program_name,
|
||||
program_name.clone(),
|
||||
program_id.network().to_string(),
|
||||
handler,
|
||||
file_path.clone(),
|
||||
@ -220,6 +216,6 @@ fn compile_leo_file(
|
||||
.write_all(instructions.as_bytes())
|
||||
.map_err(CliError::failed_to_load_instructions)?;
|
||||
|
||||
tracing::info!("✅ Compiled '{}' into Aleo instructions", file_name);
|
||||
tracing::info!("✅ Compiled '{program_name}.aleo' into Aleo instructions");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -35,12 +35,18 @@ pub use example::Example;
|
||||
pub mod execute;
|
||||
pub use execute::Execute;
|
||||
|
||||
pub mod query;
|
||||
pub use query::Query;
|
||||
|
||||
pub mod new;
|
||||
pub use new::New;
|
||||
|
||||
// pub mod node;
|
||||
// pub use node::Node;
|
||||
|
||||
pub mod remove;
|
||||
pub use remove::Remove;
|
||||
|
||||
pub mod run;
|
||||
pub use run::Run;
|
||||
|
||||
|
93
leo/cli/commands/query/block.rs
Normal file
93
leo/cli/commands/query/block.rs
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::cli::context::Context;
|
||||
use clap::Parser;
|
||||
|
||||
// Query on-chain information related to blocks.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Block {
|
||||
#[clap(help = "Fetch a block by specifying its height or hash", required_unless_present_any = &["latest", "latest_hash", "latest_height", "range"])]
|
||||
pub(crate) id: Option<String>,
|
||||
#[arg(short, long, help = "Get the latest block", default_value = "false", conflicts_with_all(["latest_hash", "latest_height", "range", "transactions", "to_height"]))]
|
||||
pub(crate) latest: bool,
|
||||
#[arg(short, long, help = "Get the latest block hash", default_value = "false", conflicts_with_all(["latest", "latest_height", "range", "transactions", "to_height"]))]
|
||||
pub(crate) latest_hash: bool,
|
||||
#[arg(short, long, help = "Get the latest block height", default_value = "false", conflicts_with_all(["latest", "latest_hash", "range", "transactions", "to_height"]))]
|
||||
pub(crate) latest_height: bool,
|
||||
#[arg(short, long, help = "Get up to 50 consecutive blocks", number_of_values = 2, value_names = &["START_HEIGHT", "END_HEIGHT"], conflicts_with_all(["latest", "latest_hash", "latest_height", "transactions", "to_height"]))]
|
||||
pub(crate) range: Option<Vec<String>>,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Get all transactions at the specified block height",
|
||||
conflicts_with("to_height"),
|
||||
default_value = "false"
|
||||
)]
|
||||
pub(crate) transactions: bool,
|
||||
#[arg(short, long, help = "Lookup the block height corresponding to a hash value", default_value = "false")]
|
||||
pub(crate) to_height: bool,
|
||||
}
|
||||
|
||||
impl Command for Block {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _input: Self::Input) -> Result<Self::Output> {
|
||||
// Build custom url to fetch from based on the flags and user's input.
|
||||
let url = if self.latest_height {
|
||||
"block/height/latest".to_string()
|
||||
} else if self.latest_hash {
|
||||
"block/hash/latest".to_string()
|
||||
} else if self.latest {
|
||||
"block/latest".to_string()
|
||||
} else if let Some(range) = self.range {
|
||||
// Make sure the range is composed of valid numbers.
|
||||
is_valid_numerical_input(&range[0])?;
|
||||
is_valid_numerical_input(&range[1])?;
|
||||
|
||||
// Make sure the range is not too large.
|
||||
if range[1].parse::<u32>().unwrap() - range[0].parse::<u32>().unwrap() > 50 {
|
||||
return Err(UtilError::invalid_range().into());
|
||||
}
|
||||
format!("blocks?start={}&end={}", range[0], range[1])
|
||||
} else if self.transactions {
|
||||
is_valid_numerical_input(&self.id.clone().unwrap())?;
|
||||
format!("block/{}/transactions", self.id.unwrap()).to_string()
|
||||
} else if self.to_height {
|
||||
let id = self.id.unwrap();
|
||||
is_valid_hash(&id)?;
|
||||
format!("height/{}", id).to_string()
|
||||
} else if let Some(id) = self.id {
|
||||
is_valid_height_or_hash(&id)?;
|
||||
format!("block/{}", id)
|
||||
} else {
|
||||
unreachable!("All cases are covered")
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
}
|
40
leo/cli/commands/query/committee.rs
Normal file
40
leo/cli/commands/query/committee.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Query the committee.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Committee {}
|
||||
|
||||
impl Command for Committee {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
Ok("/committee/latest".to_string())
|
||||
}
|
||||
}
|
69
leo/cli/commands/query/mempool.rs
Normal file
69
leo/cli/commands/query/mempool.rs
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::cli::context::Context;
|
||||
use clap::Parser;
|
||||
|
||||
// Query transactions and transmissions from the memory pool.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Mempool {
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Get the memory pool transactions",
|
||||
default_value = "false",
|
||||
required_unless_present = "transmissions",
|
||||
conflicts_with("transmissions")
|
||||
)]
|
||||
pub(crate) transactions: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Get the memory pool transmissions",
|
||||
default_value = "false",
|
||||
required_unless_present = "transactions",
|
||||
conflicts_with("transactions")
|
||||
)]
|
||||
pub(crate) transmissions: bool,
|
||||
}
|
||||
|
||||
impl Command for Mempool {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _input: Self::Input) -> Result<Self::Output> {
|
||||
// Build custom url to fetch from based on the flags and user's input.
|
||||
let url = if self.transactions {
|
||||
"memoryPool/transactions".to_string()
|
||||
} else if self.transmissions {
|
||||
"memoryPool/transmissions".to_string()
|
||||
} else {
|
||||
unreachable!("All cases are covered")
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
}
|
153
leo/cli/commands/query/mod.rs
Normal file
153
leo/cli/commands/query/mod.rs
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
mod block;
|
||||
use block::Block;
|
||||
|
||||
mod program;
|
||||
use program::Program;
|
||||
|
||||
mod state_root;
|
||||
use state_root::StateRoot;
|
||||
|
||||
mod committee;
|
||||
use committee::Committee;
|
||||
|
||||
mod mempool;
|
||||
use mempool::Mempool;
|
||||
|
||||
mod peers;
|
||||
use peers::Peers;
|
||||
|
||||
mod transaction;
|
||||
use transaction::Transaction;
|
||||
|
||||
mod utils;
|
||||
use utils::*;
|
||||
|
||||
use leo_errors::UtilError;
|
||||
|
||||
/// Query live data from the Aleo network.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Query {
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
global = true,
|
||||
help = "Endpoint to retrieve network state from. Defaults to http://api.explorer.aleo.org/v1.",
|
||||
default_value = "http://api.explorer.aleo.org/v1"
|
||||
)]
|
||||
pub endpoint: String,
|
||||
#[clap(short, long, global = true, help = "Network to use. Defaults to testnet3.", default_value = "testnet3")]
|
||||
pub(crate) network: String,
|
||||
#[clap(subcommand)]
|
||||
command: QueryCommands,
|
||||
}
|
||||
|
||||
impl Command for Query {
|
||||
type Input = ();
|
||||
type Output = ();
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
let output = match self.command {
|
||||
QueryCommands::Block { command } => command.apply(context, ())?,
|
||||
QueryCommands::Transaction { command } => command.apply(context, ())?,
|
||||
QueryCommands::Program { command } => command.apply(context, ())?,
|
||||
QueryCommands::Stateroot { command } => command.apply(context, ())?,
|
||||
QueryCommands::Committee { command } => command.apply(context, ())?,
|
||||
QueryCommands::Mempool { command } => {
|
||||
if self.endpoint == "http://api.explorer.aleo.org/v1" {
|
||||
tracing::warn!(
|
||||
"⚠️ `leo query mempool` is only valid when using a custom endpoint. Specify one using `--endpoint`."
|
||||
);
|
||||
}
|
||||
command.apply(context, ())?
|
||||
}
|
||||
QueryCommands::Peers { command } => {
|
||||
if self.endpoint == "http://api.explorer.aleo.org/v1" {
|
||||
tracing::warn!(
|
||||
"⚠️ `leo query peers` is only valid when using a custom endpoint. Specify one using `--endpoint`."
|
||||
);
|
||||
}
|
||||
command.apply(context, ())?
|
||||
}
|
||||
};
|
||||
|
||||
// Make GET request to retrieve on-chain state.
|
||||
let url = format!("{}/{}/{}", self.endpoint, self.network, output);
|
||||
let response = ureq::get(&url.clone())
|
||||
.set(&format!("X-Aleo-Leo-{}", env!("CARGO_PKG_VERSION")), "true")
|
||||
.call()
|
||||
.map_err(|err| UtilError::failed_to_retrieve_from_endpoint(err, Default::default()))?;
|
||||
if response.status() == 200 {
|
||||
tracing::info!("✅ Successfully retrieved data from '{url}'.\n");
|
||||
// Unescape the newlines.
|
||||
println!("{}\n", response.into_string().unwrap().replace("\\n", "\n"));
|
||||
Ok(())
|
||||
} else {
|
||||
Err(UtilError::network_error(url, response.status(), Default::default()).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
enum QueryCommands {
|
||||
#[clap(about = "Query block information")]
|
||||
Block {
|
||||
#[clap(flatten)]
|
||||
command: Block,
|
||||
},
|
||||
#[clap(about = "Query transaction information")]
|
||||
Transaction {
|
||||
#[clap(flatten)]
|
||||
command: Transaction,
|
||||
},
|
||||
#[clap(about = "Query program source code and live mapping values")]
|
||||
Program {
|
||||
#[clap(flatten)]
|
||||
command: Program,
|
||||
},
|
||||
#[clap(about = "Query the latest stateroot")]
|
||||
Stateroot {
|
||||
#[clap(flatten)]
|
||||
command: StateRoot,
|
||||
},
|
||||
#[clap(about = "Query the current committee")]
|
||||
Committee {
|
||||
#[clap(flatten)]
|
||||
command: Committee,
|
||||
},
|
||||
#[clap(about = "Query transactions and transmissions from the memory pool")]
|
||||
Mempool {
|
||||
#[clap(flatten)]
|
||||
command: Mempool,
|
||||
},
|
||||
#[clap(about = "Query peer information")]
|
||||
Peers {
|
||||
#[clap(flatten)]
|
||||
command: Peers,
|
||||
},
|
||||
}
|
61
leo/cli/commands/query/peers.rs
Normal file
61
leo/cli/commands/query/peers.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::cli::context::Context;
|
||||
use clap::Parser;
|
||||
|
||||
// Query information about network peers.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Peers {
|
||||
#[arg(short, long, help = "Get all peer metrics", default_value = "false", conflicts_with("count"))]
|
||||
pub(crate) metrics: bool,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Get the count of all participating peers",
|
||||
default_value = "false",
|
||||
conflicts_with("metrics")
|
||||
)]
|
||||
pub(crate) count: bool,
|
||||
}
|
||||
|
||||
impl Command for Peers {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _input: Self::Input) -> Result<Self::Output> {
|
||||
// Build custom url to fetch from based on the flags and user's input.
|
||||
let url = if self.metrics {
|
||||
"peers/all/metrics".to_string()
|
||||
} else if self.count {
|
||||
"peers/count".to_string()
|
||||
} else {
|
||||
"peers/all".to_string()
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
}
|
67
leo/cli/commands/query/program.rs
Normal file
67
leo/cli/commands/query/program.rs
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use clap::Parser;
|
||||
use leo_package::package::Package;
|
||||
|
||||
/// Query program source code and live mapping values.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Program {
|
||||
#[clap(name = "NAME", help = "The name of the program to fetch")]
|
||||
pub(crate) name: String,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "Get all mappings defined in the program",
|
||||
default_value = "false",
|
||||
conflicts_with = "mapping_value"
|
||||
)]
|
||||
pub(crate) mappings: bool,
|
||||
#[arg(short, long, help = "Get the value corresponding to the specified mapping and key.", number_of_values = 2, value_names = &["MAPPING", "KEY"], conflicts_with = "mappings")]
|
||||
pub(crate) mapping_value: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
impl Command for Program {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
// Check that the program name is valid.
|
||||
let program = check_valid_program_name(self.name);
|
||||
// Build custom url to fetch from based on the flags and user's input.
|
||||
let url = if let Some(mapping_info) = self.mapping_value {
|
||||
// Check that the mapping name is valid.
|
||||
Package::<CurrentNetwork>::is_aleo_name_valid(&mapping_info[0]);
|
||||
format!("program/{}/mapping/{}/{}", program, mapping_info[0], mapping_info[1])
|
||||
} else if self.mappings {
|
||||
format!("program/{}/mappings", program)
|
||||
} else {
|
||||
format!("program/{}", program)
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
}
|
40
leo/cli/commands/query/state_root.rs
Normal file
40
leo/cli/commands/query/state_root.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Query the latest stateroot.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct StateRoot {}
|
||||
|
||||
impl Command for StateRoot {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
Ok("/stateRoot/latest".to_string())
|
||||
}
|
||||
}
|
68
leo/cli/commands/query/transaction.rs
Normal file
68
leo/cli/commands/query/transaction.rs
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
/// Query transaction information.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Transaction {
|
||||
#[clap(name = "ID", help = "The id of the transaction to fetch", required_unless_present_any = &["from_program", "from_transition", "from_io", "range"])]
|
||||
pub(crate) id: Option<String>,
|
||||
#[arg(short, long, help = "Get the transaction only if it has been confirmed", default_value = "false", conflicts_with_all(["from_io", "from_transition", "from_program"]))]
|
||||
pub(crate) confirmed: bool,
|
||||
#[arg(value_name = "INPUT_OR_OUTPUT_ID", short, long, help = "Get the transition id that an input or output id occurred in", conflicts_with_all(["from_program", "from_transition", "confirmed", "id"]))]
|
||||
pub(crate) from_io: Option<String>,
|
||||
#[arg(value_name = "TRANSITION_ID", short, long, help = "Get the id of the transaction containing the specified transition", conflicts_with_all(["from_io", "from_program", "confirmed", "id"]))]
|
||||
pub(crate) from_transition: Option<String>,
|
||||
#[arg(value_name = "PROGRAM", short, long, help = "Get the id of the transaction id that the specified program was deployed in", conflicts_with_all(["from_io", "from_transition", "confirmed", "id"]))]
|
||||
pub(crate) from_program: Option<String>,
|
||||
}
|
||||
|
||||
impl Command for Transaction {
|
||||
type Input = ();
|
||||
type Output = String;
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _context: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, _context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
// Build custom url to fetch from based on the flags and user's input.
|
||||
let url = if let Some(io_id) = self.from_io {
|
||||
let field = is_valid_field(&io_id)?;
|
||||
format!("find/transitionID/{field}")
|
||||
} else if let Some(transition) = self.from_transition {
|
||||
is_valid_transition_id(&transition)?;
|
||||
format!("find/transactionID/{transition}")
|
||||
} else if let Some(program) = self.from_program {
|
||||
// Check that the program name is valid.
|
||||
format!("find/transactionID/deployment/{}", check_valid_program_name(program))
|
||||
} else if let Some(id) = self.id {
|
||||
is_valid_transaction_id(&id)?;
|
||||
if self.confirmed { format!("transaction/confirmed/{}", id) } else { format!("transaction/{}", id) }
|
||||
} else {
|
||||
unreachable!("All command paths covered.")
|
||||
};
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
}
|
94
leo/cli/commands/query/utils.rs
Normal file
94
leo/cli/commands/query/utils.rs
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
|
||||
use leo_errors::{LeoError, Result, UtilError};
|
||||
|
||||
use leo_package::package::Package;
|
||||
|
||||
// A valid hash is 61 characters long, with preface "ab1" and all characters lowercase or numbers.
|
||||
pub fn is_valid_hash(hash: &str) -> Result<(), LeoError> {
|
||||
if hash.len() != 61 {
|
||||
Err(UtilError::invalid_input_id_len(hash, "hash").into())
|
||||
} else if !hash.starts_with("ab1") && hash.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) {
|
||||
Err(UtilError::invalid_input_id(hash, "hash", "ab1").into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// A valid transaction id is 61 characters long, with preface "at1" and all characters lowercase or numbers.
|
||||
pub fn is_valid_transaction_id(transaction: &str) -> Result<(), LeoError> {
|
||||
if transaction.len() != 61 {
|
||||
Err(UtilError::invalid_input_id_len(transaction, "transaction").into())
|
||||
} else if !transaction.starts_with("at1")
|
||||
&& transaction.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
|
||||
{
|
||||
Err(UtilError::invalid_input_id(transaction, "transaction", "at1").into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// A valid transition id is 61 characters long, with preface "au1" and all characters lowercase or numbers.
|
||||
pub fn is_valid_transition_id(transition: &str) -> Result<(), LeoError> {
|
||||
if transition.len() != 61 {
|
||||
Err(UtilError::invalid_input_id_len(transition, "transition").into())
|
||||
} else if !transition.starts_with("au1") && transition.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
|
||||
{
|
||||
Err(UtilError::invalid_input_id(transition, "transition", "au1").into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// A valid numerical input is a u32.
|
||||
pub fn is_valid_numerical_input(num: &str) -> Result<(), LeoError> {
|
||||
if num.parse::<u32>().is_err() { Err(UtilError::invalid_numerical_input(num).into()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
// A valid height or hash.
|
||||
pub fn is_valid_height_or_hash(input: &str) -> Result<(), LeoError> {
|
||||
match (is_valid_hash(input), is_valid_numerical_input(input)) {
|
||||
(Ok(_), _) | (_, Ok(_)) => Ok(()),
|
||||
_ => Err(UtilError::invalid_height_or_hash(input).into()),
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the string is a valid field, allowing for optional `field` suffix.
|
||||
pub fn is_valid_field(field: &str) -> Result<String, LeoError> {
|
||||
let split = field.split("field").collect::<Vec<&str>>();
|
||||
|
||||
if split.len() == 1 && split[0].chars().all(|c| c.is_numeric()) {
|
||||
Ok(format!("{}field", field))
|
||||
} else if split.len() == 2 && split[0].chars().all(|c| c.is_numeric()) && split[1].is_empty() {
|
||||
Ok(field.to_string())
|
||||
} else {
|
||||
Err(UtilError::invalid_field(field).into())
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the string is a valid program name in Aleo.
|
||||
pub fn check_valid_program_name(name: String) -> String {
|
||||
if name.ends_with(".aleo") {
|
||||
Package::<CurrentNetwork>::is_aleo_name_valid(&name[0..name.len() - 5]);
|
||||
name
|
||||
} else {
|
||||
Package::<CurrentNetwork>::is_aleo_name_valid(&name);
|
||||
format!("{}.aleo", name)
|
||||
}
|
||||
}
|
130
leo/cli/commands/remove.rs
Normal file
130
leo/cli/commands/remove.rs
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright (C) 2019-2023 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use leo_retriever::{Dependency, Manifest};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Remove a dependency from the current package.
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(name = "leo", author = "The Aleo Team <hello@aleo.org>", version)]
|
||||
pub struct Remove {
|
||||
#[clap(
|
||||
name = "NAME",
|
||||
help = "The dependency name. Ex: `credits.aleo` or `credits`.",
|
||||
required_unless_present = "all"
|
||||
)]
|
||||
pub(crate) name: Option<String>,
|
||||
|
||||
#[clap(short = 'l', long, help = "Path to local dependency")]
|
||||
pub(crate) local: Option<PathBuf>,
|
||||
|
||||
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "testnet3")]
|
||||
pub(crate) network: String,
|
||||
|
||||
#[clap(long, help = "Clear all previous dependencies.", default_value = "false")]
|
||||
pub(crate) all: bool,
|
||||
}
|
||||
|
||||
impl Command for Remove {
|
||||
type Input = ();
|
||||
type Output = ();
|
||||
|
||||
fn log_span(&self) -> Span {
|
||||
tracing::span!(tracing::Level::INFO, "Leo")
|
||||
}
|
||||
|
||||
fn prelude(&self, _: Context) -> Result<Self::Input> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
|
||||
let path = context.dir()?;
|
||||
|
||||
// TODO: Dedup with Add Command. Requires merging utils/retriever/program_context with leo/package as both involve modifying the manifest.
|
||||
// Deserialize the manifest.
|
||||
let program_data: String = std::fs::read_to_string(path.join("program.json"))
|
||||
.map_err(|err| PackageError::failed_to_read_file(path.to_str().unwrap(), err))?;
|
||||
let manifest: Manifest = serde_json::from_str(&program_data)
|
||||
.map_err(|err| PackageError::failed_to_deserialize_manifest_file(path.to_str().unwrap(), err))?;
|
||||
|
||||
let dependencies: Vec<Dependency> = if !self.all {
|
||||
// Make sure the program name is valid.
|
||||
// Allow both `credits.aleo` and `credits` syntax.
|
||||
let name: String = match &self.name {
|
||||
Some(name)
|
||||
if name.ends_with(".aleo")
|
||||
&& Package::<CurrentNetwork>::is_aleo_name_valid(&name[0..name.len() - 5]) =>
|
||||
{
|
||||
name.clone()
|
||||
}
|
||||
Some(name) if Package::<CurrentNetwork>::is_aleo_name_valid(name) => format!("{name}.aleo"),
|
||||
name => return Err(PackageError::invalid_file_name_dependency(name.clone().unwrap()).into()),
|
||||
};
|
||||
|
||||
let mut found_match = false;
|
||||
let dep = match manifest.dependencies() {
|
||||
Some(ref dependencies) => dependencies
|
||||
.iter()
|
||||
.filter_map(|dependency| {
|
||||
if dependency.name() == &name {
|
||||
found_match = true;
|
||||
let msg = match (dependency.path(), dependency.network()) {
|
||||
(Some(local_path), _) => format!(
|
||||
"local dependency to `{}` from path `{}`",
|
||||
name,
|
||||
local_path.to_str().unwrap().replace('\"', "")
|
||||
),
|
||||
(_, Some(network)) => {
|
||||
format!("network dependency to `{}` from network `{}`", name, network)
|
||||
}
|
||||
_ => format!("git dependency to `{name}`"),
|
||||
};
|
||||
tracing::warn!("✅ Successfully removed the {msg}.");
|
||||
None
|
||||
} else {
|
||||
Some(dependency.clone())
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
// Throw error if no match is found.
|
||||
if !found_match {
|
||||
return Err(PackageError::dependency_not_found(name).into());
|
||||
}
|
||||
|
||||
dep
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// Update the manifest file.
|
||||
let new_manifest = Manifest::new(
|
||||
manifest.program(),
|
||||
manifest.version(),
|
||||
manifest.description(),
|
||||
manifest.license(),
|
||||
Some(dependencies),
|
||||
);
|
||||
let new_manifest_data = serde_json::to_string_pretty(&new_manifest)
|
||||
.map_err(|err| PackageError::failed_to_serialize_manifest_file(path.to_str().unwrap(), err))?;
|
||||
std::fs::write(path.join("program.json"), new_manifest_data).map_err(PackageError::failed_to_write_manifest)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -37,8 +37,8 @@ pub struct Package<N: Network> {
|
||||
|
||||
impl<N: Network> Package<N> {
|
||||
pub fn new(package_name: &str) -> Result<Self> {
|
||||
// Check that the package name is valid.
|
||||
if !Self::is_package_name_valid(package_name) {
|
||||
// Check that the package name is a valid Aleo program name.
|
||||
if !Self::is_aleo_name_valid(package_name) {
|
||||
return Err(PackageError::invalid_package_name(package_name).into());
|
||||
}
|
||||
|
||||
@ -51,35 +51,35 @@ impl<N: Network> Package<N> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the package name is valid.
|
||||
/// Returns `true` if it is a valid Aleo name.
|
||||
///
|
||||
/// Package names can only contain ASCII alphanumeric characters and underscores.
|
||||
pub fn is_package_name_valid(package_name: &str) -> bool {
|
||||
// Check that the package name is nonempty.
|
||||
if package_name.is_empty() {
|
||||
tracing::error!("Project names must be nonempty");
|
||||
/// Aleo names can only contain ASCII alphanumeric characters and underscores.
|
||||
pub fn is_aleo_name_valid(name: &str) -> bool {
|
||||
// Check that the name is nonempty.
|
||||
if name.is_empty() {
|
||||
tracing::error!("Aleo names must be nonempty");
|
||||
return false;
|
||||
}
|
||||
|
||||
let first = package_name.chars().next().unwrap();
|
||||
let first = name.chars().next().unwrap();
|
||||
|
||||
// Check that the first character is not an underscore.
|
||||
if first == '_' {
|
||||
tracing::error!("Project names cannot begin with an underscore");
|
||||
tracing::error!("Aleo names cannot begin with an underscore");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the first character is not a number.
|
||||
if first.is_numeric() {
|
||||
tracing::error!("Project names cannot begin with a number");
|
||||
tracing::error!("Aleo names cannot begin with a number");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate and check that the package name is valid.
|
||||
for current in package_name.chars() {
|
||||
// Check that the package name contains only ASCII alphanumeric or underscores.
|
||||
// Iterate and check that the name is valid.
|
||||
for current in name.chars() {
|
||||
// Check that the program name contains only ASCII alphanumeric or underscores.
|
||||
if !current.is_ascii_alphanumeric() && current != '_' {
|
||||
tracing::error!("Project names must can only contain ASCII alphanumeric characters and underscores.");
|
||||
tracing::error!("Aleo names must can only contain ASCII alphanumeric characters and underscores.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -89,8 +89,8 @@ impl<N: Network> Package<N> {
|
||||
|
||||
/// Returns `true` if a package is can be initialized at a given path.
|
||||
pub fn can_initialize(package_name: &str, path: &Path) -> bool {
|
||||
// Check that the package name is valid.
|
||||
if !Self::is_package_name_valid(package_name) {
|
||||
// Check that the package name is a valid Aleo program name.
|
||||
if !Self::is_aleo_name_valid(package_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -112,8 +112,8 @@ impl<N: Network> Package<N> {
|
||||
|
||||
/// Returns `true` if a package is initialized at the given path
|
||||
pub fn is_initialized(package_name: &str, path: &Path) -> bool {
|
||||
// Check that the package name is valid.
|
||||
if !Self::is_package_name_valid(package_name) {
|
||||
// Check that the package name is a valid Aleo program name.
|
||||
if !Self::is_aleo_name_valid(package_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -173,25 +173,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_is_package_name_valid() {
|
||||
assert!(Package::<CurrentNetwork>::is_package_name_valid("foo"));
|
||||
assert!(Package::<CurrentNetwork>::is_package_name_valid("foo_bar"));
|
||||
assert!(Package::<CurrentNetwork>::is_package_name_valid("foo1"));
|
||||
assert!(Package::<CurrentNetwork>::is_package_name_valid("foo_bar___baz_"));
|
||||
assert!(Package::<CurrentNetwork>::is_aleo_name_valid("foo"));
|
||||
assert!(Package::<CurrentNetwork>::is_aleo_name_valid("foo_bar"));
|
||||
assert!(Package::<CurrentNetwork>::is_aleo_name_valid("foo1"));
|
||||
assert!(Package::<CurrentNetwork>::is_aleo_name_valid("foo_bar___baz_"));
|
||||
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo-bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo-bar-baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo-1"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid(""));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("-"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("-foo"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("-foo-"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("_foo"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo--bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo---bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo--bar--baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo---bar---baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo*bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("foo,bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_package_name_valid("1-foo"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo-bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo-bar-baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo-1"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid(""));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("-"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("-foo"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("-foo-"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("_foo"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo--bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo---bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo--bar--baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo---bar---baz"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo*bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("foo,bar"));
|
||||
assert!(!Package::<CurrentNetwork>::is_aleo_name_valid("1-foo"));
|
||||
}
|
||||
}
|
||||
|
@ -522,7 +522,7 @@ fn fetch_from_network(endpoint: &String, program: &String) -> Result<String, Uti
|
||||
let url = format!("{}/program/{}", endpoint, program);
|
||||
let response = ureq::get(&url.clone())
|
||||
.call()
|
||||
.map_err(|err| UtilError::failed_to_retrieve_from_endpoint(url.clone(), err, Default::default()))?;
|
||||
.map_err(|err| UtilError::failed_to_retrieve_from_endpoint(err, Default::default()))?;
|
||||
if response.status() == 200 {
|
||||
Ok(response.into_string().unwrap())
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user