use super::{DEFAULT_ENDPOINT, Developer};
use crate::{
commands::StoreFormat,
helpers::args::{parse_private_key, prepare_endpoint},
};
use snarkvm::{
circuit::{Aleo, AleoCanaryV0, AleoTestnetV0, AleoV0},
console::{
network::{CanaryV0, MainnetV0, Network, TestnetV0},
program::ProgramOwner,
},
ledger::store::helpers::memory::BlockMemory,
prelude::{
ProgramID,
VM,
block::Transaction,
deployment_cost,
query::{Query, QueryTrait},
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
};
use aleo_std::StorageMode;
use anyhow::Result;
use clap::{Parser, builder::NonEmptyStringValueParser};
use colored::Colorize;
use snarkvm::prelude::{Address, ConsensusVersion};
use std::str::FromStr;
use ureq::http::Uri;
use zeroize::Zeroize;
use anyhow::Context;
#[derive(Debug, Parser)]
#[command(
group(clap::ArgGroup::new("mode").required(true).multiple(false)),
group(clap::ArgGroup::new("key").required(true).multiple(false))
)]
pub struct Deploy {
program_id: String,
#[clap(long)]
path: Option<String>,
#[clap(short = 'p', long, group = "key", value_parser=NonEmptyStringValueParser::default())]
private_key: Option<String>,
#[clap(long, group = "key")]
dev_key: Option<u16>,
#[clap(long, group = "key", value_parser=NonEmptyStringValueParser::default())]
private_key_file: Option<String>,
#[clap(short, long, alias="query", default_value=DEFAULT_ENDPOINT, verbatim_doc_comment)]
endpoint: Uri,
#[clap(long, default_value_t = 0)]
priority_fee: u64,
#[clap(short, long)]
record: Option<String>,
#[clap(short, long, group = "mode", verbatim_doc_comment)]
broadcast: Option<Option<Uri>>,
#[clap(short, long, group = "mode")]
dry_run: bool,
#[clap(long, group = "mode")]
store: Option<String>,
#[clap(long, value_enum, default_value_t = StoreFormat::Bytes, requires="store")]
store_format: StoreFormat,
#[clap(long, requires = "broadcast")]
wait: bool,
#[clap(long, default_value_t = 60, requires = "wait")]
timeout: u64,
}
impl Drop for Deploy {
fn drop(&mut self) {
self.private_key.zeroize();
}
}
impl Deploy {
pub fn parse<N: Network>(self) -> Result<String> {
match N::ID {
MainnetV0::ID => self.construct_deployment::<MainnetV0, AleoV0>(),
TestnetV0::ID => self.construct_deployment::<TestnetV0, AleoTestnetV0>(),
CanaryV0::ID => self.construct_deployment::<CanaryV0, AleoCanaryV0>(),
_ => unreachable!(),
}
.with_context(|| "Deployment failed")
}
fn construct_deployment<N: Network, A: Aleo<Network = N, BaseField = N::Field>>(self) -> Result<String> {
let endpoint = prepare_endpoint(self.endpoint.clone())?;
let query = Query::<N, BlockMemory<N>>::from(endpoint.clone());
let private_key = parse_private_key(self.private_key.clone(), self.private_key_file.clone(), self.dev_key)?;
let program_id = ProgramID::from_str(&self.program_id).with_context(|| "Failed to parse program ID")?;
let package =
Developer::parse_package(program_id, &self.path).with_context(|| "Failed to parse program package")?;
println!("📦 Creating deployment transaction for '{}'...\n", &program_id.to_string().bold());
let process = package.get_process()?;
let mut deployment =
package.deploy::<A>(&process, None).with_context(|| "Failed to generate the deployment")?;
let consensus_version =
N::CONSENSUS_VERSION(query.current_block_height().with_context(|| "Failed to query consensus height")?)?;
if consensus_version < ConsensusVersion::V9 {
deployment.set_program_checksum_raw(None);
deployment.set_program_owner_raw(None);
} else {
deployment.set_program_checksum_raw(Some(package.program().to_checksum()));
deployment.set_program_owner_raw(Some(Address::try_from(&private_key)?));
};
let deployment_id = deployment.to_deployment_id().with_context(|| "Failed to compute deployment ID")?;
let (minimum_deployment_cost, (_, _, _, _)) = deployment_cost(&process, &deployment, consensus_version)
.with_context(|| "Failed to compute the minimum deployment cost")?;
let transaction = {
let rng = &mut rand::thread_rng();
let store = ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)
.with_context(|| "Failed to open the consensus store")?;
let vm = VM::from(store).with_context(|| "Failed to initialize the virtual machine")?;
let fee = match &self.record {
Some(record) => {
let fee_record =
Developer::parse_record(&private_key, record).with_context(|| "Failed to parse record")?;
let fee_authorization = vm.authorize_fee_private(
&private_key,
fee_record,
minimum_deployment_cost,
self.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
.with_context(|| "Failed to execute fee authorization")?
}
None => {
let fee_authorization = vm.authorize_fee_public(
&private_key,
minimum_deployment_cost,
self.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(&query), rng)
.with_context(|| "Failed to execute fee authorization")?
}
};
let owner = ProgramOwner::new(&private_key, deployment_id, rng)
.with_context(|| "Failed to construct program owner")?;
Transaction::from_deployment(owner, deployment, fee).with_context(|| "Failed to crate transaction")?
};
println!("✅ Created deployment transaction for '{}'", program_id.to_string().bold());
Developer::handle_transaction(
&endpoint,
&self.broadcast,
self.dry_run,
&self.store,
self.store_format,
self.wait,
self.timeout,
transaction,
program_id.to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{CLI, Command, DeveloperCommand};
use anyhow::bail;
#[test]
fn clap_snarkos_deploy_missing_mode() {
let arg_vec = &[
"snarkos",
"developer",
"deploy",
"--private-key=PRIVATE_KEY",
"--endpoint=ENDPOINT",
"--priority-fee=77",
"--record=RECORD",
"hello.aleo",
];
let err = CLI::try_parse_from(arg_vec).unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
}
#[test]
fn clap_snarkos_deploy() -> Result<()> {
let arg_vec = &[
"snarkos",
"developer",
"deploy",
"--private-key=PRIVATE_KEY",
"--endpoint=ENDPOINT",
"--priority-fee=77",
"--dry-run",
"--record=RECORD",
"hello.aleo",
];
let cli = CLI::try_parse_from(arg_vec)?;
let Command::Developer(developer) = cli.command else {
bail!("Unexpected result of clap parsing!");
};
let DeveloperCommand::Deploy(deploy) = developer.command else {
bail!("Unexpected result of clap parsing!");
};
assert_eq!(developer.network, 0);
assert_eq!(deploy.program_id, "hello.aleo");
assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
assert_eq!(deploy.private_key_file, None);
assert_eq!(deploy.endpoint, "ENDPOINT");
assert!(deploy.dry_run);
assert!(deploy.broadcast.is_none());
assert_eq!(deploy.store, None);
assert_eq!(deploy.priority_fee, 77);
assert_eq!(deploy.record, Some("RECORD".to_string()));
Ok(())
}
#[test]
fn clap_snarkos_deploy_broadcast() -> Result<()> {
let arg_vec = &[
"snarkos",
"developer",
"deploy",
"--private-key=PRIVATE_KEY",
"--endpoint=ENDPOINT",
"--priority-fee=77",
"--broadcast=ENDPOINT2",
"--record=RECORD",
"hello.aleo",
];
let cli = CLI::try_parse_from(arg_vec)?;
let Command::Developer(developer) = cli.command else {
bail!("Unexpected result of clap parsing!");
};
let DeveloperCommand::Deploy(deploy) = developer.command else {
bail!("Unexpected result of clap parsing!");
};
assert_eq!(developer.network, 0);
assert_eq!(deploy.program_id, "hello.aleo");
assert_eq!(deploy.private_key, Some("PRIVATE_KEY".to_string()));
assert_eq!(deploy.private_key_file, None);
assert_eq!(deploy.endpoint, "ENDPOINT");
assert!(!deploy.dry_run);
assert_eq!(Some(Some(Uri::try_from("ENDPOINT2").unwrap())), deploy.broadcast);
assert_eq!(deploy.store, None);
assert_eq!(deploy.priority_fee, 77);
assert_eq!(deploy.record, Some("RECORD".to_string()));
Ok(())
}
}