This commit is contained in:
evan-schott 2024-05-15 11:01:13 -07:00
parent 753f789bae
commit 8254320846
7 changed files with 363 additions and 1328 deletions

1242
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -43,14 +43,10 @@ members = [
"utils/retriever"
]
[workspace.dependencies.snarkos-cli]
git = "https://github.com/AleoNet/snarkOS.git"
rev = "01ea476"
[workspace.dependencies.snarkvm]
#version = "0.16.19"
git = "https://github.com/AleoNet/snarkVM.git"
rev = "140ff26"
rev = "fddd8b9"
[lib]
path = "leo/lib.rs"
@ -156,9 +152,6 @@ version = "1.0"
[dependencies.serial_test]
version = "3.1.1"
[dependencies.snarkos-cli]
workspace = true
[dependencies.snarkvm]
workspace = true
features = [ "circuit", "console" ]

View File

@ -243,4 +243,25 @@ create_messages!(
msg: format!("Failed to build program: {error}"),
help: None,
}
@backtraced
failed_to_parse_record {
args: (error: impl Display),
msg: format!("Failed to parse the record string.\nSnarkVM Error: {error}"),
help: None,
}
@backtraced
string_parse_error {
args: (error: impl Display),
msg: format!("{error}"),
help: None,
}
@backtraced
broadcast_error {
args: (error: impl Display),
msg: format!("{error}"),
help: None,
}
);

View File

