use .env for network, sk, endpoint (can override with CLI flag)

This commit is contained in:
evan-schott 2024-06-20 12:31:22 -07:00
parent b79c0a1ab6
commit 2f32657828
12 changed files with 146 additions and 69 deletions

View File

@ -285,13 +285,4 @@ pub fn compile_and_process<'a>(parsed: &'a mut Compiler<'a, CurrentNetwork>) ->
Ok(bytecode)
}
/// Returns the private key from the .env file specified in the directory.
#[allow(unused)]
pub fn dotenv_private_key(directory: &Path) -> Result<PrivateKey<CurrentNetwork>> {
use std::str::FromStr;
dotenvy::from_path(directory.join(".env")).map_err(|_| anyhow!("Missing a '.env' file in the test directory."))?;
// Load the private key from the environment.
let private_key = dotenvy::var("PRIVATE_KEY").map_err(|e| anyhow!("Missing PRIVATE_KEY - {e}"))?;
// Parse the private key.
PrivateKey::<CurrentNetwork>::from_str(&private_key)
}

View File

@ -264,4 +264,25 @@ create_messages!(
msg: format!("{error}"),
help: None,
}
@backtraced
failed_to_get_endpoint_from_env {
args: (command: impl Display),
msg: "Failed to get an endpoint.".to_string(),
help: Some(format!("Either make sure you have a `.env` file in current project directory with an `ENDPOINT` variable set, or set the `--endpoint` flag when invoking the CLI command.\n Example: `ENDPOINT=https://api.explorer.aleo.org/v1` or `leo {command} --endpoint \"https://api.explorer.aleo.org/v1\"`.")),
}
@backtraced
failed_to_get_private_key_from_env {
args: (command: impl Display),
msg: "Failed to get a private key.".to_string(),
help: Some(format!("Either make sure you have a `.env` file in current project directory with a `PRIVATE_KEY` variable set, or set the `--private-key` flag when invoking the CLI command.\n Example: `PRIVATE_KEY=0x1234...` or `leo {command} --private-key \"APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH\"`.")),
}
@backtraced
failed_to_get_network_from_env {
args: (command: impl Display),
msg: "Failed to get a network.".to_string(),
help: Some(format!("Either make sure you have a `.env` file in current project directory with a `NETWORK` variable set, or set the `--network` flag when invoking the CLI command.\n Example: `NETWORK=testnet` or `leo {command} --network testnet`.")),
}
);

View File

@ -109,7 +109,7 @@ impl LeoError {
FlattenError(error) => error.error_code(),
UtilError(error) => error.error_code(),
LastErrorCode(_) => unreachable!(),
Anyhow(_) => unimplemented!(), // todo: implement error codes for snarkvm errors.
Anyhow(_) => "SnarkVM Error".to_string(), // todo: implement error codes for snarkvm errors.
}
}
@ -128,7 +128,7 @@ impl LeoError {
FlattenError(error) => error.exit_code(),
UtilError(error) => error.exit_code(),
LastErrorCode(code) => *code,
Anyhow(_) => unimplemented!(), // todo: implement exit codes for snarkvm errors.
Anyhow(_) => 11000, // todo: implement exit codes for snarkvm errors.
}
}
}

View File

@ -93,7 +93,7 @@ impl Command for Build {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.options.network.as_str())?;
let network = NetworkName::try_from(context.get_network(&self.options.network, "build")?)?;
match network {
NetworkName::MainnetV0 => handle_build::<MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_build::<TestnetV0>(&self, context),
@ -123,7 +123,7 @@ fn handle_build<N: Network>(command: &Build, context: Context) -> Result<<Build
// 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())
let mut retriever = Retriever::<N>::new(main_sym, &package_path, &home_path, context.get_endpoint(&command.options.endpoint, "build")?.to_string())
.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()))?;

View File

