Skip to main content

leo_compiler/
run.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17//! Utilities for running Leo programs in test environments.
18//!
19//! Currently this is used by:
20//! - the test runner in `test_execution.rs`, and
21//! - the `leo test` command in `cli/commands/test.rs`.
22//!
23//! Provides functions for:
24//! - Running programs without a ledger (`run_without_ledger`). To be used for evaluating non-async code.
25//! - Running programs with a full ledger (`run_with_ledger`), including setup of VM, blocks, and execution tracking.
26//!   To be used for executing async code.
27//!
28//! Also defines types for program configuration, test cases, and outcomes.
29
30use 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::{FinalizeStoreTrait, ProgramCore, StackTrait},
64};
65use std::{
66    fmt,
67    panic::{AssertUnwindSafe, catch_unwind},
68    str::FromStr as _,
69};
70
71type CurrentNetwork = TestnetV0;
72
73/// Programs and configuration to run.
74#[derive(Debug)]
75pub struct Config {
76    pub seed: u64,
77    // If `None`, start at the height for the latest consensus version.
78    pub start_height: Option<u32>,
79    pub programs: Vec<Program>,
80    /// Skip proof generation for faster testing. Requires `dev_skip_checks`.
81    pub skip_proving: bool,
82}
83
84/// A program to deploy to the ledger.
85#[derive(Clone, Debug, Default)]
86pub struct Program {
87    pub bytecode: String,
88    pub name: String,
89}
90
91/// A single finalize-store entry to write before a case is evaluated.
92///
93/// `run_without_ledger` doesn't run finalize blocks, so mappings are otherwise empty — seeding
94/// is the only way to give view test cases (and finalize-reading transitions) state to read.
95#[derive(Clone, Debug)]
96pub struct SeedMapping {
97    /// Mapping name in the case's `program_name`.
98    pub mapping: String,
99    /// Plaintext key in snarkVM display form.
100    pub key: String,
101    /// Plaintext value in snarkVM display form.
102    pub value: String,
103}
104
105/// A particular case to run.
106#[derive(Clone, Debug, Default)]
107pub struct Case {
108    pub program_name: String,
109    pub function: String,
110    pub private_key: Option<String>,
111    pub input: Vec<String>,
112    /// Pre-populated finalize-store entries written before the case is evaluated.
113    pub seed_mapping: Vec<SeedMapping>,
114}
115
116/// The status of a case that was run.
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum ExecutionStatus {
119    None,
120    Aborted(Option<String>),
121    Accepted,
122    Rejected,
123    Halted(String),
124}
125
126impl fmt::Display for ExecutionStatus {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        match self {
129            Self::Halted(s) => write!(f, "halted ({s})"),
130            Self::None => write!(f, "none"),
131            Self::Aborted(None) => write!(f, "aborted"),
132            Self::Aborted(Some(reason)) => write!(f, "aborted: {reason}"),
133            Self::Accepted => write!(f, "accepted"),
134            Self::Rejected => write!(f, "rejected"),
135        }
136    }
137}
138
139#[derive(Debug, Clone)]
140pub enum EvaluationStatus {
141    Success,
142    Failed(String),
143}
144
145impl fmt::Display for EvaluationStatus {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        match self {
148            Self::Success => write!(f, "success"),
149            Self::Failed(e) => write!(f, "failed: {e}"),
150        }
151    }
152}
153
154/// Shared fields for all outcome types.
155#[derive(Debug, Clone)]
156pub struct Outcome {
157    pub program_name: String,
158    pub function: String,
159    pub output: Value,
160}
161
162impl Outcome {
163    pub fn output(&self) -> Value {
164        self.output.clone()
165    }
166}
167
168/// Outcome of an evaluation-only run (no execution trace, no verification).
169#[derive(Debug, Clone)]
170pub struct EvaluationOutcome {
171    pub outcome: Outcome,
172    pub status: EvaluationStatus,
173}
174
175impl EvaluationOutcome {
176    pub fn output(&self) -> Value {
177        self.outcome.output()
178    }
179}
180
181/// Outcome that includes execution and verification details.
182#[derive(Debug, Clone)]
183pub struct ExecutionOutcome {
184    pub outcome: Outcome,
185    pub verified: bool,
186    pub execution: String,
187    pub status: ExecutionStatus,
188}
189
190impl ExecutionOutcome {
191    pub fn output(&self) -> Value {
192        self.outcome.output()
193    }
194}
195
196/// Placeholder verifying key used for proof-less deployments.
197pub const PLACEHOLDER_VK: &str = "verifier1q9qqqqqqqqqqqqyvxgqqqqqqqqq87vsqqqqqqqqqhe7sqqqqqqqqqma4qqqqqqqqqq65yqqqqqqqqqqvqqqqqqqqqqqgtlaj49fmrk2d8slmselaj9tpucgxv6awu6yu4pfcn5xa0yy0tpxpc8wemasjvvxr9248vt3509vpk3u60ejyfd9xtvjmudpp7ljq2csk4yqz70ug3x8xp3xn3ul0yrrw0mvd2g8ju7rts50u3smue03gp99j88f0ky8h6fjlpvh58rmxv53mldmgrxa3fq6spsh8gt5whvsyu2rk4a2wmeyrgvvdf29pwp02srktxnvht3k6ff094usjtllggva2ym75xc4lzuqu9xx8ylfkm3qc7lf7ktk9uu9du5raukh828dzgq26hrarq5ajjl7pz7zk924kekjrp92r6jh9dpp05mxtuffwlmvew84dvnqrkre7lw29mkdzgdxwe7q8z0vnkv2vwwdraekw2va3plu7rkxhtnkuxvce0qkgxcxn5mtg9q2c3vxdf2r7jjse2g68dgvyh85q4mzfnvn07lletrpty3vypus00gfu9m47rzay4mh5w9f03z9zgzgzhkv0mupdqsk8naljqm9tc2qqzhf6yp3mnv2ey89xk7sw9pslzzlkndfd2upzmew4e4vnrkr556kexs9qrykkuhsr260mnrgh7uv0sp2meky0keeukaxgjdsnmy77kl48g3swcvqdjm50ejzr7x04vy7hn7anhd0xeetclxunnl7pd6e52qxdlr3nmutz4zr8f2xqa57a2zkl59a28w842cj4783zpy9hxw03k6vz4a3uu7sm072uqknpxjk8fyq4vxtqd08kd93c2mt40lj9ag35nm4rwcfjayejk57m9qqu83qnkrj3sz90pw808srmf705n2yu6gvqazpvu2mwm8x6mgtlsntxfhr0qas43rqxnccft36z4ygty86390t7vrt08derz8368z8ekn3yywxgp4uq24gm6e58tpp0lcvtpsm3nkwpnmzztx4qvkaf6vk38wg787h8mfpqqqqqqqqqqffkful";
198
199/// Placeholder certificate used for proof-less deployments.
200pub const PLACEHOLDER_CERT: &str =
201    "certificate1qyqsqqqqqqqqqqxvwszp09v860w62s2l4g6eqf0kzppyax5we36957ywqm2dplzwvvlqg0kwlnmhzfatnax7uaqt7yqqqw0sc4u";
202
203/// Deploy a program without generating certificates or proofs.
204fn deploy_without_proof(
205    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
206    private_key: &PrivateKey<CurrentNetwork>,
207    program: &ProgramCore<CurrentNetwork>,
208    edition: u16,
209    consensus_version: ConsensusVersion,
210    rng: &mut ChaCha20Rng,
211) -> anyhow::Result<Transaction<CurrentNetwork>> {
212    // Create placeholder verifying keys and certificates for each function and record.
213    // The ledger requires exactly num_functions + num_records verifying keys per deployment.
214    let placeholder_vk = VerifyingKey::from_str(PLACEHOLDER_VK)?;
215    let placeholder_cert = Certificate::from_str(PLACEHOLDER_CERT)?;
216    let verifying_keys = program
217        .functions()
218        .keys()
219        .chain(program.records().keys())
220        .map(|name| (*name, (placeholder_vk.clone(), placeholder_cert.clone())))
221        .collect::<Vec<_>>();
222
223    // Create the deployment with placeholders.
224    let mut deployment = Deployment::new(edition, program.clone(), verifying_keys, None, None)
225        .map_err(|e| anyhow!("Failed to create deployment: {e}"))?;
226
227    // Set the program owner and checksum.
228    deployment.set_program_owner_raw(Some(Address::try_from(private_key)?));
229    deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
230
231    // Compute the deployment ID and construct the owner.
232    let deployment_id = deployment.to_deployment_id()?;
233    let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
234
235    // Calculate the minimum deployment cost.
236    let (minimum_deployment_cost, _) = deployment_cost(vm.process(), &deployment, consensus_version)?;
237
238    // Authorize the fee (public, no proof).
239    let fee_authorization = vm.authorize_fee_public(private_key, minimum_deployment_cost, 0, deployment_id, rng)?;
240
241    // Create a fee transition without a proof.
242    let state_root = vm.block_store().current_state_root();
243    let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
244
245    Transaction::from_deployment(owner, deployment, fee).map_err(|e| anyhow!("Failed to create deployment tx: {e}"))
246}
247
248/// Execute a transition without generating proofs. Returns (Transaction, Response).
249fn execute_without_proof(
250    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
251    private_key: &PrivateKey<CurrentNetwork>,
252    program_id: &str,
253    function_name: &str,
254    inputs: impl ExactSizeIterator<Item = impl TryInto<SvmValue<CurrentNetwork>>>,
255    consensus_version: ConsensusVersion,
256    rng: &mut ChaCha20Rng,
257) -> anyhow::Result<(Transaction<CurrentNetwork>, snarkvm::prelude::Response<CurrentNetwork>)> {
258    // Authorize the execution (fast, no proving).
259    let authorization = vm.authorize(private_key, program_id, function_name, inputs, rng)?;
260
261    // Evaluate to get the response (outputs, no proving).
262    let response = vm.process().evaluate::<AleoTestnetV0>(authorization.clone())?;
263
264    // Build the execution without a proof.
265    let state_root = vm.block_store().current_state_root();
266    let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?;
267
268    // Calculate the execution cost for fee authorization.
269    let (cost, _) = execution_cost(vm.process(), &execution, consensus_version)?;
270
271    // Authorize and create the fee without a proof.
272    let execution_id = authorization.to_execution_id()?;
273    let fee_authorization = vm.authorize_fee_public(private_key, cost, 0, execution_id, rng)?;
274    let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
275
276    let transaction = Transaction::from_execution(execution, Some(fee))?;
277    Ok((transaction, response))
278}
279
280/// Evaluates a set of cases against some programs without using a ledger.
281///
282/// Each case is run in isolation, producing an `EvaluationOutcome` for its
283/// output and success/failure status. Panics and errors in authorization or
284/// evaluation are caught and reported as failures.
285pub fn run_without_ledger(config: &Config, cases: &[Case]) -> Result<Vec<EvaluationOutcome>> {
286    // Nothing to do
287    if cases.is_empty() {
288        return Ok(Vec::new());
289    }
290
291    let programs_and_editions: Vec<(snarkvm::prelude::Program<CurrentNetwork>, u16)> = config
292        .programs
293        .iter()
294        .map(|Program { bytecode, name }| {
295            let program = snarkvm::prelude::Program::<CurrentNetwork>::from_str(bytecode)
296                .map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
297            // Assume edition 1. We can consider parametrizing this in the future.
298            let edition: u16 = 1;
299            Ok((program, edition))
300        })
301        .collect::<Result<Vec<_>>>()?;
302
303    let outcomes: Vec<EvaluationOutcome> = cases
304        .iter()
305        .map(|case| {
306            let rng = &mut ChaCha20Rng::seed_from_u64(config.seed);
307
308            // Helper to produce an EvaluationOutcome with `Failed` status
309            let failed_outcome = |e: String| EvaluationOutcome {
310                outcome: Outcome {
311                    program_name: case.program_name.clone(),
312                    function: case.function.clone(),
313                    output: Value::make_unit(),
314                },
315                status: EvaluationStatus::Failed(e),
316            };
317
318            let vm = match ConsensusStore::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::open(
319                StorageMode::Production,
320            ) {
321                Ok(store) => match VM::from(store) {
322                    Ok(vm) => vm,
323                    Err(e) => return failed_outcome(format!("VM init error: {e}")),
324                },
325                Err(e) => return failed_outcome(format!("Consensus store open error: {e}")),
326            };
327
328            if let Err(e) = vm.process().lock().add_programs_with_editions(&programs_and_editions) {
329                return failed_outcome(format!("Failed to add programs: {e}"));
330            }
331
332            // `add_programs_with_editions` registers programs in the process but does not touch
333            // the finalize store. Views that read mappings need the mappings to be present in
334            // the finalize store, so initialize each program's mappings here. This mirrors what
335            // a real deployment would do during finalize.
336            for (program, _) in &programs_and_editions {
337                for mapping_name in program.mappings().keys() {
338                    // `initialize_mapping` returns an error if the mapping is already present.
339                    // We discard that error: across multiple cases the same mapping is initialized
340                    // each time.
341                    let _ = vm.finalize_store().initialize_mapping(*program.id(), *mapping_name);
342                }
343            }
344
345            let private_key = match PrivateKey::from_str(leo_ast::TEST_PRIVATE_KEY) {
346                Ok(pk) => pk,
347                Err(e) => return failed_outcome(format!("Private key parse error: {e}")),
348            };
349            let program_id = match ProgramID::<CurrentNetwork>::from_str(&case.program_name) {
350                Ok(pid) => pid,
351                Err(e) => return failed_outcome(format!("ProgramID parse error: {e}")),
352            };
353            let function_id = match Identifier::<CurrentNetwork>::from_str(&case.function) {
354                Ok(fid) => fid,
355                Err(e) => return failed_outcome(format!("FunctionID parse error: {e}")),
356            };
357
358            // Seed any pre-populated mapping entries before evaluating the case. Useful for
359            // view test cases where `run_without_ledger` doesn't run finalize blocks, so
360            // mappings are otherwise empty.
361            for SeedMapping { mapping: mapping_name_str, key: key_str, value: value_str } in &case.seed_mapping {
362                let mapping_name = match Identifier::<CurrentNetwork>::from_str(mapping_name_str) {
363                    Ok(n) => n,
364                    Err(e) => return failed_outcome(format!("Failed to parse seed mapping name: {e}")),
365                };
366                let key = match snarkvm::prelude::Plaintext::<CurrentNetwork>::from_str(key_str) {
367                    Ok(k) => k,
368                    Err(e) => return failed_outcome(format!("Failed to parse seed key: {e}")),
369                };
370                let value = match SvmValue::<CurrentNetwork>::from_str(value_str) {
371                    Ok(v) => v,
372                    Err(e) => return failed_outcome(format!("Failed to parse seed value: {e}")),
373                };
374                if let Err(e) = vm.finalize_store().update_key_value(program_id, mapping_name, key, value) {
375                    return failed_outcome(format!("Failed to seed mapping: {e}"));
376                }
377            }
378
379            // Views and transitions take different snarkVM paths:
380            //   - Transitions go through `authorize` + `evaluate`, producing a transition object.
381            //   - Views go through `evaluate_view_at_height`, returning plaintext outputs with no transition and
382            //     no transaction.
383            let is_view = vm
384                .process()
385                .get_stack(program_id)
386                .map(|stack| stack.program().contains_view(&function_id))
387                .unwrap_or(false);
388
389            if is_view {
390                handle_view(case, &vm, program_id, function_id)
391            } else {
392                handle_transition(case, &vm, program_id, function_id, &private_key, rng)
393            }
394        })
395        .collect();
396
397    Ok(outcomes)
398}
399
400/// Evaluate a single view-fn case against `vm`'s in-memory finalize store and return the outcome.
401fn handle_view(
402    case: &Case,
403    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
404    program_id: ProgramID<CurrentNetwork>,
405    function_id: Identifier<CurrentNetwork>,
406) -> EvaluationOutcome {
407    let failed = |e: String| failed_evaluation_outcome(case, e);
408    let parsed_inputs: Vec<SvmValue<CurrentNetwork>> = match case
409        .input
410        .iter()
411        .map(|s| SvmValue::<CurrentNetwork>::from_str(s))
412        .collect::<std::result::Result<Vec<_>, _>>()
413    {
414        Ok(v) => v,
415        Err(e) => return failed(format!("Failed to parse view input: {e}")),
416    };
417    // For an empty in-memory consensus store there is no block 0, so route directly through
418    // `Process::evaluate_view_at_height` with a fabricated `FinalizeGlobalState`. Tests are off-consensus by
419    // construction, so the values here only matter for queries that read `block.height` / `network.id`.
420    let state = match snarkvm::synthesizer::program::FinalizeGlobalState::new::<CurrentNetwork>(
421        0,
422        0,
423        None,
424        0,
425        0,
426        Default::default(),
427    ) {
428        Ok(s) => s,
429        Err(e) => return failed(format!("Failed to build FinalizeGlobalState: {e}")),
430    };
431    let response = match catch_unwind(AssertUnwindSafe(|| {
432        vm.process().evaluate_view_at_height(state, vm.finalize_store(), program_id, function_id, parsed_inputs, 0)
433    })) {
434        Ok(Ok(resp)) => resp,
435        Ok(Err(e)) => return failed(format!("{e}")),
436        Err(e) => return failed(format!("{e:?}")),
437    };
438    let output = match response.len() {
439        0 => Value::make_unit(),
440        1 => response[0].clone().into(),
441        _ => Value::make_tuple(response.iter().map(|x| x.clone().into())),
442    };
443    EvaluationOutcome {
444        outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
445        status: EvaluationStatus::Success,
446    }
447}
448
449/// Evaluate a single transition case (authorize + evaluate) against `vm` and return the outcome.
450fn handle_transition(
451    case: &Case,
452    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
453    program_id: ProgramID<CurrentNetwork>,
454    function_id: Identifier<CurrentNetwork>,
455    private_key: &PrivateKey<CurrentNetwork>,
456    rng: &mut ChaCha20Rng,
457) -> EvaluationOutcome {
458    let failed = |e: String| failed_evaluation_outcome(case, e);
459    let inputs = case.input.iter();
460
461    // --- catch panics from authorize ---
462    let authorization =
463        match catch_unwind(AssertUnwindSafe(|| vm.authorize(private_key, program_id, function_id, inputs, rng))) {
464            Ok(Ok(auth)) => auth,
465            Ok(Err(e)) => return failed(format!("{e}")),
466            Err(e) => return failed(format!("{e:?}")),
467        };
468
469    // --- catch panics from evaluate ---
470    let response = match catch_unwind(AssertUnwindSafe(|| vm.process().evaluate::<AleoTestnetV0>(authorization))) {
471        Ok(Ok(resp)) => resp,
472        Ok(Err(e)) => return failed(format!("{e}")),
473        Err(e) => return failed(format!("{e:?}")),
474    };
475
476    let outputs = response.outputs();
477    let output = match outputs.len() {
478        0 => Value::make_unit(),
479        1 => outputs[0].clone().into(),
480        _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
481    };
482
483    EvaluationOutcome {
484        outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
485        status: EvaluationStatus::Success,
486    }
487}
488
489/// Build a `Failed` outcome carrying `e` for the given case.
490fn failed_evaluation_outcome(case: &Case, e: String) -> EvaluationOutcome {
491    EvaluationOutcome {
492        outcome: Outcome {
493            program_name: case.program_name.clone(),
494            function: case.function.clone(),
495            output: Value::make_unit(),
496        },
497        status: EvaluationStatus::Failed(e),
498    }
499}
500
501/// Run the functions indicated by `cases` from the programs in `config`.
502pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<ExecutionOutcome>>> {
503    if case_sets.is_empty() {
504        return Ok(Vec::new());
505    }
506
507    // Initialize an rng.
508    let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
509
510    // Initialize a genesis private key.
511    let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
512
513    // Store all of the non-genesis blocks created during set up.
514    let mut blocks = Vec::new();
515
516    // Load the genesis block.
517    let genesis_block =
518        Block::from_bytes_le(include_bytes!("resources/genesis_8d710d7e2_40val_snarkos_dev_network.bin"))?;
519
520    // Initialize a `Ledger`. This should always succeed.
521    // Use `new_test` to avoid spurious block-tree persistence errors on drop.
522    let ledger = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
523        genesis_block.clone(),
524        StorageMode::new_test(None),
525    )
526    .unwrap();
527
528    // Advance the `VM` to the start height, defaulting to the height for the latest consensus version.
529    let latest_consensus_version = ConsensusVersion::latest();
530    let start_height =
531        config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
532    while ledger.latest_height() < start_height {
533        let block = ledger
534            .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
535            .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
536        ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
537        blocks.push(block);
538    }
539
540    // Deploy each bytecode separately.
541    for Program { bytecode, name } in &config.programs {
542        // Parse the bytecode as an Aleo program.
543        // Note that this function checks that the bytecode is well-formed.
544        let aleo_program =
545            ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
546
547        let mut deploy = |edition: u16| -> Result<()> {
548            let deployment = if config.skip_proving {
549                deploy_without_proof(
550                    ledger.vm(),
551                    &genesis_private_key,
552                    &aleo_program,
553                    edition,
554                    latest_consensus_version,
555                    &mut rng,
556                )
557                .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
558            } else {
559                // Add the program to the ledger.
560                // Note that this function performs an additional validity check on the bytecode.
561                ledger
562                    .vm()
563                    .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
564                    .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
565            };
566            let block = ledger
567                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
568                .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
569            ledger
570                .advance_to_next_block(&block)
571                .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
572
573            // Check that the deployment transaction was accepted.
574            if block.transactions().num_accepted() != 1 {
575                return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
576            }
577
578            // Store the block.
579            blocks.push(block);
580
581            Ok(())
582        };
583
584        // Deploy the program.
585        deploy(0)?;
586        // If the program does not have a constructor, deploy it twice to satisfy the edition requirement.
587        if !aleo_program.contains_constructor() {
588            deploy(1)?;
589        }
590    }
591
592    // Initialize ledger instances for each case set.
593    let mut indexed_ledgers = vec![(0, ledger)];
594    indexed_ledgers.extend(
595        (1..case_sets.len())
596            .map(|i| {
597                // Initialize a `Ledger`. This should always succeed.
598                // Use `new_test` to avoid spurious block-tree persistence errors on drop.
599                let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
600                    genesis_block.clone(),
601                    StorageMode::new_test(None),
602                )
603                .expect("Failed to load copy of ledger");
604                // Add the setup blocks.
605                for block in blocks.iter() {
606                    l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
607                }
608
609                (i, l)
610            })
611            .collect::<Vec<_>>(),
612    );
613
614    // For each of the case sets, run the cases sequentially.
615    let results = indexed_ledgers
616        .into_iter()
617        .map(|(index, ledger)| {
618            // Get the cases for this ledger.
619            let cases = &case_sets[index];
620            // Clone the RNG.
621            let mut rng = rng.clone();
622
623            // Fund each private key used in the test cases with 1M ALEO.
624            let skip_proving = config.skip_proving;
625            let transactions: Vec<Transaction<CurrentNetwork>> = cases
626                .iter()
627                .filter_map(|case| case.private_key.as_ref())
628                .map(|key| {
629                    // Parse the private key.
630                    let private_key =
631                        PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
632                    // Convert the private key to an address.
633                    let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
634                    // Generate the transaction.
635                    if skip_proving {
636                        let (tx, _) = execute_without_proof(
637                            ledger.vm(),
638                            &genesis_private_key,
639                            "credits.aleo",
640                            "transfer_public",
641                            [
642                                SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
643                                SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
644                            ]
645                            .iter(),
646                            latest_consensus_version,
647                            &mut rng,
648                        )
649                        .expect("Failed to generate funding transaction");
650                        tx
651                    } else {
652                        ledger
653                            .vm()
654                            .execute(
655                                &genesis_private_key,
656                                ("credits.aleo", "transfer_public"),
657                                [
658                                    SvmValue::from_str(&format!("{address}"))
659                                        .expect("Failed to parse recipient address"),
660                                    SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
661                                ]
662                                .iter(),
663                                None,
664                                0u64,
665                                None,
666                                &mut rng,
667                            )
668                            .expect("Failed to generate funding transaction")
669                    }
670                })
671                .collect();
672
673            // Create a block with the funding transactions.
674            let block = ledger
675                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
676                .expect("Failed to prepare advance to next beacon block");
677            // Assert that no transactions were aborted or rejected.
678            assert!(block.aborted_transaction_ids().is_empty());
679            assert_eq!(block.transactions().num_rejected(), 0);
680            // Advance the ledger to the next block.
681            ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
682
683            let mut case_outcomes = Vec::new();
684
685            for case in cases {
686                assert!(
687                    ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
688                    "Program {} should exist.",
689                    case.program_name
690                );
691
692                let private_key = case
693                    .private_key
694                    .as_ref()
695                    .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
696                    .unwrap_or(genesis_private_key);
697
698                let mut execution = None;
699                let mut verified = false;
700                let mut status = ExecutionStatus::None;
701                let mut abort_reason: Option<String> = None;
702
703                // Halts are handled by panics, so we need to catch them.
704                // I'm not thrilled about this usage of `AssertUnwindSafe`, but it seems to be
705                // used frequently in SnarkVM anyway.
706                let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
707                    if skip_proving {
708                        execute_without_proof(
709                            ledger.vm(),
710                            &private_key,
711                            &case.program_name,
712                            &case.function,
713                            case.input.iter(),
714                            latest_consensus_version,
715                            &mut rng,
716                        )
717                    } else {
718                        ledger
719                            .vm()
720                            .execute_with_response(
721                                &private_key,
722                                (&case.program_name, &case.function),
723                                case.input.iter(),
724                                None,
725                                0,
726                                None,
727                                &mut rng,
728                            )
729                            .map_err(anyhow::Error::from)
730                    }
731                }));
732
733                if let Err(payload) = execute_output {
734                    let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
735                    let s2 = payload.downcast_ref::<String>().cloned();
736                    let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
737
738                    case_outcomes.push(ExecutionOutcome {
739                        outcome: Outcome {
740                            program_name: case.program_name.clone(),
741                            function: case.function.clone(),
742                            output: Value::make_unit(),
743                        },
744                        status: ExecutionStatus::Halted(s),
745                        verified: false,
746                        execution: "".to_string(),
747                    });
748
749                    continue;
750                }
751
752                let result = execute_output.unwrap().and_then(|(transaction, response)| {
753                    // Skip verification when proving is skipped — proofs are absent
754                    // and verification is meaningless.
755                    verified = skip_proving || ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
756                    execution = Some(transaction.clone());
757                    let block = ledger
758                        .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], &mut rng)
759                        .map_err(|e| anyhow::anyhow!("{e}"))?;
760                    status =
761                        match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
762                            (false, _) => {
763                                // Attempt check_transaction to diagnose abort reason.
764                                if let Some(ref tx) = execution
765                                    && let Err(e) = ledger.vm().check_transaction(tx, None, &mut rng)
766                                {
767                                    abort_reason = Some(format!("{e}"));
768                                }
769                                ExecutionStatus::Aborted(abort_reason.take())
770                            }
771                            (true, true) => ExecutionStatus::Accepted,
772                            (true, false) => ExecutionStatus::Rejected,
773                        };
774                    ledger.advance_to_next_block(&block)?;
775                    Ok(response)
776                });
777
778                let output = match result {
779                    Ok(response) => {
780                        let outputs = response.outputs();
781                        match outputs.len() {
782                            0 => Value::make_unit(),
783                            1 => outputs[0].clone().into(),
784                            _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
785                        }
786                    }
787                    Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
788                };
789
790                // Extract the execution, removing the global state root and proof.
791                // This is necessary as they are not deterministic across runs, even with RNG fixed.
792                let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
793                    Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
794                } else {
795                    None
796                };
797
798                case_outcomes.push(ExecutionOutcome {
799                    outcome: Outcome {
800                        program_name: case.program_name.clone(),
801                        function: case.function.clone(),
802                        output,
803                    },
804                    status,
805                    verified,
806                    execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
807                });
808            }
809
810            Ok((index, case_outcomes))
811        })
812        .collect::<Result<Vec<_>>>()?;
813
814    // Reorder results to match input order.
815    let mut ordered_results: Vec<Vec<ExecutionOutcome>> = vec![Default::default(); case_sets.len()];
816    for (index, outcomes) in results.into_iter() {
817        ordered_results[index] = outcomes;
818    }
819
820    Ok(ordered_results)
821}