@ -15,13 +15,24 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use aleo_std::StorageMode;
use leo_retriever::NetworkName;
use snarkos_cli::commands::{Deploy as SnarkOSDeploy, Developer};
use snarkvm::{
circuit::{Aleo, AleoTestnetV0, AleoV0},
cli::helpers::dotenv_private_key,
prelude::{MainnetV0, TestnetV0},
ledger::query::Query as SnarkVMQuery,
package::Package as SnarkVMPackage,
prelude::{
deployment_cost,
store::{helpers::memory::ConsensusMemory, ConsensusStore},
MainnetV0,
PrivateKey,
ProgramOwner,
TestnetV0,
VM,
},
};
use std::path::PathBuf;
use std::{path::PathBuf, str::FromStr};
/// Deploys an Aleo program.
#[derive(Parser, Debug)]
@ -39,7 +50,7 @@ pub struct Deploy {
)]
pub(crate) wait: u64,
#[clap(flatten)]
pub(crate) compiler_options: BuildOptions,
pub(crate) options: BuildOptions,
}
impl Command for Deploy {
@ -52,79 +63,121 @@ impl Command for Deploy {
fn prelude(&self, context: Context) -> Result<Self::Input> {
if !self.no_build {
(Build { options: self.compiler_options.clone() }).execute(context)?;
(Build { options: self.options.clone() }).execute(context)?;
}
Ok(())
}
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 = 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;
if private_key.is_none() {
private_key =
Some(dotenv_private_key().map_err(CliError::failed_to_read_environment_private_key)?.to_string());
let network = NetworkName::try_from(self.options.network.as_str())?;
match network {
NetworkName::MainnetV0 => handle_deploy::<AleoV0, MainnetV0>(&self, context),
NetworkName::TestnetV0 => handle_deploy::<AleoTestnetV0, TestnetV0>(&self, context),
}
let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
// Extract post-ordered list of local dependencies' paths from `leo.lock`.
if self.recursive {
// Cannot combine with private fee.
if self.fee_options.record.is_some() {
return Err(CliError::recursive_deploy_with_record().into());
}
all_paths = context.local_dependency_paths()?;
}
// Add the parent program to be deployed last.
all_paths.push((project_name, context.dir()?.join("build")));
for (index, (name, path)) in all_paths.iter().enumerate() {
// Set the deploy arguments.
let mut deploy_args = vec![
"snarkos".to_string(),
"--private-key".to_string(),
private_key.as_ref().unwrap().clone(),
"--query".to_string(),
self.compiler_options.endpoint.clone(),
"--priority-fee".to_string(),
self.fee_options.priority_fee.to_string(),
"--network".to_string(),
network.id().to_string(),
"--path".to_string(),
path.to_str().unwrap().parse().unwrap(),
"--broadcast".to_string(),
format!("{}/{}/transaction/broadcast", self.compiler_options.endpoint, self.compiler_options.network)
.to_string(),
name.clone(),
];
// Use record as payment option if it is provided.
if let Some(record) = self.fee_options.record.clone() {
deploy_args.push("--record".to_string());
deploy_args.push(record);
};
let deploy = SnarkOSDeploy::try_parse_from(deploy_args).unwrap();
// Deploy program.
Developer::Deploy(deploy).parse().map_err(CliError::failed_to_execute_deploy)?;
// Sleep for `wait_gap` seconds.
// This helps avoid parents from being serialized before children.
if index < all_paths.len() - 1 {
std::thread::sleep(std::time::Duration::from_secs(self.wait));
}
}
Ok(())
}
}
// A helper function to handle deployment logic.
fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
command: &Deploy,
context: Context,
) -> 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(),
)?,
};
// Specify the query
let query = SnarkVMQuery::from(&command.options.endpoint);
let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
// Extract post-ordered list of local dependencies' paths from `leo.lock`.
if command.recursive {
// Cannot combine with private fee.
if command.fee_options.record.is_some() {
return Err(CliError::recursive_deploy_with_record().into());
}
all_paths = context.local_dependency_paths()?;
}
// Add the parent program to be deployed last.
all_paths.push((project_name, context.dir()?.join("build")));
for (index, (name, path)) in all_paths.iter().enumerate() {
// Fetch the package from the directory.
let package = SnarkVMPackage::<N>::open(path)?;
println!("📦 Creating deployment transaction for '{}'...\n", &name.bold());
// Generate the deployment
let deployment = package.deploy::<A>(None)?;
let deployment_id = deployment.to_deployment_id()?;
// Generate the deployment transaction.
let transaction = {
// Initialize an RNG.
let rng = &mut rand::thread_rng();
let store = ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)?;
// Initialize the VM.
let vm = VM::from(store)?;
// Compute the minimum deployment cost.
let (minimum_deployment_cost, _) = deployment_cost(&deployment)?;
// Prepare the fees.
let fee = match &command.fee_options.record {
Some(record) => {
let fee_record = parse_record(&private_key, record)?;
let fee_authorization = vm.authorize_fee_private(
&private_key,
fee_record,
minimum_deployment_cost,
command.fee_options.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)?
}
None => {
let fee_authorization = vm.authorize_fee_public(
&private_key,
minimum_deployment_cost,
command.fee_options.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)?
}
};
// Construct the owner.
let owner = ProgramOwner::new(&private_key, deployment_id, rng)?;
// Create a new transaction.
Transaction::from_deployment(owner, deployment, fee)?
};
println!("✅ Created deployment transaction for '{}'", name.bold());
// Determine if the transaction should be broadcast, stored, or displayed to the user.
handle_broadcast(
&format!("{}/{}/transaction/broadcast", command.options.endpoint, command.fee_options.network),
transaction,
name,
)?;
if index < all_paths.len() - 1 {
std::thread::sleep(std::time::Duration::from_secs(command.wait));
}
}
Ok(())
}

View File

