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