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    // `evaluate_view_with_stack_at_height` with a fabricated `FinalizeGlobalState`. Tests are off-consensus
419    // by construction, so the values here only matter for queries that read `block.height` / `block.timestamp`
420    // / `network.id`; the timestamp is fixed so those reads are deterministic. `VM::evaluate_view_at_height`
421    // cannot be used here: it resolves the program edition from on-chain deployments, which an empty store
422    // does not have.
423    let state = match snarkvm::synthesizer::program::FinalizeGlobalState::new::<CurrentNetwork>(
424        0,
425        0,
426        Some(1234567890i64),
427        0,
428        0,
429        Default::default(),
430        None,
431    ) {
432        Ok(s) => s,
433        Err(e) => return failed(format!("Failed to build FinalizeGlobalState: {e}")),
434    };
435    // Off-consensus tests deploy nothing, so the registered stack carries no program owner. Rebuild the
436    // stack (the only way to a mutable handle — `get_stack` hands out a shared `Arc`) and set the owner to
437    // the test key's address, mirroring a real deployment, so views can read `self.program_owner`.
438    let registered = match vm.process().get_stack(program_id) {
439        Ok(stack) => stack,
440        Err(e) => return failed(format!("Failed to load stack for `{program_id}`: {e}")),
441    };
442    let mut stack = match snarkvm::synthesizer::process::Stack::new(vm.process(), registered.program()) {
443        Ok(stack) => stack,
444        Err(e) => return failed(format!("Failed to build stack for `{program_id}`: {e}")),
445    };
446    match PrivateKey::<CurrentNetwork>::from_str(leo_ast::TEST_PRIVATE_KEY)
447        .and_then(|pk| Address::<CurrentNetwork>::try_from(&pk))
448    {
449        Ok(owner) => stack.set_program_owner(Some(owner)),
450        Err(e) => return failed(format!("Failed to derive program owner: {e}")),
451    };
452    let response = match catch_unwind(AssertUnwindSafe(|| {
453        snarkvm::synthesizer::process::evaluate_view_with_stack_at_height(
454            state,
455            vm.finalize_store(),
456            &stack,
457            &function_id,
458            parsed_inputs,
459            0,
460        )
461    })) {
462        Ok(Ok(resp)) => resp,
463        Ok(Err(e)) => return failed(format!("{e}")),
464        Err(e) => return failed(format!("{e:?}")),
465    };
466    let output = match response.len() {
467        0 => Value::make_unit(),
468        1 => response[0].clone().into(),
469        _ => Value::make_tuple(response.iter().map(|x| x.clone().into())),
470    };
471    EvaluationOutcome {
472        outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
473        status: EvaluationStatus::Success,
474    }
475}
476
477/// Evaluate a single transition case (authorize + evaluate) against `vm` and return the outcome.
478fn handle_transition(
479    case: &Case,
480    vm: &VM<CurrentNetwork, ConsensusMemory<CurrentNetwork>>,
481    program_id: ProgramID<CurrentNetwork>,
482    function_id: Identifier<CurrentNetwork>,
483    private_key: &PrivateKey<CurrentNetwork>,
484    rng: &mut ChaCha20Rng,
485) -> EvaluationOutcome {
486    let failed = |e: String| failed_evaluation_outcome(case, e);
487    let inputs = case.input.iter();
488
489    // --- catch panics from authorize ---
490    let authorization =
491        match catch_unwind(AssertUnwindSafe(|| vm.authorize(private_key, program_id, function_id, inputs, rng))) {
492            Ok(Ok(auth)) => auth,
493            Ok(Err(e)) => return failed(format!("{e}")),
494            Err(e) => return failed(format!("{e:?}")),
495        };
496
497    // --- catch panics from evaluate ---
498    let response = match catch_unwind(AssertUnwindSafe(|| vm.process().evaluate::<AleoTestnetV0>(authorization))) {
499        Ok(Ok(resp)) => resp,
500        Ok(Err(e)) => return failed(format!("{e}")),
501        Err(e) => return failed(format!("{e:?}")),
502    };
503
504    let outputs = response.outputs();
505    let output = match outputs.len() {
506        0 => Value::make_unit(),
507        1 => outputs[0].clone().into(),
508        _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
509    };
510
511    EvaluationOutcome {
512        outcome: Outcome { program_name: case.program_name.clone(), function: case.function.clone(), output },
513        status: EvaluationStatus::Success,
514    }
515}
516
517/// Build a `Failed` outcome carrying `e` for the given case.
518fn failed_evaluation_outcome(case: &Case, e: String) -> EvaluationOutcome {
519    EvaluationOutcome {
520        outcome: Outcome {
521            program_name: case.program_name.clone(),
522            function: case.function.clone(),
523            output: Value::make_unit(),
524        },
525        status: EvaluationStatus::Failed(e),
526    }
527}
528
529/// Run the functions indicated by `cases` from the programs in `config`.
530pub fn run_with_ledger(config: &Config, case_sets: &[Vec<Case>]) -> Result<Vec<Vec<ExecutionOutcome>>> {
531    if case_sets.is_empty() {
532        return Ok(Vec::new());
533    }
534
535    // Initialize an rng.
536    let mut rng = ChaCha20Rng::seed_from_u64(config.seed);
537
538    // Initialize a genesis private key.
539    let genesis_private_key = PrivateKey::from_str(TEST_PRIVATE_KEY).unwrap();
540
541    // Store all of the non-genesis blocks created during set up.
542    let mut blocks = Vec::new();
543
544    // Load the genesis block.
545    let genesis_block =
546        Block::from_bytes_le(include_bytes!("resources/genesis_8d710d7e2_40val_snarkos_dev_network.bin"))?;
547
548    // Initialize a `Ledger`. This should always succeed.
549    // Use `new_test` to avoid spurious block-tree persistence errors on drop.
550    let ledger = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
551        genesis_block.clone(),
552        StorageMode::new_test(None),
553    )
554    .unwrap();
555
556    // Advance the `VM` to the start height, defaulting to the height for the latest consensus version.
557    let latest_consensus_version = ConsensusVersion::latest();
558    let start_height =
559        config.start_height.unwrap_or(CurrentNetwork::CONSENSUS_HEIGHT(latest_consensus_version).unwrap());
560    while ledger.latest_height() < start_height {
561        let block = ledger
562            .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![], &mut rng)
563            .map_err(|_| anyhow!("Failed to prepare advance to next beacon block"))?;
564        ledger.advance_to_next_block(&block).map_err(|_| anyhow!("Failed to advance to next block"))?;
565        blocks.push(block);
566    }
567
568    // Deploy each bytecode separately.
569    for Program { bytecode, name } in &config.programs {
570        // Parse the bytecode as an Aleo program.
571        // Note that this function checks that the bytecode is well-formed.
572        let aleo_program =
573            ProgramCore::from_str(bytecode).map_err(|e| anyhow!("Failed to parse bytecode of program {name}: {e}"))?;
574
575        let mut deploy = |edition: u16| -> Result<()> {
576            let deployment = if config.skip_proving {
577                deploy_without_proof(
578                    ledger.vm(),
579                    &genesis_private_key,
580                    &aleo_program,
581                    edition,
582                    latest_consensus_version,
583                    &mut rng,
584                )
585                .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
586            } else {
587                // Add the program to the ledger.
588                // Note that this function performs an additional validity check on the bytecode.
589                ledger
590                    .vm()
591                    .deploy(&genesis_private_key, &aleo_program, None, 0, None, &mut rng)
592                    .map_err(|e| anyhow!("Failed to deploy program {name}: {e}"))?
593            };
594            let block = ledger
595                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], vec![deployment], &mut rng)
596                .map_err(|e| anyhow!("Failed to prepare to advance block for program {name}: {e}"))?;
597            ledger
598                .advance_to_next_block(&block)
599                .map_err(|e| anyhow!("Failed to advance block for program {name}: {e}"))?;
600
601            // Check that the deployment transaction was accepted.
602            if block.transactions().num_accepted() != 1 {
603                return Err(anyhow!("Deployment transaction for program {name} not accepted.").into());
604            }
605
606            // Store the block.
607            blocks.push(block);
608
609            Ok(())
610        };
611
612        // Deploy the program.
613        deploy(0)?;
614        // If the program does not have a constructor, deploy it twice to satisfy the edition requirement.
615        if !aleo_program.contains_constructor() {
616            deploy(1)?;
617        }
618    }
619
620    // Initialize ledger instances for each case set.
621    let mut indexed_ledgers = vec![(0, ledger)];
622    indexed_ledgers.extend(
623        (1..case_sets.len())
624            .map(|i| {
625                // Initialize a `Ledger`. This should always succeed.
626                // Use `new_test` to avoid spurious block-tree persistence errors on drop.
627                let l = Ledger::<CurrentNetwork, ConsensusMemory<CurrentNetwork>>::load(
628                    genesis_block.clone(),
629                    StorageMode::new_test(None),
630                )
631                .expect("Failed to load copy of ledger");
632                // Add the setup blocks.
633                for block in blocks.iter() {
634                    l.advance_to_next_block(block).expect("Failed to add setup block to ledger");
635                }
636
637                (i, l)
638            })
639            .collect::<Vec<_>>(),
640    );
641
642    // For each of the case sets, run the cases sequentially.
643    let results = indexed_ledgers
644        .into_iter()
645        .map(|(index, ledger)| {
646            // Get the cases for this ledger.
647            let cases = &case_sets[index];
648            // Clone the RNG.
649            let mut rng = rng.clone();
650
651            // Fund each private key used in the test cases with 1M ALEO.
652            let skip_proving = config.skip_proving;
653            let transactions: Vec<Transaction<CurrentNetwork>> = cases
654                .iter()
655                .filter_map(|case| case.private_key.as_ref())
656                .map(|key| {
657                    // Parse the private key.
658                    let private_key =
659                        PrivateKey::<CurrentNetwork>::from_str(key).expect("Failed to parse private key.");
660                    // Convert the private key to an address.
661                    let address = Address::try_from(private_key).expect("Failed to convert private key to address.");
662                    // Generate the transaction.
663                    if skip_proving {
664                        let (tx, _) = execute_without_proof(
665                            ledger.vm(),
666                            &genesis_private_key,
667                            "credits.aleo",
668                            "transfer_public",
669                            [
670                                SvmValue::from_str(&format!("{address}")).expect("Failed to parse recipient address"),
671                                SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
672                            ]
673                            .iter(),
674                            latest_consensus_version,
675                            &mut rng,
676                        )
677                        .expect("Failed to generate funding transaction");
678                        tx
679                    } else {
680                        ledger
681                            .vm()
682                            .execute(
683                                &genesis_private_key,
684                                ("credits.aleo", "transfer_public"),
685                                [
686                                    SvmValue::from_str(&format!("{address}"))
687                                        .expect("Failed to parse recipient address"),
688                                    SvmValue::from_str("1_000_000_000_000u64").expect("Failed to parse amount"),
689                                ]
690                                .iter(),
691                                None,
692                                0u64,
693                                None,
694                                &mut rng,
695                            )
696                            .expect("Failed to generate funding transaction")
697                    }
698                })
699                .collect();
700
701            // Create a block with the funding transactions.
702            let block = ledger
703                .prepare_advance_to_next_beacon_block(&genesis_private_key, vec![], vec![], transactions, &mut rng)
704                .expect("Failed to prepare advance to next beacon block");
705            // Assert that no transactions were aborted or rejected.
706            assert!(block.aborted_transaction_ids().is_empty());
707            assert_eq!(block.transactions().num_rejected(), 0);
708            // Advance the ledger to the next block.
709            ledger.advance_to_next_block(&block).expect("Failed to advance to next block");
710
711            let mut case_outcomes = Vec::new();
712
713            for case in cases {
714                assert!(
715                    ledger.vm().contains_program(&ProgramID::from_str(&case.program_name).unwrap()),
716                    "Program {} should exist.",
717                    case.program_name
718                );
719
720                let private_key = case
721                    .private_key
722                    .as_ref()
723                    .map(|key| PrivateKey::from_str(key).expect("Failed to parse private key."))
724                    .unwrap_or(genesis_private_key);
725
726                let mut execution = None;
727                let mut verified = false;
728                let mut status = ExecutionStatus::None;
729                let mut abort_reason: Option<String> = None;
730
731                // Halts are handled by panics, so we need to catch them.
732                // I'm not thrilled about this usage of `AssertUnwindSafe`, but it seems to be
733                // used frequently in SnarkVM anyway.
734                let execute_output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
735                    if skip_proving {
736                        execute_without_proof(
737                            ledger.vm(),
738                            &private_key,
739                            &case.program_name,
740                            &case.function,
741                            case.input.iter(),
742                            latest_consensus_version,
743                            &mut rng,
744                        )
745                    } else {
746                        ledger
747                            .vm()
748                            .execute_with_response(
749                                &private_key,
750                                (&case.program_name, &case.function),
751                                case.input.iter(),
752                                None,
753                                0,
754                                None,
755                                &mut rng,
756                            )
757                            .map_err(anyhow::Error::from)
758                    }
759                }));
760
761                if let Err(payload) = execute_output {
762                    let s1 = payload.downcast_ref::<&str>().map(|s| s.to_string());
763                    let s2 = payload.downcast_ref::<String>().cloned();
764                    let s = s1.or(s2).unwrap_or_else(|| "Unknown panic payload".to_string());
765
766                    case_outcomes.push(ExecutionOutcome {
767                        outcome: Outcome {
768                            program_name: case.program_name.clone(),
769                            function: case.function.clone(),
770                            output: Value::make_unit(),
771                        },
772                        status: ExecutionStatus::Halted(s),
773                        verified: false,
774                        execution: "".to_string(),
775                    });
776
777                    continue;
778                }
779
780                let result = execute_output.unwrap().and_then(|(transaction, response)| {
781                    // Skip verification when proving is skipped — proofs are absent
782                    // and verification is meaningless.
783                    verified = skip_proving || ledger.vm().check_transaction(&transaction, None, &mut rng).is_ok();
784                    execution = Some(transaction.clone());
785                    let block = ledger
786                        .prepare_advance_to_next_beacon_block(&private_key, vec![], vec![], vec![transaction], &mut rng)
787                        .map_err(|e| anyhow::anyhow!("{e}"))?;
788                    status =
789                        match (block.aborted_transaction_ids().is_empty(), block.transactions().num_accepted() == 1) {
790                            (false, _) => {
791                                // Attempt check_transaction to diagnose abort reason.
792                                if let Some(ref tx) = execution
793                                    && let Err(e) = ledger.vm().check_transaction(tx, None, &mut rng)
794                                {
795                                    abort_reason = Some(format!("{e}"));
796                                }
797                                ExecutionStatus::Aborted(abort_reason.take())
798                            }
799                            (true, true) => ExecutionStatus::Accepted,
800                            (true, false) => ExecutionStatus::Rejected,
801                        };
802                    ledger.advance_to_next_block(&block)?;
803                    Ok(response)
804                });
805
806                let output = match result {
807                    Ok(response) => {
808                        let outputs = response.outputs();
809                        match outputs.len() {
810                            0 => Value::make_unit(),
811                            1 => outputs[0].clone().into(),
812                            _ => Value::make_tuple(outputs.iter().map(|x| x.clone().into())),
813                        }
814                    }
815                    Err(e) => Value::make_string(format!("Failed to extract output: {e}")),
816                };
817
818                // Extract the execution, removing the global state root and proof.
819                // This is necessary as they are not deterministic across runs, even with RNG fixed.
820                let execution = if let Some(Transaction::Execute(_, _, execution, _)) = execution {
821                    Some(Execution::from(execution.into_transitions(), Default::default(), None).unwrap())
822                } else {
823                    None
824                };
825
826                case_outcomes.push(ExecutionOutcome {
827                    outcome: Outcome {
828                        program_name: case.program_name.clone(),
829                        function: case.function.clone(),
830                        output,
831                    },
832                    status,
833                    verified,
834                    execution: serde_json::to_string_pretty(&execution).expect("Serialization failure"),
835                });
836            }
837
838            Ok((index, case_outcomes))
839        })
840        .collect::<Result<Vec<_>>>()?;
841
842    // Reorder results to match input order.
843    let mut ordered_results: Vec<Vec<ExecutionOutcome>> = vec![Default::default(); case_sets.len()];
844    for (index, outcomes) in results.into_iter() {
845        ordered_results[index] = outcomes;
846    }
847
848    Ok(ordered_results)
849}