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