1use super::*;
18
19impl<N: Network> ProgramManager<N> {
20 #[allow(clippy::too_many_arguments)]
31 pub fn execute_program_offline<A: Aleo<Network = N>>(
32 &self,
33 private_key: &PrivateKey<N>,
34 program: &Program<N>,
35 function: impl TryInto<Identifier<N>>,
36 imports: &[Program<N>],
37 inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
38 include_outputs: bool,
39 url: &str,
40 ) -> Result<OfflineExecution<N>> {
41 let rng = &mut rand::thread_rng();
43 let query = Query::<N, BlockMemory<N>>::from(url);
44
45 let function_name = function.try_into().map_err(|_| anyhow!("Invalid function name"))?;
47 let program_id = program.id();
48 println!("Checking function {function_name:?} exists in {program_id:?}");
49 ensure!(
50 program.contains_function(&function_name),
51 "Program {program_id:?} does not contain function {function_name:?}, aborting execution"
52 );
53
54 let store = ConsensusStore::<N, ConsensusMemory<N>>::open(None)?;
56 let vm = VM::<N, ConsensusMemory<N>>::from(store)?;
57 let credits_id = ProgramID::<N>::from_str("credits.aleo")?;
58 imports.iter().try_for_each(|program| {
59 if &credits_id != program.id() {
60 vm.process().write().add_program(program)?
61 }
62 Ok::<(), Error>(())
63 })?;
64 let _ = vm.process().write().add_program(program);
65
66 let authorization = vm.authorize(private_key, program_id, function_name, inputs, rng)?;
68
69 let locator = Locator::new(*program_id, function_name);
71 let (response, mut trace) = vm.process().write().execute::<A, _>(authorization, rng)?;
72 trace.prepare(query)?;
73 let execution = trace.prove_execution::<A, _>(&locator.to_string(), &mut rand::thread_rng())?;
74
75 let mut public_outputs = vec![];
77 response.outputs().iter().zip(response.output_ids().iter()).for_each(|(output, output_id)| {
78 if let OutputID::Public(_) = output_id {
79 public_outputs.push(output.clone());
80 }
81 });
82
83 let response = if include_outputs { Some(response) } else { None };
85
86 Ok(OfflineExecution::new(execution, response, trace, Some(public_outputs)))
88 }
89
90 pub fn execute_program(
94 &mut self,
95 program_id: impl TryInto<ProgramID<N>>,
96 function: impl TryInto<Identifier<N>>,
97 inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
98 priority_fee: u64,
99 fee_record: Option<Record<N, Plaintext<N>>>,
100 password: Option<&str>,
101 private_key: Option<&PrivateKey<N>>,
102 ) -> Result<String> {
103 ensure!(
105 self.api_client.is_some(),
106 "❌ Network client not set. A network client must be set before execution in order to send an execution transaction to the Aleo network"
107 );
108
109 let program_id = program_id.try_into().map_err(|_| anyhow!("Invalid program ID"))?;
111 let function_id = function.try_into().map_err(|_| anyhow!("Invalid function name"))?;
112 let function_name = function_id.to_string();
113 let api_client = self.api_client()?;
114
115 let program = self
117 .get_program(program_id)
118 .or_else(|_| api_client.get_program(program_id))?;
119
120
121 let private_key = private_key.map_or_else(
123 || { self.get_private_key(password) },
124 |private_key| Ok(*private_key),
125 )?;
126 let node_url = self.api_client.as_ref().unwrap().base_url().to_string();
127 let transaction = Self::create_execute_transaction(
128 &private_key,
129 priority_fee,
130 inputs,
131 fee_record,
132 &program,
133 function_id,
134 node_url,
135 self.api_client()?,
136 &self.vm,
137 )?;
138
139 println!("Attempting to broadcast execution transaction for {program_id:?}");
141 let execution = self.broadcast_transaction(transaction);
142
143 if execution.is_ok() {
145 println!("✅ Execution of function {function_name:?} from program {program_id:?}' broadcast successfully");
146 } else {
147 println!("❌ Execution of function {function_name:?} from program {program_id:?} failed to broadcast");
148 }
149
150 execution
151 }
152
153 #[allow(clippy::too_many_arguments)]
155 pub fn create_execute_transaction(
156 private_key: &PrivateKey<N>,
157 priority_fee: u64,
158 inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
159 fee_record: Option<Record<N, Plaintext<N>>>,
160 program: &Program<N>,
161 function: impl TryInto<Identifier<N>>,
162 node_url: String,
163 api_client: &AleoAPIClient<N>,
164 vm: &Option<VM<N, ConsensusMemory<N>>>,
165 ) -> Result<Transaction<N>> {
166 let rng = &mut rand::thread_rng();
168 let query = Query::from(node_url);
169
170 let function_name = function.try_into().map_err(|_| anyhow!("Invalid function name"))?;
172 let program_id = program.id();
173 println!("Checking function {function_name:?} exists in {program_id:?}");
174 ensure!(
175 program.contains_function(&function_name),
176 "Program {program_id:?} does not contain function {function_name:?}, aborting execution"
177 );
178
179 if let Some(vm) = vm {
181 let credits_id = ProgramID::<N>::from_str("credits.aleo")?;
182 api_client.get_program_imports_from_source(program)?.iter().try_for_each(|(_, import)| {
183 if import.id() != &credits_id && !vm.process().read().contains_program(import.id()) {
184 vm.process().write().add_program(import)?
185 }
186 Ok::<_, Error>(())
187 })?;
188
189 if !vm.process().read().contains_program(program.id()) {
192 vm.process().write().add_program(program)?;
193 }
194 vm.execute(private_key, (program_id, function_name), inputs, fee_record, priority_fee, Some(query), rng)
195 } else {
196 let vm = Self::initialize_vm(api_client, program, true)?;
197 vm.execute(private_key, (program_id, function_name), inputs, fee_record, priority_fee, Some(query), rng)
198 }
199 }
200
201 pub fn estimate_execution_fee<A: Aleo<Network = N>>(
206 &self,
207 program: &Program<N>,
208 function: impl TryInto<Identifier<N>>,
209 inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
210 ) -> Result<(u64, (u64, u64))> {
211 let url = self.api_client.as_ref().map_or_else(
212 || bail!("A network client must be configured to estimate a program execution fee"),
213 |api_client| Ok(api_client.base_url()),
214 )?;
215
216 let function_name = function.try_into().map_err(|_| anyhow!("Invalid function name"))?;
218 let program_id = program.id();
219 println!("Checking function {function_name:?} exists in {program_id:?}");
220 ensure!(
221 program.contains_function(&function_name),
222 "Program {program_id:?} does not contain function {function_name:?}, aborting execution"
223 );
224
225 let rng = &mut rand::thread_rng();
228 let query = Query::<N, BlockMemory<N>>::from(url);
229 let vm = Self::initialize_vm(self.api_client()?, program, true)?;
230
231 let private_key = PrivateKey::<N>::new(rng)?;
233
234 let authorization = vm.authorize(&private_key, program_id, function_name, inputs, rng)?;
236
237 let locator = Locator::new(*program_id, function_name);
238 let (_, mut trace) = vm.process().write().execute::<A, _>(authorization, rng)?;
239 trace.prepare(query)?;
240 let execution = trace.prove_execution::<A, _>(&locator.to_string(), &mut rand::thread_rng())?;
241 execution_cost(&vm, &execution)
242 }
243
244 pub fn estimate_finalize_fee(&self, program: &Program<N>, function: impl TryInto<Identifier<N>>) -> Result<u64> {
250 let function_name = function.try_into().map_err(|_| anyhow!("Invalid function name"))?;
251 match program.get_function(&function_name)?.finalize_logic() {
252 Some(finalize) => cost_in_microcredits(finalize),
253 None => Ok(0u64),
254 }
255 }
256}
257
258#[cfg(test)]
259#[cfg(not(feature = "wasm"))]
260mod tests {
261 use super::*;
262 use crate::{random_program, random_program_id, AleoAPIClient, RECORD_5_MICROCREDITS};
263 use snarkvm::circuit::AleoV0;
264 use snarkvm_console::network::Testnet3;
265
266 #[test]
267 fn test_fee_estimation() {
268 let private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
269 let api_client = AleoAPIClient::<Testnet3>::testnet3();
270 let program_manager =
271 ProgramManager::<Testnet3>::new(Some(private_key), None, Some(api_client.clone()), None, false).unwrap();
272
273 let finalize_program = program_manager.api_client.as_ref().unwrap().get_program("credits.aleo").unwrap();
274 let hello_hello = program_manager.api_client.as_ref().unwrap().get_program("hello_hello.aleo").unwrap();
275 let (total, (storage, finalize)) = program_manager
277 .estimate_execution_fee::<AleoV0>(
278 &finalize_program,
279 "transfer_public",
280 vec!["aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px", "5u64"].into_iter(),
281 )
282 .unwrap();
283 let finalize_only = program_manager.estimate_finalize_fee(&finalize_program, "transfer_public").unwrap();
284 assert!(finalize_only > 0);
285 assert!(finalize > storage);
286 assert_eq!(finalize, finalize_only);
287 assert_eq!(total, finalize_only + storage);
288 assert_eq!(storage, total - finalize_only);
289
290 let (total, (storage, finalize)) = program_manager
292 .estimate_execution_fee::<AleoV0>(&hello_hello, "hello", vec!["5u32", "5u32"].into_iter())
293 .unwrap();
294 let finalize_only = program_manager.estimate_finalize_fee(&hello_hello, "hello").unwrap();
295 assert!(storage > 0);
296 assert_eq!(finalize_only, 0);
297 assert_eq!(finalize, finalize_only);
298 assert_eq!(total, finalize_only + storage);
299 assert_eq!(storage, total - finalize_only);
300
301 let random = random_program();
303 let (total, (storage, namespace)) = program_manager.estimate_deployment_fee::<AleoV0>(&random).unwrap();
304 let namespace_only = ProgramManager::estimate_namespace_fee(random.id()).unwrap();
305 assert_eq!(namespace, 1000000);
306 assert_eq!(namespace, namespace_only);
307 assert_eq!(total, namespace_only + storage);
308 assert_eq!(storage, total - namespace_only);
309
310 let nested_import_program = api_client.get_program("imported_add_mul.aleo").unwrap();
312 let finalize_only = program_manager.estimate_finalize_fee(&nested_import_program, "add_and_double").unwrap();
313 let (total, (storage, finalize)) = program_manager
314 .estimate_execution_fee::<AleoV0>(
315 &nested_import_program,
316 "add_and_double",
317 vec!["5u32", "5u32"].into_iter(),
318 )
319 .unwrap();
320 assert!(storage > 0);
321 assert_eq!(finalize_only, 0);
322 assert_eq!(finalize, finalize_only);
323 assert_eq!(total, finalize_only + storage);
324 assert_eq!(storage, total - finalize_only);
325
326 let (total, (storage, namespace)) =
327 program_manager.estimate_deployment_fee::<AleoV0>(&nested_import_program).unwrap();
328 let namespace_only = ProgramManager::estimate_namespace_fee(nested_import_program.id()).unwrap();
329 assert_eq!(namespace, 1000000);
330 assert_eq!(namespace, namespace_only);
331 assert_eq!(total, namespace_only + storage);
332 assert_eq!(storage, total - namespace_only);
333 }
334
335 #[test]
336 #[ignore]
337 fn test_execution() {
338 let private_key = PrivateKey::<Testnet3>::from_str(RECIPIENT_PRIVATE_KEY).unwrap();
339 let encrypted_private_key =
340 crate::Encryptor::encrypt_private_key_with_secret(&private_key, "password").unwrap();
341 let api_client = AleoAPIClient::<Testnet3>::local_testnet3("3033");
342 let record_finder = RecordFinder::new(api_client.clone());
343 let mut program_manager =
344 ProgramManager::<Testnet3>::new(Some(private_key), None, Some(api_client.clone()), None, false).unwrap();
345
346 let fee = 2_500_000;
347 let finalize_fee = 8_000_000;
348
349 for i in 0..5 {
351 let fee_record = record_finder.find_one_record(&private_key, fee, None).unwrap();
352 let execution = program_manager.execute_program(
354 "credits_import_test.aleo",
355 "test",
356 ["1312u32", "62131112u32"].into_iter(),
357 fee,
358 Some(fee_record),
359 None,
360 None,
361 );
362 println!("{:?}", execution);
363
364 if execution.is_ok() {
365 break;
366 } else if i == 4 {
367 panic!("{}", format!("Execution failed after 5 attempts with error: {:?}", execution));
368 }
369 }
370
371 let mut program_manager =
373 ProgramManager::<Testnet3>::new(None, Some(encrypted_private_key), Some(api_client), None, false).unwrap();
374
375 for i in 0..5 {
376 let fee_record = record_finder.find_one_record(&private_key, fee, None).unwrap();
377 let execution = program_manager.execute_program(
379 "credits_import_test.aleo",
380 "test",
381 ["1337u32", "42u32"].into_iter(),
382 fee,
383 Some(fee_record),
384 Some("password"),
385 None,
386 );
387 if execution.is_ok() {
388 break;
389 } else if i == 4 {
390 panic!("{}", format!("Execution failed after 5 attempts with error: {:?}", execution));
391 }
392 }
393
394 for i in 0..5 {
396 let fee_record = record_finder.find_one_record(&private_key, finalize_fee, None).unwrap();
397 let execution = program_manager.execute_program(
399 "finalize_test.aleo",
400 "increase_counter",
401 ["0u32", "42u32"].into_iter(),
402 finalize_fee,
403 Some(fee_record),
404 Some("password"),
405 None,
406 );
407 if execution.is_ok() {
408 break;
409 } else if i == 4 {
410 panic!("{}", format!("Execution failed after 5 attempts with error: {:?}", execution));
411 }
412 }
413
414 for i in 0..5 {
416 let fee_record = record_finder.find_one_record(&private_key, finalize_fee, None).unwrap();
417 let execution = program_manager.execute_program(
419 "double_test.aleo",
420 "double_it",
421 ["42u32"].into_iter(),
422 finalize_fee,
423 Some(fee_record),
424 Some("password"),
425 None,
426 );
427 if execution.is_ok() {
428 break;
429 } else if i == 4 {
430 panic!("{}", format!("Execution failed after 5 attempts with error: {:?}", execution));
431 }
432 }
433 }
434
435 #[test]
436 fn test_execution_failure_modes() {
437 let rng = &mut rand::thread_rng();
438 let recipient_private_key = PrivateKey::<Testnet3>::new(rng).unwrap();
439 let api_client = AleoAPIClient::<Testnet3>::testnet3();
440 let record_5_microcredits = Record::<Testnet3, Plaintext<Testnet3>>::from_str(RECORD_5_MICROCREDITS).unwrap();
441 let record_2000000001_microcredits =
442 Record::<Testnet3, Plaintext<Testnet3>>::from_str(RECORD_2000000001_MICROCREDITS).unwrap();
443
444 let mut program_manager =
446 ProgramManager::<Testnet3>::new(Some(recipient_private_key), None, Some(api_client), None, false).unwrap();
447
448 let execution = program_manager.execute_program(
450 "hello.aleo",
451 "hello",
452 ["5u32", "6u32"].into_iter(),
453 500000,
454 Some(record_5_microcredits),
455 None,
456 None,
457 );
458
459 assert!(execution.is_err());
460
461 let execution = program_manager.execute_program(
463 "hello.aleo",
464 "hello",
465 ["5u32", "6u32"].into_iter(),
466 200,
467 Some(record_2000000001_microcredits.clone()),
468 None,
469 None,
470 );
471
472 assert!(execution.is_err());
473
474 let randomized_program_id = random_program_id(16);
476 let execution = program_manager.execute_program(
477 &randomized_program_id,
478 "hello",
479 ["5u32", "6u32"].into_iter(),
480 500000,
481 Some(record_2000000001_microcredits.clone()),
482 None,
483 None,
484 );
485
486 assert!(execution.is_err());
487
488 let execution = program_manager.execute_program(
490 "hello.aleo",
491 "random_function",
492 ["5u32", "6u32"].into_iter(),
493 500000,
494 Some(record_2000000001_microcredits.clone()),
495 None,
496 None,
497 );
498
499 assert!(execution.is_err());
500
501 let execution = program_manager.execute_program(
503 "hello.aleo",
504 "random_function",
505 ["5u32", "6u32"].into_iter(),
506 500000,
507 Some(record_2000000001_microcredits),
508 None,
509 None,
510 );
511
512 assert!(execution.is_err());
513 }
514}