1use leo_ast::{TEST_PRIVATE_KEY, const_eval::Value};
31use leo_errors::Result;
32
33use aleo_std_storage::StorageMode;
34use anyhow::anyhow;
35use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng as _};
36use serde_json;
37use snarkvm::{
38 circuit::AleoTestnetV0,
39 prelude::{
40 Address,
41 Block,
42 Certificate,
43 ConsensusVersion,
44 Deployment,
45 Execution,
46 Fee,
47 FromBytes,
48 Identifier,
49 Ledger,
50 Network,
51 PrivateKey,
52 ProgramID,
53 ProgramOwner,
54 TestnetV0,
55 Transaction,
56 VM,
57 Value as SvmValue,
58 VerifyingKey,
59 deployment_cost,
60 execution_cost,
61 store::{ConsensusStore, helpers::memory::ConsensusMemory},
62 },
63 synthesizer::program::ProgramCore,
64};
65use std::{
66 fmt,
67 panic::{AssertUnwindSafe, catch_unwind},
68 str::FromStr as _,
69};
70
71type CurrentNetwork = TestnetV0;
72
73#[derive(Debug)]
75pub struct Config {
76 pub seed: u64,
77 pub start_height: Option<u32>,
79 pub programs: Vec<Program>,
80 pub skip_proving: bool,
82}
83
84#[derive(Clone, Debug, Default)]
86pub struct Program {
87 pub bytecode: String,
88 pub name: String,
89}
90
91#[derive(Clone, Debug, Default)]
93pub struct Case {
94 pub program_name: String,
95 pub function: String,
96 pub private_key: Option<String>,
97 pub input: Vec<String>,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum ExecutionStatus {
103 None,
104 Aborted(Option<String>),
105 Accepted,
106 Rejected,
107 Halted(String),
108}
109
110impl fmt::Display for ExecutionStatus {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::Halted(s) => write!(f, "halted ({s})"),
114 Self::None => write!(f, "none"),
115 Self::Aborted(None) => write!(f, "aborted"),
116 Self::Aborted(Some(reason)) => write!(f, "aborted: {reason}"),
117 Self::Accepted => write!(f, "accepted"),
118 Self::Rejected => write!(f, "rejected"),
119 }
120 }
121}
122
123#[derive(Debug, Clone)]
124pub enum EvaluationStatus {
125 Success,
126 Failed(String),
127}
128
129impl fmt::Display for EvaluationStatus {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self {
132 Self::Success => write!(f, "success"),
133 Self::Failed(e) => write!(f, "failed: {e}"),
134 }
135 }
136}
137
138#[derive(Debug, Clone)]
140pub struct Outcome {
141 pub program_name: String,
142 pub function: String,
143 pub output: Value,
144}
145
146impl Outcome {
147 pub fn output(&self) -> Value {
148 self.output.clone()
149 }
150}
151
152#[derive(Debug, Clone)]
154pub struct EvaluationOutcome {
155 pub outcome: Outcome,
156 pub status: EvaluationStatus,
157}
158
159impl EvaluationOutcome {
160 pub fn output(&self) -> Value {
161 self.outcome.output()
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct ExecutionOutcome {
168 pub outcome: Outcome,
169 pub verified: bool,
170 pub execution: String,
171 pub status: ExecutionStatus,
172}
173
174impl ExecutionOutcome {
175 pub fn output(&self) -> Value {
176 self.outcome.output()
177 }
178}
179
180pub const PLACEHOLDER_VK: &str = "verifier1q9qqqqqqqqqqqqyvxgqqqqqqqqq87vsqqqqqqqqqhe7sqqqqqqqqqma4qqqqqqqqqq65yqqqqqqqqqqvqqqqqqqqqqqgtlaj49fmrk2d8slmselaj9tpucgxv6awu6yu4pfcn5xa0yy0tpxpc8wemasjvvxr9248vt3509vpk3u60ejyfd9xtvjmudpp7ljq2csk4yqz70ug3x8xp3xn3ul0yrrw0mvd2g8ju7rts50u3smue03gp99j88f0ky8h6fjlpvh58rmxv53mldmgrxa3fq6spsh8gt5whvsyu2rk4a2wmeyrgvvdf29pwp02srktxnvht3k6ff094usjtllggva2ym75xc4lzuqu9xx8ylfkm3qc7lf7ktk9uu9du5raukh828dzgq26hrarq5ajjl7pz7zk924kekjrp92r6jh9dpp05mxtuffwlmvew84dvnqrkre7lw29mkdzgdxwe7q8z0vnkv2vwwdraekw2va3plu7rkxhtnkuxvce0qkgxcxn5mtg9q2c3vxdf2r7jjse2g68dgvyh85q4mzfnvn07lletrpty3vypus00gfu9m47rzay4mh5w9f03z9zgzgzhkv0mupdqsk8naljqm9tc2qqzhf6yp3mnv2ey89xk7sw9pslzzlkndfd2upzmew4e4vnrkr556kexs9qrykkuhsr260mnrgh7uv0sp2meky0keeukaxgjdsnmy77kl48g3swcvqdjm50ejzr7x04vy7hn7anhd0xeetclxunnl7pd6e52qxdlr3nmutz4zr8f2xqa57a2zkl59a28w842cj4783zpy9hxw03k6vz4a3uu7sm072uqknpxjk8fyq4vxtqd08kd93c2mt40lj9ag35nm4rwcfjayejk57m9qqu83qnkrj3sz90pw808srmf705n2yu6gvqazpvu2mwm8x6mgtlsntxfhr0qas43rqxnccft36z4ygty86390t7vrt08derz8368z8ekn3yywxgp4uq24gm6e58tpp0lcvtpsm3nkwpnmzztx4qvkaf6vk38wg787h8mfpqqqqqqqqqqffkful";
182
183pub const PLACEHOLDER_CERT: &str =
185 "certificate1qyqsqqqqqqqqqqxvwszp09v860w62s2l4g6eqf0kzppyax5we36957ywqm2dplzwvvlqg0kwlnmhzfatnax7uaqt7yqqqw0sc4u";
186
187fn deploy_without_proof(
189 vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
190 private_key: &PrivateKey<CurrentNetwork>,
191 program: &ProgramCore<CurrentNetwork>,
192 edition: u16,
193 consensus_version: ConsensusVersion,
194 rng: &mut ChaCha20Rng,
195) -> anyhow::Result<Transaction<CurrentNetwork>> {
196 let placeholder_vk = VerifyingKey::from_str(PLACEHOLDER_VK)?;
199 let placeholder_cert = Certificate::from_str(PLACEHOLDER_CERT)?;
200 let verifying_keys = program
201 .functions()
202 .keys()
203 .chain(program.records().keys())
204 .map(|name| (*name, (placeholder_vk.clone(), placeholder_cert.clone())))
205 .collect::<Vec<_>>();
206
207 let mut deployment = Deployment::new(edition, program.clone(), verifying_keys, None, None)
209 .map_err(|e| anyhow!("Failed to create deployment: {e}"))?;
210
211 deployment.set_program_owner_raw(Some(Address::try_from(private_key)?));
213 deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
214
215 let deployment_id = deployment.to_deployment_id()?;
217 let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
218
219 let (minimum_deployment_cost, _) = deployment_cost(&vm.process().read(), &deployment, consensus_version)?;
221
222 let fee_authorization = vm.authorize_fee_public(private_key, minimum_deployment_cost, 0, deployment_id, rng)?;
224
225 let state_root = vm.block_store().current_state_root();
227 let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
228
229 Transaction::from_deployment(owner, deployment, fee).map_err(|e| anyhow!("Failed to create deployment tx: {e}"))
230}
231
232fn execute_without_proof(
234 vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
235 private_key: &PrivateKey<CurrentNetwork>,
236 program_id: &str,
237 function_name: &str,
238 inputs: impl ExactSizeIterator<Item = impl TryInto<SvmValue<CurrentNetwork>>>,
239 consensus_version: ConsensusVersion,
240 rng: &mut ChaCha20Rng,
241) -> anyhow::Result<(Transaction<CurrentNetwork>, snarkvm::prelude::Response<CurrentNetwork>)> {
242 let authorization = vm.authorize(private_key, program_id, function_name, inputs, rng)?;
244
245 let response = vm.process().read().evaluate::<AleoTestnetV0>(authorization.clone())?;
247
248 let state_root = vm.block_store().current_state_root();
250 let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?;
251
252 let (cost, _) = execution_cost(&vm.process().read(), &execution, consensus_version)?;
254
255 let execution_id = authorization.to_execution_id()?;
257 let fee_authorization = vm.authorize_fee_public(private_key, cost, 0, execution_id, rng)?;
258 let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
259
260 let transaction = Transaction::from_execution(execution, Some(fee))?;
261 Ok((transaction, response))
262}
263
264pub fn run_without_ledger(config: &Config, cases: &[Case]) -> Result<Vec<EvaluationOutcome>> {
270 if cases.is_empty() {
272 return Ok(Vec::new());
273 }
274
275 let programs_and_editions: Vec<(snarkvm::prelude::Program<CurrentNetwork>, u16)> = config
276 .programs
277 .iter()
278 .map(|Program { bytecode, name }| {
279 let program = snarkvm::prelude::Program::<CurrentNetwork>::from_str(bytecode)
280 .map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
281 let edition: u16 = 1;
283 Ok((program, edition))
284 })
285 .collect::<Result<Vec<_>>>()?;
286
287 let outcomes: Vec<EvaluationOutcome> = cases
288 .iter()
289 .map(|case| {
290 let rng = &mut ChaCha20Rng::seed_from_u64(config.seed);
291
292 let failed_outcome = |e: String| EvaluationOutcome {
294 outcome: Outcome {
295 program_name: case.program_name.clone(),
296 function: case.function.clone(),
297 output: Value::make_unit(),
298 },
299 status: EvaluationStatus::Failed(e),
300 };
301
302 let vm = match ConsensusStore::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::open(
303 StorageMode::Production,
304 ) {
305 Ok(store) => match VM::from(store) {
306 Ok(vm) => vm,
307 Err(e) => return failed_outcome(format!("VM init error: {e}")),
308 },
309 Err(e) => return failed_outcome(format!("Consensus store open error: {e}")),
310 };
311
312 if let Err(e) = vm.process().write().add_programs_with_editions(&programs_and_editions) {
313 return failed_outcome(format!("Failed to add programs: {e}"));
314 }
315
316 let private_key = match PrivateKey::from_str(leo_ast::TEST_PRIVATE_KEY) {
317 Ok(pk) => pk,
318 Err(e) => return failed_outcome(format!("Private key parse error: {e}")),
319 };
320 let program_id = match ProgramID::<CurrentNetwork>::from_str(&case.program_name) {
321 Ok(pid) => pid,
322 Err(e) => return failed_outcome(format!("ProgramID parse error: {e}")),
323 };
324 let function_id = match Identifier::<CurrentNetwork>::from_str(&case.function) {
325 Ok(fid) => fid,
326 Err(e) => return failed_outcome(format!("FunctionID parse error: {e}")),
327 };
328 let inputs = case.input.iter();
329
330 let authorization = match catch_unwind(AssertUnwindSafe(|| {
332 vm.authorize(&private_key, program_id, function_id, inputs, rng)
333 })) {
334 Ok(Ok(auth)) => auth,
335 Ok(Err(e)) => return failed_outcome(format!("{e}")),
336 Err(e) => return failed_outcome(format!("{e:?}")),
337 };
338
339 let response =
341 match catch_unwind(AssertUnwindSafe(|| vm.process().read().evaluate::<AleoTestnetV0>(authorization))) {
342 Ok(Ok(resp)) => resp,
343 Ok(Err(e)) => return failed_outcome(format!("{e}")),
344 Err(e) => return failed_outcome(format!("{e:?}")),
345 };
346
347 let outputs = response.outputs();
348 let output = match outputs.len() {
349 0 => Value::make_unit(),
350 1 => outputs[0].clone().into(),
351 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
352 };
353
354 EvaluationOutcome {
355 outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
356 status: EvaluationStatus::Success,
357 }
358 })
359 .collect();
360
361 Ok(outcomes)
362}
363
364pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<ExecutionOutcome>>> {
366 if case_sets.is_empty() {
367 return Ok(Vec::new());
368 }
369
370 let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
372
373 let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
375
376 let mut blocks = Vec::new();
378
379 let genesis_block =
381 Block::from_bytes_le(include_bytes!("resources/genesis_8d710d7e2_40val_snarkos_dev_network.bin"))?;
382
383 let ledger =
385 Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(genesis_block.clone(), StorageMode::Production)
386 .unwrap();
387
388 let latest_consensus_version = ConsensusVersion::latest();
390 let start_height =
391 config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
392 while ledger.latest_height() < start_height {
393 let block = ledger
394 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
395 .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
396 ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
397 blocks.push(block);
398 }
399
400 for Program { bytecode, name } in &config.programs {
402 let aleo_program =
405 ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
406
407 let mut deploy = |edition: u16| -> Result<()> {
408 let deployment = if config.skip_proving {
409 deploy_without_proof(
410 ledger.vm(),
411 &genesis_private_key,
412 &aleo_program,
413 edition,
414 latest_consensus_version,
415 &mut rng,
416 )
417 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
418 } else {
419 ledger
422 .vm()
423 .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
424 .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
425 };
426 let block = ledger
427 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
428 .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
429 ledger
430 .advance_to_next_block(&block)
431 .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
432
433 if block.transactions().num_accepted() != 1 {
435 return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
436 }
437
438 blocks.push(block);
440
441 Ok(())
442 };
443
444 deploy(0)?;
446 if !aleo_program.contains_constructor() {
448 deploy(1)?;
449 }
450 }
451
452 let mut indexed_ledgers = vec![(0, ledger)];
454 indexed_ledgers.extend(
455 (1..case_sets.len())
456 .map(|i| {
457 let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
459 genesis_block.clone(),
460 StorageMode::Production,
461 )
462 .expect("Failed to load copy of ledger");
463 for block in blocks.iter() {
465 l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
466 }
467
468 (i, l)
469 })
470 .collect::<Vec<_>>(),
471 );
472
473 let results = indexed_ledgers
475 .into_iter()
476 .map(|(index, ledger)| {
477 let cases = &case_sets[index];
479 let mut rng = rng.clone();
481
482 let skip_proving = config.skip_proving;
484 let transactions: Vec<Transaction<CurrentNetwork>> = cases
485 .iter()
486 .filter_map(|case| case.private_key.as_ref())
487 .map(|key| {
488 let private_key =
490 PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
491 let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
493 if skip_proving {
495 let (tx, _) = execute_without_proof(
496 ledger.vm(),
497 &genesis_private_key,
498 "credits.aleo",
499 "transfer_public",
500 [
501 SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
502 SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
503 ]
504 .iter(),
505 latest_consensus_version,
506 &mut rng,
507 )
508 .expect("Failed to generate funding transaction");
509 tx
510 } else {
511 ledger
512 .vm()
513 .execute(
514 &genesis_private_key,
515 ("credits.aleo", "transfer_public"),
516 [
517 SvmValue::from_str(&format!("{address}"))
518 .expect("Failed to parse recipient address"),
519 SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
520 ]
521 .iter(),
522 None,
523 0u64,
524 None,
525 &mut rng,
526 )
527 .expect("Failed to generate funding transaction")
528 }
529 })
530 .collect();
531
532 let block = ledger
534 .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
535 .expect("Failed to prepare advance to next beacon block");
536 assert!(block.aborted_transaction_ids().is_empty());
538 assert_eq!(block.transactions().num_rejected(), 0);
539 ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
541
542 let mut case_outcomes = Vec::new();
543
544 for case in cases {
545 assert!(
546 ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
547 "Program {} should exist.",
548 case.program_name
549 );
550
551 let private_key = case
552 .private_key
553 .as_ref()
554 .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
555 .unwrap_or(genesis_private_key);
556
557 let mut execution = None;
558 let mut verified = false;
559 let mut status = ExecutionStatus::None;
560 let mut abort_reason: Option<String> = None;
561
562 let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
566 if skip_proving {
567 execute_without_proof(
568 ledger.vm(),
569 &private_key,
570 &case.program_name,
571 &case.function,
572 case.input.iter(),
573 latest_consensus_version,
574 &mut rng,
575 )
576 } else {
577 ledger
578 .vm()
579 .execute_with_response(
580 &private_key,
581 (&case.program_name, &case.function),
582 case.input.iter(),
583 None,
584 0,
585 None,
586 &mut rng,
587 )
588 .map_err(anyhow::Error::from)
589 }
590 }));
591
592 if let Err(payload) = execute_output {
593 let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
594 let s2 = payload.downcast_ref::<String>().cloned();
595 let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
596
597 case_outcomes.push(ExecutionOutcome {
598 outcome: Outcome {
599 program_name: case.program_name.clone(),
600 function: case.function.clone(),
601 output: Value::make_unit(),
602 },
603 status: ExecutionStatus::Halted(s),
604 verified: false,
605 execution: "".to_string(),
606 });
607
608 continue;
609 }
610
611 let result = execute_output.unwrap().and_then(|(transaction, response)| {
612 verified = skip_proving || ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
615 execution = Some(transaction.clone());
616 let block = ledger
617 .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], &mut rng)
618 .map_err(|e| anyhow::anyhow!("{e}"))?;
619 status =
620 match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
621 (false, _) => {
622 if let Some(ref tx) = execution
624 && let Err(e) = ledger.vm().check_transaction(tx, None, &mut rng)
625 {
626 abort_reason = Some(format!("{e}"));
627 }
628 ExecutionStatus::Aborted(abort_reason.take())
629 }
630 (true, true) => ExecutionStatus::Accepted,
631 (true, false) => ExecutionStatus::Rejected,
632 };
633 ledger.advance_to_next_block(&block)?;
634 Ok(response)
635 });
636
637 let output = match result {
638 Ok(response) => {
639 let outputs = response.outputs();
640 match outputs.len() {
641 0 => Value::make_unit(),
642 1 => outputs[0].clone().into(),
643 _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
644 }
645 }
646 Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
647 };
648
649 let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
652 Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
653 } else {
654 None
655 };
656
657 case_outcomes.push(ExecutionOutcome {
658 outcome: Outcome {
659 program_name: case.program_name.clone(),
660 function: case.function.clone(),
661 output,
662 },
663 status,
664 verified,
665 execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
666 });
667 }
668
669 Ok((index, case_outcomes))
670 })
671 .collect::<Result<Vec<_>>>()?;
672
673 let mut ordered_results: Vec<Vec<ExecutionOutcome>> = vec![Default::default(); case_sets.len()];
675 for (index, outcomes) in results.into_iter() {
676 ordered_results[index] = outcomes;
677 }
678
679 Ok(ordered_results)
680}