@ -17,7 +17,6 @@
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::{MainnetV0, Network, Parser as SnarkVMParser, TestnetV0},
@ -74,69 +73,67 @@ impl Command for Execute {
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(),
"--network".to_string(),
N::ID.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(());
// // 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.

View File

@ -57,11 +57,15 @@ use super::*;
use crate::cli::helpers::context::*;
use leo_errors::{emitter::Handler, CliError, PackageError, Result};
use leo_package::{build::*, outputs::OutputsDirectory, package::*};
use snarkvm::prelude::{block::Transaction, Ciphertext, Plaintext, PrivateKey, Record, ViewKey};
use clap::Parser;
use colored::Colorize;
use std::str::FromStr;
use tracing::span::Span;
use snarkvm::{console::network::Network, prelude::ToBytes};
/// Base trait for the Leo CLI, see methods and their documentation for details.
pub trait Command {
/// If the current command requires running another command beforehand
@ -202,7 +206,9 @@ impl Default for BuildOptions {
#[derive(Parser, Clone, Debug)]
pub struct FeeOptions {
#[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")]
pub(crate) priority_fee: String,
pub(crate) priority_fee: u64,
#[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(
@ -213,8 +219,90 @@ pub struct FeeOptions {
record: Option<String>,
}
impl Default for FeeOptions {
fn default() -> Self {
Self { priority_fee: "0".to_string(), private_key: None, record: None }
/// Parses the record string. If the string is a ciphertext, then attempt to decrypt it. Lifted from snarkOS.
pub fn parse_record<N: Network>(private_key: &PrivateKey<N>, record: &str) -> Result<Record<N, Plaintext<N>>> {
match record.starts_with("record1") {
true => {
// Parse the ciphertext.
let ciphertext = Record::<N, Ciphertext<N>>::from_str(record)?;
// Derive the view key.
let view_key = ViewKey::try_from(private_key)?;
// Decrypt the ciphertext.
Ok(ciphertext.decrypt(&view_key)?)
}
false => Ok(Record::<N, Plaintext<N>>::from_str(record)?),
}
}
/// Determine if the transaction should be broadcast or displayed to user.
fn handle_broadcast<N: Network>(endpoint: &String, transaction: Transaction<N>, operation: &String) -> Result<()> {
println!("Broadcasting transaction to {}...", endpoint.clone());
// Get the transaction id.
let transaction_id = transaction.id();
// TODO: remove
println!("Transaction {:?}", transaction);
let tx_bytes = transaction.to_bytes_le()?;
println!("Transaction bytes: {:?}", tx_bytes);
let tx_json = serde_json::to_string(&transaction).unwrap();
println!("Transaction JSON: {:?}", tx_json);
let deserialize_tx_json = serde_json::from_str::<Transaction<N>>(&tx_json).unwrap();
println!("Deserialized transaction: {:?}", deserialize_tx_json);
// Send the deployment request to the local development node.
return match ureq::post(endpoint).send_json(&transaction) {
Ok(id) => {
// Remove the quotes from the response.
let _response_string =
id.into_string().map_err(CliError::string_parse_error)?.trim_matches('\"').to_string();
match transaction {
Transaction::Deploy(..) => {
println!(
"⌛ Deployment {transaction_id} ('{}') has been broadcast to {}.",
operation.bold(),
endpoint
)
}
Transaction::Execute(..) => {
println!(
"⌛ Execution {transaction_id} ('{}') has been broadcast to {}.",
operation.bold(),
endpoint
)
}
Transaction::Fee(..) => {
println!("❌ Failed to broadcast fee '{}' to the {}.", operation.bold(), endpoint)
}
}
Ok(())
}
Err(error) => {
let error_message = match error {
ureq::Error::Status(code, response) => {
format!("(status code {code}: {:?})", response.into_string().map_err(CliError::string_parse_error)?)
}
ureq::Error::Transport(err) => format!("({err})"),
};
let msg = match transaction {
Transaction::Deploy(..) => {
format!("❌ Failed to deploy '{}' to {}: {}", operation.bold(), &endpoint, error_message)
}
Transaction::Execute(..) => {
format!(
"❌ Failed to broadcast execution '{}' to {}: {}",
operation.bold(),
&endpoint,
error_message
)
}
Transaction::Fee(..) => {
format!("❌ Failed to broadcast fee '{}' to {}: {}", operation.bold(), &endpoint, error_message)
}
};
Err(CliError::broadcast_error(msg).into())
}
};
}

View File

@ -26,9 +26,6 @@ version = "=1.12.0"
path = "../../utils/retriever"
version = "1.12.0"
[dependencies.snarkos-cli]
workspace = true
[dependencies.snarkvm]
workspace = true