snarkos_cli/commands/developer/
execute.rs1use super::Developer;
17use crate::commands::StoreFormat;
18use snarkvm::{
19 console::network::{CanaryV0, MainnetV0, Network, TestnetV0},
20 ledger::store::helpers::memory::BlockMemory,
21 prelude::{
22 Address,
23 Identifier,
24 Locator,
25 PrivateKey,
26 Process,
27 ProgramID,
28 VM,
29 Value,
30 query::Query,
31 store::{ConsensusStore, helpers::memory::ConsensusMemory},
32 },
33};
34
35use aleo_std::StorageMode;
36use anyhow::{Result, anyhow, bail};
37use clap::Parser;
38use colored::Colorize;
39use std::{path::PathBuf, str::FromStr};
40use zeroize::Zeroize;
41
42#[derive(Debug, Parser)]
44pub struct Execute {
45 program_id: String,
47 function: String,
49 inputs: Vec<String>,
51 #[clap(default_value = "0", long = "network")]
53 pub network: u16,
54 #[clap(short, long)]
56 private_key: Option<String>,
57 #[clap(long)]
59 private_key_file: Option<String>,
60 #[clap(short, long)]
62 query: String,
63 #[clap(long)]
65 priority_fee: Option<u64>,
66 #[clap(short, long)]
68 record: Option<String>,
69 #[clap(short, long, conflicts_with = "dry_run")]
71 broadcast: Option<String>,
72 #[clap(short, long, conflicts_with = "broadcast")]
74 dry_run: bool,
75 #[clap(long)]
77 store: Option<String>,
78 #[clap(long, value_enum, default_value_t = StoreFormat::Bytes)]
81 store_format: StoreFormat,
82 #[clap(long = "storage_path")]
85 pub storage_path: Option<PathBuf>,
86}
87
88impl Drop for Execute {
89 fn drop(&mut self) {
91 if let Some(mut pk) = self.private_key.take() {
92 pk.zeroize()
93 }
94 }
95}
96
97impl Execute {
98 #[allow(clippy::format_in_format_args)]
100 pub fn parse(self) -> Result<String> {
101 if !self.dry_run && self.broadcast.is_none() && self.store.is_none() {
103 bail!("❌ Please specify one of the following actions: --broadcast, --dry-run, --store");
104 }
105
106 match self.network {
108 MainnetV0::ID => self.construct_execution::<MainnetV0>(),
109 TestnetV0::ID => self.construct_execution::<TestnetV0>(),
110 CanaryV0::ID => self.construct_execution::<CanaryV0>(),
111 unknown_id => bail!("Unknown network ID ({unknown_id})"),
112 }
113 }
114
115 fn construct_execution<N: Network>(&self) -> Result<String> {
117 let query = Query::<N, BlockMemory<N>>::from(&self.query);
119
120 let key_str = match (self.private_key.as_ref(), self.private_key_file.as_ref()) {
122 (Some(private_key), None) => private_key.to_owned(),
123 (None, Some(private_key_file)) => {
124 let path = private_key_file.parse::<PathBuf>().map_err(|e| anyhow!("Invalid path - {e}"))?;
125 std::fs::read_to_string(path)?.trim().to_string()
126 }
127 (None, None) => bail!("Missing the '--private-key' or '--private-key-file' argument"),
128 (Some(_), Some(_)) => {
129 bail!("Cannot specify both the '--private-key' and '--private-key-file' flags")
130 }
131 };
132 let private_key = PrivateKey::from_str(&key_str)?;
133
134 let program_id = ProgramID::from_str(&self.program_id)?;
136
137 let function = Identifier::from_str(&self.function)?;
139
140 let inputs = self.inputs.iter().map(|input| Value::from_str(input)).collect::<Result<Vec<Value<N>>>>()?;
142
143 let locator = Locator::<N>::from_str(&format!("{program_id}/{function}"))?;
144 println!("📦 Creating execution transaction for '{}'...\n", &locator.to_string().bold());
145
146 let transaction = {
148 let rng = &mut rand::thread_rng();
150
151 let storage_mode = match &self.storage_path {
153 Some(path) => StorageMode::Custom(path.clone()),
154 None => StorageMode::Production,
155 };
156 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(storage_mode)?;
157
158 let vm = VM::from(store)?;
160
161 load_program(&self.query, &mut vm.process().write(), &program_id)?;
163
164 let fee_record = match &self.record {
166 Some(record_string) => Some(Developer::parse_record(&private_key, record_string)?),
167 None => None,
168 };
169 let priority_fee = self.priority_fee.unwrap_or(0);
170
171 vm.execute(
173 &private_key,
174 (program_id, function),
175 inputs.iter(),
176 fee_record,
177 priority_fee,
178 Some(&query),
179 rng,
180 )?
181 };
182
183 if self.record.is_none() {
185 let address = Address::try_from(&private_key)?;
187 let public_balance = Developer::get_public_balance(&address, &self.query)?;
188
189 let storage_cost = transaction
191 .execution()
192 .ok_or_else(|| anyhow!("The transaction does not contain an execution"))?
193 .size_in_bytes()?;
194
195 let base_fee = storage_cost.saturating_add(self.priority_fee.unwrap_or(0));
199
200 if public_balance < base_fee {
202 bail!(
203 "❌ The public balance of {} is insufficient to pay the base fee for `{}`",
204 public_balance,
205 locator.to_string().bold()
206 );
207 }
208 }
209
210 println!("✅ Created execution transaction for '{}'", locator.to_string().bold());
211
212 Developer::handle_transaction(
214 &self.broadcast,
215 self.dry_run,
216 &self.store,
217 self.store_format,
218 transaction,
219 locator.to_string(),
220 )
221 }
222}
223
224fn load_program<N: Network>(endpoint: &str, process: &mut Process<N>, program_id: &ProgramID<N>) -> Result<()> {
226 let program = Developer::fetch_program(program_id, endpoint)?;
228
229 if process.contains_program(program.id()) {
231 return Ok(());
232 }
233
234 for import_program_id in program.imports().keys() {
236 if !process.contains_program(import_program_id) {
238 load_program(endpoint, process, import_program_id)?;
240 }
241 }
242
243 if !process.contains_program(program.id()) {
245 process.add_program(&program)?;
246 }
247
248 Ok(())
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::commands::{CLI, Command};
255
256 #[test]
257 fn clap_snarkos_execute() {
258 let arg_vec = vec![
259 "snarkos",
260 "developer",
261 "execute",
262 "--private-key",
263 "PRIVATE_KEY",
264 "--query",
265 "QUERY",
266 "--priority-fee",
267 "77",
268 "--record",
269 "RECORD",
270 "hello.aleo",
271 "hello",
272 "1u32",
273 "2u32",
274 ];
275 let cli = CLI::parse_from(arg_vec);
276
277 if let Command::Developer(Developer::Execute(execute)) = cli.command {
278 assert_eq!(execute.network, 0);
279 assert_eq!(execute.private_key, Some("PRIVATE_KEY".to_string()));
280 assert_eq!(execute.query, "QUERY");
281 assert_eq!(execute.priority_fee, Some(77));
282 assert_eq!(execute.record, Some("RECORD".into()));
283 assert_eq!(execute.program_id, "hello.aleo".to_string());
284 assert_eq!(execute.function, "hello".to_string());
285 assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
286 } else {
287 panic!("Unexpected result of clap parsing!");
288 }
289 }
290
291 #[test]
292 fn clap_snarkos_execute_pk_file() {
293 let arg_vec = vec![
294 "snarkos",
295 "developer",
296 "execute",
297 "--private-key-file",
298 "PRIVATE_KEY_FILE",
299 "--query",
300 "QUERY",
301 "--priority-fee",
302 "77",
303 "--record",
304 "RECORD",
305 "hello.aleo",
306 "hello",
307 "1u32",
308 "2u32",
309 ];
310 let cli = CLI::parse_from(arg_vec);
311
312 if let Command::Developer(Developer::Execute(execute)) = cli.command {
313 assert_eq!(execute.network, 0);
314 assert_eq!(execute.private_key_file, Some("PRIVATE_KEY_FILE".to_string()));
315 assert_eq!(execute.query, "QUERY");
316 assert_eq!(execute.priority_fee, Some(77));
317 assert_eq!(execute.record, Some("RECORD".into()));
318 assert_eq!(execute.program_id, "hello.aleo".to_string());
319 assert_eq!(execute.function, "hello".to_string());
320 assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
321 } else {
322 panic!("Unexpected result of clap parsing!");
323 }
324 }
325}