snarkos_cli/commands/developer/
execute.rsuse super::Developer;
use snarkvm::{
console::network::{CanaryV0, MainnetV0, Network, TestnetV0},
prelude::{
Address,
Identifier,
Locator,
PrivateKey,
Process,
ProgramID,
VM,
Value,
query::Query,
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
};
use aleo_std::StorageMode;
use anyhow::{Result, anyhow, bail};
use clap::Parser;
use colored::Colorize;
use std::{path::PathBuf, str::FromStr};
use zeroize::Zeroize;
#[derive(Debug, Parser)]
pub struct Execute {
program_id: String,
function: String,
inputs: Vec<String>,
#[clap(default_value = "0", long = "network")]
pub network: u16,
#[clap(short, long)]
private_key: Option<String>,
#[clap(long)]
private_key_file: Option<String>,
#[clap(short, long)]
query: String,
#[clap(long)]
priority_fee: Option<u64>,
#[clap(short, long)]
record: Option<String>,
#[clap(short, long, conflicts_with = "dry_run")]
broadcast: Option<String>,
#[clap(short, long, conflicts_with = "broadcast")]
dry_run: bool,
#[clap(long)]
store: Option<String>,
#[clap(long = "storage_path")]
pub storage_path: Option<PathBuf>,
}
impl Drop for Execute {
fn drop(&mut self) {
if let Some(mut pk) = self.private_key.take() {
pk.zeroize()
}
}
}
impl Execute {
#[allow(clippy::format_in_format_args)]
pub fn parse(self) -> Result<String> {
if !self.dry_run && self.broadcast.is_none() && self.store.is_none() {
bail!("❌ Please specify one of the following actions: --broadcast, --dry-run, --store");
}
match self.network {
MainnetV0::ID => self.construct_execution::<MainnetV0>(),
TestnetV0::ID => self.construct_execution::<TestnetV0>(),
CanaryV0::ID => self.construct_execution::<CanaryV0>(),
unknown_id => bail!("Unknown network ID ({unknown_id})"),
}
}
fn construct_execution<N: Network>(&self) -> Result<String> {
let query = Query::from(&self.query);
let key_str = match (self.private_key.as_ref(), self.private_key_file.as_ref()) {
(Some(private_key), None) => private_key.to_owned(),
(None, Some(private_key_file)) => {
let path = private_key_file.parse::<PathBuf>().map_err(|e| anyhow!("Invalid path - {e}"))?;
std::fs::read_to_string(path)?.trim().to_string()
}
(None, None) => bail!("Missing the '--private-key' or '--private-key-file' argument"),
(Some(_), Some(_)) => {
bail!("Cannot specify both the '--private-key' and '--private-key-file' flags")
}
};
let private_key = PrivateKey::from_str(&key_str)?;
let program_id = ProgramID::from_str(&self.program_id)?;
let function = Identifier::from_str(&self.function)?;
let inputs = self.inputs.iter().map(|input| Value::from_str(input)).collect::<Result<Vec<Value<N>>>>()?;
let locator = Locator::<N>::from_str(&format!("{}/{}", program_id, function))?;
println!("📦 Creating execution transaction for '{}'...\n", &locator.to_string().bold());
let transaction = {
let rng = &mut rand::thread_rng();
let storage_mode = match &self.storage_path {
Some(path) => StorageMode::Custom(path.clone()),
None => StorageMode::Production,
};
let store = ConsensusStore::<N, ConsensusMemory<N>>::open(storage_mode)?;
let vm = VM::from(store)?;
load_program(&self.query, &mut vm.process().write(), &program_id)?;
let fee_record = match &self.record {
Some(record_string) => Some(Developer::parse_record(&private_key, record_string)?),
None => None,
};
let priority_fee = self.priority_fee.unwrap_or(0);
vm.execute(&private_key, (program_id, function), inputs.iter(), fee_record, priority_fee, Some(query), rng)?
};
if self.record.is_none() {
let address = Address::try_from(&private_key)?;
let public_balance = Developer::get_public_balance(&address, &self.query)?;
let storage_cost = transaction
.execution()
.ok_or_else(|| anyhow!("The transaction does not contain an execution"))?
.size_in_bytes()?;
let base_fee = storage_cost.saturating_add(self.priority_fee.unwrap_or(0));
if public_balance < base_fee {
bail!(
"❌ The public balance of {} is insufficient to pay the base fee for `{}`",
public_balance,
locator.to_string().bold()
);
}
}
println!("✅ Created execution transaction for '{}'", locator.to_string().bold());
Developer::handle_transaction(&self.broadcast, self.dry_run, &self.store, transaction, locator.to_string())
}
}
fn load_program<N: Network>(endpoint: &str, process: &mut Process<N>, program_id: &ProgramID<N>) -> Result<()> {
let program = Developer::fetch_program(program_id, endpoint)?;
if process.contains_program(program.id()) {
return Ok(());
}
for import_program_id in program.imports().keys() {
if !process.contains_program(import_program_id) {
load_program(endpoint, process, import_program_id)?;
}
}
if !process.contains_program(program.id()) {
process.add_program(&program)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::commands::{CLI, Command};
#[test]
fn clap_snarkos_execute() {
let arg_vec = vec![
"snarkos",
"developer",
"execute",
"--private-key",
"PRIVATE_KEY",
"--query",
"QUERY",
"--priority-fee",
"77",
"--record",
"RECORD",
"hello.aleo",
"hello",
"1u32",
"2u32",
];
let cli = CLI::parse_from(arg_vec);
if let Command::Developer(Developer::Execute(execute)) = cli.command {
assert_eq!(execute.network, 0);
assert_eq!(execute.private_key, Some("PRIVATE_KEY".to_string()));
assert_eq!(execute.query, "QUERY");
assert_eq!(execute.priority_fee, Some(77));
assert_eq!(execute.record, Some("RECORD".into()));
assert_eq!(execute.program_id, "hello.aleo".to_string());
assert_eq!(execute.function, "hello".to_string());
assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
} else {
panic!("Unexpected result of clap parsing!");
}
}
#[test]
fn clap_snarkos_execute_pk_file() {
let arg_vec = vec![
"snarkos",
"developer",
"execute",
"--private-key-file",
"PRIVATE_KEY_FILE",
"--query",
"QUERY",
"--priority-fee",
"77",
"--record",
"RECORD",
"hello.aleo",
"hello",
"1u32",
"2u32",
];
let cli = CLI::parse_from(arg_vec);
if let Command::Developer(Developer::Execute(execute)) = cli.command {
assert_eq!(execute.network, 0);
assert_eq!(execute.private_key_file, Some("PRIVATE_KEY_FILE".to_string()));
assert_eq!(execute.query, "QUERY");
assert_eq!(execute.priority_fee, Some(77));
assert_eq!(execute.record, Some("RECORD".into()));
assert_eq!(execute.program_id, "hello.aleo".to_string());
assert_eq!(execute.function, "hello".to_string());
assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
} else {
panic!("Unexpected result of clap parsing!");
}
}
}