1use leo_ast::{TEST_PRIVATE_KEY, interpreter_value::Value};
35use leo_errors::Result;
36
37use aleo_std_storage::StorageMode;
38use anyhow::anyhow;
39use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng as _};
40use rayon::prelude::*;
41use serde_json;
42use snarkvm::{
43 circuit::AleoTestnetV0,
44 prelude::{
45 Address,
46 ConsensusVersion,
47 Execution,
48 Identifier,
49 Ledger,
50 Network,
51 PrivateKey,
52 ProgramID,
53 TestnetV0,
54 Transaction,
55 VM,
56 Value as SvmValue,
57 store::{ConsensusStore, helpers::memory::ConsensusMemory},
58 },
59 synthesizer::program::ProgramCore,
60};
61use std::{
62 fmt,
63 panic::{AssertUnwindSafe, catch_unwind},
64 str::FromStr as _,
65};
66
67type CurrentNetwork = TestnetV0;
68
69#[derive(Debug)]
71pub struct Config {
72 pub seed: u64,
73 pub start_height: Option<u32>,
75 pub programs: Vec<Program>,
76}
77
78#[derive(Clone, Debug, Default)]
80pub struct Program {
81 pub bytecode: String,
82 pub name: String,
83}
84
85#[derive(Clone, Debug, Default)]
87pub struct Case {
88 pub program_name: String,
89 pub function: String,
90 pub private_key: Option<String>,
91 pub input: Vec<String>,
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
96pub enum ExecutionStatus {
97 None,
98 Aborted,
99 Accepted,
100 Rejected,
101 Halted(String),
102}
103
104impl fmt::Display for ExecutionStatus {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 match self {
107 Self::Halted(s) => write!(f, "halted ({s})"),
108 Self::None => write!(f, "none"),
109 Self::Aborted => write!(f, "aborted"),
110 Self::Accepted => write!(f, "accepted"),
111 Self::Rejected => write!(f, "rejected"),
112 }
113 }
114}
115
116#[derive(Debug, Clone)]
117pub enum EvaluationStatus {
118 Success,
119 Failed(String),
120}
121
122impl fmt::Display for EvaluationStatus {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 match self {
125 Self::Success => write!(f, "success"),
126 Self::Failed(e) => write!(f, "failed: {e}"),
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
133pub struct Outcome {
134 pub program_name: String,
135 pub function: String,
136 pub output: Value,
137}
138
139impl Outcome {
140 pub fn output(&self) -> Value {
141 self.output.clone()
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct EvaluationOutcome {
148 pub outcome: Outcome,
149 pub status: EvaluationStatus,
150}
151
152impl EvaluationOutcome {
153 pub fn output(&self) -> Value {
154 self.outcome.output()
155 }
156}
157
158#[derive(Debug, Clone)]
160pub struct ExecutionOutcome {
161 pub outcome: Outcome,
162 pub verified: bool,
163 pub execution: String,
164 pub status: ExecutionStatus,
165}
166
167impl ExecutionOutcome {
168 pub fn output(&self) -> Value {
169 self.outcome.output()
170 }
171}
172
173pub fn run_without_ledger(config: &Config, cases: &[Case]) -> Result<Vec<EvaluationOutcome>> {
179 if cases.is_empty() {
181 return Ok(Vec::new());
182 }
183
184 let programs_and_editions: Vec<(snarkvm::prelude::Program<CurrentNetwork>, u16)> = config
185 .programs
186 .iter()
187 .map(|Program { bytecode, name }| {
188 let program = snarkvm::prelude::Program::<CurrentNetwork>::from_str(bytecode)
189 .map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
190 let edition: u16 = 1;
192 Ok((program, edition))
193 })
194 .collect::<Result<Vec<_>>>()?;
195
196 let outcomes: Vec<EvaluationOutcome> = cases
197 .par_iter()
198 .map(|case| {
199 let rng = &mut ChaCha20Rng::seed_from_u64(config.seed);
200
201 let failed_outcome = |e: String| EvaluationOutcome {
203 outcome: Outcome {
204 program_name: case.program_name.clone(),
205 function: case.function.clone(),
206 output: Value::make_unit(),
207 },
208 status: EvaluationStatus::Failed(e),
209 };
210
211 let vm = match ConsensusStore::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::open(
212 StorageMode::Production,
213 ) {
214 Ok(store) => match VM::from(store) {
215 Ok(vm) => vm,
216 Err(e) => return failed_outcome(format!("VM init error: {e}")),
217 },
218 Err(e) => return failed_outcome(format!("Consensus store open error: {e}")),
219 };
220
221 if let Err(e) = vm.process().write().add_programs_with_editions(&programs_and_editions) {
222 return failed_outcome(format!("Failed to add programs: {e}"));
223 }
224
225 let private_key = match PrivateKey::from_str(leo_ast::TEST_PRIVATE_KEY) {
226 Ok(pk) => pk,
227 Err(e) => return failed_outcome(format!("Private key parse error: {e}")),
228 };
229 let program_id = match ProgramID::<CurrentNetwork>::from_str(&case.program_name) {
230 Ok(pid) => pid,
231 Err(e) => return failed_outcome(format!("ProgramID parse error: {e}")),
232 };
233 let function_id = match Identifier::<CurrentNetwork>::from_str(&case.function) {
234 Ok(fid) => fid,
235 Err(e) => return failed_outcome(format!("FunctionID parse error: {e}")),
236 };
237 let inputs = case.input.iter();
238
239 let authorization = match catch_unwind(AssertUnwindSafe(|| {
241 vm.authorize(&private_key, program_id, function_id, inputs, rng)
242 })) {
243 Ok(Ok(auth)) => auth,
244 Ok(Err(e)) => return failed_outcome(format!("{e}")),
245 Err(e) => return failed_outcome(format!("{e:?}")),
246 };
247
248 let response =
250 match catch_unwind(AssertUnwindSafe(|| vm.process().read().evaluate::<AleoTestnetV0>(authorization))) {
251 Ok(Ok(resp)) => resp,
252 Ok(Err(e)) => return failed_outcome(format!("{e}")),
253 Err(e) => return failed_outcome(format!("{e:?}")),
254 };
255
256 let outputs = response.outputs();
257 let output = match outputs.len() {
258 0 => Value::make_unit(),
259 1 => outputs[0].clone().into(),
260 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
261 };
262
263 EvaluationOutcome {
264 outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
265 status: EvaluationStatus::Success,
266 }
267 })
268 .collect();
269
270 Ok(outcomes)
271}
272
273pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<ExecutionOutcome>>> {
275 if case_sets.is_empty() {
276 return Ok(Vec::new());
277 }
278
279 let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
281
282 let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
284
285 let mut blocks = Vec::new();
287
288 let genesis_block = VM::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::from(ConsensusStore::open(0).unwrap())
290 .unwrap()
291 .genesis_beacon(&genesis_private_key, &mut rng)
292 .unwrap();
293
294 let ledger =
296 Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(genesis_block.clone(), StorageMode::Production)
297 .unwrap();
298
299 let latest_consensus_version = ConsensusVersion::latest();
301 let start_height =
302 config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
303 while ledger.latest_height() < start_height {
304 let block = ledger
305 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
306 .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
307 ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
308 blocks.push(block);
309 }
310
311 for Program { bytecode, name } in &config.programs {
313 let aleo_program =
316 ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
317
318 let mut deploy = || -> Result<()> {
319 let deployment = ledger
322 .vm()
323 .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
324 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?;
325 let block = ledger
326 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
327 .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
328 ledger
329 .advance_to_next_block(&block)
330 .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
331
332 if block.transactions().num_accepted() != 1 {
334 return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
335 }
336
337 blocks.push(block);
339
340 Ok(())
341 };
342
343 deploy()?;
345 if !aleo_program.contains_constructor() {
347 deploy()?;
348 }
349 }
350
351 let mut indexed_ledgers = vec![(0, ledger)];
353 indexed_ledgers.extend(
354 (1..case_sets.len())
355 .into_par_iter()
356 .map(|i| {
357 let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
359 genesis_block.clone(),
360 StorageMode::Production,
361 )
362 .expect("Failed to load copy of ledger");
363 for block in blocks.iter() {
365 l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
366 }
367
368 (i, l)
369 })
370 .collect::<Vec<_>>(),
371 );
372
373 let results = indexed_ledgers
375 .into_par_iter()
376 .map(|(index, ledger)| {
377 let cases = &case_sets[index];
379 let mut rng = rng.clone();
381
382 let transactions: Vec<Transaction<CurrentNetwork>> = cases
384 .iter()
385 .filter_map(|case| case.private_key.as_ref())
386 .map(|key| {
387 let private_key =
389 PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
390 let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
392 ledger
394 .vm()
395 .execute(
396 &genesis_private_key,
397 ("credits.aleo", "transfer_public"),
398 [
399 SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
400 SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
401 ]
402 .iter(),
403 None,
404 0u64,
405 None,
406 &mut rng,
407 )
408 .expect("Failed to generate funding transaction")
409 })
410 .collect();
411
412 let block = ledger
414 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
415 .expect("Failed to prepare advance to next beacon block");
416 assert!(block.aborted_transaction_ids().is_empty());
418 assert_eq!(block.transactions().num_rejected(), 0);
419 ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
421
422 let mut case_outcomes = Vec::new();
423
424 for case in cases {
425 assert!(
426 ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
427 "Program {} should exist.",
428 case.program_name
429 );
430
431 let private_key = case
432 .private_key
433 .as_ref()
434 .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
435 .unwrap_or(genesis_private_key);
436
437 let mut execution = None;
438 let mut verified = false;
439 let mut status = ExecutionStatus::None;
440
441 let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
445 ledger.vm().execute_with_response(
446 &private_key,
447 (&case.program_name, &case.function),
448 case.input.iter(),
449 None,
450 0,
451 None,
452 &mut rng,
453 )
454 }));
455
456 if let Err(payload) = execute_output {
457 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
458 let s2 = payload.downcast_ref::<String>().cloned();
459 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
460
461 case_outcomes.push(ExecutionOutcome {
462 outcome: Outcome {
463 program_name: case.program_name.clone(),
464 function: case.function.clone(),
465 output: Value::make_unit(),
466 },
467 status: ExecutionStatus::Halted(s),
468 verified: false,
469 execution: "".to_string(),
470 });
471
472 continue;
473 }
474
475 let result = execute_output.unwrap().and_then(|(transaction, response)| {
476 verified = ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
477 execution = Some(transaction.clone());
478 let block = ledger.prepare_advance_to_next_beacon_block(
479 &private_key,
480 vec![],
481 vec![],
482 vec![transaction],
483 &mut rng,
484 )?;
485 status =
486 match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
487 (false, _) => ExecutionStatus::Aborted,
488 (true, true) => ExecutionStatus::Accepted,
489 (true, false) => ExecutionStatus::Rejected,
490 };
491 ledger.advance_to_next_block(&block)?;
492 Ok(response)
493 });
494
495 let output = match result {
496 Ok(response) => {
497 let outputs = response.outputs();
498 match outputs.len() {
499 0 => Value::make_unit(),
500 1 => outputs[0].clone().into(),
501 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
502 }
503 }
504 Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
505 };
506
507 let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
510 Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
511 } else {
512 None
513 };
514
515 case_outcomes.push(ExecutionOutcome {
516 outcome: Outcome {
517 program_name: case.program_name.clone(),
518 function: case.function.clone(),
519 output,
520 },
521 status,
522 verified,
523 execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
524 });
525 }
526
527 Ok((index, case_outcomes))
528 })
529 .collect::<Result<Vec<_>>>()?;
530
531 let mut ordered_results: Vec<Vec<ExecutionOutcome>> = vec![Default::default(); case_sets.len()];
533 for (index, outcomes) in results.into_iter() {
534 ordered_results[index] = outcomes;
535 }
536
537 Ok(ordered_results)
538}