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")]
78 pub storage_path: Option<PathBuf>,
79}
80
81impl Drop for Execute {
82 fn drop(&mut self) {
84 if let Some(mut pk) = self.private_key.take() {
85 pk.zeroize()
86 }
87 }
88}
89
90impl Execute {
91 #[allow(clippy::format_in_format_args)]
93 pub fn parse(self) -> Result<String> {
94 if !self.dry_run && self.broadcast.is_none() && self.store.is_none() {
96 bail!("❌ Please specify one of the following actions: --broadcast, --dry-run, --store");
97 }
98
99 match self.network {
101 MainnetV0::ID => self.construct_execution::<MainnetV0>(),
102 TestnetV0::ID => self.construct_execution::<TestnetV0>(),
103 CanaryV0::ID => self.construct_execution::<CanaryV0>(),
104 unknown_id => bail!("Unknown network ID ({unknown_id})"),
105 }
106 }
107
108 fn construct_execution<N: Network>(&self) -> Result<String> {
110 let query = Query::from(&self.query);
112
113 let key_str = match (self.private_key.as_ref(), self.private_key_file.as_ref()) {
115 (Some(private_key), None) => private_key.to_owned(),
116 (None, Some(private_key_file)) => {
117 let path = private_key_file.parse::<PathBuf>().map_err(|e| anyhow!("Invalid path - {e}"))?;
118 std::fs::read_to_string(path)?.trim().to_string()
119 }
120 (None, None) => bail!("Missing the '--private-key' or '--private-key-file' argument"),
121 (Some(_), Some(_)) => {
122 bail!("Cannot specify both the '--private-key' and '--private-key-file' flags")
123 }
124 };
125 let private_key = PrivateKey::from_str(&key_str)?;
126
127 let program_id = ProgramID::from_str(&self.program_id)?;
129
130 let function = Identifier::from_str(&self.function)?;
132
133 let inputs = self.inputs.iter().map(|input| Value::from_str(input)).collect::<Result<Vec<Value<N>>>>()?;
135
136 let locator = Locator::<N>::from_str(&format!("{}/{}", program_id, function))?;
137 println!("📦 Creating execution transaction for '{}'...\n", &locator.to_string().bold());
138
139 let transaction = {
141 let rng = &mut rand::thread_rng();
143
144 let storage_mode = match &self.storage_path {
146 Some(path) => StorageMode::Custom(path.clone()),
147 None => StorageMode::Production,
148 };
149 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(storage_mode)?;
150
151 let vm = VM::from(store)?;
153
154 load_program(&self.query, &mut vm.process().write(), &program_id)?;
156
157 let fee_record = match &self.record {
159 Some(record_string) => Some(Developer::parse_record(&private_key, record_string)?),
160 None => None,
161 };
162 let priority_fee = self.priority_fee.unwrap_or(0);
163
164 vm.execute(&private_key, (program_id, function), inputs.iter(), fee_record, priority_fee, Some(query), rng)?
166 };
167
168 if self.record.is_none() {
170 let address = Address::try_from(&private_key)?;
172 let public_balance = Developer::get_public_balance(&address, &self.query)?;
173
174 let storage_cost = transaction
176 .execution()
177 .ok_or_else(|| anyhow!("The transaction does not contain an execution"))?
178 .size_in_bytes()?;
179
180 let base_fee = storage_cost.saturating_add(self.priority_fee.unwrap_or(0));
184
185 if public_balance < base_fee {
187 bail!(
188 "❌ The public balance of {} is insufficient to pay the base fee for `{}`",
189 public_balance,
190 locator.to_string().bold()
191 );
192 }
193 }
194
195 println!("✅ Created execution transaction for '{}'", locator.to_string().bold());
196
197 Developer::handle_transaction(&self.broadcast, self.dry_run, &self.store, transaction, locator.to_string())
199 }
200}
201
202fn load_program<N: Network>(endpoint: &str, process: &mut Process<N>, program_id: &ProgramID<N>) -> Result<()> {
204 let program = Developer::fetch_program(program_id, endpoint)?;
206
207 if process.contains_program(program.id()) {
209 return Ok(());
210 }
211
212 for import_program_id in program.imports().keys() {
214 if !process.contains_program(import_program_id) {
216 load_program(endpoint, process, import_program_id)?;
218 }
219 }
220
221 if !process.contains_program(program.id()) {
223 process.add_program(&program)?;
224 }
225
226 Ok(())
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use crate::commands::{CLI, Command};
233
234 #[test]
235 fn clap_snarkos_execute() {
236 let arg_vec = vec![
237 "snarkos",
238 "developer",
239 "execute",
240 "--private-key",
241 "PRIVATE_KEY",
242 "--query",
243 "QUERY",
244 "--priority-fee",
245 "77",
246 "--record",
247 "RECORD",
248 "hello.aleo",
249 "hello",
250 "1u32",
251 "2u32",
252 ];
253 let cli = CLI::parse_from(arg_vec);
254
255 if let Command::Developer(Developer::Execute(execute)) = cli.command {
256 assert_eq!(execute.network, 0);
257 assert_eq!(execute.private_key, Some("PRIVATE_KEY".to_string()));
258 assert_eq!(execute.query, "QUERY");
259 assert_eq!(execute.priority_fee, Some(77));
260 assert_eq!(execute.record, Some("RECORD".into()));
261 assert_eq!(execute.program_id, "hello.aleo".to_string());
262 assert_eq!(execute.function, "hello".to_string());
263 assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
264 } else {
265 panic!("Unexpected result of clap parsing!");
266 }
267 }
268
269 #[test]
270 fn clap_snarkos_execute_pk_file() {
271 let arg_vec = vec![
272 "snarkos",
273 "developer",
274 "execute",
275 "--private-key-file",
276 "PRIVATE_KEY_FILE",
277 "--query",
278 "QUERY",
279 "--priority-fee",
280 "77",
281 "--record",
282 "RECORD",
283 "hello.aleo",
284 "hello",
285 "1u32",
286 "2u32",
287 ];
288 let cli = CLI::parse_from(arg_vec);
289
290 if let Command::Developer(Developer::Execute(execute)) = cli.command {
291 assert_eq!(execute.network, 0);
292 assert_eq!(execute.private_key_file, Some("PRIVATE_KEY_FILE".to_string()));
293 assert_eq!(execute.query, "QUERY");
294 assert_eq!(execute.priority_fee, Some(77));
295 assert_eq!(execute.record, Some("RECORD".into()));
296 assert_eq!(execute.program_id, "hello.aleo".to_string());
297 assert_eq!(execute.function, "hello".to_string());
298 assert_eq!(execute.inputs, vec!["1u32".to_string(), "2u32".to_string()]);
299 } else {
300 panic!("Unexpected result of clap parsing!");
301 }
302 }
303}