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