This commit is contained in:
Pranav Gaddamadugu 2024-05-28 06:41:53 -07:00
parent 48f135cb7c
commit 2cc2177158
23 changed files with 639 additions and 558 deletions

1
Cargo.lock generated
View File

@ -1888,6 +1888,7 @@ dependencies = [
"leo-package",
"leo-parser",
"leo-passes",
"leo-retriever",
"leo-span",
"leo-test-framework",
"rand",

View File

@ -51,12 +51,15 @@ features = [ ]
[dev-dependencies.leo-disassembler]
path = "../../utils/disassembler"
[dev-dependencies.leo-test-framework]
path = "../../tests/test-framework"
[dev-dependencies.leo-package]
path = "../../leo/package"
[dev-dependencies.leo-retriever]
path = "../../utils/retriever"
[dev-dependencies.leo-test-framework]
path = "../../tests/test-framework"
[dev-dependencies.aleo-std-storage]
version = "0.1.7"
default-features = false

View File

@ -27,6 +27,7 @@ use leo_errors::{
LeoWarning,
};
use leo_package::root::env::Env;
use leo_retriever::NetworkName;
use leo_span::source_map::FileName;
use leo_test_framework::{test::TestConfig, Test};
@ -136,9 +137,16 @@ pub fn setup_build_directory(
// Create the manifest file.
let _manifest_file = Manifest::create(&directory, &program_id).unwrap();
// Get the network name.
let network = match CurrentNetwork::ID {
MainnetV0::ID => NetworkName::MainnetV0,
TestnetV0::ID => NetworkName::TestnetV0,
_ => panic!("Invalid network"),
};
// Create the environment file.
Env::<CurrentNetwork>::new().unwrap().write_to(&directory).unwrap();
if Env::<CurrentNetwork>::exists_at(&directory) {
Env::new(network).unwrap().write_to(&directory).unwrap();
if Env::exists_at(&directory) {
println!(".env file created at {:?}", &directory);
}

View File

@ -26,8 +26,6 @@ use std::{
path::{Path, PathBuf},
};
type CurrentNetwork = snarkvm::prelude::MainnetV0;
#[derive(Debug, Parser)]
#[clap(name = "leo parser", about = "Parse Leo AST and store it as a JSON")]
struct Opt {
@ -40,6 +38,10 @@ struct Opt {
/// Whether to print result to STDOUT.
#[clap(short, long)]
print_stdout: bool,
/// The network to use. Defaults to mainnet.
#[clap(long, default_value = "mainnet")]
pub(crate) network: String,
}
fn main() -> Result<(), String> {
@ -50,7 +52,15 @@ fn main() -> Result<(), String> {
Handler::with(|h| {
let node_builder = NodeBuilder::default();
let ast = leo_parser::parse_ast::<CurrentNetwork>(h, &node_builder, &code.src, code.start_pos)?;
let ast = match opt.network.as_str() {
"mainnet" => {
leo_parser::parse_ast::<snarkvm::prelude::MainnetV0>(h, &node_builder, &code.src, code.start_pos)
}
"testnet" => {
leo_parser::parse_ast::<snarkvm::prelude::TestnetV0>(h, &node_builder, &code.src, code.start_pos)
}
_ => panic!("Invalid network"),
}?;
let json = Ast::to_json_string(&ast)?;
println!("{json}");
Ok(json)

View File

@ -222,4 +222,11 @@ create_messages!(
msg: "Cannot combine recursive deploy with private fee.".to_string(),
help: None,
}
@backtraced
invalid_network_name {
args: (network: impl Display),
msg: format!("Invalid network name: {network}"),
help: Some("Valid network names are `testnet` and `mainnet`.".to_string()),
}
);

View File

@ -314,6 +314,8 @@ mod test_helpers {
use leo_span::symbol::create_session_if_not_set_then;
use std::path::Path;
const NETWORK: &str = "mainnet";
pub(crate) fn sample_nested_package(temp_dir: &Path) {
let name = "nested";
@ -327,7 +329,7 @@ mod test_helpers {
let new = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: name.to_string() } },
command: Commands::New { command: New { name: name.to_string(), network: "mainnet".to_string() } },
path: Some(project_directory.clone()),
home: None,
};
@ -428,7 +430,7 @@ function external_nested_function:
let create_grandparent_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "grandparent".to_string() } },
command: Commands::New { command: New { name: "grandparent".to_string(), network: "mainnet".to_string() } },
path: Some(grandparent_directory.clone()),
home: None,
};
@ -436,7 +438,7 @@ function external_nested_function:
let create_parent_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "parent".to_string() } },
command: Commands::New { command: New { name: "parent".to_string(), network: "mainnet".to_string() } },
path: Some(parent_directory.clone()),
home: None,
};
@ -444,7 +446,7 @@ function external_nested_function:
let create_child_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "child".to_string() } },
command: Commands::New { command: New { name: "child".to_string(), network: "mainnet".to_string() } },
path: Some(child_directory.clone()),
home: None,
};
@ -559,7 +561,7 @@ program child.aleo {
let create_outer_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "outer".to_string() } },
command: Commands::New { command: New { name: "outer".to_string(), network: "mainnet".to_string() } },
path: Some(outer_directory.clone()),
home: None,
};
@ -567,7 +569,7 @@ program child.aleo {
let create_inner_1_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "inner_1".to_string() } },
command: Commands::New { command: New { name: "inner_1".to_string(), network: "mainnet".to_string() } },
path: Some(inner_1_directory.clone()),
home: None,
};
@ -575,7 +577,7 @@ program child.aleo {
let create_inner_2_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "inner_2".to_string() } },
command: Commands::New { command: New { name: "inner_2".to_string(), network: "mainnet".to_string() } },
path: Some(inner_2_directory.clone()),
home: None,
};
@ -697,7 +699,7 @@ program outer.aleo {
let create_outer_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "outer_2".to_string() } },
command: Commands::New { command: New { name: "outer_2".to_string(), network: "mainnet".to_string() } },
path: Some(outer_directory.clone()),
home: None,
};
@ -705,7 +707,7 @@ program outer.aleo {
let create_inner_1_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "inner_1".to_string() } },
command: Commands::New { command: New { name: "inner_1".to_string(), network: "mainnet".to_string() } },
path: Some(inner_1_directory.clone()),
home: None,
};
@ -713,7 +715,7 @@ program outer.aleo {
let create_inner_2_project = CLI {
debug: false,
quiet: false,
command: Commands::New { command: New { name: "inner_2".to_string() } },
command: Commands::New { command: New { name: "inner_2".to_string(), network: "mainnet".to_string() } },
path: Some(inner_2_directory.clone()),
home: None,
};

View File

@ -18,14 +18,15 @@ use super::*;
use leo_errors::UtilError;
use leo_package::root::Env;
use snarkvm::{
cli::dotenv_private_key,
console::program::{Signature, ToFields, Value},
prelude::{Address, PrivateKey, ViewKey},
};
use crossterm::ExecutableCommand;
use leo_retriever::NetworkName;
use rand::SeedableRng;
use rand_chacha::ChaChaRng;
use snarkvm::prelude::{MainnetV0, Network, TestnetV0};
use std::{
io::{self, Read, Write},
path::PathBuf,
@ -46,23 +47,27 @@ pub enum Account {
/// Print sensitive information (such as private key) discreetly to an alternate screen
#[clap(long)]
discreet: bool,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
network: String,
},
/// Derive an Aleo account from a private key.
Import {
/// Private key plaintext
private_key: Option<PrivateKey<CurrentNetwork>>,
private_key: Option<String>,
/// Write the private key to the .env file.
#[clap(short = 'w', long)]
write: bool,
/// Print sensitive information (such as private key) discreetly to an alternate screen
#[clap(long)]
discreet: bool,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
network: String,
},
/// Sign a message using your Aleo private key.
Sign {
/// Specify the account private key of the node
#[clap(long = "private-key")]
private_key: Option<PrivateKey<CurrentNetwork>>,
private_key: Option<String>,
/// Specify the path to a file containing the account private key of the node
#[clap(long = "private-key-file")]
private_key_file: Option<String>,
@ -75,12 +80,14 @@ pub enum Account {
/// When enabled, parses the message as bytes instead of Aleo literals
#[clap(short = 'r', long)]
raw: bool,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
network: String,
},
/// Verify a message from an Aleo address.
Verify {
/// Address to use for verification
#[clap(short = 'a', long)]
address: Address<CurrentNetwork>,
address: String,
/// Signature to verify
#[clap(short = 's', long)]
signature: String,
@ -90,6 +97,8 @@ pub enum Account {
/// When enabled, parses the message as bytes instead of Aleo literals
#[clap(short = 'r', long)]
raw: bool,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
network: String,
},
}
@ -109,79 +118,43 @@ impl Command for Account {
Self: Sized,
{
match self {
Account::New { seed, write, discreet } => {
// Sample a new Aleo account.
let private_key = match seed {
// Recover the field element deterministically.
Some(seed) => PrivateKey::new(&mut ChaChaRng::seed_from_u64(seed)),
// Sample a random field element.
None => PrivateKey::new(&mut ChaChaRng::from_entropy()),
}
.map_err(CliError::failed_to_parse_seed)?;
// Derive the view key and address and print to stdout.
print_keys(private_key, discreet)?;
// Save key data to .env file.
if write {
write_to_env_file(private_key, &ctx)?;
}
Account::New { seed, write, discreet, network } => {
// Parse the network.
let network = NetworkName::try_from(network.as_str())?;
match network {
NetworkName::MainnetV0 => generate_new_account::<MainnetV0>(seed, write, discreet, &ctx),
NetworkName::TestnetV0 => generate_new_account::<TestnetV0>(seed, write, discreet, &ctx),
}?
}
Account::Import { private_key, write, discreet } => {
let priv_key = match discreet {
true => {
let private_key_input = rpassword::prompt_password("Please enter your private key: ").unwrap();
snarkvm::prelude::FromStr::from_str(&private_key_input)
.map_err(CliError::failed_to_parse_private_key)?
}
false => match private_key {
Some(private_key) => private_key,
None => {
return Err(CliError::failed_to_execute_account(
"PRIVATE_KEY shouldn't be empty when --discreet is false",
)
.into());
}
},
};
// Derive the view key and address and print to stdout.
print_keys(priv_key, discreet)?;
// Save key data to .env file.
if write {
write_to_env_file(priv_key, &ctx)?;
}
Account::Import { private_key, write, discreet, network } => {
// Parse the network.
let network = NetworkName::try_from(network.as_str())?;
match network {
NetworkName::MainnetV0 => import_account::<MainnetV0>(private_key, write, discreet, &ctx),
NetworkName::TestnetV0 => import_account::<TestnetV0>(private_key, write, discreet, &ctx),
}?
}
Self::Sign { message, seed, raw, private_key, private_key_file } => {
let key = match (private_key, private_key_file) {
(Some(private_key), None) => private_key,
(None, Some(private_key_file)) => {
let path = private_key_file
.parse::<PathBuf>()
.map_err(|e| CliError::cli_invalid_input(format!("invalid path - {e}")))?;
let key_str = std::fs::read_to_string(path).map_err(UtilError::failed_to_read_file)?;
PrivateKey::<CurrentNetwork>::from_str(key_str.trim())
.map_err(|e| CliError::cli_invalid_input(format!("could not parse private key: {e}")))?
Self::Sign { message, seed, raw, private_key, private_key_file, network } => {
// Parse the network.
let network = NetworkName::try_from(network.as_str())?;
let result = match network {
NetworkName::MainnetV0 => {
sign_message::<MainnetV0>(message, seed, raw, private_key, private_key_file)
}
(None, None) => {
// Attempt to pull private key from env, then .env file
match dotenvy::var("PRIVATE_KEY")
.map_or_else(|_| dotenv_private_key(), |key| PrivateKey::<CurrentNetwork>::from_str(&key))
{
Ok(key) => key,
Err(_) => Err(CliError::cli_invalid_input(
"missing the '--private-key', '--private-key-file', PRIVATE_KEY env, or .env",
))?,
}
NetworkName::TestnetV0 => {
sign_message::<TestnetV0>(message, seed, raw, private_key, private_key_file)
}
(Some(_), Some(_)) => Err(CliError::cli_invalid_input(
"cannot specify both the '--private-key' and '--private-key-file' flags",
))?,
};
println!("{}", sign_message(key, message, seed, raw)?);
}?;
println!("{result}")
}
Self::Verify { address, signature, message, raw } => {
println!("{}", verify_message(address, signature, message, raw)?)
Self::Verify { address, signature, message, raw, network } => {
// Parse the network.
let network = NetworkName::try_from(network.as_str())?;
let result = match network {
NetworkName::MainnetV0 => verify_message::<MainnetV0>(address, signature, message, raw),
NetworkName::TestnetV0 => verify_message::<TestnetV0>(address, signature, message, raw),
}?;
println!("{result}")
}
}
Ok(())
@ -190,10 +163,60 @@ impl Command for Account {
// Helper functions
// Generate a new account.
fn generate_new_account<N: Network>(seed: Option<u64>, write: bool, discreet: bool, ctx: &Context) -> Result<()> {
// Sample a new Aleo account.
let private_key = match seed {
// Recover the field element deterministically.
Some(seed) => PrivateKey::<N>::new(&mut ChaChaRng::seed_from_u64(seed)),
// Sample a random field element.
None => PrivateKey::new(&mut ChaChaRng::from_entropy()),
}
.map_err(CliError::failed_to_parse_seed)?;
// Derive the view key and address and print to stdout.
print_keys(private_key, discreet)?;
// Save key data to .env file.
if write {
write_to_env_file(private_key, ctx)?;
}
Ok(())
}
// Import an account.
fn import_account<N: Network>(private_key: Option<String>, write: bool, discreet: bool, ctx: &Context) -> Result<()> {
let priv_key = match discreet {
true => {
let private_key_input = rpassword::prompt_password("Please enter your private key: ").unwrap();
FromStr::from_str(&private_key_input).map_err(CliError::failed_to_parse_private_key)?
}
false => match private_key {
Some(private_key) => FromStr::from_str(&private_key).map_err(CliError::failed_to_parse_private_key)?,
None => {
return Err(CliError::failed_to_execute_account(
"PRIVATE_KEY shouldn't be empty when --discreet is false",
)
.into());
}
},
};
// Derive the view key and address and print to stdout.
print_keys::<N>(priv_key, discreet)?;
// Save key data to .env file.
if write {
write_to_env_file::<N>(priv_key, ctx)?;
}
Ok(())
}
// Print keys as a formatted string without log level.
fn print_keys(private_key: PrivateKey<CurrentNetwork>, discreet: bool) -> Result<()> {
fn print_keys<N: Network>(private_key: PrivateKey<N>, discreet: bool) -> Result<()> {
let view_key = ViewKey::try_from(&private_key)?;
let address = Address::<CurrentNetwork>::try_from(&view_key)?;
let address = Address::<N>::try_from(&view_key)?;
if !discreet {
println!(
@ -209,14 +232,101 @@ fn print_keys(private_key: PrivateKey<CurrentNetwork>, discreet: bool) -> Result
"### Do not share or lose this private key! Press any key to complete. ###",
)?;
println!("\n {:>12} {view_key}\n {:>12} {address}\n", "View Key".cyan().bold(), "Address".cyan().bold(),);
Ok(())
}
// Sign a message with an Aleo private key
pub(crate) fn sign_message<N: Network>(
message: String,
seed: Option<u64>,
raw: bool,
private_key: Option<String>,
private_key_file: Option<String>,
) -> Result<String> {
let private_key = match (private_key, private_key_file) {
(Some(private_key), None) => PrivateKey::<N>::from_str(private_key.trim())
.map_err(|e| CliError::cli_invalid_input(format!("could not parse private key: {e}")))?,
(None, Some(private_key_file)) => {
let path = private_key_file
.parse::<PathBuf>()
.map_err(|e| CliError::cli_invalid_input(format!("invalid path - {e}")))?;
let key_str = std::fs::read_to_string(path).map_err(UtilError::failed_to_read_file)?;
PrivateKey::<N>::from_str(key_str.trim())
.map_err(|e| CliError::cli_invalid_input(format!("could not parse private key: {e}")))?
}
(None, None) => {
// Attempt to pull private key from env, then .env file
match dotenvy::var("PRIVATE_KEY") {
Ok(key) => PrivateKey::<N>::from_str(key.trim())
.map_err(|e| CliError::cli_invalid_input(format!("could not parse private key: {e}")))?,
Err(_) => Err(CliError::cli_invalid_input(
"missing the '--private-key', '--private-key-file', PRIVATE_KEY env, or .env",
))?,
}
}
(Some(_), Some(_)) => {
Err(CliError::cli_invalid_input("cannot specify both the '--private-key' and '--private-key-file' flags"))?
}
};
// Recover the seed.
let mut rng = match seed {
// Recover the field element deterministically.
Some(seed) => ChaChaRng::seed_from_u64(seed),
// Sample a random field element.
None => ChaChaRng::from_entropy(),
};
// Sign the message
let signature = if raw {
private_key.sign_bytes(message.as_bytes(), &mut rng)
} else {
let fields = Value::<N>::from_str(&message)?
.to_fields()
.map_err(|_| CliError::cli_invalid_input("Failed to parse a valid Aleo value"))?;
private_key.sign(&fields, &mut rng)
}
.map_err(|_| CliError::cli_runtime_error("Failed to sign the message"))?
.to_string();
// Return the signature as a string
Ok(signature)
}
// Verify a signature with an Aleo address
pub(crate) fn verify_message<N: Network>(
address: String,
signature: String,
message: String,
raw: bool,
) -> Result<String> {
// Parse the address.
let address = Address::<N>::from_str(&address)?;
let signature = Signature::<N>::from_str(&signature)
.map_err(|e| CliError::cli_invalid_input(format!("Failed to parse a valid signature: {e}")))?;
// Verify the signature
let verified = if raw {
signature.verify_bytes(&address, message.as_bytes())
} else {
let fields = Value::<N>::from_str(&message)?
.to_fields()
.map_err(|_| CliError::cli_invalid_input("Failed to parse a valid Aleo value"))?;
signature.verify(&address, &fields)
};
// Return the verification result
match verified {
true => Ok("✅ The signature is valid".to_string()),
false => Err(CliError::cli_runtime_error("❌ The signature is invalid"))?,
}
}
// Write the network and private key to the .env file in project directory.
fn write_to_env_file(private_key: PrivateKey<CurrentNetwork>, ctx: &Context) -> Result<()> {
let data = format!("NETWORK=mainnet\nPRIVATE_KEY={private_key}\n");
fn write_to_env_file<N: Network>(private_key: PrivateKey<N>, ctx: &Context) -> Result<()> {
let data = format!("PRIVATE_KEY={private_key}\n");
let program_dir = ctx.dir()?;
Env::<CurrentNetwork>::from(data).write_to(&program_dir)?;
Env::from(data).write_to(&program_dir)?;
tracing::info!("✅ Private Key written to {}", program_dir.join(".env").display());
Ok(())
}
@ -242,86 +352,31 @@ fn wait_for_keypress() {
std::io::stdin().read_exact(&mut single_key).unwrap();
}
// Sign a message with an Aleo private key
pub(crate) fn sign_message(
private_key: PrivateKey<CurrentNetwork>,
message: String,
seed: Option<u64>,
raw: bool,
) -> Result<String> {
// Recover the seed.
let mut rng = match seed {
// Recover the field element deterministically.
Some(seed) => ChaChaRng::seed_from_u64(seed),
// Sample a random field element.
None => ChaChaRng::from_entropy(),
};
// Sign the message
let signature = if raw {
private_key.sign_bytes(message.as_bytes(), &mut rng)
} else {
let fields = Value::<CurrentNetwork>::from_str(&message)?
.to_fields()
.map_err(|_| CliError::cli_invalid_input("Failed to parse a valid Aleo value"))?;
private_key.sign(&fields, &mut rng)
}
.map_err(|_| CliError::cli_runtime_error("Failed to sign the message"))?
.to_string();
// Return the signature as a string
Ok(signature)
}
// Verify a signature with an Aleo address
pub(crate) fn verify_message(
address: Address<CurrentNetwork>,
signature: String,
message: String,
raw: bool,
) -> Result<String> {
let signature = Signature::<CurrentNetwork>::from_str(&signature)
.map_err(|e| CliError::cli_invalid_input(format!("Failed to parse a valid signature: {e}")))?;
// Verify the signature
let verified = if raw {
signature.verify_bytes(&address, message.as_bytes())
} else {
let fields = Value::<CurrentNetwork>::from_str(&message)?
.to_fields()
.map_err(|_| CliError::cli_invalid_input("Failed to parse a valid Aleo value"))?;
signature.verify(&address, &fields)
};
// Return the verification result
match verified {
true => Ok("✅ The signature is valid".to_string()),
false => Err(CliError::cli_runtime_error("❌ The signature is invalid"))?,
}
}
#[cfg(test)]
mod tests {
use super::{sign_message, verify_message};
type CurrentNetwork = snarkvm::prelude::MainnetV0;
#[test]
fn test_signature_raw() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".parse().unwrap();
let message = "Hello, world!".to_string();
assert!(sign_message(key, message, None, true).is_ok());
assert!(sign_message::<CurrentNetwork>(message, None, true, Some(key), None).is_ok());
}
#[test]
fn test_signature() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".parse().unwrap();
let message = "5field".to_string();
assert!(sign_message(key, message, None, false).is_ok());
assert!(sign_message::<CurrentNetwork>(message, None, false, Some(key), None).is_ok());
}
#[test]
fn test_signature_fail() {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".parse().unwrap();
let message = "not a literal value".to_string();
assert!(sign_message(key, message, None, false).is_err());
assert!(sign_message::<CurrentNetwork>(message, None, false, Some(key), None).is_err());
}
#[test]
@ -330,7 +385,7 @@ mod tests {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".parse().unwrap();
let message = "Hello, world!".to_string();
let expected = "sign175pmqldmkqw2nwp7wz7tfmpyqdnvzaq06mh8t2g22frsmrdtuvpf843p0wzazg27rwrjft8863vwn5a5cqgr97ldw69cyq53l0zlwqhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkevd26g";
let actual = sign_message(key, message, seed, true).unwrap();
let actual = sign_message::<CurrentNetwork>(message, seed, true, Some(key), None).unwrap();
assert_eq!(expected, actual);
}
@ -340,7 +395,7 @@ mod tests {
let key = "APrivateKey1zkp61PAYmrYEKLtRWeWhUoDpFnGLNuHrCciSqN49T86dw3p".parse().unwrap();
let message = "5field".to_string();
let expected = "sign1ad29myqy8gv6xve2r6tuly39m63l2mpfpyvqkwdl2umxqek6q5qxmy63zmhjx75x90sqxq69u5ntzp25kp59e0hp4hj8l8085sg7vqlesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk7v46re";
let actual = sign_message(key, message, seed, false).unwrap();
let actual = sign_message::<CurrentNetwork>(message, seed, false, Some(key), None).unwrap();
assert_eq!(expected, actual);
}
@ -350,23 +405,23 @@ mod tests {
let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j".parse().unwrap();
let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
let message = "Hello, world!".to_string();
assert!(verify_message(address, signature, message, true).is_ok());
assert!(verify_message::<CurrentNetwork>(address, signature, message, true).is_ok());
// test signature of "Hello, world!" against the message "Different Message"
let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
let message = "Different Message".to_string();
assert!(verify_message(address, signature, message, true).is_err());
assert!(verify_message::<CurrentNetwork>(address, signature, message, true).is_err());
// test signature of "Hello, world!" against the wrong address
let signature = "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq".to_string();
let message = "Hello, world!".to_string();
let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".parse().unwrap();
assert!(verify_message(wrong_address, signature, message, true).is_err());
assert!(verify_message::<CurrentNetwork>(wrong_address, signature, message, true).is_err());
// test a valid signature of "Different Message"
let signature = "sign1424ztyt9hcm77nq450gvdszrvtg9kvhc4qadg4nzy9y0ah7wdqq7t36cxal42p9jj8e8pjpmc06lfev9nvffcpqv0cxwyr0a2j2tjqlesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk3yrr50".to_string();
let message = "Different Message".to_string();
assert!(verify_message(address, signature, message, true).is_ok());
assert!(verify_message::<CurrentNetwork>(address, signature, message, true).is_ok());
}
#[test]
@ -375,22 +430,22 @@ mod tests {
let address = "aleo1zecnqchckrzw7dlsyf65g6z5le2rmys403ecwmcafrag0e030yxqrnlg8j".parse().unwrap();
let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
let message = "5field".to_string();
assert!(verify_message(address, signature, message, false).is_ok());
assert!(verify_message::<CurrentNetwork>(address, signature, message, false).is_ok());
// test signature of 5u8 against the message 10u8
let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
let message = "10field".to_string();
assert!(verify_message(address, signature, message, false).is_err());
assert!(verify_message::<CurrentNetwork>(address, signature, message, false).is_err());
// test signature of 5u8 against the wrong address
let signature = "sign1j7swjfnyujt2vme3ulu88wdyh2ddj85arh64qh6c6khvrx8wvsp8z9wtzde0sahqj2qwz8rgzt803c0ceega53l4hks2mf5sfsv36qhesm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkdetews".to_string();
let message = "5field".to_string();
let wrong_address = "aleo1uxl69laseuv3876ksh8k0nd7tvpgjt6ccrgccedpjk9qwyfensxst9ftg5".parse().unwrap();
assert!(verify_message(wrong_address, signature, message, false).is_err());
assert!(verify_message::<CurrentNetwork>(wrong_address, signature, message, false).is_err());
// test a valid signature of 10u8
let signature = "sign1t9v2t5tljk8pr5t6vkcqgkus0a3v69vryxmfrtwrwg0xtj7yv5qj2nz59e5zcyl50w23lhntxvt6vzeqfyu6dt56698zvfj2l6lz6q0esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qk8rh9kt".to_string();
let message = "10field".to_string();
assert!(verify_message(address, signature, message, false).is_ok());
assert!(verify_message::<CurrentNetwork>(address, signature, message, false).is_ok());
}
}

View File

@ -59,12 +59,10 @@ impl Command for Add {
// 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 if name.ends_with(".aleo") && Package::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 if Package::is_aleo_name_valid(name) => format!("{name}.aleo"),
name => return Err(PackageError::invalid_file_name_dependency(name).into()),
};
@ -113,7 +111,7 @@ impl Command for Add {
}
None => {
tracing::info!("✅ Added network dependency to program `{name}` from network `{}`.", self.network);
Dependency::new(name, Location::Network, Some(NetworkName::from(&self.network)), None)
Dependency::new(name, Location::Network, Some(NetworkName::try_from(self.network.as_str())?), None)
}
});

View File

@ -20,12 +20,13 @@ use leo_ast::Stub;
use leo_compiler::{Compiler, CompilerOptions, OutputOptions};
use leo_errors::UtilError;
use leo_package::{build::BuildDirectory, outputs::OutputsDirectory, source::SourceDirectory};
use leo_retriever::Retriever;
use leo_retriever::{NetworkName, Retriever};
use leo_span::Symbol;
use snarkvm::{package::Package, prelude::ProgramID};
use indexmap::IndexMap;
use snarkvm::prelude::{MainnetV0, Network, TestnetV0};
use std::{
io::Write,
path::{Path, PathBuf},
@ -88,101 +89,92 @@ impl Command for Build {
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Get the package path.
let package_path = context.dir()?;
let home_path = context.home()?;
// Parse the network.
let network = NetworkName::try_from(self.options.network.as_str())?;
match network {
NetworkName::MainnetV0 => handle_build::<MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_build::<TestnetV0>(&self, context),
}
}
}
// Open the build directory.
let build_directory = BuildDirectory::create(&package_path)?;
// A helper function to handle the build command.
fn handle_build<N: Network>(command: &Build, context: Context) -> Result<<Build as Command>::Output> {
// Get the package path.
let package_path = context.dir()?;
let home_path = context.home()?;
// Get the program id.
let manifest = context.open_manifest()?;
let program_id = manifest.program_id();
// Open the build directory.
let build_directory = BuildDirectory::create(&package_path)?;
// Initialize error handler
let handler = Handler::default();
// Get the program id.
let manifest = context.open_manifest::<N>()?;
let program_id = manifest.program_id();
// Retrieve all local dependencies in post order
let main_sym = Symbol::intern(&program_id.name().to_string());
let mut retriever =
Retriever::<CurrentNetwork>::new(main_sym, &package_path, &home_path, self.options.endpoint.clone())
.map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
let mut local_dependencies =
retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
// Initialize error handler
let handler = Handler::default();
// Push the main program at the end of the list to be compiled after all of its dependencies have been processed
local_dependencies.push(main_sym);
// Retrieve all local dependencies in post order
let main_sym = Symbol::intern(&program_id.name().to_string());
let mut retriever = Retriever::<N>::new(main_sym, &package_path, &home_path, command.options.endpoint.clone())
.map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
let mut local_dependencies =
retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?;
// Recursive build will recursively compile all local dependencies. Can disable to save compile time.
let recursive_build = !self.options.non_recursive;
// Push the main program at the end of the list to be compiled after all of its dependencies have been processed
local_dependencies.push(main_sym);
// Loop through all local dependencies and compile them in order
for dependency in local_dependencies.into_iter() {
if recursive_build || dependency == main_sym {
// Get path to the local project
let (local_path, stubs) = retriever.prepare_local(dependency)?;
// Recursive build will recursively compile all local dependencies. Can disable to save compile time.
let recursive_build = !command.options.non_recursive;
// Create the outputs directory.
let local_outputs_directory = OutputsDirectory::create(&local_path)?;
// Loop through all local dependencies and compile them in order
for dependency in local_dependencies.into_iter() {
if recursive_build || dependency == main_sym {
// Get path to the local project
let (local_path, stubs) = retriever.prepare_local(dependency)?;
// Open the build directory.
let local_build_directory = BuildDirectory::create(&local_path)?;
// Create the outputs directory.
let local_outputs_directory = OutputsDirectory::create(&local_path)?;
// Fetch paths to all .leo files in the source directory.
let local_source_files = SourceDirectory::files(&local_path)?;
// Open the build directory.
let local_build_directory = BuildDirectory::create(&local_path)?;
// Check the source files.
SourceDirectory::check_files(&local_source_files)?;
// Fetch paths to all .leo files in the source directory.
let local_source_files = SourceDirectory::files(&local_path)?;
// Compile all .leo files into .aleo files.
for file_path in local_source_files {
compile_leo_file(
file_path,
&ProgramID::<CurrentNetwork>::try_from(format!("{}.aleo", dependency))
.map_err(|_| UtilError::snarkvm_error_building_program_id(Default::default()))?,
&local_outputs_directory,
&local_build_directory,
&handler,
self.options.clone(),
stubs.clone(),
)?;
}
// Check the source files.
SourceDirectory::check_files(&local_source_files)?;
// Compile all .leo files into .aleo files.
for file_path in local_source_files {
compile_leo_file(
file_path,
&ProgramID::<N>::try_from(format!("{}.aleo", dependency))
.map_err(|_| UtilError::snarkvm_error_building_program_id(Default::default()))?,
&local_outputs_directory,
&local_build_directory,
&handler,
command.options.clone(),
stubs.clone(),
)?;
}
// Writes `leo.lock` as well as caches objects (when target is an intermediate dependency)
retriever.process_local(dependency, recursive_build)?;
}
// `Package::open` checks that the build directory and that `main.aleo` and all imported files are well-formed.
Package::<CurrentNetwork>::open(&build_directory).map_err(CliError::failed_to_execute_build)?;
// // Unset the Leo panic hook.
// let _ = std::panic::take_hook();
//
// // Change the cwd to the build directory to compile aleo files.
// std::env::set_current_dir(&build_directory)
// .map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
//
// // Call the `build` command.
// let mut args = vec![SNARKVM_COMMAND];
// if self.options.offline {
// args.push("--offline");
// }
// let command = AleoBuild::try_parse_from(&args).map_err(CliError::failed_to_execute_aleo_build)?;
// let result = command.parse().map_err(CliError::failed_to_execute_aleo_build)?;
//
// // Log the result of the build
// tracing::info!("{}", result);
Ok(())
// Writes `leo.lock` as well as caches objects (when target is an intermediate dependency)
retriever.process_local(dependency, recursive_build)?;
}
// `Package::open` checks that the build directory and that `main.aleo` and all imported files are well-formed.
Package::<N>::open(&build_directory).map_err(CliError::failed_to_execute_build)?;
Ok(())
}
/// Compiles a Leo file in the `src/` directory.
#[allow(clippy::too_many_arguments)]
fn compile_leo_file(
fn compile_leo_file<N: Network>(
file_path: PathBuf,
program_id: &ProgramID<CurrentNetwork>,
program_id: &ProgramID<N>,
outputs: &Path,
build: &Path,
handler: &Handler,
@ -197,7 +189,7 @@ fn compile_leo_file(
aleo_file_path.push(format!("main.{}", program_id.network()));
// Create a new instance of the Leo compiler.
let mut compiler = Compiler::<CurrentNetwork>::new(
let mut compiler = Compiler::<N>::new(
program_name.clone(),
program_id.network().to_string(),
handler,

View File

@ -15,8 +15,12 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use leo_retriever::NetworkName;
use snarkos_cli::commands::{Deploy as SnarkOSDeploy, Developer};
use snarkvm::cli::helpers::dotenv_private_key;
use snarkvm::{
cli::helpers::dotenv_private_key,
prelude::{MainnetV0, TestnetV0},
};
use std::path::PathBuf;
/// Deploys an Aleo program.
@ -54,8 +58,13 @@ impl Command for Deploy {
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.compiler_options.network.as_str())?;
// Get the program name.
let project_name = context.open_manifest()?.program_id().to_string();
let project_name = match network {
NetworkName::MainnetV0 => context.open_manifest::<MainnetV0>()?.program_id().to_string(),
NetworkName::TestnetV0 => context.open_manifest::<TestnetV0>()?.program_id().to_string(),
};
// Get the private key.
let mut private_key = self.fee_options.private_key;
@ -91,7 +100,7 @@ impl Command for Deploy {
"--path".to_string(),
path.to_str().unwrap().parse().unwrap(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.fee_options.network)
format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.compiler_options.network)
.to_string(),
name.clone(),
];

View File

@ -18,15 +18,13 @@ use super::*;
use std::fs;
/// The example programs that can be generated.
/// Initialize a new Leo example.
#[derive(Parser, Debug)]
pub enum Example {
#[clap(name = "lottery", about = "A public lottery program")]
Lottery,
#[clap(name = "tictactoe", about = "A standard tic-tac-toe game program")]
TicTacToe,
#[clap(name = "token", about = "A transparent & shielded custom token program")]
Token,
pub struct Example {
#[clap(name = "EXAMPLE", help = "The example to initialize.")]
pub(crate) example: ExampleVariant,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
pub(crate) network: String,
}
impl Command for Example {
@ -35,7 +33,7 @@ impl Command for Example {
fn prelude(&self, context: Context) -> Result<Self::Input> {
// Run leo new EXAMPLE_NAME
(New { name: self.name() }).execute(context)
(New { name: self.example.name(), network: self.network }).execute(context)
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output>
@ -46,20 +44,20 @@ impl Command for Example {
// Write the main file.
let main_file_path = package_dir.join("src").join("main.leo");
fs::write(main_file_path, self.main_file_string()).map_err(CliError::failed_to_write_file)?;
fs::write(main_file_path, self.example.main_file_string()).map_err(CliError::failed_to_write_file)?;
// Write the README file.
let readme_file_path = package_dir.join("README.md");
let readme_file_path_string = readme_file_path.display().to_string();
fs::write(readme_file_path, self.readme_file_string()).map_err(CliError::failed_to_write_file)?;
fs::write(readme_file_path, self.example.readme_file_string()).map_err(CliError::failed_to_write_file)?;
// Write the run.sh file.
let run_file_path = package_dir.join("run.sh");
fs::write(run_file_path, self.run_file_string()).map_err(CliError::failed_to_write_file)?;
fs::write(run_file_path, self.example.run_file_string()).map_err(CliError::failed_to_write_file)?;
tracing::info!(
"🚀 To run the '{}' program follow the instructions at {}",
self.name().bold(),
self.example.name().bold(),
readme_file_path_string
);
@ -67,12 +65,23 @@ impl Command for Example {
}
}
impl Example {
/// The example programs that can be generated.
#[derive(Parser, Debug, Copy, Clone)]
pub enum ExampleVariant {
#[clap(name = "lottery", about = "A public lottery program")]
Lottery,
#[clap(name = "tictactoe", about = "A standard tic-tac-toe game program")]
TicTacToe,
#[clap(name = "token", about = "A transparent & shielded custom token program")]
Token,
}
impl ExampleVariant {
fn name(&self) -> String {
match self {
Example::Lottery => "lottery".to_string(),
Example::TicTacToe => "tictactoe".to_string(),
Example::Token => "token".to_string(),
Self::Lottery => "lottery".to_string(),
Self::TicTacToe => "tictactoe".to_string(),
Self::Token => "token".to_string(),
}
}

View File

@ -16,10 +16,11 @@
use super::*;
use clap::Parser;
use leo_retriever::NetworkName;
use snarkos_cli::commands::{Developer, Execute as SnarkOSExecute};
use snarkvm::{
cli::{helpers::dotenv_private_key, Execute as SnarkVMExecute},
prelude::Parser as SnarkVMParser,
prelude::{MainnetV0, Network, Parser as SnarkVMParser, TestnetV0},
};
/// Build, Prove and Run Leo program with inputs
@ -60,132 +61,142 @@ impl Command for Execute {
}
fn apply(self, context: Context, _input: Self::Input) -> Result<Self::Output> {
// If the `broadcast` flag is set, then broadcast the transaction.
if self.broadcast {
// Get the program name.
let program_name = match (self.program, self.local) {
(Some(name), true) => {
let local = context.open_manifest()?.program_id().to_string();
// Throw error if local name doesn't match the specified name.
if name == local {
local
} else {
return Err(PackageError::conflicting_on_chain_program_name(local, name).into());
}
}
(Some(name), false) => name,
(None, true) => context.open_manifest()?.program_id().to_string(),
(None, false) => return Err(PackageError::missing_on_chain_program_name().into()),
};
// Get the private key.
let private_key = match self.fee_options.private_key {
Some(private_key) => private_key,
None => dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(),
};
// Set deploy arguments.
let mut fee_args = vec![
"snarkos".to_string(),
"--private-key".to_string(),
private_key.clone(),
"--query".to_string(),
self.compiler_options.endpoint.clone(),
"--priority-fee".to_string(),
self.fee_options.priority_fee.to_string(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.fee_options.network)
.to_string(),
];
// Use record as payment option if it is provided.
if let Some(record) = self.fee_options.record.clone() {
fee_args.push("--record".to_string());
fee_args.push(record);
};
// Execute program.
Developer::Execute(
SnarkOSExecute::try_parse_from(
[
// The arguments for determining fee.
fee_args,
// The program ID and function name.
vec![program_name, self.name],
// The function inputs.
self.inputs,
]
.concat(),
)
.unwrap(),
)
.parse()
.map_err(CliError::failed_to_execute_deploy)?;
return Ok(());
// Parse the network.
let network = NetworkName::try_from(self.compiler_options.network.as_str())?;
match network {
NetworkName::MainnetV0 => handle_execute::<MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_execute::<TestnetV0>(&self, context),
}
// If input values are provided, then run the program with those inputs.
// Otherwise, use the input file.
let mut inputs = self.inputs;
// Compose the `execute` command.
let mut arguments = vec![SNARKVM_COMMAND.to_string(), self.name];
// Add the inputs to the arguments.
match self.file {
Some(file) => {
// Get the contents from the file.
let path = context.dir()?.join(file);
let raw_content = std::fs::read_to_string(&path)
.map_err(|err| PackageError::failed_to_read_file(path.display(), err))?;
// Parse the values from the file.
let mut content = raw_content.as_str();
let mut values = vec![];
while let Ok((remaining, value)) = snarkvm::prelude::Value::<CurrentNetwork>::parse(content) {
content = remaining;
values.push(value);
}
// Check that the remaining content is empty.
if !content.trim().is_empty() {
return Err(PackageError::failed_to_read_input_file(path.display()).into());
}
// Convert the values to strings.
let mut inputs_from_file = values.into_iter().map(|value| value.to_string()).collect::<Vec<String>>();
// Add the inputs from the file to the arguments.
arguments.append(&mut inputs_from_file);
}
None => arguments.append(&mut inputs),
}
// Add the compiler options to the arguments.
if self.compiler_options.offline {
arguments.push(String::from("--offline"));
}
// Add the endpoint to the arguments.
arguments.push(String::from("--endpoint"));
arguments.push(self.compiler_options.endpoint.clone());
// Open the Leo build/ directory.
let path = context.dir()?;
let build_directory = BuildDirectory::open(&path)?;
// Change the cwd to the Leo build/ directory to compile aleo files.
std::env::set_current_dir(&build_directory)
.map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
// Unset the Leo panic hook.
let _ = std::panic::take_hook();
// Call the `execute` command.
println!();
let command = SnarkVMExecute::try_parse_from(&arguments).map_err(CliError::failed_to_parse_execute)?;
let res = command.parse().map_err(CliError::failed_to_execute_execute)?;
// Log the output of the `execute` command.
tracing::info!("{}", res);
Ok(())
}
}
// A helper function to handle the `execute` command.
fn handle_execute<N: Network>(command: &Execute, context: Context) -> Result<<Execute as Command>::Output> {
// If the `broadcast` flag is set, then broadcast the transaction.
if command.broadcast {
// Get the program name.
let program_name = match (command.program, command.local) {
(Some(name), true) => {
let local = context.open_manifest::<N>()?.program_id().to_string();
// Throw error if local name doesn't match the specified name.
if name == local {
local
} else {
return Err(PackageError::conflicting_on_chain_program_name(local, name).into());
}
}
(Some(name), false) => name,
(None, true) => context.open_manifest::<N>()?.program_id().to_string(),
(None, false) => return Err(PackageError::missing_on_chain_program_name().into()),
};
// Get the private key.
let private_key = match command.fee_options.private_key {
Some(private_key) => private_key,
None => dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(),
};
// Set deploy arguments.
let mut fee_args = vec![
"snarkos".to_string(),
"--private-key".to_string(),
private_key.clone(),
"--query".to_string(),
command.compiler_options.endpoint.clone(),
"--priority-fee".to_string(),
command.fee_options.priority_fee.to_string(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", command.compiler_options.endpoint, command.compiler_options.network)
.to_string(),
];
// Use record as payment option if it is provided.
if let Some(record) = command.fee_options.record.clone() {
fee_args.push("--record".to_string());
fee_args.push(record);
};
// Execute program.
Developer::Execute(
SnarkOSExecute::try_parse_from(
[
// The arguments for determining fee.
fee_args,
// The program ID and function name.
vec![program_name, command.name],
// The function inputs.
command.inputs,
]
.concat(),
)
.unwrap(),
)
.parse()
.map_err(CliError::failed_to_execute_deploy)?;
return Ok(());
}
// If input values are provided, then run the program with those inputs.
// Otherwise, use the input file.
let mut inputs = command.inputs;
// Compose the `execute` command.
let mut arguments = vec![SNARKVM_COMMAND.to_string(), command.name];
// Add the inputs to the arguments.
match command.file {
Some(file) => {
// Get the contents from the file.
let path = context.dir()?.join(file);
let raw_content =
std::fs::read_to_string(&path).map_err(|err| PackageError::failed_to_read_file(path.display(), err))?;
// Parse the values from the file.
let mut content = raw_content.as_str();
let mut values = vec![];
while let Ok((remaining, value)) = snarkvm::prelude::Value::<N>::parse(content) {
content = remaining;
values.push(value);
}
// Check that the remaining content is empty.
if !content.trim().is_empty() {
return Err(PackageError::failed_to_read_input_file(path.display()).into());
}
// Convert the values to strings.
let mut inputs_from_file = values.into_iter().map(|value| value.to_string()).collect::<Vec<String>>();
// Add the inputs from the file to the arguments.
arguments.append(&mut inputs_from_file);
}
None => arguments.append(&mut inputs),
}
// Add the compiler options to the arguments.
if command.compiler_options.offline {
arguments.push(String::from("--offline"));
}
// Add the endpoint to the arguments.
arguments.push(String::from("--endpoint"));
arguments.push(command.compiler_options.endpoint.clone());
// Open the Leo build/ directory.
let path = context.dir()?;
let build_directory = BuildDirectory::open(&path)?;
// Change the cwd to the Leo build/ directory to compile aleo files.
std::env::set_current_dir(&build_directory)
.map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
// Unset the Leo panic hook.
let _ = std::panic::take_hook();
// Call the `execute` command.
println!();
let command = SnarkVMExecute::try_parse_from(&arguments).map_err(CliError::failed_to_parse_execute)?;
let res = command.parse().map_err(CliError::failed_to_execute_execute)?;
// Log the output of the `execute` command.
tracing::info!("{}", res);
Ok(())
}

View File

@ -130,6 +130,8 @@ pub trait Command {
pub struct BuildOptions {
#[clap(long, help = "Endpoint to retrieve network state from.", default_value = "http://api.explorer.aleo.org/v1")]
pub endpoint: String,
#[clap(long, help = "Network to broadcast to. Defaults to mainnet.", default_value = "mainnet")]
pub(crate) network: String,
#[clap(long, help = "Does not recursively compile dependencies.")]
pub non_recursive: bool,
#[clap(long, help = "Enables offline mode.")]
@ -174,8 +176,6 @@ pub struct BuildOptions {
pub struct FeeOptions {
#[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")]
pub(crate) priority_fee: String,
#[clap(long, help = "Network to broadcast to. Defaults to mainnet.", default_value = "mainnet")]
pub(crate) network: String,
#[clap(long, help = "Private key to authorize fee expenditure.")]
pub(crate) private_key: Option<String>,
#[clap(

View File

@ -16,13 +16,15 @@
use super::*;
use snarkvm::{cli::New as SnarkVMNew, file::AleoFile};
use leo_retriever::NetworkName;
/// Create new Leo project
#[derive(Parser, Debug)]
pub struct New {
#[clap(name = "NAME", help = "Set package name")]
pub(crate) name: String,
#[clap(short = 'n', long, help = "Name of the network to use", default_value = "mainnet")]
pub(crate) network: String,
}
impl Command for New {
@ -38,6 +40,9 @@ impl Command for New {
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.network.as_str())?;
// Derive the location of the parent directory to the project.
let mut package_path = context.parent_dir()?;
@ -45,48 +50,15 @@ impl Command for New {
std::env::set_current_dir(&package_path)
.map_err(|err| PackageError::failed_to_set_cwd(package_path.display(), err))?;
// Call the `aleo new` command from the Aleo SDK.
let command =
SnarkVMNew::try_parse_from([SNARKVM_COMMAND, &self.name]).map_err(CliError::failed_to_parse_new)?;
let result = command.parse().map_err(CliError::failed_to_execute_new)?;
// Log the output of the `aleo new` command.
tracing::info!("{}", result);
// Initialize the Leo package in the directory created by `aleo new`.
package_path.push(&self.name);
std::env::set_current_dir(&package_path)
.map_err(|err| PackageError::failed_to_set_cwd(package_path.display(), err))?;
Package::<CurrentNetwork>::initialize(&self.name, &package_path)?;
// Open the program manifest.
let manifest = context.open_manifest()?;
// Create a path to the build directory.
let mut build_directory = package_path.clone();
build_directory.push(BUILD_DIRECTORY_NAME);
// Write the Aleo file into the build directory.
AleoFile::create(&build_directory, manifest.program_id(), true)
.map_err(PackageError::failed_to_create_aleo_file)?;
// build_aleo_file.push(AleoFile::<Network>::main_file_name());
//
// println!("{}", build_aleo_file.display());
//
//
// std::fs::File::create(build_aleo_file).map_err()
// aleo_file.write_to(&build_aleo_file).map_err(PackageError::failed_to_write_aleo_file)?;
// Open the `main.aleo` file path.
let aleo_file = AleoFile::open(&package_path, manifest.program_id(), true)
.map_err(PackageError::failed_to_open_aleo_file)?;
let mut aleo_file_path = package_path.clone();
aleo_file_path.push(AleoFile::<CurrentNetwork>::main_file_name());
// Remove the Aleo file from the package directory.
aleo_file.remove(&aleo_file_path).map_err(PackageError::failed_to_remove_aleo_file)?;
// Initialize the package.
match network {
NetworkName::MainnetV0 => Package::initialize(&self.name, network, &package_path),
NetworkName::TestnetV0 => Package::initialize(&self.name, network, &package_path),
}?;
Ok(())
}

View File

@ -54,7 +54,7 @@ impl Command for Program {
// 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]);
Package::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)

View File

@ -85,10 +85,10 @@ pub fn is_valid_field(field: &str) -> Result<String, LeoError> {
// 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]);
Package::is_aleo_name_valid(&name[0..name.len() - 5]);
name
} else {
Package::<CurrentNetwork>::is_aleo_name_valid(&name);
Package::is_aleo_name_valid(&name);
format!("{}.aleo", name)
}
}

View File

@ -29,12 +29,6 @@ pub struct Remove {
)]
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,
}
@ -62,18 +56,8 @@ impl Command for Remove {
.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()),
};
// Note that this unwrap is safe since `name` is required if `all` is `false`.
let name: String = self.name.unwrap().clone();
let mut found_match = false;
let dep = match manifest.dependencies() {

View File

@ -16,7 +16,11 @@
use super::*;
use snarkvm::{cli::Run as SnarkVMRun, prelude::Parser as SnarkVMParser};
use leo_retriever::NetworkName;
use snarkvm::{
cli::Run as SnarkVMRun,
prelude::{MainnetV0, Network, Parser as SnarkVMParser, TestnetV0},
};
/// Build, Prove and Run Leo program with inputs
#[derive(Parser, Debug)]
@ -47,56 +51,66 @@ impl Command for Run {
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
let mut inputs = self.inputs;
// Compose the `run` command.
let mut arguments = vec![SNARKVM_COMMAND.to_string(), self.name];
// Add the inputs to the arguments.
match self.file {
Some(file) => {
// Get the contents from the file.
let path = context.dir()?.join(file);
let raw_content = std::fs::read_to_string(&path)
.map_err(|err| PackageError::failed_to_read_file(path.display(), err))?;
// Parse the values from the file.
let mut content = raw_content.as_str();
let mut values = vec![];
while let Ok((remaining, value)) = snarkvm::prelude::Value::<CurrentNetwork>::parse(content) {
content = remaining;
values.push(value);
}
// Check that the remaining content is empty.
if !content.trim().is_empty() {
return Err(PackageError::failed_to_read_input_file(path.display()).into());
}
// Convert the values to strings.
let mut inputs_from_file = values.into_iter().map(|value| value.to_string()).collect::<Vec<String>>();
// Add the inputs from the file to the arguments.
arguments.append(&mut inputs_from_file);
}
None => arguments.append(&mut inputs),
// Parse the network.
let network = NetworkName::try_from(self.compiler_options.network.as_str())?;
match network {
NetworkName::MainnetV0 => handle_run::<MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_run::<TestnetV0>(&self, context),
}
// Open the Leo build/ directory
let path = context.dir()?;
let build_directory = BuildDirectory::open(&path)?;
// Change the cwd to the Leo build/ directory to compile aleo files.
std::env::set_current_dir(&build_directory)
.map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
// Unset the Leo panic hook
let _ = std::panic::take_hook();
// Call the `run` command.
println!();
let command = SnarkVMRun::try_parse_from(&arguments).map_err(CliError::failed_to_parse_run)?;
let res = command.parse().map_err(CliError::failed_to_execute_run)?;
// Log the output of the `run` command.
tracing::info!("{}", res);
Ok(())
}
}
// A helper function to handle the run command.
fn handle_run<N: Network>(command: &Run, context: Context) -> Result<<Run as Command>::Output> {
let mut inputs = command.inputs;
// Compose the `run` command.
let mut arguments = vec![SNARKVM_COMMAND.to_string(), command.name];
// Add the inputs to the arguments.
match command.file {
Some(file) => {
// Get the contents from the file.
let path = context.dir()?.join(file);
let raw_content =
std::fs::read_to_string(&path).map_err(|err| PackageError::failed_to_read_file(path.display(), err))?;
// Parse the values from the file.
let mut content = raw_content.as_str();
let mut values = vec![];
while let Ok((remaining, value)) = snarkvm::prelude::Value::<N>::parse(content) {
content = remaining;
values.push(value);
}
// Check that the remaining content is empty.
if !content.trim().is_empty() {
return Err(PackageError::failed_to_read_input_file(path.display()).into());
}
// Convert the values to strings.
let mut inputs_from_file = values.into_iter().map(|value| value.to_string()).collect::<Vec<String>>();
// Add the inputs from the file to the arguments.
arguments.append(&mut inputs_from_file);
}
None => arguments.append(&mut inputs),
}
// Open the Leo build/ directory
let path = context.dir()?;
let build_directory = BuildDirectory::open(&path)?;
// Change the cwd to the Leo build/ directory to compile aleo files.
std::env::set_current_dir(&build_directory)
.map_err(|err| PackageError::failed_to_set_cwd(build_directory.display(), err))?;
// Unset the Leo panic hook
let _ = std::panic::take_hook();
// Call the `run` command.
println!();
let command = SnarkVMRun::try_parse_from(&arguments).map_err(CliError::failed_to_parse_run)?;
let res = command.parse().map_err(CliError::failed_to_execute_run)?;
// Log the output of the `run` command.
tracing::info!("{}", res);
Ok(())
}

View File

@ -24,6 +24,7 @@ use snarkvm::file::Manifest;
use aleo_std::aleo_dir;
use indexmap::IndexMap;
use snarkvm::prelude::Network;
use std::{
env::current_dir,
fs::File,
@ -76,10 +77,10 @@ impl Context {
/// Returns the package name as a String.
/// Opens the manifest file `program.json` and creates the build directory if it doesn't exist.
pub fn open_manifest(&self) -> Result<Manifest<CurrentNetwork>> {
pub fn open_manifest<N: Network>(&self) -> Result<Manifest<N>> {
// Open the manifest file.
let path = self.dir()?;
let manifest = Manifest::<CurrentNetwork>::open(&path).map_err(PackageError::failed_to_open_manifest)?;
let manifest = Manifest::<N>::open(&path).map_err(PackageError::failed_to_open_manifest)?;
// Lookup the program id.
// let program_id = manifest.program_id();
@ -97,7 +98,7 @@ impl Context {
std::fs::read_to_string(manifest.path()).map_err(PackageError::failed_to_read_manifest)?;
// Construct the file path.
let build_manifest_path = build_path.join(Manifest::<CurrentNetwork>::file_name());
let build_manifest_path = build_path.join(Manifest::<N>::file_name());
// Write the file.
File::create(build_manifest_path)

View File

@ -23,7 +23,6 @@ pub use commands::*;
mod helpers;
pub use helpers::*;
pub(crate) type CurrentNetwork = snarkvm::prelude::MainnetV0;
pub(crate) const SNARKVM_COMMAND: &str = "snarkvm";
#[cfg(test)]

View File

@ -21,22 +21,22 @@ use crate::{
source::{MainFile, SourceDirectory},
};
use leo_errors::{PackageError, Result};
use snarkvm::console::prelude::Network;
use leo_retriever::NetworkName;
use serde::Deserialize;
use std::{marker::PhantomData, path::Path};
use std::path::Path;
#[derive(Deserialize)]
pub struct Package<N: Network> {
pub struct Package {
pub name: String,
pub version: String,
pub description: Option<String>,
pub license: Option<String>,
_phantom: PhantomData<N>,
pub network: NetworkName,
}
impl<N: Network> Package<N> {
pub fn new(package_name: &str) -> Result<Self> {
impl Package {
pub fn new(package_name: &str, network: NetworkName) -> Result<Self> {
// 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());
@ -47,7 +47,7 @@ impl<N: Network> Package<N> {
version: "0.1.0".to_owned(),
description: None,
license: None,
_phantom: PhantomData,
network,
})
}
@ -126,7 +126,7 @@ impl<N: Network> Package<N> {
}
/// Creates a Leo package at the given path
pub fn initialize(package_name: &str, path: &Path) -> Result<()> {
pub fn initialize(package_name: &str, network: NetworkName, path: &Path) -> Result<()> {
// Verify that the .gitignore file does not exist.
if !Gitignore::exists_at(path) {
// Create the .gitignore file.
@ -134,9 +134,9 @@ impl<N: Network> Package<N> {
}
// Verify that the .env file does not exist.
if !Env::<N>::exists_at(path) {
if !Env::exists_at(path) {
// Create the .env file.
Env::<N>::new()?.write_to(path)?;
Env::new(network)?.write_to(path)?;
}
// Create the source directory.
@ -169,29 +169,27 @@ impl<N: Network> Package<N> {
mod tests {
use super::*;
type CurrentNetwork = snarkvm::prelude::MainnetV0;
#[test]
fn test_is_package_name_valid() {
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::is_aleo_name_valid("foo"));
assert!(Package::is_aleo_name_valid("foo_bar"));
assert!(Package::is_aleo_name_valid("foo1"));
assert!(Package::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-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"));
assert!(!Package::is_aleo_name_valid("foo-bar"));
assert!(!Package::is_aleo_name_valid("foo-bar-baz"));
assert!(!Package::is_aleo_name_valid("foo-1"));
assert!(!Package::is_aleo_name_valid(""));
assert!(!Package::is_aleo_name_valid("-"));
assert!(!Package::is_aleo_name_valid("-foo"));
assert!(!Package::is_aleo_name_valid("-foo-"));
assert!(!Package::is_aleo_name_valid("_foo"));
assert!(!Package::is_aleo_name_valid("foo--bar"));
assert!(!Package::is_aleo_name_valid("foo---bar"));
assert!(!Package::is_aleo_name_valid("foo--bar--baz"));
assert!(!Package::is_aleo_name_valid("foo---bar---baz"));
assert!(!Package::is_aleo_name_valid("foo*bar"));
assert!(!Package::is_aleo_name_valid("foo,bar"));
assert!(!Package::is_aleo_name_valid("1-foo"));
}
}

View File

@ -16,26 +16,28 @@
//! The `.env` file.
use leo_errors::{PackageError, Result};
use snarkvm::console::{account::PrivateKey, prelude::Network};
use snarkvm::console::account::PrivateKey;
use leo_retriever::NetworkName;
use serde::Deserialize;
use std::{borrow::Cow, fs::File, io::Write, marker::PhantomData, path::Path};
use snarkvm::prelude::{MainnetV0, TestnetV0};
use std::{borrow::Cow, fs::File, io::Write, path::Path};
pub static ENV_FILENAME: &str = ".env";
// TODO: Should this be generic over network?
#[derive(Deserialize, Default)]
pub struct Env<N: Network> {
pub struct Env {
data: String,
_phantom: PhantomData<N>,
}
impl<N: Network> Env<N> {
pub fn new() -> Result<Self> {
Ok(Self { data: Self::template()?, _phantom: PhantomData })
impl Env {
pub fn new(network: NetworkName) -> Result<Self> {
Ok(Self { data: Self::template(network)? })
}
pub fn from(data: String) -> Self {
Self { data, _phantom: PhantomData }
Self { data }
}
pub fn exists_at(path: &Path) -> bool {
@ -57,13 +59,16 @@ impl<N: Network> Env<N> {
Ok(())
}
fn template() -> Result<String> {
fn template(network: NetworkName) -> Result<String> {
// Initialize an RNG.
let rng = &mut rand::thread_rng();
// Initialize a new development private key.
let private_key = PrivateKey::<N>::new(rng)?;
let private_key = match network {
NetworkName::MainnetV0 => PrivateKey::<MainnetV0>::new(rng)?.to_string(),
NetworkName::TestnetV0 => PrivateKey::<TestnetV0>::new(rng)?.to_string(),
};
Ok(format!("NETWORK=mainnet\nPRIVATE_KEY={private_key}\n"))
Ok(format!("NETWORK={network}\nPRIVATE_KEY={private_key}\n"))
}
}

View File

@ -14,6 +14,7 @@
// 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 leo_errors::{CliError, LeoError};
use serde::{Deserialize, Serialize};
use std::fmt;
@ -26,12 +27,14 @@ pub enum NetworkName {
MainnetV0,
}
impl From<&String> for NetworkName {
fn from(network: &String) -> Self {
match network.to_ascii_lowercase().as_str() {
"testnet" => NetworkName::TestnetV0,
"mainnet" => NetworkName::MainnetV0,
_ => panic!("Invalid network"),
impl TryFrom<&str> for NetworkName {
type Error = LeoError;
fn try_from(network: &str) -> Result<Self, LeoError> {
match network {
"testnet" => Ok(NetworkName::TestnetV0),
"mainnet" => Ok(NetworkName::MainnetV0),
_ => Err(LeoError::CliError(CliError::invalid_network_name(network))),
}
}
}