@ -72,10 +72,11 @@ impl Command for Deploy {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.options.network.as_str())?;
let network = NetworkName::try_from(context.get_network(&self.options.network, "deploy")?)?;
let endpoint = context.get_endpoint(&self.options.endpoint, "deploy")?;
match network {
NetworkName::MainnetV0 => handle_deploy::<AleoV0, MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_deploy::<AleoTestnetV0, TestnetV0>(&self, context),
NetworkName::MainnetV0 => handle_deploy::<AleoV0, MainnetV0>(&self, context, network, &endpoint),
NetworkName::TestnetV0 => handle_deploy::<AleoTestnetV0, TestnetV0>(&self, context, network, &endpoint),
}
}
}
@ -84,21 +85,18 @@ impl Command for Deploy {
fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
command: &Deploy,
context: Context,
network: NetworkName,
endpoint: &str,
) -> Result<<Deploy as Command>::Output> {
// Get the program name.
let project_name = context.open_manifest::<N>()?.program_id().to_string();
// Get the private key.
let private_key = match &command.fee_options.private_key {
Some(key) => PrivateKey::from_str(key)?,
None => PrivateKey::from_str(
&dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string(),
)?,
};
let private_key = context.get_private_key(&command.fee_options.private_key, "deploy")?;
let address = Address::try_from(&private_key)?;
// Specify the query
let query = SnarkVMQuery::from(&command.options.endpoint);
let query = SnarkVMQuery::from(endpoint);
let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
@ -164,8 +162,8 @@ fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
// Make sure the user has enough public balance to pay for the deployment.
check_balance(
&private_key,
&command.options.endpoint,
&command.options.network,
&endpoint,
&network.to_string(),
context.clone(),
total_cost,
)?;
@ -190,7 +188,7 @@ fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
if !command.fee_options.yes {
let prompt = format!(
"Do you want to submit deployment of program `{name}.aleo` to network {} via endpoint {} using address {}?",
command.options.network, command.options.endpoint, address
network, endpoint, address
);
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
@ -204,7 +202,7 @@ fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
}
println!("✅ Created deployment transaction for '{}'\n", name.bold());
handle_broadcast(
&format!("{}/{}/transaction/broadcast", command.options.endpoint, command.options.network),
&format!("{}/{}/transaction/broadcast", endpoint, network),
transaction,
name,
)?;

View File

@ -87,16 +87,17 @@ impl Command for Execute {
fn apply(self, context: Context, _input: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.compiler_options.network.as_str())?;
let network = NetworkName::try_from(context.get_network(&self.compiler_options.network, "deploy")?)?;
let endpoint = context.get_endpoint(&self.compiler_options.endpoint, "deploy")?;
match network {
NetworkName::MainnetV0 => handle_execute::<AleoV0>(self, context),
NetworkName::TestnetV0 => handle_execute::<AleoTestnetV0>(self, context),
NetworkName::MainnetV0 => handle_execute::<AleoV0>(self, context, network, &endpoint),
NetworkName::TestnetV0 => handle_execute::<AleoTestnetV0>(self, context, network, &endpoint),
}
}
}
// A helper function to handle the `execute` command.
fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execute as Command>::Output> {
fn handle_execute<A: Aleo>(command: Execute, context: Context, network: NetworkName, endpoint: &str) -> Result<<Execute as Command>::Output> {
// If input values are provided, then run the program with those inputs.
// Otherwise, use the input file.
let mut inputs = command.inputs.clone();
@ -156,7 +157,7 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
// Specify the query
let query =
SnarkVMQuery::<A::Network, BlockMemory<A::Network>>::from(command.compiler_options.endpoint.clone());
SnarkVMQuery::<A::Network, BlockMemory<A::Network>>::from(endpoint);
// Initialize the storage.
let store = ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?;
@ -167,7 +168,7 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
// Load the main program, and all of its imports.
let program_id = &ProgramID::<A::Network>::from_str(&format!("{}.aleo", program_name))?;
// TODO: X
load_program_from_network(&command, context.clone(), &mut vm.process().write(), program_id)?;
load_program_from_network(&command, context.clone(), &mut vm.process().write(), program_id, network, endpoint)?;
let fee_record = if let Some(record) = command.fee_options.record {
Some(parse_record(&private_key, &record)?)
@ -208,8 +209,8 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
if fee_record.is_none() {
check_balance::<A::Network>(
&private_key,
&command.compiler_options.endpoint,
&command.compiler_options.network,
endpoint,
&network.to_string(),
context,
total_cost,
)?;
@ -220,7 +221,7 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
if !command.fee_options.yes {
let prompt = format!(
"Do you want to submit execution of function `{}` on program `{program_name}.aleo` to network {} via endpoint {} using address {}?",
&command.name, command.compiler_options.network, command.compiler_options.endpoint, address
&command.name, network, endpoint, address
);
let confirmation = Confirm::with_theme(&ColorfulTheme::default())
.with_prompt(prompt)
@ -236,7 +237,7 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
handle_broadcast(
&format!(
"{}/{}/transaction/broadcast",
command.compiler_options.endpoint, command.compiler_options.network
endpoint, network
),
transaction,
&program_name,
@ -262,7 +263,7 @@ fn handle_execute<A: Aleo>(command: Execute, context: Context) -> Result<<Execut
// Execute the request.
let (response, execution, metrics) = package
.execute::<A, _>(
command.compiler_options.endpoint.clone(),
endpoint.to_string(),
&private_key,
Identifier::try_from(command.name.clone())?,
&inputs,
@ -339,11 +340,13 @@ fn load_program_from_network<N: Network>(
context: Context,
process: &mut Process<N>,
program_id: &ProgramID<N>,
network: NetworkName,
endpoint: &str,
) -> Result<()> {
// Fetch the program.
let program_src = Query {
endpoint: command.compiler_options.endpoint.clone(),
network: command.compiler_options.network.clone(),
endpoint: Some(endpoint.to_string()),
network: Some(network.to_string()),
command: QueryCommands::Program {
command: crate::cli::commands::query::Program {
name: program_id.to_string(),
@ -365,7 +368,7 @@ fn load_program_from_network<N: Network>(
// Add the imports to the process if does not exist yet.
if !process.contains_program(import_program_id) {
// Recursively load the program and its imports.
load_program_from_network(command, context.clone(), process, import_program_id)?;
load_program_from_network(command, context.clone(), process, import_program_id, network, endpoint)?;
}
}

View File

@ -135,12 +135,10 @@ pub trait Command {
pub struct BuildOptions {
#[clap(
long,
help = "Endpoint to retrieve network state from.",
default_value = "https://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,
help = "Endpoint to retrieve network state from. Overrides setting in `.env`.")]
pub endpoint: Option<String>,
#[clap(long, help = "Network to broadcast to. Overrides setting in `.env`.")]
pub(crate) network: Option<String>,
#[clap(long, help = "Does not recursively compile dependencies.")]
pub non_recursive: bool,
#[clap(long, help = "Enables offline mode.")]
@ -182,8 +180,8 @@ pub struct BuildOptions {
impl Default for BuildOptions {
fn default() -> Self {
Self {
endpoint: "http://api.explorer.aleo.org/v1".to_string(),
network: "mainnet".to_string(),
endpoint: None,
network:None,
non_recursive: false,
offline: false,
enable_symbol_table_spans: false,
@ -252,8 +250,8 @@ fn check_balance<N: Network>(
let address = Address::<N>::try_from(ViewKey::try_from(private_key)?)?;
// Query the public balance of the address on the `account` mapping from `credits.aleo`.
let mut public_balance = Query {
endpoint: endpoint.to_string(),
network: network.to_string(),
endpoint: Some(endpoint.to_string()),
network: Some(network.to_string()),
command: QueryCommands::Program {
command: crate::cli::commands::query::Program {
name: "credits".to_string(),

View File

@ -51,12 +51,11 @@ pub struct Query {
short,
long,
global = true,
help = "Endpoint to retrieve network state from. Defaults to https://api.explorer.aleo.org/v1.",
default_value = "https://api.explorer.aleo.org/v1"
help = "Endpoint to retrieve network state from. Defaults to entry in `.env`.",
)]
pub endpoint: String,
#[clap(short, long, global = true, help = "Network to use. Defaults to mainnet.", default_value = "mainnet")]
pub(crate) network: String,
pub endpoint: Option<String>,
#[clap(short, long, global = true, help = "Network to use. Defaults to entry in `.env`.")]
pub(crate) network: Option<String>,
#[clap(subcommand)]
pub command: QueryCommands,
}
@ -75,16 +74,17 @@ impl Command for Query {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.network.as_str())?;
let network = NetworkName::try_from(context.get_network(&self.network, "query")?)?;
let endpoint = context.get_endpoint(&self.endpoint, "query")?;
match network {
NetworkName::MainnetV0 => handle_query::<MainnetV0>(self, context),
NetworkName::TestnetV0 => handle_query::<TestnetV0>(self, context),
NetworkName::MainnetV0 => handle_query::<MainnetV0>(self, context, &network.to_string(), &endpoint),
NetworkName::TestnetV0 => handle_query::<TestnetV0>(self, context, &network.to_string(), &endpoint),
}
}
}
// A helper function to handle the `query` command.
fn handle_query<N: Network>(query: Query, context: Context) -> Result<<Query as Command>::Output> {
fn handle_query<N: Network>(query: Query, context: Context, network: &str, endpoint: &str) -> Result<<Query as Command>::Output> {
let recursive = context.recursive;
let (program, output) = match query.command {
QueryCommands::Block { command } => (None, command.apply(context, ())?),
@ -98,7 +98,7 @@ fn handle_query<N: Network>(query: Query, context: Context) -> Result<<Query as
QueryCommands::Stateroot { command } => (None, command.apply(context, ())?),
QueryCommands::Committee { command } => (None, command.apply(context, ())?),
QueryCommands::Mempool { command } => {
if query.endpoint == "https://api.explorer.aleo.org/v1" {
if endpoint == "https://api.explorer.aleo.org/v1" {
tracing::warn!(
"⚠️ `leo query mempool` is only valid when using a custom endpoint. Specify one using `--endpoint`."
);
@ -106,7 +106,7 @@ fn handle_query<N: Network>(query: Query, context: Context) -> Result<<Query as
(None, command.apply(context, ())?)
}
QueryCommands::Peers { command } => {
if query.endpoint == "https://api.explorer.aleo.org/v1" {
if endpoint == "https://api.explorer.aleo.org/v1" {
tracing::warn!(
"⚠️ `leo query peers` is only valid when using a custom endpoint. Specify one using `--endpoint`."
);
@ -116,7 +116,7 @@ fn handle_query<N: Network>(query: Query, context: Context) -> Result<<Query as
};
// Make GET request to retrieve on-chain state.
let url = format!("{}/{}/{output}", query.endpoint, query.network);
let url = format!("{}/{}/{output}", endpoint, network);
let result = fetch_from_network(&url)?;
if !recursive {
tracing::info!("✅ Successfully retrieved data from '{url}'.\n");

View File

@ -52,7 +52,7 @@ impl Command for Run {
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
// Parse the network.
let network = NetworkName::try_from(self.compiler_options.network.as_str())?;
let network = NetworkName::try_from(context.get_network(&self.compiler_options.network, "run")?)?;
match network {
NetworkName::MainnetV0 => handle_run::<MainnetV0>(self, context),
NetworkName::TestnetV0 => handle_run::<TestnetV0>(self, context),

View File

@ -23,13 +23,14 @@ use snarkvm::file::Manifest;
use aleo_std::aleo_dir;
use indexmap::IndexMap;
use snarkvm::prelude::Network;
use snarkvm::prelude::{anyhow, Itertools, Network, PrivateKey};
use std::{
env::current_dir,
fs::File,
io::Write,
path::{Path, PathBuf},
};
use std::str::FromStr;
/// Project context, manifest, current directory etc
/// All the info that is relevant in most of the commands
@ -140,4 +141,54 @@ impl Context {
Ok(list)
}
/// Returns the private key from the .env file specified in the directory.
pub fn dotenv_private_key<N: Network>(&self, command: &str) -> Result<PrivateKey<N>> {
dotenvy::from_path(self.dir()?.join(".env")).map_err(|_| CliError::failed_to_get_private_key_from_env(command))?;
// Load the private key from the environment.
let private_key = dotenvy::var("PRIVATE_KEY").map_err(|_| CliError::failed_to_get_private_key_from_env(command))?;
// Parse the private key.
Ok(PrivateKey::<N>::from_str(&private_key)?)
}
/// Returns the endpoint from the .env file specified in the directory.
pub fn dotenv_endpoint(&self, command: &str) -> Result<String> {
dotenvy::from_path(self.dir()?.join(".env")).map_err(|_| CliError::failed_to_get_endpoint_from_env(command))?;
// Load the endpoint from the environment.
Ok(dotenvy::var("ENDPOINT").map_err(|_| CliError::failed_to_get_endpoint_from_env(command))?)
}
/// Returns the network from the .env file specified in the directory.
pub fn dotenv_network(&self, command: &str) -> Result<String> {
dotenvy::from_path(self.dir()?.join(".env")).map_err(|_| CliError::failed_to_get_network_from_env(command))?;
// Load the network from the environment.
Ok(dotenvy::var("NETWORK").map_err(|_| CliError::failed_to_get_network_from_env(command))?)
}
/// Returns the endpoint to interact with the network.
/// If the `--endpoint` options is not provided, it will default to the one in the `.env` file.
pub fn get_endpoint(&self, endpoint: &Option<String>, command: &str) -> Result<String> {
match endpoint {
Some(endpoint) => Ok(endpoint.clone()),
None => Ok(self.dotenv_endpoint(command)?),
}
}
/// Returns the network name.
/// If the `--network` options is not provided, it will default to the one in the `.env` file.
pub fn get_network(&self, network: &Option<String>, command: &str) -> Result<String> {
match network {
Some(network) => Ok(network.clone()),
None => Ok(self.dotenv_network(command)?),
}
}
/// Returns the private key.
/// If the `--private-key` options is not provided, it will default to the one in the `.env` file.
pub fn get_private_key<N: Network>(&self, private_key: &Option<String>, command: &str) -> Result<PrivateKey<N>> {
match private_key {
Some(private_key) => Ok(PrivateKey::<N>::from_str(&private_key)?),
None => self.dotenv_private_key(command),
}
}
}

View File

@ -22,8 +22,9 @@ use leo_errors::{PackageError, Result};
use leo_retriever::{Manifest, NetworkName};
use serde::Deserialize;
use snarkvm::prelude::Network;
use snarkvm::prelude::{Network, PrivateKey};
use std::path::Path;
use std::str::FromStr;
#[derive(Deserialize)]
pub struct Package {
@ -146,8 +147,9 @@ impl Package {
// Create the .gitignore file.
Gitignore::new().write_to(&path)?;
// Create the .env file.
Env::<N>::new(None, endpoint)?.write_to(&path)?;
// Create the .env file.
// Include the private key of validator 0 for ease of use with local devnets, as it will automatically be seeded with funds.
Env::<N>::new(Some(PrivateKey::<N>::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH")?), endpoint)?.write_to(&path)?;
// Create a manifest.
let manifest = Manifest::default(package_name);

View File

@ -48,6 +48,19 @@ impl TryFrom<&str> for NetworkName {
}
}
}
impl TryFrom<String> for NetworkName {
type Error = LeoError;
fn try_from(network: String) -> Result<Self, LeoError> {
if network == "testnet" {
Ok(NetworkName::TestnetV0)
} else if network == "mainnet" {
Ok(NetworkName::MainnetV0)
} else {
Err(LeoError::CliError(CliError::invalid_network_name(&network)))
}
}
}
impl fmt::Display for NetworkName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {